diff -r 000000000000 -r 6474c204b198 browser/extensions/shumway/content/web/preview.js --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/browser/extensions/shumway/content/web/preview.js Wed Dec 31 06:09:35 2014 +0100 @@ -0,0 +1,219 @@ +/* -*- Mode: js; js-indent-level: 2; indent-tabs-mode: nil; tab-width: 2 -*- */ +/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */ +/* + * Copyright 2013 Mozilla Foundation + * + * 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. + */ + + +// Extenstion communication object +var FirefoxCom = (function FirefoxComClosure() { + return { + /** + * Creates an event that the extension is listening for and will + * synchronously respond to. + * NOTE: It is reccomended to use request() instead since one day we may not + * be able to synchronously reply. + * @param {String} action The action to trigger. + * @param {String} data Optional data to send. + * @return {*} The response. + */ + requestSync: function(action, data) { + var request = document.createTextNode(''); + document.documentElement.appendChild(request); + + var sender = document.createEvent('CustomEvent'); + sender.initCustomEvent('shumway.message', true, false, + {action: action, data: data, sync: true}); + request.dispatchEvent(sender); + var response = sender.detail.response; + document.documentElement.removeChild(request); + return response; + }, + /** + * Creates an event that the extension is listening for and will + * asynchronously respond by calling the callback. + * @param {String} action The action to trigger. + * @param {String} data Optional data to send. + * @param {Function} callback Optional response callback that will be called + * with one data argument. + */ + request: function(action, data, callback) { + var request = document.createTextNode(''); + request.setUserData('action', action, null); + request.setUserData('data', data, null); + request.setUserData('sync', false, null); + if (callback) { + request.setUserData('callback', callback, null); + + document.addEventListener('shumway.response', function listener(event) { + var node = event.target, + response = event.detail.response; + + document.documentElement.removeChild(node); + + document.removeEventListener('shumway.response', listener, false); + return callback(response); + }, false); + } + document.documentElement.appendChild(request); + + var sender = document.createEvent('CustomEvent'); + sender.initCustomEvent('shumway.message', true, false, + {action: action, data: data, sync: false}); + return request.dispatchEvent(sender); + } + }; +})(); + +function fallback() { + FirefoxCom.requestSync('fallback', null) +} + +var BYTES_TO_LOAD = 32768; +var BYTES_TO_PARSE = 32768; + +function runSniffer() { + var flashParams = JSON.parse(FirefoxCom.requestSync('getPluginParams', null)); + document.head.getElementsByTagName('base')[0].href = flashParams.baseUrl; + movieUrl = flashParams.url; + document.getElementById('playbutton').addEventListener('click', function() { + switchToFullMode(); + }); + document.getElementById('fullmode').addEventListener('click', function() { + switchToFullMode(); + return false; + }); + document.getElementById('fallback').addEventListener('click', function() { + fallback(); + return false; + }); + FirefoxCom.requestSync('loadFile', {url: movieUrl, sessionId: 0, limit: BYTES_TO_LOAD}); +} + +var subscription, movieUrl, buffers = [];; + +addEventListener("message", function handlerMessage(e) { + var args = e.data; + switch (args.callback) { + case "loadFile": + if (args.sessionId != 0) { + return; + } + switch (args.topic) { + case "progress": + buffers.push(args.array); + break; + case "error": + console.error('Unable to download ' + movieUrl + ': ' + args.error); + break; + case "close": + parseSwf(); + break; + } + break; + } +}, true); + +function inflateData(bytes, outputLength) { + verifyDeflateHeader(bytes); + var stream = new Stream(bytes, 2); + var output = { + data: new Uint8Array(outputLength), + available: 0, + completed: false + }; + var state = {}; + // inflate while we can + try { + do { + inflateBlock(stream, output, state); + } while (!output.completed && stream.pos < stream.end + && output.available < outputLength); + } catch (e) { + console.log('inflate aborted: ' + e); + } + return new Stream(output.data, 0, Math.min(output.available, outputLength)); +} + +function parseSwf() { + var sum = 0; + for (var i = 0; i < buffers.length; i++) + sum += buffers[i].length; + var data = new Uint8Array(sum), j = 0; + for (var i = 0; i < buffers.length; i++) { + data.set(buffers[i], j); j += buffers[i].length; + } + + var backgroundColor; + try { + var magic1 = data[0]; + var magic2 = data[1]; + var magic3 = data[2]; + if ((magic1 !== 70 && magic1 !== 67) || magic2 !== 87 || magic3 !== 83) + throw new Error('unsupported file format'); + + var compressed = magic1 === 67; + var stream = compressed ? inflateData(data.subarray(8), BYTES_TO_PARSE) : + new Stream(data, 8, data.length - 8); + var bytes = stream.bytes; + + var SWF_TAG_CODE_SET_BACKGROUND_COLOR = 9; + var PREFETCH_SIZE = 17 /* RECT */ + + 4 /* Frames rate and count */;; + stream.ensure(PREFETCH_SIZE); + var rectFieldSize = bytes[stream.pos] >> 3; + stream.pos += ((5 + 4 * rectFieldSize + 7) >> 3) + 4; // skipping other header fields + + // for now just sniffing background color + while (stream.pos < stream.end && + !backgroundColor) { + stream.ensure(2); + var tagCodeAndLength = stream.getUint16(stream.pos, true); + stream.pos += 2; + var tagCode = tagCodeAndLength >> 6; + var length = tagCodeAndLength & 0x3F; + if (length == 0x3F) { + stream.ensure(4); + length = stream.getInt32(stream.pos, true); + stream.pos += 4; + if (length < 0) throw new Error('invalid length'); + } + stream.ensure(length); + switch (tagCode) { + case SWF_TAG_CODE_SET_BACKGROUND_COLOR: + backgroundColor = 'rgb(' + bytes[stream.pos] + ', ' + + bytes[stream.pos + 1] + ', ' + + bytes[stream.pos + 2] + ')'; + break; + } + stream.pos += length; + } + } catch (e) { + console.log('parsing aborted: ' + e); + } + if (backgroundColor) { + document.body.style.backgroundColor = backgroundColor; + } +} + +document.addEventListener('keydown', function (e) { + if (e.keyCode == 119 && e.ctrlKey) { // Ctrl+F8 + window.location.replace("data:application/x-moz-playpreview;,application/x-shockwave-flash,full,paused=true"); + } +}, false); + +function switchToFullMode() { + window.location.replace("data:application/x-moz-playpreview;,application/x-shockwave-flash,full"); +}