readium-js-viewer icon indicating copy to clipboard operation
readium-js-viewer copied to clipboard

cloud reader, iOS Chrome browser, EPUB fails to load (okay in Safari)

Open danielweck opened this issue 10 years ago • 24 comments

test URL: http://readium-cloudreader.divshot.io/?epub=epub_content%2Faccessible_epub_3

danielweck avatar Jun 02 '15 07:06 danielweck

A similar / related issue? https://github.com/readium/readium-js-viewer/issues/351

danielweck avatar Jun 02 '15 10:06 danielweck

Any ideas how this issue can be moved forward? I am keenly interested in getting readium-js-viewer functional in the Facebook embedded browser for iOS. Let me know if some experiments might aid in a diagnosis, maybe I can gather some information.

davedmiller avatar Jul 06 '15 23:07 davedmiller

@davedmiller in the description of your other issue (#351) you claim that the Readium cloud reader works fine in Chrome for iOS. Yet this fails in my tests (thus this issue). Could you please check at your end, and report which version of iOS you are using? Many thanks!

danielweck avatar Jul 07 '15 10:07 danielweck

While I am sure that I had Readium working in iOS Chrome at one point, I am unable to confirm that today. I too am seeing a failure when I attempt to load Readium in iOS Chrome. What I see in that instance is very similar to what I see when loading Readium-JS-Viewer in the Facebook app embedded browser, which is an infinite spinner.

davedmiller avatar Jul 07 '15 13:07 davedmiller

Which version of iOS? 7 or 8?

rkwright avatar Jul 07 '15 16:07 rkwright

I have experienced the same issue using the readium-shared-js code in our custom built wrapper. The issue occurs on iOS 7/8 with the latest version of Chrome from the app store.

I also used a UIWebView test project (https://github.com/paulirish/iOS-WebView-App) and the UIWebView was able to load the reader in iOS 7/8, so it seems to be Chrome specific.

matwood avatar Jul 07 '15 19:07 matwood

All of my testing has been on iOS 8.

davedmiller avatar Aug 06 '15 23:08 davedmiller

I have been working on this issue and #351 & #352 for some time now. Issue #112 seems to be related.

In the file iframe_zip_loader.js at line #19 I have added this:

isIE = true;

Resulting in:

... define(['URIjs', 'readium_shared_js/views/iframe_loader', 'underscore', './discover_content_type'], function(URI, IFrameLoader, _, ContentTypeDiscovery){

var zipIframeLoader = function( getCurrentResourceFetcher, contentDocumentTextPreprocessor) {

    var isIE = (window.navigator.userAgent.indexOf("Trident") > 0 || window.navigator.userAgent.indexOf("Edge") > 0);
    isIE = true;

    var basicIframeLoader = new IFrameLoader();

    var self = this;

    var _contentDocumentTextPreprocessor = contentDocumentTextPreprocessor;

    this.addIFrameEventListener = function (eventName, callback, context) {
        basicIframeLoader.addIFrameEventListener(eventName, callback, context);
    };

...

I have not completed all of my testing yet, but this change has made it so the following environments fully load my epubs, the infinitely spinning spinner is gone.. The environments include:

  • Facebook embedded browser on Kindle Fire 7 OS version 4.5.5 (KF7FB),
  • Facebook embedded browser on Android 5.1.1 Nexus 9 (N9FB),
  • chrome on ios 8.4.1 (iPhoneChrome), and
  • Facebook embedded browser on ios 8.4.1 (iPhoneFB).

Actually the iPhoneFB environment was "sort of kind of" working before, but was clearly having problems with layout etc. The change I described above has cured those issues too.

Now I could use a little help. I am not entirely sure what string to look for in the userAgent for these environments and devices. I include the userAgents I have observed here:

On KF7FB UserAgent:Mozilla/5.0 (Linux; Android 4.4.3; KFASWI Build/KTU84M) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/34.0.0.0 Mobile Safari/537.36 [FB_IAB/FB4A;FBAV/35.0.0.48.273;]"

On N9FB UserAgent:Mozilla/5.0 (Linux; Android 5.1.1; Nexus 9 Build/LMY48I; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/44.0.2403.117 Safari/537.36 [FB_IAB/FB4A;FBAV/41.0.0.25.131;]"

On iPhoneChrome UserAgent:Mozilla/5.0 (iPhone; CPU iPhone OS 8_4_1 like Mac OS X) AppleWebKit/600.1.4 (KHTML, like Gecko) CriOS/44.0.2403.67 Mobile/12H321 Safari/600.1.4"

On iPhoneFB User-Agent: Mozilla/5.0 (iPhone; CPU iPhone OS 8_4_1 like Mac OS X) AppleWebKit/600.1.4 (KHTML, like Gecko) Mobile/12H321 [FBAN/FBIOS;FBAV/37.0.0.21.273;FBBV/13822349;FBDV/iPhone7,1;FBMD/iPhone;FBSN/iPhone OS;FBSV/8.4.1;FBSS/3; FBCR/AT&T;FBID/phone;FBLC/en_US;FBOP/5]

It would be helpful if someone smarter than I, could review these useragents and suggest some detection code to set isIE true for these environments.

Thanks,

Dave

davedmiller avatar Aug 18 '15 23:08 davedmiller

@davedmiller Good find on where the problem is located. I looked over the source and the isIE is only used in 4 places. Instead of trying to determine isIE which is problematic for many reasons you listed, would it make sense instead to determine if the function needed is available? IE 10 for example added Blob support and revokeObjectUrl().

https://developer.mozilla.org/en-US/docs/Web/API/URL/revokeObjectURL https://developer.mozilla.org/en-US/docs/Web/API/Blob

See here for example of checking if the standard/preferred way is available and falling back if not:

https://github.com/bibliolabs/readium-shared-js/blob/master/js/views/cfi_navigation_logic.js#L698

Setting it up this way futures proofs the code when the preferred/standard is finally implemented across all browsers. Admittedly this will not always work when there are browser specific implementation bugs, but those can happen with any browser.

matwood avatar Aug 19 '15 13:08 matwood

@davedmiller thank you for the detailed analysis. Please bear in mind that the document.open/write/close() method breaks XHTML (XML) namespaces and CSS selectors such as those including epub:type (because only the text/html content type is supported). Conversely, the Blob-URI method supports application/xml+html documents, which is why we use it everywhere possible. The isIE=true trick seems like a good idea at first glance, but a better long-term solution would be to figure-out why Blob-URI does not work on MS Trident and Edge.

https://github.com/readium/readium-js/blob/develop/js/epub-fetch/iframe_zip_loader.js#L100

if (!isIE) {
                var contentType = 'text/html';
                if (attachedData.spineItem.media_type && attachedData.spineItem.media_type.length) {
                    contentType = attachedData.spineItem.media_type;
                }

                var documentDataUri = window.URL.createObjectURL(
                    new Blob([contentDocumentData], {'type': contentType})
                );
            } else {

                iframe.contentWindow.document.open();

                    iframe.contentWindow.document.write(contentDocumentData);
            }

danielweck avatar Aug 19 '15 15:08 danielweck

@davedmiller @matwood there's a good discussion thread on document.open/write/close() vs. Blob-URI here: https://github.com/readium/readium-js/issues/86#issuecomment-65820675 (includes a test EPUB file)

danielweck avatar Aug 19 '15 15:08 danielweck

@danielweck I did not mean to imply that one should always set isIE to true. As a 'temporary' fix for the mobile FB embedded browser and Chrome on iOS only. Without such a change, the current user experience is that the pages never load, and the user cannot read the content. With this change, albeit with some parsing errors, the user experience is that the pages load and the user can read the books.

Long term, I think you are right, there needs to be a better solution. I will endeavor to find a better solution, time permitting. Maybe we should be contacting Google, Microsoft, and Facebook to get changes made to their browsers.

In the meantime, I need to improve the current user experience and will put in a short term fix for my companies use.

davedmiller avatar Aug 20 '15 14:08 davedmiller

@davedmiller great! :)

danielweck avatar Aug 20 '15 15:08 danielweck

Just a comment: While there is nothing wrong with trying to convince the browser-makers to make changes, the sad fact is that their agenda is pretty different from ours and, even if they want to, their timelines and feature-lists are heavily constrained. So for us, it is not a viable strategy - at least in the short term.

rkwright avatar Aug 20 '15 15:08 rkwright

I'm not sure the exact cause yet, but the underlying issue is that the UIWebview sometimes does not call the onload event after the iframe.setAttribute("src", documentDataUri);. I am having the same problem while integrating the viewer into a Cordova iOS app. It looks like the iframe fails to load the Blob and continues on like nothing happened. The iframe also does not contain the blob data after the above call which supports my theory that it fails. Currently I believe it may be related to this bug:

http://stackoverflow.com/questions/23355179/safari-doesnt-call-iframe-onload-when-src-is-not-valid-site

The odd thing is that my test basic UIWebview app works, and taking the isIE path works in chrome and cordova. The main difference I have seen so far is the baseURI of the iFrame is a file:/// in the cordova app instead of http:// in the UIWebview app. The good news is that I believe we have finally found the exact issue, and now just need to figure out a fix/work around.

matwood avatar Aug 26 '15 14:08 matwood

Thanks @matwood As a potential solution, I like the idea of polling for the readyState, using a _.once() callback (or similar) to ensure the onload logic is not invoked more than once. Something like:

    var doc = iframe.contentDocument || iframe.contentWindow;
    if (doc.document) doc = doc.document;
    var _timer = setInterval(function() {
        if (doc.readyState == 'complete') {
            clearInterval(_timer);
            ONCE_ONLOAD_CALLBACK();
        }
    }, 100);

What do you think?

danielweck avatar Aug 26 '15 15:08 danielweck

@danielweck I'm going to try that shortly, although I'm not sure it will work because when I debug the code it looks like setting the blob on the iframe silently fails. So while I think the issue is related it may not be the same thing.

At this point I also really want to know the exact trigger of the problem :)

matwood avatar Aug 26 '15 15:08 matwood

I believe this is caused by the same issue with the Blob URIs not being accepted by the iframe.

https://github.com/readium/readium-js/issues/72

matwood avatar Aug 26 '15 18:08 matwood

@danielweck I wanted to confirm that the solution with the timer does NOT work because the iframe is failing to load the blob. ~~I'm fairly confident the createObjectUrl is the culprit and is not something I can fix without having access to UIWebViews source code.~~ I'm going to look for work around.

I see the code comments and you explained above that using document.write breaks other functionality. Is this also true for using src or srcdoc?

matwood avatar Aug 27 '15 14:08 matwood

@matwood, I would strongly encourage you to look at the feature/WKWebView branch and look into using WKWebView instead of UIWebView. I am not sure why this feature branch has not been merged to develop and master.

BluefireMicah avatar Aug 27 '15 14:08 BluefireMicah

The problem with the WKWebView is that it will not fix Chrome on iOS or Cordova. Neither has moved to the WKWebView fully yet.

matwood avatar Aug 27 '15 15:08 matwood

As a final follow up to this issue I'm retracting my statement above about createObjectUrl being the culprit. After trying to make something work with src and srdoc I finally got an origin access error. In Cordova the baseURI of the iframe is a file:/// as is the documentDataUri. I believe these are causing browser security issues [1]. Hopefully my research here helps someone else fix the issue if possible, but for now I'm going to detect Cordova[2] and Chrome iOS[3] and follow the IE path.

[1] The UIWebView works fine loading the same code from a website. It only fails when loading the code from the devices file system. [2] iframe.baseURI.match('file:///') [3] window.navigator.userAgent.match('CriOS')

matwood avatar Aug 27 '15 21:08 matwood

I am experiencing the same issue as describe above, I believe. Using the most recent chrome browser on an IPad (IoS 9.3.5), a hybrid app using readium-js-viewer master branch, the iframe.onload() function never executes. If I change 'isIE' to true, then it does.

kimtuck avatar Oct 25 '16 19:10 kimtuck

Please try the following fix from this Pull Request: https://github.com/readium/readium-js/pull/73 (in fact, to make things simpler in your tests, just force var isIE = true;, and check the results)

danielweck avatar Jul 13 '17 19:07 danielweck