Skip to content

Instantly share code, notes, and snippets.



Last active Feb 23, 2020
What would you like to do?
A bookmarklet to view the page source on iOS Safari - without sending your URL to a third party service.

view-source on iOS Safari - without 3rd party services

A bookmarklet to display the current page's HTML in a new tab. Supports HTML syntax highlighting and indentation, JavaScript keywords will be highlighted, too.


Before adding the bookmarklet, you need to either build view-source.js, or copy view-source.min.js to your clipboard, as we'll paste this into the bookmarklet later.

Safari apparently doesn't support adding a custom bookmarklet, so you'll need to follow these steps:

  1. Go to about:blank
  2. Share button
  3. Add to bookmarks
  4. Call it "View Source" or similar
  5. Go to your bookmarks and edit the previously created bookmark
  6. Paste the code into the URL

Important: You need to paste either the .min.js contents or build it yourself. If the URL isn't prefixed with javascript:, the bookmarklet won't work.

"name": "view-source-safari",
"version": "1.0.0",
"main": "view-source.js",
"license": "MIT",
"scripts": {
"build": "terser --compress --mangle -- view-source.js | sed '1s;^;javascript:;' | sed -E 's/( |\\\\n)//g' > view-source.min.js",
"copy": "cat view-source.min.js | pbcopy"
"devDependencies": {
"prettier": "^1.19.1",
"terser": "^4.6.3"
(() => {
* @param {HTMLElement} node
function visitScript(node) {
if (!node.textContent) {
return "";
const highlightedKeywords = [
(text, keyword) =>
new RegExp(keyword, "g"),
`<span class="keyword">${keyword}</span>`
const formatted = highlightedKeywords.replace(/([;\{\}])/g, "$1<br />");
return `
<pre class="indent">${highlightedKeywords}</pre>
* @param {HTMLElement} node
function visit(node) {
if (node.nodeType !== Node.ELEMENT_NODE) {
return node.textContent;
const attributes = node.attributes
? [...node.attributes]
.map(attribute =>
? ` <span class="attribute">${}=</span><span class="string">"${attribute.value}"</span>`
: ` <span class="attribute">${}</span>`
: "";
const tag = node.tagName.toLowerCase();
const children =
tag === "script"
? visitScript(node)
: [...node.childNodes]
.map(child => `<div class="indent">${visit(child)}</div>`)
return `
<span class="tag">&lt;${tag}</span>${attributes}<span class="tag">&gt;</span>${children}<span class="tag">&lt;/${tag}&gt;</span>
const tab ="about:blank");
const source = window.document.querySelector("html");
<title>View Source: ${document.title}</title>
body {
font-family: monospace;
font-size: 14px;
white-space: nowrap;
.indent {
padding-left: 1rem;
.attribute {
color: green;
.string {
color: orange;
.tag {
color: blue;
.keyword {
color: purple;
javascript:(()=>{const"about:blank"),t=window.document.querySelector("html");n.document.write(`<html><head><title>View Source: ${document.title}</title><style>body {font-family: monospace;font-size: 14px;white-space: nowrap;}.indent {padding-left: 1rem;}.attribute {color: green;}.string {color: orange;}.tag {color: blue;}.keyword {color: purple;}</style></head><body>${function n(t){if(t.nodeType!==Node.ELEMENT_NODE)return t.textContent;const e=t.attributes?[...t.attributes].map(n=>n.value?` <span class="attribute">${}=</span><span class="string">"${n.value}"</span>`:` <span class="attribute">${}</span>`).join(""):"",a=t.tagName.toLowerCase();return`<span class="tag">&lt;${a}</span>${e}<span class="tag">&gt;</span>${"script"===a?function(n){if(!n.textContent)return"";const t=["function","if","else","for","var","let","const","instanceof","typeof"].reduce((n,t)=>n.replace(new RegExp(t,"g"),`<span class="keyword">${t}</span>`),n.textContent);return t.replace(/([;\{\}])/g,"$1<br />"),`<pre class="indent">${t}</pre>`}(t):[...t.childNodes].map(t=>`<div class="indent">${n(t)}</div>`).join("")}<span class="tag">&lt;/${a}&gt;</span>`}(t)}</body></html>`)})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment