michael@0: /* -*- Mode: js; js-indent-level: 2; indent-tabs-mode: nil; tab-width: 2 -*- */ michael@0: /* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */ michael@0: /* michael@0: * Copyright 2013 Mozilla Foundation michael@0: * michael@0: * Licensed under the Apache License, Version 2.0 (the "License"); michael@0: * you may not use this file except in compliance with the License. michael@0: * You may obtain a copy of the License at michael@0: * michael@0: * http://www.apache.org/licenses/LICENSE-2.0 michael@0: * michael@0: * Unless required by applicable law or agreed to in writing, software michael@0: * distributed under the License is distributed on an "AS IS" BASIS, michael@0: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. michael@0: * See the License for the specific language governing permissions and michael@0: * limitations under the License. michael@0: */ michael@0: michael@0: michael@0: // Extenstion communication object michael@0: var FirefoxCom = (function FirefoxComClosure() { michael@0: return { michael@0: /** michael@0: * Creates an event that the extension is listening for and will michael@0: * synchronously respond to. michael@0: * NOTE: It is reccomended to use request() instead since one day we may not michael@0: * be able to synchronously reply. michael@0: * @param {String} action The action to trigger. michael@0: * @param {String} data Optional data to send. michael@0: * @return {*} The response. michael@0: */ michael@0: requestSync: function(action, data) { michael@0: var request = document.createTextNode(''); michael@0: document.documentElement.appendChild(request); michael@0: michael@0: var sender = document.createEvent('CustomEvent'); michael@0: sender.initCustomEvent('shumway.message', true, false, michael@0: {action: action, data: data, sync: true}); michael@0: request.dispatchEvent(sender); michael@0: var response = sender.detail.response; michael@0: document.documentElement.removeChild(request); michael@0: return response; michael@0: }, michael@0: /** michael@0: * Creates an event that the extension is listening for and will michael@0: * asynchronously respond by calling the callback. michael@0: * @param {String} action The action to trigger. michael@0: * @param {String} data Optional data to send. michael@0: * @param {Function} callback Optional response callback that will be called michael@0: * with one data argument. michael@0: */ michael@0: request: function(action, data, callback) { michael@0: var request = document.createTextNode(''); michael@0: request.setUserData('action', action, null); michael@0: request.setUserData('data', data, null); michael@0: request.setUserData('sync', false, null); michael@0: if (callback) { michael@0: request.setUserData('callback', callback, null); michael@0: michael@0: document.addEventListener('shumway.response', function listener(event) { michael@0: var node = event.target, michael@0: response = event.detail.response; michael@0: michael@0: document.documentElement.removeChild(node); michael@0: michael@0: document.removeEventListener('shumway.response', listener, false); michael@0: return callback(response); michael@0: }, false); michael@0: } michael@0: document.documentElement.appendChild(request); michael@0: michael@0: var sender = document.createEvent('CustomEvent'); michael@0: sender.initCustomEvent('shumway.message', true, false, michael@0: {action: action, data: data, sync: false}); michael@0: return request.dispatchEvent(sender); michael@0: } michael@0: }; michael@0: })(); michael@0: michael@0: function fallback() { michael@0: FirefoxCom.requestSync('fallback', null) michael@0: } michael@0: michael@0: var BYTES_TO_LOAD = 32768; michael@0: var BYTES_TO_PARSE = 32768; michael@0: michael@0: function runSniffer() { michael@0: var flashParams = JSON.parse(FirefoxCom.requestSync('getPluginParams', null)); michael@0: document.head.getElementsByTagName('base')[0].href = flashParams.baseUrl; michael@0: movieUrl = flashParams.url; michael@0: document.getElementById('playbutton').addEventListener('click', function() { michael@0: switchToFullMode(); michael@0: }); michael@0: document.getElementById('fullmode').addEventListener('click', function() { michael@0: switchToFullMode(); michael@0: return false; michael@0: }); michael@0: document.getElementById('fallback').addEventListener('click', function() { michael@0: fallback(); michael@0: return false; michael@0: }); michael@0: FirefoxCom.requestSync('loadFile', {url: movieUrl, sessionId: 0, limit: BYTES_TO_LOAD}); michael@0: } michael@0: michael@0: var subscription, movieUrl, buffers = [];; michael@0: michael@0: addEventListener("message", function handlerMessage(e) { michael@0: var args = e.data; michael@0: switch (args.callback) { michael@0: case "loadFile": michael@0: if (args.sessionId != 0) { michael@0: return; michael@0: } michael@0: switch (args.topic) { michael@0: case "progress": michael@0: buffers.push(args.array); michael@0: break; michael@0: case "error": michael@0: console.error('Unable to download ' + movieUrl + ': ' + args.error); michael@0: break; michael@0: case "close": michael@0: parseSwf(); michael@0: break; michael@0: } michael@0: break; michael@0: } michael@0: }, true); michael@0: michael@0: function inflateData(bytes, outputLength) { michael@0: verifyDeflateHeader(bytes); michael@0: var stream = new Stream(bytes, 2); michael@0: var output = { michael@0: data: new Uint8Array(outputLength), michael@0: available: 0, michael@0: completed: false michael@0: }; michael@0: var state = {}; michael@0: // inflate while we can michael@0: try { michael@0: do { michael@0: inflateBlock(stream, output, state); michael@0: } while (!output.completed && stream.pos < stream.end michael@0: && output.available < outputLength); michael@0: } catch (e) { michael@0: console.log('inflate aborted: ' + e); michael@0: } michael@0: return new Stream(output.data, 0, Math.min(output.available, outputLength)); michael@0: } michael@0: michael@0: function parseSwf() { michael@0: var sum = 0; michael@0: for (var i = 0; i < buffers.length; i++) michael@0: sum += buffers[i].length; michael@0: var data = new Uint8Array(sum), j = 0; michael@0: for (var i = 0; i < buffers.length; i++) { michael@0: data.set(buffers[i], j); j += buffers[i].length; michael@0: } michael@0: michael@0: var backgroundColor; michael@0: try { michael@0: var magic1 = data[0]; michael@0: var magic2 = data[1]; michael@0: var magic3 = data[2]; michael@0: if ((magic1 !== 70 && magic1 !== 67) || magic2 !== 87 || magic3 !== 83) michael@0: throw new Error('unsupported file format'); michael@0: michael@0: var compressed = magic1 === 67; michael@0: var stream = compressed ? inflateData(data.subarray(8), BYTES_TO_PARSE) : michael@0: new Stream(data, 8, data.length - 8); michael@0: var bytes = stream.bytes; michael@0: michael@0: var SWF_TAG_CODE_SET_BACKGROUND_COLOR = 9; michael@0: var PREFETCH_SIZE = 17 /* RECT */ + michael@0: 4 /* Frames rate and count */;; michael@0: stream.ensure(PREFETCH_SIZE); michael@0: var rectFieldSize = bytes[stream.pos] >> 3; michael@0: stream.pos += ((5 + 4 * rectFieldSize + 7) >> 3) + 4; // skipping other header fields michael@0: michael@0: // for now just sniffing background color michael@0: while (stream.pos < stream.end && michael@0: !backgroundColor) { michael@0: stream.ensure(2); michael@0: var tagCodeAndLength = stream.getUint16(stream.pos, true); michael@0: stream.pos += 2; michael@0: var tagCode = tagCodeAndLength >> 6; michael@0: var length = tagCodeAndLength & 0x3F; michael@0: if (length == 0x3F) { michael@0: stream.ensure(4); michael@0: length = stream.getInt32(stream.pos, true); michael@0: stream.pos += 4; michael@0: if (length < 0) throw new Error('invalid length'); michael@0: } michael@0: stream.ensure(length); michael@0: switch (tagCode) { michael@0: case SWF_TAG_CODE_SET_BACKGROUND_COLOR: michael@0: backgroundColor = 'rgb(' + bytes[stream.pos] + ', ' + michael@0: bytes[stream.pos + 1] + ', ' + michael@0: bytes[stream.pos + 2] + ')'; michael@0: break; michael@0: } michael@0: stream.pos += length; michael@0: } michael@0: } catch (e) { michael@0: console.log('parsing aborted: ' + e); michael@0: } michael@0: if (backgroundColor) { michael@0: document.body.style.backgroundColor = backgroundColor; michael@0: } michael@0: } michael@0: michael@0: document.addEventListener('keydown', function (e) { michael@0: if (e.keyCode == 119 && e.ctrlKey) { // Ctrl+F8 michael@0: window.location.replace("data:application/x-moz-playpreview;,application/x-shockwave-flash,full,paused=true"); michael@0: } michael@0: }, false); michael@0: michael@0: function switchToFullMode() { michael@0: window.location.replace("data:application/x-moz-playpreview;,application/x-shockwave-flash,full"); michael@0: }