browser/extensions/shumway/content/ShumwayStreamConverter.jsm

Thu, 22 Jan 2015 13:21:57 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Thu, 22 Jan 2015 13:21:57 +0100
branch
TOR_BUG_9701
changeset 15
b8a032363ba2
permissions
-rw-r--r--

Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6

michael@0 1 /* -*- Mode: js; js-indent-level: 2; indent-tabs-mode: nil; tab-width: 2 -*- */
michael@0 2 /* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
michael@0 3 /*
michael@0 4 * Copyright 2013 Mozilla Foundation
michael@0 5 *
michael@0 6 * Licensed under the Apache License, Version 2.0 (the "License");
michael@0 7 * you may not use this file except in compliance with the License.
michael@0 8 * You may obtain a copy of the License at
michael@0 9 *
michael@0 10 * http://www.apache.org/licenses/LICENSE-2.0
michael@0 11 *
michael@0 12 * Unless required by applicable law or agreed to in writing, software
michael@0 13 * distributed under the License is distributed on an "AS IS" BASIS,
michael@0 14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
michael@0 15 * See the License for the specific language governing permissions and
michael@0 16 * limitations under the License.
michael@0 17 */
michael@0 18
michael@0 19 'use strict';
michael@0 20
michael@0 21 var EXPORTED_SYMBOLS = ['ShumwayStreamConverter', 'ShumwayStreamOverlayConverter'];
michael@0 22
michael@0 23 const Cc = Components.classes;
michael@0 24 const Ci = Components.interfaces;
michael@0 25 const Cr = Components.results;
michael@0 26 const Cu = Components.utils;
michael@0 27
michael@0 28 const SHUMWAY_CONTENT_TYPE = 'application/x-shockwave-flash';
michael@0 29 const EXPECTED_PLAYPREVIEW_URI_PREFIX = 'data:application/x-moz-playpreview;,' +
michael@0 30 SHUMWAY_CONTENT_TYPE;
michael@0 31
michael@0 32 const FIREFOX_ID = '{ec8030f7-c20a-464f-9b0e-13a3a9e97384}';
michael@0 33 const SEAMONKEY_ID = '{92650c4d-4b8e-4d2a-b7eb-24ecf4f6b63a}';
michael@0 34
michael@0 35 const MAX_CLIPBOARD_DATA_SIZE = 8000;
michael@0 36
michael@0 37 Cu.import('resource://gre/modules/XPCOMUtils.jsm');
michael@0 38 Cu.import('resource://gre/modules/Services.jsm');
michael@0 39 Cu.import('resource://gre/modules/NetUtil.jsm');
michael@0 40 Cu.import('resource://gre/modules/Promise.jsm');
michael@0 41
michael@0 42 XPCOMUtils.defineLazyModuleGetter(this, 'PrivateBrowsingUtils',
michael@0 43 'resource://gre/modules/PrivateBrowsingUtils.jsm');
michael@0 44
michael@0 45 XPCOMUtils.defineLazyModuleGetter(this, 'AddonManager',
michael@0 46 'resource://gre/modules/AddonManager.jsm');
michael@0 47
michael@0 48 XPCOMUtils.defineLazyModuleGetter(this, 'ShumwayTelemetry',
michael@0 49 'resource://shumway/ShumwayTelemetry.jsm');
michael@0 50
michael@0 51 let Svc = {};
michael@0 52 XPCOMUtils.defineLazyServiceGetter(Svc, 'mime',
michael@0 53 '@mozilla.org/mime;1', 'nsIMIMEService');
michael@0 54
michael@0 55 let StringInputStream = Cc["@mozilla.org/io/string-input-stream;1"];
michael@0 56 let MimeInputStream = Cc["@mozilla.org/network/mime-input-stream;1"];
michael@0 57
michael@0 58 function getBoolPref(pref, def) {
michael@0 59 try {
michael@0 60 return Services.prefs.getBoolPref(pref);
michael@0 61 } catch (ex) {
michael@0 62 return def;
michael@0 63 }
michael@0 64 }
michael@0 65
michael@0 66 function getStringPref(pref, def) {
michael@0 67 try {
michael@0 68 return Services.prefs.getComplexValue(pref, Ci.nsISupportsString).data;
michael@0 69 } catch (ex) {
michael@0 70 return def;
michael@0 71 }
michael@0 72 }
michael@0 73
michael@0 74 function log(aMsg) {
michael@0 75 let msg = 'ShumwayStreamConverter.js: ' + (aMsg.join ? aMsg.join('') : aMsg);
michael@0 76 Services.console.logStringMessage(msg);
michael@0 77 dump(msg + '\n');
michael@0 78 }
michael@0 79
michael@0 80 function getDOMWindow(aChannel) {
michael@0 81 var requestor = aChannel.notificationCallbacks ||
michael@0 82 aChannel.loadGroup.notificationCallbacks;
michael@0 83 var win = requestor.getInterface(Components.interfaces.nsIDOMWindow);
michael@0 84 return win;
michael@0 85 }
michael@0 86
michael@0 87 function parseQueryString(qs) {
michael@0 88 if (!qs)
michael@0 89 return {};
michael@0 90
michael@0 91 if (qs.charAt(0) == '?')
michael@0 92 qs = qs.slice(1);
michael@0 93
michael@0 94 var values = qs.split('&');
michael@0 95 var obj = {};
michael@0 96 for (var i = 0; i < values.length; i++) {
michael@0 97 var kv = values[i].split('=');
michael@0 98 var key = kv[0], value = kv[1];
michael@0 99 obj[decodeURIComponent(key)] = decodeURIComponent(value);
michael@0 100 }
michael@0 101
michael@0 102 return obj;
michael@0 103 }
michael@0 104
michael@0 105 function domainMatches(host, pattern) {
michael@0 106 if (!pattern) return false;
michael@0 107 if (pattern === '*') return true;
michael@0 108 host = host.toLowerCase();
michael@0 109 var parts = pattern.toLowerCase().split('*');
michael@0 110 if (host.indexOf(parts[0]) !== 0) return false;
michael@0 111 var p = parts[0].length;
michael@0 112 for (var i = 1; i < parts.length; i++) {
michael@0 113 var j = host.indexOf(parts[i], p);
michael@0 114 if (j === -1) return false;
michael@0 115 p = j + parts[i].length;
michael@0 116 }
michael@0 117 return parts[parts.length - 1] === '' || p === host.length;
michael@0 118 }
michael@0 119
michael@0 120 function fetchPolicyFile(url, cache, callback) {
michael@0 121 if (url in cache) {
michael@0 122 return callback(cache[url]);
michael@0 123 }
michael@0 124
michael@0 125 log('Fetching policy file at ' + url);
michael@0 126 var MAX_POLICY_SIZE = 8192;
michael@0 127 var xhr = Components.classes["@mozilla.org/xmlextras/xmlhttprequest;1"]
michael@0 128 .createInstance(Ci.nsIXMLHttpRequest);
michael@0 129 xhr.open('GET', url, true);
michael@0 130 xhr.overrideMimeType('text/xml');
michael@0 131 xhr.onprogress = function (e) {
michael@0 132 if (e.loaded >= MAX_POLICY_SIZE) {
michael@0 133 xhr.abort();
michael@0 134 cache[url] = false;
michael@0 135 callback(null, 'Max policy size');
michael@0 136 }
michael@0 137 };
michael@0 138 xhr.onreadystatechange = function(event) {
michael@0 139 if (xhr.readyState === 4) {
michael@0 140 // TODO disable redirects
michael@0 141 var doc = xhr.responseXML;
michael@0 142 if (xhr.status !== 200 || !doc) {
michael@0 143 cache[url] = false;
michael@0 144 return callback(null, 'Invalid HTTP status: ' + xhr.statusText);
michael@0 145 }
michael@0 146 // parsing params
michael@0 147 var params = doc.documentElement.childNodes;
michael@0 148 var policy = { siteControl: null, allowAccessFrom: []};
michael@0 149 for (var i = 0; i < params.length; i++) {
michael@0 150 switch (params[i].localName) {
michael@0 151 case 'site-control':
michael@0 152 policy.siteControl = params[i].getAttribute('permitted-cross-domain-policies');
michael@0 153 break;
michael@0 154 case 'allow-access-from':
michael@0 155 var access = {
michael@0 156 domain: params[i].getAttribute('domain'),
michael@0 157 security: params[i].getAttribute('security') === 'true'
michael@0 158 };
michael@0 159 policy.allowAccessFrom.push(access);
michael@0 160 break;
michael@0 161 default:
michael@0 162 // TODO allow-http-request-headers-from and other
michael@0 163 break;
michael@0 164 }
michael@0 165 }
michael@0 166 callback(cache[url] = policy);
michael@0 167 }
michael@0 168 };
michael@0 169 xhr.send(null);
michael@0 170 }
michael@0 171
michael@0 172 function isShumwayEnabledFor(actions) {
michael@0 173 // disabled for PrivateBrowsing windows
michael@0 174 if (PrivateBrowsingUtils.isWindowPrivate(actions.window)) {
michael@0 175 return false;
michael@0 176 }
michael@0 177 // disabled if embed tag specifies shumwaymode (for testing purpose)
michael@0 178 if (actions.objectParams['shumwaymode'] === 'off') {
michael@0 179 return false;
michael@0 180 }
michael@0 181
michael@0 182 var url = actions.url;
michael@0 183 var baseUrl = actions.baseUrl;
michael@0 184
michael@0 185 // blacklisting well known sites with issues
michael@0 186 if (/\.ytimg\.com\//i.test(url) /* youtube movies */ ||
michael@0 187 /\/vui.swf\b/i.test(url) /* vidyo manager */ ||
michael@0 188 /soundcloud\.com\/player\/assets\/swf/i.test(url) /* soundcloud */ ||
michael@0 189 /sndcdn\.com\/assets\/swf/.test(url) /* soundcloud */ ||
michael@0 190 /vimeocdn\.com/.test(url) /* vimeo */) {
michael@0 191 return false;
michael@0 192 }
michael@0 193
michael@0 194 return true;
michael@0 195 }
michael@0 196
michael@0 197 function getVersionInfo() {
michael@0 198 var deferred = Promise.defer();
michael@0 199 var versionInfo = {
michael@0 200 geckoMstone : 'unknown',
michael@0 201 geckoBuildID: 'unknown',
michael@0 202 shumwayVersion: 'unknown'
michael@0 203 };
michael@0 204 try {
michael@0 205 versionInfo.geckoMstone = Services.prefs.getCharPref('gecko.mstone');
michael@0 206 versionInfo.geckoBuildID = Services.prefs.getCharPref('gecko.buildID');
michael@0 207 } catch (e) {
michael@0 208 log('Error encountered while getting platform version info:', e);
michael@0 209 }
michael@0 210 try {
michael@0 211 var addonId = "shumway@research.mozilla.org";
michael@0 212 AddonManager.getAddonByID(addonId, function(addon) {
michael@0 213 versionInfo.shumwayVersion = addon ? addon.version : 'n/a';
michael@0 214 deferred.resolve(versionInfo);
michael@0 215 });
michael@0 216 } catch (e) {
michael@0 217 log('Error encountered while getting Shumway version info:', e);
michael@0 218 deferred.resolve(versionInfo);
michael@0 219 }
michael@0 220 return deferred.promise;
michael@0 221 }
michael@0 222
michael@0 223 function fallbackToNativePlugin(window, userAction, activateCTP) {
michael@0 224 var obj = window.frameElement;
michael@0 225 var doc = obj.ownerDocument;
michael@0 226 var e = doc.createEvent("CustomEvent");
michael@0 227 e.initCustomEvent("MozPlayPlugin", true, true, activateCTP);
michael@0 228 obj.dispatchEvent(e);
michael@0 229
michael@0 230 ShumwayTelemetry.onFallback(userAction);
michael@0 231 }
michael@0 232
michael@0 233 // All the priviledged actions.
michael@0 234 function ChromeActions(url, window, document) {
michael@0 235 this.url = url;
michael@0 236 this.objectParams = null;
michael@0 237 this.movieParams = null;
michael@0 238 this.baseUrl = url;
michael@0 239 this.isOverlay = false;
michael@0 240 this.isPausedAtStart = false;
michael@0 241 this.window = window;
michael@0 242 this.document = document;
michael@0 243 this.externalComInitialized = false;
michael@0 244 this.allowScriptAccess = false;
michael@0 245 this.crossdomainRequestsCache = Object.create(null);
michael@0 246 this.telemetry = {
michael@0 247 startTime: Date.now(),
michael@0 248 features: [],
michael@0 249 errors: [],
michael@0 250 pageIndex: 0
michael@0 251 };
michael@0 252 }
michael@0 253
michael@0 254 ChromeActions.prototype = {
michael@0 255 getBoolPref: function (data) {
michael@0 256 if (!/^shumway\./.test(data.pref)) {
michael@0 257 return null;
michael@0 258 }
michael@0 259 return getBoolPref(data.pref, data.def);
michael@0 260 },
michael@0 261 getCompilerSettings: function getCompilerSettings() {
michael@0 262 return JSON.stringify({
michael@0 263 appCompiler: getBoolPref('shumway.appCompiler', true),
michael@0 264 sysCompiler: getBoolPref('shumway.sysCompiler', false),
michael@0 265 verifier: getBoolPref('shumway.verifier', true)
michael@0 266 });
michael@0 267 },
michael@0 268 addProfilerMarker: function (marker) {
michael@0 269 if ('nsIProfiler' in Ci) {
michael@0 270 let profiler = Cc["@mozilla.org/tools/profiler;1"].getService(Ci.nsIProfiler);
michael@0 271 profiler.AddMarker(marker);
michael@0 272 }
michael@0 273 },
michael@0 274 getPluginParams: function getPluginParams() {
michael@0 275 return JSON.stringify({
michael@0 276 url: this.url,
michael@0 277 baseUrl : this.baseUrl,
michael@0 278 movieParams: this.movieParams,
michael@0 279 objectParams: this.objectParams,
michael@0 280 isOverlay: this.isOverlay,
michael@0 281 isPausedAtStart: this.isPausedAtStart
michael@0 282 });
michael@0 283 },
michael@0 284 _canDownloadFile: function canDownloadFile(data, callback) {
michael@0 285 var url = data.url, checkPolicyFile = data.checkPolicyFile;
michael@0 286
michael@0 287 // TODO flash cross-origin request
michael@0 288 if (url === this.url) {
michael@0 289 // allow downloading for the original file
michael@0 290 return callback({success: true});
michael@0 291 }
michael@0 292
michael@0 293 // allows downloading from the same origin
michael@0 294 var parsedUrl, parsedBaseUrl;
michael@0 295 try {
michael@0 296 parsedUrl = NetUtil.newURI(url);
michael@0 297 } catch (ex) { /* skipping invalid urls */ }
michael@0 298 try {
michael@0 299 parsedBaseUrl = NetUtil.newURI(this.url);
michael@0 300 } catch (ex) { /* skipping invalid urls */ }
michael@0 301
michael@0 302 if (parsedUrl && parsedBaseUrl &&
michael@0 303 parsedUrl.prePath === parsedBaseUrl.prePath) {
michael@0 304 return callback({success: true});
michael@0 305 }
michael@0 306
michael@0 307 // additionally using internal whitelist
michael@0 308 var whitelist = getStringPref('shumway.whitelist', '');
michael@0 309 if (whitelist && parsedUrl) {
michael@0 310 var whitelisted = whitelist.split(',').some(function (i) {
michael@0 311 return domainMatches(parsedUrl.host, i);
michael@0 312 });
michael@0 313 if (whitelisted) {
michael@0 314 return callback({success: true});
michael@0 315 }
michael@0 316 }
michael@0 317
michael@0 318 if (!checkPolicyFile || !parsedUrl || !parsedBaseUrl) {
michael@0 319 return callback({success: false});
michael@0 320 }
michael@0 321
michael@0 322 // we can request crossdomain.xml
michael@0 323 fetchPolicyFile(parsedUrl.prePath + '/crossdomain.xml', this.crossdomainRequestsCache,
michael@0 324 function (policy, error) {
michael@0 325
michael@0 326 if (!policy || policy.siteControl === 'none') {
michael@0 327 return callback({success: false});
michael@0 328 }
michael@0 329 // TODO assuming master-only, there are also 'by-content-type', 'all', etc.
michael@0 330
michael@0 331 var allowed = policy.allowAccessFrom.some(function (i) {
michael@0 332 return domainMatches(parsedBaseUrl.host, i.domain) &&
michael@0 333 (!i.secure || parsedBaseUrl.scheme.toLowerCase() === 'https');
michael@0 334 });
michael@0 335 return callback({success: allowed});
michael@0 336 }.bind(this));
michael@0 337 },
michael@0 338 loadFile: function loadFile(data) {
michael@0 339 var url = data.url;
michael@0 340 var checkPolicyFile = data.checkPolicyFile;
michael@0 341 var sessionId = data.sessionId;
michael@0 342 var limit = data.limit || 0;
michael@0 343 var method = data.method || "GET";
michael@0 344 var mimeType = data.mimeType;
michael@0 345 var postData = data.postData || null;
michael@0 346
michael@0 347 var win = this.window;
michael@0 348 var baseUrl = this.baseUrl;
michael@0 349
michael@0 350 var performXHR = function () {
michael@0 351 var xhr = Components.classes["@mozilla.org/xmlextras/xmlhttprequest;1"]
michael@0 352 .createInstance(Ci.nsIXMLHttpRequest);
michael@0 353 xhr.open(method, url, true);
michael@0 354 xhr.responseType = "moz-chunked-arraybuffer";
michael@0 355
michael@0 356 if (baseUrl) {
michael@0 357 // Setting the referer uri, some site doing checks if swf is embedded
michael@0 358 // on the original page.
michael@0 359 xhr.setRequestHeader("Referer", baseUrl);
michael@0 360 }
michael@0 361
michael@0 362 // TODO apply range request headers if limit is specified
michael@0 363
michael@0 364 var lastPosition = 0;
michael@0 365 xhr.onprogress = function (e) {
michael@0 366 var position = e.loaded;
michael@0 367 var data = new Uint8Array(xhr.response);
michael@0 368 win.postMessage({callback:"loadFile", sessionId: sessionId, topic: "progress",
michael@0 369 array: data, loaded: e.loaded, total: e.total}, "*");
michael@0 370 lastPosition = position;
michael@0 371 if (limit && e.total >= limit) {
michael@0 372 xhr.abort();
michael@0 373 }
michael@0 374 };
michael@0 375 xhr.onreadystatechange = function(event) {
michael@0 376 if (xhr.readyState === 4) {
michael@0 377 if (xhr.status !== 200 && xhr.status !== 0) {
michael@0 378 win.postMessage({callback:"loadFile", sessionId: sessionId, topic: "error",
michael@0 379 error: xhr.statusText}, "*");
michael@0 380 }
michael@0 381 win.postMessage({callback:"loadFile", sessionId: sessionId, topic: "close"}, "*");
michael@0 382 }
michael@0 383 };
michael@0 384 if (mimeType)
michael@0 385 xhr.setRequestHeader("Content-Type", mimeType);
michael@0 386 xhr.send(postData);
michael@0 387 win.postMessage({callback:"loadFile", sessionId: sessionId, topic: "open"}, "*");
michael@0 388 };
michael@0 389
michael@0 390 this._canDownloadFile({url: url, checkPolicyFile: checkPolicyFile}, function (data) {
michael@0 391 if (data.success) {
michael@0 392 performXHR();
michael@0 393 } else {
michael@0 394 log("data access id prohibited to " + url + " from " + baseUrl);
michael@0 395 win.postMessage({callback:"loadFile", sessionId: sessionId, topic: "error",
michael@0 396 error: "only original swf file or file from the same origin loading supported"}, "*");
michael@0 397 }
michael@0 398 });
michael@0 399 },
michael@0 400 fallback: function(automatic) {
michael@0 401 automatic = !!automatic;
michael@0 402 fallbackToNativePlugin(this.window, !automatic, automatic);
michael@0 403 },
michael@0 404 setClipboard: function (data) {
michael@0 405 if (typeof data !== 'string' ||
michael@0 406 data.length > MAX_CLIPBOARD_DATA_SIZE ||
michael@0 407 !this.document.hasFocus()) {
michael@0 408 return;
michael@0 409 }
michael@0 410 // TODO other security checks?
michael@0 411
michael@0 412 let clipboard = Cc["@mozilla.org/widget/clipboardhelper;1"]
michael@0 413 .getService(Ci.nsIClipboardHelper);
michael@0 414 clipboard.copyString(data);
michael@0 415 },
michael@0 416 unsafeSetClipboard: function (data) {
michael@0 417 if (typeof data !== 'string') {
michael@0 418 return;
michael@0 419 }
michael@0 420 let clipboard = Cc["@mozilla.org/widget/clipboardhelper;1"].getService(Ci.nsIClipboardHelper);
michael@0 421 clipboard.copyString(data);
michael@0 422 },
michael@0 423 endActivation: function () {
michael@0 424 if (ActivationQueue.currentNonActive === this) {
michael@0 425 ActivationQueue.activateNext();
michael@0 426 }
michael@0 427 },
michael@0 428 reportTelemetry: function (data) {
michael@0 429 var topic = data.topic;
michael@0 430 switch (topic) {
michael@0 431 case 'firstFrame':
michael@0 432 var time = Date.now() - this.telemetry.startTime;
michael@0 433 ShumwayTelemetry.onFirstFrame(time);
michael@0 434 break;
michael@0 435 case 'parseInfo':
michael@0 436 ShumwayTelemetry.onParseInfo({
michael@0 437 parseTime: +data.parseTime,
michael@0 438 size: +data.bytesTotal,
michael@0 439 swfVersion: data.swfVersion|0,
michael@0 440 frameRate: +data.frameRate,
michael@0 441 width: data.width|0,
michael@0 442 height: data.height|0,
michael@0 443 bannerType: data.bannerType|0,
michael@0 444 isAvm2: !!data.isAvm2
michael@0 445 });
michael@0 446 break;
michael@0 447 case 'feature':
michael@0 448 var featureType = data.feature|0;
michael@0 449 var MIN_FEATURE_TYPE = 0, MAX_FEATURE_TYPE = 999;
michael@0 450 if (featureType >= MIN_FEATURE_TYPE && featureType <= MAX_FEATURE_TYPE &&
michael@0 451 !this.telemetry.features[featureType]) {
michael@0 452 this.telemetry.features[featureType] = true; // record only one feature per SWF
michael@0 453 ShumwayTelemetry.onFeature(featureType);
michael@0 454 }
michael@0 455 break;
michael@0 456 case 'error':
michael@0 457 var errorType = data.error|0;
michael@0 458 var MIN_ERROR_TYPE = 0, MAX_ERROR_TYPE = 2;
michael@0 459 if (errorType >= MIN_ERROR_TYPE && errorType <= MAX_ERROR_TYPE &&
michael@0 460 !this.telemetry.errors[errorType]) {
michael@0 461 this.telemetry.errors[errorType] = true; // record only one report per SWF
michael@0 462 ShumwayTelemetry.onError(errorType);
michael@0 463 }
michael@0 464 break;
michael@0 465 }
michael@0 466 },
michael@0 467 reportIssue: function(exceptions) {
michael@0 468 var base = "http://shumway-issue-reporter.paas.allizom.org/input?";
michael@0 469 var windowUrl = this.window.parent.wrappedJSObject.location + '';
michael@0 470 var params = 'url=' + encodeURIComponent(windowUrl);
michael@0 471 params += '&swf=' + encodeURIComponent(this.url);
michael@0 472 getVersionInfo().then(function (versions) {
michael@0 473 params += '&ffbuild=' + encodeURIComponent(versions.geckoMstone + ' (' +
michael@0 474 versions.geckoBuildID + ')');
michael@0 475 params += '&shubuild=' + encodeURIComponent(versions.shumwayVersion);
michael@0 476 }).then(function () {
michael@0 477 var postDataStream = StringInputStream.
michael@0 478 createInstance(Ci.nsIStringInputStream);
michael@0 479 postDataStream.data = 'exceptions=' + encodeURIComponent(exceptions);
michael@0 480 var postData = MimeInputStream.createInstance(Ci.nsIMIMEInputStream);
michael@0 481 postData.addHeader("Content-Type", "application/x-www-form-urlencoded");
michael@0 482 postData.addContentLength = true;
michael@0 483 postData.setData(postDataStream);
michael@0 484 this.window.openDialog('chrome://browser/content', '_blank',
michael@0 485 'all,dialog=no', base + params, null, null,
michael@0 486 postData);
michael@0 487 }.bind(this));
michael@0 488 },
michael@0 489 externalCom: function (data) {
michael@0 490 if (!this.allowScriptAccess)
michael@0 491 return;
michael@0 492
michael@0 493 // TODO check security ?
michael@0 494 var parentWindow = this.window.parent.wrappedJSObject;
michael@0 495 var embedTag = this.embedTag.wrappedJSObject;
michael@0 496 switch (data.action) {
michael@0 497 case 'init':
michael@0 498 if (this.externalComInitialized)
michael@0 499 return;
michael@0 500
michael@0 501 this.externalComInitialized = true;
michael@0 502 var eventTarget = this.window.document;
michael@0 503 initExternalCom(parentWindow, embedTag, eventTarget);
michael@0 504 return;
michael@0 505 case 'getId':
michael@0 506 return embedTag.id;
michael@0 507 case 'eval':
michael@0 508 return parentWindow.__flash__eval(data.expression);
michael@0 509 case 'call':
michael@0 510 return parentWindow.__flash__call(data.request);
michael@0 511 case 'register':
michael@0 512 return embedTag.__flash__registerCallback(data.functionName);
michael@0 513 case 'unregister':
michael@0 514 return embedTag.__flash__unregisterCallback(data.functionName);
michael@0 515 }
michael@0 516 },
michael@0 517 getWindowUrl: function() {
michael@0 518 return this.window.parent.wrappedJSObject.location + '';
michael@0 519 }
michael@0 520 };
michael@0 521
michael@0 522 // Event listener to trigger chrome privedged code.
michael@0 523 function RequestListener(actions) {
michael@0 524 this.actions = actions;
michael@0 525 }
michael@0 526 // Receive an event and synchronously or asynchronously responds.
michael@0 527 RequestListener.prototype.receive = function(event) {
michael@0 528 var message = event.target;
michael@0 529 var action = event.detail.action;
michael@0 530 var data = event.detail.data;
michael@0 531 var sync = event.detail.sync;
michael@0 532 var actions = this.actions;
michael@0 533 if (!(action in actions)) {
michael@0 534 log('Unknown action: ' + action);
michael@0 535 return;
michael@0 536 }
michael@0 537 if (sync) {
michael@0 538 var response = actions[action].call(this.actions, data);
michael@0 539 var detail = event.detail;
michael@0 540 detail.__exposedProps__ = {response: 'r'};
michael@0 541 detail.response = response;
michael@0 542 } else {
michael@0 543 var response;
michael@0 544 if (event.detail.callback) {
michael@0 545 var cookie = event.detail.cookie;
michael@0 546 response = function sendResponse(response) {
michael@0 547 var doc = actions.document;
michael@0 548 try {
michael@0 549 var listener = doc.createEvent('CustomEvent');
michael@0 550 listener.initCustomEvent('shumway.response', true, false,
michael@0 551 {response: response,
michael@0 552 cookie: cookie,
michael@0 553 __exposedProps__: {response: 'r', cookie: 'r'}});
michael@0 554
michael@0 555 return message.dispatchEvent(listener);
michael@0 556 } catch (e) {
michael@0 557 // doc is no longer accessible because the requestor is already
michael@0 558 // gone. unloaded content cannot receive the response anyway.
michael@0 559 }
michael@0 560 };
michael@0 561 }
michael@0 562 actions[action].call(this.actions, data, response);
michael@0 563 }
michael@0 564 };
michael@0 565
michael@0 566 var ActivationQueue = {
michael@0 567 nonActive: [],
michael@0 568 initializing: -1,
michael@0 569 activationTimeout: null,
michael@0 570 get currentNonActive() {
michael@0 571 return this.nonActive[this.initializing];
michael@0 572 },
michael@0 573 enqueue: function ActivationQueue_enqueue(actions) {
michael@0 574 this.nonActive.push(actions);
michael@0 575 if (this.nonActive.length === 1) {
michael@0 576 this.activateNext();
michael@0 577 }
michael@0 578 },
michael@0 579 findLastOnPage: function ActivationQueue_findLastOnPage(baseUrl) {
michael@0 580 for (var i = this.nonActive.length - 1; i >= 0; i--) {
michael@0 581 if (this.nonActive[i].baseUrl === baseUrl) {
michael@0 582 return this.nonActive[i];
michael@0 583 }
michael@0 584 }
michael@0 585 return null;
michael@0 586 },
michael@0 587 activateNext: function ActivationQueue_activateNext() {
michael@0 588 function weightInstance(actions) {
michael@0 589 // set of heuristics for find the most important instance to load
michael@0 590 var weight = 0;
michael@0 591 // using linear distance to the top-left of the view area
michael@0 592 if (actions.embedTag) {
michael@0 593 var window = actions.window;
michael@0 594 var clientRect = actions.embedTag.getBoundingClientRect();
michael@0 595 weight -= Math.abs(clientRect.left - window.scrollX) +
michael@0 596 Math.abs(clientRect.top - window.scrollY);
michael@0 597 }
michael@0 598 var doc = actions.document;
michael@0 599 if (!doc.hidden) {
michael@0 600 weight += 100000; // might not be that important if hidden
michael@0 601 }
michael@0 602 if (actions.embedTag &&
michael@0 603 actions.embedTag.ownerDocument.hasFocus()) {
michael@0 604 weight += 10000; // parent document is focused
michael@0 605 }
michael@0 606 return weight;
michael@0 607 }
michael@0 608
michael@0 609 if (this.activationTimeout) {
michael@0 610 this.activationTimeout.cancel();
michael@0 611 this.activationTimeout = null;
michael@0 612 }
michael@0 613
michael@0 614 if (this.initializing >= 0) {
michael@0 615 this.nonActive.splice(this.initializing, 1);
michael@0 616 }
michael@0 617 var weights = [];
michael@0 618 for (var i = 0; i < this.nonActive.length; i++) {
michael@0 619 try {
michael@0 620 var weight = weightInstance(this.nonActive[i]);
michael@0 621 weights.push(weight);
michael@0 622 } catch (ex) {
michael@0 623 // unable to calc weight the instance, removing
michael@0 624 log('Shumway instance weight calculation failed: ' + ex);
michael@0 625 this.nonActive.splice(i, 1);
michael@0 626 i--;
michael@0 627 }
michael@0 628 }
michael@0 629
michael@0 630 do {
michael@0 631 if (this.nonActive.length === 0) {
michael@0 632 this.initializing = -1;
michael@0 633 return;
michael@0 634 }
michael@0 635
michael@0 636 var maxWeightIndex = 0;
michael@0 637 var maxWeight = weights[0];
michael@0 638 for (var i = 1; i < weights.length; i++) {
michael@0 639 if (maxWeight < weights[i]) {
michael@0 640 maxWeight = weights[i];
michael@0 641 maxWeightIndex = i;
michael@0 642 }
michael@0 643 }
michael@0 644 try {
michael@0 645 this.initializing = maxWeightIndex;
michael@0 646 this.nonActive[maxWeightIndex].activationCallback();
michael@0 647 break;
michael@0 648 } catch (ex) {
michael@0 649 // unable to initialize the instance, trying another one
michael@0 650 log('Shumway instance initialization failed: ' + ex);
michael@0 651 this.nonActive.splice(maxWeightIndex, 1);
michael@0 652 weights.splice(maxWeightIndex, 1);
michael@0 653 }
michael@0 654 } while (true);
michael@0 655
michael@0 656 var ACTIVATION_TIMEOUT = 3000;
michael@0 657 this.activationTimeout = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
michael@0 658 this.activationTimeout.initWithCallback(function () {
michael@0 659 log('Timeout during shumway instance initialization');
michael@0 660 this.activateNext();
michael@0 661 }.bind(this), ACTIVATION_TIMEOUT, Ci.nsITimer.TYPE_ONE_SHOT);
michael@0 662 }
michael@0 663 };
michael@0 664
michael@0 665 function activateShumwayScripts(window, preview) {
michael@0 666 function loadScripts(scripts, callback) {
michael@0 667 function scriptLoaded() {
michael@0 668 leftToLoad--;
michael@0 669 if (leftToLoad === 0) {
michael@0 670 callback();
michael@0 671 }
michael@0 672 }
michael@0 673 var leftToLoad = scripts.length;
michael@0 674 var document = window.document.wrappedJSObject;
michael@0 675 var head = document.getElementsByTagName('head')[0];
michael@0 676 for (var i = 0; i < scripts.length; i++) {
michael@0 677 var script = document.createElement('script');
michael@0 678 script.type = "text/javascript";
michael@0 679 script.src = scripts[i];
michael@0 680 script.onload = scriptLoaded;
michael@0 681 head.appendChild(script);
michael@0 682 }
michael@0 683 }
michael@0 684
michael@0 685 function initScripts() {
michael@0 686 if (preview) {
michael@0 687 loadScripts(['resource://shumway/web/preview.js'], function () {
michael@0 688 window.wrappedJSObject.runSniffer();
michael@0 689 });
michael@0 690 } else {
michael@0 691 loadScripts(['resource://shumway/shumway.js',
michael@0 692 'resource://shumway/web/avm-sandbox.js'], function () {
michael@0 693 window.wrappedJSObject.runViewer();
michael@0 694 });
michael@0 695 }
michael@0 696 }
michael@0 697
michael@0 698 window.wrappedJSObject.SHUMWAY_ROOT = "resource://shumway/";
michael@0 699
michael@0 700 if (window.document.readyState === "interactive" ||
michael@0 701 window.document.readyState === "complete") {
michael@0 702 initScripts();
michael@0 703 } else {
michael@0 704 window.document.addEventListener('DOMContentLoaded', initScripts);
michael@0 705 }
michael@0 706 }
michael@0 707
michael@0 708 function initExternalCom(wrappedWindow, wrappedObject, targetDocument) {
michael@0 709 if (!wrappedWindow.__flash__initialized) {
michael@0 710 wrappedWindow.__flash__initialized = true;
michael@0 711 wrappedWindow.__flash__toXML = function __flash__toXML(obj) {
michael@0 712 switch (typeof obj) {
michael@0 713 case 'boolean':
michael@0 714 return obj ? '<true/>' : '<false/>';
michael@0 715 case 'number':
michael@0 716 return '<number>' + obj + '</number>';
michael@0 717 case 'object':
michael@0 718 if (obj === null) {
michael@0 719 return '<null/>';
michael@0 720 }
michael@0 721 if ('hasOwnProperty' in obj && obj.hasOwnProperty('length')) {
michael@0 722 // array
michael@0 723 var xml = '<array>';
michael@0 724 for (var i = 0; i < obj.length; i++) {
michael@0 725 xml += '<property id="' + i + '">' + __flash__toXML(obj[i]) + '</property>';
michael@0 726 }
michael@0 727 return xml + '</array>';
michael@0 728 }
michael@0 729 var xml = '<object>';
michael@0 730 for (var i in obj) {
michael@0 731 xml += '<property id="' + i + '">' + __flash__toXML(obj[i]) + '</property>';
michael@0 732 }
michael@0 733 return xml + '</object>';
michael@0 734 case 'string':
michael@0 735 return '<string>' + obj.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;') + '</string>';
michael@0 736 case 'undefined':
michael@0 737 return '<undefined/>';
michael@0 738 }
michael@0 739 };
michael@0 740 var sandbox = new Cu.Sandbox(wrappedWindow, {sandboxPrototype: wrappedWindow});
michael@0 741 wrappedWindow.__flash__eval = function (evalInSandbox, sandbox, expr) {
michael@0 742 this.console.log('__flash__eval: ' + expr);
michael@0 743 return evalInSandbox(expr, sandbox);
michael@0 744 }.bind(wrappedWindow, Cu.evalInSandbox, sandbox);
michael@0 745 wrappedWindow.__flash__call = function (expr) {
michael@0 746 this.console.log('__flash__call (ignored): ' + expr);
michael@0 747 };
michael@0 748 }
michael@0 749 wrappedObject.__flash__registerCallback = function (functionName) {
michael@0 750 wrappedWindow.console.log('__flash__registerCallback: ' + functionName);
michael@0 751 this[functionName] = function () {
michael@0 752 var args = Array.prototype.slice.call(arguments, 0);
michael@0 753 wrappedWindow.console.log('__flash__callIn: ' + functionName);
michael@0 754 var e = targetDocument.createEvent('CustomEvent');
michael@0 755 e.initCustomEvent('shumway.remote', true, false, {
michael@0 756 functionName: functionName,
michael@0 757 args: args,
michael@0 758 __exposedProps__: {args: 'r', functionName: 'r', result: 'rw'}
michael@0 759 });
michael@0 760 targetDocument.dispatchEvent(e);
michael@0 761 return e.detail.result;
michael@0 762 };
michael@0 763 };
michael@0 764 wrappedObject.__flash__unregisterCallback = function (functionName) {
michael@0 765 wrappedWindow.console.log('__flash__unregisterCallback: ' + functionName);
michael@0 766 delete this[functionName];
michael@0 767 };
michael@0 768 }
michael@0 769
michael@0 770 function ShumwayStreamConverterBase() {
michael@0 771 }
michael@0 772
michael@0 773 ShumwayStreamConverterBase.prototype = {
michael@0 774 QueryInterface: XPCOMUtils.generateQI([
michael@0 775 Ci.nsISupports,
michael@0 776 Ci.nsIStreamConverter,
michael@0 777 Ci.nsIStreamListener,
michael@0 778 Ci.nsIRequestObserver
michael@0 779 ]),
michael@0 780
michael@0 781 /*
michael@0 782 * This component works as such:
michael@0 783 * 1. asyncConvertData stores the listener
michael@0 784 * 2. onStartRequest creates a new channel, streams the viewer and cancels
michael@0 785 * the request so Shumway can do the request
michael@0 786 * Since the request is cancelled onDataAvailable should not be called. The
michael@0 787 * onStopRequest does nothing. The convert function just returns the stream,
michael@0 788 * it's just the synchronous version of asyncConvertData.
michael@0 789 */
michael@0 790
michael@0 791 // nsIStreamConverter::convert
michael@0 792 convert: function(aFromStream, aFromType, aToType, aCtxt) {
michael@0 793 throw Cr.NS_ERROR_NOT_IMPLEMENTED;
michael@0 794 },
michael@0 795
michael@0 796 getUrlHint: function(requestUrl) {
michael@0 797 return requestUrl.spec;
michael@0 798 },
michael@0 799
michael@0 800 createChromeActions: function(window, document, urlHint) {
michael@0 801 var url = urlHint;
michael@0 802 var baseUrl;
michael@0 803 var pageUrl;
michael@0 804 var element = window.frameElement;
michael@0 805 var isOverlay = false;
michael@0 806 var objectParams = {};
michael@0 807 if (element) {
michael@0 808 // PlayPreview overlay "belongs" to the embed/object tag and consists of
michael@0 809 // DIV and IFRAME. Starting from IFRAME and looking for first object tag.
michael@0 810 var tagName = element.nodeName, containerElement;
michael@0 811 while (tagName != 'EMBED' && tagName != 'OBJECT') {
michael@0 812 // plugin overlay skipping until the target plugin is found
michael@0 813 isOverlay = true;
michael@0 814 containerElement = element;
michael@0 815 element = element.parentNode;
michael@0 816 if (!element) {
michael@0 817 throw new Error('Plugin element is not found');
michael@0 818 }
michael@0 819 tagName = element.nodeName;
michael@0 820 }
michael@0 821
michael@0 822 if (isOverlay) {
michael@0 823 // Checking if overlay is a proper PlayPreview overlay.
michael@0 824 for (var i = 0; i < element.children.length; i++) {
michael@0 825 if (element.children[i] === containerElement) {
michael@0 826 throw new Error('Plugin element is invalid');
michael@0 827 }
michael@0 828 }
michael@0 829 }
michael@0 830 }
michael@0 831
michael@0 832 if (element) {
michael@0 833 // Getting absolute URL from the EMBED tag
michael@0 834 url = element.srcURI.spec;
michael@0 835
michael@0 836 pageUrl = element.ownerDocument.location.href; // proper page url?
michael@0 837
michael@0 838 if (tagName == 'EMBED') {
michael@0 839 for (var i = 0; i < element.attributes.length; ++i) {
michael@0 840 var paramName = element.attributes[i].localName.toLowerCase();
michael@0 841 objectParams[paramName] = element.attributes[i].value;
michael@0 842 }
michael@0 843 } else {
michael@0 844 for (var i = 0; i < element.childNodes.length; ++i) {
michael@0 845 var paramElement = element.childNodes[i];
michael@0 846 if (paramElement.nodeType != 1 ||
michael@0 847 paramElement.nodeName != 'PARAM') {
michael@0 848 continue;
michael@0 849 }
michael@0 850 var paramName = paramElement.getAttribute('name').toLowerCase();
michael@0 851 objectParams[paramName] = paramElement.getAttribute('value');
michael@0 852 }
michael@0 853 }
michael@0 854 }
michael@0 855
michael@0 856 if (!url) { // at this point url shall be known -- asserting
michael@0 857 throw new Error('Movie url is not specified');
michael@0 858 }
michael@0 859
michael@0 860 baseUrl = objectParams.base || pageUrl;
michael@0 861
michael@0 862 var movieParams = {};
michael@0 863 if (objectParams.flashvars) {
michael@0 864 movieParams = parseQueryString(objectParams.flashvars);
michael@0 865 }
michael@0 866 var queryStringMatch = /\?([^#]+)/.exec(url);
michael@0 867 if (queryStringMatch) {
michael@0 868 var queryStringParams = parseQueryString(queryStringMatch[1]);
michael@0 869 for (var i in queryStringParams) {
michael@0 870 if (!(i in movieParams)) {
michael@0 871 movieParams[i] = queryStringParams[i];
michael@0 872 }
michael@0 873 }
michael@0 874 }
michael@0 875
michael@0 876 var allowScriptAccess = false;
michael@0 877 switch (objectParams.allowscriptaccess || 'sameDomain') {
michael@0 878 case 'always':
michael@0 879 allowScriptAccess = true;
michael@0 880 break;
michael@0 881 case 'never':
michael@0 882 allowScriptAccess = false;
michael@0 883 break;
michael@0 884 default:
michael@0 885 if (!pageUrl)
michael@0 886 break;
michael@0 887 try {
michael@0 888 // checking if page is in same domain (? same protocol and port)
michael@0 889 allowScriptAccess =
michael@0 890 Services.io.newURI('/', null, Services.io.newURI(pageUrl, null, null)).spec ==
michael@0 891 Services.io.newURI('/', null, Services.io.newURI(url, null, null)).spec;
michael@0 892 } catch (ex) {}
michael@0 893 break;
michael@0 894 }
michael@0 895
michael@0 896 var actions = new ChromeActions(url, window, document);
michael@0 897 actions.objectParams = objectParams;
michael@0 898 actions.movieParams = movieParams;
michael@0 899 actions.baseUrl = baseUrl || url;
michael@0 900 actions.isOverlay = isOverlay;
michael@0 901 actions.embedTag = element;
michael@0 902 actions.isPausedAtStart = /\bpaused=true$/.test(urlHint);
michael@0 903 actions.allowScriptAccess = allowScriptAccess;
michael@0 904 return actions;
michael@0 905 },
michael@0 906
michael@0 907 // nsIStreamConverter::asyncConvertData
michael@0 908 asyncConvertData: function(aFromType, aToType, aListener, aCtxt) {
michael@0 909 // Store the listener passed to us
michael@0 910 this.listener = aListener;
michael@0 911 },
michael@0 912
michael@0 913 // nsIStreamListener::onDataAvailable
michael@0 914 onDataAvailable: function(aRequest, aContext, aInputStream, aOffset, aCount) {
michael@0 915 // Do nothing since all the data loading is handled by the viewer.
michael@0 916 log('SANITY CHECK: onDataAvailable SHOULD NOT BE CALLED!');
michael@0 917 },
michael@0 918
michael@0 919 // nsIRequestObserver::onStartRequest
michael@0 920 onStartRequest: function(aRequest, aContext) {
michael@0 921 // Setup the request so we can use it below.
michael@0 922 aRequest.QueryInterface(Ci.nsIChannel);
michael@0 923
michael@0 924 aRequest.QueryInterface(Ci.nsIWritablePropertyBag);
michael@0 925
michael@0 926 // Change the content type so we don't get stuck in a loop.
michael@0 927 aRequest.setProperty('contentType', aRequest.contentType);
michael@0 928 aRequest.contentType = 'text/html';
michael@0 929
michael@0 930 // TODO For now suspending request, however we can continue fetching data
michael@0 931 aRequest.suspend();
michael@0 932
michael@0 933 var originalURI = aRequest.URI;
michael@0 934
michael@0 935 // checking if the plug-in shall be run in simple mode
michael@0 936 var isSimpleMode = originalURI.spec === EXPECTED_PLAYPREVIEW_URI_PREFIX &&
michael@0 937 getBoolPref('shumway.simpleMode', false);
michael@0 938
michael@0 939 // Create a new channel that loads the viewer as a resource.
michael@0 940 var viewerUrl = isSimpleMode ?
michael@0 941 'resource://shumway/web/simple.html' :
michael@0 942 'resource://shumway/web/viewer.html';
michael@0 943 var channel = Services.io.newChannel(viewerUrl, null, null);
michael@0 944
michael@0 945 var converter = this;
michael@0 946 var listener = this.listener;
michael@0 947 // Proxy all the request observer calls, when it gets to onStopRequest
michael@0 948 // we can get the dom window.
michael@0 949 var proxy = {
michael@0 950 onStartRequest: function(request, context) {
michael@0 951 listener.onStartRequest(aRequest, context);
michael@0 952 },
michael@0 953 onDataAvailable: function(request, context, inputStream, offset, count) {
michael@0 954 listener.onDataAvailable(aRequest, context, inputStream, offset, count);
michael@0 955 },
michael@0 956 onStopRequest: function(request, context, statusCode) {
michael@0 957 // Cancel the request so the viewer can handle it.
michael@0 958 aRequest.resume();
michael@0 959 aRequest.cancel(Cr.NS_BINDING_ABORTED);
michael@0 960
michael@0 961 var domWindow = getDOMWindow(channel);
michael@0 962 let actions = converter.createChromeActions(domWindow,
michael@0 963 domWindow.document,
michael@0 964 converter.getUrlHint(originalURI));
michael@0 965
michael@0 966 if (!isShumwayEnabledFor(actions)) {
michael@0 967 fallbackToNativePlugin(domWindow, false, true);
michael@0 968 return;
michael@0 969 }
michael@0 970
michael@0 971 // Report telemetry on amount of swfs on the page
michael@0 972 if (actions.isOverlay) {
michael@0 973 // Looking for last actions with same baseUrl
michael@0 974 var prevPageActions = ActivationQueue.findLastOnPage(actions.baseUrl);
michael@0 975 var pageIndex = !prevPageActions ? 1 : (prevPageActions.telemetry.pageIndex + 1);
michael@0 976 actions.telemetry.pageIndex = pageIndex;
michael@0 977 ShumwayTelemetry.onPageIndex(pageIndex);
michael@0 978 } else {
michael@0 979 ShumwayTelemetry.onPageIndex(0);
michael@0 980 }
michael@0 981
michael@0 982 actions.activationCallback = function(domWindow, isSimpleMode) {
michael@0 983 delete this.activationCallback;
michael@0 984 activateShumwayScripts(domWindow, isSimpleMode);
michael@0 985 }.bind(actions, domWindow, isSimpleMode);
michael@0 986 ActivationQueue.enqueue(actions);
michael@0 987
michael@0 988 let requestListener = new RequestListener(actions);
michael@0 989 domWindow.addEventListener('shumway.message', function(event) {
michael@0 990 requestListener.receive(event);
michael@0 991 }, false, true);
michael@0 992
michael@0 993 listener.onStopRequest(aRequest, context, statusCode);
michael@0 994 }
michael@0 995 };
michael@0 996
michael@0 997 // Keep the URL the same so the browser sees it as the same.
michael@0 998 channel.originalURI = aRequest.URI;
michael@0 999 channel.loadGroup = aRequest.loadGroup;
michael@0 1000
michael@0 1001 // We can use resource principal when data is fetched by the chrome
michael@0 1002 // e.g. useful for NoScript
michael@0 1003 var securityManager = Cc['@mozilla.org/scriptsecuritymanager;1']
michael@0 1004 .getService(Ci.nsIScriptSecurityManager);
michael@0 1005 var uri = Services.io.newURI(viewerUrl, null, null);
michael@0 1006 var resourcePrincipal = securityManager.getNoAppCodebasePrincipal(uri);
michael@0 1007 aRequest.owner = resourcePrincipal;
michael@0 1008 channel.asyncOpen(proxy, aContext);
michael@0 1009 },
michael@0 1010
michael@0 1011 // nsIRequestObserver::onStopRequest
michael@0 1012 onStopRequest: function(aRequest, aContext, aStatusCode) {
michael@0 1013 // Do nothing.
michael@0 1014 }
michael@0 1015 };
michael@0 1016
michael@0 1017 // properties required for XPCOM registration:
michael@0 1018 function copyProperties(obj, template) {
michael@0 1019 for (var prop in template) {
michael@0 1020 obj[prop] = template[prop];
michael@0 1021 }
michael@0 1022 }
michael@0 1023
michael@0 1024 function ShumwayStreamConverter() {}
michael@0 1025 ShumwayStreamConverter.prototype = new ShumwayStreamConverterBase();
michael@0 1026 copyProperties(ShumwayStreamConverter.prototype, {
michael@0 1027 classID: Components.ID('{4c6030f7-e20a-264f-5b0e-ada3a9e97384}'),
michael@0 1028 classDescription: 'Shumway Content Converter Component',
michael@0 1029 contractID: '@mozilla.org/streamconv;1?from=application/x-shockwave-flash&to=*/*'
michael@0 1030 });
michael@0 1031
michael@0 1032 function ShumwayStreamOverlayConverter() {}
michael@0 1033 ShumwayStreamOverlayConverter.prototype = new ShumwayStreamConverterBase();
michael@0 1034 copyProperties(ShumwayStreamOverlayConverter.prototype, {
michael@0 1035 classID: Components.ID('{4c6030f7-e20a-264f-5f9b-ada3a9e97384}'),
michael@0 1036 classDescription: 'Shumway PlayPreview Component',
michael@0 1037 contractID: '@mozilla.org/streamconv;1?from=application/x-moz-playpreview&to=*/*'
michael@0 1038 });
michael@0 1039 ShumwayStreamOverlayConverter.prototype.getUrlHint = function (requestUrl) {
michael@0 1040 return '';
michael@0 1041 };

mercurial