I'm currently working on a WordPress addition which loads full post content (normally it shows exceprts) when asked to. I did my code like this:
$(".readMore").click(function() {
var url = $(this).attr("href");
$(this).parent("p").parent("div").children("div.text").slideUp("slow", function () {
$(this).load(url + " .text", function(){
$(this).slideDown("slow");
});
});
$(this).parent("p").fadeOut();
return false; });
And it works. But I don't want images to be loaded. I tried .text:not(img), but it didn't worked. How can I do this?
The trick, of course, is preventing the images from being downloaded unnecessarily by the user's browser; not displaying them is easy.
I only have two browsers were it's easy and convenient to tell what's downloading: Chrome and Firefox+Firebug. In my tests, Martin's solution using *:not(img) results in the images being downloaded (although not displayed) in both Chrome and Firefox+Firebug. (I emphasize "Firefox+Firebug" because Firebug can change the behavior of Firefox on occasion, and so it may well be changing its behavior here, although I don't think it is; more on that below.)
It took some tweaking, but this seems to do the trick (more on testing below):
$.ajax({
url: url,
success: function(data) {
var div = $("<div>").html(data);
if (stripImages) {
// Find the images, remove them, and explicitly
// clear the `src` property from each of them
div.find("img").remove().each(function() {
this.src = "";
});
}
$(targetSelector).append(div.children());
},
error: function(jxhr, status, err) {
display("ajax error, status = " + status + ", err = " + err);
}
});
Live example The "Include big image" checkbox includes a large file from NASA's Astronomy Picture of the Day (APOD).
The key there was setting the src of the img elements to "". On Chrome, just removing the elements was enough to prevent Chrome starting the download of the images, but on Firefox+Firebug it not only started downloading them, but continued even when the download took considerable time. Clearing the src causes Firefox to abort the download (I can see this in the Firebug Net console).
So what about IE? Or Firefox without Firebug? I only did unscientific testing of those, but it's promising: If I run my live example of Martin's solution on either IE or Firefox without Firebug in a VM, I see the VM's network interface working hard, suggesting that it's downloading that big APOD picture. In contrast, if I run my solution above in that same environment (with caches cleared, etc., etc.), I don't see the VM network interface doing that work, suggesting that the download is either not being started or is being aborted early on.
.text *:not(img) will select every descendant from .text that is not an image, so in theory it should work.
Related
I have this problem where in firefox the speech gets cut off if the page is auto-refreshed, but in google chrome it finishes saying the speech even if the page is auto-refreshed. How do I fix it so that the speech doesn't get cut off in firefox even when the page is auto-refreshed?
msg = new SpeechSynthesisUtterance("please finish saying this entire sentence.");
window.speechSynthesis.speak(msg);
(function ($) {
'use strict';
if (window == window.top) {
var body = $('body').empty();
var myframe = $('<iframe>')
.attr({ src: location.href })
.css({ height: '95vh', width: '100%' })
.appendTo(body)
.on('load', function () {
var interval;
interval = 750;
setTimeout(function () {
myframe.attr({ src: location.href });
}, interval);
});
}
})(jQuery);
I have this problem where in firefox the speech gets cut off if the
page is auto-refreshed, but in google chrome it finishes saying the
speech even if the page is auto-refreshed.
The described behaviour for Firefox is a sane expected implementation.
Browsing the source code of Firefox and Chromium the implementation of speechSynthesis.speak() is based on a socket connection with the local speech server. That server at *nix is usually speech-dispatcher or speechd (speech-dispatcher). See How to programmatically send a unix socket command to a system server autospawned by browser or convert JavaScript to C++ souce code for Chromium? for description of trying to implement SSML parsing at Chromium.
Eventually decided to write own code to achieve that requirement using JavaScript according to the W3C specification SpeechSynthesisSSMLParser after asking more than one question at SE sites, filing issues and bugs and posting on W3C mailings lists without any evidence that SSML parsing would ever be included as part of the Web Speech API.
Once that connection is initiated a queue is created for calls to .speak(). Even when the connection is closed Task Manager might still show the active process registered by the service.
The process at Chromium/Chrome is not without bugs, the closest that have filed to what is being described at the question is
Issue 797624: "speak speak slash" is audio output of .speak() following two calls to .speak(), .pause() and .resume()
Why hasn't Issue 88072 and Issue 795371 been answered? Are Internals>SpeechSynthesis and Blink>Speech dead? (for possible reason why "but in google chrome it finishes saying the speech even if the page is auto-refreshed." is still possible at Chrome)
.volume property issues
Issue 797512: Setting SpeechSynthesisUtterance.volume does not change volume of audio output of speechSynthesis.speak() (Chromium/Chrome)
Bug 1426978 Setting SpeechSynthesisUtterance.volume does not change volume of audio output of speechSynthesis.speak() (Firefox)
The most egregious issue being Chromium/Chrome webkitSpeechReconition implementation which records the users' audio and posts that audio data to a remote service, where a transcript is returned to the browser - without explicitly notifying the user that is taking place, marked WONT FIX
Issue 816095: Does webkitSpeechRecognition send recorded audio to a remote web service by default?
Relevant W3C Speech API issues at GitHub
The UA should be able to disallow speak() from autoplaying #27
Precisely define when speak() should fail due to autoplay rules #35 (ironically, relevant to the reported behaviour at Chromium/Chrome and output described at this question, see Web Audio, Autoplay Policy and Games and Autoplay Policy Changes)
Intent to Deprecate: speechSynthesis.speak without user activation
Summary
The SpeechSynthesis API is actively being abused on the web. We don’t have hard data on abuse, but since other autoplay avenues are
starting to be closed, abuse is anecdotally moving to the Web Speech
API, which doesn't follow autoplay rules.
After deprecation, the plan is to cause speechSynthesis.speak to
immediately fire an error if specific autoplay rules are not
satisfied. This will align it with other audio APIs in Chrome.
Timing of SpeechSynthesis state changes not defined #39
Timing of SpeechSynthesisUtterance events firing not defined #40
Clarify what happens if two windows try to speak #47
In summary, would not describe the behaviour at Firefox as a "problem", but the behaviour at Chrome as being a potential "problem".
Diving in to W3C Web Speech API implementation at browsers is not a trivial task. For several reasons. Including the apparent focus, or available option of, commercial TTS/SST services and proprietary, closed-source implementations of speech synthesis and speech recognition in "smart phones"; in lieu of fixing the various issues with the actual deployment of the W3C Web Speech API at modern browsers.
The maintainers of speechd (speech-dispatcher) are very helpful with regards to the server side (local speech-dispatcher socket).
Cannot speak for Firefox maintainers. Would estimate it is unlikely that if a bug is filed relevant to the feature request of continuing execution of audio output by .speak() from reloaded window is consistent with recent autoplay policies implemented by browsers. Though you can still file a Firefox bug to ask if audio output (from any API or interface) is expected to continue during reload of the current window; and if there are any preferences or policies which can be set to override the described behaviour, as suggested at the answer by #zip. And get the answer from the implementers themselves.
There are individuals and groups that compose FOSS code which are active in the domain and willing to help SST/TTS development, many of which are active at GitHub, which is another option to ask questions about how to implement what you are trying to achieve specifically at Firefox browser.
Outside of asking implementers for the feature request, you can read the source code and try create one or more workarounds. Alternatives include using meSpeak.js, though that still does not necessarily address if Firefox is intentionally blocking audio output during reload of the window.
Not sure why there's a difference in behavior... guest271314 might be on to something in his answer. However, you may be able to prevent FF from stopping the tts by intercepting the reload event with a onbeforeunload handler and waiting for the utterance to finish:
msg = new SpeechSynthesisUtterance("say something");
window.speechSynthesis.speak(msg);
window.onbeforeunload = function(e) {
if(window.speechSynthesis.speaking){
event.preventDefault();
msg.addEventListener('end', function(event) {
//logic to continue unload here
});
}
};
EDITED: See more elegant solution with promises below initial answer!
Below snippet is a workaround to the browser inconsistencies found in Firefox, checking synth.speaking in the interval and only triggering a reload if it's false prevents the synth from cutting of prematurely:
(It does not NOT work properly in the SO snippet, I assume it doesn't like iFrames in iFrames or whatever, just copy paste the code in a file and open it with Firefox!)
<p>I'm in the body, but will be in an iFrame</p>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script>
var synth = window.speechSynthesis;
msg = new SpeechSynthesisUtterance("please finish saying this entire sentence.");
synth.speak(msg);
(function ($) {
'use strict';
if (window == window.top) {
var body = $('body').empty();
var myframe = $('<iframe>')
.attr({ src: location.href })
.css({ height: '95vh', width: '100%' })
.appendTo(body)
.on('load', function () {
var interval;
interval = setInterval(function () {
if (!synth.speaking) {
myframe.attr({ src: location.href });
clearInterval(interval);
}
}, 750);
});
}
})(jQuery);
</script>
A more elaborate solution could be to not have any setTimeout() or setInterval() at all, but use promises instead. Like this the page will simply reload whenever the message is done synthesizing, no matter how short or long it is. This will also prevent the "double"/overlapping-speech on the initial pageload. Not sure if this helps in your scenario, but here you go:
<button id="toggleSpeech">Stop Speaking!</button>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script>
if (window == window.top) {
window.speech = {
say: function(msg) {
return new Promise(function(resolve, reject) {
if (!SpeechSynthesisUtterance) {
reject('Web Speech API is not supported');
}
var utterance = new SpeechSynthesisUtterance(msg);
utterance.addEventListener('end', function() {
resolve();
});
utterance.addEventListener('error', function(event) {
reject('An error has occurred while speaking: ' + event.error);
});
window.speechSynthesis.speak(utterance);
});
},
speak: true,
};
}
(function($) {
'use strict';
if (window == window.top) {
var body = $('body').empty();
var myframe = $('<iframe>')
.attr({ src: location.href })
.css({ height: '95vh', width: '100%' })
.appendTo(body)
.on('load', function () {
var $iframe = $(this).contents();
$iframe.find('#toggleSpeech').on('click', function(e) {
console.log('speaking will stop when the last sentence is done...');
window.speech.speak = !window.speech.speak;
});
window.speech.say('please finish saying this entire sentence.')
.then(function() {
if ( window.speech.speak ) {
console.log('speaking done, reloading iframe!');
myframe.attr({ src: location.href });
}
});
});
}
})(jQuery);
</script>
NOTE: Chrome (since v70) does NOT allow the immediate calling of window.speechSynthesis.speak(new SpeechSynthesisUtterance(msg)) anymore, you will get an error speechSynthesis.speak() without user activation is no longer allowed..., more details here. So technically the user would have to activate the script in Chrome to make it work!
Firefox:
First of all type and search for the “about: config” inside the browser by filling it in the address bar. This will take to another page where there will be a pop up asking to Take Any Risk, you need to accept that. Look for the preference named “accessibility.blockautorefresh” from the list and then right-click over that. There will be some options appearing as the list on the screen, select the Toggle option and then set it to True rather than False. This change will block the Auto Refresh on the Firefox browser. Remember that this option is revertable!
I have checked online including this web site and have been unable to find a solution to my problem.
I am trying to us the JQuery File Upload plugin and have made some amendments to restrict the files types to CSV and XML, as well as restricting to 1 file only and up to 5MB per file. These new properties work fine and it also works fine in all browsers except IE.
I have bound an event on the fileupload event that will render the CSV as a HTML table. This works fine in all browsers except IE. In IE, it either throws an error or freezes with the green progress bar at the top right displaying the following information:
7.123775601068567 bit/s | 00:00:00 | 100.00 % | 0.00 KB / 0.00 KB
It actually looks like it is hanging as it does not seem to complete the upload. And when I reload the same page it shows the correct display! without the progress bar!
UPDATE: The code is fine when I simply upload the CSV file. Now the problem appears when I attempt to include a callback option. It seems related to the 'fileuploadcompleted' event, which I updated from 'fileuploaddone', which has improved the result but still not right and does not execute and render the HTML table at all (See link above for example)
// Load existing files:
$('#fileupload').each(function () {
var that = this;
$.getJSON(this.action, function (result) {
if (result && result.length) {
$(that).fileupload('option', 'done')
.call(that, null, {result: result});
}
});
}).bind('fileuploadcompleted', function (e, data) {
//console.log('fileuploaddone:start');
var $filename = $('#fileupload td.name').text().trim();
//console.log('filename is ' + $filename);
$.get('../jQuery-File-Upload/server/php/files/' + $filename, function(data) {
$('#CSVSource').html('<pre>' + data + '</pre>');
});
$('#CSVTable').CSVToTable('../jQuery-File-Upload/server/php/files/' + $filename,{
loadingImage: 'img/loading-table.gif',
startLine: 0 }).bind("loadComplete",function() {//for future use});
}).bind('fileuploaddestroy', function (e, data) {
//console.log('fileuploaddestroy');
$('div#CSVTable').fadeOut(1000);
});
Any ideas how to get this working in IE?
Many thanks.
ok the issue is resolved after much digging, though solution was quite simple and has been covered here:
How to deal with ie8 jquery object doesn't support this property or method Error?
So basically in the code above I should have been using the JQuery version of trim, which was updated as follows:
var $filename = $.trim($('#fileupload td.name').text());
posting the question in some strange way helped me resolve this issue :)
As a side note: I've also noticed that if you have htaccess restrictions on the folder where your 'upload' web page is, you can also have different behaviour in IE. For example, with access restrictions set, the upload works though it does not display the actual file uploaded, despite uploading. And when you delete the file, it asks you to log in.
I have a JsFiddle here, and added Microsoft AJAX to be loaded through external JS/resource section. How can I tell whether or not my JS code is run after the AJAX file has finished loading?
Seems that the AJAX does not load either. :(
Here is the code in the JSFiddle:
Type.registerNamespace("Tutorial.Chapter1");
Tutorial.Chapter1.Person = function(firstName, lastName) {
this._firstName = firstName;
this._lastName = lastName;
};
Tutorial.Chapter1.Person.prototype = {
set_firstName: function(value) {
this._firstName = value;
},
get_firstName: function() {
return this._firstName;
},
set_lastName: function(value) {
this._lastName = value;
},
get_lastName: function() {
return this._lastName;
},
_firstName: "",
_lastName: "",
displayName: function() {
alert("Hi! " + this._firstName + " " + this._lastName);
}
};
Tutorial.Chapter1.Person.registerClass("Tutorial.Chapter1.Person", null);
The External Resources tab of jsFiddle is currently somewhat tricky and unstable to use.
The resources defined here are often not correctly included into the code. There seems to be an issue with the automatic recognition of JS and CSS resources. If this happens, the external resource is simply not added to the head section of the resulting code. You can check that by reviewing the source code of the Result frame of your jsFiddle. You will find that your MS AJAX resource is simply NOT mentioned in the resulting HTML code.
The correct recognition can actually be forced by adding a dummy value to the resource's URL like this (see –>jsFiddle docs for more info):
...&dummy=.js
Here is an example that shows how to add the external Google Maps API resource to a jsFiddle (mind the dummy parameter at the very end!):
https://maps.googleapis.com/maps/api/js?sensor=false&dummy=.js
Unfortunately this won't work for you as the MS AJAX URL will fail when additional parameters are appended.
A solution (and currently the safest way to load external resources) is to avoid the External Resources tab altogether and load external code manually in the first line(s) of jsFiddle's HTML window like this:
<script type='text/javascript' src="http://ajax.aspnetcdn.com/ajax/3.5/MicrosoftAjax.js"></script>
Here is your jsFiddle modified to use that method: http://jsfiddle.net/rEzW5/12/
It actually does not do a lot (I did not check what is wrong with the rest of your code), but at least it does not throw JavaScript errors anymore.
Open "Add Resources" section and add the url of your external script...
#Jpsy's approach no longer seems to work (see my comment under his answer).
For me, adding the resource under External Resources also didn't work. (According to the Firefox Debugger, it couldn't find the resource).
The only way I was able to get an external bit of JavaScript code (in my case jquery.backstretch.js) to work, was to use Google to find a Fiddle which used this resource (and worked), then Fork this Fiddle and copy/paste all my code into the HTML, CSS and JavaScript panels. Ugh!
#clayRay, You absolutely went thru a code surgery. Just resolved that by mentioning external source in plain html which in my case is
<script src="https://code.jquery.com/jquery-2.2.4.min.js"></script>
Using External Resources tab didn't help a bit...
I just found out that the Screen Capture by Google extension makes my website's window.onresize event not fire.
I want to perform a javascript check to see if the user has ScreenCapture installed and if so, warn the user of the problem.
A year ago I think I heard of some javascript code that could do this, maybe using some google API, but I don't remember.
Any insight on this? I haven't developed any extensions so I don't really know how they work.
[EDIT]
So I have been asked to show some code. As seen in my previous question ( window.onresize not firing in Chrome but firing in Chrome Incognito ), the problem occurs on any window.onresize event function, so I don't think my code really matters.
Also, there is quite a lot of my code, I don't know how much of it to paste or if it would be helpful.
var debounce = function (func, threshold, execAsap)
{
var timeout;
return function debounced () {//alert("1.1 Y U NO WORK?");
var obj = this, args = arguments;
function delayed () {
if (!execAsap)
func.apply(obj, args);
timeout = null;
}
if (timeout)
clearTimeout(timeout);
else if (execAsap)
func.apply(obj, args);
timeout = setTimeout(delayed, threshold || 100);
};
};
window.onresize = debounce(function (e) { //alert("1.2 Y U NO WORK?");
flag = true;
var point = window.center({width:1,height:1});
doCenter(point);
// does something here, but only once after mouse cursor stops
}, 100, false);
I would like to stress that the problem is not due to the debounce. window.onresize = t; function t (e) { alert("wtf?");} won't work either.
[EDIT2]
Here's the result:
var screenCapture = null;
var screenCaptureImg = document.createElement("img");
screenCaptureImg.setAttribute("src", "chrome-extension://cpngackimfmofbokmjmljamhdncknpmg/images/arrow.png");
/*
* Add event listeners for both "load"- and "error"-event
* Set the variable showing the existence of the extension by
* setting it to "true" or "false" according to the fired event
*/
screenCaptureImg.addEventListener("load", doLoad, false);
function doLoad(e){
screenCapture = true; //removeImgTag(e);
alert("I've so cleverly detected that your Chrome has the ScreenCapture extension enabled. \n\nThis extension interferes with my website's DOM and long story short, it won't be able to scale properly.\n\nSo please disable it. \nConsider this extension: \"Disable All Extensions Plus\", it's a handy selective disabler.");
}
screenCaptureImg.addEventListener("error", function(e){
screenCapture = false; //removeImgTag(e);
}, false);
/*
function removeImgTag(e) {
e.currentTarget.parentNode.removeChild(e.currentTarget);
}
*/
Note that I couldn't get removeImgTag to work, because (at least in chrome), I don't seem to have access to the document object in order to create or remove elements from my page, from within these event functions. This is also why I'm displaying an alert instead of elegantly writing up a document.getElementById("something").innerHTML=...
To detect if an extension is installed in Chrome, you can check for a known resource included in the extension such as an image. Resources for the extension are referenced using the following URL pattern:
chrome-extension://<extensionID>/<pathToFile>
The basic detection technique involves creating a hidden image tag and attaching load and error events to it to see if the image loads (as described here for Firefox):
extensionImg.setAttribute("src", "chrome-extension://<INSERT EXTENSION ID HERE>/images/someImage.png"); // See below for discussion of how to find this
/*
* Add event listeners for both "load"- and "error"-event
* Set the variable showing the existence of the extension by
* setting it to "true" or "false" according to the fired event
*/
extensionImg.addEventListener("load", function(e) {
extensionExists = true;
removeImgTag(e);
}, false);
extensionImg.addEventListener("error", function(e) {
extensionExists = false;
removeImgTag(e);
}, false);
function removeImgTag(e) {
e.currentTarget.parentNode.removeChild(e.currentTarget);
}
Check the installation directory of the extension in the Chrome configuration to find a likely target for detection. On my Linux workstation extensions are located in:
~/.config/chromium/Default/Extensions
You can see that I have 3 extensions installed right now:
~/.config/chromium/Default/Extensions$ ls
cpecbmjeidppdiampimghndkikcmoadk nmpeeekfhbmikbdhlpjbfmnpgcbeggic
cpngackimfmofbokmjmljamhdncknpmg
The odd looking names are the unique IDs given to the extension when it is uploaded to the Chrome webstore. You can obtain the ID either from the webstore or by going to the Extensions tab (wrench -> Extensions) and hovering over the link to the extension in question, or "Screen Capture (by Google)" in this case (note the asterisked extension ID):
https://chrome.google.com/webstore/detail/**cpngackimfmofbokmjmljamhdncknpmg**
In the extension directory there will be one or more versions; you can ignore this. Within the version directory is the actual content of the extension:
~/.config/chromium/Default/Extensions/cpngackimfmofbokmjmljamhdncknpmg/5.0.3_0$ ls
account.js images page.js sina_microblog.js
ajax.js isLoad.js picasa.js site.js
background.html _locales plugin style.css
editor.js manifest.json popup.html ui.js
facebook.js notification.html sha1.js upload_ui.js
hotkey_storage.js oauth.js shortcut.js
hub.html options.html showimage.css
i18n_styles page_context.js showimage.html
In the case of the Screen Capture extension there are a number of images to use:
~/.config/chromium/Default/Extensions/cpngackimfmofbokmjmljamhdncknpmg/5.0.3_0/images$ ls
arrow.png icon_128.png icon_save.png print.png
copy.png icon_16.png line.png region.png
cross.png icon_19.png loading.gif screen.png
custom.png icon_32.png loading_icon.gif sina_icon.png
delete_account_icon.png icon_48.png mark.png toolbar_bg.png
down_arrow.png icon_close.png picasa_icon.png upload.png
facebook_icon.png icon_copy.png popup_bg.jpg whole.png
These can be referenced under this URL:
chrome-extension://cpngackimfmofbokmjmljamhdncknpmg/images/arrow.png
This technique obviously depends on the stability of the content of the extension. I recommend using an image that looks likely to remain through all versions.
As mentioned above, the same technique can be used to detect Firefox extensions. In this case the content URL looks like this:
chrome://<EXTENSION NAME>/content/<PATH TO RESOURCE>
On my Linux workstation Firefox extensions are located in:
~/.mozilla/firefox/<USER PROFILE ID>/extensions
Where <USER PROFILE ID> looks something like this: "h4aqaewq.default"
You can see that I have 2 extensions installed right now, one of which is a directory installation and the other of which is a XPI (pronounced "zippy") file:
~/.mozilla/firefox/h4aqaewq.default/extensions$ ls
{3e9a3920-1b27-11da-8cd6-0800200c9a66} staged
firebug#software.joehewitt.com.xpi
The "staged" directory is where Firefox keeps extensions that will be updated (I think). The GUID directory with the brackets is a directory-based extension installation, and the .xpi file is Firebug.
Note: XPI is going away (see the link above). It's basically a zip file that can be opened and inspected by anything that understands zip. I used Emacs.
Finding the extension ID in Firefox is a bit more involved. Go to "Tools -> Add-ons", click the Extensions tab, click the "More" link next to the extension description, then click the "reviews" link to go to the Firefox extension site and get the ID from the URL (note the asterisked extension ID):
https://addons.mozilla.org/en-US/firefox/addon/**firebug**/reviews/?src=api
There's probably an easier way to do this; suggestions welcome.
TODO: how to find a likely image in a Firefox extension.
As an extra note, in Chrome you can only communicate with an extension via the shared DOM of the page: Host page communication
I am curious about people's experiences with replacing the entire document at runtime in an Ajax web app. It's rare, but I've found a few situations where the app requires an entire page rebuild and everything is present locally without needing another server round-trip.
I can easily prepare the new document as either a new DOM tree or as a String. So I'm evaluating the trade-offs for various approaches.
If I want to use the String approach this seems to work:
document.open();
document.write(newStringDoc);
document.close();
Most browsers do this just fine, but many have a slight flicker when re-rendering. I've noticed that on the 2nd time through Firefox 4.0b7 will just sit there and spin as if it is loading. Hitting the stop button on the location bar seems to complete the page render. (Edit: this appears to be fixed in 4.0b8) Also this method seems to prevent the user from hitting refresh to reload the current URL (it reloads the dynamically generated page).
If I use a new DOM tree approach (which has different advantages/disadvantages in flexibility and speed), then this seems to work:
document.replaceChild(newDomDoc, document.documentElement);
Most browsers seem to handle this perfectly fine without flicker. Unfortunately, IE9 beta throws "DOM Exception: HIERARCHY_REQUEST_ERR (3)" on replaceChild and never completes. I haven't tried the latest preview release to see if this is just a new bug that got fixed. (Edit: this appears to be fixed in RC1.)
My question: does anyone have a different approach than either of these? Does anyone have any other caveats where perhaps a particular browser fundamentally breaks down with one of these approaches?
Update: Perhaps this will add context and help the imagination. Consider a situation where an application is offline. There is no server available to redirect or refresh. The necessary state of the application is already loaded (or stored) client-side. The UI is constructed from client-side templates.
I believe that Gmail uses iframes embedded within a root document. It appears the starting document for at least some of these iframes are just a bare HTML5 document which the parent document then manipulates.
Using an iframe would be another variant on the requirement to replace the current document by replacing the entire child iframe or just its document. The same situation exists though of what approach to attach the new document to the iframe.
I guess I will answer this with my own findings as I'm wrapping up my research on this.
Since the two browsers which have issues with one of these methods are both beta, I've opened bug reports which hopefully will resolve those before their full release:
Firefox 4 Beta: https://bugzilla.mozilla.org/show_bug.cgi?id=615927
Edit: Fixed in FF 4b8.
Internet Explorer 9 Beta: https://connect.microsoft.com/IE/feedback/details/626473
Edit: Fixed in IE9 RC1.
I've also found pretty consistently that this...
document.replaceChild(newDomDoc, document.documentElement);
...is 2-10x faster than this...
var doc = document.open("text/html");
doc.write(newStringDoc);
doc.close();
...even when including the time needed to build the DOM nodes vs. build the HTML string. This might be the reason for the flicker, or perhaps just another supporting argument for the DOM approach. Chrome doesn't have any flicker with either method.
Note the subtle change of storing the returned document which circumvents the bug in Firefox 4.0b7.
Also note this added MIME type which the IE docs claim is "required".
Finally, Internet Explorer seems to have a bit of trouble resolving link tags that were built before the new document is swapped in. Assigning the link href back to itself appears to patch it up.
// IE requires link repair
if (document.createStyleSheet) {
var head = document.documentElement.firstChild;
while (head && (head.tagName||"") !== "HEAD") {
head = head.nextSibling;
}
if (head) {
var link = head.firstChild;
while (link) {
if ((link.tagName||"") === "LINK") {
link.href = link.href;
}
link = link.nextSibling;
}
}
}
One could cover all bases and combine them like this...
var doc = document;
try {
var newRoot = newDoc.toDOM();
doc.replaceChild(newRoot, doc.documentElement);
// IE requires link repair
if (doc.createStyleSheet) {
var head = newRoot.firstChild;
while (head && (head.tagName||"") !== "HEAD") {
head = head.nextSibling;
}
if (head) {
var link = head.firstChild;
while (link) {
if ((link.tagName||"") === "LINK") {
link.href = link.href;
}
link = link.nextSibling;
}
}
}
} catch (ex) {
doc = doc.open("text/html");
doc.write(newDoc.toString());
doc.close();
}
...assuming you have the ability to choose your approach like I do.