Extract the current DOM and print it as a string, with styles intact
I think this could be a solution (it took me nearly a whole day!).
It returns a string representing the DOM of any element, with all external styles included in the "style" attributes except default values, and does not permanently modify that element.
For example: console.log(document.body.serializeWithStyles());
You can load this code in Web Inspector command line or from a script tag in the body element but NOT in the head element because it requires the existence of document.body.
I have tested it on desktop Safari 5 (I don't have the mobile version).
It works like this:
For each element in the DOM:
1) caching the value of style.cssText property, which represents the inline style, in an array;
2) calling getComputedStyle on the element;
3) checking if we have the css default values lookup table corresponding to this element's tag name;
4) building it if not;
5) iterating through the result, finding which values are non default using the lookup table;
6) applying those non default style values to the element.
Then storing the outerHTML as the result;
For each element, restoring the inline styles from the cache;
Returning the previously stored result.
The code:
Element.prototype.serializeWithStyles = (function () {
// Mapping between tag names and css default values lookup tables. This allows to exclude default values in the result.
var defaultStylesByTagName = {};
// Styles inherited from style sheets will not be rendered for elements with these tag names
var noStyleTags = {"BASE":true,"HEAD":true,"HTML":true,"META":true,"NOFRAME":true,"NOSCRIPT":true,"PARAM":true,"SCRIPT":true,"STYLE":true,"TITLE":true};
// This list determines which css default values lookup tables are precomputed at load time
// Lookup tables for other tag names will be automatically built at runtime if needed
var tagNames = ["A","ABBR","ADDRESS","AREA","ARTICLE","ASIDE","AUDIO","B","BASE","BDI","BDO","BLOCKQUOTE","BODY","BR","BUTTON","CANVAS","CAPTION","CENTER","CITE","CODE","COL","COLGROUP","COMMAND","DATALIST","DD","DEL","DETAILS","DFN","DIV","DL","DT","EM","EMBED","FIELDSET","FIGCAPTION","FIGURE","FONT","FOOTER","FORM","H1","H2","H3","H4","H5","H6","HEAD","HEADER","HGROUP","HR","HTML","I","IFRAME","IMG","INPUT","INS","KBD","KEYGEN","LABEL","LEGEND","LI","LINK","MAP","MARK","MATH","MENU","META","METER","NAV","NOBR","NOSCRIPT","OBJECT","OL","OPTION","OPTGROUP","OUTPUT","P","PARAM","PRE","PROGRESS","Q","RP","RT","RUBY","S","SAMP","SCRIPT","SECTION","SELECT","SMALL","SOURCE","SPAN","STRONG","STYLE","SUB","SUMMARY","SUP","SVG","TABLE","TBODY","TD","TEXTAREA","TFOOT","TH","THEAD","TIME","TITLE","TR","TRACK","U","UL","VAR","VIDEO","WBR"];
// Precompute the lookup tables.
for (var i = 0; i < tagNames.length; i++) {
if(!noStyleTags[tagNames[i]]) {
defaultStylesByTagName[tagNames[i]] = computeDefaultStyleByTagName(tagNames[i]);
}
}
function computeDefaultStyleByTagName(tagName) {
var defaultStyle = {};
var element = document.body.appendChild(document.createElement(tagName));
var computedStyle = getComputedStyle(element);
for (var i = 0; i < computedStyle.length; i++) {
defaultStyle[computedStyle[i]] = computedStyle[computedStyle[i]];
}
document.body.removeChild(element);
return defaultStyle;
}
function getDefaultStyleByTagName(tagName) {
tagName = tagName.toUpperCase();
if (!defaultStylesByTagName[tagName]) {
defaultStylesByTagName[tagName] = computeDefaultStyleByTagName(tagName);
}
return defaultStylesByTagName[tagName];
}
return function serializeWithStyles() {
if (this.nodeType !== Node.ELEMENT_NODE) { throw new TypeError(); }
var cssTexts = [];
var elements = this.querySelectorAll("*");
for ( var i = 0; i < elements.length; i++ ) {
var e = elements[i];
if (!noStyleTags[e.tagName]) {
var computedStyle = getComputedStyle(e);
var defaultStyle = getDefaultStyleByTagName(e.tagName);
cssTexts[i] = e.style.cssText;
for (var ii = 0; ii < computedStyle.length; ii++) {
var cssPropName = computedStyle[ii];
if (computedStyle[cssPropName] !== defaultStyle[cssPropName]) {
e.style[cssPropName] = computedStyle[cssPropName];
}
}
}
}
var result = this.outerHTML;
for ( var i = 0; i < elements.length; i++ ) {
elements[i].style.cssText = cssTexts[i];
}
return result;
}
})();
In case you want to capture the whole page, it is easier to just get all non-inline stylesheets and inline them.
The approach in the accepted answer is magnificent, but quite slow and touches the whole document.
I took the following approach to capture a page including style:
document.documentElement.outerHTML;
get all stylesheets from the
document.styleSheets
API
Along the lines of:
function captureCss(){
var cssrules = "";
var sheets = document.styleSheets;
for(var i = 0; i<sheets.length; i++){
if(!sheets[i].disabled && sheets[i].href != null) { // or sheets[i].href.nodeName == 'LINK'
if(sheets[i].rules == null){ // can be null because of cross origin policy
try{
var fetched = XHR GET(sheets[i].href); // works nicely because it hits the cache
if(fetched){
cssrules += "<style>\n"+fetched+"\n</style>\n"
}
}catch(e){
console.log(e);
}
continue;
}
for(var j=0;j<sheets[i].rules.length;j++){
cssrules += "<style>\n"+sheets[i].rules[j].cssText+"\n</style>\n"
}
}
}
return cssrules;
}
- Add the captured
cssrules
as the first thing of the header in theouterHtml
html text
This way you get a self contained styled page.
This is obviously less applicable for partial content.
Can't you just do document.getElementsByTagName('body')[0].innerHTML? When I make changes in the inspector and then enter the above javascript in the console, it returns the updated HTML.
EDIT: I just tried putting that script in a function and attaching it to an onclick event. Made some updates in the inspector, clicked button, and it worked:
HTML
<button onclick="printDOM()">Print DOM</button>
Javascript
function printDOM() {
console.log(document.getElementsByTagName('body')[0].innerHTML) ;
}