Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

screenshoter

  • Loading branch information...
commit 5d54fc7a411fa57527f73932681ec6b4f35281eb 1 parent e73c169
@ebidel authored
View
23 screenshoter/README.md
@@ -0,0 +1,23 @@
+# Instructions
+
+ - Install the ws websocket library for Node: [http://einaros.github.com/ws/](http://einaros.github.com/ws/)
+
+ - Start a webserver:
+
+ python -m SimpleHTTPServer 8000
+
+ - Start the websocket server:
+
+ node server.js --port=3000
+
+ - Open your browser to [http://localhost:8000](http://localhost:8000).
+
+ - Save the contents of `bookmarklet.js` as a bookmark. For convenience, this file contains the uncompressed source as well as a bookmarklet version. Copy and paste the latter.
+
+ - Open another browser tab to any website and click the bookmarklet. You should start seeing the page stream from this new tab to the tab in #3. Scrolling the site should
+ be reflected on the screenshot viewer.
+
+# Issues?
+
+- Make sure `HOST = 'localhost:8000'` in `bookmarklet.js` points to your webserver's host.
+- Make sure `WS_HOST = 'localhost:3000'` in `app.js` points to the correct location.
View
32 screenshoter/app.js
@@ -0,0 +1,32 @@
+window.URL = window.URL || window.webkitURL;
+
+var WS_HOST = 'localhost:3000';
+var ws = null;
+var REFRESH_EVERY = 1000; // ms
+
+function connect() {
+ ws = new WebSocket('ws://' + WS_HOST, ['dumby-protocol']);
+ ws.binaryType = 'blob';
+
+ ws.onopen = function(e) {
+ console.log('WebSocket connection OPEN');
+ };
+
+ ws.onclose = function(e) {
+ console.log('WebSocket connection CLOSED');
+ };
+
+ ws.onerror = function(e) {
+ console.log('WebSocket connection ERROR', e);
+ };
+
+ return ws;
+}
+
+function send() {
+ setInterval(function() {
+ if (ws.bufferedAmount == 0) {
+ ws.send(screenshotPage());
+ }
+ }, REFRESH_EVERY);
+}
View
25 screenshoter/bookmarklet.js
@@ -0,0 +1,25 @@
+(function() {
+ var HOST = 'localhost:8000';
+
+ var s = document.createElement('script');
+ s.type = 'text/javascript';
+ s.defer = true;
+ s.src = 'http://' + HOST + '/screencapture.js'
+
+ var s2 = document.createElement('script');
+ s2.type = 'text/javascript';
+ s2.defer = true;
+ s2.src = 'http://' + HOST + '/app.js'
+ s2.onload = function(e) {
+ connect();
+ send();
+ };
+
+ var scripts = document.getElementsByTagName('script')[0];
+ scripts.parentNode.insertBefore(s, scripts);
+ scripts.parentNode.insertBefore(s2, scripts);
+})();
+
+/* Bookmarklet version. Create a bookmark and save this as its URL:
+javascript:(function(){var b=document.createElement("script");b.type="text/javascript";b.defer=!0;b.src="http://localhost:8000/screencapture.js";var a=document.createElement("script");a.type="text/javascript";a.defer=!0;a.src="http://localhost:8000/app.js";a.onload=function(){connect();send()};var c=document.getElementsByTagName("script")[0];c.parentNode.insertBefore(b,c);c.parentNode.insertBefore(a,c)})();
+*/
View
114 screenshoter/index.html
@@ -0,0 +1,114 @@
+<!DOCTYPE html>
+<!--
+Copyright 2012 Eric Bidelman
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+
+Author: Eric Bidelman (ebidel@gmail.com)
+-->
+<html>
+<head>
+ <title>Binary WebSocket Tab Sharing</title>
+ <meta charset="utf-8">
+ <meta http-equiv="X-UA-Compatible" content="chrome=1">
+ <link rel="stylesheet" href="http://fonts.googleapis.com/css?family=Orbitron|Kameron|Muli|Open+Sans:regular,semibold,italic,italicsemibold|Droid+Sans+Mono&v2">
+<style>
+body {
+ font-family: 'Open Sans';
+}
+iframe, #screenshot0, #screenshot1 {
+ width: 100%;
+ height: 500px;
+ border: 1px solid black;
+}
+</style>
+</head>
+<body>
+
+<h3>Screenshot from host:</h3>
+<iframe id="screenshot0" hidden></iframe>
+<iframe id="screenshot1" hidden></iframe>
+
+<div id="frames-container"></div>
+
+<script src="screencapture.js"></script>
+<script src="app.js"></script>
+<script>
+var framesContainer = document.querySelector('#frames-container');
+var currentFrameIdx = 0;
+var playbackIntervalId = null;
+var ws = connect();
+
+ws.onmessage = function(e) {
+ // Shouldn't have to create a new blob, but e.data isn't preserving type.
+ var blob = new Blob([e.data], {type: 'text/html'});
+
+ var iframe = document.createElement('iframe');
+ iframe.src = window.URL.createObjectURL(blob);
+ iframe.hidden = true;
+ iframe.onload = renderFrame;
+
+ // Force the iframe content to load by appending to the DOM.
+ framesContainer.appendChild(iframe);
+};
+
+function renderFrame() {
+ if (framesContainer.children.length) {
+ var frame = framesContainer.children[currentFrameIdx];
+
+ if(!frame) {
+ return;
+ }
+
+ if (currentFrameIdx > 0) {
+ var prevFrame = frame.previousElementSibling;
+ prevFrame.hidden = true;
+ window.URL.revokeObjectURL(prevFrame.src);
+ }
+
+ frame.hidden = false;
+
+ currentFrameIdx++;
+ }
+}
+
+function playback(interval) {
+ clearInterval(playbackIntervalId);
+
+ if (!framesContainer.children.length) {
+ return;
+ }
+
+ var i = 0;
+ playbackIntervalId = setInterval(function() {
+ var iframe = framesContainer.children[i];
+ if (i > 0) {
+ framesContainer.children[i - 1].hidden = true;
+ } else if (i == 0) {
+ framesContainer.children[framesContainer.children.length - 1].hidden = true;
+ }
+ iframe.hidden = false;
+
+ i++;
+ i %= framesContainer.children.length;
+ }, interval);
+}
+
+//setInterval(renderFrame, 1000);
+</script>
+<!--[if IE]>
+<script src="http://ajax.googleapis.com/ajax/libs/chrome-frame/1/CFInstall.min.js"></script>
+<script>CFInstall.check({mode: 'overlay'});</script>
+<![endif]-->
+</body>
+</html>
View
103 screenshoter/screencapture.js
@@ -0,0 +1,103 @@
+// Copyright 2012 Eric Bidelman
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+// Author: Eric Bidelman (ebidel@)
+
+(function(exports) {
+function urlsToAbsolute(nodeList) {
+ if (!nodeList.length) {
+ return [];
+ }
+
+ var attrName = 'href';
+ if (nodeList[0].__proto__ === HTMLImageElement.prototype ||
+ nodeList[0].__proto__ === HTMLScriptElement.prototype) {
+ attrName = 'src';
+ }
+
+ nodeList = [].map.call(nodeList, function(el, i) {
+ var attr = el.getAttribute(attrName);
+ // If no src/href is present, disregard.
+ if (!attr) {
+ return;
+ }
+
+ var absURL = /^(https?|data):/i.test(attr);
+ if (absURL) {
+ return el;
+ } else {
+ if (attr.indexOf('/') != 0) { // src="images/test.jpg"
+ el.setAttribute(attrName, document.location.origin + document.location.pathname + attr);
+ } else if (attr.match(/^\/\//)) { // src="//static.server/test.jpg"
+ el.setAttribute(attrName, document.location.protocol + attr);
+ } else {
+ el.setAttribute(attrName, document.location.origin + attr);
+ }
+ return el;
+ }
+ });
+ return nodeList;
+}
+
+// TODO: current limitation is css background images are not included.
+function screenshotPage() {
+ // 1. Rewrite current doc's imgs, css, and script URLs to be absolute before
+ // we duplicate. This ensures no broken links when viewing the duplicate.
+ urlsToAbsolute(document.images);
+ urlsToAbsolute(document.querySelectorAll("link[rel='stylesheet']"));
+ //urlsToAbsolute(document.scripts);
+
+ // 2. Duplicate entire document.
+ var screenshot = document.documentElement.cloneNode(true);
+
+ // 3. Screenshot should be readyonly, no scrolling, and no selections.
+ screenshot.style.pointerEvents = 'none';
+ screenshot.style.overflow = 'hidden';
+ screenshot.style.webkitUserSelect = 'none';
+ screenshot.style.mozUserSelect = 'none';
+ screenshot.style.msUserSelect = 'none';
+ screenshot.style.oUserSelect = 'none';
+ screenshot.style.userSelect = 'none';
+
+ // 4. Preserve current x,y scroll position of this page. See addOnPageLoad_().
+ screenshot.dataset.scrollX = window.scrollX;
+ screenshot.dataset.scrollY = window.scrollY;
+
+ // 4.5. When the screenshot loads (e.g. as ablob URL, as iframe.src, etc.),
+ // scroll it to the same location of this page. Do this by appending a
+ // window.onDOMContentLoaded listener which pulls out the saved scrollX/Y
+ // state from the DOM.
+ var script = document.createElement('script');
+ script.textContent = '(' + addOnPageLoad_.toString() + ')();'; // self calling.
+ screenshot.querySelector('body').appendChild(script);
+
+ // 5. Create a new .html file from the cloned content.
+ var blob = new Blob([screenshot.outerHTML], {type: 'text/html'});
+
+ return blob;
+}
+
+// NOTE: Not to be invoked directly. When the screenshot loads, it should scroll
+// to the same x,y location of this page.
+function addOnPageLoad_() {
+ window.addEventListener('DOMContentLoaded', function(e) {
+ var scrollX = document.documentElement.dataset.scrollX || 0;
+ var scrollY = document.documentElement.dataset.scrollY || 0;
+ window.scrollTo(scrollX, scrollY);
+ });
+}
+
+exports.screenshotPage = screenshotPage;
+
+})(window);
View
60 screenshoter/server.js
@@ -0,0 +1,60 @@
+#!/usr/bin/env node
+
+var WebSocketServer = require('ws').Server;
+
+var http = require('http');
+var url = require('url');
+var fs = require('fs');
+
+var args = { /* defaults */
+ port: '8080'
+};
+
+/* Parse command line options */
+var pattern = /^--(.*?)(?:=(.*))?$/;
+process.argv.forEach(function(value) {
+ var match = pattern.exec(value);
+ if (match) {
+ args[match[1]] = match[2] ? match[2] : true;
+ }
+});
+
+var port = parseInt(args.port, 10);
+
+console.log("Usage: ./server.js [--port=8080]");
+
+var connections = {};
+
+// ws is the fastest websocket lib - http://einaros.github.com/ws/
+var wsServer = new WebSocketServer({port: port});
+
+wsServer.on('connection', function(ws) {
+
+ ws.id = Date.now(); // Assign unique id to this ws connection.
+ connections[ws.id] = ws;
+
+ console.log((new Date()) + ' Connection accepted: ' + ws.id);
+
+ ws.on('message', function(message, flags) {
+ console.log('Received ' + (flags.binary ? 'binary' : '') + ' message: ' +
+ message.length + ' bytes.');
+ broadcast(message, this, flags);
+ });
+
+ ws.on('close', function() {
+ console.log((new Date()) + " Peer " + this.id + " disconnected.");
+ delete connections[this.id];
+ });
+});
+
+// Broadcasts a message to all connected sockets accept for the sender.
+function broadcast(message, fromWs, flags) {
+ for (var id in connections) {
+ if (id != fromWs.id) {
+ connections[id].send(message, {
+ binary: flags.binary ? true : false,
+ mask: false
+ });
+ }
+ }
+}
Please sign in to comment.
Something went wrong with that request. Please try again.