Wed, 31 Dec 2014 06:09:35 +0100
Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.
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, '&').replace(/</g, '<').replace(/>/g, '>') + '</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 | }; |