Skip to content

Instantly share code, notes, and snippets.

@Hashbrown777
Last active June 12, 2023 09:06
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Hashbrown777/4a093dcadac43d93b188526ab838aa7a to your computer and use it in GitHub Desktop.
Save Hashbrown777/4a093dcadac43d93b188526ab838aa7a to your computer and use it in GitHub Desktop.
Clones the selected element in the console into a new window, copying all styles and re-inserting any pseudo elements.
(function ({element, asBody, embedUrls, convertPseudo}) {
let copy = open().document;
copy.open();
copy.write('<!doctype html>\n<html><head></head><body></body></html>');
copy.close();
if (asBody) {
for (const name of element.getAttributeNames())
copy.body.setAttribute(name, element.getAttribute(name));
for (const node of element.children)
copy.body.appendChild(copy.importNode(node, true));
copy = [[copy.body, element]];
}
else {
copy = [[
copy.body.appendChild(copy.importNode(element, true)),
element
]];
if (asBody == null) {
copy.push([
copy[0][0].parentElement,
{computedStyleMap:document.body.computedStyleMap.bind(document.body)}
]);
}
}
let current;
const toHyphenate = /^(moz|webkit|ms|o)(?=[A-Z])|[A-Z]/g;
const doHyphenate = (what) => ('-' + what.toLowerCase());
function pseudo(element, which) {
if (!(element instanceof Element))
return;
element = getComputedStyle(element, ':' + which);
if (element.content == 'none')
return;
const computedStyleMap = new Map();
for (let index = 0; index < element.length; ++index) {
computedStyleMap.set(
element[index].replace(toHyphenate, doHyphenate),
element[element[index]]
);
}
element = current.ownerDocument.createElement('span');
element.className = which;
copy.push([element, {computedStyleMap:() => (computedStyleMap)}]);
current[(which == 'after') ? 'append' : 'prepend'](element);
}
const cssUrl = /(?<=^|[^a-zA-Z0-9_-])url\(\s*['"]?((?<!['"])[^\s)'"]+|(?<=')(?:[^'\\]|\\.)+(?=')|(?<=")(?:[^"\\]|\\.)+(?="))['"]?\s*\)/;
const urls = {length:0};
const getUrl = (fallback, url) => {
if (!urls[url]) {
const key = `--fetched${++urls.length}`;
urls[url] = `var(${key})`;
(async (css) => {
const file = new FileReader();
file.onloadend = () => {
css.setProperty(key, `url('${file.result}')`);
};
file.onerror = () => {
css.setProperty(key, fallback);
};
try {
file.readAsDataURL(await (await fetch(url)).blob());
}
catch (e) {
file.onerror();
}
})(current.ownerDocument.documentElement.style);
}
return urls[url];
};
while (copy.length) {
[current, element] = copy.pop();
for (
let next = [current.lastElementChild, element.lastElementChild];
next[1];
next = [next[0].previousElementSibling, next[1].previousElementSibling]
)
copy.push(next);
pseudo(element, 'after');
pseudo(element, 'before');
element = element.computedStyleMap();
current.removeAttribute('style');
const style = current.computedStyleMap();
const output = [];
for (let [name, value] of element.entries()) {
value = value.toString();
if (style.getAll(name).toString() != value) {
output.push(`${name}:${value.replace(cssUrl, getUrl)};`);
}
}
if (!output.length)
continue;
if (
convertPseudo ||
element.get('content').toString() == 'normal'
) {
current.setAttribute('style', output.join(''));
continue;
}
if (!current.parentElement.hasAttribute('data-pseudo')) {
current.parentElement.setAttribute(
'data-pseudo',
current.ownerDocument.head.children.length
);
}
current
.ownerDocument
.head
.appendChild(current.ownerDocument.createElement('style'))
.textContent
= `[data-pseudo="${
current.parentElement.getAttribute('data-pseudo')
}"]:${current.className} {
${output.join('\n')}
content:${element.get('content')};
}`;
current.remove();
}
})({
element : $0,
embedUrls : true,
//set to null if you want to copy the body styles
asBody : false,
//You can elect to switch this to true to effectively convert pseudo elements to real ones.
//However you'll need to parse `element.get('content')` and insert that into `current.textContent` which is non-trivial,
//since it's not just a matter of stripping quotes or parsing as json.
//For example:
// - in css encapsulating characters can be single quotes and unicode escape sequences are '\1234' not '\u1234'
// - the string could be an evaluated join or parenthesised à la `( "hello" "there" )`
// - they can even use css variables or references like 'attr()'
convertPseudo : false
})
@Hashbrown777
Copy link
Author

Tangentially: Delete print media rules from css

for (let sheet of document.styleSheets) { let index = -1; for (let rule of (() => { try { return sheet.cssRules } catch { return [] } })()) { ++index; if (/print/.test(rule.conditionText)) { console.log(rule); sheet.deleteRule(index); } } }

@Hashbrown777
Copy link
Author

Hashbrown777 commented Dec 5, 2022

fix for printing pages with youtube embeds (issue)

//run in each youtube iframe
for (let div of document.querySelectorAll('body,.html5-video-player,.ytp-cued-thumbnail-overlay-image')) { div.style.background = 'transparent'; }
//actually even gradients dont print, so add this to the one above
//for (let div of document.querySelectorAll('.ytp-gradient-top')) { div.style.background = 'linear-gradient(180deg, rgba(0,0,0,0.6) 0%, rgba(255,255,255,0) 75%)'; }

//run in top
for (let frame of document.querySelectorAll('iframe.YOUTUBE-iframe-video')) { frame.style.background = `url('${frame.getAttribute('data-thumbnail-src')}')`; frame.style.backgroundSize = 'cover'; frame.style.backgroundPosition = 'center'; }

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment