Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Unable to open default pdf in file:/// protocol when code embedded in qrc protocol (Qt) #5057

Closed
jcourtois86 opened this issue Jul 18, 2014 · 22 comments · Fixed by #5100
Closed

Comments

@jcourtois86
Copy link

Hi there,

I'm trying to embeed the last version of PDF.js viewer in my custom application using Qt webkit. I added the build version of PDF.js to my qrc file, and succeed to load the viewer.html.

The issue I have is when loading the pdf file which is using "file" scheme, it complains about cross origin problem:

XMLHttpRequest cannot load file:///H:/Downloads/8R23987019162_18.pdf. Cross origin requests are only supported for HTTP.

If I do load the PDF.js viewer.html from my file system and not from the resources (qrc), it works fine. Of course for my customer, it's necessary to embeed to the PDF viewer in my executable.

According to the Qt webkit documentation (http://qt-project.org/doc/qt-5/qwebsecurityorigin.html) it should simply works : "By default local schemes like file:// and qrc:// are concidered to be in the same security origin, and can access each other's resources. ". It was also working on Qt4 with an older version of PDF.js (that's why I'm updating it).

I tried to add some addAccessWhitelistEntry() without any success.

Here is my constructor that inherits from QWebView :

    page()->settings()->setAttribute(QWebSettings::DeveloperExtrasEnabled, true);
    page()->settings()->setAttribute(QWebSettings::AcceleratedCompositingEnabled, true);
    page()->settings()->setAttribute(QWebSettings::LocalContentCanAccessRemoteUrls, true);
    page()->settings()->setAttribute(QWebSettings::LocalContentCanAccessFileUrls, true);
    page()->settings()->setAttribute(QWebSettings::JavascriptEnabled, true);

    // Object that contains path
    page()->mainFrame()->addToJavaScriptWindowObject("customPdfJsObject",m_loader,QWebFrame::QtOwnership);

    page()->mainFrame()->load( QUrl("qrc:/pdf.js/web/viewer.html"));

The only change I made to viewer .js is :

var DEFAULT_URL = customPdfJsObject.myPath;

What is also strange is that if I use the Open Menu inside the viewer and select the same file on disk, it open correctly.

Thanks for your help.

@jcourtois86
Copy link
Author

I now use the url parameter directly but still doesn't work...

page()->mainFrame()->load(QUrl("qrc:/pdf.js/web/viewer.html?file=" + path));

What will be the difference between a file open with the URL and using the open button and file chooser (where it works)?

@timvandermeij
Copy link
Contributor

AFAIK both use PDFView.open(file, 0);, so there should be no difference. I know nothing about the QRC protocol, but since it complains about cross origin requests, might it be possible to disable that check somehow?

@jcourtois86
Copy link
Author

After investigating a bit more, I'm not sure it's related to QRC protocol.

If I open viewser.html on my Windows 7 OS, it use my default browser (Google Chrome) and is not able to load the relative default document:

PDF.js v1.0.473 (build: 1694cd8)
Message: Unexpected server response (0) while retrieving PDF "file:///C:/Projets/DematStore/src/plugin/basicui/pdf.js/web/compressed.tracemonkey-pldi-09.pdf".

I have indeed similar errors in the console

XMLHttpRequest cannot load file:///C:/Projets/DematStore/src/plugin/basicui/pdf.js/web/compressed.tracemonkey-pldi-09.pdf. Cross origin requests are only supported for HTTP. viewer.html:1
Uncaught TypeError: Cannot read property 'xhr' of undefined 

Then if I use the open button, and fetch the same file from File Open Directory dialog, it load properly from the same origin 'file://'.

I would also like to disable the check but can it be done in PDF.js or is it a browser setting?

@timvandermeij
Copy link
Contributor

Hm, strange. We should look into this more. I'm also cc'ing @yurydelendik, perhaps he knows more about what can cause this.

@jcourtois86
Copy link
Author

Thanks for your help.

I checked the function pdfViewOpen(url, ...) and the url is different from default opening and using open button.

On Google Chrome:
Default: compressed.tracemonkey-pldi-09.pdf
Open Button: blob:null/713e5690-ed36-454e-8733-ae49394899c7

With QtWebkit and qrc protocol
Default: file:///H:/Downloads/LISEZ-MOI_8.pdf
Open Button: blob:qrc:///53e62ab0-0478-4dbc-9f74-2839e52506a6

The link was somehow converted from local file to qrc protocol using blob (?) which is why it works the second time. Any idea what is doing this?

@jcourtois86
Copy link
Author

I did more testing, I have the problem with Google Chrome on both Windows and Mac (Version 36.0.1985.125).

But It's fine on Firefox 31 and Safari 6.1.5, when I open viewer.html, it open the default document just fine.

In addition in Chrome I have this error, that I don't have with QtWebkit. But this is probably another issue: Uncaught TypeError: Cannot read property 'xhr' of undefined

@timvandermeij
Copy link
Contributor

/cc @Rob--W

Any idea what may be the issue with Chrome here?

@jcourtois86
Copy link
Author

@timvandermeij Is there a way to use blob to transform the url when using ?file= like the open button does?

@yurydelendik
Copy link
Contributor

Is there a way to use blob to transform the url when using ?file= like the open button does?

Yes, see http://jsbin.com/heqaqeya/1/edit?js (wfm in Firefox)

@yurydelendik
Copy link
Contributor

Actually, maybe. Crossorigin restrictions apply in this case as well http://jsbin.com/husamoji/1/edit

@jcourtois86
Copy link
Author

@yurydelendik Indeed I obtain: XMLHttpRequest cannot load blob:http://run.jsbin.com/c292c5b1-5aee-4783-98ed-412dcb408cad. Cross origin requests are only supported for HTTP.

@Rob--W
Copy link
Member

Rob--W commented Jul 29, 2014

  • If you open a PDF via ?file=, then the contents of that file is fetched via XMLHttpRequest.
  • If you select a file with the Open button, then the contents of the selected file is read with the FileReader API.

The second method always works, because access to the file content in that way is not restricted. The first method could fail if PDF.js does not get cross-origin / file:-access permissions.

In the Chrome extension, file:-access is disabled by default for security reasons. Visit chrome://extensions/?id=oemmndcbldboiebfnladdacbdfmadadm and put a check before "Allow access to file URLs" as suggested at https://chrome.google.com/webstore/detail/pdf-viewer/oemmndcbldboiebfnladdacbdfmadadm.

If you use PDF.js at the file:// scheme in Chrome, then you need to pass the --allow-file-access-from-files flag to the command. The equivalent in QtWebkit seems to be QWebSettings::LocalContentCanAccessFileUrls.

If you provide the source code of a minimal example that uses PDF.js to embed a PDF file in a Qt application, then I can take a look at your issue in the evening. If you prefer to keep the source private, just mail it to me at [email protected]. If it is Windows-specific project, make sure that it is compatible with VS 2010.

@jcourtois86
Copy link
Author

@Rob--W Thank you for your explanations.

I created a small Qt project with just one widget being my PDFViewer (derived from QWebView). It's very basic Qt so it shouldn't be any problem with MSVC 2010. You can right click in the webview and click "Inspect" to see console. You will need to change the QUrl in main.cpp to match a file in your disk.

http://jcourtois.fr/files/PDFViewer_Issue5057.zip

I personnaly use Qt 5.3.1, just be sure to use Qt 5 as there was no issue in Qt 4 at the time. I embedded v1.0.473 in qrc file.

If you have any question, please ask. Thanks again.

@Rob--W
Copy link
Member

Rob--W commented Jul 29, 2014

Great, no need for Windows, I can just compile it on my Linux box with qmake :)

The PDF fails to load because QtWebKit does apparently not support cross-origin XMLHttpRequest in Web Workers. This can be "fixed" by appending #disableWorker=true to the URL. This work-around is a suboptimal solution, because all (CPU-intensive) work will be done on the main thread instead of a worker thread, causing the UI to not be as smooth as when Web workers are enabled.

I'll look into a way to gracefully fall back to fetching the resource in the main thread and passing the data to the web worker. With transferables, this operation should be relatively cheap. Even if transferable messages are not supported, then the extra memory copy operation is not worse than seeing no PDF at all.

#include "pdfjswidget.h"

#include <QtWebKitWidgets/QWebPage>
#include <QtWebKitWidgets/QWebFrame>

PdfJsWidget::PdfJsWidget(const QUrl& res, QWidget *parent) :
    QWebView(parent)
{
    const QString path = res.toString();

    setRenderHint(QPainter::Antialiasing);
    setRenderHint(QPainter::TextAntialiasing);
    setRenderHint(QPainter::SmoothPixmapTransform);
    setRenderHint(QPainter::HighQualityAntialiasing);

    QWebSettings* settings = QWebSettings::globalSettings();
    settings->setAttribute(QWebSettings::DeveloperExtrasEnabled, true);
    settings->setAttribute(QWebSettings::AcceleratedCompositingEnabled, true);
    settings->setAttribute(QWebSettings::LocalContentCanAccessRemoteUrls, true);
    settings->setAttribute(QWebSettings::LocalContentCanAccessFileUrls, true);
    settings->setAttribute(QWebSettings::LocalStorageEnabled, true); // Note: Added as well to allow PDF history to be kept.
    settings->setAttribute(QWebSettings::JavascriptEnabled, true);

    page()->mainFrame()->load(QUrl("qrc:/pdfjs/web/viewer.html?file=" + path + "#disableWorker=true"));
}

@jcourtois86
Copy link
Author

@Rob--W Thank you very much for the work around, even is not optimal.

I will keep me posted on the new issue you opened.

Cheers

@Rob--W
Copy link
Member

Rob--W commented Jul 30, 2014

@johnlamericain By the way, you should URL-encode the file before concatenating it with the URL. Otherwise files with percentages in the name may not open correctly. QUrl::toPercentEncoding seems to do the job: http://qt-project.org/doc/qt-5/qurl.html#toPercentEncoding

const QString path = QUrl::toPercentEncoding(res.toString());

@jcourtois86
Copy link
Author

@Rob--W Thanks I will add this as well.

@jcourtois86
Copy link
Author

@Rob--W Thanks, it works like a charm.

@sivashankararumugam
Copy link

Using Xamarin forms for UWP (windows phone 10)
I'm facing same issue in loadind the pdf file form local folder
eg: ms-appx-web:///Assets/pdfjs/web/viewer.html?file=C:\Users\username\Desktop\pdf-sample.pdf.

error : PDF.js v1.8.186 (build: 32e01cd)
Message: Missing PDF "file:///C:/Users/username/Desktop/pdf-sample.pdf".
Actually I need to display the pdf file that is downloaded in local folder.

Path is taken from "string filepath = ApplicationData.Current.LocalFolder.Path + "//Prajesh.pdf";

can anyone share your idea

Thanks in advance

@BoobalanK
Copy link

BoobalanK commented Oct 9, 2017

Hi @sivashankararumugam ,
Exaclty same situation here
Did you acheived that (loading pdf file from local folder)? Please can you share the idea.

Thanks in advance

@uap-universe
Copy link

@BoobalanK @sivashankararumugam this issue a bit old, but it seems to be still an open question. So here is my solution:

In the viewer.js there is a function webViewerOpenFileViaURL, that checks, if the file starts with file:// and then loads the content via XHR and uses the blob stuff from the open method to load the file.

You could make this function somehow public and directly call it, or you use the idea to write your own function like this:

	function openXHRBlob(file) {
		PDFViewerApplication.setTitleUsingUrl(file);
		var xhr = new XMLHttpRequest();
		xhr.onload = function () {
			PDFViewerApplication.open(new Uint8Array(xhr.response));
		};
		try {
			xhr.open('GET', file);
			xhr.responseType = 'arraybuffer';
			xhr.send();
		} catch (ex) {
			throw ex;
		}
	}

You can call this function from anywhere to open local PDFs without restrictions. Also tested under WebEngineView QML Item.

@fpibbs
Copy link

fpibbs commented Nov 3, 2018

@BoobalanK @sivashankararumugam this issue a bit old, but it seems to be still an open question. So here is my solution:

In the viewer.js there is a function webViewerOpenFileViaURL, that checks, if the file starts with file:// and then loads the content via XHR and uses the blob stuff from the open method to load the file.

You could make this function somehow public and directly call it, or you use the idea to write your own function like this:

	function openXHRBlob(file) {
		PDFViewerApplication.setTitleUsingUrl(file);
		var xhr = new XMLHttpRequest();
		xhr.onload = function () {
			PDFViewerApplication.open(new Uint8Array(xhr.response));
		};
		try {
			xhr.open('GET', file);
			xhr.responseType = 'arraybuffer';
			xhr.send();
		} catch (ex) {
			throw ex;
		}
	}

You can call this function from anywhere to open local PDFs without restrictions. Also tested under WebEngineView QML Item.

Any possibility to share your working configuration to understand correct syntax to load a (example: c:\test.pdf) from a local machine?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

8 participants