diff --git a/Gruntfile.js b/Gruntfile.js index 535ac130e..a4286d9da 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -103,6 +103,9 @@ module.exports = function(grunt) { (grunt.option('rebuild') ? ' -r' : ''), cwd: 'utils/playerglobal-builder' }, + debug_server: { + cmd: 'node examples/inspector/debug/server.js' + }, gate: { cmd: '"utils/jsshell/js" build/ts/shell.js -x -g ' + (grunt.option('verbose') ? '-v ' : '') + diff --git a/examples/inspector/debug/pingpong.js b/examples/inspector/debug/pingpong.js new file mode 100644 index 000000000..2438f22f7 --- /dev/null +++ b/examples/inspector/debug/pingpong.js @@ -0,0 +1,130 @@ +/* + * Copyright 2015 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. + */ + +// Simple class for synchronous XHR communication. +// See also examples/inspector/debug/server.js. + +var PingPongConnection = (function () { + function PingPongConnection(url, onlySend) { + this.url = url; + this.onData = null; + this.onError = null; + this.currentXhr = null; + this.closed = false; + + if (!onlySend) { + this.idle(); + } + } + + PingPongConnection.prototype = { + idle: function () { + function requestIncoming(connection) { + var xhr = new XMLHttpRequest(); + xhr.open('GET', connection.url + '?idle', true); + xhr.onload = function () { + if (xhr.status === 204 && + xhr.getResponseHeader('X-PingPong-Error') === 'timeout') { + requestIncoming(connection); + return; + } + if (xhr.status === 200) { + var result; + if (connection.onData) { + var response = xhr.responseText; + result = connection.onData(response ? JSON.parse(response) : undefined); + } + if (xhr.getResponseHeader('X-PingPong-Async') === '1') { + requestIncoming(connection); + } else { + sendResponse(connection, result); + } + return; + } + + if (connection.onError) { + connection.onError(xhr.statusText); + } + }; + xhr.onerror = function () { + if (connection.onError) { + connection.onError(xhr.error); + } + }; + xhr.send(); + connection.currentXhr = xhr; + } + function sendResponse(connection, result) { + var xhr = new XMLHttpRequest(); + xhr.open('POST', connection.url + '?response', false); + xhr.onload = function () { + if (xhr.status !== 204) { + if (connection.onError) { + connection.onError(xhr.statusText); + } + } + requestIncoming(connection); + }; + xhr.onerror = function () { + if (connection.onError) { + connection.onError(xhr.error); + } + }; + xhr.send(result === undefined ? '' : JSON.stringify(result)); + connection.currentXhr = xhr; + } + requestIncoming(this); + }, + send: function (data, async, timeout) { + if (this.closed) { + throw new Error('connection closed'); + } + + async = !!async; + timeout |= 0; + + var encoded = data === undefined ? '' : JSON.stringify(data); + if (async) { + var xhr = new XMLHttpRequest(); + xhr.open('POST', this.url + '?async', true); + xhr.send(encoded); + return; + } else { + var xhr = new XMLHttpRequest(); + xhr.open('POST', this.url, false); + if (timeout > 0) { + xhr.setRequestHeader('X-PingPong-Timeout', timeout); + } + xhr.send(encoded); + if (xhr.status === 204 && + xhr.getResponseHeader('X-PingPong-Error') === 'timeout') { + throw new Error('sync request timeout'); + } + var response = xhr.responseText; + return response ? JSON.parse(response) : undefined; + } + }, + close: function () { + if (this.currentXhr) { + this.currentXhr.abort(); + this.currentXhr = null; + } + this.closed = true; + } + }; + + return PingPongConnection; +})(); diff --git a/examples/inspector/debug/server.js b/examples/inspector/debug/server.js new file mode 100644 index 000000000..15b6bb5b6 --- /dev/null +++ b/examples/inspector/debug/server.js @@ -0,0 +1,217 @@ +/* + * Copyright 2015 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. + */ +/*jslint node: true */ + +// Simple HTTP server for synchronous XHR communication. +// See also examples/inspector/debug/pingpong.js. + +'use strict'; + +var http = require('http'); + +var ALLOW_FROM_DOMAIN = 'http://localhost:8000'; +var DEFAULT_BIND_HOST = 'localhost'; +var DEFAULT_BIND_PORT = 8010; + +var IDLE_TIMEOUT = 500; +var SYNC_TIMEOUT = 120000; + +var incomingData = {}; +var incomingResponse = {}; +var outgoingResponse = {}; +var currentReading = []; + +var verbose = false; + +function WebServer() { + this.host = DEFAULT_BIND_HOST; + this.port = DEFAULT_BIND_PORT; + this.server = null; +} +WebServer.prototype = { + start: function (callback) { + this.server = http.createServer(this._handler.bind(this)); + this.server.listen(this.port, this.host, callback); + console.log( + 'Server running at http://' + this.host + ':' + this.port + '/'); + console.log('Allowing requests from: ' + ALLOW_FROM_DOMAIN); + }, + stop: function (callback) { + this.server.close(callback); + this.server = null; + }, + _handler: function (request, response) { + function setStandardHeaders(response) { + response.setHeader('Access-Control-Allow-Origin', ALLOW_FROM_DOMAIN); + response.setHeader('Access-Control-Allow-Headers', 'Content-Type,X-PingPong-Timeout'); + response.setHeader('Access-Control-Expose-Headers', 'Content-Type,X-PingPong-Async,X-PingPong-From,X-PingPong-Error'); + response.setHeader('Content-Type', 'text/plain'); + response.setHeader('Cache-Control', 'no-cache, no-store, must-revalidate'); + response.setHeader('Pragma', 'no-cache'); + response.setHeader('Expires', 0); + } + + function sendData(response, data, isAsync, fromId) { + setStandardHeaders(response); + response.setHeader('Content-Type', 'text/plain'); + if (isAsync) { + response.setHeader('X-PingPong-Async', 1); + } + response.setHeader('X-PingPong-From', fromId); + response.writeHead(200); + response.end(data); + } + + function sendNoData(response) { + setStandardHeaders(response); + response.writeHead(204); + response.end(); + } + + function sendTimeout(response) { + setStandardHeaders(response); + response.setHeader('X-PingPong-Error', 'timeout'); + response.writeHead(204); + response.end(); + } + + var method = request.method; + if (request.method === 'OPTIONS') { + setStandardHeaders(response); + response.writeHead(200); + response.end(); + return; + } + + var url = request.url; + var urlParts = /([^?]*)((?:\?(.*))?)/.exec(url); + var pathParts = urlParts[1].split('/'); + var queryPart = urlParts[3]; + + var sessionId = pathParts[1], fromId = pathParts[2], toId = pathParts[3]; + var isResponse = queryPart === 'response', isAsync = queryPart === 'async'; + verbose && console.log(sessionId + ': ' + fromId + '->' + toId + ' ' + + isResponse + ' ' + isAsync + ' ' + request.method); + var keyId = sessionId + '_' + fromId + '_' + toId; + var reverseKeyId = sessionId + '_' + toId + '_' + fromId; + + if (request.method === 'POST') { + response.on('close', function () { + verbose && console.log('connection closed'); // TODO client closed without response.end + }); + + var body = ''; + request.on('data', function (data) { + body += data; + }); + request.on('end', function () { + verbose && console.log(' ... ' + body.substring(0, 140)); + item.isReady = true; + while (currentReading.length > 0 && currentReading[0].isReady) { + currentReading.shift().fn(); + } + }); + + var item = { + isReady: false, + fn: function () { + if (isResponse) { + if (outgoingResponse[reverseKeyId]) { + sendData(outgoingResponse[reverseKeyId].shift().response, body, true, fromId); + if (outgoingResponse[reverseKeyId].length === 0) { + delete outgoingResponse[reverseKeyId]; + } + } else { + console.error('Out of sequence response for ' + reverseKeyId); + } + sendNoData(response); + } else { + if (!isAsync) { + if (!outgoingResponse[keyId]) { + outgoingResponse[keyId] = []; + } + var requestTimeout = +request.headers['x-pingpong-timeout']; + var syncTimeout = requestTimeout || SYNC_TIMEOUT; + outgoingResponse[keyId].push({response: response}); + setTimeout(function () { + var responses = outgoingResponse[keyId]; + if (!responses) { + return; + } + for (var i = 0; i < responses.length; i++) { + if (responses[i].response === response) { + if (responses.length === 1) { + delete outgoingResponse[keyId]; + } else { + responses.splice(i, 1); + } + sendTimeout(response); + if (!requestTimeout) { + console.error('Sync request timeout: ' + keyId); + } + break; + } + } + }, syncTimeout); + } else { + sendNoData(response); + } + if (incomingResponse[reverseKeyId]) { + sendData(incomingResponse[reverseKeyId].response, body, isAsync, fromId); + delete incomingResponse[reverseKeyId]; + } else { + if (!incomingData[reverseKeyId]) { + incomingData[reverseKeyId] = []; + } + incomingData[reverseKeyId].push({data: body, isAsync: isAsync}); + } + } + } + }; + currentReading.push(item); + return; + } + + if (request.method == 'GET' && !isResponse) { + if (incomingData[keyId]) { + var data = incomingData[keyId].shift(); + sendData(response, data.data, data.isAsync, toId); + if (incomingData[keyId].length === 0) { + delete incomingData[keyId]; + } + } else { + if (incomingResponse[keyId]) { + console.error('Double incoming response from ' + keyId); + } + incomingResponse[keyId] = {response: response}; + } + setTimeout(function () { + if (incomingResponse[keyId] && incomingResponse[keyId].response === response) { + delete incomingResponse[keyId]; + sendTimeout(response); + } + }, IDLE_TIMEOUT); + return; + } + + setStandardHeaders(response); + response.writeHead(500); + response.end('Invalid request'); + } +}; + +var server = new WebServer(); +server.start(); diff --git a/examples/inspector/inspector.html b/examples/inspector/inspector.html index 5b9b2746a..90e130948 100644 --- a/examples/inspector/inspector.html +++ b/examples/inspector/inspector.html @@ -160,6 +160,7 @@ limitations under the License. + @@ -168,6 +169,7 @@ limitations under the License. + diff --git a/examples/inspector/js/inspector.js b/examples/inspector/js/inspector.js index b9f443034..b9d34171e 100644 --- a/examples/inspector/js/inspector.js +++ b/examples/inspector/js/inspector.js @@ -82,6 +82,10 @@ if (queryVariables['rfile'] && !startPromise) { url: queryVariables['rfile'], args: parseQueryString(queryVariables['flashvars']) }); +} else { + if (state.remoteEnabled) { + initRemoteDebugging(); + } } if (startPromise) { startPromise.then(function (config) { @@ -106,6 +110,7 @@ function showOpenFileButton(show) { var flashOverlay; var currentSWFUrl; var currentPlayer; +var processExternalCommand; var easelHost; function runIFramePlayer(data) { @@ -130,11 +135,14 @@ function runIFramePlayer(data) { playerWorker.postMessage(data, '*'); easelHost = new Shumway.GFX.Window.WindowEaselHost(easel, playerWorker, window); + if (processExternalCommand) { + easelHost.processExternalCommand = processExternalCommand; + } }); container.appendChild(playerWorkerIFrame); } -function executeFile(file, buffer, movieParams) { +function executeFile(file, buffer, movieParams, remoteDebugging) { var filename = file.split('?')[0].split('#')[0]; if (state.useIFramePlayer && filename.endsWith(".swf")) { @@ -142,7 +150,8 @@ function executeFile(file, buffer, movieParams) { runIFramePlayer({sysMode: sysMode, appMode: appMode, movieParams: movieParams, file: file, asyncLoading: asyncLoading, stageAlign: state.salign, stageScale: state.scale, - fileReadChunkSize: state.fileReadChunkSize, loaderURL: state.loaderURL}); + fileReadChunkSize: state.fileReadChunkSize, loaderURL: state.loaderURL, + remoteDebugging: !!remoteDebugging}); return; } @@ -189,7 +198,15 @@ function executeFile(file, buffer, movieParams) { player.displayParameters = easel.getDisplayParameters(); player.loaderUrl = state.loaderURL; + if (remoteDebugging) { + Shumway.ExternalInterfaceService.instance = player.createExternalInterfaceService(); + } + easelHost = new Shumway.GFX.Test.TestEaselHost(easel); + if (processExternalCommand) { + easelHost.processExternalCommand = processExternalCommand; + } + player.load(file, buffer); currentSWFUrl = swfURL; diff --git a/examples/inspector/js/inspectorPlayer.js b/examples/inspector/js/inspectorPlayer.js index d7905d52d..2df62a6ba 100644 --- a/examples/inspector/js/inspectorPlayer.js +++ b/examples/inspector/js/inspectorPlayer.js @@ -102,6 +102,10 @@ function runSwfPlayer(data) { fileReadChunkSize = data.fileReadChunkSize; var file = data.file; configureExternalInterfaceMocks(file); + if (data.remoteDebugging) { + Shumway.ClipboardService.instance = parent.Shumway.ClipboardService.instance; + Shumway.FileLoadingService.instance = parent.Shumway.FileLoadingService.instance; + } Shumway.createAVM2(builtinPath, playerglobalInfo, sysMode, appMode, function (avm2) { function runSWF(file) { var player = new Shumway.Player.Window.WindowPlayer(window); @@ -111,6 +115,10 @@ function runSwfPlayer(data) { player.displayParameters = displayParameters; player.loaderUrl = loaderURL; player.load(file); + + if (data.remoteDebugging) { + Shumway.ExternalInterfaceService.instance = player.createExternalInterfaceService(); + } } file = Shumway.FileLoadingService.instance.setBaseUrl(file); if (asyncLoading) { diff --git a/examples/inspector/js/inspectorSettings.js b/examples/inspector/js/inspectorSettings.js index b0fe996fc..6acbda774 100644 --- a/examples/inspector/js/inspectorSettings.js +++ b/examples/inspector/js/inspectorSettings.js @@ -35,7 +35,9 @@ var stateDefaults = { scale: 'noscale', width: -1, height: -1, - loaderURL: '' + loaderURL: '', + remoteEnabled: false, + remoteSWF: '' }; for (var option in stateDefaults) { @@ -115,6 +117,8 @@ var GUI = (function () { inspectorOptions.add(state, "width", -1, 4096, 1).onChange(saveInspectorOption); inspectorOptions.add(state, "height", -1, 4096, 1).onChange(saveInspectorOption); inspectorOptions.add(state, "loaderURL").onChange(saveInspectorOption); + inspectorOptions.add(state, "remoteEnabled").onChange(saveInspectorOption); + inspectorOptions.add(state, "remoteSWF").onChange(saveInspectorOption); //inspectorOptions.add(state, "mute").onChange(saveInspectorOption); if (state.folderOpen) { inspectorOptions.open(); diff --git a/examples/inspector/js/remoteDebugging.js b/examples/inspector/js/remoteDebugging.js new file mode 100644 index 000000000..7e86c80f4 --- /dev/null +++ b/examples/inspector/js/remoteDebugging.js @@ -0,0 +1,170 @@ +/* + * Copyright 2015 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. + */ + +var removeDebuggerBaseURL = 'http://localhost:8010'; + +var remoteDebuggerId; +var remoteDebugger; +var remoteDebuggerController; +function initRemoteDebugging() { + remoteDebuggerId = (Date.now() % 100000) + 2; + remoteDebugger = new PingPongConnection(removeDebuggerBaseURL + '/debug/' + remoteDebuggerId + '/1'); + remoteDebugger.onData = remoteDebugger_onData; + + remoteDebuggerController = new PingPongConnection(removeDebuggerBaseURL + '/debugController/1/2'); + remoteDebuggerController.onData = function (data) { + switch (data.action) { + case 'getDebugger': + if (data.swfUrl && data.swfUrl.indexOf(state.remoteSWF) === 0) { + return remoteDebuggerId; + } + return 0; + case 'enableDebugging': + var properties = document.getElementById('settingsContainer').querySelectorAll('.property-name'); + for (var i = 0; i < properties.length; i++) { + if (properties[i].textContent === 'remoteSWF') { + var input = properties[i].parentElement.getElementsByTagName('input')[0]; + input.value = data.swfUrl; + break; + } + } + state.remoteSWF = data.swfUrl; + saveInspectorState(); + return; + } + }; +} + +var externalInteraceCallback; +function remoteDebuggerInitServices() { + window.addEventListener('beforeunload', function(event) { + remoteDebugger.send({action: 'reload'}, true); + }); + processExternalCommand = function (command) { + switch (command.action) { + case 'isEnabled': + command.result = true; + break; + case 'initJS': + remoteDebuggerSendMessage({action: 'externalCom', data: {action: 'init'}, sync: true}); + externalInteraceCallback = function (functionName, args) { + return easelHost.sendExernalCallback(functionName, args); + }; + break; + default: + var result = remoteDebuggerSendMessage({action: 'externalCom', data: command, sync: true}); + command.result = result ? JSON.parse(result) : undefined; + break; + } + }; + Shumway.ClipboardService.instance = { + setClipboard: function (data) { + remoteDebuggerSendMessage({action: 'setClipboard', data: data}, false); + } + }; + Shumway.FileLoadingService.instance = { + baseUrl: null, + nextSessionId: 1, // 0 - is reserved + sessions: [], + createSession: function () { + var sessionId = this.nextSessionId++; + return this.sessions[sessionId] = { + open: function (request) { + var self = this; + var path = Shumway.FileLoadingService.instance.resolveUrl(request.url); + console.log('Session #' + sessionId + ': loading ' + path); + remoteDebuggerSendMessage({ + action: 'loadFile', + data: {url: path, method: request.method, + mimeType: request.mimeType, postData: request.data, + checkPolicyFile: request.checkPolicyFile, sessionId: sessionId} + }, true); + }, + notify: function (args) { + switch (args.topic) { + case "open": + this.onopen(); + break; + case "close": + this.onclose(); + Shumway.FileLoadingService.instance.sessions[sessionId] = null; + console.log('Session #' + sessionId + ': closed'); + break; + case "error": + this.onerror && this.onerror(args.error); + break; + case "progress": + console.log('Session #' + sessionId + ': loaded ' + args.loaded + '/' + args.total); + this.onprogress && this.onprogress(new Uint8Array(args.array), {bytesLoaded: args.loaded, bytesTotal: args.total}); + break; + } + }, + close: function () { + if (Shumway.FileLoadingService.instance.sessions[sessionId]) { + // TODO send abort + } + } + }; + }, + setBaseUrl: function (url) { + return Shumway.FileLoadingService.instance.baseUrl = url; + }, + resolveUrl: function (url) { + return new URL(url, Shumway.FileLoadingService.instance.baseUrl).href; + }, + navigateTo: function (url, target) { + remoteDebuggerSendMessage({ + action: 'navigateTo', + data: { + url: this.resolveUrl(url), + target: target + } + }, true); + } + }; +} + +function remoteDebuggerSendMessage(detail, async) { + return remoteDebugger.send({action: 'sendMessage', detail: detail}, async); +} + +function remoteDebugger_onData(data) { + switch (data.action) { + case 'runViewer': + showOpenFileButton(false); + remoteDebuggerInitServices(); + + var flashParams = JSON.parse(remoteDebuggerSendMessage({action: 'getPluginParams', data: null, sync: true})); + + var movieUrl = flashParams.url; + var movieParams = flashParams.movieParams; + executeFile(movieUrl, undefined, movieParams, true); + + remoteDebuggerSendMessage({action: 'endActivation', data: null}, true); + return; + case 'onExternalCallback': + var call = data.detail; + externalInteraceCallback(call.functionName, call.args); + return; + case 'onLoadFileCallback': + var args = data.detail; + var session = Shumway.FileLoadingService.instance.sessions[args.sessionId]; + if (session) { + session.notify(args); + } + return; + } +} diff --git a/extension/firefox/Makefile b/extension/firefox/Makefile index 584a58dee..e272409f0 100644 --- a/extension/firefox/Makefile +++ b/extension/firefox/Makefile @@ -27,6 +27,7 @@ build: ensureoutputdir cp -R ../../LICENSE content chrome bootstrap.js chrome.manifest icon.png icon64.png $(BUILD_DIR)/ sed s/\(SHUMWAY_VERSION\)/$(VERSION)/ install.rdf > $(BUILD_DIR)/install.rdf sed s/\(SHUMWAY_VERSION\)/$(VERSION)/ update.rdf > $(BUILD_DIR)/update.rdf + cp ../../examples/inspector/debug/pingpong.js $(BUILD_DIR)/chrome/ # Copying WebGL shaders mkdir -p $(BUILD_DIR)/content/gfx/gl/shaders cp ../../src/gfx/gl/shaders/combined.frag $(BUILD_DIR)/content/gfx/gl/shaders/ diff --git a/extension/firefox/chrome/content.js b/extension/firefox/chrome/content.js index 0f622f876..c4b0e8954 100644 --- a/extension/firefox/chrome/content.js +++ b/extension/firefox/chrome/content.js @@ -46,6 +46,10 @@ function sendMessage(action, data, sync, callbackCookie) { return Components.utils.cloneInto(result, content); } +function enableDebug() { + sendAsyncMessage('Shumway:enableDebug', null); +} + addMessageListener('Shumway:init', function (message) { sendAsyncMessage('Shumway:running', {}, { externalInterface: externalInterfaceWrapper @@ -55,6 +59,7 @@ addMessageListener('Shumway:init', function (message) { // up Xray wrappers. shumwayComAdapter = Components.utils.createObjectIn(content, {defineAs: 'ShumwayCom'}); Components.utils.exportFunction(sendMessage, shumwayComAdapter, {defineAs: 'sendMessage'}); + Components.utils.exportFunction(enableDebug, shumwayComAdapter, {defineAs: 'enableDebug'}); Object.defineProperties(shumwayComAdapter, { onLoadFileCallback: { value: null, writable: true }, onExternalCallback: { value: null, writable: true }, diff --git a/extension/firefox/chrome/viewer.wrapper.html b/extension/firefox/chrome/viewer.wrapper.html index 249795069..95ffa0adb 100644 --- a/extension/firefox/chrome/viewer.wrapper.html +++ b/extension/firefox/chrome/viewer.wrapper.html @@ -36,11 +36,23 @@ limitations under the License. line-height: 0; border: 0px none; } + + body.remoteStopped { + background-color: red; + } + body.remoteDebug { + background-color: green; + } + body.remoteReload { + background-color: yellow; + } + + diff --git a/extension/firefox/chrome/viewerDebugger.js b/extension/firefox/chrome/viewerDebugger.js new file mode 100644 index 000000000..47bb5303f --- /dev/null +++ b/extension/firefox/chrome/viewerDebugger.js @@ -0,0 +1,80 @@ +/* + * Copyright 2015 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. + */ + +Components.utils.import('resource://gre/modules/Services.jsm'); + +var DebugUtils = (function () { + var baseUrl = null; + + function getBaseUrl() { + if (baseUrl === null) { + try { + baseUrl = Services.prefs.getCharPref('shumway.debug.url'); + } catch (e) { + baseUrl = 'http://localhost:8010'; + } + } + return baseUrl; + } + + function getEnabledDebuggerId(swfUrl) { + var id = 0; + var url = getBaseUrl() + '/debugController/2/1'; + var connection = new PingPongConnection(url, true); + try { + id = connection.send({action: 'getDebugger', swfUrl: swfUrl}, + false, 500); + } catch (e) { + // ignoring failed send request + } + connection.close(); + return id; + } + + function enableDebug(swfUrl) { + var url = getBaseUrl() + '/debugController/2/1'; + var connection = new PingPongConnection(url, true); + try { + connection.send({action: 'enableDebugging', swfUrl: swfUrl}, true); + } catch (e) { + // ignoring failed send request + } + connection.close(); + } + + function createDebuggerConnection(swfUrl) { + var debuggerId = getEnabledDebuggerId(swfUrl); + if (!debuggerId) { + return null; + } + + var url = getBaseUrl() + '/debug/1/' + debuggerId; + console.log('Starting remote debugger with ' + url); + return new PingPongConnection(url); + } + + return { + get isEnabled() { + try { + return Services.prefs.getBoolPref('shumway.debug.enabled'); + } catch (e) { + return false; + } + }, + enableDebug: enableDebug, + createDebuggerConnection: createDebuggerConnection + }; +})(); diff --git a/extension/firefox/chrome/viewerWrapper.js b/extension/firefox/chrome/viewerWrapper.js index 0add126eb..08b057ea0 100644 --- a/extension/firefox/chrome/viewerWrapper.js +++ b/extension/firefox/chrome/viewerWrapper.js @@ -53,6 +53,7 @@ function runViewer() { // ShumwayStreamConverter. var shumwayComAdapter = Components.utils.createObjectIn(childWindow, {defineAs: 'ShumwayCom'}); Components.utils.exportFunction(sendMessage, shumwayComAdapter, {defineAs: 'sendMessage'}); + Components.utils.exportFunction(enableDebug, shumwayComAdapter, {defineAs: 'enableDebug'}); Object.defineProperties(shumwayComAdapter, { onLoadFileCallback: { value: null, writable: true }, onExternalCallback: { value: null, writable: true }, @@ -117,6 +118,10 @@ function runViewer() { return window.notifyShumwayMessage(detail); }); + messageManager.addMessageListener('Shumway:enableDebug', function (message) { + enableDebug(); + }); + window.onExternalCallback = function (call) { return externalInterface.callback(JSON.stringify(call)); }; @@ -135,7 +140,71 @@ function runViewer() { messageManager.sendAsyncMessage('Shumway:init', {}); } + + function handleDebug(connection) { + viewer.parentNode.removeChild(viewer); // we don't need viewer anymore + document.body.className = 'remoteDebug'; + + function sendMessage(data) { + var detail = { + action: data.action, + data: data.data, + sync: data.sync + }; + if (data.callback) { + detail.callback = true; + detail.cookie = data.cookie; + } + return window.notifyShumwayMessage(detail); + } + + connection.onData = function (data) { + switch (data.action) { + case 'sendMessage': + return sendMessage(data.detail); + case 'reload': + document.body.className = 'remoteReload'; + setTimeout(function () { + window.top.location.reload(); + }, 1000); + return; + } + }; + + window.onExternalCallback = function (call) { + return connection.send({action: 'onExternalCallback', detail: call}); + }; + + window.onMessageCallback = function (response) { + return connection.send({action: 'onMessageCallback', detail: response}); + }; + + window.onLoadFileCallback = function (args) { + if (args.array) { + args.array = Array.prototype.slice.call(args.array, 0); + } + return connection.send({action: 'onLoadFileCallback', detail: args}, true); + }; + + connection.send({action: 'runViewer'}, true); + } + + function enableDebug() { + DebugUtils.enableDebug(window.swfUrlLoading); + setTimeout(function () { + window.top.location.reload(); + }, 1000); + } + promise.then(function (oop) { + if (DebugUtils.isEnabled) { + var debuggerConnection = DebugUtils.createDebuggerConnection(window.swfUrlLoading); + if (debuggerConnection) { + handleDebug(debuggerConnection); + return; + } + } + if (oop) { handlerOOP(); } else { diff --git a/extension/firefox/content/ShumwayStreamConverter.jsm b/extension/firefox/content/ShumwayStreamConverter.jsm index 910548536..9eb90140d 100644 --- a/extension/firefox/content/ShumwayStreamConverter.jsm +++ b/extension/firefox/content/ShumwayStreamConverter.jsm @@ -411,7 +411,7 @@ ChromeActions.prototype = { var position = e.loaded; var data = new Uint8Array(xhr.response); notifyLoadFileListener({callback:"loadFile", sessionId: sessionId, - topic: "progress", array: data, loaded: e.loaded, total: e.total}); + topic: "progress", array: data, loaded: position, total: e.total}); lastPosition = position; if (limit && e.total >= limit) { xhr.abort(); @@ -1034,6 +1034,8 @@ ShumwayStreamConverterBase.prototype = { return; } + domWindow.swfUrlLoading = actions.url; + // Report telemetry on amount of swfs on the page if (actions.isOverlay) { // Looking for last actions with same baseUrl diff --git a/extension/firefox/content/web/viewer.html b/extension/firefox/content/web/viewer.html index 0c03147d9..47a930ce8 100644 --- a/extension/firefox/content/web/viewer.html +++ b/extension/firefox/content/web/viewer.html @@ -111,6 +111,7 @@ limitations under the License. + diff --git a/extension/firefox/content/web/viewer.js b/extension/firefox/content/web/viewer.js index 717c83873..75b2305a0 100644 --- a/extension/firefox/content/web/viewer.js +++ b/extension/firefox/content/web/viewer.js @@ -165,6 +165,13 @@ function runViewer() { var version = Shumway.version || ''; document.getElementById('aboutMenu').label = document.getElementById('aboutMenu').label.replace('%version%', version); + + var debugMenuEnabled = FirefoxCom.requestSync('getBoolPref', {pref: 'shumway.debug.enabled', def: false}); + if (debugMenuEnabled) { + document.getElementById('debugMenu').addEventListener('click', enableDebug); + } else { + document.getElementById('debugMenu').remove(); + } } function showURL() { @@ -205,6 +212,10 @@ function showAbout() { window.open('http://areweflashyet.com/'); } +function enableDebug() { + ShumwayCom.enableDebug(); +} + var movieUrl, movieParams, objectParams; window.addEventListener("message", function handlerMessage(e) { diff --git a/extension/mozcentral/Makefile b/extension/mozcentral/Makefile index 87f8b1603..0ce8045e8 100644 --- a/extension/mozcentral/Makefile +++ b/extension/mozcentral/Makefile @@ -26,6 +26,7 @@ build: ensureoutputdir echo "Creating mozcentral package" mkdir -p $(BUILD_EXTENSION_DIR) cp -R ../../LICENSE $(EXTENSION_SRC)/chrome $(EXTENSION_SRC)/content $(BUILD_EXTENSION_DIR)/ + cp ../../examples/inspector/debug/pingpong.js $(BUILD_DIR)/chrome/ cp -R browser $(BUILD_DIR)/ mkdir -p $(BUILD_EXTENSION_DIR)/content/gfx/gl/shaders cp ../../src/gfx/gl/shaders/combined.frag $(BUILD_EXTENSION_DIR)/content/gfx/gl/shaders/ diff --git a/src/avm2/natives/byteArray.ts b/src/avm2/natives/byteArray.ts index a0934d7f4..c04cbbe02 100644 --- a/src/avm2/natives/byteArray.ts +++ b/src/avm2/natives/byteArray.ts @@ -113,7 +113,7 @@ module Shumway.AVM2.AS { buffer = new Uint8Array(source).buffer; } else if ('buffer' in source) { if (source.buffer instanceof ArrayBuffer) { - buffer = new Uint8Array(source).buffer; + buffer = new Uint8Array(source.buffer).buffer; } else if (source.buffer instanceof Uint8Array) { var begin = source.buffer.byteOffset; buffer = source.buffer.buffer.slice(begin, begin + source.buffer.length);