browser/extensions/shumway/content/ShumwayStreamConverter.jsm

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/browser/extensions/shumway/content/ShumwayStreamConverter.jsm	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,1041 @@
     1.4 +/* -*- Mode: js; js-indent-level: 2; indent-tabs-mode: nil; tab-width: 2 -*- */
     1.5 +/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
     1.6 +/*
     1.7 + * Copyright 2013 Mozilla Foundation
     1.8 + *
     1.9 + * Licensed under the Apache License, Version 2.0 (the "License");
    1.10 + * you may not use this file except in compliance with the License.
    1.11 + * You may obtain a copy of the License at
    1.12 + *
    1.13 + *     http://www.apache.org/licenses/LICENSE-2.0
    1.14 + *
    1.15 + * Unless required by applicable law or agreed to in writing, software
    1.16 + * distributed under the License is distributed on an "AS IS" BASIS,
    1.17 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    1.18 + * See the License for the specific language governing permissions and
    1.19 + * limitations under the License.
    1.20 + */
    1.21 +
    1.22 +'use strict';
    1.23 +
    1.24 +var EXPORTED_SYMBOLS = ['ShumwayStreamConverter', 'ShumwayStreamOverlayConverter'];
    1.25 +
    1.26 +const Cc = Components.classes;
    1.27 +const Ci = Components.interfaces;
    1.28 +const Cr = Components.results;
    1.29 +const Cu = Components.utils;
    1.30 +
    1.31 +const SHUMWAY_CONTENT_TYPE = 'application/x-shockwave-flash';
    1.32 +const EXPECTED_PLAYPREVIEW_URI_PREFIX = 'data:application/x-moz-playpreview;,' +
    1.33 +                                        SHUMWAY_CONTENT_TYPE;
    1.34 +
    1.35 +const FIREFOX_ID = '{ec8030f7-c20a-464f-9b0e-13a3a9e97384}';
    1.36 +const SEAMONKEY_ID = '{92650c4d-4b8e-4d2a-b7eb-24ecf4f6b63a}';
    1.37 +
    1.38 +const MAX_CLIPBOARD_DATA_SIZE = 8000;
    1.39 +
    1.40 +Cu.import('resource://gre/modules/XPCOMUtils.jsm');
    1.41 +Cu.import('resource://gre/modules/Services.jsm');
    1.42 +Cu.import('resource://gre/modules/NetUtil.jsm');
    1.43 +Cu.import('resource://gre/modules/Promise.jsm');
    1.44 +
    1.45 +XPCOMUtils.defineLazyModuleGetter(this, 'PrivateBrowsingUtils',
    1.46 +  'resource://gre/modules/PrivateBrowsingUtils.jsm');
    1.47 +
    1.48 +XPCOMUtils.defineLazyModuleGetter(this, 'AddonManager',
    1.49 +  'resource://gre/modules/AddonManager.jsm');
    1.50 +
    1.51 +XPCOMUtils.defineLazyModuleGetter(this, 'ShumwayTelemetry',
    1.52 +  'resource://shumway/ShumwayTelemetry.jsm');
    1.53 +
    1.54 +let Svc = {};
    1.55 +XPCOMUtils.defineLazyServiceGetter(Svc, 'mime',
    1.56 +                                   '@mozilla.org/mime;1', 'nsIMIMEService');
    1.57 +
    1.58 +let StringInputStream = Cc["@mozilla.org/io/string-input-stream;1"];
    1.59 +let MimeInputStream = Cc["@mozilla.org/network/mime-input-stream;1"];
    1.60 +
    1.61 +function getBoolPref(pref, def) {
    1.62 +  try {
    1.63 +    return Services.prefs.getBoolPref(pref);
    1.64 +  } catch (ex) {
    1.65 +    return def;
    1.66 +  }
    1.67 +}
    1.68 +
    1.69 +function getStringPref(pref, def) {
    1.70 +  try {
    1.71 +    return Services.prefs.getComplexValue(pref, Ci.nsISupportsString).data;
    1.72 +  } catch (ex) {
    1.73 +    return def;
    1.74 +  }
    1.75 +}
    1.76 +
    1.77 +function log(aMsg) {
    1.78 +  let msg = 'ShumwayStreamConverter.js: ' + (aMsg.join ? aMsg.join('') : aMsg);
    1.79 +  Services.console.logStringMessage(msg);
    1.80 +  dump(msg + '\n');
    1.81 +}
    1.82 +
    1.83 +function getDOMWindow(aChannel) {
    1.84 +  var requestor = aChannel.notificationCallbacks ||
    1.85 +                  aChannel.loadGroup.notificationCallbacks;
    1.86 +  var win = requestor.getInterface(Components.interfaces.nsIDOMWindow);
    1.87 +  return win;
    1.88 +}
    1.89 +
    1.90 +function parseQueryString(qs) {
    1.91 +  if (!qs)
    1.92 +    return {};
    1.93 +
    1.94 +  if (qs.charAt(0) == '?')
    1.95 +    qs = qs.slice(1);
    1.96 +
    1.97 +  var values = qs.split('&');
    1.98 +  var obj = {};
    1.99 +  for (var i = 0; i < values.length; i++) {
   1.100 +    var kv = values[i].split('=');
   1.101 +    var key = kv[0], value = kv[1];
   1.102 +    obj[decodeURIComponent(key)] = decodeURIComponent(value);
   1.103 +  }
   1.104 +
   1.105 +  return obj;
   1.106 +}
   1.107 +
   1.108 +function domainMatches(host, pattern) {
   1.109 +  if (!pattern) return false;
   1.110 +  if (pattern === '*') return true;
   1.111 +  host = host.toLowerCase();
   1.112 +  var parts = pattern.toLowerCase().split('*');
   1.113 +  if (host.indexOf(parts[0]) !== 0) return false;
   1.114 +  var p = parts[0].length;
   1.115 +  for (var i = 1; i < parts.length; i++) {
   1.116 +    var j = host.indexOf(parts[i], p);
   1.117 +    if (j === -1) return false;
   1.118 +    p = j + parts[i].length;
   1.119 +  }
   1.120 +  return parts[parts.length - 1] === '' || p === host.length;
   1.121 +}
   1.122 +
   1.123 +function fetchPolicyFile(url, cache, callback) {
   1.124 +  if (url in cache) {
   1.125 +    return callback(cache[url]);
   1.126 +  }
   1.127 +
   1.128 +  log('Fetching policy file at ' + url);
   1.129 +  var MAX_POLICY_SIZE = 8192;
   1.130 +  var xhr =  Components.classes["@mozilla.org/xmlextras/xmlhttprequest;1"]
   1.131 +                               .createInstance(Ci.nsIXMLHttpRequest);
   1.132 +  xhr.open('GET', url, true);
   1.133 +  xhr.overrideMimeType('text/xml');
   1.134 +  xhr.onprogress = function (e) {
   1.135 +    if (e.loaded >= MAX_POLICY_SIZE) {
   1.136 +      xhr.abort();
   1.137 +      cache[url] = false;
   1.138 +      callback(null, 'Max policy size');
   1.139 +    }
   1.140 +  };
   1.141 +  xhr.onreadystatechange = function(event) {
   1.142 +    if (xhr.readyState === 4) {
   1.143 +      // TODO disable redirects
   1.144 +      var doc = xhr.responseXML;
   1.145 +      if (xhr.status !== 200 || !doc) {
   1.146 +        cache[url] = false;
   1.147 +        return callback(null, 'Invalid HTTP status: ' + xhr.statusText);
   1.148 +      }
   1.149 +      // parsing params
   1.150 +      var params = doc.documentElement.childNodes;
   1.151 +      var policy = { siteControl: null, allowAccessFrom: []};
   1.152 +      for (var i = 0; i < params.length; i++) {
   1.153 +        switch (params[i].localName) {
   1.154 +        case 'site-control':
   1.155 +          policy.siteControl = params[i].getAttribute('permitted-cross-domain-policies');
   1.156 +          break;
   1.157 +        case 'allow-access-from':
   1.158 +          var access = {
   1.159 +            domain: params[i].getAttribute('domain'),
   1.160 +            security: params[i].getAttribute('security') === 'true'
   1.161 +          };
   1.162 +          policy.allowAccessFrom.push(access);
   1.163 +          break;
   1.164 +        default:
   1.165 +          // TODO allow-http-request-headers-from and other
   1.166 +          break;
   1.167 +        }
   1.168 +      }
   1.169 +      callback(cache[url] = policy);
   1.170 +    }
   1.171 +  };
   1.172 +  xhr.send(null);
   1.173 +}
   1.174 +
   1.175 +function isShumwayEnabledFor(actions) {
   1.176 +  // disabled for PrivateBrowsing windows
   1.177 +  if (PrivateBrowsingUtils.isWindowPrivate(actions.window)) {
   1.178 +    return false;
   1.179 +  }
   1.180 +  // disabled if embed tag specifies shumwaymode (for testing purpose)
   1.181 +  if (actions.objectParams['shumwaymode'] === 'off') {
   1.182 +    return false;
   1.183 +  }
   1.184 +
   1.185 +  var url = actions.url;
   1.186 +  var baseUrl = actions.baseUrl;
   1.187 +
   1.188 +  // blacklisting well known sites with issues
   1.189 +  if (/\.ytimg\.com\//i.test(url) /* youtube movies */ ||
   1.190 +    /\/vui.swf\b/i.test(url) /* vidyo manager */  ||
   1.191 +    /soundcloud\.com\/player\/assets\/swf/i.test(url) /* soundcloud */ ||
   1.192 +    /sndcdn\.com\/assets\/swf/.test(url) /* soundcloud */ ||
   1.193 +    /vimeocdn\.com/.test(url) /* vimeo */) {
   1.194 +    return false;
   1.195 +  }
   1.196 +
   1.197 +  return true;
   1.198 +}
   1.199 +
   1.200 +function getVersionInfo() {
   1.201 +  var deferred = Promise.defer();
   1.202 +  var versionInfo = {
   1.203 +    geckoMstone : 'unknown',
   1.204 +    geckoBuildID: 'unknown',
   1.205 +    shumwayVersion: 'unknown'
   1.206 +  };
   1.207 +  try {
   1.208 +    versionInfo.geckoMstone = Services.prefs.getCharPref('gecko.mstone');
   1.209 +    versionInfo.geckoBuildID = Services.prefs.getCharPref('gecko.buildID');
   1.210 +  } catch (e) {
   1.211 +    log('Error encountered while getting platform version info:', e);
   1.212 +  }
   1.213 +  try {
   1.214 +    var addonId = "shumway@research.mozilla.org";
   1.215 +    AddonManager.getAddonByID(addonId, function(addon) {
   1.216 +      versionInfo.shumwayVersion = addon ? addon.version : 'n/a';
   1.217 +      deferred.resolve(versionInfo);
   1.218 +    });
   1.219 +  } catch (e) {
   1.220 +    log('Error encountered while getting Shumway version info:', e);
   1.221 +    deferred.resolve(versionInfo);
   1.222 +  }
   1.223 +  return deferred.promise;
   1.224 +}
   1.225 +
   1.226 +function fallbackToNativePlugin(window, userAction, activateCTP) {
   1.227 +  var obj = window.frameElement;
   1.228 +  var doc = obj.ownerDocument;
   1.229 +  var e = doc.createEvent("CustomEvent");
   1.230 +  e.initCustomEvent("MozPlayPlugin", true, true, activateCTP);
   1.231 +  obj.dispatchEvent(e);
   1.232 +
   1.233 +  ShumwayTelemetry.onFallback(userAction);
   1.234 +}
   1.235 +
   1.236 +// All the priviledged actions.
   1.237 +function ChromeActions(url, window, document) {
   1.238 +  this.url = url;
   1.239 +  this.objectParams = null;
   1.240 +  this.movieParams = null;
   1.241 +  this.baseUrl = url;
   1.242 +  this.isOverlay = false;
   1.243 +  this.isPausedAtStart = false;
   1.244 +  this.window = window;
   1.245 +  this.document = document;
   1.246 +  this.externalComInitialized = false;
   1.247 +  this.allowScriptAccess = false;
   1.248 +  this.crossdomainRequestsCache = Object.create(null);
   1.249 +  this.telemetry = {
   1.250 +    startTime: Date.now(),
   1.251 +    features: [],
   1.252 +    errors: [],
   1.253 +    pageIndex: 0
   1.254 +  };
   1.255 +}
   1.256 +
   1.257 +ChromeActions.prototype = {
   1.258 +  getBoolPref: function (data) {
   1.259 +    if (!/^shumway\./.test(data.pref)) {
   1.260 +      return null;
   1.261 +    }
   1.262 +    return getBoolPref(data.pref, data.def);
   1.263 +  },
   1.264 +  getCompilerSettings: function getCompilerSettings() {
   1.265 +    return JSON.stringify({
   1.266 +      appCompiler: getBoolPref('shumway.appCompiler', true),
   1.267 +      sysCompiler: getBoolPref('shumway.sysCompiler', false),
   1.268 +      verifier: getBoolPref('shumway.verifier', true)
   1.269 +    });
   1.270 +  },
   1.271 +  addProfilerMarker: function (marker) {
   1.272 +    if ('nsIProfiler' in Ci) {
   1.273 +      let profiler = Cc["@mozilla.org/tools/profiler;1"].getService(Ci.nsIProfiler);
   1.274 +      profiler.AddMarker(marker);
   1.275 +    }
   1.276 +  },
   1.277 +  getPluginParams: function getPluginParams() {
   1.278 +    return JSON.stringify({
   1.279 +      url: this.url,
   1.280 +      baseUrl : this.baseUrl,
   1.281 +      movieParams: this.movieParams,
   1.282 +      objectParams: this.objectParams,
   1.283 +      isOverlay: this.isOverlay,
   1.284 +      isPausedAtStart: this.isPausedAtStart
   1.285 +     });
   1.286 +  },
   1.287 +  _canDownloadFile: function canDownloadFile(data, callback) {
   1.288 +    var url = data.url, checkPolicyFile = data.checkPolicyFile;
   1.289 +
   1.290 +    // TODO flash cross-origin request
   1.291 +    if (url === this.url) {
   1.292 +      // allow downloading for the original file
   1.293 +      return callback({success: true});
   1.294 +    }
   1.295 +
   1.296 +    // allows downloading from the same origin
   1.297 +    var parsedUrl, parsedBaseUrl;
   1.298 +    try {
   1.299 +      parsedUrl = NetUtil.newURI(url);
   1.300 +    } catch (ex) { /* skipping invalid urls */ }
   1.301 +    try {
   1.302 +      parsedBaseUrl = NetUtil.newURI(this.url);
   1.303 +    } catch (ex) { /* skipping invalid urls */ }
   1.304 +
   1.305 +    if (parsedUrl && parsedBaseUrl &&
   1.306 +        parsedUrl.prePath === parsedBaseUrl.prePath) {
   1.307 +      return callback({success: true});
   1.308 +    }
   1.309 +
   1.310 +    // additionally using internal whitelist
   1.311 +    var whitelist = getStringPref('shumway.whitelist', '');
   1.312 +    if (whitelist && parsedUrl) {
   1.313 +      var whitelisted = whitelist.split(',').some(function (i) {
   1.314 +        return domainMatches(parsedUrl.host, i);
   1.315 +      });
   1.316 +      if (whitelisted) {
   1.317 +        return callback({success: true});
   1.318 +      }
   1.319 +    }
   1.320 +
   1.321 +    if (!checkPolicyFile || !parsedUrl || !parsedBaseUrl) {
   1.322 +      return callback({success: false});
   1.323 +    }
   1.324 +
   1.325 +    // we can request crossdomain.xml
   1.326 +    fetchPolicyFile(parsedUrl.prePath + '/crossdomain.xml', this.crossdomainRequestsCache,
   1.327 +      function (policy, error) {
   1.328 +
   1.329 +      if (!policy || policy.siteControl === 'none') {
   1.330 +        return callback({success: false});
   1.331 +      }
   1.332 +      // TODO assuming master-only, there are also 'by-content-type', 'all', etc.
   1.333 +
   1.334 +      var allowed = policy.allowAccessFrom.some(function (i) {
   1.335 +        return domainMatches(parsedBaseUrl.host, i.domain) &&
   1.336 +          (!i.secure || parsedBaseUrl.scheme.toLowerCase() === 'https');
   1.337 +      });
   1.338 +      return callback({success: allowed});
   1.339 +    }.bind(this));
   1.340 +  },
   1.341 +  loadFile: function loadFile(data) {
   1.342 +    var url = data.url;
   1.343 +    var checkPolicyFile = data.checkPolicyFile;
   1.344 +    var sessionId = data.sessionId;
   1.345 +    var limit = data.limit || 0;
   1.346 +    var method = data.method || "GET";
   1.347 +    var mimeType = data.mimeType;
   1.348 +    var postData = data.postData || null;
   1.349 +
   1.350 +    var win = this.window;
   1.351 +    var baseUrl = this.baseUrl;
   1.352 +
   1.353 +    var performXHR = function () {
   1.354 +      var xhr = Components.classes["@mozilla.org/xmlextras/xmlhttprequest;1"]
   1.355 +                                  .createInstance(Ci.nsIXMLHttpRequest);
   1.356 +      xhr.open(method, url, true);
   1.357 +      xhr.responseType = "moz-chunked-arraybuffer";
   1.358 +
   1.359 +      if (baseUrl) {
   1.360 +        // Setting the referer uri, some site doing checks if swf is embedded
   1.361 +        // on the original page.
   1.362 +        xhr.setRequestHeader("Referer", baseUrl);
   1.363 +      }
   1.364 +
   1.365 +      // TODO apply range request headers if limit is specified
   1.366 +
   1.367 +      var lastPosition = 0;
   1.368 +      xhr.onprogress = function (e) {
   1.369 +        var position = e.loaded;
   1.370 +        var data = new Uint8Array(xhr.response);
   1.371 +        win.postMessage({callback:"loadFile", sessionId: sessionId, topic: "progress",
   1.372 +                         array: data, loaded: e.loaded, total: e.total}, "*");
   1.373 +        lastPosition = position;
   1.374 +        if (limit && e.total >= limit) {
   1.375 +          xhr.abort();
   1.376 +        }
   1.377 +      };
   1.378 +      xhr.onreadystatechange = function(event) {
   1.379 +        if (xhr.readyState === 4) {
   1.380 +          if (xhr.status !== 200 && xhr.status !== 0) {
   1.381 +            win.postMessage({callback:"loadFile", sessionId: sessionId, topic: "error",
   1.382 +                             error: xhr.statusText}, "*");
   1.383 +          }
   1.384 +          win.postMessage({callback:"loadFile", sessionId: sessionId, topic: "close"}, "*");
   1.385 +        }
   1.386 +      };
   1.387 +      if (mimeType)
   1.388 +        xhr.setRequestHeader("Content-Type", mimeType);
   1.389 +      xhr.send(postData);
   1.390 +      win.postMessage({callback:"loadFile", sessionId: sessionId, topic: "open"}, "*");
   1.391 +    };
   1.392 +
   1.393 +    this._canDownloadFile({url: url, checkPolicyFile: checkPolicyFile}, function (data) {
   1.394 +      if (data.success) {
   1.395 +        performXHR();
   1.396 +      } else {
   1.397 +        log("data access id prohibited to " + url + " from " + baseUrl);
   1.398 +        win.postMessage({callback:"loadFile", sessionId: sessionId, topic: "error",
   1.399 +          error: "only original swf file or file from the same origin loading supported"}, "*");
   1.400 +      }
   1.401 +    });
   1.402 +  },
   1.403 +  fallback: function(automatic) {
   1.404 +    automatic = !!automatic;
   1.405 +    fallbackToNativePlugin(this.window, !automatic, automatic);
   1.406 +  },
   1.407 +  setClipboard: function (data) {
   1.408 +    if (typeof data !== 'string' ||
   1.409 +        data.length > MAX_CLIPBOARD_DATA_SIZE ||
   1.410 +        !this.document.hasFocus()) {
   1.411 +      return;
   1.412 +    }
   1.413 +    // TODO other security checks?
   1.414 +
   1.415 +    let clipboard = Cc["@mozilla.org/widget/clipboardhelper;1"]
   1.416 +                      .getService(Ci.nsIClipboardHelper);
   1.417 +    clipboard.copyString(data);
   1.418 +  },
   1.419 +  unsafeSetClipboard: function (data) {
   1.420 +    if (typeof data !== 'string') {
   1.421 +      return;
   1.422 +    }
   1.423 +    let clipboard = Cc["@mozilla.org/widget/clipboardhelper;1"].getService(Ci.nsIClipboardHelper);
   1.424 +    clipboard.copyString(data);
   1.425 +  },
   1.426 +  endActivation: function () {
   1.427 +    if (ActivationQueue.currentNonActive === this) {
   1.428 +      ActivationQueue.activateNext();
   1.429 +    }
   1.430 +  },
   1.431 +  reportTelemetry: function (data) {
   1.432 +    var topic = data.topic;
   1.433 +    switch (topic) {
   1.434 +    case 'firstFrame':
   1.435 +      var time = Date.now() - this.telemetry.startTime;
   1.436 +      ShumwayTelemetry.onFirstFrame(time);
   1.437 +      break;
   1.438 +    case 'parseInfo':
   1.439 +      ShumwayTelemetry.onParseInfo({
   1.440 +        parseTime: +data.parseTime,
   1.441 +        size: +data.bytesTotal,
   1.442 +        swfVersion: data.swfVersion|0,
   1.443 +        frameRate: +data.frameRate,
   1.444 +        width: data.width|0,
   1.445 +        height: data.height|0,
   1.446 +        bannerType: data.bannerType|0,
   1.447 +        isAvm2: !!data.isAvm2
   1.448 +      });
   1.449 +      break;
   1.450 +    case 'feature':
   1.451 +      var featureType = data.feature|0;
   1.452 +      var MIN_FEATURE_TYPE = 0, MAX_FEATURE_TYPE = 999;
   1.453 +      if (featureType >= MIN_FEATURE_TYPE && featureType <= MAX_FEATURE_TYPE &&
   1.454 +          !this.telemetry.features[featureType]) {
   1.455 +        this.telemetry.features[featureType] = true; // record only one feature per SWF
   1.456 +        ShumwayTelemetry.onFeature(featureType);
   1.457 +      }
   1.458 +      break;
   1.459 +    case 'error':
   1.460 +      var errorType = data.error|0;
   1.461 +      var MIN_ERROR_TYPE = 0, MAX_ERROR_TYPE = 2;
   1.462 +      if (errorType >= MIN_ERROR_TYPE && errorType <= MAX_ERROR_TYPE &&
   1.463 +          !this.telemetry.errors[errorType]) {
   1.464 +        this.telemetry.errors[errorType] = true; // record only one report per SWF
   1.465 +        ShumwayTelemetry.onError(errorType);
   1.466 +      }
   1.467 +      break;
   1.468 +    }
   1.469 +  },
   1.470 +  reportIssue: function(exceptions) {
   1.471 +    var base = "http://shumway-issue-reporter.paas.allizom.org/input?";
   1.472 +    var windowUrl = this.window.parent.wrappedJSObject.location + '';
   1.473 +    var params = 'url=' + encodeURIComponent(windowUrl);
   1.474 +    params += '&swf=' + encodeURIComponent(this.url);
   1.475 +    getVersionInfo().then(function (versions) {
   1.476 +      params += '&ffbuild=' + encodeURIComponent(versions.geckoMstone + ' (' +
   1.477 +                                                 versions.geckoBuildID + ')');
   1.478 +      params += '&shubuild=' + encodeURIComponent(versions.shumwayVersion);
   1.479 +    }).then(function () {
   1.480 +      var postDataStream = StringInputStream.
   1.481 +                           createInstance(Ci.nsIStringInputStream);
   1.482 +      postDataStream.data = 'exceptions=' + encodeURIComponent(exceptions);
   1.483 +      var postData = MimeInputStream.createInstance(Ci.nsIMIMEInputStream);
   1.484 +      postData.addHeader("Content-Type", "application/x-www-form-urlencoded");
   1.485 +      postData.addContentLength = true;
   1.486 +      postData.setData(postDataStream);
   1.487 +      this.window.openDialog('chrome://browser/content', '_blank',
   1.488 +                             'all,dialog=no', base + params, null, null,
   1.489 +                             postData);
   1.490 +    }.bind(this));
   1.491 +  },
   1.492 +  externalCom: function (data) {
   1.493 +    if (!this.allowScriptAccess)
   1.494 +      return;
   1.495 +
   1.496 +    // TODO check security ?
   1.497 +    var parentWindow = this.window.parent.wrappedJSObject;
   1.498 +    var embedTag = this.embedTag.wrappedJSObject;
   1.499 +    switch (data.action) {
   1.500 +    case 'init':
   1.501 +      if (this.externalComInitialized)
   1.502 +        return;
   1.503 +
   1.504 +      this.externalComInitialized = true;
   1.505 +      var eventTarget = this.window.document;
   1.506 +      initExternalCom(parentWindow, embedTag, eventTarget);
   1.507 +      return;
   1.508 +    case 'getId':
   1.509 +      return embedTag.id;
   1.510 +    case 'eval':
   1.511 +      return parentWindow.__flash__eval(data.expression);
   1.512 +    case 'call':
   1.513 +      return parentWindow.__flash__call(data.request);
   1.514 +    case 'register':
   1.515 +      return embedTag.__flash__registerCallback(data.functionName);
   1.516 +    case 'unregister':
   1.517 +      return embedTag.__flash__unregisterCallback(data.functionName);
   1.518 +    }
   1.519 +  },
   1.520 +  getWindowUrl: function() {
   1.521 +    return this.window.parent.wrappedJSObject.location + '';
   1.522 +  }
   1.523 +};
   1.524 +
   1.525 +// Event listener to trigger chrome privedged code.
   1.526 +function RequestListener(actions) {
   1.527 +  this.actions = actions;
   1.528 +}
   1.529 +// Receive an event and synchronously or asynchronously responds.
   1.530 +RequestListener.prototype.receive = function(event) {
   1.531 +  var message = event.target;
   1.532 +  var action = event.detail.action;
   1.533 +  var data = event.detail.data;
   1.534 +  var sync = event.detail.sync;
   1.535 +  var actions = this.actions;
   1.536 +  if (!(action in actions)) {
   1.537 +    log('Unknown action: ' + action);
   1.538 +    return;
   1.539 +  }
   1.540 +  if (sync) {
   1.541 +    var response = actions[action].call(this.actions, data);
   1.542 +    var detail = event.detail;
   1.543 +    detail.__exposedProps__ = {response: 'r'};
   1.544 +    detail.response = response;
   1.545 +  } else {
   1.546 +    var response;
   1.547 +    if (event.detail.callback) {
   1.548 +      var cookie = event.detail.cookie;
   1.549 +      response = function sendResponse(response) {
   1.550 +        var doc = actions.document;
   1.551 +        try {
   1.552 +          var listener = doc.createEvent('CustomEvent');
   1.553 +          listener.initCustomEvent('shumway.response', true, false,
   1.554 +                                   {response: response,
   1.555 +                                    cookie: cookie,
   1.556 +                                    __exposedProps__: {response: 'r', cookie: 'r'}});
   1.557 +
   1.558 +          return message.dispatchEvent(listener);
   1.559 +        } catch (e) {
   1.560 +          // doc is no longer accessible because the requestor is already
   1.561 +          // gone. unloaded content cannot receive the response anyway.
   1.562 +        }
   1.563 +      };
   1.564 +    }
   1.565 +    actions[action].call(this.actions, data, response);
   1.566 +  }
   1.567 +};
   1.568 +
   1.569 +var ActivationQueue = {
   1.570 +  nonActive: [],
   1.571 +  initializing: -1,
   1.572 +  activationTimeout: null,
   1.573 +  get currentNonActive() {
   1.574 +    return this.nonActive[this.initializing];
   1.575 +  },
   1.576 +  enqueue: function ActivationQueue_enqueue(actions) {
   1.577 +    this.nonActive.push(actions);
   1.578 +    if (this.nonActive.length === 1) {
   1.579 +      this.activateNext();
   1.580 +    }
   1.581 +  },
   1.582 +  findLastOnPage: function ActivationQueue_findLastOnPage(baseUrl) {
   1.583 +    for (var i = this.nonActive.length - 1; i >= 0; i--) {
   1.584 +      if (this.nonActive[i].baseUrl === baseUrl) {
   1.585 +        return this.nonActive[i];
   1.586 +      }
   1.587 +    }
   1.588 +    return null;
   1.589 +  },
   1.590 +  activateNext: function ActivationQueue_activateNext() {
   1.591 +    function weightInstance(actions) {
   1.592 +      // set of heuristics for find the most important instance to load
   1.593 +      var weight = 0;
   1.594 +      // using linear distance to the top-left of the view area
   1.595 +      if (actions.embedTag) {
   1.596 +        var window = actions.window;
   1.597 +        var clientRect = actions.embedTag.getBoundingClientRect();
   1.598 +        weight -= Math.abs(clientRect.left - window.scrollX) +
   1.599 +                  Math.abs(clientRect.top - window.scrollY);
   1.600 +      }
   1.601 +      var doc = actions.document;
   1.602 +      if (!doc.hidden) {
   1.603 +        weight += 100000; // might not be that important if hidden
   1.604 +      }
   1.605 +      if (actions.embedTag &&
   1.606 +          actions.embedTag.ownerDocument.hasFocus()) {
   1.607 +        weight += 10000; // parent document is focused
   1.608 +      }
   1.609 +      return weight;
   1.610 +    }
   1.611 +
   1.612 +    if (this.activationTimeout) {
   1.613 +      this.activationTimeout.cancel();
   1.614 +      this.activationTimeout = null;
   1.615 +    }
   1.616 +
   1.617 +    if (this.initializing >= 0) {
   1.618 +      this.nonActive.splice(this.initializing, 1);
   1.619 +    }
   1.620 +    var weights = [];
   1.621 +    for (var i = 0; i < this.nonActive.length; i++) {
   1.622 +      try {
   1.623 +        var weight = weightInstance(this.nonActive[i]);
   1.624 +        weights.push(weight);
   1.625 +      } catch (ex) {
   1.626 +        // unable to calc weight the instance, removing
   1.627 +        log('Shumway instance weight calculation failed: ' + ex);
   1.628 +        this.nonActive.splice(i, 1);
   1.629 +        i--;
   1.630 +      }
   1.631 +    }
   1.632 +
   1.633 +    do {
   1.634 +      if (this.nonActive.length === 0) {
   1.635 +        this.initializing = -1;
   1.636 +        return;
   1.637 +      }
   1.638 +
   1.639 +      var maxWeightIndex = 0;
   1.640 +      var maxWeight = weights[0];
   1.641 +      for (var i = 1; i < weights.length; i++) {
   1.642 +        if (maxWeight < weights[i]) {
   1.643 +          maxWeight = weights[i];
   1.644 +          maxWeightIndex = i;
   1.645 +        }
   1.646 +      }
   1.647 +      try {
   1.648 +        this.initializing = maxWeightIndex;
   1.649 +        this.nonActive[maxWeightIndex].activationCallback();
   1.650 +        break;
   1.651 +      } catch (ex) {
   1.652 +        // unable to initialize the instance, trying another one
   1.653 +        log('Shumway instance initialization failed: ' + ex);
   1.654 +        this.nonActive.splice(maxWeightIndex, 1);
   1.655 +        weights.splice(maxWeightIndex, 1);
   1.656 +      }
   1.657 +    } while (true);
   1.658 +
   1.659 +    var ACTIVATION_TIMEOUT = 3000;
   1.660 +    this.activationTimeout = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
   1.661 +    this.activationTimeout.initWithCallback(function () {
   1.662 +      log('Timeout during shumway instance initialization');
   1.663 +      this.activateNext();
   1.664 +    }.bind(this), ACTIVATION_TIMEOUT, Ci.nsITimer.TYPE_ONE_SHOT);
   1.665 +  }
   1.666 +};
   1.667 +
   1.668 +function activateShumwayScripts(window, preview) {
   1.669 +  function loadScripts(scripts, callback) {
   1.670 +    function scriptLoaded() {
   1.671 +      leftToLoad--;
   1.672 +      if (leftToLoad === 0) {
   1.673 +        callback();
   1.674 +      }
   1.675 +    }
   1.676 +    var leftToLoad = scripts.length;
   1.677 +    var document = window.document.wrappedJSObject;
   1.678 +    var head = document.getElementsByTagName('head')[0];
   1.679 +    for (var i = 0; i < scripts.length; i++) {
   1.680 +      var script = document.createElement('script');
   1.681 +      script.type = "text/javascript";
   1.682 +      script.src = scripts[i];
   1.683 +      script.onload = scriptLoaded;
   1.684 +      head.appendChild(script);
   1.685 +    }
   1.686 +  }
   1.687 +
   1.688 +  function initScripts() {
   1.689 +    if (preview) {
   1.690 +      loadScripts(['resource://shumway/web/preview.js'], function () {
   1.691 +        window.wrappedJSObject.runSniffer();
   1.692 +      });
   1.693 +    } else {
   1.694 +      loadScripts(['resource://shumway/shumway.js',
   1.695 +                   'resource://shumway/web/avm-sandbox.js'], function () {
   1.696 +        window.wrappedJSObject.runViewer();
   1.697 +      });
   1.698 +    }
   1.699 +  }
   1.700 +
   1.701 +  window.wrappedJSObject.SHUMWAY_ROOT = "resource://shumway/";
   1.702 +
   1.703 +  if (window.document.readyState === "interactive" ||
   1.704 +      window.document.readyState === "complete") {
   1.705 +    initScripts();
   1.706 +  } else {
   1.707 +    window.document.addEventListener('DOMContentLoaded', initScripts);
   1.708 +  }
   1.709 +}
   1.710 +
   1.711 +function initExternalCom(wrappedWindow, wrappedObject, targetDocument) {
   1.712 +  if (!wrappedWindow.__flash__initialized) {
   1.713 +    wrappedWindow.__flash__initialized = true;
   1.714 +    wrappedWindow.__flash__toXML = function __flash__toXML(obj) {
   1.715 +      switch (typeof obj) {
   1.716 +      case 'boolean':
   1.717 +        return obj ? '<true/>' : '<false/>';
   1.718 +      case 'number':
   1.719 +        return '<number>' + obj + '</number>';
   1.720 +      case 'object':
   1.721 +        if (obj === null) {
   1.722 +          return '<null/>';
   1.723 +        }
   1.724 +        if ('hasOwnProperty' in obj && obj.hasOwnProperty('length')) {
   1.725 +          // array
   1.726 +          var xml = '<array>';
   1.727 +          for (var i = 0; i < obj.length; i++) {
   1.728 +            xml += '<property id="' + i + '">' + __flash__toXML(obj[i]) + '</property>';
   1.729 +          }
   1.730 +          return xml + '</array>';
   1.731 +        }
   1.732 +        var xml = '<object>';
   1.733 +        for (var i in obj) {
   1.734 +          xml += '<property id="' + i + '">' + __flash__toXML(obj[i]) + '</property>';
   1.735 +        }
   1.736 +        return xml + '</object>';
   1.737 +      case 'string':
   1.738 +        return '<string>' + obj.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;') + '</string>';
   1.739 +      case 'undefined':
   1.740 +        return '<undefined/>';
   1.741 +      }
   1.742 +    };
   1.743 +    var sandbox = new Cu.Sandbox(wrappedWindow, {sandboxPrototype: wrappedWindow});
   1.744 +    wrappedWindow.__flash__eval = function (evalInSandbox, sandbox, expr) {
   1.745 +      this.console.log('__flash__eval: ' + expr);
   1.746 +      return evalInSandbox(expr, sandbox);
   1.747 +    }.bind(wrappedWindow, Cu.evalInSandbox, sandbox);
   1.748 +    wrappedWindow.__flash__call = function (expr) {
   1.749 +      this.console.log('__flash__call (ignored): ' + expr);
   1.750 +    };
   1.751 +  }
   1.752 +  wrappedObject.__flash__registerCallback = function (functionName) {
   1.753 +    wrappedWindow.console.log('__flash__registerCallback: ' + functionName);
   1.754 +    this[functionName] = function () {
   1.755 +      var args = Array.prototype.slice.call(arguments, 0);
   1.756 +      wrappedWindow.console.log('__flash__callIn: ' + functionName);
   1.757 +      var e = targetDocument.createEvent('CustomEvent');
   1.758 +      e.initCustomEvent('shumway.remote', true, false, {
   1.759 +        functionName: functionName,
   1.760 +        args: args,
   1.761 +        __exposedProps__: {args: 'r', functionName: 'r', result: 'rw'}
   1.762 +      });
   1.763 +      targetDocument.dispatchEvent(e);
   1.764 +      return e.detail.result;
   1.765 +    };
   1.766 +  };
   1.767 +  wrappedObject.__flash__unregisterCallback = function (functionName) {
   1.768 +    wrappedWindow.console.log('__flash__unregisterCallback: ' + functionName);
   1.769 +    delete this[functionName];
   1.770 +  };
   1.771 +}
   1.772 +
   1.773 +function ShumwayStreamConverterBase() {
   1.774 +}
   1.775 +
   1.776 +ShumwayStreamConverterBase.prototype = {
   1.777 +  QueryInterface: XPCOMUtils.generateQI([
   1.778 +      Ci.nsISupports,
   1.779 +      Ci.nsIStreamConverter,
   1.780 +      Ci.nsIStreamListener,
   1.781 +      Ci.nsIRequestObserver
   1.782 +  ]),
   1.783 +
   1.784 +  /*
   1.785 +   * This component works as such:
   1.786 +   * 1. asyncConvertData stores the listener
   1.787 +   * 2. onStartRequest creates a new channel, streams the viewer and cancels
   1.788 +   *    the request so Shumway can do the request
   1.789 +   * Since the request is cancelled onDataAvailable should not be called. The
   1.790 +   * onStopRequest does nothing. The convert function just returns the stream,
   1.791 +   * it's just the synchronous version of asyncConvertData.
   1.792 +   */
   1.793 +
   1.794 +  // nsIStreamConverter::convert
   1.795 +  convert: function(aFromStream, aFromType, aToType, aCtxt) {
   1.796 +    throw Cr.NS_ERROR_NOT_IMPLEMENTED;
   1.797 +  },
   1.798 +
   1.799 +  getUrlHint: function(requestUrl) {
   1.800 +    return requestUrl.spec;
   1.801 +  },
   1.802 +
   1.803 +  createChromeActions: function(window, document, urlHint) {
   1.804 +    var url = urlHint;
   1.805 +    var baseUrl;
   1.806 +    var pageUrl;
   1.807 +    var element = window.frameElement;
   1.808 +    var isOverlay = false;
   1.809 +    var objectParams = {};
   1.810 +    if (element) {
   1.811 +      // PlayPreview overlay "belongs" to the embed/object tag and consists of
   1.812 +      // DIV and IFRAME. Starting from IFRAME and looking for first object tag.
   1.813 +      var tagName = element.nodeName, containerElement;
   1.814 +      while (tagName != 'EMBED' && tagName != 'OBJECT') {
   1.815 +        // plugin overlay skipping until the target plugin is found
   1.816 +        isOverlay = true;
   1.817 +        containerElement = element;
   1.818 +        element = element.parentNode;
   1.819 +        if (!element) {
   1.820 +          throw new Error('Plugin element is not found');
   1.821 +        }
   1.822 +        tagName = element.nodeName;
   1.823 +      }
   1.824 +
   1.825 +      if (isOverlay) {
   1.826 +        // Checking if overlay is a proper PlayPreview overlay.
   1.827 +        for (var i = 0; i < element.children.length; i++) {
   1.828 +          if (element.children[i] === containerElement) {
   1.829 +            throw new Error('Plugin element is invalid');
   1.830 +          }
   1.831 +        }
   1.832 +      }
   1.833 +    }
   1.834 +
   1.835 +    if (element) {
   1.836 +      // Getting absolute URL from the EMBED tag
   1.837 +      url = element.srcURI.spec;
   1.838 +
   1.839 +      pageUrl = element.ownerDocument.location.href; // proper page url?
   1.840 +
   1.841 +      if (tagName == 'EMBED') {
   1.842 +        for (var i = 0; i < element.attributes.length; ++i) {
   1.843 +          var paramName = element.attributes[i].localName.toLowerCase();
   1.844 +          objectParams[paramName] = element.attributes[i].value;
   1.845 +        }
   1.846 +      } else {
   1.847 +        for (var i = 0; i < element.childNodes.length; ++i) {
   1.848 +          var paramElement = element.childNodes[i];
   1.849 +          if (paramElement.nodeType != 1 ||
   1.850 +              paramElement.nodeName != 'PARAM') {
   1.851 +            continue;
   1.852 +          }
   1.853 +          var paramName = paramElement.getAttribute('name').toLowerCase();
   1.854 +          objectParams[paramName] = paramElement.getAttribute('value');
   1.855 +        }
   1.856 +      }
   1.857 +    }
   1.858 +
   1.859 +    if (!url) { // at this point url shall be known -- asserting
   1.860 +      throw new Error('Movie url is not specified');
   1.861 +    }
   1.862 +
   1.863 +    baseUrl = objectParams.base || pageUrl;
   1.864 +
   1.865 +    var movieParams = {};
   1.866 +    if (objectParams.flashvars) {
   1.867 +      movieParams = parseQueryString(objectParams.flashvars);
   1.868 +    }
   1.869 +    var queryStringMatch = /\?([^#]+)/.exec(url);
   1.870 +    if (queryStringMatch) {
   1.871 +      var queryStringParams = parseQueryString(queryStringMatch[1]);
   1.872 +      for (var i in queryStringParams) {
   1.873 +        if (!(i in movieParams)) {
   1.874 +          movieParams[i] = queryStringParams[i];
   1.875 +        }
   1.876 +      }
   1.877 +    }
   1.878 +
   1.879 +    var allowScriptAccess = false;
   1.880 +    switch (objectParams.allowscriptaccess || 'sameDomain') {
   1.881 +    case 'always':
   1.882 +      allowScriptAccess = true;
   1.883 +      break;
   1.884 +    case 'never':
   1.885 +      allowScriptAccess = false;
   1.886 +      break;
   1.887 +    default:
   1.888 +      if (!pageUrl)
   1.889 +        break;
   1.890 +      try {
   1.891 +        // checking if page is in same domain (? same protocol and port)
   1.892 +        allowScriptAccess =
   1.893 +          Services.io.newURI('/', null, Services.io.newURI(pageUrl, null, null)).spec ==
   1.894 +          Services.io.newURI('/', null, Services.io.newURI(url, null, null)).spec;
   1.895 +      } catch (ex) {}
   1.896 +      break;
   1.897 +    }
   1.898 +
   1.899 +    var actions = new ChromeActions(url, window, document);
   1.900 +    actions.objectParams = objectParams;
   1.901 +    actions.movieParams = movieParams;
   1.902 +    actions.baseUrl = baseUrl || url;
   1.903 +    actions.isOverlay = isOverlay;
   1.904 +    actions.embedTag = element;
   1.905 +    actions.isPausedAtStart = /\bpaused=true$/.test(urlHint);
   1.906 +    actions.allowScriptAccess = allowScriptAccess;
   1.907 +    return actions;
   1.908 +  },
   1.909 +
   1.910 +  // nsIStreamConverter::asyncConvertData
   1.911 +  asyncConvertData: function(aFromType, aToType, aListener, aCtxt) {
   1.912 +    // Store the listener passed to us
   1.913 +    this.listener = aListener;
   1.914 +  },
   1.915 +
   1.916 +  // nsIStreamListener::onDataAvailable
   1.917 +  onDataAvailable: function(aRequest, aContext, aInputStream, aOffset, aCount) {
   1.918 +    // Do nothing since all the data loading is handled by the viewer.
   1.919 +    log('SANITY CHECK: onDataAvailable SHOULD NOT BE CALLED!');
   1.920 +  },
   1.921 +
   1.922 +  // nsIRequestObserver::onStartRequest
   1.923 +  onStartRequest: function(aRequest, aContext) {
   1.924 +    // Setup the request so we can use it below.
   1.925 +    aRequest.QueryInterface(Ci.nsIChannel);
   1.926 +
   1.927 +    aRequest.QueryInterface(Ci.nsIWritablePropertyBag);
   1.928 +
   1.929 +    // Change the content type so we don't get stuck in a loop.
   1.930 +    aRequest.setProperty('contentType', aRequest.contentType);
   1.931 +    aRequest.contentType = 'text/html';
   1.932 +
   1.933 +    // TODO For now suspending request, however we can continue fetching data
   1.934 +    aRequest.suspend();
   1.935 +
   1.936 +    var originalURI = aRequest.URI;
   1.937 +
   1.938 +    // checking if the plug-in shall be run in simple mode
   1.939 +    var isSimpleMode = originalURI.spec === EXPECTED_PLAYPREVIEW_URI_PREFIX &&
   1.940 +                       getBoolPref('shumway.simpleMode', false);
   1.941 +
   1.942 +    // Create a new channel that loads the viewer as a resource.
   1.943 +    var viewerUrl = isSimpleMode ?
   1.944 +                    'resource://shumway/web/simple.html' :
   1.945 +                    'resource://shumway/web/viewer.html';
   1.946 +    var channel = Services.io.newChannel(viewerUrl, null, null);
   1.947 +
   1.948 +    var converter = this;
   1.949 +    var listener = this.listener;
   1.950 +    // Proxy all the request observer calls, when it gets to onStopRequest
   1.951 +    // we can get the dom window.
   1.952 +    var proxy = {
   1.953 +      onStartRequest: function(request, context) {
   1.954 +        listener.onStartRequest(aRequest, context);
   1.955 +      },
   1.956 +      onDataAvailable: function(request, context, inputStream, offset, count) {
   1.957 +        listener.onDataAvailable(aRequest, context, inputStream, offset, count);
   1.958 +      },
   1.959 +      onStopRequest: function(request, context, statusCode) {
   1.960 +        // Cancel the request so the viewer can handle it.
   1.961 +        aRequest.resume();
   1.962 +        aRequest.cancel(Cr.NS_BINDING_ABORTED);
   1.963 +
   1.964 +        var domWindow = getDOMWindow(channel);
   1.965 +        let actions = converter.createChromeActions(domWindow,
   1.966 +                                                    domWindow.document,
   1.967 +                                                    converter.getUrlHint(originalURI));
   1.968 +
   1.969 +        if (!isShumwayEnabledFor(actions)) {
   1.970 +          fallbackToNativePlugin(domWindow, false, true);
   1.971 +          return;
   1.972 +        }
   1.973 +
   1.974 +        // Report telemetry on amount of swfs on the page
   1.975 +        if (actions.isOverlay) {
   1.976 +          // Looking for last actions with same baseUrl
   1.977 +          var prevPageActions = ActivationQueue.findLastOnPage(actions.baseUrl);
   1.978 +          var pageIndex = !prevPageActions ? 1 : (prevPageActions.telemetry.pageIndex + 1);
   1.979 +          actions.telemetry.pageIndex = pageIndex;
   1.980 +          ShumwayTelemetry.onPageIndex(pageIndex);
   1.981 +        } else {
   1.982 +          ShumwayTelemetry.onPageIndex(0);
   1.983 +        }
   1.984 +
   1.985 +        actions.activationCallback = function(domWindow, isSimpleMode) {
   1.986 +          delete this.activationCallback;
   1.987 +          activateShumwayScripts(domWindow, isSimpleMode);
   1.988 +        }.bind(actions, domWindow, isSimpleMode);
   1.989 +        ActivationQueue.enqueue(actions);
   1.990 +
   1.991 +        let requestListener = new RequestListener(actions);
   1.992 +        domWindow.addEventListener('shumway.message', function(event) {
   1.993 +          requestListener.receive(event);
   1.994 +        }, false, true);
   1.995 +
   1.996 +        listener.onStopRequest(aRequest, context, statusCode);
   1.997 +      }
   1.998 +    };
   1.999 +
  1.1000 +    // Keep the URL the same so the browser sees it as the same.
  1.1001 +    channel.originalURI = aRequest.URI;
  1.1002 +    channel.loadGroup = aRequest.loadGroup;
  1.1003 +
  1.1004 +    // We can use resource principal when data is fetched by the chrome
  1.1005 +    // e.g. useful for NoScript
  1.1006 +    var securityManager = Cc['@mozilla.org/scriptsecuritymanager;1']
  1.1007 +                          .getService(Ci.nsIScriptSecurityManager);
  1.1008 +    var uri = Services.io.newURI(viewerUrl, null, null);
  1.1009 +    var resourcePrincipal = securityManager.getNoAppCodebasePrincipal(uri);
  1.1010 +    aRequest.owner = resourcePrincipal;
  1.1011 +    channel.asyncOpen(proxy, aContext);
  1.1012 +  },
  1.1013 +
  1.1014 +  // nsIRequestObserver::onStopRequest
  1.1015 +  onStopRequest: function(aRequest, aContext, aStatusCode) {
  1.1016 +    // Do nothing.
  1.1017 +  }
  1.1018 +};
  1.1019 +
  1.1020 +// properties required for XPCOM registration:
  1.1021 +function copyProperties(obj, template) {
  1.1022 +  for (var prop in template) {
  1.1023 +    obj[prop] = template[prop];
  1.1024 +  }
  1.1025 +}
  1.1026 +
  1.1027 +function ShumwayStreamConverter() {}
  1.1028 +ShumwayStreamConverter.prototype = new ShumwayStreamConverterBase();
  1.1029 +copyProperties(ShumwayStreamConverter.prototype, {
  1.1030 +  classID: Components.ID('{4c6030f7-e20a-264f-5b0e-ada3a9e97384}'),
  1.1031 +  classDescription: 'Shumway Content Converter Component',
  1.1032 +  contractID: '@mozilla.org/streamconv;1?from=application/x-shockwave-flash&to=*/*'
  1.1033 +});
  1.1034 +
  1.1035 +function ShumwayStreamOverlayConverter() {}
  1.1036 +ShumwayStreamOverlayConverter.prototype = new ShumwayStreamConverterBase();
  1.1037 +copyProperties(ShumwayStreamOverlayConverter.prototype, {
  1.1038 +  classID: Components.ID('{4c6030f7-e20a-264f-5f9b-ada3a9e97384}'),
  1.1039 +  classDescription: 'Shumway PlayPreview Component',
  1.1040 +  contractID: '@mozilla.org/streamconv;1?from=application/x-moz-playpreview&to=*/*'
  1.1041 +});
  1.1042 +ShumwayStreamOverlayConverter.prototype.getUrlHint = function (requestUrl) {
  1.1043 +  return '';
  1.1044 +};

mercurial