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: // Extension 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 e = document.createEvent('CustomEvent'); michael@0: e.initCustomEvent('shumway.message', true, false, michael@0: {action: action, data: data, sync: true}); michael@0: document.dispatchEvent(e); michael@0: return e.detail.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 e = document.createEvent('CustomEvent'); michael@0: e.initCustomEvent('shumway.message', true, false, michael@0: {action: action, data: data, sync: false}); michael@0: if (callback) { michael@0: if ('nextId' in FirefoxCom.request) { michael@0: FirefoxCom.request.nextId = 1; michael@0: } michael@0: var cookie = "requestId" + (FirefoxCom.request.nextId++); michael@0: e.detail.cookie = cookie; michael@0: e.detail.callback = true; michael@0: michael@0: document.addEventListener('shumway.response', function listener(event) { michael@0: if (cookie !== event.detail.cookie) michael@0: return; michael@0: michael@0: document.removeEventListener('shumway.response', listener, false); michael@0: michael@0: var response = event.detail.response; michael@0: return callback(response); michael@0: }, false); michael@0: } michael@0: return document.dispatchEvent(e); michael@0: }, michael@0: initJS: function (callback) { michael@0: FirefoxCom.request('externalCom', {action: 'init'}); michael@0: document.addEventListener('shumway.remote', function (e) { michael@0: e.detail.result = callback(e.detail.functionName, e.detail.args); michael@0: }, false); 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 playerglobalInfo = { michael@0: abcs: SHUMWAY_ROOT + "playerglobal/playerglobal.abcs", michael@0: catalog: SHUMWAY_ROOT + "playerglobal/playerglobal.json" michael@0: }; michael@0: michael@0: function runViewer() { michael@0: var flashParams = JSON.parse(FirefoxCom.requestSync('getPluginParams', null)); michael@0: FileLoadingService.setBaseUrl(flashParams.baseUrl); michael@0: michael@0: movieUrl = flashParams.url; michael@0: if (!movieUrl) { michael@0: console.log("no movie url provided -- stopping here"); michael@0: FirefoxCom.request('endActivation', null); michael@0: return; michael@0: } michael@0: michael@0: movieParams = flashParams.movieParams; michael@0: objectParams = flashParams.objectParams; michael@0: var isOverlay = flashParams.isOverlay; michael@0: pauseExecution = flashParams.isPausedAtStart; michael@0: michael@0: console.log("url=" + movieUrl + ";params=" + uneval(movieParams)); michael@0: if (movieParams.fmt_list && movieParams.url_encoded_fmt_stream_map) { michael@0: // HACK removing FLVs from the fmt_list michael@0: movieParams.fmt_list = movieParams.fmt_list.split(',').filter(function (s) { michael@0: var fid = s.split('/')[0]; michael@0: return fid !== '5' && fid !== '34' && fid !== '35'; // more? michael@0: }).join(','); michael@0: } michael@0: michael@0: parseSwf(movieUrl, movieParams, objectParams); michael@0: michael@0: if (isOverlay) { michael@0: document.getElementById('overlay').className = 'enabled'; michael@0: var fallbackDiv = document.getElementById('fallback'); michael@0: fallbackDiv.addEventListener('click', function(e) { michael@0: fallback(); michael@0: e.preventDefault(); michael@0: }); michael@0: var reportDiv = document.getElementById('report'); michael@0: reportDiv.addEventListener('click', function(e) { michael@0: reportIssue(); michael@0: e.preventDefault(); michael@0: }); michael@0: var fallbackMenu = document.getElementById('fallbackMenu'); michael@0: fallbackMenu.removeAttribute('hidden'); michael@0: fallbackMenu.addEventListener('click', fallback); michael@0: } michael@0: var showURLMenu = document.getElementById('showURLMenu'); michael@0: showURLMenu.addEventListener('click', showURL); michael@0: var inspectorMenu = document.getElementById('inspectorMenu'); michael@0: inspectorMenu.addEventListener('click', showInInspector); michael@0: var reportMenu = document.getElementById('reportMenu'); michael@0: reportMenu.addEventListener('click', reportIssue); michael@0: michael@0: document.getElementById('copyProfileMenu').addEventListener('click', copyProfile); michael@0: } michael@0: michael@0: function showURL() { michael@0: window.prompt("Copy to clipboard", movieUrl); michael@0: } michael@0: michael@0: function showInInspector() { michael@0: var base = "http://www.areweflashyet.com/shumway/examples/inspector/inspector.html?rfile="; michael@0: var params = ''; michael@0: for (var k in movieParams) { michael@0: params += '&' + k + '=' + encodeURIComponent(movieParams[k]); michael@0: } michael@0: window.open(base + encodeURIComponent(movieUrl) + params); michael@0: } michael@0: michael@0: function reportIssue() { michael@0: var duplicatesMap = Object.create(null); michael@0: var prunedExceptions = []; michael@0: avm2.exceptions.forEach(function(e) { michael@0: var ident = e.source + e.message + e.stack; michael@0: var entry = duplicatesMap[ident]; michael@0: if (!entry) { michael@0: entry = duplicatesMap[ident] = { michael@0: source: e.source, michael@0: message: e.message, michael@0: stack: e.stack, michael@0: count: 0 michael@0: }; michael@0: prunedExceptions.push(entry); michael@0: } michael@0: entry.count++; michael@0: }); michael@0: FirefoxCom.requestSync('reportIssue', JSON.stringify(prunedExceptions)); michael@0: } michael@0: michael@0: function copyProfile() { michael@0: function toArray(v) { michael@0: var array = []; michael@0: for (var i = 0; i < v.length; i++) { michael@0: array.push(v[i]); michael@0: } michael@0: return array; michael@0: } michael@0: var profile = { michael@0: loops: {counts: toArray($L), lines: $LL}, michael@0: functions: {counts: toArray($F), lines: $FL}, michael@0: allocations: {counts: toArray($A), lines: $AL} michael@0: }; michael@0: FirefoxCom.request('unsafeSetClipboard', JSON.stringify(profile)); michael@0: } michael@0: michael@0: var movieUrl, movieParams, objectParams; michael@0: michael@0: window.addEventListener("message", function handlerMessage(e) { michael@0: var args = e.data; michael@0: switch (args.callback) { michael@0: case "loadFile": michael@0: var session = FileLoadingService.sessions[args.sessionId]; michael@0: if (session) { michael@0: session.notify(args); michael@0: } michael@0: break; michael@0: } michael@0: }, true); michael@0: michael@0: var TelemetryService = { michael@0: reportTelemetry: function (data) { michael@0: FirefoxCom.request('reportTelemetry', data, null); michael@0: } michael@0: }; michael@0: michael@0: var FileLoadingService = { michael@0: get baseUrl() { return movieUrl; }, michael@0: nextSessionId: 1, // 0 - is reserved michael@0: sessions: [], michael@0: createSession: function () { michael@0: var sessionId = this.nextSessionId++; michael@0: return this.sessions[sessionId] = { michael@0: open: function (request) { michael@0: var self = this; michael@0: var path = FileLoadingService.resolveUrl(request.url); michael@0: console.log('Session #' + sessionId +': loading ' + path); michael@0: FirefoxCom.requestSync('loadFile', {url: path, method: request.method, michael@0: mimeType: request.mimeType, postData: request.data, michael@0: checkPolicyFile: request.checkPolicyFile, sessionId: sessionId}); michael@0: }, michael@0: notify: function (args) { michael@0: switch (args.topic) { michael@0: case "open": this.onopen(); break; michael@0: case "close": michael@0: this.onclose(); michael@0: FileLoadingService.sessions[sessionId] = null; michael@0: console.log('Session #' + sessionId +': closed'); michael@0: break; michael@0: case "error": michael@0: this.onerror && this.onerror(args.error); michael@0: break; michael@0: case "progress": michael@0: console.log('Session #' + sessionId + ': loaded ' + args.loaded + '/' + args.total); michael@0: this.onprogress && this.onprogress(args.array, {bytesLoaded: args.loaded, bytesTotal: args.total}); michael@0: break; michael@0: } michael@0: } michael@0: }; michael@0: }, michael@0: setBaseUrl: function (url) { michael@0: var a = document.createElement('a'); michael@0: a.href = url || '#'; michael@0: a.setAttribute('style', 'display: none;'); michael@0: document.body.appendChild(a); michael@0: FileLoadingService.baseUrl = a.href; michael@0: document.body.removeChild(a); michael@0: }, michael@0: resolveUrl: function (url) { michael@0: if (url.indexOf('://') >= 0) return url; michael@0: michael@0: var base = FileLoadingService.baseUrl; michael@0: base = base.lastIndexOf('/') >= 0 ? base.substring(0, base.lastIndexOf('/') + 1) : ''; michael@0: if (url.indexOf('/') === 0) { michael@0: var m = /^[^:]+:\/\/[^\/]+/.exec(base); michael@0: if (m) base = m[0]; michael@0: } michael@0: return base + url; michael@0: } michael@0: }; michael@0: michael@0: function parseSwf(url, movieParams, objectParams) { michael@0: var enableVerifier = Shumway.AVM2.Runtime.enableVerifier; michael@0: var EXECUTION_MODE = Shumway.AVM2.Runtime.EXECUTION_MODE; michael@0: michael@0: var compilerSettings = JSON.parse( michael@0: FirefoxCom.requestSync('getCompilerSettings', null)); michael@0: enableVerifier.value = compilerSettings.verifier; michael@0: michael@0: // init misc preferences michael@0: turboMode.value = FirefoxCom.requestSync('getBoolPref', {pref: 'shumway.turboMode', def: false}); michael@0: hud.value = FirefoxCom.requestSync('getBoolPref', {pref: 'shumway.hud', def: false}); michael@0: forceHidpi.value = FirefoxCom.requestSync('getBoolPref', {pref: 'shumway.force_hidpi', def: false}); michael@0: dummyAnimation.value = FirefoxCom.requestSync('getBoolPref', {pref: 'shumway.dummyMode', def: false}); michael@0: michael@0: console.log("Compiler settings: " + JSON.stringify(compilerSettings)); michael@0: console.log("Parsing " + url + "..."); michael@0: function loaded() { michael@0: FirefoxCom.request('endActivation', null); michael@0: } michael@0: michael@0: createAVM2(builtinPath, playerglobalInfo, avm1Path, michael@0: compilerSettings.sysCompiler ? EXECUTION_MODE.COMPILE : EXECUTION_MODE.INTERPRET, michael@0: compilerSettings.appCompiler ? EXECUTION_MODE.COMPILE : EXECUTION_MODE.INTERPRET, michael@0: function (avm2) { michael@0: console.time("Initialize Renderer"); michael@0: SWF.embed(url, document, document.getElementById("viewer"), { michael@0: url: url, michael@0: movieParams: movieParams, michael@0: objectParams: objectParams, michael@0: onComplete: loaded, michael@0: onBeforeFrame: frame michael@0: }); michael@0: }); michael@0: } michael@0: michael@0: var pauseExecution = false; michael@0: var initializeFrameControl = true; michael@0: function frame(e) { michael@0: if (initializeFrameControl) { michael@0: // marking that movie is started michael@0: document.body.classList.add("started"); michael@0: michael@0: TelemetryService.reportTelemetry({topic: "firstFrame"}); michael@0: michael@0: // skipping frame 0 michael@0: initializeFrameControl = false; michael@0: return; michael@0: } michael@0: if (pauseExecution) { michael@0: e.cancel = true; 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: pauseExecution = !pauseExecution; michael@0: } michael@0: }, false);