Bug 962736 - Update Shumway to version 0.8.2. r=till
☠☠ backed out by 861d0354e018 ☠ ☠
authorYury Delendik <ydelendik@mozilla.com>
Wed, 22 Jan 2014 14:13:47 -0600
changeset 181115 41143dba70eb8c00da3a96088366280be93d60be
parent 181114 464aa919d253f99c6d10abee582cb2f57b1f2403
child 181116 4852d88cc2aee7ace4d8bda463ca08893e54444d
push id3343
push userffxbld
push dateMon, 17 Mar 2014 21:55:32 +0000
treeherdermozilla-beta@2f7d3415f79f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerstill
bugs962736
milestone29.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 962736 - Update Shumway to version 0.8.2. r=till
browser/extensions/shumway/content/ShumwayStreamConverter.jsm
browser/extensions/shumway/content/shumway-worker.js
browser/extensions/shumway/content/shumway.js
browser/extensions/shumway/content/version.txt
browser/extensions/shumway/content/web/avm-sandbox.js
browser/extensions/shumway/content/web/viewer.html
--- a/browser/extensions/shumway/content/ShumwayStreamConverter.jsm
+++ b/browser/extensions/shumway/content/ShumwayStreamConverter.jsm
@@ -32,28 +32,40 @@ const EXPECTED_PLAYPREVIEW_URI_PREFIX = 
 const FIREFOX_ID = '{ec8030f7-c20a-464f-9b0e-13a3a9e97384}';
 const SEAMONKEY_ID = '{92650c4d-4b8e-4d2a-b7eb-24ecf4f6b63a}';
 
 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');
 
 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);
   } catch (ex) {
     return def;
   }
 }
 
@@ -183,16 +195,33 @@ function isShumwayEnabledFor(actions) {
     /sndcdn\.com\/assets\/swf/.test(url) /* soundcloud */ ||
     /vimeocdn\.com/.test(url) /* vimeo */) {
     return false;
   }
 
   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;
   var e = doc.createEvent("CustomEvent");
   e.initCustomEvent("MozPlayPlugin", true, true, activateCTP);
   obj.dispatchEvent(e);
 
   ShumwayTelemetry.onFallback(userAction);
@@ -361,17 +390,17 @@ ChromeActions.prototype = {
       } else {
         log("data access id prohibited to " + url + " from " + baseUrl);
         win.postMessage({callback:"loadFile", sessionId: sessionId, topic: "error",
           error: "only original swf file or file from the same origin loading supported"}, "*");
       }
     });
   },
   fallback: function(automatic) {
-    automatic = !!automatic; // cast to boolean
+    automatic = !!automatic;
     fallbackToNativePlugin(this.window, !automatic, automatic);
   },
   setClipboard: function (data) {
     if (typeof data !== 'string' ||
         data.length > MAX_CLIPBOARD_DATA_SIZE ||
         !this.document.hasFocus()) {
       return;
     }
@@ -427,16 +456,36 @@ ChromeActions.prototype = {
       if (errorType >= MIN_ERROR_TYPE && errorType <= MAX_ERROR_TYPE &&
           !this.telemetry.errors[errorType]) {
         this.telemetry.errors[errorType] = true; // record only one report per SWF
         ShumwayTelemetry.onError(errorType);
       }
       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;
 
     // TODO check security ?
     var parentWindow = this.window.parent.wrappedJSObject;
     var embedTag = this.embedTag.wrappedJSObject;
     switch (data.action) {
@@ -454,16 +503,19 @@ ChromeActions.prototype = {
       return parentWindow.__flash__eval(data.expression);
     case 'call':
       return parentWindow.__flash__call(data.request);
     case 'register':
       return embedTag.__flash__registerCallback(data.functionName);
     case 'unregister':
       return embedTag.__flash__unregisterCallback(data.functionName);
     }
+  },
+  getWindowUrl: function() {
+    return this.window.parent.wrappedJSObject.location + '';
   }
 };
 
 // Event listener to trigger chrome privedged code.
 function RequestListener(actions) {
   this.actions = actions;
 }
 // Receive an event and synchronously or asynchronously responds.
--- a/browser/extensions/shumway/content/shumway-worker.js
+++ b/browser/extensions/shumway/content/shumway-worker.js
@@ -3870,16 +3870,23 @@ function createParsingContext(commitData
           height: (bbox.yMax - bbox.yMin) / 20,
           isAvm2: !(!result.fileAttributes.doAbc)
         };
       }
       commitData({
         command: 'complete',
         stats: stats
       });
+    },
+    onexception: function (e) {
+      commitData({
+        type: 'exception',
+        message: e.message,
+        stack: e.stack
+      });
     }
   };
 }
 function parseBytes(bytes, commitData) {
   SWF.parse(bytes, createParsingContext(commitData));
 }
 function ResourceLoader(scope) {
   this.subscription = null;
@@ -5808,17 +5815,17 @@ var readHeader = function readHeader($by
   $.frameRate = readUi8($bytes, $stream) + frameRateFraction / 256;
   $.frameCount = readUi16($bytes, $stream);
   return $;
 };
 (function (global) {
   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;
   var tag = null;
   if (context._readTag) {
     tag = context._readTag;
     delete context._readTag;
   }
@@ -5877,16 +5884,17 @@ function readTags(context, stream, swfVe
       if (onprogress) {
         onprogress(context);
       }
     } else {
       context._readTag = tag;
     }
   } catch (e) {
     if (e !== StreamNoDataError) {
+      onexception && onexception(e);
       throw e;
     }
     stream.pos = lastSuccessfulPosition;
     context._readTag = tag;
   }
 }
 function HeadTailBuffer(defaultSize) {
   this.bufferSize = defaultSize || 16;
@@ -6036,17 +6044,17 @@ BodyParser.prototype = {
     }
     var finalBlock = false;
     if (progressInfo) {
       swf.bytesLoaded = progressInfo.bytesLoaded;
       swf.bytesTotal = progressInfo.bytesTotal;
       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);
     this.totalRead += read;
     if (options.oncomplete && swf.tags[swf.tags.length - 1].finalTag) {
       options.oncomplete(swf);
     }
   }
--- a/browser/extensions/shumway/content/shumway.js
+++ b/browser/extensions/shumway/content/shumway.js
@@ -5016,33 +5016,20 @@ var disableRenderVisitor = rendererOptio
 var disableMouseVisitor = rendererOptions.register(new Option('dmv', 'disableMouseVisitor', 'boolean', false, 'disable mouse visitor'));
 var showRedrawRegions = rendererOptions.register(new Option('rr', 'showRedrawRegions', 'boolean', false, 'show redraw regions'));
 var renderAsWireframe = rendererOptions.register(new Option('raw', 'renderAsWireframe', 'boolean', false, 'render as wireframe'));
 var showQuadTree = rendererOptions.register(new Option('qt', 'showQuadTree', 'boolean', false, 'show quad tree'));
 var turboMode = rendererOptions.register(new Option('', 'turbo', 'boolean', false, 'turbo mode'));
 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) {
       var tempCanvas = this.cache.shift();
       if (!tempCanvas) {
         tempCanvas = {
           canvas: document.createElement('canvas')
         };
@@ -5428,23 +5415,17 @@ function RenderingContext(refreshStage, 
   this.invalidPath = invalidPath;
   this.isClippingMask = false;
   this.colorTransform = new RenderingColorTransform();
   this.parentCtxs = [];
 }
 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) {
       ctx.globalAlpha *= child._alpha;
     }
     if (context.invalidPath && !child._invalid && !context.refreshStage) {
       return;
     }
@@ -5541,21 +5522,67 @@ function initializeHUD(stage, parentCanv
   var canvasContainer = document.createElement('div');
   canvasContainer.appendChild(canvas);
   canvasContainer.style.position = 'absolute';
   canvasContainer.style.top = '0px';
   canvasContainer.style.left = '0px';
   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) {
     initializeHUD(stage, ctx.canvas);
   }
   function updateRenderTransform() {
     frameWidth = ctx.canvas.width;
     frameHeight = ctx.canvas.height;
@@ -5603,78 +5630,27 @@ function renderStage(stage, ctx, events)
     ctx.setTransform(scaleX, 0, 0, scaleY, offsetX, offsetY);
     var m = stage._concatenatedTransform;
     m.a = scaleX;
     m.d = scaleY;
     m.tx = offsetX * 20;
     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) {
       updateRenderTransform();
       stage._invalid = false;
       refreshStage = true;
     }
     var mouseMoved = false;
@@ -5703,17 +5679,24 @@ function renderStage(stage, ctx, events)
         timelineWrapBroadcastMessage(domain, 'executeFrame');
         timelineWrapBroadcastMessage(domain, 'exitFrame');
         timelineLeave('events');
       }
       if (stage._deferRenderEvent) {
         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');
         invalidPath = stage._processInvalidations(refreshStage);
         timelineLeave('invalidate');
         traceRenderer.value && frameWriter.leave('< Invalidation');
         if (!disableRenderVisitor.value && !invalidPath.isEmpty) {
           timelineEnter('render');
@@ -5726,16 +5709,17 @@ function renderStage(stage, ctx, events)
           ctx.strokeStyle = 'green';
           renderQuadTree(ctx, stage._qtree);
         }
         if (invalidPath && !refreshStage && showRedrawRegions.value) {
           ctx.strokeStyle = 'red';
           invalidPath.draw(ctx);
           ctx.stroke();
         }
+        frameScheduler.endDraw();
       }
       if (mouseMoved && !disableMouseVisitor.value) {
         renderFrame && timelineEnter('mouse');
         traceRenderer.value && frameWriter.enter('> Mouse Handling');
         stage._handleMouse();
         traceRenderer.value && frameWriter.leave('< Mouse Handling');
         renderFrame && timelineLeave('mouse');
         ctx.canvas.style.cursor = stage._cursor;
@@ -5752,74 +5736,154 @@ function renderStage(stage, ctx, events)
         traceRenderer.value && appendToFrameTerminal('End Frame Time: ' + frameElapsedTime.toFixed(2) + ' (' + frameFPS.toFixed(2) + ' fps, ' + frameFPSAverage.average().toFixed(2) + ' average fps)', 'purple');
       }
       timelineLeave('frame');
     } else {
       traceRenderer.value && appendToFrameTerminal('Skip Frame', 'black');
     }
     sampleEnd();
   }
-  var frameRequested = true;
-  var skipNextFrameDraw = false;
   (function draw() {
-    var now = performance.now();
     var renderFrame = true;
     if (events.onBeforeFrame) {
       var e = {
           cancel: false
         };
       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();
     }
     if (renderingTerminated) {
       if (events.onTerminated) {
         events.onTerminated();
       }
       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) {
       $ || ($ = {});
       $.id = readUi16($bytes, $stream);
       var $0 = $.bbox = {};
       bbox($bytes, $stream, $0, swfVersion, tagCode);
       var isMorph = $.isMorph = tagCode === 46 || tagCode === 84;
       if (isMorph) {
@@ -7240,17 +7304,17 @@ var readHeader = function readHeader($by
   $0.yMin = yMin;
   $0.yMax = yMax;
   align($bytes, $stream);
   var frameRateFraction = readUi8($bytes, $stream);
   $.frameRate = readUi8($bytes, $stream) + frameRateFraction / 256;
   $.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;
   var tag = null;
   if (context._readTag) {
     tag = context._readTag;
     delete context._readTag;
   }
@@ -7309,16 +7373,17 @@ function readTags(context, stream, swfVe
       if (onprogress) {
         onprogress(context);
       }
     } else {
       context._readTag = tag;
     }
   } catch (e) {
     if (e !== StreamNoDataError) {
+      onexception && onexception(e);
       throw e;
     }
     stream.pos = lastSuccessfulPosition;
     context._readTag = tag;
   }
 }
 function HeadTailBuffer(defaultSize) {
   this.bufferSize = defaultSize || 16;
@@ -7468,17 +7533,17 @@ BodyParser.prototype = {
     }
     var finalBlock = false;
     if (progressInfo) {
       swf.bytesLoaded = progressInfo.bytesLoaded;
       swf.bytesTotal = progressInfo.bytesTotal;
       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);
     this.totalRead += read;
     if (options.oncomplete && swf.tags[swf.tags.length - 1].finalTag) {
       options.oncomplete(swf);
     }
   }
@@ -7897,16 +7962,23 @@ function createParsingContext(commitData
           height: (bbox.yMax - bbox.yMin) / 20,
           isAvm2: !(!result.fileAttributes.doAbc)
         };
       }
       commitData({
         command: 'complete',
         stats: stats
       });
+    },
+    onexception: function (e) {
+      commitData({
+        type: 'exception',
+        message: e.message,
+        stack: e.stack
+      });
     }
   };
 }
 function parseBytes(bytes, commitData) {
   SWF.parse(bytes, createParsingContext(commitData));
 }
 function ResourceLoader(scope) {
   this.subscription = null;
@@ -9458,16 +9530,21 @@ function interpretActions(actionsData, s
           stack.push(undefined);
         }
       }
       if (!recoveringFromError) {
         if (currentContext.errorsIgnored++ >= MAX_AVM1_ERRORS_LIMIT) {
           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;
       }
     }
   }
 }
 var ActionTracerFactory = function () {
     var indentation = 0;
     var tracer = {
@@ -24533,21 +24610,30 @@ var ApplicationDomain = function () {
         if (!this.base) {
           Type.initializeTypes(this);
         }
       },
       broadcastMessage: function (type, message, origin) {
         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();
         }
       },
       traceLoadedClasses: function (lastOnly) {
         var writer = new IndentingWriter();
         lastOnly || writer.enter('Loaded Classes And Interfaces');
         var classes = lastOnly ? [
@@ -33497,49 +33583,66 @@ var Interpreter = new (function () {
               throw e;
             }
           }
       }
     };
     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;
       this.loadAVM1 = loadAVM1;
       this.isAVM1Loaded = false;
       this.exception = {
         value: undefined
       };
-    }
-    avm2.currentAbc = function () {
+      this.exceptions = [];
+    }
+    avm2Ctor.currentAbc = function () {
       var caller = arguments.callee;
       var maxDepth = 20;
       var abc = null;
       for (var i = 0; i < maxDepth && caller; i++) {
         var mi = caller.methodInfo;
         if (mi) {
           abc = mi.abc;
           break;
         }
         caller = caller.caller;
       }
       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 = {};
 (function () {
   var index = [
       {
         'name': 'Object',
         'offset': 0,
@@ -36931,38 +37034,21 @@ function grabAbc(abcName) {
   var entry = libraryScripts[abcName];
   if (entry) {
     var offset = entry.offset;
     var length = entry.length;
     return new AbcFile(new Uint8Array(libraryAbcs, offset, length), 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;
     new BinaryFileReader(builtinPath).readAll(null, function (buffer) {
       builtinAbc = new AbcFile(new Uint8Array(buffer), 'builtin.abc');
       executeAbc();
     });
   });
@@ -37653,17 +37739,17 @@ var DisplayObjectDefinition = function (
                 }
                 node = nextNode;
               } while (node);
             }
           } else {
             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);
               a = m2.d * det;
               b = -m2.b * det;
               c = -m2.c * det;
               d = m2.a * det;
               tx = -(a * m2.tx + c * m2.ty);
@@ -37692,24 +37778,24 @@ var DisplayObjectDefinition = function (
               d: m.d * m2.d,
               tx: m.tx * m2.a + m2.tx,
               ty: m.ty * m2.d + m2.ty
             };
           }
           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;
           pt.x = (m.d * x - m.c * y) * det | 0;
           pt.y = (m.a * y - m.b * x) * det | 0;
         },
         _hitTest: function (use_xy, x, y, useShape, hitTestObject) {
           if (use_xy) {
@@ -37740,17 +37826,17 @@ var DisplayObjectDefinition = function (
                     return true;
                   }
                 }
               }
             }
             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;
               }
             }
             return false;
           }
           var b1 = this.getBounds(this._stage);
           var b2 = hitTestObject.getBounds(hitTestObject._stage);
           x = Math.max(b1.xMin, b2.xMin);
@@ -38133,16 +38219,19 @@ var DisplayObjectDefinition = function (
               xMax = bbox.xMax;
               yMin = bbox.yMin;
               yMax = bbox.yMax;
             } else {
               var children = this._children;
               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;
                 var x2 = b.xMax;
                 var y2 = b.yMax;
                 xMin = Math.min(xMin, x1, x2);
                 xMax = Math.max(xMax, x1, x2);
                 yMin = Math.min(yMin, y1, y2);
@@ -38243,17 +38332,17 @@ var DisplayObjectDefinition = function (
           if (xMax - xMin === 0 || yMax - yMin === 0) {
             return {
               xMin: 0,
               yMin: 0,
               xMax: 0,
               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;
           var y1 = m.b * xMax + m.d * yMin + m.ty | 0;
           var x2 = m.a * xMax + m.c * yMax + m.tx | 0;
           var y2 = m.b * xMax + m.d * yMax + m.ty | 0;
           var x3 = m.a * xMin + m.c * yMax + m.tx | 0;
           var y3 = m.b * xMin + m.d * yMax + m.ty | 0;
@@ -39836,17 +39925,25 @@ var LoaderDefinition = function () {
           if (WORKERS_ENABLED) {
             worker = new Worker(SHUMWAY_ROOT + LOADER_PATH);
           } else {
             worker = new ResourceLoader(window);
           }
           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();
             session.onprogress = function (data, progress) {
               worker.postMessage({
                 data: data,
                 progress: progress
               });
@@ -40438,62 +40535,20 @@ var MovieClipDefinition = function () {
             return this._framesLoaded;
           }
           return frameNum;
         },
         _registerStartSounds: function (frameNum, starts) {
           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];
           if (starts) {
             var sounds = this._sounds || (this._sounds = {});
             var loader = this.loaderInfo._loader;
             for (var i = 0; i < starts.length; i++) {
               var start = starts[i];
@@ -40516,52 +40571,18 @@ var MovieClipDefinition = function () {
                 delete sound.channel;
               }
               if (!info.stop) {
                 var loops = info.hasLoops ? info.loopCount : 0;
                 sound.channel = sound.object.play(0, loops);
               }
             }
           }
-          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 () {
           if (!this.$as2Object) {
             if (this._avm1SymbolClass) {
               var nativeObject = this, nativeObjectClass = this._avm1SymbolClass;
               var constructWrapper = function () {
                 this.init(nativeObject);
@@ -40731,16 +40752,187 @@ var MovieClipDefinition = function () {
           addFrameScript: def.addFrameScript,
           prevScene: def.prevScene,
           nextScene: def.nextScene
         }
       }
     };
     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',
       initialize: function () {
       },
       __glue__: {
         native: {
           static: {},
@@ -41517,17 +41709,17 @@ var StageDefinition = function () {
         this._mouseMoved = false;
         this._mouseTarget = this;
         this._mouseEvents = [];
         this._cursor = 'auto';
         this._stageVideos = [];
         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) {
         displayObject._stage = this;
         var parent = displayObject._parent;
         displayObject._level = parent._level + 1;
         displayObject._invalid = true;
         var children = displayObject._children;
@@ -42135,54 +42327,63 @@ var EventDispatcherDefinition = function
         processListeners(listeners, event, eventClass, bubbles, target);
       }
       return !event._isDefaultPrevented;
     }
     function processListeners(queue, event, eventClass, bubbles, target, currentTarget, eventPhase) {
       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;
+        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;
+                }
+              }
+            }
+            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 {
-                  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;
-          }
+                }
+              } 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;
     }
     return {
       __class__: 'flash.events.EventDispatcher',
       initialize: function () {
         this._target = this;
@@ -42342,25 +42543,25 @@ var MouseEventDefinition = function () {
       },
       __glue__: {
         native: {
           instance: {
             updateAfterEvent: function updateAfterEvent() {
             },
             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;
               }
               return this._localX / 20;
             },
             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;
               }
               return this._localY / 20;
             },
             localX: {
               get: function localX() {
                 return this._localX / 20;
@@ -43276,17 +43477,17 @@ var TransformDefinition = function () {
           var cxform = this.colorTransform;
           cxform.concat(this._target.parent.transform.concatenatedColorTransform);
           return cxform;
         },
         get concatenatedMatrix() {
           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() {
           if (this._target._current3DTransform) {
             return null;
           }
           var m = this._target._currentTransform;
           return new flash.geom.Matrix(m.a, m.b, m.c, m.d, m.tx / 20, m.ty / 20);
--- a/browser/extensions/shumway/content/version.txt
+++ b/browser/extensions/shumway/content/version.txt
@@ -1,1 +1,2 @@
-0.7.933
+0.8.2
+17fa9cc
--- a/browser/extensions/shumway/content/web/avm-sandbox.js
+++ b/browser/extensions/shumway/content/web/avm-sandbox.js
@@ -103,48 +103,74 @@ function runViewer() {
       var fid = s.split('/')[0];
       return fid !== '5' && fid !== '34' && fid !== '35'; // more?
     }).join(',');
   }
 
   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);
   }
   var showURLMenu = document.getElementById('showURLMenu');
   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() {
   var base = "http://www.areweflashyet.com/shumway/examples/inspector/inspector.html?rfile=";
   var params = '';
   for (var k in movieParams) {
     params += '&' + k + '=' + encodeURIComponent(movieParams[k]);
   }
   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 = [];
     for (var i = 0; i < v.length; i++) {
       array.push(v[i]);
     }
     return array;
   }
@@ -231,16 +257,22 @@ var FileLoadingService = {
   }
 };
 
 function parseSwf(url, movieParams, objectParams) {
   var compilerSettings = JSON.parse(
     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() {
     FirefoxCom.request('endActivation', null);
   }
 
   createAVM2(builtinPath, playerGlobalPath, avm1Path,
     compilerSettings.sysCompiler ? EXECUTION_MODE.COMPILE : EXECUTION_MODE.INTERPRET,
--- a/browser/extensions/shumway/content/web/viewer.html
+++ b/browser/extensions/shumway/content/web/viewer.html
@@ -33,59 +33,75 @@ limitations under the License.
 
       #viewer {
         position:fixed !important;
         left:0;top:0;bottom:0;right:0;
         overflow: hidden;
         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;
         }
       }
     </style>
   </head>
 
   <body contextmenu="shumwayMenu">
     <div id="viewer"></div>
     <section>
-      <a id="fallback" href="#">Shumway <span class="icon">&times;</span></a>
+      <div id="overlay">
+        <a id="fallback" href="#">Shumway <span class="icon">&times;</span></a>
+        <a id="report" href="#">Report Problems</a>
+      </div>
       <menu type="context" id="shumwayMenu">
         <menuitem label="Show URL" id="showURLMenu"></menuitem>
         <menuitem label="Copy Profile" id="copyProfileMenu"></menuitem>
         <menuitem label="Open in Inspector" id="inspectorMenu"></menuitem>
+        <menuitem label="Report Problems" id="reportMenu"></menuitem>
         <menuitem label="Fallback to Flash" id="fallbackMenu" hidden></menuitem>
       </menu>
     </section>
   </body>
 </html>