惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

G
Google Developers Blog
S
Schneier on Security
The Hacker News
The Hacker News
P
Proofpoint News Feed
Spread Privacy
Spread Privacy
L
LINUX DO - 热门话题
L
Lohrmann on Cybersecurity
I
Intezer
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
www.infosecurity-magazine.com
www.infosecurity-magazine.com
Schneier on Security
Schneier on Security
Security Latest
Security Latest
AWS News Blog
AWS News Blog
B
Blog RSS Feed
Microsoft Security Blog
Microsoft Security Blog
有赞技术团队
有赞技术团队
博客园 - 叶小钗
The Last Watchdog
The Last Watchdog
O
OpenAI News
月光博客
月光博客
Hacker News: Ask HN
Hacker News: Ask HN
阮一峰的网络日志
阮一峰的网络日志
S
Security @ Cisco Blogs
Google Online Security Blog
Google Online Security Blog
云风的 BLOG
云风的 BLOG
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
Latest news
Latest news
P
Palo Alto Networks Blog
Last Week in AI
Last Week in AI
M
MIT News - Artificial intelligence
Google DeepMind News
Google DeepMind News
P
Proofpoint News Feed
C
CERT Recently Published Vulnerability Notes
Apple Machine Learning Research
Apple Machine Learning Research
U
Unit 42
PCI Perspectives
PCI Perspectives
博客园 - 聂微东
SecWiki News
SecWiki News
宝玉的分享
宝玉的分享
Forbes - Security
Forbes - Security
H
Heimdal Security Blog
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
Hugging Face - Blog
Hugging Face - Blog
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
T
Troy Hunt's Blog
博客园 - 三生石上(FineUI控件)
Application and Cybersecurity Blog
Application and Cybersecurity Blog
罗磊的独立博客
WordPress大学
WordPress大学
D
Darknet – Hacking Tools, Hacker News & Cyber Security

Mathias Bynens

A horrifying globalThis polyfill in universal JavaScript JavaScript engine fundamentals: optimizing prototypes JavaScript engine fundamentals: Shapes and Inline Caches Asynchronous stack traces: why await beats Promise#then() ECMAScript regular expressions are getting better! Unicode property escapes in JavaScript regular expressions ES2015 const is not about immutability Valid JavaScript variable names in ES2015 Unicode-aware regular expressions in ES2015 Dear Google, please fix plain text emails in Gmail PBKDF2+HMAC hash collisions explained JavaScript has a Unicode problem Processing Content Security Policy violation reports Hiding JSON-formatted data in the DOM with CSP enabled Loading JSON-formatted data with Ajax and xhr.responseType='json' Reserved keywords in JavaScript How to support full Unicode in MySQL databases How to speedrun Dropbox’s Dropquest 2012 Unquoted font family names in CSS Unquoted property names / object keys in JavaScript Valid JavaScript variable names in ES5 CSS character escape sequences JavaScript’s internal character encoding: UCS-2 or UTF-16? The smallest possible valid (X)HTML documents JavaScript character escape sequences JavaScript foo.prototype.bar notation Ambiguous ampersands HTML element + attribute notation How I detect and use localStorage: a simple JavaScript pattern Unquoted attribute values in HTML and CSS/JS selectors The end-tag open (ETAGO) delimiter Using the oninput event handler with onkeyup/onkeydown as its fallback Everything you always wanted to know about touch icons In defense of CSS hacks — introducing “safe CSS hacks” AirPlay video support in iOS Safari — a bookmarklet Completing Dropbox’s Dropquest 2011 in 60 seconds Using CSS without HTML How to create simple Mac apps from shell scripts Using setTimeout to speed up window.onload Bulletproof JavaScript benchmarks How to enable Safari Reader on your site? The XML serialization of HTML5, aka ‘XHTML5’ The id attribute got more classy in HTML5 The three levels of HTML5 usage The HTML5 document.head DOM tree accessor Bulletproof HTML5 <details> fallback using jQuery Displaying hidden elements like <head> using CSS Inline <script> and <style> vs. external .js and .css — what’s the size threshold? Using Showdown/PageDown with and without jQuery
Thoughts on Safari Reader’s generated HTML
Mathias · 2010-06-14 · via Mathias Bynens

After my post on how to enable Safari Reader on your site, I decided to play around with it a bit more. As it turns out, every time you click that shiny little Reader button, Safari generates an HTML document and displays it as an overlay to the original document. Let’s have a look at the HTML and CSS used in this process, and find out how we can mess with it.

Safari Reader generates the following HTML:

<!DOCTYPE html>
<html>
<head>
	<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
	<base href="{article URL}">
	<title><!-- {article title} --></title>
	<style id="article-content">
		@media print {
			.original-url {
				display: none;
			}
		}

		h1.title {
			font-family: Palatino, Georgia, Times, "Times New Roman", serif;
			font-weight: bold;
			font-size: 1.33em;
			line-height: 1.25em;
		}

		h1 {
			font-size: 1.25em;
		}

		h2 {
			font-size: 1.125em;
		}

		h3 {
			font-size: 1.05em;
		}

		.page a {
			text-decoration: none;
			color: rgb(32, 0, 127);
		}

		.page a:visited {
			color: rgb(32, 0, 127);
		}

		#article img {
			/* Float images to the left, so that text will nicely flow around them. */
			float: left;
			margin-right: 12px;
		}

		#article img.reader-image-tiny {
			/* Don't float very small images -- let them display where they occur in the text. */
			float: none;
			margin: 0;
		}

		#article img.reader-image-large {
			float: none;
			margin: auto;
			display: block;
		}

		.float {
			margin: 8px 0;
			font-size: 65%;
			line-height: 1.4;
			text-align: left;
		}

		.float.left {
			float: left;
			margin-right: 20px;
		}

		.float.right {
			float: right;
			margin-left: 20px;
		}

		.float.full-width {
			float: none;
			display: block;
		}

		.page {
			font: 20px Palatino, Georgia, Times, "Times New Roman", serif;
			line-height: 160%;
			text-align: justify;
		}

		.page:first-of-type .title {
			display: block;
		}

		.page table {
			font-size: 0.9em;
			text-align: left;
		}

		.page.rtl table {
			text-align: right;
		}

		.page-number {
			display: none;
		}

		.title {
			display: none;
		}
	</style>
	<style id="reader-ui">
		@media screen {
			body {
				margin: 0;
				padding: 0;
				background-color: transparent;
				-webkit-user-select: none;
			}
			.cached embed,
			.cached applet,
			.cached object {
				display: none !important;
			}
			#background {
				background-color: rgba(0, 0, 0, 0.8);
				-webkit-transform: translateZ(0);
				position: fixed;
				top: 0;
				bottom: 0;
				left: 0;
				right: 0;
			}
			#container {
				margin-left: -431px;
				left: 50%;
				width: 862px;
				height: 100%;
				position: absolute;
				pointer-events: none;
			}
			#centered {
				position: absolute;
				top: 0;
				width: 100%;
				height: 100%;
				z-index: 0;
			}
			.preloading #background {
				opacity: 0;
			}
			.preloading #centered {
				-webkit-transform: translate3d(0, 100%, 0);
			}
			.activating #background {
				-webkit-transition: opacity 0.40s ease-out;
				opacity: 1.0;
			}
			.activating #fade-top {
				-webkit-animation-name: fadeTopActivationFadeIn;
				-webkit-animation-duration: 0.40s;
				-webkit-animation-timing-function: ease-out;
			}
			.activating.skip-transition #fade-top {
				-webkit-animation: none !important;
			}
			@-webkit-keyframes fadeTopActivationFadeIn {
				0% {
					opacity: 0;
				}
				80% {
					opacity: 0;
				}
				100% {
					opacity: 1;
				}
			}
			.activating.skip-transition #background {
				-webkit-transition: none !important;
			}
			.activating #centered {
				-webkit-transition: -webkit-transform 0.40s ease-out;
				-webkit-transform: translate3d(0, 0, 0);
			}
			.activating.skip-transition #centered {
				-webkit-transition: none !important;
			}
			.deactivating #background {
				-webkit-transition: opacity 0.40s ease-in;
				opacity: 0;
			}
			.deactivating #fade-top {
				opacity: 0;
			}
			.deactivating #fade-bottom {
				-webkit-transition: opacity 0.40s ease-in;
				opacity: 0;
			}
			.deactivating #centered {
				-webkit-transition: -webkit-transform 0.40s ease-in;
				-webkit-transform: translate3d(0, 100%, 0);
			}
			.deactivating #hud {
				-webkit-transition: opacity 0.25s ease-in;
				opacity: 0 !important;
			}
			#drop-shadow {
				position: absolute;
				top: 0;
				bottom: 0;
				width: 800px;
				left: 10px;
				border-width: 24px 24px;
				-webkit-border-image: url(safari-resource:/ReaderDropShadow.png) 24 24 24 24 stretch stretch;
				opacity: 0;
			}
			::-webkit-scrollbar {
				width: 21px;
			}
			::-webkit-scrollbar:horizontal {
				display: none;
			}
			::-webkit-scrollbar-track {
				margin-top: 20px;
				margin-bottom: 20px;
				-webkit-border-image: url(safari-resource:/ReaderTrack.png) 21 0 21 0;
				border-width: 21px 0;
			}
			::-webkit-scrollbar-track:disabled {
				display: none;
			}
			::-webkit-scrollbar-thumb {
				-webkit-border-image: url(safari-resource:/ReaderThumb.png) 19 0 19 0;
				border-width: 19px 0;
				min-height: 40px;
			}
			#hud {
				position: fixed;
				width: 314px;
				height: 72px;
				left: 50%;
				margin-left: -157px;
				bottom: 30px;
				background: rgba(0, 0, 0, 0.75);
				-webkit-border-radius: 12px;
				z-index: 100;
				opacity: 0;
				-webkit-transition: opacity 0.75s;
				pointer-events: auto;
				zoom: reset;
			}
			#hud button {
				display: block;
				float: left;
				width: 48px;
				height: 48px;
				padding: 0;
				border: none;
				margin: 12px 5px;
			}
			#hud button:first-of-type {
				margin-left: 12px;
			}
			#hud button:last-of-type {
				margin-right: 12px;
			}
			#hud-zoom-out {
				background: url(safari-resource:/ReaderHUDZoomOutInactive.png) no-repeat;
			}
			#hud-zoom-out:active {
				background: url(safari-resource:/ReaderHUDZoomOutActive.png) no-repeat;
			}
			#hud-zoom-in {
				background: url(safari-resource:/ReaderHUDZoomInInactive.png) no-repeat;
			}
			#hud-zoom-in:active {
				background: url(safari-resource:/ReaderHUDZoomInActive.png) no-repeat;
			}
			#hud-mail {
				background: url(safari-resource:/ReaderHUDMailContentsInactive.png) no-repeat;
			}
			#hud-mail:active {
				background: url(safari-resource:/ReaderHUDMailContentsActive.png) no-repeat;
			}
			#hud-print {
				background: url(safari-resource:/ReaderHUDPrintInactive.png) no-repeat;
			}
			#hud-print:active {
				background: url(safari-resource:/ReaderHUDPrintActive.png) no-repeat;
			}
			#hud-exit {
				background: url(safari-resource:/ReaderHUDCloseInactive.png) no-repeat;
			}
			#hud-exit:active {
				background: url(safari-resource:/ReaderHUDCloseActive.png) no-repeat;
			}
			#article {
				/* The width of 819px here includes 19px for the WebKit scrollbar’s width. */
				/* The padding-right of 8px separates the scrollbar from the article itself. */

				position: absolute;
				height: 100%;
				left: 34px;
				width: 819px;
				padding-right: 8px;
				overflow: scroll;
				z-index: 0;
				outline: none;
				pointer-events: auto;
				-webkit-user-select: auto;
				-webkit-transform: translateZ(0);
			}
			.article-fade {
				position: absolute;
				left: 34px;
				height: 36px;
				width: 800px;
				z-index: 10;
				pointer-events: none;
			}
			#fade-top {
				top: 0;
				background: url(safari-resource:/ReaderFadeTop.png) repeat-x;
			}
			#fade-bottom {
				bottom: 0;
				background: url(safari-resource:/ReaderFadeBottom.png) repeat-x;
			}
			#resize-indicator {
				position: fixed;
				bottom: 0;
				right: 0;
				width: 12px;
				height: 12px;
				background: url(safari-resource:/TopSitesCornerResize.png);
			}
			.page:only-of-type .page-number {
				display: none;
			}
			.page-number {
				display: block;
				font: bold 11px Helvetica, sans-serif;
				margin-left: 12px;
				color: #B2B2B2;
				position: absolute;
				right: 10px;
				top: 10px;
				-webkit-user-select: none;
			}
			.page:first-of-type {
				margin-top: 22px;
			}
			.page:last-of-type {
				margin-bottom: 22px;
			}
			.page {
				width: 658px;
				margin-left: auto;
				margin-right: auto;
				margin-top: 10px;
				padding: 45px 70px;
				color: black;
				background: white;
				border: 1px solid #c3c3c3;
				position: relative;
				overflow: hidden;
				-webkit-transition: height .5s ease-out;
			}
			.page.rtl {
				direction: rtl;
			}
			#incoming-page-placeholder {
				height: 30px;
				margin-bottom: 0;
			}
			#incoming-page-corner {
				position: absolute;
				right: 10px;
				top: 8px;
			}
			#incoming-page-spinner {
				width: 16px;
				height: 16px;
				float: right;
				background: url(safari-resource:/ReaderSpinner.png);
			}
			#incoming-page-text {
				float: right;
				margin-top: 2px;
				margin-left: 8px;
				color: #B2B2B2;
				font: bold 11px Helvetica, sans-serif;
				-webkit-user-select: none;
			}
			#next-page-container {
				position: absolute;
				display: none;
			}
			.no-transition {
				-webkit-transition: none !important;
			}
			/* These keyframes try to reproduce the 12 discrete steps seen in a standard system progress indicator. */

			@-webkit-keyframes discreteSpinner {
				0% {
					-webkit-transform: rotate(0deg);
				}
				8.332% {
					-webkit-transform: rotate(0deg);
				}
				8.333% {
					-webkit-transform: rotate(30deg);
				}
				16.665% {
					-webkit-transform: rotate(30deg);
				}
				16.666% {
					-webkit-transform: rotate(60deg);
				}
				24.999% {
					-webkit-transform: rotate(60deg);
				}
				25.000% {
					-webkit-transform: rotate(90deg);
				}
				33.332% {
					-webkit-transform: rotate(90deg);
				}
				33.333% {
					-webkit-transform: rotate(120deg);
				}
				41.665% {
					-webkit-transform: rotate(120deg);
				}
				41.666% {
					-webkit-transform: rotate(150deg);
				}
				49.999% {
					-webkit-transform: rotate(150deg);
				}
				50.000% {
					-webkit-transform: rotate(180deg);
				}
				58.332% {
					-webkit-transform: rotate(180deg);
				}
				58.333% {
					-webkit-transform: rotate(210deg);
				}
				66.665% {
					-webkit-transform: rotate(210deg);
				}
				66.666% {
					-webkit-transform: rotate(240deg);
				}
				74.999% {
					-webkit-transform: rotate(240deg);
				}
				75.000% {
					-webkit-transform: rotate(270deg);
				}
				83.332% {
					-webkit-transform: rotate(270deg);
				}
				83.333% {
					-webkit-transform: rotate(300deg);
				}
				91.665% {
					-webkit-transform: rotate(300deg);
				}
				91.666% {
					-webkit-transform: rotate(330deg);
				}
				100% {
					-webkit-transform: rotate(330deg);
				}
			}
			.animation-discrete-spinner {
				-webkit-animation-name: discreteSpinner;
				-webkit-animation-duration: 1s;
				-webkit-animation-iteration-count: infinite;
				-webkit-animation-timing-function: linear;
			}
		}
	</style>
	<script src="safari-resource:/localizedStrings.js"></script>
</head>
<!-- The body element will have class="preloading", which gets nullified after the loading is completed. — Mathias -->
<body class="	" onload="ReaderJS.loaded();" onselectstart="setHUDAcceptsPointerEvents(false);" onmouseup="setHUDAcceptsPointerEvents(true);" onblur="didLoseFocus();">
	<div id="background" onclick="deactivateAfterAnimation();"></div>
	<iframe id="next-page-container"></iframe>
	<div id="container">
	<div id="resize-indicator" style="display: block;"></div>
	<div id="hud" onmouseover="hudMouseOver(event);" onmouseout="hudMouseOut(event);" style="pointer-events: none; opacity: 0;">
		<button id="hud-zoom-out" onclick="makeTextSmaller();" title="Zoom out" disabled="true"></button>
		<button id="hud-zoom-in" onclick="makeTextLarger();" title="Zoom in" disabled="true"></button>
		<button id="hud-mail" onclick="mailArticle();" title="Mail this page" disabled="true"></button>
		<button id="hud-print" onclick="printArticle();" title="Print this page" disabled="true"></button>
		<button id="hud-exit" onclick="deactivateAfterAnimation();" title="Exit Reader" disabled="true"></button>
	</div>
	<div id="centered">
		<div id="drop-shadow" style="opacity: 1;"></div>
		<div id="article" onscroll="articleScrolled();" tabindex="0">
		<!-- This node contains a number of 'page' class divs. -->
		<div class="page" style="font-family: Palatino, Georgia, Times, 'Times New Roman', serif; font-size: 17px; line-height: 1.4; padding-bottom: 85px;">
			<div class="page-number">Page 1 of 1</div>
			<h1 class="title"><!-- {article title} --></h1>
			<!-- {article HTML} -->
		</div>
		</div>
	</div>
	<div id="fade-top" class="article-fade" style="clip: rect(-147px 800px 36px 0px);"></div>
	<div id="fade-bottom" class="article-fade" style="clip: rect(0px 800px 36px 0px);"></div>
	</div>
</body>
</html>

Note that I’ve replaced all variable content with HTML comments of the form <!-- {article title} --> to make it easier to focus on the generated code Safari uses every time Reader is opened. The raw template (without any indication of where the actual content will go) can be found at file:///Applications/Safari.app/Contents/Resources/Reader.html on Mac.

Some things to note:

  • Even though the HTML5 DOCTYPE is used, the document’s character encoding is still being set the old-school way.
  • The <body> element of a Reader document has an ‘empty’ class attribute (containing only whitespace). When cross-referencing this with the Reader template, we learn that initially this attribute has a value: class="preloading".
  • The pre-loading spinner (as visible when using Reader on a multi-page article) is not a GIF image, but uses [vendor-specific] ‘CSS3’ properties for the rotation. (Look for discreteSpinner in the code.)
  • When Reader is opened, the Web Inspector gets closed automatically. In addition, the ‘Show Web Inspector’ and ‘Show Error Console’ items (found under the ‘Develop’ menu) are disabled. The keyboard shortcuts for these functions (respectively ⌘ + ⌥ + I and ⌘ + ⌥ + C) don’t work either. However, it’s still possible to right-click the Reader document and select ‘Inspect Element’. Doing this will open the Web Inspector anyway, and give you access to all of its tools.

Customizing the Safari Reader UI with JavaScript and CSS

Because Safari Reader is HTML-based, you can easily write your own CSS to be used instead of the default one.

For example, try running the following JavaScript code from the console after opening Reader to greatly improve its usability and general awesomeness:

(function(d) {
	var s = d.createElement('style');
	var c = '#background{background:#f773b5 url(https://i.imgur.com/bB7aD.jpg)}h1.title,.page{font-family:"Comic Sans MS"!important}';
	c += 'h1.title{color:#f773b5}.page{background:rgba(255,255,255,.9);-webkit-animation-name:f;-webkit-animation-duration:5s;';
	c += '-webkit-animation-iteration-count:infinite;-webkit-animation-timing-function:linear}@-webkit-keyframes f{';
	c += '0%{-webkit-transform:rotate(0) scale(1)}25%{-webkit-transform:rotate(-4deg) scale(.95)}50%{-webkit-transform:rotate(0) scale(1)}';
	c += '75%{-webkit-transform:rotate(4deg) scale(.95)}100%{-webkit-transform:rotate(0) scale(1)}}';
	s.appendChild(d.createTextNode(c));
	d.head.appendChild(s)
}(document));

Much better, right?

How to target the Reader document using JavaScript?

I wanted to make this a bookmarklet, by rewriting the code as a one liner and prefixing it with javascript:, but unfortunately that doesn’t seem to work. When using a bookmarklet in Safari with Reader enabled, document still refers to the original HTML document, not the Reader document. Oddly enough, document does refer to the Reader document when entered in the console.

Here’s the thing — I have no idea how the Reader document can be targeted in JavaScript. It’s not a frame or an iframe; the DOM of the original document doesn’t seem to be altered in any way.

It would be very interesting to find out how Safari Reader documents can be manipulated using JavaScript, without having to use the console.

Any clever ideas?