diff --git a/browser/extensions/shumway/content/ShumwayStreamConverter.jsm b/browser/extensions/shumway/content/ShumwayStreamConverter.jsm index 435b6e60ce2..0b2bab6a7e1 100644 --- a/browser/extensions/shumway/content/ShumwayStreamConverter.jsm +++ b/browser/extensions/shumway/content/ShumwayStreamConverter.jsm @@ -37,6 +37,16 @@ const MAX_CLIPBOARD_DATA_SIZE = 8000; Cu.import('resource://gre/modules/XPCOMUtils.jsm'); Cu.import('resource://gre/modules/Services.jsm'); Cu.import('resource://gre/modules/NetUtil.jsm'); +Cu.import("resource://gre/modules/AddonManager.jsm"); + +var shumwayVersion; +try { + AddonManager.getAddonByID("shumway@research.mozilla.org", function(addon) { + shumwayVersion = addon.version; + }); +} catch (ignored) { + +} XPCOMUtils.defineLazyModuleGetter(this, 'PrivateBrowsingUtils', 'resource://gre/modules/PrivateBrowsingUtils.jsm'); @@ -44,11 +54,13 @@ XPCOMUtils.defineLazyModuleGetter(this, 'PrivateBrowsingUtils', XPCOMUtils.defineLazyModuleGetter(this, 'ShumwayTelemetry', 'resource://shumway/ShumwayTelemetry.jsm'); -let appInfo = Cc['@mozilla.org/xre/app-info;1'].getService(Ci.nsIXULAppInfo); let Svc = {}; XPCOMUtils.defineLazyServiceGetter(Svc, 'mime', '@mozilla.org/mime;1', 'nsIMIMEService'); +let StringInputStream = Cc["@mozilla.org/io/string-input-stream;1"]; +let MimeInputStream = Cc["@mozilla.org/network/mime-input-stream;1"]; + function getBoolPref(pref, def) { try { return Services.prefs.getBoolPref(pref); @@ -188,6 +200,23 @@ function isShumwayEnabledFor(actions) { return true; } +function getVersionInfo() { + var versionInfo = { + geckoMstone : 'unknown', + geckoBuildID: 'unknown', + shumwayVersion: 'unknown' + }; + try { + versionInfo.geckoMstone = Services.prefs.getCharPref('gecko.mstone'); + versionInfo.geckoBuildID = Services.prefs.getCharPref('gecko.buildID'); + versionInfo.shumwayVersion = shumwayVersion; + } catch (e) { + console.warn('Error encountered while getting platform and shumway ' + + 'version info:', e); + } + return versionInfo; +} + function fallbackToNativePlugin(window, userAction, activateCTP) { var obj = window.frameElement; var doc = obj.ownerDocument; @@ -366,7 +395,7 @@ ChromeActions.prototype = { }); }, fallback: function(automatic) { - automatic = !!automatic; // cast to boolean + automatic = !!automatic; fallbackToNativePlugin(this.window, !automatic, automatic); }, setClipboard: function (data) { @@ -432,6 +461,26 @@ ChromeActions.prototype = { break; } }, + reportIssue: function(exceptions) { + var base = "http://shumway-issue-reporter.paas.allizom.org/input?"; + var windowUrl = this.window.parent.wrappedJSObject.location + ''; + var params = 'url=' + encodeURIComponent(windowUrl); + params += '&swf=' + encodeURIComponent(this.url); + var versions = getVersionInfo(); + params += '&ffbuild=' + encodeURIComponent(versions.geckoMstone + ' (' + + versions.geckoBuildID + ')'); + params += '&shubuild=' + encodeURIComponent(versions.shumwayVersion); + var postDataStream = StringInputStream. + createInstance(Ci.nsIStringInputStream); + postDataStream.data = 'exceptions=' + encodeURIComponent(exceptions); + var postData = MimeInputStream.createInstance(Ci.nsIMIMEInputStream); + postData.addHeader("Content-Type", "application/x-www-form-urlencoded"); + postData.addContentLength = true; + postData.setData(postDataStream); + this.window.openDialog('chrome://browser/content', '_blank', + 'all,dialog=no', base + params, null, null, + postData); + }, externalCom: function (data) { if (!this.allowScriptAccess) return; @@ -459,6 +508,9 @@ ChromeActions.prototype = { case 'unregister': return embedTag.__flash__unregisterCallback(data.functionName); } + }, + getWindowUrl: function() { + return this.window.parent.wrappedJSObject.location + ''; } }; diff --git a/browser/extensions/shumway/content/shumway-worker.js b/browser/extensions/shumway/content/shumway-worker.js index e82777de4a8..6032e7e6b24 100644 --- a/browser/extensions/shumway/content/shumway-worker.js +++ b/browser/extensions/shumway/content/shumway-worker.js @@ -3875,6 +3875,13 @@ function createParsingContext(commitData) { command: 'complete', stats: stats }); + }, + onexception: function (e) { + commitData({ + type: 'exception', + message: e.message, + stack: e.stack + }); } }; } @@ -5813,7 +5820,7 @@ var readHeader = function readHeader($bytes, $stream, $, swfVersion, tagCode) { global['tagHandler'] = tagHandler; global['readHeader'] = readHeader; }(this)); -function readTags(context, stream, swfVersion, final, onprogress) { +function readTags(context, stream, swfVersion, final, onprogress, onexception) { var tags = context.tags; var bytes = stream.bytes; var lastSuccessfulPosition; @@ -5882,6 +5889,7 @@ function readTags(context, stream, swfVersion, final, onprogress) { } } catch (e) { if (e !== StreamNoDataError) { + onexception && onexception(e); throw e; } stream.pos = lastSuccessfulPosition; @@ -6041,7 +6049,7 @@ BodyParser.prototype = { finalBlock = progressInfo.bytesLoaded >= progressInfo.bytesTotal; } var readStartTime = performance.now(); - readTags(swf, stream, swfVersion, finalBlock, options.onprogress); + readTags(swf, stream, swfVersion, finalBlock, options.onprogress, options.onexception); swf.parseTime += performance.now() - readStartTime; var read = stream.pos; buffer.removeHead(read); diff --git a/browser/extensions/shumway/content/shumway.js b/browser/extensions/shumway/content/shumway.js index 3d804d42c2d..bdbf73144a1 100644 --- a/browser/extensions/shumway/content/shumway.js +++ b/browser/extensions/shumway/content/shumway.js @@ -5021,23 +5021,10 @@ var turboMode = rendererOptions.register(new Option('', 'turbo', 'boolean', fals var forceHidpi = rendererOptions.register(new Option('', 'forceHidpi', 'boolean', false, 'force hidpi')); var skipFrameDraw = rendererOptions.register(new Option('', 'skipFrameDraw', 'boolean', true, 'skip frame when not on time')); var hud = rendererOptions.register(new Option('', 'hud', 'boolean', false, 'show hud mode')); +var dummyAnimation = rendererOptions.register(new Option('', 'dummy', 'boolean', false, 'show test balls animation')); var enableConstructChildren = rendererOptions.register(new Option('', 'constructChildren', 'boolean', true, 'Construct Children')); var enableEnterFrame = rendererOptions.register(new Option('', 'enterFrame', 'boolean', true, 'Enter Frame')); var enableAdvanceFrame = rendererOptions.register(new Option('', 'advanceFrame', 'boolean', true, 'Advance Frame')); -if (typeof FirefoxCom !== 'undefined') { - turboMode.value = FirefoxCom.requestSync('getBoolPref', { - pref: 'shumway.turboMode', - def: false - }); - hud.value = FirefoxCom.requestSync('getBoolPref', { - pref: 'shumway.hud', - def: false - }); - forceHidpi.value = FirefoxCom.requestSync('getBoolPref', { - pref: 'shumway.force_hidpi', - def: false - }); -} var CanvasCache = { cache: [], getCanvas: function getCanvas(protoCanvas) { @@ -5433,13 +5420,7 @@ function RenderingContext(refreshStage, invalidPath) { function renderDisplayObject(child, ctx, context) { var m = child._currentTransform; if (m) { - if (m.a * m.d == m.b * m.c) { - ctx.closePath(); - ctx.rect(0, 0, 0, 0); - ctx.clip(); - } else { - ctx.transform(m.a, m.b, m.c, m.d, m.tx / 20, m.ty / 20); - } + ctx.transform(m.a, m.b, m.c, m.d, m.tx / 20, m.ty / 20); } if (!renderAsWireframe.value) { if (child._alpha !== 1) { @@ -5546,11 +5527,57 @@ function initializeHUD(stage, parentCanvas) { canvasContainer.style.width = '100%'; canvasContainer.style.height = '150px'; canvasContainer.style.backgroundColor = 'rgba(0, 0, 0, 0.4)'; + canvasContainer.style.pointerEvents = 'none'; parentCanvas.parentElement.appendChild(canvasContainer); hudTimeline = new Timeline(canvas); hudTimeline.setFrameRate(stage._frameRate); hudTimeline.refreshEvery(10); } +function createRenderDummyBalls(ctx, stage) { + var dummyBalls; + var radius = 10; + var speed = 1; + var m = stage._concatenatedTransform; + var scaleX = m.a, scaleY = m.d; + dummyBalls = []; + for (var i = 0; i < 10; i++) { + dummyBalls.push({ + position: { + x: radius + Math.random() * ((ctx.canvas.width - 2 * radius) / scaleX), + y: radius + Math.random() * ((ctx.canvas.height - 2 * radius) / scaleY) + }, + velocity: { + x: speed * (Math.random() - 0.5), + y: speed * (Math.random() - 0.5) + } + }); + } + ctx.fillStyle = 'black'; + ctx.lineWidth = 2; + ctx.fillRect(0, 0, ctx.canvas.width, ctx.canvas.height); + return function renderDummyBalls() { + ctx.fillStyle = 'black'; + ctx.fillRect(0, 0, ctx.canvas.width, ctx.canvas.height); + ctx.strokeStyle = 'green'; + dummyBalls.forEach(function (ball) { + var position = ball.position; + var velocity = ball.velocity; + ctx.beginPath(); + ctx.arc(position.x, position.y, radius, 0, Math.PI * 2, true); + ctx.stroke(); + var x = position.x + velocity.x; + var y = position.y + velocity.y; + if (x < radius || x > ctx.canvas.width / scaleX - radius) { + velocity.x *= -1; + } + if (y < radius || y > ctx.canvas.height / scaleY - radius) { + velocity.y *= -1; + } + position.x += velocity.x; + position.y += velocity.y; + }); + }; +} function renderStage(stage, ctx, events) { var frameWidth, frameHeight; if (!timeline && hud.value) { @@ -5608,68 +5635,17 @@ function renderStage(stage, ctx, events) { m.ty = offsetY * 20; } updateRenderTransform(); - var frameTime = 0; - var maxDelay = 1000 / stage._frameRate; - var nextRenderAt = performance.now(); + var frameScheduler = new FrameScheduler(); + stage._frameScheduler = frameScheduler; var requestAnimationFrame = window.requestAnimationFrame || window.mozRequestAnimationFrame || window.webkitRequestAnimationFrame || window.oRequestAnimationFrame || window.msRequestAnimationFrame || window.setTimeout; - var renderDummyBalls; - var dummyBalls; - if (typeof FirefoxCom !== 'undefined' && FirefoxCom.requestSync('getBoolPref', { - pref: 'shumway.dummyMode', - def: false - })) { - var radius = 10; - var speed = 1; - var m = stage._concatenatedTransform; - var scaleX = m.a, scaleY = m.d; - dummyBalls = []; - for (var i = 0; i < 10; i++) { - dummyBalls.push({ - position: { - x: radius + Math.random() * ((ctx.canvas.width - 2 * radius) / scaleX), - y: radius + Math.random() * ((ctx.canvas.height - 2 * radius) / scaleY) - }, - velocity: { - x: speed * (Math.random() - 0.5), - y: speed * (Math.random() - 0.5) - } - }); - } - ctx.fillStyle = 'black'; - ctx.lineWidth = 2; - ctx.fillRect(0, 0, ctx.canvas.width, ctx.canvas.height); - renderDummyBalls = function () { - ctx.fillStyle = 'black'; - ctx.fillRect(0, 0, ctx.canvas.width, ctx.canvas.height); - ctx.strokeStyle = 'green'; - dummyBalls.forEach(function (ball) { - var position = ball.position; - var velocity = ball.velocity; - ctx.beginPath(); - ctx.arc(position.x, position.y, radius, 0, Math.PI * 2, true); - ctx.stroke(); - var x = position.x + velocity.x; - var y = position.y + velocity.y; - if (x < radius || x > ctx.canvas.width / scaleX - radius) { - velocity.x *= -1; - } - if (y < radius || y > ctx.canvas.height / scaleY - radius) { - velocity.y *= -1; - } - position.x += velocity.x; - position.y += velocity.y; - }); - }; - } + var renderDummyBalls = dummyAnimation.value && createRenderDummyBalls(ctx, stage); console.timeEnd('Initialize Renderer'); console.timeEnd('Total'); var firstRun = true; var frameCount = 0; var frameFPSAverage = new metrics.Average(120); - function drawFrame(renderFrame, frameRequested) { - if (!skipFrameDraw.value) { - frameRequested = true; - } + var frameRequested = true; + function drawFrame(renderFrame, repaint) { sampleStart(); var refreshStage = false; if (stage._invalid) { @@ -5708,7 +5684,14 @@ function renderStage(stage, ctx, events) { stage._deferRenderEvent = false; domain.broadcastMessage('render', 'render'); } - if (isCanvasVisible(ctx.canvas) && (refreshStage || renderFrame) && frameRequested) { + var drawEnabled = isCanvasVisible(ctx.canvas) && (refreshStage || renderFrame) && (frameRequested || repaint || !skipFrameDraw.value); + if (drawEnabled && !repaint && skipFrameDraw.value && frameScheduler.shallSkipDraw) { + drawEnabled = false; + frameScheduler.skipDraw(); + traceRenderer.value && appendToFrameTerminal('Skip Frame Draw', 'red'); + } + if (drawEnabled) { + frameScheduler.startDraw(); var invalidPath = null; traceRenderer.value && frameWriter.enter('> Invalidation'); timelineEnter('invalidate'); @@ -5731,6 +5714,7 @@ function renderStage(stage, ctx, events) { invalidPath.draw(ctx); ctx.stroke(); } + frameScheduler.endDraw(); } if (mouseMoved && !disableMouseVisitor.value) { renderFrame && timelineEnter('mouse'); @@ -5757,10 +5741,7 @@ function renderStage(stage, ctx, events) { } sampleEnd(); } - var frameRequested = true; - var skipNextFrameDraw = false; (function draw() { - var now = performance.now(); var renderFrame = true; if (events.onBeforeFrame) { var e = { @@ -5769,29 +5750,20 @@ function renderStage(stage, ctx, events) { events.onBeforeFrame(e); renderFrame = !e.cancel; } - frameTime = now; - if (renderFrame && renderDummyBalls) { - renderDummyBalls(); + if (renderDummyBalls) { + if (renderFrame) { + renderDummyBalls(); + events.onAfterFrame && events.onAfterFrame(); + } + setTimeout(draw); return; } - drawFrame(renderFrame, frameRequested && !skipNextFrameDraw); + frameScheduler.startFrame(stage._frameRate); + drawFrame(renderFrame, false); + frameScheduler.endFrame(); frameRequested = false; - maxDelay = 1000 / stage._frameRate; - if (!turboMode.value) { - nextRenderAt += maxDelay; - var wasLate = false; - while (nextRenderAt < now) { - wasLate = true; - nextRenderAt += maxDelay; - } - if (wasLate && !skipNextFrameDraw) { - skipNextFrameDraw = true; - traceRenderer.value && appendToFrameTerminal('Skip Frame Draw', 'red'); - } else { - skipNextFrameDraw = false; - } - } else { - nextRenderAt = now; + if (!frameScheduler.isOnTime) { + traceRenderer.value && appendToFrameTerminal('Frame Is Late', 'red'); } if (renderFrame && events.onAfterFrame) { events.onAfterFrame(); @@ -5802,19 +5774,111 @@ function renderStage(stage, ctx, events) { } return; } - setTimeout(draw, Math.max(0, nextRenderAt - performance.now())); + setTimeout(draw, turboMode.value ? 0 : frameScheduler.nextFrameIn); }()); (function frame() { if (renderingTerminated) { return; } - if (stage._invalid || stage._mouseMoved) { + frameRequested = true; + if ((stage._invalid || stage._mouseMoved) && !renderDummyBalls) { drawFrame(false, true); } - frameRequested = true; requestAnimationFrame(frame); }()); } +var FrameScheduler = function () { + var STATS_TO_REMEMBER = 50; + var MAX_DRAWS_TO_SKIP = 2; + var INTERVAL_PADDING_MS = 4; + var SPEED_ADJUST_RATE = 0.9; + function FrameScheduler() { + this._drawStats = []; + this._drawStatsSum = 0; + this._drawStarted = 0; + this._drawsSkipped = 0; + this._expectedNextFrameAt = performance.now(); + this._onTime = true; + this._trackDelta = false; + this._delta = 0; + this._onTimeDelta = 0; + } + FrameScheduler.prototype = { + get shallSkipDraw() { + if (this._drawsSkipped >= MAX_DRAWS_TO_SKIP) { + return false; + } + var averageDraw = this._drawStats.length < STATS_TO_REMEMBER ? 0 : this._drawStatsSum / this._drawStats.length; + var estimatedDrawEnd = performance.now() + averageDraw; + return estimatedDrawEnd + INTERVAL_PADDING_MS > this._expectedNextFrameAt; + }, + get nextFrameIn() { + return Math.max(0, this._expectedNextFrameAt - performance.now()); + }, + get isOnTime() { + return this._onTime; + }, + startFrame: function (frameRate) { + var interval = 1000 / frameRate; + var adjustedInterval = interval; + var delta = this._onTimeDelta + this._delta; + if (delta !== 0) { + if (delta < 0) { + adjustedInterval *= SPEED_ADJUST_RATE; + } else if (delta > 0) { + adjustedInterval /= SPEED_ADJUST_RATE; + } + this._onTimeDelta += interval - adjustedInterval; + } + this._expectedNextFrameAt += adjustedInterval; + this._onTime = true; + }, + endFrame: function () { + var estimatedNextFrameStart = performance.now() + INTERVAL_PADDING_MS; + if (estimatedNextFrameStart > this._expectedNextFrameAt) { + if (this._trackDelta) { + this._onTimeDelta += this._expectedNextFrameAt - estimatedNextFrameStart; + console.log(this._onTimeDelta); + } + this._expectedNextFrameAt = estimatedNextFrameStart; + this._onTime = false; + } + }, + startDraw: function () { + this._drawsSkipped = 0; + this._drawStarted = performance.now(); + }, + endDraw: function () { + var drawTime = performance.now() - this._drawStarted; + this._drawStats.push(drawTime); + this._drawStatsSum += drawTime; + while (this._drawStats.length > STATS_TO_REMEMBER) { + this._drawStatsSum -= this._drawStats.shift(); + } + }, + skipDraw: function () { + this._drawsSkipped++; + }, + setDelta: function (value) { + if (!this._trackDelta) { + return; + } + this._delta = value; + }, + startTrackDelta: function () { + this._trackDelta = true; + }, + endTrackDelta: function () { + if (!this._trackDelta) { + return; + } + this._trackDelta = false; + this._delta = 0; + this._onTimeDelta = 0; + } + }; + return FrameScheduler; + }(); var tagHandler = function (global) { function defineShape($bytes, $stream, $, swfVersion, tagCode) { $ || ($ = {}); @@ -7245,7 +7309,7 @@ var readHeader = function readHeader($bytes, $stream, $, swfVersion, tagCode) { $.frameCount = readUi16($bytes, $stream); return $; }; -function readTags(context, stream, swfVersion, final, onprogress) { +function readTags(context, stream, swfVersion, final, onprogress, onexception) { var tags = context.tags; var bytes = stream.bytes; var lastSuccessfulPosition; @@ -7314,6 +7378,7 @@ function readTags(context, stream, swfVersion, final, onprogress) { } } catch (e) { if (e !== StreamNoDataError) { + onexception && onexception(e); throw e; } stream.pos = lastSuccessfulPosition; @@ -7473,7 +7538,7 @@ BodyParser.prototype = { finalBlock = progressInfo.bytesLoaded >= progressInfo.bytesTotal; } var readStartTime = performance.now(); - readTags(swf, stream, swfVersion, finalBlock, options.onprogress); + readTags(swf, stream, swfVersion, finalBlock, options.onprogress, options.onexception); swf.parseTime += performance.now() - readStartTime; var read = stream.pos; buffer.removeHead(read); @@ -7902,6 +7967,13 @@ function createParsingContext(commitData) { command: 'complete', stats: stats }); + }, + onexception: function (e) { + commitData({ + type: 'exception', + message: e.message, + stack: e.stack + }); } }; } @@ -9463,6 +9535,11 @@ function interpretActions(actionsData, scopeContainer, constantPool, registers) throw new AS2CriticalError('long running script -- AVM1 errors limit is reached'); } console.error('AVM1 error: ' + e); + avm2.exceptions.push({ + source: 'avm1', + message: e.message, + stack: e.stack + }); recoveringFromError = true; } } @@ -24538,11 +24615,20 @@ var ApplicationDomain = function () { if (false) { Timer.start('broadcast: ' + type); } - this.onMessage.notify1(type, { - data: message, - origin: origin, - source: this - }); + try { + this.onMessage.notify1(type, { + data: message, + origin: origin, + source: this + }); + } catch (e) { + avm2.exceptions.push({ + source: type, + message: e.message, + stack: e.stack + }); + throw e; + } if (false) { Timer.stop(); } @@ -33502,7 +33588,23 @@ var Interpreter = new (function () { return Interpreter; }())(); var AVM2 = function () { - function avm2(sysMode, appMode, findDefiningAbc, loadAVM1) { + function findDefiningAbc(mn) { + if (!avm2.builtinsLoaded) { + return null; + } + for (var i = 0; i < mn.namespaces.length; i++) { + var name = mn.namespaces[i].originalURI + ':' + mn.name; + var abcName = playerGlobalNames[name]; + if (abcName) { + break; + } + } + if (abcName) { + return grabAbc(abcName); + } + return null; + } + function avm2Ctor(sysMode, appMode, loadAVM1) { this.systemDomain = new ApplicationDomain(this, null, sysMode, true); this.applicationDomain = new ApplicationDomain(this, this.systemDomain, appMode, false); this.findDefiningAbc = findDefiningAbc; @@ -33511,8 +33613,9 @@ var AVM2 = function () { this.exception = { value: undefined }; + this.exceptions = []; } - avm2.currentAbc = function () { + avm2Ctor.currentAbc = function () { var caller = arguments.callee; var maxDepth = 20; var abc = null; @@ -33526,15 +33629,15 @@ var AVM2 = function () { } return abc; }; - avm2.currentDomain = function () { + avm2Ctor.currentDomain = function () { var abc = this.currentAbc(); return abc.applicationDomain; }; - avm2.prototype = { + avm2Ctor.prototype = { notifyConstruct: function notifyConstruct(instanceConstructor, args) { } }; - return avm2; + return avm2Ctor; }(); var playerGlobalNames = {}; var playerGlobalScripts = {}; @@ -36936,28 +37039,11 @@ function grabAbc(abcName) { } return null; } -function findDefiningAbc(mn) { - if (!avm2.builtinsLoaded) { - return null; - } - var name; - for (var i = 0; i < mn.namespaces.length; i++) { - var name = mn.namespaces[i].originalURI + ':' + mn.name; - var abcName = playerGlobalNames[name]; - if (abcName) { - break; - } - } - if (abcName) { - return grabAbc(abcName); - } - return null; -} var avm2; var libraryScripts = playerGlobalScripts; var libraryNames = playerGlobalNames; function createAVM2(builtinPath, libraryPath, avm1Path, sysMode, appMode, next) { - avm2 = new AVM2(sysMode, appMode, findDefiningAbc, loadAVM1); + avm2 = new AVM2(sysMode, appMode, loadAVM1); var builtinAbc, libraryAbc, avm1Abc; new BinaryFileReader(libraryPath).readAll(null, function (buffer) { libraryAbcs = buffer; @@ -37658,7 +37744,7 @@ var DisplayObjectDefinition = function () { m = this._concatenatedTransform; } if (targetCoordSpace && targetCoordSpace !== this._stage) { - m2 = targetCoordMatrix || targetCoordSpace._getConcatenatedTransform(); + m2 = targetCoordMatrix || targetCoordSpace._getConcatenatedTransform(null, false); var a = 1, b = 0, c = 0, d = 1, tx = 0, ty = 0; if (m2.b || m2.c) { var det = 1 / (m2.a * m2.d - m2.b * m2.c); @@ -37697,14 +37783,14 @@ var DisplayObjectDefinition = function () { return m; }, _applyCurrentTransform: function (pt) { - var m = this._getConcatenatedTransform(); + var m = this._getConcatenatedTransform(null, false); var x = pt.x; var y = pt.y; pt.x = m.a * x + m.c * y + m.tx | 0; pt.y = m.d * y + m.b * x + m.ty | 0; }, _applyConcatenatedInverseTransform: function (pt) { - var m = this._getConcatenatedTransform(); + var m = this._getConcatenatedTransform(null, false); var det = 1 / (m.a * m.d - m.b * m.c); var x = pt.x - m.tx; var y = pt.y - m.ty; @@ -37745,7 +37831,7 @@ var DisplayObjectDefinition = function () { var children = this._children; for (var i = 0, n = children.length; i < n; i++) { var child = children[i]; - if (child._hitTest && child._hitTest(true, x, y, true)) { + if (child._hitTest && child._hitTest(true, x, y, true, null)) { return true; } } @@ -38138,6 +38224,9 @@ var DisplayObjectDefinition = function () { var numChildren = children.length; for (var i = 0; i < numChildren; i++) { var child = children[i]; + if (!flash.display.DisplayObject.class.isInstanceOf(child)) { + continue; + } var b = child.getBounds(this); var x1 = b.xMin; var y1 = b.yMin; @@ -38248,7 +38337,7 @@ var DisplayObjectDefinition = function () { yMax: 0 }; } - var m = targetCoordSpace && !flash.display.DisplayObject.class.isInstanceOf(targetCoordSpace) ? targetCoordSpace : this._getConcatenatedTransform(targetCoordSpace); + var m = targetCoordSpace && !flash.display.DisplayObject.class.isInstanceOf(targetCoordSpace) ? targetCoordSpace : this._getConcatenatedTransform(targetCoordSpace, false); var x0 = m.a * xMin + m.c * yMin + m.tx | 0; var y0 = m.b * xMin + m.d * yMin + m.ty | 0; var x1 = m.a * xMax + m.c * yMin + m.tx | 0; @@ -39841,7 +39930,15 @@ var LoaderDefinition = function () { var loader = this; loader._worker = worker; worker.onmessage = function (evt) { - loader._commitData(evt.data); + if (evt.data.type === 'exception') { + avm2.exceptions.push({ + source: 'parser', + message: evt.data.message, + stack: evt.data.stack + }); + } else { + loader._commitData(evt.data); + } }; if (flash.net.URLRequest.class.isInstanceOf(request)) { var session = FileLoadingService.createSession(); @@ -40443,52 +40540,10 @@ var MovieClipDefinition = function () { this._startSoundRegistrations[frameNum] = starts; }, _initSoundStream: function (streamInfo) { - var soundStream = this._soundStream = { - data: { - sampleRate: streamInfo.sampleRate, - channels: streamInfo.channels - }, - seekIndex: [], - position: 0 - }; - var isMP3 = streamInfo.format === 'mp3'; - if (isMP3 && PLAY_USING_AUDIO_TAG) { - var element = document.createElement('audio'); - if (element.canPlayType('audio/mpeg')) { - soundStream.element = element; - soundStream.rawFrames = []; - return; - } - } - soundStream.data.pcm = new Float32Array(streamInfo.samplesCount * streamInfo.channels); - if (isMP3) { - soundStream.decoderPosition = 0; - soundStream.decoderSession = new MP3DecoderSession(); - soundStream.decoderSession.onframedata = function (frameData) { - var position = soundStream.decoderPosition; - soundStream.data.pcm.set(frameData, position); - soundStream.decoderPosition = position + frameData.length; - }.bind(this); - soundStream.decoderSession.onerror = function (error) { - console.error('ERROR: MP3DecoderSession: ' + error); - }; - } + this._soundStream = new MovieClipSoundStream(streamInfo, this); }, _addSoundStreamBlock: function (frameNum, streamBlock) { - var soundStream = this._soundStream; - var streamPosition = soundStream.position; - soundStream.seekIndex[frameNum] = streamPosition + streamBlock.seek * soundStream.data.channels; - soundStream.position = streamPosition + streamBlock.samplesCount * soundStream.data.channels; - if (soundStream.rawFrames) { - soundStream.rawFrames.push(streamBlock.data); - return; - } - var decoderSession = soundStream.decoderSession; - if (decoderSession) { - decoderSession.pushAsync(streamBlock.data); - } else { - soundStream.data.pcm.set(streamBlock.pcm, streamPosition); - } + this._soundStream.appendBlock(frameNum, streamBlock); }, _startSounds: function (frameNum) { var starts = this._startSoundRegistrations[frameNum]; @@ -40521,42 +40576,8 @@ var MovieClipDefinition = function () { } } } - if (this._soundStream && !isNaN(this._soundStream.seekIndex[frameNum])) { - var PAUSE_WHEN_OF_SYNC_GREATER = 2; - var RESET_WHEN_OF_SYNC_GREATER = 4; - var soundStream = this._soundStream; - var element = soundStream.element; - if (element) { - var soundStreamData = soundStream.data; - var time = soundStream.seekIndex[frameNum] / soundStreamData.sampleRate / soundStreamData.channels; - if (this._complete && !soundStream.channel) { - var blob = new Blob(soundStream.rawFrames); - element.preload = 'metadata'; - element.loop = false; - element.src = URL.createObjectURL(blob); - var symbolClass = flash.media.SoundChannel.class; - var channel = symbolClass.createAsSymbol({ - element: element - }); - symbolClass.instanceConstructor.call(channel); - soundStream.channel = channel; - } else if (!isNaN(element.duration) && element.currentTime > time + PAUSE_WHEN_OF_SYNC_GREATER && element.currentTime < time + RESET_WHEN_OF_SYNC_GREATER) { - element.pause(); - } else if (!isNaN(element.duration) && (element.paused || Math.abs(element.currentTime - time) > RESET_WHEN_OF_SYNC_GREATER)) { - if (Math.abs(element.currentTime - time) > PAUSE_WHEN_OF_SYNC_GREATER) { - element.pause(); - element.currentTime = time; - } - element.play(); - } - } else if (!soundStream.sound) { - var symbolClass = flash.media.Sound.class; - var sound = symbolClass.createAsSymbol(this._soundStream.data); - symbolClass.instanceConstructor.call(sound); - var channel = sound.play(); - soundStream.sound = sound; - soundStream.channel = channel; - } + if (this._soundStream) { + this._soundStream.playFrame(frameNum); } }, _getAS2Object: function () { @@ -40736,6 +40757,177 @@ var MovieClipDefinition = function () { }; return def; }.call(this); +var MovieClipSoundStream = function () { + var MP3_MIME_TYPE = 'audio/mpeg'; + function openMediaSource(soundStream, mediaSource) { + var sourceBuffer; + try { + sourceBuffer = mediaSource.addSourceBuffer(MP3_MIME_TYPE); + soundStream.mediaSource = mediaSource; + soundStream.sourceBuffer = sourceBuffer; + soundStream.rawFrames.forEach(function (data) { + sourceBuffer.appendBuffer(data); + }); + delete soundStream.rawFrames; + } catch (e) { + console.error('MediaSource mp3 playback is not supported: ' + e); + } + } + function syncTime(element, movieClip) { + var initialized = false; + var startMediaTime, startRealTime; + element.addEventListener('timeupdate', function (e) { + if (!initialized) { + startMediaTime = element.currentTime; + startRealTime = performance.now(); + initialized = true; + movieClip._stage._frameScheduler.startTrackDelta(); + return; + } + var mediaDelta = element.currentTime - startMediaTime; + var realDelta = performance.now() - startRealTime; + movieClip._stage._frameScheduler.setDelta(realDelta - mediaDelta * 1000); + }); + element.addEventListener('pause', function (e) { + movieClip._stage._frameScheduler.endTrackDelta(); + initialized = false; + }); + element.addEventListener('seeking', function (e) { + movieClip._stage._frameScheduler.endTrackDelta(); + initialized = false; + }); + } + function MovieClipSoundStream(streamInfo, movieClip) { + this.movieClip = movieClip; + this.data = { + sampleRate: streamInfo.sampleRate, + channels: streamInfo.channels + }; + this.seekIndex = []; + this.position = 0; + var isMP3 = streamInfo.format === 'mp3'; + if (isMP3 && PLAY_USING_AUDIO_TAG) { + var element = document.createElement('audio'); + element.preload = 'metadata'; + element.loop = false; + syncTime(element, movieClip); + if (element.canPlayType(MP3_MIME_TYPE)) { + this.element = element; + if (typeof MediaSource !== 'undefined') { + var mediaSource = new MediaSource(); + mediaSource.addEventListener('sourceopen', openMediaSource.bind(null, this, mediaSource)); + element.src = URL.createObjectURL(mediaSource); + } else { + console.warn('MediaSource is not supported'); + } + this.rawFrames = []; + return; + } + } + var totalSamples = streamInfo.samplesCount * streamInfo.channels; + this.data.pcm = new Float32Array(totalSamples); + if (isMP3) { + var soundStream = this; + soundStream.decoderPosition = 0; + soundStream.decoderSession = new MP3DecoderSession(); + soundStream.decoderSession.onframedata = function (frameData) { + var position = soundStream.decoderPosition; + soundStream.data.pcm.set(frameData, position); + soundStream.decoderPosition = position + frameData.length; + }.bind(this); + soundStream.decoderSession.onerror = function (error) { + console.error('ERROR: MP3DecoderSession: ' + error); + }; + } + } + MovieClipSoundStream.prototype = { + appendBlock: function (frameNum, streamBlock) { + var streamPosition = this.position; + this.seekIndex[frameNum] = streamPosition + streamBlock.seek * this.data.channels; + this.position = streamPosition + streamBlock.samplesCount * this.data.channels; + if (this.sourceBuffer) { + this.sourceBuffer.appendBuffer(streamBlock.data); + return; + } + if (this.rawFrames) { + this.rawFrames.push(streamBlock.data); + return; + } + var decoderSession = this.decoderSession; + if (decoderSession) { + decoderSession.pushAsync(streamBlock.data); + } else { + this.data.pcm.set(streamBlock.pcm, streamPosition); + } + }, + playFrame: function (frameNum) { + if (isNaN(this.seekIndex[frameNum])) { + return; + } + var PAUSE_WHEN_OF_SYNC_GREATER = 1; + var PLAYBACK_ADJUSTMENT = 0.25; + var element = this.element; + if (element) { + var soundStreamData = this.data; + var time = this.seekIndex[frameNum] / soundStreamData.sampleRate / soundStreamData.channels; + if (!this.channel && (this.movieClip._complete || this.sourceBuffer)) { + if (!this.sourceBuffer) { + var blob = new Blob(this.rawFrames); + element.src = URL.createObjectURL(blob); + } + var symbolClass = flash.media.SoundChannel.class; + var channel = symbolClass.createAsSymbol({ + element: element + }); + symbolClass.instanceConstructor.call(channel); + this.channel = channel; + this.expectedFrame = 0; + this.waitFor = 0; + } else if (this.sourceBuffer || !isNaN(element.duration)) { + if (this.mediaSource && this.movieClip._complete) { + this.mediaSource.endOfStream(); + this.mediaSource = null; + } + var elementTime = element.currentTime; + if (this.expectedFrame !== frameNum) { + if (element.paused) { + element.play(); + element.addEventListener('playing', function setTime(e) { + element.removeEventListener('playing', setTime); + element.currentTime = time; + }); + } else { + element.currentTime = time; + } + } else if (this.waitFor > 0) { + if (this.waitFor <= time) { + if (element.paused) { + element.play(); + } + this.waitFor = 0; + } + } else if (elementTime - time > PAUSE_WHEN_OF_SYNC_GREATER) { + console.warn('Sound is faster than frames by ' + (elementTime - time)); + this.waitFor = elementTime - PLAYBACK_ADJUSTMENT; + element.pause(); + } else if (time - elementTime > PAUSE_WHEN_OF_SYNC_GREATER) { + console.warn('Sound is slower than frames by ' + (time - elementTime)); + element.currentTime = time + PLAYBACK_ADJUSTMENT; + } + this.expectedFrame = frameNum + 1; + } + } else if (!this.sound) { + var symbolClass = flash.media.Sound.class; + var sound = symbolClass.createAsSymbol(this.data); + symbolClass.instanceConstructor.call(sound); + var channel = sound.play(); + this.sound = sound; + this.channel = channel; + } + } + }; + return MovieClipSoundStream; + }(); var NativeMenuDefinition = function () { return { __class__: 'flash.display.NativeMenu', @@ -41522,7 +41714,7 @@ var StageDefinition = function () { this._concatenatedTransform.invalid = false; }, _setup: function setup(ctx, options) { - this._qtree = new QuadTree(0, 0, this._stageWidth, this._stageHeight, 0); + this._qtree = new QuadTree(0, 0, this._stageWidth, this._stageHeight, null); this._invalid = true; }, _addToStage: function addToStage(displayObject) { @@ -42140,44 +42332,53 @@ var EventDispatcherDefinition = function () { if (queue) { queue = queue.slice(); var needsInit = true; - for (var i = 0; i < queue.length; i++) { - var item = queue[i]; - var methodInfo = item.handleEvent.methodInfo; - if (methodInfo) { - if (methodInfo.parameters.length) { - if (!methodInfo.parameters[0].isUsed) { - item.handleEvent(); - continue; - } - } - } - if (needsInit) { - if (typeof event === 'string') { - if (eventClass) { - event = new eventClass(event); - } else { - if (event in mouseEvents) { - event = new flash.events.MouseEvent(event, mouseEvents[event]); - if (target._stage) { - event._localX = target.mouseX; - event._localY = target.mouseY; - } - } else { - event = new flash.events.Event(event); + try { + for (var i = 0; i < queue.length; i++) { + var item = queue[i]; + var methodInfo = item.handleEvent.methodInfo; + if (methodInfo) { + if (methodInfo.parameters.length) { + if (!methodInfo.parameters[0].isUsed) { + item.handleEvent(); + continue; } } - } else if (event._target) { - event = event.clone(); } - event._target = target; - event._currentTarget = currentTarget || target; - event._eventPhase = eventPhase || 2; - needsInit = false; - } - item.handleEvent(event); - if (event._stopImmediatePropagation) { - break; + if (needsInit) { + if (typeof event === 'string') { + if (eventClass) { + event = new eventClass(event); + } else { + if (event in mouseEvents) { + event = new flash.events.MouseEvent(event, mouseEvents[event]); + if (target._stage) { + event._localX = target.mouseX; + event._localY = target.mouseY; + } + } else { + event = new flash.events.Event(event); + } + } + } else if (event._target) { + event = event.clone(); + } + event._target = target; + event._currentTarget = currentTarget || target; + event._eventPhase = eventPhase || 2; + needsInit = false; + } + item.handleEvent(event); + if (event._stopImmediatePropagation) { + break; + } } + } catch (e) { + avm2.exceptions.push({ + source: 'avm2', + message: e.message, + stack: e.stack + }); + throw e; } } return !event._stopPropagation; @@ -42347,7 +42548,7 @@ var MouseEventDefinition = function () { }, getStageX: function getStageX() { if (this._target) { - var m = this._target._getConcatenatedTransform(); + var m = this._target._getConcatenatedTransform(null, false); var x = m.a * this._localX + m.c * this._localY + m.tx; return x / 20; } @@ -42355,7 +42556,7 @@ var MouseEventDefinition = function () { }, getStageY: function getStageY() { if (this._target) { - var m = this._target._getConcatenatedTransform(); + var m = this._target._getConcatenatedTransform(null, false); var y = m.d * this._localY + m.b * this._localX + m.ty; return y / 20; } @@ -43281,7 +43482,7 @@ var TransformDefinition = function () { if (this._target._current3DTransform) { return null; } - var m = this._target._getConcatenatedTransform(); + var m = this._target._getConcatenatedTransform(null, false); return new flash.geom.Matrix(m.a, m.b, m.c, m.d, m.tx / 20, m.ty / 20); }, get matrix() { diff --git a/browser/extensions/shumway/content/version.txt b/browser/extensions/shumway/content/version.txt index 9ac3cdc5f37..d99f15bbc2b 100644 --- a/browser/extensions/shumway/content/version.txt +++ b/browser/extensions/shumway/content/version.txt @@ -1 +1,2 @@ -0.7.933 +0.8.2 +17fa9cc diff --git a/browser/extensions/shumway/content/web/avm-sandbox.js b/browser/extensions/shumway/content/web/avm-sandbox.js index c28492dde56..4ddd6d05d37 100644 --- a/browser/extensions/shumway/content/web/avm-sandbox.js +++ b/browser/extensions/shumway/content/web/avm-sandbox.js @@ -108,12 +108,17 @@ function runViewer() { parseSwf(movieUrl, movieParams, objectParams); if (isOverlay) { + document.getElementById('overlay').className = 'enabled'; var fallbackDiv = document.getElementById('fallback'); - fallbackDiv.className = 'enabled'; fallbackDiv.addEventListener('click', function(e) { fallback(); e.preventDefault(); }); + var reportDiv = document.getElementById('report'); + reportDiv.addEventListener('click', function(e) { + reportIssue(); + e.preventDefault(); + }); var fallbackMenu = document.getElementById('fallbackMenu'); fallbackMenu.removeAttribute('hidden'); fallbackMenu.addEventListener('click', fallback); @@ -122,13 +127,14 @@ function runViewer() { showURLMenu.addEventListener('click', showURL); var inspectorMenu = document.getElementById('inspectorMenu'); inspectorMenu.addEventListener('click', showInInspector); + var reportMenu = document.getElementById('reportMenu'); + reportMenu.addEventListener('click', reportIssue); document.getElementById('copyProfileMenu').addEventListener('click', copyProfile); } function showURL() { - var flashParams = JSON.parse(FirefoxCom.requestSync('getPluginParams', null)); - window.prompt("Copy to clipboard", flashParams.url); + window.prompt("Copy to clipboard", movieUrl); } function showInInspector() { @@ -140,6 +146,26 @@ function showInInspector() { window.open(base + encodeURIComponent(movieUrl) + params); } +function reportIssue() { + var duplicatesMap = Object.create(null); + var prunedExceptions = []; + avm2.exceptions.forEach(function(e) { + var ident = e.source + e.message + e.stack; + var entry = duplicatesMap[ident]; + if (!entry) { + entry = duplicatesMap[ident] = { + source: e.source, + message: e.message, + stack: e.stack, + count: 0 + }; + prunedExceptions.push(entry); + } + entry.count++; + }); + FirefoxCom.requestSync('reportIssue', JSON.stringify(prunedExceptions)); +} + function copyProfile() { function toArray(v) { var array = []; @@ -236,6 +262,12 @@ function parseSwf(url, movieParams, objectParams) { FirefoxCom.requestSync('getCompilerSettings', null)); enableVerifier.value = compilerSettings.verifier; + // init misc preferences + turboMode.value = FirefoxCom.requestSync('getBoolPref', {pref: 'shumway.turboMode', def: false}); + hud.value = FirefoxCom.requestSync('getBoolPref', {pref: 'shumway.hud', def: false}); + forceHidpi.value = FirefoxCom.requestSync('getBoolPref', {pref: 'shumway.force_hidpi', def: false}); + dummyAnimation.value = FirefoxCom.requestSync('getBoolPref', {pref: 'shumway.dummyMode', def: false}); + console.log("Compiler settings: " + JSON.stringify(compilerSettings)); console.log("Parsing " + url + "..."); function loaded() { diff --git a/browser/extensions/shumway/content/web/viewer.html b/browser/extensions/shumway/content/web/viewer.html index 4438cbc5c56..a84c885114e 100644 --- a/browser/extensions/shumway/content/web/viewer.html +++ b/browser/extensions/shumway/content/web/viewer.html @@ -38,38 +38,50 @@ limitations under the License. line-height: 0; } - #fallback { + #overlay { display: none; } - #fallback.enabled { + #overlay.enabled { display: block; position:fixed; + bottom: 0; + right: 0; + } + + #report, #fallback { + float: right; + width: 70px; height: 16px; + padding: 8px 4px 4px; color: white; - right: 0px; bottom: 0px; width: 70px; height: 16px; - padding: 4px; - padding-top: 8px; background-color: rgba(0, 0, 0, 0.62); font: bold 10px sans-serif; text-align: center; text-decoration: none; } + #report { + display: none; + width: 100px; + } + #overlay:hover #report { + display: block; + } #fallback .icon { display: none; color: white; } - #fallback.enabled:hover .icon { + #fallback:hover .icon { display: inline-block; } - #fallback:hover { - background-color: rgb(0, 0, 0); + #report:hover, #fallback:hover { + background-color: black; } @media screen and (max-width: 100px), screen and (max-height: 40px) { - body.started #fallback { + body.started #overlay { display: none; } } @@ -79,11 +91,15 @@ limitations under the License.