I've been experiencing what initially appeared to be an intermittent issue where my application wouldn't work offline.
A few details regarding my application:
The entry point into my application is a Login page
All of the pages in my application, w/ the exception of the Login page, display dynamic data. And to ensure the pages that display dynamic data aren't cached, I chose to only have the Login page include the manifest attribute in it's html element.
The total size of the assets listed in my manifest file is roughly 1MB.
The steps I take to reproduce the issue (assume I do not have the applicationCache resources cached on my browser/device):
Navigate to the Login Page (applicationCache resources will begin downloading)
Immediately Login to the application
Go offline and request an offline resource
Notice the browser failed to serve the resource from applicationCache
While I do not have any concrete proof, what I ultimately discovered is navigating away from the Login page, while the browser is in the process of retrieving applicationCache assets, interrupts the download of appCache assets and leads to offline resources not being served up when offline. Is this expected browser behavior? If I wait a sufficient amount of time and give the browser a chance to download assets, offline functionality works.
In order to insure offline functionality, do I need to prevent the user from navigating away from the Login page until the applicationCache cache event is fired?
Is this expected browser behavior?
It is indeed intended behaviour. See http://diveintohtml5.info/offline.html#debugging
if even a single resource listed in your cache manifest file fails to download properly, the entire process of caching your offline web application will fail. Your browser will fire the error event, but there is no indication of what the actual problem was.
One solution I could think of would be to check on beforeunload if the the window.applicationCache.status is either checking or downloading.
Or you might be able to set a flag in the users localStorage, indicating that the last attempt was not successfull, using the error event (see below) and try to refetch the files until everything was loaded successfully.
If you've got lots of stuff to cache, you can show a progress bar and some text asking the user to be patient while the page is loading. For the progress bar you can use event.loaded and event.total in the progress event of your cache handling function.
var appCache = window.applicationCache;
// Fired after the first cache of the manifest.
appCache.addEventListener('cached', handleCacheEvent, false);
// Checking for an update. Always the first event fired in the sequence.
appCache.addEventListener('checking', handleCacheEvent, false);
// An update was found. The browser is fetching resources.
appCache.addEventListener('downloading', handleCacheEvent, false);
// The manifest returns 404 or 410, the download failed,
// or the manifest changed while the download was in progress.
appCache.addEventListener('error', handleCacheError, false);
// Fired after the first download of the manifest.
appCache.addEventListener('noupdate', handleCacheEvent, false);
// Fired if the manifest file returns a 404 or 410.
// This results in the application cache being deleted.
appCache.addEventListener('obsolete', handleCacheEvent, false);
// Fired for each resource listed in the manifest as it is being fetched.
appCache.addEventListener('progress', handleCacheEvent, false);
// Fired when the manifest resources have been newly redownloaded.
appCache.addEventListener('updateready', handleCacheEvent, false);
function handleCacheEvent(e){
if(e.type && (e.type=='progress' || e.type=='ProgressEvent')){
console.log('percent:', Math.round(e.loaded/e.total*100)+'%', 'total:', e.total, 'loaded:',e.loaded);
}
}
Related
I have read near 20 other posts about this particular error, but most seem to be issues with the code calling Response.Close or similar, which is not our case. I understand that this particular error means that typically a user browsed away from the web page or cancelled the request midway, but in our case we are getting this error without cancelling a request. I can observe the error just after a few seconds, the download just fails in the browser (both Chrome and IE, so it's not browser specific).
We have a web api controller that serves a file download.
[HttpGet]
public HttpResponseMessage Download()
{
//
// Enumerates a directory and returns a Read-only FileStream of the download
var stream = dataProvider.GetServerVersionAssemblyStream(configuration.DownloadDirectory, configuration.ServerVersion);
if (stream == null)
{
return new HttpResponseMessage(HttpStatusCode.NotFound);
}
var response = new HttpResponseMessage(HttpStatusCode.OK)
{
Content = new StreamContent(stream)
};
response.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment");
response.Content.Headers.ContentDisposition.FileName = $"{configuration.ServerVersion}.exe";
response.Content.Headers.ContentType = new MediaTypeHeaderValue(MediaTypeNames.Application.Octet);
response.Content.Headers.ContentLength = stream.Length;
return response;
}
Is there something incorrect we are doing in our Download method, or is there something we need to tweak in IIS?
This happens sporadically. I can't observe a pattern, it works sometimes and other times it fails repeatedly.
The file download is about 150MB
The download is initiated from a hyperlink on our web page, there is no special calling code
The download is over HTTPS (HTTP is disabled)
The Web Api is hosted on Azure
It doesn't appear to be timing out, it can happen just after a second or two, so it's not hitting the default 30 second timeout values
I also noticed I can't seem to initiate multiple file downloads from the server at once, which is concerning. This needs to be able to serve 150+ businesses and multiple simultaneous downloads, so I'm concerned there is something we need to tweak in IIS or the Web Api.
I was able to finally fix our problem. For us it turned out to be a combination of two things: 1) we had several memory leaks and CPU intensive code in our Web Api that was impacting concurrent downloads, and 2) we ultimately resolved the issue by changing MinBytesPerSecond (see: https://blogs.msdn.microsoft.com/benjaminperkins/2013/02/01/its-not-iis/) to a lower value, or 0 to disable. We have not had an issue since.
After testing the web app with Lighthouse I have such error:
User will not be prompted to Install the Web App Browsers can
proactively prompt users to add your app to their homescreen, which
can lead to higher engagement. Learn more.
Failures: Service worker
does not successfully serve the manifest's start_url.
All the criteria described here are satisfied:
The site is served over HTTPS.
A service worker is registered.
The scope of the service worker includes the page you audited and the page specified in the start_url property of the web app manifest.
A web app manifest exists and meets the following criteria: Has a
valid name property. Has a valid short_name property. Has a valid start_url property. Has a valid display property and the value is
standalone, fullscreen, or minimal-ui. Specifies an icon that is at
least 192px by 192px.
The manifest files is rendered via script. Important variables are
scope_url = 'https://website.com/app/'
start_url = 'https://website.com/app/about/'
ServiceWoker.js is quite simple:
self.addEventListener('push', function(e) {
...
});
self.addEventListener('notificationclick', function (e) {
...
);
Web App and sw.js are served from start_url.
What else can I check?
Edit 1. When I try to "Add to homescreen" from Chrome console I get:
Site cannot be installed: the page does not work offline
what is close to this comment
After changing start_url to 'https://website.com/app/'
and adding:
self.addEventListener('fetch', function(e){
});
to serviceWorker.js the problem has been solved.
It seems that a recent version of chrome-devtools-lighthouse verifies that the your start_url is served successfully (some content and response code 200 is returned) even while the device is offline.
What is the exact difference between oncached and onupdateready methods in AppCache API? It seems like both get fired when the manifest items are downloaded completely. If that's the case, which event get fired first?
The oncached event gets fired when the cache is downloaded for the first time.
The onupdateready event gets fired when the previously cached resources have been updated, typically when an update to the manifest file is detected. This does not automatically update the cache, it just downloads the new resources. The swapCache method should be called to switch the old cache with the newly downloaded one.
From this link (emphasis mine):
window.applicationCache.oncached - This eventhandler is called, after the downloading eventhandler is called, when all the resources in the manifest file have been downloaded. No more eventhandlers are called after this eventhandler.
window.applicationCache.onupdateready - This eventhandler is called, after the downloading eventhandler is called, when there has been an updated to an existing Application Cache and all the resources in the manifest file have been downloaded. No more eventhandlers are called after this eventhandler.
Event listeners for each of these events can be added as explained here.
appCache.addEventListener('cached', function(e) {
// Cache event code
}, false);
appCache.addEventListener('updateready', function (e) {
appCache.swapCache();
window.location.reload();
}, false);
I have a chrome extension which monitors the browser in a special way, sending some data to a web-server. In the current configuration this is the localhost. So the content script contains a code like this:
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function(data)...
xhr.open('GET', url, true);
xhr.send();
where url parameter is 'http://localhost/ctrl?params' (or http://127.0.0.1/ctrl?params - it doesn't matter).
Manifest-file contains all necessary permissions for cross-site requests.
The extension works fine on most sites, but on one site I get the error:
XMLHttpRequest cannot load http://localhost/ctrl?params. Origin http://www.thissite.com is not allowed by Access-Control-Allow-Origin.
I've tried several permissions which are proposed here (*://*/*, http://*/*, and <all_urls>), but no one helped to solve the problem.
So, the question is what can be wrong with this specific site (apparently there may be another sites with similar misbehaviour, and I'd like to know the nature of this), and how to fix the error?
(tl;dr: see two possible workarounds at the end of the answer)
This is the series of events that happens, which leads to the behavior that you see:
http://www.wix.com/ begins to load
It has a <script> tag that asynchronously loads the Facebook Connect script:
(function() {
var e = document.createElement('script');
e.type = 'text/javascript';
e.src = document.location.protocol +
'//connect.facebook.net/en_US/all.js';
e.async = true;
document.getElementById('fb-root').appendChild(e);
}());
Once the HTML (but not resources, including the Facebook Connect script) of the wix.com page loads, the DOMContentLoaded event fires. Since your content script uses "run_at" : "document_end", it gets injected and run at this time.
Your content script runs the following code (as best as I can tell, it wants to do the bulk of its work after the load event fires):
window.onload = function() {
// code that eventually does the cross-origin XMLHttpRequest
};
The Facebook Connect script loads, and it has its own load event handler, which it adds with this snippet:
(function() {
var oldonload=window.onload;
window.onload=function(){
// Run new onload code
if(oldonload) {
if(typeof oldonload=='string') {
eval(oldonload);
} else {
oldonload();
}
}
};
})();
(this is the first key part) Since your script set the onload property, oldonload is your script's load handler.
Eventually, all resources are loaded, and the load event handler fires.
Facebook Connect's load handler is run, which run its own code, and then invokes oldonload. (this is the second key part) Since the page is invoking your load handler, it's not running it in your script's isolated world, but in the page's "main world". Only the script's isolated world has cross-origin XMLHttpRequest access, so the request fails.
To see a simplified test case of this, see this page (which mimics http://www.wix.com), which loads this script (which mimics Facebook Connect). I've also put up simplified versions of the content script and extension manifest.
The fact that your load handler ends up running in the "main world" is most likely a manifestation of Chrome bug 87520 (the bug has security implications, so you might not be able to see it).
There are two ways to work around this:
Instead of using "run_at" : "document_end" and a load event handler, you can use the default running time (document_idle, after the document loads) and just have your code run inline.
Instead of adding your load event handler by setting the window.onload property, use window.addEventListener('load', func). That way your event handler will not be visible to the Facebook Connect, so it'll get run in the content script's isolated world.
The access control origin issue you're seeing is likely manifest in the headers for the response (out of your control), rather than the request (under your control).
Access-Control-Allow-Origin is a policy for CORS, set in the header. Using PHP, for example, you use a set of headers like the following to enable CORS:
header('Access-Control-Allow-Origin: http://blah.com');
header('Access-Control-Allow-Credentials: true' );
header('Access-Control-Allow-Headers: Content-Type, Content-Disposition, attachment');
If sounds like that if the server is setting a specific origin in this header, then your Chrome extension is following the directive to allow cross-domain (POST?) requests from only that domain.
I've created a website using HTML 5 offline Application Cache and it works well in most cases, but for some users it fails. In Chrome, when the application is being cached, the progress is displayed for each file and also error messages if something goes wrong, like:
Application Cache Checking event
Application Cache Downloading event
...
Application Cache Progress event (7 of 521) http://localhost/HTML5App/js/main.js
...
Application Cache Error event: Failed to commit new cache to storage, would exceed quota.
I've added event listeners to window.applicationCache (error, noupdate, obsolete, etc.), but there is no information stored on the nature of the error.
Is there a way to access this information from the web site using JavaScript ?
I would like to determine somehow which file caused the error or what kind of error occurred.
I believe that the spec doesn't mention that the exact cause of the exception should be included in the error. Currently the console is your only friend.
To wit, your current error "exceed quota" is due to the fact that Chrome currently limits the storage to 5MB. You can work around this by creating an app package that requests unlimited_Storage via the permission model. See http://code.google.com/chrome/apps/docs/developers_guide.html#live for more details.
If you want specific error messages on the "onerror" handler raise a bug on http://crbug.com/new