зеркало из https://github.com/mozilla/gecko-dev.git
Merge fx-team to central, a=merge
This commit is contained in:
Коммит
573bbc33bd
|
@ -1864,6 +1864,8 @@ pref("browser.translation.engine", "bing");
|
|||
// Telemetry settings.
|
||||
// Determines if Telemetry pings can be archived locally.
|
||||
pref("toolkit.telemetry.archive.enabled", true);
|
||||
// Whether we enable opt-out Telemetry for a sample of the release population.
|
||||
pref("toolkit.telemetry.optoutSample", true);
|
||||
|
||||
// Telemetry experiments settings.
|
||||
pref("experiments.enabled", true);
|
||||
|
|
|
@ -49,7 +49,6 @@ const gXPInstallObserver = {
|
|||
var options = {
|
||||
displayURI: installInfo.originatingURI,
|
||||
timeout: Date.now() + 30000,
|
||||
removeOnDismissal: true,
|
||||
};
|
||||
|
||||
let cancelInstallation = () => {
|
||||
|
|
|
@ -67,7 +67,6 @@ skip-if = !crashreporter
|
|||
[browser_plugins_added_dynamically.js]
|
||||
[browser_pluginnotification.js]
|
||||
[browser_plugin_infolink.js]
|
||||
skip-if = e10s # bug 1160166
|
||||
[browser_plugin_reloading.js]
|
||||
[browser_blocklist_content.js]
|
||||
skip-if = !e10s
|
||||
|
|
|
@ -40,15 +40,11 @@ skip-if = e10s && debug # e10s/Linux/Debug Leaking docshells (bug 1150147)
|
|||
[browser_social_chatwindowfocus.js]
|
||||
skip-if = e10s # tab crash on data url used in this test
|
||||
[browser_social_contextmenu.js]
|
||||
skip-if = e10s # Bug 1072669 context menu relies on target element
|
||||
[browser_social_errorPage.js]
|
||||
[browser_social_flyout.js]
|
||||
skip-if = e10s # when we backed out bug 1047603, this test broke.
|
||||
[browser_social_isVisible.js]
|
||||
[browser_social_marks.js]
|
||||
skip-if = e10s && debug # Leaking docshells (bug 1150147)
|
||||
[browser_social_multiprovider.js]
|
||||
skip-if = e10s # Bug 1069162 - lots of orange
|
||||
[browser_social_multiworker.js]
|
||||
[browser_social_perwindowPB.js]
|
||||
[browser_social_sidebar.js]
|
||||
|
|
|
@ -283,277 +283,9 @@ loop.shared.mixins = (function() {
|
|||
|
||||
/**
|
||||
* Media setup mixin. Provides a common location for settings for the media
|
||||
* elements and handling updates of the media containers.
|
||||
* elements.
|
||||
*/
|
||||
var MediaSetupMixin = {
|
||||
componentDidMount: function() {
|
||||
this.resetDimensionsCache();
|
||||
},
|
||||
|
||||
/**
|
||||
* Resets the dimensions cache, e.g. for when the session is ended, and
|
||||
* before a new session, so that we always ensure we see an update when a
|
||||
* new session is started.
|
||||
*/
|
||||
resetDimensionsCache: function() {
|
||||
this._videoDimensionsCache = {
|
||||
local: {},
|
||||
remote: {}
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* Whenever the dimensions change of a video stream, this function is called
|
||||
* by `updateVideoDimensions` to store the new values and notifies the callee
|
||||
* if the dimensions changed compared to the currently stored values.
|
||||
*
|
||||
* @param {String} which Type of video stream. May be 'local' or 'remote'
|
||||
* @param {Object} newDimensions Object containing 'width' and 'height' properties
|
||||
* @return {Boolean} `true` when the dimensions have changed,
|
||||
* `false` if not
|
||||
*/
|
||||
_updateDimensionsCache: function(which, newDimensions) {
|
||||
var cache = this._videoDimensionsCache[which];
|
||||
var cacheKeys = Object.keys(cache);
|
||||
var changed = false;
|
||||
Object.keys(newDimensions).forEach(function(videoType) {
|
||||
if (cacheKeys.indexOf(videoType) === -1) {
|
||||
cache[videoType] = newDimensions[videoType];
|
||||
cache[videoType].aspectRatio = this.getAspectRatio(cache[videoType]);
|
||||
changed = true;
|
||||
return;
|
||||
}
|
||||
if (cache[videoType].width !== newDimensions[videoType].width) {
|
||||
cache[videoType].width = newDimensions[videoType].width;
|
||||
changed = true;
|
||||
}
|
||||
if (cache[videoType].height !== newDimensions[videoType].height) {
|
||||
cache[videoType].height = newDimensions[videoType].height;
|
||||
changed = true;
|
||||
}
|
||||
if (changed) {
|
||||
cache[videoType].aspectRatio = this.getAspectRatio(cache[videoType]);
|
||||
}
|
||||
}, this);
|
||||
// Remove any streams that are no longer being published.
|
||||
cacheKeys.forEach(function(videoType) {
|
||||
if (!(videoType in newDimensions)) {
|
||||
delete cache[videoType];
|
||||
changed = true;
|
||||
}
|
||||
});
|
||||
return changed;
|
||||
},
|
||||
|
||||
/**
|
||||
* Whenever the dimensions change of a video stream, this function is called
|
||||
* to process these changes and possibly trigger an update to the video
|
||||
* container elements.
|
||||
*
|
||||
* @param {Object} localVideoDimensions Object containing 'width' and 'height'
|
||||
* properties grouped by stream name
|
||||
* @param {Object} remoteVideoDimensions Object containing 'width' and 'height'
|
||||
* properties grouped by stream name
|
||||
*/
|
||||
updateVideoDimensions: function(localVideoDimensions, remoteVideoDimensions) {
|
||||
var localChanged = this._updateDimensionsCache("local", localVideoDimensions);
|
||||
var remoteChanged = this._updateDimensionsCache("remote", remoteVideoDimensions);
|
||||
if (localChanged || remoteChanged) {
|
||||
this.updateVideoContainer();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Get the aspect ratio of a width/ height pair, which should be the dimensions
|
||||
* of a stream. The returned object is an aspect ratio indexed by 1; the leading
|
||||
* size has a value smaller than 1 and the slave size has a value of 1.
|
||||
* this is exactly the same as notations like 4:3 and 16:9, which are merely
|
||||
* human-readable forms of their fractional counterparts. 4:3 === 1:0.75 and
|
||||
* 16:9 === 1:0.5625.
|
||||
* So we're using the aspect ratios in their original form, because that's
|
||||
* easier to do calculus with.
|
||||
*
|
||||
* Example:
|
||||
* A stream with dimensions `{ width: 640, height: 480 }` yields an indexed
|
||||
* aspect ratio of `{ width: 1, height: 0.75 }`. This means that the 'height'
|
||||
* will determine the value of 'width' when the stream is stretched or shrunk
|
||||
* to fit inside its container element at the maximum size.
|
||||
*
|
||||
* @param {Object} dimensions Object containing 'width' and 'height' properties
|
||||
* @return {Object} Contains the indexed aspect ratio for 'width'
|
||||
* and 'height' assigned to the corresponding
|
||||
* properties.
|
||||
*/
|
||||
getAspectRatio: function(dimensions) {
|
||||
if (dimensions.width === dimensions.height) {
|
||||
return {width: 1, height: 1};
|
||||
}
|
||||
var denominator = Math.max(dimensions.width, dimensions.height);
|
||||
return {
|
||||
width: dimensions.width / denominator,
|
||||
height: dimensions.height / denominator
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* Retrieve the dimensions of the active remote video stream. This assumes
|
||||
* that if screens are being shared, the remote camera stream is hidden.
|
||||
* Example output:
|
||||
* {
|
||||
* width: 680,
|
||||
* height: 480,
|
||||
* streamWidth: 640,
|
||||
* streamHeight: 480,
|
||||
* offsetX: 20,
|
||||
* offsetY: 0
|
||||
* }
|
||||
*
|
||||
* Note: This expects a class on the element that has the name "remote" or the
|
||||
* same name as the possible video types (currently only "screen").
|
||||
* Note: Once we support multiple remote video streams, this function will
|
||||
* need to be updated.
|
||||
*
|
||||
* @param {string} videoType The video type according to the sdk, e.g. "camera" or
|
||||
* "screen".
|
||||
* @return {Object} contains the remote stream dimension properties of its
|
||||
* container node, the stream itself and offset of the stream
|
||||
* relative to its container node in pixels.
|
||||
*/
|
||||
getRemoteVideoDimensions: function(videoType) {
|
||||
var remoteVideoDimensions;
|
||||
|
||||
if (videoType in this._videoDimensionsCache.remote) {
|
||||
var node = this._getElement("." + (videoType === "camera" ? "remote" : videoType));
|
||||
var width = node.offsetWidth;
|
||||
// If the width > 0 then we record its real size by taking its aspect
|
||||
// ratio in account. Due to the 'contain' fit-mode, the stream will be
|
||||
// centered inside the video element.
|
||||
// We'll need to deal with more than one remote video stream whenever
|
||||
// that becomes something we need to support.
|
||||
if (width) {
|
||||
remoteVideoDimensions = {
|
||||
width: width,
|
||||
height: node.offsetHeight
|
||||
};
|
||||
|
||||
var ratio = this._videoDimensionsCache.remote[videoType].aspectRatio;
|
||||
// Leading axis is the side that has the smallest ratio.
|
||||
var leadingAxis = Math.min(ratio.width, ratio.height) === ratio.width ?
|
||||
"width" : "height";
|
||||
var slaveAxis = leadingAxis === "height" ? "width" : "height";
|
||||
|
||||
// We need to work out if the leading axis of the video is full, by
|
||||
// calculating the expected length of the leading axis based on the
|
||||
// length of the slave axis and aspect ratio.
|
||||
var leadingAxisFull = remoteVideoDimensions[slaveAxis] * ratio[leadingAxis] >
|
||||
remoteVideoDimensions[leadingAxis];
|
||||
|
||||
if (leadingAxisFull) {
|
||||
// If the leading axis is "full" then we need to adjust the slave axis.
|
||||
var slaveAxisSize = remoteVideoDimensions[leadingAxis] / ratio[leadingAxis];
|
||||
|
||||
remoteVideoDimensions.streamWidth = leadingAxis === "width" ?
|
||||
remoteVideoDimensions.width : slaveAxisSize;
|
||||
remoteVideoDimensions.streamHeight = leadingAxis === "height" ?
|
||||
remoteVideoDimensions.height : slaveAxisSize;
|
||||
} else {
|
||||
// If the leading axis is not "full" then we need to adjust it, based
|
||||
// on the length of the leading axis.
|
||||
var leadingAxisSize = remoteVideoDimensions[slaveAxis] * ratio[leadingAxis];
|
||||
|
||||
remoteVideoDimensions.streamWidth = leadingAxis === "height" ?
|
||||
remoteVideoDimensions.width : leadingAxisSize;
|
||||
remoteVideoDimensions.streamHeight = leadingAxis === "width" ?
|
||||
remoteVideoDimensions.height : leadingAxisSize;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Supply some sensible defaults for the remoteVideoDimensions if no remote
|
||||
// stream is connected (yet).
|
||||
if (!remoteVideoDimensions) {
|
||||
node = this._getElement(".remote");
|
||||
width = node.offsetWidth;
|
||||
var height = node.offsetHeight;
|
||||
remoteVideoDimensions = {
|
||||
width: width,
|
||||
height: height,
|
||||
streamWidth: width,
|
||||
streamHeight: height
|
||||
};
|
||||
}
|
||||
|
||||
// Calculate the size of each individual letter- or pillarbox for convenience.
|
||||
remoteVideoDimensions.offsetX = remoteVideoDimensions.width -
|
||||
remoteVideoDimensions.streamWidth;
|
||||
if (remoteVideoDimensions.offsetX > 0) {
|
||||
remoteVideoDimensions.offsetX /= 2;
|
||||
}
|
||||
remoteVideoDimensions.offsetY = remoteVideoDimensions.height -
|
||||
remoteVideoDimensions.streamHeight;
|
||||
if (remoteVideoDimensions.offsetY > 0) {
|
||||
remoteVideoDimensions.offsetY /= 2;
|
||||
}
|
||||
|
||||
return remoteVideoDimensions;
|
||||
},
|
||||
|
||||
/**
|
||||
* Used to update the video container whenever the orientation or size of the
|
||||
* display area changes.
|
||||
*
|
||||
* Buffer the calls to this function to make sure we don't overflow the stack
|
||||
* with update calls when many 'resize' event are fired, to prevent blocking
|
||||
* the event loop.
|
||||
*/
|
||||
updateVideoContainer: function() {
|
||||
if (this._bufferedUpdateVideo) {
|
||||
rootObject.clearTimeout(this._bufferedUpdateVideo);
|
||||
this._bufferedUpdateVideo = null;
|
||||
}
|
||||
|
||||
this._bufferedUpdateVideo = rootObject.setTimeout(function() {
|
||||
// Since this is being called from setTimeout, any exceptions thrown
|
||||
// will propagate upwards into nothingness, unless we go out of our
|
||||
// way to catch and log them explicitly, so...
|
||||
try {
|
||||
this._bufferedUpdateVideo = null;
|
||||
var localStreamParent = this._getElement(".local .OT_publisher");
|
||||
var remoteStreamParent = this._getElement(".remote .OT_subscriber");
|
||||
var screenShareStreamParent = this._getElement(".screen .OT_subscriber");
|
||||
if (localStreamParent) {
|
||||
localStreamParent.style.width = "100%";
|
||||
}
|
||||
if (remoteStreamParent) {
|
||||
remoteStreamParent.style.height = "100%";
|
||||
}
|
||||
if (screenShareStreamParent) {
|
||||
screenShareStreamParent.style.height = "100%";
|
||||
}
|
||||
|
||||
// Update the position and dimensions of the containers of local and
|
||||
// remote video streams, if necessary. The consumer of this mixin
|
||||
// should implement the actual updating mechanism.
|
||||
Object.keys(this._videoDimensionsCache.local).forEach(
|
||||
function (videoType) {
|
||||
var ratio = this._videoDimensionsCache.local[videoType].aspectRatio;
|
||||
if (videoType == "camera" && this.updateLocalCameraPosition) {
|
||||
this.updateLocalCameraPosition(ratio);
|
||||
}
|
||||
}, this);
|
||||
Object.keys(this._videoDimensionsCache.remote).forEach(
|
||||
function (videoType) {
|
||||
var ratio = this._videoDimensionsCache.remote[videoType].aspectRatio;
|
||||
if (videoType == "camera" && this.updateRemoteCameraPosition) {
|
||||
this.updateRemoteCameraPosition(ratio);
|
||||
}
|
||||
}, this);
|
||||
} catch (ex) {
|
||||
console.error("updateVideoContainer: _bufferedVideoUpdate exception:", ex);
|
||||
}
|
||||
}.bind(this), 0);
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns the default configuration for publishing media on the sdk.
|
||||
*
|
||||
|
@ -576,15 +308,6 @@ loop.shared.mixins = (function() {
|
|||
publishVideo: options.publishVideo,
|
||||
showControls: false
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns either the required DOMNode
|
||||
*
|
||||
* @param {String} className The name of the class to get the element for.
|
||||
*/
|
||||
_getElement: function(className) {
|
||||
return this.getDOMNode().querySelector(className);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -192,233 +192,30 @@ describe("loop.shared.mixins", function() {
|
|||
});
|
||||
|
||||
describe("loop.shared.mixins.MediaSetupMixin", function() {
|
||||
var view, TestComp, rootObject;
|
||||
var localElement, remoteElement, screenShareElement;
|
||||
var view;
|
||||
|
||||
beforeEach(function() {
|
||||
TestComp = React.createClass({
|
||||
var TestComp = React.createClass({
|
||||
mixins: [loop.shared.mixins.MediaSetupMixin],
|
||||
render: function() {
|
||||
return React.DOM.div();
|
||||
}
|
||||
});
|
||||
|
||||
sandbox.useFakeTimers();
|
||||
|
||||
rootObject = {
|
||||
events: {},
|
||||
setTimeout: function(func, timeout) {
|
||||
return setTimeout(func, timeout);
|
||||
},
|
||||
clearTimeout: function(timer) {
|
||||
return clearTimeout(timer);
|
||||
},
|
||||
addEventListener: function(eventName, listener) {
|
||||
this.events[eventName] = listener;
|
||||
},
|
||||
removeEventListener: function(eventName) {
|
||||
delete this.events[eventName];
|
||||
}
|
||||
};
|
||||
|
||||
sharedMixins.setRootObject(rootObject);
|
||||
|
||||
view = TestUtils.renderIntoDocument(React.createElement(TestComp));
|
||||
|
||||
sandbox.stub(view, "getDOMNode").returns({
|
||||
querySelector: function(classSelector) {
|
||||
if (classSelector.indexOf("local") > -1) {
|
||||
return localElement;
|
||||
} else if (classSelector.indexOf("screen") > -1) {
|
||||
return screenShareElement;
|
||||
}
|
||||
return remoteElement;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
localElement = null;
|
||||
remoteElement = null;
|
||||
screenShareElement = null;
|
||||
});
|
||||
|
||||
describe("#getDefaultPublisherConfig", function() {
|
||||
it("should provide a default publisher configuration", function() {
|
||||
var defaultConfig = view.getDefaultPublisherConfig({publishVideo: true});
|
||||
|
||||
expect(defaultConfig.publishVideo).eql(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe("#getRemoteVideoDimensions", function() {
|
||||
var localVideoDimensions, remoteVideoDimensions;
|
||||
|
||||
beforeEach(function() {
|
||||
localVideoDimensions = {
|
||||
camera: {
|
||||
width: 640,
|
||||
height: 480
|
||||
}
|
||||
};
|
||||
it("should throw if publishVideo is not given", function() {
|
||||
expect(function() {
|
||||
view.getDefaultPublisherConfig();
|
||||
}).to.throw(/missing/);
|
||||
});
|
||||
|
||||
it("should fetch the correct stream sizes for leading axis width and full",
|
||||
function() {
|
||||
remoteVideoDimensions = {
|
||||
screen: {
|
||||
width: 240,
|
||||
height: 320
|
||||
}
|
||||
};
|
||||
screenShareElement = {
|
||||
offsetWidth: 480,
|
||||
offsetHeight: 700
|
||||
};
|
||||
|
||||
view.updateVideoDimensions(localVideoDimensions, remoteVideoDimensions);
|
||||
var result = view.getRemoteVideoDimensions("screen");
|
||||
|
||||
expect(result.width).eql(screenShareElement.offsetWidth);
|
||||
expect(result.height).eql(screenShareElement.offsetHeight);
|
||||
expect(result.streamWidth).eql(screenShareElement.offsetWidth);
|
||||
// The real height of the stream accounting for the aspect ratio.
|
||||
expect(result.streamHeight).eql(640);
|
||||
expect(result.offsetX).eql(0);
|
||||
// The remote element height (700) minus the stream height (640) split in 2.
|
||||
expect(result.offsetY).eql(30);
|
||||
});
|
||||
|
||||
it("should fetch the correct stream sizes for leading axis width and not full",
|
||||
function() {
|
||||
remoteVideoDimensions = {
|
||||
camera: {
|
||||
width: 240,
|
||||
height: 320
|
||||
}
|
||||
};
|
||||
remoteElement = {
|
||||
offsetWidth: 640,
|
||||
offsetHeight: 480
|
||||
};
|
||||
|
||||
view.updateVideoDimensions(localVideoDimensions, remoteVideoDimensions);
|
||||
var result = view.getRemoteVideoDimensions("camera");
|
||||
|
||||
expect(result.width).eql(remoteElement.offsetWidth);
|
||||
expect(result.height).eql(remoteElement.offsetHeight);
|
||||
// Aspect ratio modified from the height.
|
||||
expect(result.streamWidth).eql(360);
|
||||
expect(result.streamHeight).eql(remoteElement.offsetHeight);
|
||||
// The remote element width (640) minus the stream width (360) split in 2.
|
||||
expect(result.offsetX).eql(140);
|
||||
expect(result.offsetY).eql(0);
|
||||
});
|
||||
|
||||
it("should fetch the correct stream sizes for leading axis height and full",
|
||||
function() {
|
||||
remoteVideoDimensions = {
|
||||
screen: {
|
||||
width: 320,
|
||||
height: 240
|
||||
}
|
||||
};
|
||||
screenShareElement = {
|
||||
offsetWidth: 700,
|
||||
offsetHeight: 480
|
||||
};
|
||||
|
||||
view.updateVideoDimensions(localVideoDimensions, remoteVideoDimensions);
|
||||
var result = view.getRemoteVideoDimensions("screen");
|
||||
|
||||
expect(result.width).eql(screenShareElement.offsetWidth);
|
||||
expect(result.height).eql(screenShareElement.offsetHeight);
|
||||
// The real width of the stream accounting for the aspect ratio.
|
||||
expect(result.streamWidth).eql(640);
|
||||
expect(result.streamHeight).eql(screenShareElement.offsetHeight);
|
||||
// The remote element width (700) minus the stream width (640) split in 2.
|
||||
expect(result.offsetX).eql(30);
|
||||
expect(result.offsetY).eql(0);
|
||||
});
|
||||
|
||||
it("should fetch the correct stream sizes for leading axis height and not full",
|
||||
function() {
|
||||
remoteVideoDimensions = {
|
||||
camera: {
|
||||
width: 320,
|
||||
height: 240
|
||||
}
|
||||
};
|
||||
remoteElement = {
|
||||
offsetWidth: 480,
|
||||
offsetHeight: 640
|
||||
};
|
||||
|
||||
view.updateVideoDimensions(localVideoDimensions, remoteVideoDimensions);
|
||||
var result = view.getRemoteVideoDimensions("camera");
|
||||
|
||||
expect(result.width).eql(remoteElement.offsetWidth);
|
||||
expect(result.height).eql(remoteElement.offsetHeight);
|
||||
expect(result.streamWidth).eql(remoteElement.offsetWidth);
|
||||
// Aspect ratio modified from the width.
|
||||
expect(result.streamHeight).eql(360);
|
||||
expect(result.offsetX).eql(0);
|
||||
// The remote element width (640) minus the stream width (360) split in 2.
|
||||
expect(result.offsetY).eql(140);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Events", function() {
|
||||
|
||||
describe("Video stream dimensions", function() {
|
||||
var localVideoDimensions = {
|
||||
camera: {
|
||||
width: 640,
|
||||
height: 480
|
||||
}
|
||||
};
|
||||
var remoteVideoDimensions = {
|
||||
camera: {
|
||||
width: 420,
|
||||
height: 138
|
||||
}
|
||||
};
|
||||
|
||||
it("should register video dimension updates correctly", function() {
|
||||
view.updateVideoDimensions(localVideoDimensions, remoteVideoDimensions);
|
||||
|
||||
expect(view._videoDimensionsCache.local.camera.width)
|
||||
.eql(localVideoDimensions.camera.width);
|
||||
expect(view._videoDimensionsCache.local.camera.height)
|
||||
.eql(localVideoDimensions.camera.height);
|
||||
expect(view._videoDimensionsCache.local.camera.aspectRatio.width).eql(1);
|
||||
expect(view._videoDimensionsCache.local.camera.aspectRatio.height).eql(0.75);
|
||||
expect(view._videoDimensionsCache.remote.camera.width)
|
||||
.eql(remoteVideoDimensions.camera.width);
|
||||
expect(view._videoDimensionsCache.remote.camera.height)
|
||||
.eql(remoteVideoDimensions.camera.height);
|
||||
expect(view._videoDimensionsCache.remote.camera.aspectRatio.width).eql(1);
|
||||
expect(view._videoDimensionsCache.remote.camera.aspectRatio.height)
|
||||
.eql(0.32857142857142857);
|
||||
});
|
||||
|
||||
it("should unregister video dimension updates correctly", function() {
|
||||
view.updateVideoDimensions(localVideoDimensions, {});
|
||||
|
||||
expect("camera" in view._videoDimensionsCache.local).eql(true);
|
||||
expect("camera" in view._videoDimensionsCache.remote).eql(false);
|
||||
});
|
||||
|
||||
it("should not populate the cache on another component instance", function() {
|
||||
view.updateVideoDimensions(localVideoDimensions, remoteVideoDimensions);
|
||||
|
||||
var view2 =
|
||||
TestUtils.renderIntoDocument(React.createElement(TestComp));
|
||||
|
||||
expect(view2._videoDimensionsCache.local).to.be.empty;
|
||||
expect(view2._videoDimensionsCache.remote).to.be.empty;
|
||||
});
|
||||
|
||||
it("should return a set of defaults based on the options", function() {
|
||||
expect(view.getDefaultPublisherConfig({
|
||||
publishVideo: true
|
||||
}).publishVideo).eql(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -9,6 +9,8 @@ const TEST_URI = "data:text/html;charset=utf-8,Web Console HPKP invalid " +
|
|||
"header test";
|
||||
const SJS_URL = "https://example.com/browser/browser/devtools/webconsole/" +
|
||||
"test/test_hpkp-invalid-headers.sjs";
|
||||
const LEARN_MORE_URI = "https://developer.mozilla.org/docs/Web/Security/" +
|
||||
"Public_Key_Pinning";
|
||||
const NON_BUILTIN_ROOT_PREF = "security.cert_pinning.process_headers_from_" +
|
||||
"non_builtin_roots";
|
||||
|
||||
|
@ -86,7 +88,7 @@ function* checkForMessage(curTest, hud) {
|
|||
|
||||
content.location = curTest.url;
|
||||
|
||||
yield waitForMessages({
|
||||
let results = yield waitForMessages({
|
||||
webconsole: hud,
|
||||
messages: [
|
||||
{
|
||||
|
@ -94,7 +96,17 @@ function* checkForMessage(curTest, hud) {
|
|||
text: curTest.text,
|
||||
category: CATEGORY_SECURITY,
|
||||
severity: SEVERITY_WARNING,
|
||||
objects: true,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
yield testClickOpenNewTab(hud, results);
|
||||
}
|
||||
|
||||
function testClickOpenNewTab(hud, results) {
|
||||
let warningNode = results[0].clickableElements[0];
|
||||
ok(warningNode, "link element");
|
||||
ok(warningNode.classList.contains("learn-more-link"), "link class name");
|
||||
return simulateMessageLinkClick(warningNode, LEARN_MORE_URI);
|
||||
}
|
||||
|
|
|
@ -46,6 +46,8 @@ const TRACKING_PROTECTION_LEARN_MORE = "https://developer.mozilla.org/Firefox/Pr
|
|||
|
||||
const INSECURE_PASSWORDS_LEARN_MORE = "https://developer.mozilla.org/docs/Security/InsecurePasswords";
|
||||
|
||||
const PUBLIC_KEY_PINS_LEARN_MORE = "https://developer.mozilla.org/docs/Web/Security/Public_Key_Pinning";
|
||||
|
||||
const STRICT_TRANSPORT_SECURITY_LEARN_MORE = "https://developer.mozilla.org/docs/Security/HTTP_Strict_Transport_Security";
|
||||
|
||||
const WEAK_SIGNATURE_ALGORITHM_LEARN_MORE = "https://developer.mozilla.org/docs/Security/Weak_Signature_Algorithm";
|
||||
|
@ -1668,25 +1670,28 @@ WebConsoleFrame.prototype = {
|
|||
{
|
||||
let url;
|
||||
switch (aScriptError.category) {
|
||||
case "Insecure Password Field":
|
||||
url = INSECURE_PASSWORDS_LEARN_MORE;
|
||||
break;
|
||||
case "Mixed Content Message":
|
||||
case "Mixed Content Blocker":
|
||||
url = MIXED_CONTENT_LEARN_MORE;
|
||||
break;
|
||||
case "Invalid HSTS Headers":
|
||||
url = STRICT_TRANSPORT_SECURITY_LEARN_MORE;
|
||||
break;
|
||||
case "SHA-1 Signature":
|
||||
url = WEAK_SIGNATURE_ALGORITHM_LEARN_MORE;
|
||||
break;
|
||||
case "Tracking Protection":
|
||||
url = TRACKING_PROTECTION_LEARN_MORE;
|
||||
break;
|
||||
default:
|
||||
// Unknown category. Return without adding more info node.
|
||||
return;
|
||||
case "Insecure Password Field":
|
||||
url = INSECURE_PASSWORDS_LEARN_MORE;
|
||||
break;
|
||||
case "Mixed Content Message":
|
||||
case "Mixed Content Blocker":
|
||||
url = MIXED_CONTENT_LEARN_MORE;
|
||||
break;
|
||||
case "Invalid HPKP Headers":
|
||||
url = PUBLIC_KEY_PINS_LEARN_MORE;
|
||||
break;
|
||||
case "Invalid HSTS Headers":
|
||||
url = STRICT_TRANSPORT_SECURITY_LEARN_MORE;
|
||||
break;
|
||||
case "SHA-1 Signature":
|
||||
url = WEAK_SIGNATURE_ALGORITHM_LEARN_MORE;
|
||||
break;
|
||||
case "Tracking Protection":
|
||||
url = TRACKING_PROTECTION_LEARN_MORE;
|
||||
break;
|
||||
default:
|
||||
// Unknown category. Return without adding more info node.
|
||||
return;
|
||||
}
|
||||
|
||||
this.addLearnMoreWarningNode(aNode, url);
|
||||
|
|
|
@ -21,10 +21,26 @@ Components.utils.import('resource://gre/modules/Promise.jsm');
|
|||
Components.utils.import('resource://gre/modules/XPCOMUtils.jsm');
|
||||
Components.utils.import('resource://gre/modules/NetUtil.jsm');
|
||||
|
||||
function FileLoader(swfUrl, baseUrl, callback) {
|
||||
function FileLoaderSession(sessionId) {
|
||||
this.sessionId = sessionId;
|
||||
this.xhr = null;
|
||||
}
|
||||
FileLoaderSession.prototype = {
|
||||
abort() {
|
||||
if (this.xhr) {
|
||||
this.xhr.abort();
|
||||
this.xhr = null;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
function FileLoader(swfUrl, baseUrl, refererUrl, callback) {
|
||||
this.swfUrl = swfUrl;
|
||||
this.baseUrl = baseUrl;
|
||||
this.refererUrl = refererUrl;
|
||||
this.callback = callback;
|
||||
this.activeSessions = Object.create(null);
|
||||
|
||||
this.crossdomainRequestsCache = Object.create(null);
|
||||
}
|
||||
|
@ -50,18 +66,26 @@ FileLoader.prototype = {
|
|||
var method = data.method || "GET";
|
||||
var mimeType = data.mimeType;
|
||||
var postData = data.postData || null;
|
||||
var sendReferer = canSendReferer(swfUrl, this.refererUrl);
|
||||
|
||||
var session = new FileLoaderSession(sessionId);
|
||||
this.activeSessions[sessionId] = session;
|
||||
var self = this;
|
||||
|
||||
var performXHR = function () {
|
||||
var xhr = Components.classes["@mozilla.org/xmlextras/xmlhttprequest;1"]
|
||||
.createInstance(Components.interfaces.nsIXMLHttpRequest);
|
||||
// Load has been aborted before we reached this point.
|
||||
if (!self.activeSessions[sessionId]) {
|
||||
return;
|
||||
}
|
||||
var xhr = session.xhr = Components.classes["@mozilla.org/xmlextras/xmlhttprequest;1"].
|
||||
createInstance(Components.interfaces.nsIXMLHttpRequest);
|
||||
xhr.open(method, url, true);
|
||||
xhr.responseType = "moz-chunked-arraybuffer";
|
||||
|
||||
if (baseUrl) {
|
||||
if (sendReferer) {
|
||||
// Setting the referer uri, some site doing checks if swf is embedded
|
||||
// on the original page.
|
||||
xhr.setRequestHeader("Referer", baseUrl);
|
||||
xhr.setRequestHeader("Referer", self.refererUrl);
|
||||
}
|
||||
|
||||
// TODO apply range request headers if limit is specified
|
||||
|
@ -69,16 +93,23 @@ FileLoader.prototype = {
|
|||
var lastPosition = 0;
|
||||
xhr.onprogress = function (e) {
|
||||
var position = e.loaded;
|
||||
var total = e.total;
|
||||
var data = new Uint8Array(xhr.response);
|
||||
// The event's `loaded` and `total` properties are sometimes lower than the actual
|
||||
// number of loaded bytes. In that case, increase them to that value.
|
||||
position = Math.max(position, data.byteLength);
|
||||
total = Math.max(total, data.byteLength);
|
||||
|
||||
notifyLoadFileListener({callback:"loadFile", sessionId: sessionId,
|
||||
topic: "progress", array: data, loaded: position, total: e.total});
|
||||
topic: "progress", array: data, loaded: position, total: total});
|
||||
lastPosition = position;
|
||||
if (limit && e.total >= limit) {
|
||||
if (limit && total >= limit) {
|
||||
xhr.abort();
|
||||
}
|
||||
};
|
||||
xhr.onreadystatechange = function(event) {
|
||||
if (xhr.readyState === 4) {
|
||||
delete self.activeSessions[sessionId];
|
||||
if (xhr.status !== 200 && xhr.status !== 0) {
|
||||
notifyLoadFileListener({callback:"loadFile", sessionId: sessionId, topic: "error", error: xhr.statusText});
|
||||
}
|
||||
|
@ -95,9 +126,21 @@ FileLoader.prototype = {
|
|||
performXHR();
|
||||
}, function (reason) {
|
||||
log("data access is prohibited to " + url + " from " + baseUrl);
|
||||
notifyLoadFileListener({callback:"loadFile", sessionId: sessionId, topic: "error",
|
||||
error: "only original swf file or file from the same origin loading supported"});
|
||||
delete self.activeSessions[sessionId];
|
||||
notifyLoadFileListener({
|
||||
callback: "loadFile", sessionId: sessionId, topic: "error",
|
||||
error: "only original swf file or file from the same origin loading supported (XDOMAIN)"
|
||||
});
|
||||
});
|
||||
},
|
||||
abort: function(sessionId) {
|
||||
var session = this.activeSessions[sessionId];
|
||||
if (!session) {
|
||||
log("Warning: trying to abort invalid session " + sessionId);
|
||||
return;
|
||||
}
|
||||
session.abort();
|
||||
delete this.activeSessions[sessionId];
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -129,6 +172,25 @@ function disableXHRRedirect(xhr) {
|
|||
xhr.channel.notificationCallbacks = listener;
|
||||
}
|
||||
|
||||
function canSendReferer(url, refererUrl) {
|
||||
if (!refererUrl) {
|
||||
return false;
|
||||
}
|
||||
// Allow sending HTTPS referer only to HTTPS.
|
||||
var parsedUrl, parsedRefererUrl;
|
||||
try {
|
||||
parsedRefererUrl = NetUtil.newURI(refererUrl);
|
||||
} catch (ex) { /* skipping invalid urls */ }
|
||||
if (!parsedRefererUrl ||
|
||||
parsedRefererUrl.scheme.toLowerCase() !== 'https') {
|
||||
return true;
|
||||
}
|
||||
try {
|
||||
parsedUrl = NetUtil.newURI(url);
|
||||
} catch (ex) { /* skipping invalid urls */ }
|
||||
return !!parsedUrl && parsedUrl.scheme.toLowerCase() === 'https';
|
||||
}
|
||||
|
||||
function canDownloadFile(url, checkPolicyFile, swfUrl, cache) {
|
||||
// TODO flash cross-origin request
|
||||
if (url === swfUrl) {
|
||||
|
|
|
@ -0,0 +1,258 @@
|
|||
/*
|
||||
* Copyright 2015 Mozilla Foundation
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
var EXPORTED_SYMBOLS = ['LocalConnectionService'];
|
||||
|
||||
Components.utils.import('resource://gre/modules/NetUtil.jsm');
|
||||
Components.utils.import('resource://gre/modules/Services.jsm');
|
||||
|
||||
const localConnectionsRegistry = Object.create(null);
|
||||
|
||||
function isConnectionNameValid(connectionName) {
|
||||
return typeof connectionName === 'string' &&
|
||||
(connectionName[0] === '_' || connectionName.split(':').length === 2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a trusted qualified connection name from an already qualified name and a swfUrl.
|
||||
*
|
||||
* While connection names are already qualified at this point, the qualification happens in
|
||||
* untrusted code. To ensure that the name is correctly qualified, this function compares the
|
||||
* qualification domain with the current SWF's URL and substitutes that URL's domain if
|
||||
* required. A warning is logged in that case.
|
||||
*/
|
||||
function _getQualifiedConnectionName(connectionName, swfUrl) {
|
||||
// Already syntactically invalid connection names mustn't get here.
|
||||
if (!isConnectionNameValid(connectionName)) {
|
||||
// TODO: add telemetry
|
||||
throw new Error('Syntactically invalid local-connection name encountered', connectionName,
|
||||
swfUrl);
|
||||
}
|
||||
var [domain, name] = connectionName.split(':');
|
||||
var parsedURL = NetUtil.newURI(swfUrl);
|
||||
if (domain !== parsedURL.host) {
|
||||
// TODO: add telemetry
|
||||
log('Warning: invalid local-connection name qualification found: ' + connectionName);
|
||||
return parsedURL.host + ':' + swfUrl;
|
||||
}
|
||||
return connectionName;
|
||||
}
|
||||
|
||||
function _getLocalConnection(connectionName) {
|
||||
// Treat invalid connection names as non-existent. This can only happen if player code
|
||||
// misbehaves, though.
|
||||
if (!isConnectionNameValid(connectionName)) {
|
||||
// TODO: add telemetry
|
||||
return null;
|
||||
}
|
||||
var connection = localConnectionsRegistry[connectionName];
|
||||
if (connection && Components.utils.isDeadWrapper(connection.callback)) {
|
||||
delete localConnectionsRegistry[connectionName];
|
||||
return null;
|
||||
}
|
||||
return localConnectionsRegistry[connectionName];
|
||||
}
|
||||
|
||||
function LocalConnectionService(content, environment) {
|
||||
var traceLocalConnection = getBoolPref('shumway.localConnection.trace', false);
|
||||
var api = {
|
||||
createLocalConnection: function (connectionName, callback) {
|
||||
connectionName = connectionName + '';
|
||||
traceLocalConnection && content.console.log(`Creating local connection "${connectionName}" ` +
|
||||
`for SWF with URL ${environment.swfUrl}`);
|
||||
|
||||
if (!isConnectionNameValid(connectionName)) {
|
||||
// TODO: add telemetry
|
||||
traceLocalConnection && content.console.warn(`Invalid localConnection name `);
|
||||
return -1; // LocalConnectionConnectResult.InvalidName
|
||||
}
|
||||
if (typeof callback !== 'function') {
|
||||
// TODO: add telemetry
|
||||
traceLocalConnection && content.console.warn(`Invalid callback for localConnection`);
|
||||
return -3; // LocalConnectionConnectResult.InvalidCallback
|
||||
}
|
||||
connectionName = _getQualifiedConnectionName(connectionName, environment.swfUrl);
|
||||
if (_getLocalConnection(connectionName)) {
|
||||
traceLocalConnection && content.console.log(`localConnection ` +
|
||||
`name "${connectionName}" already taken`);
|
||||
return -2; // LocalConnectionConnectResult.AlreadyTaken
|
||||
}
|
||||
|
||||
var parsedURL = NetUtil.newURI(environment.swfUrl);
|
||||
|
||||
var connection = {
|
||||
callback,
|
||||
domain: parsedURL.host,
|
||||
environment: environment,
|
||||
secure: parsedURL.protocol === 'https:',
|
||||
allowedSecureDomains: Object.create(null),
|
||||
allowedInsecureDomains: Object.create(null)
|
||||
};
|
||||
localConnectionsRegistry[connectionName] = connection;
|
||||
return 0; // LocalConnectionConnectResult.Success
|
||||
},
|
||||
hasLocalConnection: function (connectionName) {
|
||||
connectionName = _getQualifiedConnectionName(connectionName + '', environment.swfUrl);
|
||||
|
||||
var result = !!_getLocalConnection(connectionName);
|
||||
traceLocalConnection && content.console.log(`hasLocalConnection "${connectionName}"? ` +
|
||||
result);
|
||||
return result;
|
||||
},
|
||||
closeLocalConnection: function (connectionName) {
|
||||
connectionName = _getQualifiedConnectionName(connectionName + '', environment.swfUrl);
|
||||
traceLocalConnection && content.console.log(`Closing local connection "${connectionName}" ` +
|
||||
`for SWF with URL ${environment.swfUrl}`);
|
||||
|
||||
var connection = _getLocalConnection(connectionName);
|
||||
if (!connection) {
|
||||
traceLocalConnection && content.console.log(`localConnection "${connectionName}" not ` +
|
||||
`connected`);
|
||||
return -1; // LocalConnectionCloseResult.NotConnected
|
||||
} else if (connection.environment !== environment) {
|
||||
// Attempts to close connections from a SWF instance that didn't create them shouldn't
|
||||
// happen. If they do, we treat them as if the connection didn't exist.
|
||||
traceLocalConnection && content.console.warn(`Ignored attempt to close localConnection ` +
|
||||
`"${connectionName}" from SWF instance that ` +
|
||||
`didn't create it`);
|
||||
return -1; // LocalConnectionCloseResult.NotConnected
|
||||
}
|
||||
delete localConnectionsRegistry[connectionName];
|
||||
return 0; // LocalConnectionCloseResult.Success
|
||||
},
|
||||
sendLocalConnectionMessage: function (connectionName, methodName, argsBuffer, sender,
|
||||
senderDomain, senderIsSecure) {
|
||||
connectionName = connectionName + '';
|
||||
methodName = methodName + '';
|
||||
senderDomain = senderDomain + '';
|
||||
senderIsSecure = !!senderIsSecure;
|
||||
// TODO: sanitize argsBuffer argument. Ask bholley how to do so.
|
||||
traceLocalConnection && content.console.log(`sending localConnection message ` +
|
||||
`"${methodName}" to "${connectionName}"`);
|
||||
|
||||
// Since we don't currently trust the sender information passed in here, we use the
|
||||
// currently running SWF's URL instead.
|
||||
var parsedURL = NetUtil.newURI(environment.swfUrl);
|
||||
var parsedURLIsSecure = parsedURL.protocol === 'https:';
|
||||
if (parsedURL.host !== senderDomain || parsedURLIsSecure !== senderIsSecure) {
|
||||
traceLocalConnection && content.console.warn(`sending localConnection message ` +
|
||||
`"${methodName}" to "${connectionName}"`);
|
||||
}
|
||||
senderDomain = parsedURL.host;
|
||||
senderIsSecure = parsedURLIsSecure;
|
||||
|
||||
var connection = _getLocalConnection(connectionName);
|
||||
if (!connection) {
|
||||
traceLocalConnection && content.console.log(`localConnection "${connectionName}" not ` +
|
||||
`connected`);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
var allowed = false;
|
||||
if (connection.secure) {
|
||||
// If the receiver is secure, the sender has to be, too, or it has to be whitelisted
|
||||
// with allowInsecureDomain.
|
||||
if (senderIsSecure) {
|
||||
if (senderDomain === connection.domain ||
|
||||
senderDomain in connection.allowedSecureDomains ||
|
||||
'*' in connection.allowedSecureDomains) {
|
||||
allowed = true;
|
||||
}
|
||||
} else {
|
||||
if (senderDomain in connection.allowedInsecureDomains ||
|
||||
'*' in connection.allowedInsecureDomains) {
|
||||
allowed = true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// For non-secure connections, allowedSecureDomains is expected to contain all allowed
|
||||
// domains, secure on non-secure, so we don't have to check both.
|
||||
if (senderDomain === connection.domain ||
|
||||
senderDomain in connection.allowedSecureDomains ||
|
||||
'*' in connection.allowedSecureDomains) {
|
||||
allowed = true;
|
||||
}
|
||||
}
|
||||
if (!allowed) {
|
||||
traceLocalConnection && content.console.warn(`LocalConnection message rejected: domain ` +
|
||||
`${senderDomain} not allowed.`);
|
||||
return {
|
||||
name: 'SecurityError',
|
||||
$Bgmessage: "The current security context does not allow this operation.",
|
||||
_errorID: 3315
|
||||
};
|
||||
}
|
||||
var callback = connection.callback;
|
||||
var clonedArgs = Components.utils.cloneInto(argsBuffer, callback);
|
||||
callback(methodName, clonedArgs);
|
||||
} catch (e) {
|
||||
// TODO: add telemetry
|
||||
content.console.warn('Unexpected error encountered while sending LocalConnection message.');
|
||||
}
|
||||
},
|
||||
allowDomainsForLocalConnection: function (connectionName, domains, secure) {
|
||||
connectionName = _getQualifiedConnectionName(connectionName + '', environment.swfUrl);
|
||||
secure = !!secure;
|
||||
var connection = _getLocalConnection(connectionName);
|
||||
if (!connection) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
domains = Components.utils.cloneInto(domains, connection);
|
||||
} catch (e) {
|
||||
log('error in allowDomainsForLocalConnection: ' + e);
|
||||
return;
|
||||
}
|
||||
traceLocalConnection && content.console.log(`allowing ${secure ? '' : 'in'}secure domains ` +
|
||||
`[${domains}] for localConnection ` +
|
||||
`"${connectionName}"`);
|
||||
function validateDomain(domain) {
|
||||
if (typeof domain !== 'string') {
|
||||
return false;
|
||||
}
|
||||
if (domain === '*') {
|
||||
return true;
|
||||
}
|
||||
try {
|
||||
var uri = NetUtil.newURI('http://' + domain);
|
||||
return uri.host === domain;
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (!Array.isArray(domains) || !domains.every(validateDomain)) {
|
||||
traceLocalConnection && content.console.warn(`Invalid domains rejected`);
|
||||
return;
|
||||
}
|
||||
var allowedDomains = secure ?
|
||||
connection.allowedSecureDomains :
|
||||
connection.allowedInsecureDomains;
|
||||
domains.forEach(domain => allowedDomains[domain] = true);
|
||||
}
|
||||
};
|
||||
|
||||
// Don't return `this` even though this function is treated as a ctor. Makes cloning into the
|
||||
// content compartment an internal operation the client code doesn't have to worry about.
|
||||
return Components.utils.cloneInto(api, content, {cloneFunctions:true});
|
||||
}
|
||||
|
||||
function getBoolPref(pref, def) {
|
||||
try {
|
||||
return Services.prefs.getBoolPref(pref);
|
||||
} catch (ex) {
|
||||
return def;
|
||||
}
|
||||
}
|
|
@ -16,15 +16,17 @@
|
|||
|
||||
var EXPORTED_SYMBOLS = ['ShumwayCom'];
|
||||
|
||||
Components.utils.import('resource://gre/modules/XPCOMUtils.jsm');
|
||||
Components.utils.import('resource://gre/modules/Services.jsm');
|
||||
Components.utils.import('resource://gre/modules/NetUtil.jsm');
|
||||
Components.utils.import('resource://gre/modules/Promise.jsm');
|
||||
Components.utils.import('resource://gre/modules/Services.jsm');
|
||||
Components.utils.import('resource://gre/modules/XPCOMUtils.jsm');
|
||||
|
||||
Components.utils.import('chrome://shumway/content/SpecialInflate.jsm');
|
||||
Components.utils.import('chrome://shumway/content/SpecialStorage.jsm');
|
||||
Components.utils.import('chrome://shumway/content/RtmpUtils.jsm');
|
||||
Components.utils.import('chrome://shumway/content/ExternalInterface.jsm');
|
||||
Components.utils.import('chrome://shumway/content/FileLoader.jsm');
|
||||
Components.utils.import('chrome://shumway/content/LocalConnection.jsm');
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, 'ShumwayTelemetry',
|
||||
'resource://shumway/ShumwayTelemetry.jsm');
|
||||
|
@ -75,6 +77,9 @@ function sanitizeTelemetryArgs(args) {
|
|||
case 'feature':
|
||||
request.featureType = args.feature | 0;
|
||||
break;
|
||||
case 'loadResource':
|
||||
request.resultType = args.resultType | 0;
|
||||
break;
|
||||
case 'error':
|
||||
request.errorType = args.error | 0;
|
||||
break;
|
||||
|
@ -89,6 +94,7 @@ function sanitizeLoadFileArgs(args) {
|
|||
sessionId: +args.sessionId,
|
||||
limit: +args.limit || 0,
|
||||
mimeType: String(args.mimeType || ''),
|
||||
method: (args.method + '') || 'GET',
|
||||
postData: args.postData || null
|
||||
};
|
||||
}
|
||||
|
@ -112,109 +118,190 @@ function sanitizeExternalComArgs(args) {
|
|||
return request;
|
||||
}
|
||||
|
||||
var cloneIntoFromContent = (function () {
|
||||
// waiveXrays are used due to bug 1150771, checking if we are affected
|
||||
// TODO remove workaround after Firefox 40 is released (2015-08-11)
|
||||
let sandbox1 = new Components.utils.Sandbox(null);
|
||||
let sandbox2 = new Components.utils.Sandbox(null);
|
||||
let arg = Components.utils.evalInSandbox('({buf: new ArrayBuffer(2)})', sandbox1);
|
||||
let clonedArg = Components.utils.cloneInto(arg, sandbox2);
|
||||
if (!Components.utils.waiveXrays(clonedArg).buf) {
|
||||
return function (obj, contentSandbox) {
|
||||
return Components.utils.cloneInto(
|
||||
Components.utils.waiveXrays(obj), contentSandbox);
|
||||
};
|
||||
}
|
||||
|
||||
return function (obj, contentSandbox) {
|
||||
return Components.utils.cloneInto(obj, contentSandbox);
|
||||
};
|
||||
})();
|
||||
|
||||
var ShumwayEnvironment = {
|
||||
DEBUG: 'debug',
|
||||
DEVELOPMENT: 'dev',
|
||||
RELEASE: 'release',
|
||||
TEST: 'test'
|
||||
};
|
||||
|
||||
var ShumwayCom = {
|
||||
environment: getCharPref('shumway.environment', 'dev'),
|
||||
|
||||
createAdapter: function (content, callbacks, hooks) {
|
||||
// Exposing ShumwayCom object/adapter to the unprivileged content -- setting
|
||||
// up Xray wrappers.
|
||||
var wrapped = {
|
||||
enableDebug: function enableDebug() {
|
||||
environment: ShumwayCom.environment,
|
||||
|
||||
enableDebug: function () {
|
||||
callbacks.enableDebug()
|
||||
},
|
||||
|
||||
setFullscreen: function setFullscreen(value) {
|
||||
value = !!value;
|
||||
callbacks.sendMessage('setFullscreen', value, false);
|
||||
},
|
||||
|
||||
fallback: function fallback() {
|
||||
fallback: function () {
|
||||
callbacks.sendMessage('fallback', null, false);
|
||||
},
|
||||
|
||||
getSettings: function getSettings() {
|
||||
getSettings: function () {
|
||||
return Components.utils.cloneInto(
|
||||
callbacks.sendMessage('getSettings', null, true), content);
|
||||
},
|
||||
|
||||
getPluginParams: function getPluginParams() {
|
||||
getPluginParams: function () {
|
||||
return Components.utils.cloneInto(
|
||||
callbacks.sendMessage('getPluginParams', null, true), content);
|
||||
},
|
||||
|
||||
reportIssue: function reportIssue() {
|
||||
reportIssue: function () {
|
||||
callbacks.sendMessage('reportIssue', null, false);
|
||||
},
|
||||
|
||||
reportTelemetry: function reportTelemetry(args) {
|
||||
reportTelemetry: function (args) {
|
||||
var request = sanitizeTelemetryArgs(args);
|
||||
callbacks.sendMessage('reportTelemetry', request, false);
|
||||
},
|
||||
|
||||
userInput: function userInput() {
|
||||
callbacks.sendMessage('userInput', null, true);
|
||||
setupGfxComBridge: function (gfxWindow) {
|
||||
// Creates ShumwayCom adapter for the gfx iframe exposing only subset
|
||||
// of the privileged function. Removing Xrays to setup the ShumwayCom
|
||||
// property and for usage as a sandbox for cloneInto operations.
|
||||
var gfxContent = gfxWindow.contentWindow.wrappedJSObject;
|
||||
ShumwayCom.createGfxAdapter(gfxContent, callbacks, hooks);
|
||||
|
||||
setupUserInput(gfxWindow.contentWindow, callbacks);
|
||||
},
|
||||
|
||||
setupComBridge: function setupComBridge(playerWindow) {
|
||||
// postSyncMessage helper function to relay messages from the secondary
|
||||
// window to the primary one.
|
||||
function postSyncMessage(msg) {
|
||||
if (onSyncMessageCallback) {
|
||||
// the msg came from other content window
|
||||
// waiveXrays are used due to bug 1150771.
|
||||
var reclonedMsg = Components.utils.cloneInto(Components.utils.waiveXrays(msg), content);
|
||||
var result = onSyncMessageCallback(reclonedMsg);
|
||||
// the result will be sent later to other content window
|
||||
var waivedResult = Components.utils.waiveXrays(result);
|
||||
return waivedResult;
|
||||
}
|
||||
}
|
||||
|
||||
// Creates secondary ShumwayCom adapter.
|
||||
setupPlayerComBridge: function (playerWindow) {
|
||||
// Creates ShumwayCom adapter for the player iframe exposing only subset
|
||||
// of the privileged function. Removing Xrays to setup the ShumwayCom
|
||||
// property and for usage as a sandbox for cloneInto operations.
|
||||
var playerContent = playerWindow.contentWindow.wrappedJSObject;
|
||||
ShumwayCom.createPlayerAdapter(playerContent, postSyncMessage, callbacks, hooks);
|
||||
},
|
||||
|
||||
setSyncMessageCallback: function (callback) {
|
||||
if (callback !== null && typeof callback !== 'function') {
|
||||
return;
|
||||
}
|
||||
onSyncMessageCallback = callback;
|
||||
ShumwayCom.createPlayerAdapter(playerContent, callbacks, hooks);
|
||||
}
|
||||
};
|
||||
|
||||
var onSyncMessageCallback = null;
|
||||
|
||||
var shumwayComAdapter = Components.utils.cloneInto(wrapped, content, {cloneFunctions:true});
|
||||
content.ShumwayCom = shumwayComAdapter;
|
||||
},
|
||||
|
||||
createPlayerAdapter: function (content, postSyncMessage, callbacks, hooks) {
|
||||
createGfxAdapter: function (content, callbacks, hooks) {
|
||||
// Exposing ShumwayCom object/adapter to the unprivileged content -- setting
|
||||
// up Xray wrappers.
|
||||
var wrapped = {
|
||||
externalCom: function externalCom(args) {
|
||||
environment: ShumwayCom.environment,
|
||||
|
||||
setFullscreen: function (value) {
|
||||
value = !!value;
|
||||
callbacks.sendMessage('setFullscreen', value, false);
|
||||
},
|
||||
|
||||
reportTelemetry: function (args) {
|
||||
var request = sanitizeTelemetryArgs(args);
|
||||
callbacks.sendMessage('reportTelemetry', request, false);
|
||||
},
|
||||
|
||||
postAsyncMessage: function (msg) {
|
||||
if (hooks.onPlayerAsyncMessageCallback) {
|
||||
hooks.onPlayerAsyncMessageCallback(msg);
|
||||
}
|
||||
},
|
||||
|
||||
setSyncMessageCallback: function (callback) {
|
||||
if (typeof callback !== 'function') {
|
||||
log('error: attempt to set non-callable as callback in setSyncMessageCallback');
|
||||
return;
|
||||
}
|
||||
hooks.onGfxSyncMessageCallback = function (msg, sandbox) {
|
||||
var reclonedMsg = cloneIntoFromContent(msg, content);
|
||||
var result = callback(reclonedMsg);
|
||||
return cloneIntoFromContent(result, sandbox);
|
||||
};
|
||||
},
|
||||
|
||||
setAsyncMessageCallback: function (callback) {
|
||||
if (typeof callback !== 'function') {
|
||||
log('error: attempt to set non-callable as callback in setAsyncMessageCallback');
|
||||
return;
|
||||
}
|
||||
hooks.onGfxAsyncMessageCallback = function (msg) {
|
||||
var reclonedMsg = cloneIntoFromContent(msg, content);
|
||||
callback(reclonedMsg);
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
if (ShumwayCom.environment === ShumwayEnvironment.TEST) {
|
||||
wrapped.processFrame = function () {
|
||||
callbacks.sendMessage('processFrame');
|
||||
};
|
||||
|
||||
wrapped.processFSCommand = function (command, args) {
|
||||
callbacks.sendMessage('processFSCommand', command, args);
|
||||
};
|
||||
|
||||
wrapped.setScreenShotCallback = function (callback) {
|
||||
callbacks.sendMessage('setScreenShotCallback', callback);
|
||||
};
|
||||
}
|
||||
|
||||
var shumwayComAdapter = Components.utils.cloneInto(wrapped, content, {cloneFunctions:true});
|
||||
content.ShumwayCom = shumwayComAdapter;
|
||||
},
|
||||
|
||||
createPlayerAdapter: function (content, callbacks, hooks) {
|
||||
// Exposing ShumwayCom object/adapter to the unprivileged content -- setting
|
||||
// up Xray wrappers.
|
||||
var wrapped = {
|
||||
environment: ShumwayCom.environment,
|
||||
|
||||
externalCom: function (args) {
|
||||
var request = sanitizeExternalComArgs(args);
|
||||
var result = String(callbacks.sendMessage('externalCom', request, true));
|
||||
return result;
|
||||
},
|
||||
|
||||
loadFile: function loadFile(args) {
|
||||
loadFile: function (args) {
|
||||
var request = sanitizeLoadFileArgs(args);
|
||||
callbacks.sendMessage('loadFile', request, false);
|
||||
},
|
||||
|
||||
reportTelemetry: function reportTelemetry(args) {
|
||||
abortLoad: function (sessionId) {
|
||||
sessionId = sessionId|0;
|
||||
callbacks.sendMessage('abortLoad', sessionId, false);
|
||||
},
|
||||
|
||||
reportTelemetry: function (args) {
|
||||
var request = sanitizeTelemetryArgs(args);
|
||||
callbacks.sendMessage('reportTelemetry', request, false);
|
||||
},
|
||||
|
||||
setClipboard: function setClipboard(args) {
|
||||
setClipboard: function (args) {
|
||||
if (typeof args !== 'string') {
|
||||
return; // ignore non-string argument
|
||||
}
|
||||
callbacks.sendMessage('setClipboard', args, false);
|
||||
},
|
||||
|
||||
navigateTo: function navigateTo(args) {
|
||||
navigateTo: function (args) {
|
||||
var request = {
|
||||
url: String(args.url || ''),
|
||||
target: String(args.target || '')
|
||||
|
@ -222,7 +309,7 @@ var ShumwayCom = {
|
|||
callbacks.sendMessage('navigateTo', request, false);
|
||||
},
|
||||
|
||||
loadSystemResource: function loadSystemResource(id) {
|
||||
loadSystemResource: function (id) {
|
||||
loadShumwaySystemResource(id).then(function (data) {
|
||||
if (onSystemResourceCallback) {
|
||||
onSystemResourceCallback(id, Components.utils.cloneInto(data, content));
|
||||
|
@ -230,9 +317,29 @@ var ShumwayCom = {
|
|||
});
|
||||
},
|
||||
|
||||
postSyncMessage: function (msg) {
|
||||
var result = postSyncMessage(msg);
|
||||
return Components.utils.cloneInto(result, content)
|
||||
sendSyncMessage: function (msg) {
|
||||
var result;
|
||||
if (hooks.onGfxSyncMessageCallback) {
|
||||
result = hooks.onGfxSyncMessageCallback(msg, content);
|
||||
}
|
||||
return result;
|
||||
},
|
||||
|
||||
postAsyncMessage: function (msg) {
|
||||
if (hooks.onGfxAsyncMessageCallback) {
|
||||
hooks.onGfxAsyncMessageCallback(msg);
|
||||
}
|
||||
},
|
||||
|
||||
setAsyncMessageCallback: function (callback) {
|
||||
if (typeof callback !== 'function') {
|
||||
log('error: attempt to set non-callable as callback in setAsyncMessageCallback');
|
||||
return;
|
||||
}
|
||||
hooks.onPlayerAsyncMessageCallback = function (msg) {
|
||||
var reclonedMsg = cloneIntoFromContent(msg, content);
|
||||
callback(reclonedMsg);
|
||||
};
|
||||
},
|
||||
|
||||
createSpecialStorage: function () {
|
||||
|
@ -267,6 +374,14 @@ var ShumwayCom = {
|
|||
return;
|
||||
}
|
||||
onSystemResourceCallback = callback;
|
||||
},
|
||||
|
||||
getLocalConnectionService: function() {
|
||||
if (!wrappedLocalConnectionService) {
|
||||
wrappedLocalConnectionService = new LocalConnectionService(content,
|
||||
callbacks.getEnvironment());
|
||||
}
|
||||
return wrappedLocalConnectionService;
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -289,10 +404,18 @@ var ShumwayCom = {
|
|||
};
|
||||
}
|
||||
|
||||
if (ShumwayCom.environment === ShumwayEnvironment.TEST) {
|
||||
wrapped.print = function(msg) {
|
||||
callbacks.sendMessage('print', msg);
|
||||
}
|
||||
}
|
||||
|
||||
var onSystemResourceCallback = null;
|
||||
var onExternalCallback = null;
|
||||
var onLoadFileCallback = null;
|
||||
|
||||
var wrappedLocalConnectionService = null;
|
||||
|
||||
hooks.onLoadFileCallback = function (arg) {
|
||||
if (onLoadFileCallback) {
|
||||
onLoadFileCallback(Components.utils.cloneInto(arg, content));
|
||||
|
@ -346,6 +469,18 @@ function loadShumwaySystemResource(id) {
|
|||
return deferred.promise;
|
||||
}
|
||||
|
||||
function setupUserInput(contentWindow, callbacks) {
|
||||
function notifyUserInput() {
|
||||
callbacks.sendMessage('userInput', null, true);
|
||||
}
|
||||
|
||||
// Ignoring the untrusted events by providing the 4th argument for addEventListener.
|
||||
contentWindow.document.addEventListener('mousedown', notifyUserInput, true, false);
|
||||
contentWindow.document.addEventListener('mouseup', notifyUserInput, true, false);
|
||||
contentWindow.document.addEventListener('keydown', notifyUserInput, true, false);
|
||||
contentWindow.document.addEventListener('keyup', notifyUserInput, true, false);
|
||||
}
|
||||
|
||||
// All the privileged actions.
|
||||
function ShumwayChromeActions(startupInfo, window, document) {
|
||||
this.url = startupInfo.url;
|
||||
|
@ -355,6 +490,7 @@ function ShumwayChromeActions(startupInfo, window, document) {
|
|||
this.isOverlay = startupInfo.isOverlay;
|
||||
this.embedTag = startupInfo.embedTag;
|
||||
this.isPausedAtStart = startupInfo.isPausedAtStart;
|
||||
this.initStartTime = startupInfo.initStartTime;
|
||||
this.window = window;
|
||||
this.document = document;
|
||||
this.allowScriptAccess = startupInfo.allowScriptAccess;
|
||||
|
@ -365,9 +501,8 @@ function ShumwayChromeActions(startupInfo, window, document) {
|
|||
errors: []
|
||||
};
|
||||
|
||||
this.fileLoader = new FileLoader(startupInfo.url, startupInfo.baseUrl, function (args) {
|
||||
this.onLoadFileCallback(args);
|
||||
}.bind(this));
|
||||
this.fileLoader = new FileLoader(startupInfo.url, startupInfo.baseUrl, startupInfo.refererUrl,
|
||||
function (args) { this.onLoadFileCallback(args); }.bind(this));
|
||||
this.onLoadFileCallback = null;
|
||||
|
||||
this.externalInterface = null;
|
||||
|
@ -400,8 +535,7 @@ ShumwayChromeActions.prototype = {
|
|||
playerSettings: {
|
||||
turboMode: getBoolPref('shumway.turboMode', false),
|
||||
hud: getBoolPref('shumway.hud', false),
|
||||
forceHidpi: getBoolPref('shumway.force_hidpi', false),
|
||||
env: getCharPref('shumway.environment', 'dev')
|
||||
forceHidpi: getBoolPref('shumway.force_hidpi', false)
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -414,6 +548,7 @@ ShumwayChromeActions.prototype = {
|
|||
objectParams: this.objectParams,
|
||||
isOverlay: this.isOverlay,
|
||||
isPausedAtStart: this.isPausedAtStart,
|
||||
initStartTime: this.initStartTime,
|
||||
isDebuggerEnabled: getBoolPref('shumway.debug.enabled', false)
|
||||
};
|
||||
},
|
||||
|
@ -422,6 +557,10 @@ ShumwayChromeActions.prototype = {
|
|||
this.fileLoader.load(data);
|
||||
},
|
||||
|
||||
abortLoad: function abortLoad(sessionId) {
|
||||
this.fileLoader.abort(sessionId);
|
||||
},
|
||||
|
||||
navigateTo: function (data) {
|
||||
// Our restrictions are a little bit different from Flash's: let's enable
|
||||
// only http(s) and only when script execution is allowed.
|
||||
|
@ -451,20 +590,11 @@ ShumwayChromeActions.prototype = {
|
|||
},
|
||||
|
||||
userInput: function() {
|
||||
var win = this.window;
|
||||
var winUtils = win.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
|
||||
.getInterface(Components.interfaces.nsIDOMWindowUtils);
|
||||
if (winUtils.isHandlingUserInput) {
|
||||
this.lastUserInput = Date.now();
|
||||
}
|
||||
// Recording time of last user input for isUserInputInProgress below.
|
||||
this.lastUserInput = Date.now();
|
||||
},
|
||||
|
||||
isUserInputInProgress: function () {
|
||||
// TODO userInput does not work for OOP
|
||||
if (!getBoolPref('shumway.userInputSecurity', true)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// We don't trust our Shumway non-privileged code just yet to verify the
|
||||
// user input -- using userInput function above to track that.
|
||||
if ((Date.now() - this.lastUserInput) > MAX_USER_INPUT_TIMEOUT) {
|
||||
|
@ -515,6 +645,13 @@ ShumwayChromeActions.prototype = {
|
|||
ShumwayTelemetry.onFeature(featureType);
|
||||
}
|
||||
break;
|
||||
case 'loadResource':
|
||||
var resultType = request.resultType;
|
||||
var MIN_RESULT_TYPE = 0, MAX_RESULT_TYPE = 10;
|
||||
if (resultType >= MIN_RESULT_TYPE && resultType <= MAX_RESULT_TYPE) {
|
||||
ShumwayTelemetry.onLoadResource(resultType);
|
||||
}
|
||||
break;
|
||||
case 'error':
|
||||
var errorType = request.errorType;
|
||||
var MIN_ERROR_TYPE = 0, MAX_ERROR_TYPE = 2;
|
||||
|
@ -567,6 +704,35 @@ ShumwayChromeActions.prototype = {
|
|||
}
|
||||
|
||||
return this.externalInterface.processAction(data);
|
||||
},
|
||||
|
||||
postMessage: function (type, data) {
|
||||
var embedTag = this.embedTag;
|
||||
var event = embedTag.ownerDocument.createEvent('CustomEvent');
|
||||
var detail = Components.utils.cloneInto({ type: type, data: data }, embedTag.ownerDocument.wrappedJSObject);
|
||||
event.initCustomEvent('message', false, false, detail);
|
||||
embedTag.dispatchEvent(event);
|
||||
},
|
||||
|
||||
processFrame: function () {
|
||||
this.postMessage('processFrame');
|
||||
},
|
||||
|
||||
processFSCommand: function (command, data) {
|
||||
this.postMessage('processFSCommand', { command: command, data: data });
|
||||
},
|
||||
|
||||
print: function (msg) {
|
||||
this.postMessage('print', msg);
|
||||
},
|
||||
|
||||
setScreenShotCallback: function (callback) {
|
||||
var embedTag = this.embedTag;
|
||||
Components.utils.exportFunction(function () {
|
||||
// `callback` can be wrapped in a CPOW and thus cause a slow synchronous cross-process operation.
|
||||
var result = callback();
|
||||
return Components.utils.cloneInto(result, embedTag.ownerDocument);
|
||||
}, embedTag.wrappedJSObject, {defineAs: 'getCanvasData'});
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -0,0 +1,186 @@
|
|||
/*
|
||||
* Copyright 2015 Mozilla Foundation
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
var EXPORTED_SYMBOLS = ['getStartupInfo', 'parseQueryString', 'isContentWindowPrivate'];
|
||||
|
||||
Components.utils.import('resource://gre/modules/XPCOMUtils.jsm');
|
||||
Components.utils.import('resource://gre/modules/Services.jsm');
|
||||
|
||||
Components.utils.import('chrome://shumway/content/ShumwayCom.jsm');
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, 'PrivateBrowsingUtils',
|
||||
'resource://gre/modules/PrivateBrowsingUtils.jsm');
|
||||
|
||||
function flashUnescape(s) {
|
||||
return decodeURIComponent(s.split('+').join(' '));
|
||||
}
|
||||
|
||||
function parseQueryString(qs) {
|
||||
if (!qs)
|
||||
return {};
|
||||
|
||||
if (qs.charAt(0) == '?')
|
||||
qs = qs.slice(1);
|
||||
|
||||
var values = qs.split('&');
|
||||
var obj = {};
|
||||
for (var i = 0; i < values.length; i++) {
|
||||
var pair = values[i], j = pair.indexOf('=');
|
||||
if (j < 0) {
|
||||
continue; // skipping invalid values
|
||||
}
|
||||
var key = pair.substring(0, j), value = pair.substring(j + 1);
|
||||
obj[flashUnescape(key)] = flashUnescape(value);
|
||||
}
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
function isContentWindowPrivate(win) {
|
||||
if (!('isContentWindowPrivate' in PrivateBrowsingUtils)) {
|
||||
return PrivateBrowsingUtils.isWindowPrivate(win);
|
||||
}
|
||||
return PrivateBrowsingUtils.isContentWindowPrivate(win);
|
||||
}
|
||||
|
||||
function isStandardEmbedWrapper(embedElement) {
|
||||
try {
|
||||
if (embedElement.tagName !== 'EMBED') {
|
||||
return false;
|
||||
}
|
||||
var swfUrl = embedElement.src;
|
||||
var document = embedElement.ownerDocument;
|
||||
var docUrl = document.location.href;
|
||||
if (swfUrl !== docUrl) {
|
||||
return false; // document URL shall match embed src
|
||||
}
|
||||
if (document.body.children.length !== 1 ||
|
||||
document.body.firstChild !== embedElement) {
|
||||
return false; // not the only child
|
||||
}
|
||||
if (document.defaultView.top !== document.defaultView) {
|
||||
return false; // not a top window
|
||||
}
|
||||
// Looks like a standard wrapper
|
||||
return true;
|
||||
} catch (e) {
|
||||
// Declare that is not a standard fullscreen plugin wrapper for any error
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function isScriptAllowed(allowScriptAccessParameter, url, pageUrl) {
|
||||
if (!allowScriptAccessParameter) {
|
||||
allowScriptAccessParameter = 'sameDomain';
|
||||
}
|
||||
var allowScriptAccess = false;
|
||||
switch (allowScriptAccessParameter.toLowerCase()) { // ignoring case here
|
||||
case 'always':
|
||||
allowScriptAccess = true;
|
||||
break;
|
||||
case 'never':
|
||||
allowScriptAccess = false;
|
||||
break;
|
||||
default: // 'samedomain'
|
||||
if (!pageUrl)
|
||||
break;
|
||||
try {
|
||||
// checking if page is in same domain (? same protocol and port)
|
||||
allowScriptAccess =
|
||||
Services.io.newURI('/', null, Services.io.newURI(pageUrl, null, null)).spec ==
|
||||
Services.io.newURI('/', null, Services.io.newURI(url, null, null)).spec;
|
||||
} catch (ex) {}
|
||||
break;
|
||||
}
|
||||
return allowScriptAccess;
|
||||
}
|
||||
|
||||
function getStartupInfo(element) {
|
||||
var initStartTime = Date.now();
|
||||
var baseUrl;
|
||||
var pageUrl;
|
||||
var isOverlay = false;
|
||||
var objectParams = {};
|
||||
|
||||
// Getting absolute URL from the EMBED tag
|
||||
var url = element.srcURI && element.srcURI.spec;
|
||||
|
||||
pageUrl = element.ownerDocument.location.href; // proper page url?
|
||||
|
||||
var tagName = element.nodeName;
|
||||
if (tagName == 'EMBED') {
|
||||
for (var i = 0; i < element.attributes.length; ++i) {
|
||||
var paramName = element.attributes[i].localName.toLowerCase();
|
||||
objectParams[paramName] = element.attributes[i].value;
|
||||
}
|
||||
} else {
|
||||
for (var i = 0; i < element.childNodes.length; ++i) {
|
||||
var paramElement = element.childNodes[i];
|
||||
if (paramElement.nodeType != 1 ||
|
||||
paramElement.nodeName != 'PARAM') {
|
||||
continue;
|
||||
}
|
||||
var paramName = paramElement.getAttribute('name').toLowerCase();
|
||||
objectParams[paramName] = paramElement.getAttribute('value');
|
||||
}
|
||||
}
|
||||
|
||||
baseUrl = pageUrl;
|
||||
if (objectParams.base) {
|
||||
try {
|
||||
// Verifying base URL, passed in object parameters. It shall be okay to
|
||||
// ignore bad/corrupted base.
|
||||
var parsedPageUrl = Services.io.newURI(pageUrl, null, null);
|
||||
baseUrl = Services.io.newURI(objectParams.base, null, parsedPageUrl).spec;
|
||||
} catch (e) { /* it's okay to ignore any exception */ }
|
||||
}
|
||||
|
||||
var movieParams = {};
|
||||
if (objectParams.flashvars) {
|
||||
movieParams = parseQueryString(objectParams.flashvars);
|
||||
}
|
||||
var queryStringMatch = url && /\?([^#]+)/.exec(url);
|
||||
if (queryStringMatch) {
|
||||
var queryStringParams = parseQueryString(queryStringMatch[1]);
|
||||
for (var i in queryStringParams) {
|
||||
if (!(i in movieParams)) {
|
||||
movieParams[i] = queryStringParams[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var allowScriptAccess = !!url &&
|
||||
isScriptAllowed(objectParams.allowscriptaccess, url, pageUrl);
|
||||
var isFullscreenSwf = isStandardEmbedWrapper(element);
|
||||
|
||||
var document = element.ownerDocument;
|
||||
var window = document.defaultView;
|
||||
|
||||
var startupInfo = {};
|
||||
startupInfo.window = window;
|
||||
startupInfo.url = url;
|
||||
startupInfo.privateBrowsing = isContentWindowPrivate(window);
|
||||
startupInfo.objectParams = objectParams;
|
||||
startupInfo.movieParams = movieParams;
|
||||
startupInfo.baseUrl = baseUrl || url;
|
||||
startupInfo.isOverlay = isOverlay;
|
||||
startupInfo.refererUrl = !isFullscreenSwf ? baseUrl : null;
|
||||
startupInfo.embedTag = element;
|
||||
startupInfo.initStartTime = initStartTime;
|
||||
startupInfo.allowScriptAccess = allowScriptAccess;
|
||||
startupInfo.pageIndex = 0;
|
||||
return startupInfo;
|
||||
}
|
|
@ -0,0 +1,56 @@
|
|||
<!DOCTYPE html>
|
||||
<!--
|
||||
Copyright 2015 Mozilla Foundation
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
|
||||
|
||||
<style>
|
||||
html, body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
iframe {
|
||||
position:fixed !important;
|
||||
left:0;top:0;bottom:0;right:0;
|
||||
overflow: hidden;
|
||||
line-height: 0;
|
||||
border: 0px none;
|
||||
}
|
||||
|
||||
body.remoteStopped {
|
||||
background-color: red;
|
||||
}
|
||||
body.remoteDebug {
|
||||
background-color: green;
|
||||
}
|
||||
body.remoteReload {
|
||||
background-color: yellow;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<iframe id="viewer" src="resource://shumway/web/viewer.html" width="100%" height="100%"></iframe>
|
||||
<script src="chrome://shumway/content/content.js"></script>
|
||||
</body>
|
||||
</html>
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2014 Mozilla Foundation
|
||||
* Copyright 2015 Mozilla Foundation
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
@ -15,8 +15,33 @@
|
|||
*/
|
||||
|
||||
Components.utils.import('resource://gre/modules/Services.jsm');
|
||||
Components.utils.import('resource://gre/modules/Promise.jsm');
|
||||
|
||||
Components.utils.import('chrome://shumway/content/ShumwayCom.jsm');
|
||||
|
||||
var messageManager, viewerReady;
|
||||
// Checking if we loading content.js in the OOP/mozbrowser or jsplugins.
|
||||
// TODO remove mozbrowser logic when we switch to jsplugins only support
|
||||
if (typeof document === 'undefined') { // mozbrowser OOP frame script
|
||||
messageManager = this;
|
||||
viewerReady = Promise.resolve(content);
|
||||
messageManager.sendAsyncMessage('Shumway:constructed', null);
|
||||
} else { // jsplugins instance
|
||||
messageManager = window.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
|
||||
.getInterface(Components.interfaces.nsIDocShell)
|
||||
.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
|
||||
.getInterface(Components.interfaces.nsIContentFrameMessageManager);
|
||||
|
||||
var viewer = document.getElementById('viewer');
|
||||
viewerReady = new Promise(function (resolve) {
|
||||
viewer.addEventListener('load', function () {
|
||||
messageManager.sendAsyncMessage('Shumway:constructed', null);
|
||||
resolve(viewer.contentWindow);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
var externalInterfaceWrapper = {
|
||||
callback: function (call) {
|
||||
if (!shumwayComAdapterHooks.onExternalCallback) {
|
||||
|
@ -32,35 +57,37 @@ var shumwayComAdapterHooks = {};
|
|||
function sendMessage(action, data, sync) {
|
||||
var detail = {action: action, data: data, sync: sync};
|
||||
if (!sync) {
|
||||
sendAsyncMessage('Shumway:message', detail);
|
||||
messageManager.sendAsyncMessage('Shumway:message', detail);
|
||||
return;
|
||||
}
|
||||
var result = String(sendSyncMessage('Shumway:message', detail));
|
||||
var result = String(messageManager.sendSyncMessage('Shumway:message', detail));
|
||||
result = result == 'undefined' ? undefined : JSON.parse(result);
|
||||
return Components.utils.cloneInto(result, content);
|
||||
}
|
||||
|
||||
function enableDebug() {
|
||||
sendAsyncMessage('Shumway:enableDebug', null);
|
||||
messageManager.sendAsyncMessage('Shumway:enableDebug', null);
|
||||
}
|
||||
|
||||
addMessageListener('Shumway:init', function (message) {
|
||||
messageManager.addMessageListener('Shumway:init', function (message) {
|
||||
var environment = message.data;
|
||||
|
||||
sendAsyncMessage('Shumway:running', {}, {
|
||||
messageManager.sendAsyncMessage('Shumway:running', {}, {
|
||||
externalInterface: externalInterfaceWrapper
|
||||
});
|
||||
|
||||
ShumwayCom.createAdapter(content.wrappedJSObject, {
|
||||
sendMessage: sendMessage,
|
||||
enableDebug: enableDebug,
|
||||
getEnvironment: function () { return environment; }
|
||||
}, shumwayComAdapterHooks);
|
||||
viewerReady.then(function (viewerWindow) {
|
||||
ShumwayCom.createAdapter(viewerWindow.wrappedJSObject, {
|
||||
sendMessage: sendMessage,
|
||||
enableDebug: enableDebug,
|
||||
getEnvironment: function () { return environment; }
|
||||
}, shumwayComAdapterHooks);
|
||||
|
||||
content.wrappedJSObject.runViewer();
|
||||
viewerWindow.wrappedJSObject.runViewer();
|
||||
});
|
||||
});
|
||||
|
||||
addMessageListener('Shumway:loadFile', function (message) {
|
||||
messageManager.addMessageListener('Shumway:loadFile', function (message) {
|
||||
if (!shumwayComAdapterHooks.onLoadFileCallback) {
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,115 @@
|
|||
/*
|
||||
* Copyright 2015 Mozilla Foundation
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
Components.utils.import('resource://gre/modules/XPCOMUtils.jsm');
|
||||
Components.utils.import('resource://gre/modules/Services.jsm');
|
||||
Components.utils.import('resource://gre/modules/Promise.jsm');
|
||||
|
||||
Components.utils.import('chrome://shumway/content/StartupInfo.jsm');
|
||||
Components.utils.import('chrome://shumway/content/ShumwayCom.jsm');
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, 'PrivateBrowsingUtils',
|
||||
'resource://gre/modules/PrivateBrowsingUtils.jsm');
|
||||
|
||||
function log(str) {
|
||||
var msg = 'plugin.js: ' + str;
|
||||
Services.console.logStringMessage(msg);
|
||||
dump(msg + '\n');
|
||||
}
|
||||
|
||||
function runViewer() {
|
||||
function handlerOOP() {
|
||||
var frameLoader = pluginElement.frameLoader;
|
||||
var messageManager = frameLoader.messageManager;
|
||||
|
||||
var externalInterface;
|
||||
|
||||
messageManager.addMessageListener('Shumway:running', function (message) {
|
||||
externalInterface = message.objects.externalInterface;
|
||||
});
|
||||
|
||||
messageManager.addMessageListener('Shumway:message', function (message) {
|
||||
var data = message.data;
|
||||
var result = shumwayActions.invoke(data.action, data.data);
|
||||
if (message.sync) {
|
||||
return result === undefined ? 'undefined' : JSON.stringify(result);
|
||||
}
|
||||
});
|
||||
|
||||
messageManager.addMessageListener('Shumway:enableDebug', function (message) {
|
||||
enableDebug();
|
||||
});
|
||||
|
||||
shumwayActions.onExternalCallback = function (call) {
|
||||
return externalInterface.callback(JSON.stringify(call));
|
||||
};
|
||||
|
||||
shumwayActions.onLoadFileCallback = function (args) {
|
||||
messageManager.sendAsyncMessage('Shumway:loadFile', args);
|
||||
};
|
||||
|
||||
messageManager.addMessageListener('Shumway:constructed', function (message) {
|
||||
messageManager.sendAsyncMessage('Shumway:init', getEnvironment());
|
||||
});
|
||||
}
|
||||
|
||||
function getEnvironment() {
|
||||
return {
|
||||
swfUrl: startupInfo.url,
|
||||
privateBrowsing: startupInfo.privateBrowsing
|
||||
};
|
||||
}
|
||||
|
||||
function enableDebug() {
|
||||
DebugUtils.enableDebug(startupInfo.url);
|
||||
setTimeout(function () {
|
||||
// TODO fix plugin instance reloading for jsplugins
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
var startupInfo = getStartupInfo(pluginElement);
|
||||
if (!startupInfo.url) {
|
||||
// Special case when movie URL is not specified, e.g. swfobject
|
||||
// checks only version. No need to instantiate the flash plugin.
|
||||
if (startupInfo.embedTag) {
|
||||
setupSimpleExternalInterface(startupInfo.embedTag);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
var document = pluginElement.ownerDocument;
|
||||
var window = document.defaultView;
|
||||
var shumwayActions = ShumwayCom.createActions(startupInfo, window, document);
|
||||
|
||||
handlerOOP();
|
||||
|
||||
// TODO fix remote debugging for jsplugins
|
||||
}
|
||||
|
||||
function setupSimpleExternalInterface(embedTag) {
|
||||
Components.utils.exportFunction(function (variable) {
|
||||
switch (variable) {
|
||||
case '$version':
|
||||
return 'SHUMWAY 10,0,0';
|
||||
default:
|
||||
log('Unsupported GetVariable() call: ' + variable);
|
||||
return undefined;
|
||||
}
|
||||
}, embedTag.wrappedJSObject, {defineAs: 'GetVariable'});
|
||||
}
|
||||
|
||||
runViewer();
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2014 Mozilla Foundation
|
||||
* Copyright 2015 Mozilla Foundation
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
@ -22,6 +22,7 @@ const PREF_PREFIX = 'shumway.';
|
|||
const PREF_IGNORE_CTP = PREF_PREFIX + 'ignoreCTP';
|
||||
const PREF_WHITELIST = PREF_PREFIX + 'swf.whitelist';
|
||||
const SWF_CONTENT_TYPE = 'application/x-shockwave-flash';
|
||||
const PLUGIN_HANLDER_URI = 'chrome://shumway/content/content.html';
|
||||
|
||||
let Cc = Components.classes;
|
||||
let Ci = Components.interfaces;
|
||||
|
@ -31,10 +32,7 @@ let Cu = Components.utils;
|
|||
Cu.import('resource://gre/modules/XPCOMUtils.jsm');
|
||||
Cu.import('resource://gre/modules/Services.jsm');
|
||||
|
||||
Cu.import('resource://shumway/ShumwayStreamConverter.jsm');
|
||||
|
||||
let Ph = Cc["@mozilla.org/plugin/host;1"].getService(Ci.nsIPluginHost);
|
||||
let registerOverlayPreview = 'registerPlayPreviewMimeType' in Ph;
|
||||
|
||||
function getBoolPref(pref, def) {
|
||||
try {
|
||||
|
@ -55,6 +53,7 @@ function getStringPref(pref, def) {
|
|||
function log(str) {
|
||||
var msg = 'ShumwayBootstrapUtils.jsm: ' + str;
|
||||
Services.console.logStringMessage(msg);
|
||||
dump(msg + '\n');
|
||||
}
|
||||
|
||||
// Register/unregister a constructor as a factory.
|
||||
|
@ -78,9 +77,6 @@ Factory.prototype = {
|
|||
}
|
||||
};
|
||||
|
||||
let converterFactory = new Factory();
|
||||
let overlayConverterFactory = new Factory();
|
||||
|
||||
function allowedPlatformForMedia() {
|
||||
var oscpu = Cc["@mozilla.org/network/protocol;1?name=http"]
|
||||
.getService(Ci.nsIHttpProtocolHandler).oscpu;
|
||||
|
@ -96,6 +92,7 @@ function allowedPlatformForMedia() {
|
|||
|
||||
var ShumwayBootstrapUtils = {
|
||||
isRegistered: false,
|
||||
isJSPluginsSupported: false,
|
||||
|
||||
register: function () {
|
||||
if (this.isRegistered) {
|
||||
|
@ -105,22 +102,51 @@ var ShumwayBootstrapUtils = {
|
|||
this.isRegistered = true;
|
||||
|
||||
// Register the components.
|
||||
converterFactory.register(ShumwayStreamConverter);
|
||||
overlayConverterFactory.register(ShumwayStreamOverlayConverter);
|
||||
this.isJSPluginsSupported = !!Ph.registerFakePlugin &&
|
||||
getBoolPref('shumway.jsplugins', false);
|
||||
|
||||
if (registerOverlayPreview) {
|
||||
var ignoreCTP = getBoolPref(PREF_IGNORE_CTP, true);
|
||||
var whitelist = getStringPref(PREF_WHITELIST);
|
||||
// Some platforms cannot support video playback, and our whitelist targets
|
||||
// only video players atm. We need to disable Shumway for those platforms.
|
||||
if (whitelist && !Services.prefs.prefHasUserValue(PREF_WHITELIST) &&
|
||||
!allowedPlatformForMedia()) {
|
||||
log('Default SWF whitelist is used on an unsupported platform -- ' +
|
||||
'using demo whitelist.');
|
||||
whitelist = 'http://www.areweflashyet.com/*.swf';
|
||||
if (this.isJSPluginsSupported) {
|
||||
let initPluginDict = {
|
||||
handlerURI: PLUGIN_HANLDER_URI,
|
||||
mimeEntries: [
|
||||
{
|
||||
type: SWF_CONTENT_TYPE,
|
||||
description: 'Shockwave Flash',
|
||||
extension: 'swf'
|
||||
}
|
||||
],
|
||||
niceName: 'Shumway plugin',
|
||||
name: 'Shumway',
|
||||
supersedeExisting: true, // TODO verify when jsplugins (bug 558184) is implemented
|
||||
sandboxScript: 'chrome://shumway/content/plugin.js', // TODO verify when jsplugins (bug 558184) is implemented
|
||||
version: '10.0.0.0'
|
||||
};
|
||||
Ph.registerFakePlugin(initPluginDict);
|
||||
} else {
|
||||
Cu.import('resource://shumway/ShumwayStreamConverter.jsm');
|
||||
|
||||
let converterFactory = new Factory();
|
||||
converterFactory.register(ShumwayStreamConverter);
|
||||
this.converterFactory = converterFactory;
|
||||
let overlayConverterFactory = new Factory();
|
||||
overlayConverterFactory.register(ShumwayStreamOverlayConverter);
|
||||
this.overlayConverterFactory = overlayConverterFactory;
|
||||
|
||||
let registerOverlayPreview = 'registerPlayPreviewMimeType' in Ph;
|
||||
if (registerOverlayPreview) {
|
||||
var ignoreCTP = getBoolPref(PREF_IGNORE_CTP, true);
|
||||
var whitelist = getStringPref(PREF_WHITELIST);
|
||||
// Some platforms cannot support video playback, and our whitelist targets
|
||||
// only video players atm. We need to disable Shumway for those platforms.
|
||||
if (whitelist && !Services.prefs.prefHasUserValue(PREF_WHITELIST) && !allowedPlatformForMedia()) {
|
||||
log('Default SWF whitelist is used on an unsupported platform -- ' +
|
||||
'using demo whitelist.');
|
||||
whitelist = 'http://www.areweflashyet.com/*.swf';
|
||||
}
|
||||
Ph.registerPlayPreviewMimeType(SWF_CONTENT_TYPE, ignoreCTP,
|
||||
undefined, whitelist);
|
||||
}
|
||||
Ph.registerPlayPreviewMimeType(SWF_CONTENT_TYPE, ignoreCTP,
|
||||
undefined, whitelist);
|
||||
this.registerOverlayPreview = registerOverlayPreview;
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -132,11 +158,17 @@ var ShumwayBootstrapUtils = {
|
|||
this.isRegistered = false;
|
||||
|
||||
// Remove the contract/component.
|
||||
converterFactory.unregister();
|
||||
overlayConverterFactory.unregister();
|
||||
if (this.isJSPluginsSupported) {
|
||||
Ph.unregisterFakePlugin(PLUGIN_HANLDER_URI);
|
||||
} else {
|
||||
this.converterFactory.unregister();
|
||||
this.converterFactory = null;
|
||||
this.overlayConverterFactory.unregister();
|
||||
this.overlayConverterFactory = null;
|
||||
|
||||
if (registerOverlayPreview) {
|
||||
Ph.unregisterPlayPreviewMimeType(SWF_CONTENT_TYPE);
|
||||
if (this.registerOverlayPreview) {
|
||||
Ph.unregisterPlayPreviewMimeType(SWF_CONTENT_TYPE);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2013 Mozilla Foundation
|
||||
* Copyright 2015 Mozilla Foundation
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
@ -39,6 +39,8 @@ XPCOMUtils.defineLazyModuleGetter(this, 'PrivateBrowsingUtils',
|
|||
XPCOMUtils.defineLazyModuleGetter(this, 'ShumwayTelemetry',
|
||||
'resource://shumway/ShumwayTelemetry.jsm');
|
||||
|
||||
Components.utils.import('chrome://shumway/content/StartupInfo.jsm');
|
||||
|
||||
function getBoolPref(pref, def) {
|
||||
try {
|
||||
return Services.prefs.getBoolPref(pref);
|
||||
|
@ -60,31 +62,6 @@ function getDOMWindow(aChannel) {
|
|||
return win;
|
||||
}
|
||||
|
||||
function parseQueryString(qs) {
|
||||
if (!qs)
|
||||
return {};
|
||||
|
||||
if (qs.charAt(0) == '?')
|
||||
qs = qs.slice(1);
|
||||
|
||||
var values = qs.split('&');
|
||||
var obj = {};
|
||||
for (var i = 0; i < values.length; i++) {
|
||||
var kv = values[i].split('=');
|
||||
var key = kv[0], value = kv[1];
|
||||
obj[decodeURIComponent(key)] = decodeURIComponent(value);
|
||||
}
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
function isContentWindowPrivate(win) {
|
||||
if (!('isContentWindowPrivate' in PrivateBrowsingUtils)) {
|
||||
return PrivateBrowsingUtils.isWindowPrivate(win);
|
||||
}
|
||||
return PrivateBrowsingUtils.isContentWindowPrivate(win);
|
||||
}
|
||||
|
||||
function isShumwayEnabledFor(startupInfo) {
|
||||
// disabled for PrivateBrowsing windows
|
||||
if (isContentWindowPrivate(startupInfo.window) &&
|
||||
|
@ -224,13 +201,10 @@ ShumwayStreamConverterBase.prototype = {
|
|||
return requestUrl.spec;
|
||||
},
|
||||
|
||||
getStartupInfo: function(window, urlHint) {
|
||||
var url = urlHint;
|
||||
var baseUrl;
|
||||
var pageUrl;
|
||||
getStartupInfo: function(window, url) {
|
||||
var initStartTime = Date.now();
|
||||
var element = window.frameElement;
|
||||
var isOverlay = false;
|
||||
var objectParams = {};
|
||||
if (element) {
|
||||
// PlayPreview overlay "belongs" to the embed/object tag and consists of
|
||||
// DIV and IFRAME. Starting from IFRAME and looking for first object tag.
|
||||
|
@ -263,43 +237,14 @@ ShumwayStreamConverterBase.prototype = {
|
|||
}
|
||||
|
||||
if (element) {
|
||||
// Getting absolute URL from the EMBED tag
|
||||
url = element.srcURI && element.srcURI.spec;
|
||||
|
||||
pageUrl = element.ownerDocument.location.href; // proper page url?
|
||||
|
||||
if (tagName == 'EMBED') {
|
||||
for (var i = 0; i < element.attributes.length; ++i) {
|
||||
var paramName = element.attributes[i].localName.toLowerCase();
|
||||
objectParams[paramName] = element.attributes[i].value;
|
||||
}
|
||||
} else {
|
||||
for (var i = 0; i < element.childNodes.length; ++i) {
|
||||
var paramElement = element.childNodes[i];
|
||||
if (paramElement.nodeType != 1 ||
|
||||
paramElement.nodeName != 'PARAM') {
|
||||
continue;
|
||||
}
|
||||
var paramName = paramElement.getAttribute('name').toLowerCase();
|
||||
objectParams[paramName] = paramElement.getAttribute('value');
|
||||
}
|
||||
}
|
||||
return getStartupInfo(element);
|
||||
}
|
||||
|
||||
baseUrl = pageUrl;
|
||||
if (objectParams.base) {
|
||||
try {
|
||||
// Verifying base URL, passed in object parameters. It shall be okay to
|
||||
// ignore bad/corrupted base.
|
||||
var parsedPageUrl = Services.io.newURI(pageUrl);
|
||||
baseUrl = Services.io.newURI(objectParams.base, null, parsedPageUrl).spec;
|
||||
} catch (e) { /* it's okay to ignore any exception */ }
|
||||
}
|
||||
// Stream converter is used in top level window, just providing basic
|
||||
// information about SWF.
|
||||
|
||||
var objectParams = {};
|
||||
var movieParams = {};
|
||||
if (objectParams.flashvars) {
|
||||
movieParams = parseQueryString(objectParams.flashvars);
|
||||
}
|
||||
var queryStringMatch = url && /\?([^#]+)/.exec(url);
|
||||
if (queryStringMatch) {
|
||||
var queryStringParams = parseQueryString(queryStringMatch[1]);
|
||||
|
@ -310,21 +255,22 @@ ShumwayStreamConverterBase.prototype = {
|
|||
}
|
||||
}
|
||||
|
||||
var allowScriptAccess = !!url &&
|
||||
isScriptAllowed(objectParams.allowscriptaccess, url, pageUrl);
|
||||
|
||||
// Using the same data structure as we return in StartupInfo.jsm and
|
||||
// assigning constant values for fields that is not applicable for
|
||||
// the stream converter when it is used in a top level window.
|
||||
var startupInfo = {};
|
||||
startupInfo.window = window;
|
||||
startupInfo.url = url;
|
||||
startupInfo.privateBrowsing = isContentWindowPrivate(window);
|
||||
startupInfo.objectParams = objectParams;
|
||||
startupInfo.movieParams = movieParams;
|
||||
startupInfo.baseUrl = baseUrl || url;
|
||||
startupInfo.isOverlay = isOverlay;
|
||||
startupInfo.embedTag = element;
|
||||
startupInfo.isPausedAtStart = /\bpaused=true$/.test(urlHint);
|
||||
startupInfo.allowScriptAccess = allowScriptAccess;
|
||||
startupInfo.pageIndex = 0;
|
||||
startupInfo.baseUrl = url;
|
||||
startupInfo.isOverlay = false;
|
||||
startupInfo.refererUrl = null;
|
||||
startupInfo.embedTag = null;
|
||||
startupInfo.isPausedAtStart = /\bpaused=true$/.test(url);
|
||||
startupInfo.initStartTime = initStartTime;
|
||||
startupInfo.allowScriptAccess = false;
|
||||
return startupInfo;
|
||||
},
|
||||
|
||||
|
@ -387,8 +333,7 @@ ShumwayStreamConverterBase.prototype = {
|
|||
aRequest.cancel(Cr.NS_BINDING_ABORTED);
|
||||
|
||||
var domWindow = getDOMWindow(channel);
|
||||
let startupInfo = converter.getStartupInfo(domWindow,
|
||||
converter.getUrlHint(originalURI));
|
||||
let startupInfo = converter.getStartupInfo(domWindow, converter.getUrlHint(originalURI));
|
||||
|
||||
listener.onStopRequest(aRequest, context, statusCode);
|
||||
|
||||
|
@ -446,32 +391,6 @@ function setupSimpleExternalInterface(embedTag) {
|
|||
}, embedTag.wrappedJSObject, {defineAs: 'GetVariable'});
|
||||
}
|
||||
|
||||
function isScriptAllowed(allowScriptAccessParameter, url, pageUrl) {
|
||||
if (!allowScriptAccessParameter) {
|
||||
allowScriptAccessParameter = 'sameDomain';
|
||||
}
|
||||
var allowScriptAccess = false;
|
||||
switch (allowScriptAccessParameter.toLowerCase()) { // ignoring case here
|
||||
case 'always':
|
||||
allowScriptAccess = true;
|
||||
break;
|
||||
case 'never':
|
||||
allowScriptAccess = false;
|
||||
break;
|
||||
default: // 'samedomain'
|
||||
if (!pageUrl)
|
||||
break;
|
||||
try {
|
||||
// checking if page is in same domain (? same protocol and port)
|
||||
allowScriptAccess =
|
||||
Services.io.newURI('/', null, Services.io.newURI(pageUrl, null, null)).spec ==
|
||||
Services.io.newURI('/', null, Services.io.newURI(url, null, null)).spec;
|
||||
} catch (ex) {}
|
||||
break;
|
||||
}
|
||||
return allowScriptAccess;
|
||||
}
|
||||
|
||||
// properties required for XPCOM registration:
|
||||
function copyProperties(obj, template) {
|
||||
for (var prop in template) {
|
||||
|
|
|
@ -64,6 +64,10 @@ this.ShumwayTelemetry = {
|
|||
var histogram = Services.telemetry.getHistogramById("SHUMWAY_FEATURE_USED");
|
||||
histogram.add(featureType);
|
||||
},
|
||||
onLoadResource: function (resultType) {
|
||||
var histogram = Services.telemetry.getHistogramById("SHUMWAY_LOAD_RESOURCE_RESULT");
|
||||
histogram.add(resultType);
|
||||
},
|
||||
onFallback: function (userAction) {
|
||||
var histogram = Services.telemetry.getHistogramById("SHUMWAY_FALLBACK");
|
||||
histogram.add(userAction);
|
||||
|
|
Двоичные данные
browser/extensions/shumway/content/libs/builtin.abc
Двоичные данные
browser/extensions/shumway/content/libs/builtin.abc
Двоичный файл не отображается.
Двоичный файл не отображается.
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
Различия файлов скрыты, потому что одна или несколько строк слишком длинны
Различия файлов скрыты, потому что одна или несколько строк слишком длинны
|
@ -1,2 +1,2 @@
|
|||
0.10.346
|
||||
84cafb5
|
||||
0.11.422
|
||||
137ba70
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head lang="en">
|
||||
<meta charset="UTF-8">
|
||||
<title></title>
|
||||
<style>
|
||||
html, body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
overflow: hidden;
|
||||
background-color: transparent;
|
||||
line-height: 0;
|
||||
}
|
||||
|
||||
#easelContainer {
|
||||
position:fixed !important;
|
||||
left:0;top:0;bottom:0;right:0;
|
||||
overflow: hidden;
|
||||
line-height: 0;
|
||||
}
|
||||
</style>
|
||||
<script src='resource://shumway/shumway.gfx.js'></script>
|
||||
</head>
|
||||
<body contextmenu="shumwayMenu">
|
||||
<div id="easelContainer"></div>
|
||||
<menu type="context" id="shumwayMenu">
|
||||
<menuitem label="Show URL" id="showURLMenu"></menuitem>
|
||||
<menuitem label="Open in Inspector" id="inspectorMenu"></menuitem>
|
||||
<menuitem label="Report Problems" id="reportMenu"></menuitem>
|
||||
<menuitem label="Reload in Adobe Flash Player" id="fallbackMenu" hidden></menuitem>
|
||||
<menuitem label="Debug this SWF" id="debugMenu"></menuitem>
|
||||
<menuitem label="About Shumway %version%..." id="aboutMenu"></menuitem>
|
||||
</menu>
|
||||
<script src="viewerGfx.js"></script>
|
||||
</body>
|
||||
</html>
|
|
@ -27,19 +27,16 @@ limitations under the License.
|
|||
background-color: transparent;
|
||||
}
|
||||
|
||||
body.started {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
body.started iframe {
|
||||
body.started #playerIframe {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#easelContainer {
|
||||
#gfxIframe {
|
||||
position:fixed !important;
|
||||
left:0;top:0;bottom:0;right:0;
|
||||
left:0; top:0;
|
||||
width: 100%; height: 100%;
|
||||
overflow: hidden;
|
||||
line-height: 0;
|
||||
border: 0 none;
|
||||
}
|
||||
|
||||
#overlay {
|
||||
|
@ -84,7 +81,7 @@ limitations under the License.
|
|||
background-color: black;
|
||||
}
|
||||
|
||||
#playerWindow {
|
||||
#playerIframe {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
|
@ -98,25 +95,16 @@ limitations under the License.
|
|||
</style>
|
||||
</head>
|
||||
|
||||
<body contextmenu="shumwayMenu">
|
||||
<iframe id="playerWindow" width="9" height="9" src="" sandbox="allow-scripts"></iframe>
|
||||
<div id="easelContainer"></div>
|
||||
<body>
|
||||
<iframe id="playerIframe" width="9" height="9" src="" sandbox="allow-scripts"></iframe>
|
||||
<iframe id="gfxIframe" src="" sandbox="allow-scripts"></iframe>
|
||||
<section>
|
||||
<div id="overlay">
|
||||
<a id="fallback" href="#">Shumway <span class="icon">×</span></a>
|
||||
<a id="report" href="#">Report Problems</a>
|
||||
</div>
|
||||
<menu type="context" id="shumwayMenu">
|
||||
<menuitem label="Show URL" id="showURLMenu"></menuitem>
|
||||
<menuitem label="Open in Inspector" id="inspectorMenu"></menuitem>
|
||||
<menuitem label="Report Problems" id="reportMenu"></menuitem>
|
||||
<menuitem label="Reload in Adobe Flash Player" id="fallbackMenu" hidden></menuitem>
|
||||
<menuitem label="Debug this SWF" id="debugMenu"></menuitem>
|
||||
<menuitem label="About Shumway %version%..." id="aboutMenu"></menuitem>
|
||||
</menu>
|
||||
</section>
|
||||
|
||||
<script src='resource://shumway/shumway.gfx.js'></script>
|
||||
<script src='resource://shumway/web/viewer.js'></script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2013 Mozilla Foundation
|
||||
* Copyright 2015 Mozilla Foundation
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
@ -14,34 +14,7 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
function notifyUserInput() {
|
||||
ShumwayCom.userInput();
|
||||
}
|
||||
|
||||
document.addEventListener('mousedown', notifyUserInput, true);
|
||||
document.addEventListener('mouseup', notifyUserInput, true);
|
||||
document.addEventListener('keydown', notifyUserInput, true);
|
||||
document.addEventListener('keyup', notifyUserInput, true);
|
||||
|
||||
function fallback() {
|
||||
ShumwayCom.fallback();
|
||||
}
|
||||
|
||||
window.print = function(msg) {
|
||||
console.log(msg);
|
||||
};
|
||||
|
||||
var SHUMWAY_ROOT = "resource://shumway/";
|
||||
|
||||
var playerWindow;
|
||||
var playerWindowLoaded = new Promise(function(resolve) {
|
||||
var playerWindowIframe = document.getElementById("playerWindow");
|
||||
playerWindowIframe.addEventListener('load', function () {
|
||||
playerWindow = playerWindowIframe.contentWindow;
|
||||
resolve(playerWindowIframe);
|
||||
});
|
||||
playerWindowIframe.src = 'resource://shumway/web/viewer.player.html';
|
||||
});
|
||||
var movieUrl, movieParams;
|
||||
|
||||
function runViewer() {
|
||||
var flashParams = ShumwayCom.getPluginParams();
|
||||
|
@ -53,13 +26,12 @@ function runViewer() {
|
|||
}
|
||||
|
||||
movieParams = flashParams.movieParams;
|
||||
objectParams = flashParams.objectParams;
|
||||
var objectParams = flashParams.objectParams;
|
||||
var baseUrl = flashParams.baseUrl;
|
||||
var isOverlay = flashParams.isOverlay;
|
||||
pauseExecution = flashParams.isPausedAtStart;
|
||||
var isDebuggerEnabled = flashParams.isDebuggerEnabled;
|
||||
var initStartTime = flashParams.initStartTime;
|
||||
|
||||
console.log("url=" + movieUrl + ";params=" + uneval(movieParams));
|
||||
if (movieParams.fmt_list && movieParams.url_encoded_fmt_stream_map) {
|
||||
// HACK removing FLVs from the fmt_list
|
||||
movieParams.fmt_list = movieParams.fmt_list.split(',').filter(function (s) {
|
||||
|
@ -68,41 +40,103 @@ function runViewer() {
|
|||
}).join(',');
|
||||
}
|
||||
|
||||
playerWindowLoaded.then(function (playerWindowIframe) {
|
||||
ShumwayCom.setupComBridge(playerWindowIframe);
|
||||
parseSwf(movieUrl, baseUrl, movieParams, objectParams);
|
||||
var backgroundColor;
|
||||
if (objectParams) {
|
||||
var m;
|
||||
if (objectParams.bgcolor && (m = /#([0-9A-F]{6})/i.exec(objectParams.bgcolor))) {
|
||||
var hexColor = parseInt(m[1], 16);
|
||||
backgroundColor = hexColor << 8 | 0xff;
|
||||
}
|
||||
if (objectParams.wmode === 'transparent') {
|
||||
backgroundColor = 0;
|
||||
}
|
||||
}
|
||||
|
||||
playerReady.then(function () {
|
||||
var settings = ShumwayCom.getSettings();
|
||||
var playerSettings = settings.playerSettings;
|
||||
|
||||
ShumwayCom.setupPlayerComBridge(document.getElementById('playerIframe'));
|
||||
parseSwf(movieUrl, baseUrl, movieParams, objectParams, settings, initStartTime, backgroundColor);
|
||||
|
||||
if (isOverlay) {
|
||||
if (isDebuggerEnabled) {
|
||||
document.getElementById('overlay').className = 'enabled';
|
||||
var fallbackDiv = document.getElementById('fallback');
|
||||
fallbackDiv.addEventListener('click', function (e) {
|
||||
fallback();
|
||||
e.preventDefault();
|
||||
});
|
||||
var reportDiv = document.getElementById('report');
|
||||
reportDiv.addEventListener('click', function (e) {
|
||||
reportIssue();
|
||||
e.preventDefault();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
ShumwayCom.setupGfxComBridge(document.getElementById('gfxIframe'));
|
||||
gfxWindow.postMessage({
|
||||
type: 'prepareUI',
|
||||
params: {
|
||||
isOverlay: isOverlay,
|
||||
isDebuggerEnabled: isDebuggerEnabled,
|
||||
isHudOn: playerSettings.hud,
|
||||
backgroundColor: backgroundColor
|
||||
}
|
||||
}, '*')
|
||||
});
|
||||
}
|
||||
|
||||
if (isOverlay) {
|
||||
document.getElementById('overlay').className = 'enabled';
|
||||
var fallbackDiv = document.getElementById('fallback');
|
||||
fallbackDiv.addEventListener('click', function(e) {
|
||||
fallback();
|
||||
e.preventDefault();
|
||||
});
|
||||
var reportDiv = document.getElementById('report');
|
||||
reportDiv.addEventListener('click', function(e) {
|
||||
reportIssue();
|
||||
e.preventDefault();
|
||||
});
|
||||
var fallbackMenu = document.getElementById('fallbackMenu');
|
||||
fallbackMenu.removeAttribute('hidden');
|
||||
fallbackMenu.addEventListener('click', fallback);
|
||||
window.addEventListener("message", function handlerMessage(e) {
|
||||
var args = e.data;
|
||||
if (typeof args !== 'object' || args === null) {
|
||||
return;
|
||||
}
|
||||
document.getElementById('showURLMenu').addEventListener('click', showURL);
|
||||
document.getElementById('inspectorMenu').addEventListener('click', showInInspector);
|
||||
document.getElementById('reportMenu').addEventListener('click', reportIssue);
|
||||
document.getElementById('aboutMenu').addEventListener('click', showAbout);
|
||||
|
||||
var version = Shumway.version || '';
|
||||
document.getElementById('aboutMenu').label =
|
||||
document.getElementById('aboutMenu').label.replace('%version%', version);
|
||||
|
||||
if (isDebuggerEnabled) {
|
||||
document.getElementById('debugMenu').addEventListener('click', enableDebug);
|
||||
} else {
|
||||
document.getElementById('debugMenu').remove();
|
||||
if (gfxWindow && e.source === gfxWindow) {
|
||||
switch (args.callback) {
|
||||
case 'displayParameters':
|
||||
// The display parameters data will be send to the player window.
|
||||
// TODO do we need sanitize it?
|
||||
displayParametersResolved(args.params);
|
||||
break;
|
||||
case 'showURL':
|
||||
showURL();
|
||||
break;
|
||||
case 'showInInspector':
|
||||
showInInspector();
|
||||
break;
|
||||
case 'reportIssue':
|
||||
reportIssue();
|
||||
break;
|
||||
case 'showAbout':
|
||||
showAbout();
|
||||
break;
|
||||
case 'enableDebug':
|
||||
enableDebug();
|
||||
break;
|
||||
case 'fallback':
|
||||
fallback();
|
||||
break;
|
||||
default:
|
||||
console.error('Unexpected message from gfx frame: ' + args.callback);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (playerWindow && e.source === playerWindow) {
|
||||
switch (args.callback) {
|
||||
case 'started':
|
||||
document.body.classList.add('started');
|
||||
break;
|
||||
default:
|
||||
console.error('Unexpected message from player frame: ' + args.callback);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}, true);
|
||||
|
||||
function fallback() {
|
||||
ShumwayCom.fallback();
|
||||
}
|
||||
|
||||
function showURL() {
|
||||
|
@ -147,72 +181,54 @@ function enableDebug() {
|
|||
ShumwayCom.enableDebug();
|
||||
}
|
||||
|
||||
var movieUrl, movieParams, objectParams;
|
||||
var playerWindow, gfxWindow;
|
||||
|
||||
window.addEventListener("message", function handlerMessage(e) {
|
||||
var args = e.data;
|
||||
switch (args.callback) {
|
||||
case 'started':
|
||||
document.body.classList.add('started');
|
||||
break;
|
||||
}
|
||||
}, true);
|
||||
|
||||
var easelHost;
|
||||
|
||||
function parseSwf(url, baseUrl, movieParams, objectParams) {
|
||||
var settings = ShumwayCom.getSettings();
|
||||
function parseSwf(url, baseUrl, movieParams, objectParams, settings,
|
||||
initStartTime, backgroundColor) {
|
||||
var compilerSettings = settings.compilerSettings;
|
||||
var playerSettings = settings.playerSettings;
|
||||
|
||||
// init misc preferences
|
||||
Shumway.GFX.hud.value = playerSettings.hud;
|
||||
//forceHidpi.value = settings.playerSettings.forceHidpi;
|
||||
displayParametersReady.then(function (displayParameters) {
|
||||
var data = {
|
||||
type: 'runSwf',
|
||||
flashParams: {
|
||||
compilerSettings: compilerSettings,
|
||||
movieParams: movieParams,
|
||||
objectParams: objectParams,
|
||||
displayParameters: displayParameters,
|
||||
turboMode: playerSettings.turboMode,
|
||||
env: playerSettings.env,
|
||||
bgcolor: backgroundColor,
|
||||
url: url,
|
||||
baseUrl: baseUrl || url,
|
||||
initStartTime: initStartTime
|
||||
}
|
||||
};
|
||||
playerWindow.postMessage(data, '*');
|
||||
});
|
||||
}
|
||||
|
||||
console.info("Compiler settings: " + JSON.stringify(compilerSettings));
|
||||
console.info("Parsing " + url + "...");
|
||||
// We need to wait for gfx window to report display parameters before we
|
||||
// start SWF playback in the player window.
|
||||
var displayParametersResolved;
|
||||
var displayParametersReady = new Promise(function (resolve) {
|
||||
displayParametersResolved = resolve;
|
||||
});
|
||||
|
||||
var backgroundColor;
|
||||
if (objectParams) {
|
||||
var m;
|
||||
if (objectParams.bgcolor && (m = /#([0-9A-F]{6})/i.exec(objectParams.bgcolor))) {
|
||||
var hexColor = parseInt(m[1], 16);
|
||||
backgroundColor = hexColor << 8 | 0xff;
|
||||
}
|
||||
if (objectParams.wmode === 'transparent') {
|
||||
backgroundColor = 0;
|
||||
var playerReady = new Promise(function (resolve) {
|
||||
function iframeLoaded() {
|
||||
if (--iframesToLoad > 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
gfxWindow = document.getElementById('gfxIframe').contentWindow;
|
||||
playerWindow = document.getElementById('playerIframe').contentWindow;
|
||||
resolve();
|
||||
}
|
||||
|
||||
var easel = createEasel(backgroundColor);
|
||||
easelHost = new Shumway.GFX.Window.WindowEaselHost(easel, playerWindow, window);
|
||||
|
||||
var displayParameters = easel.getDisplayParameters();
|
||||
var data = {
|
||||
type: 'runSwf',
|
||||
settings: Shumway.Settings.getSettings(),
|
||||
flashParams: {
|
||||
compilerSettings: compilerSettings,
|
||||
movieParams: movieParams,
|
||||
objectParams: objectParams,
|
||||
displayParameters: displayParameters,
|
||||
turboMode: playerSettings.turboMode,
|
||||
env: playerSettings.env,
|
||||
bgcolor: backgroundColor,
|
||||
url: url,
|
||||
baseUrl: baseUrl || url
|
||||
}
|
||||
};
|
||||
playerWindow.postMessage(data, '*');
|
||||
}
|
||||
|
||||
function createEasel(backgroundColor) {
|
||||
var Stage = Shumway.GFX.Stage;
|
||||
var Easel = Shumway.GFX.Easel;
|
||||
var Canvas2DRenderer = Shumway.GFX.Canvas2DRenderer;
|
||||
|
||||
Shumway.GFX.WebGL.SHADER_ROOT = SHUMWAY_ROOT + "gfx/gl/shaders/";
|
||||
var easel = new Easel(document.getElementById("easelContainer"), false, backgroundColor);
|
||||
easel.startRendering();
|
||||
return easel;
|
||||
}
|
||||
var iframesToLoad = 2;
|
||||
document.getElementById('gfxIframe').addEventListener('load', iframeLoaded);
|
||||
document.getElementById('gfxIframe').src = 'resource://shumway/web/viewer.gfx.html';
|
||||
document.getElementById('playerIframe').addEventListener('load', iframeLoaded);
|
||||
document.getElementById('playerIframe').src = 'resource://shumway/web/viewer.player.html';
|
||||
});
|
||||
|
|
|
@ -0,0 +1,122 @@
|
|||
/*
|
||||
* Copyright 2013 Mozilla Foundation
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
var SHUMWAY_ROOT = "resource://shumway/";
|
||||
|
||||
var easel;
|
||||
function createEasel(backgroundColor) {
|
||||
var Stage = Shumway.GFX.Stage;
|
||||
var Easel = Shumway.GFX.Easel;
|
||||
var Canvas2DRenderer = Shumway.GFX.Canvas2DRenderer;
|
||||
|
||||
Shumway.GFX.WebGL.SHADER_ROOT = SHUMWAY_ROOT + "gfx/gl/shaders/";
|
||||
easel = new Easel(document.getElementById("easelContainer"), false, backgroundColor);
|
||||
|
||||
if (ShumwayCom.environment === 'test') {
|
||||
ShumwayCom.setScreenShotCallback(function () {
|
||||
// flush rendering buffers
|
||||
easel.render();
|
||||
return easel.screenShot(null, true, false).dataURL;
|
||||
});
|
||||
}
|
||||
|
||||
easel.startRendering();
|
||||
return easel;
|
||||
}
|
||||
|
||||
var easelHost;
|
||||
function createEaselHost() {
|
||||
var peer = new Shumway.Remoting.ShumwayComTransportPeer();
|
||||
easelHost = new Shumway.GFX.Window.WindowEaselHost(easel, peer);
|
||||
return easelHost;
|
||||
}
|
||||
|
||||
function setHudVisible(visible) {
|
||||
Shumway.GFX.hud.value = !!visible;
|
||||
}
|
||||
|
||||
function fallback() {
|
||||
parent.postMessage({callback: 'fallback'}, '*');
|
||||
}
|
||||
|
||||
function showURL() {
|
||||
parent.postMessage({callback: 'showURL'}, '*' );
|
||||
}
|
||||
|
||||
function showInInspector() {
|
||||
parent.postMessage({callback: 'showInInspector'}, '*');
|
||||
}
|
||||
|
||||
function reportIssue() {
|
||||
parent.postMessage({callback: 'reportIssue'}, '*');
|
||||
}
|
||||
|
||||
function showAbout() {
|
||||
parent.postMessage({callback: 'showAbout'}, '*');
|
||||
}
|
||||
|
||||
function enableDebug() {
|
||||
parent.postMessage({callback: 'enableDebug'}, '*');
|
||||
}
|
||||
|
||||
function prepareUI(params) {
|
||||
if (params.isOverlay) {
|
||||
var fallbackMenu = document.getElementById('fallbackMenu');
|
||||
fallbackMenu.removeAttribute('hidden');
|
||||
fallbackMenu.addEventListener('click', fallback);
|
||||
}
|
||||
document.getElementById('showURLMenu').addEventListener('click', showURL);
|
||||
document.getElementById('inspectorMenu').addEventListener('click', showInInspector);
|
||||
document.getElementById('reportMenu').addEventListener('click', reportIssue);
|
||||
document.getElementById('aboutMenu').addEventListener('click', showAbout);
|
||||
|
||||
var version = Shumway.version || '';
|
||||
document.getElementById('aboutMenu').label =
|
||||
document.getElementById('aboutMenu').label.replace('%version%', version);
|
||||
|
||||
if (params.isDebuggerEnabled) {
|
||||
document.getElementById('debugMenu').addEventListener('click', enableDebug);
|
||||
} else {
|
||||
document.getElementById('debugMenu').remove();
|
||||
}
|
||||
|
||||
setHudVisible(params.isHudOn);
|
||||
|
||||
createEasel(params.backgroundColor);
|
||||
createEaselHost();
|
||||
|
||||
var displayParameters = easel.getDisplayParameters();
|
||||
window.parent.postMessage({
|
||||
callback: 'displayParameters',
|
||||
params: displayParameters
|
||||
}, '*');
|
||||
}
|
||||
|
||||
window.addEventListener('message', function onWindowMessage(e) {
|
||||
var data = e.data;
|
||||
if (typeof data !== 'object' || data === null) {
|
||||
console.error('Unexpected message for gfx frame.');
|
||||
return;
|
||||
}
|
||||
switch (data.type) {
|
||||
case "prepareUI":
|
||||
prepareUI(data.params);
|
||||
break;
|
||||
default:
|
||||
console.error('Unexpected message for gfx frame: ' + args.callback);
|
||||
break;
|
||||
}
|
||||
}, true);
|
|
@ -14,38 +14,52 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
var release = true;
|
||||
|
||||
window.print = function(msg) {
|
||||
console.log(msg);
|
||||
};
|
||||
|
||||
function runSwfPlayer(flashParams) {
|
||||
var EXECUTION_MODE = Shumway.AVM2.Runtime.ExecutionMode;
|
||||
function runSwfPlayer(flashParams, settings) {
|
||||
console.info('Time from init start to SWF player start: ' + (Date.now() - flashParams.initStartTime));
|
||||
if (settings) {
|
||||
Shumway.Settings.setSettings(settings);
|
||||
}
|
||||
setupServices();
|
||||
|
||||
var compilerSettings = flashParams.compilerSettings;
|
||||
var sysMode = compilerSettings.sysCompiler ? EXECUTION_MODE.COMPILE : EXECUTION_MODE.INTERPRET;
|
||||
var appMode = compilerSettings.appCompiler ? EXECUTION_MODE.COMPILE : EXECUTION_MODE.INTERPRET;
|
||||
var asyncLoading = true;
|
||||
var baseUrl = flashParams.baseUrl;
|
||||
var objectParams = flashParams.objectParams;
|
||||
var movieUrl = flashParams.url;
|
||||
|
||||
Shumway.frameRateOption.value = flashParams.turboMode ? 60 : -1;
|
||||
Shumway.AVM2.Verifier.enabled.value = compilerSettings.verifier;
|
||||
if (ShumwayCom.environment === 'test') {
|
||||
Shumway.frameRateOption.value = 60;
|
||||
Shumway.dontSkipFramesOption.value = true;
|
||||
|
||||
window.print = function(msg) {
|
||||
ShumwayCom.print(msg.toString());
|
||||
};
|
||||
|
||||
Shumway.Random.reset();
|
||||
Shumway.installTimeWarper();
|
||||
} else {
|
||||
Shumway.frameRateOption.value = flashParams.turboMode ? 60 : -1;
|
||||
}
|
||||
|
||||
Shumway.createAVM2(Shumway.AVM2LoadLibrariesFlags.Builtin | Shumway.AVM2LoadLibrariesFlags.Playerglobal, sysMode, appMode).then(function (avm2) {
|
||||
Shumway.createSecurityDomain(Shumway.AVM2LoadLibrariesFlags.Builtin | Shumway.AVM2LoadLibrariesFlags.Playerglobal).then(function (securityDomain) {
|
||||
function runSWF(file, buffer, baseUrl) {
|
||||
var gfxService = new Shumway.Player.Window.WindowGFXService(window, window.parent);
|
||||
var player = new Shumway.Player.Player(gfxService, flashParams.env);
|
||||
var peer = new Shumway.Remoting.ShumwayComTransportPeer();
|
||||
var gfxService = new Shumway.Player.Window.WindowGFXService(securityDomain, peer);
|
||||
var player = new Shumway.Player.Player(securityDomain, gfxService, flashParams.env);
|
||||
player.defaultStageColor = flashParams.bgcolor;
|
||||
player.movieParams = flashParams.movieParams;
|
||||
player.stageAlign = (objectParams && (objectParams.salign || objectParams.align)) || '';
|
||||
player.stageScale = (objectParams && objectParams.scale) || 'showall';
|
||||
player.displayParameters = flashParams.displayParameters;
|
||||
player.initStartTime = flashParams.initStartTime;
|
||||
|
||||
player.pageUrl = baseUrl;
|
||||
console.info('Time from init start to SWF loading start: ' + (Date.now() - flashParams.initStartTime));
|
||||
player.load(file, buffer);
|
||||
playerStarted();
|
||||
}
|
||||
|
||||
Shumway.FileLoadingService.instance.init(baseUrl);
|
||||
|
@ -68,11 +82,20 @@ function setupServices() {
|
|||
Shumway.ClipboardService.instance = new Shumway.Player.ShumwayComClipboardService();
|
||||
Shumway.FileLoadingService.instance = new Shumway.Player.ShumwayComFileLoadingService();
|
||||
Shumway.SystemResourcesLoadingService.instance = new Shumway.Player.ShumwayComResourcesLoadingService(true);
|
||||
Shumway.LocalConnectionService.instance = new Shumway.Player.ShumwayComLocalConnectionService();
|
||||
}
|
||||
|
||||
function playerStarted() {
|
||||
document.body.style.backgroundColor = 'green';
|
||||
window.parent.postMessage({
|
||||
callback: 'started'
|
||||
}, '*');
|
||||
}
|
||||
|
||||
window.addEventListener('message', function onWindowMessage(e) {
|
||||
var data = e.data;
|
||||
if (typeof data !== 'object' || data === null) {
|
||||
console.error('Unexpected message for player frame.');
|
||||
return;
|
||||
}
|
||||
switch (data.type) {
|
||||
|
@ -81,12 +104,10 @@ window.addEventListener('message', function onWindowMessage(e) {
|
|||
Shumway.Settings.setSettings(data.settings);
|
||||
}
|
||||
setupServices();
|
||||
runSwfPlayer(data.flashParams);
|
||||
|
||||
document.body.style.backgroundColor = 'green';
|
||||
window.parent.postMessage({
|
||||
callback: 'started'
|
||||
}, '*');
|
||||
runSwfPlayer(data.flashParams, data.settings);
|
||||
break;
|
||||
default:
|
||||
console.error('Unexpected message for player frame: ' + args.callback);
|
||||
break;
|
||||
}
|
||||
}, true);
|
||||
|
|
|
@ -41,6 +41,7 @@ const PREF_FHR_UPLOAD_ENABLED = "datareporting.healthreport.uploadEnabled";
|
|||
const PREF_SESSIONS_BRANCH = "datareporting.sessions.";
|
||||
const PREF_UNIFIED = PREF_BRANCH + "unified";
|
||||
const PREF_UNIFIED_OPTIN = PREF_BRANCH + "unifiedIsOptIn";
|
||||
const PREF_OPTOUT_SAMPLE = PREF_BRANCH + "optoutSample";
|
||||
|
||||
// Whether the FHR/Telemetry unification features are enabled.
|
||||
// Changing this pref requires a restart.
|
||||
|
@ -90,6 +91,29 @@ XPCOMUtils.defineLazyModuleGetter(this, "TelemetrySend",
|
|||
XPCOMUtils.defineLazyModuleGetter(this, "TelemetryReportingPolicy",
|
||||
"resource://gre/modules/TelemetryReportingPolicy.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyGetter(this, "gCrcTable", function() {
|
||||
let c;
|
||||
let table = [];
|
||||
for (let n = 0; n < 256; n++) {
|
||||
c = n;
|
||||
for (let k =0; k < 8; k++) {
|
||||
c = ((c&1) ? (0xEDB88320 ^ (c >>> 1)) : (c >>> 1));
|
||||
}
|
||||
table[n] = c;
|
||||
}
|
||||
return table;
|
||||
});
|
||||
|
||||
function crc32(str) {
|
||||
let crc = 0 ^ (-1);
|
||||
|
||||
for (let i = 0; i < str.length; i++ ) {
|
||||
crc = (crc >>> 8) ^ gCrcTable[(crc ^ str.charCodeAt(i)) & 0xFF];
|
||||
}
|
||||
|
||||
return (crc ^ (-1)) >>> 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup Telemetry logging. This function also gets called when loggin related
|
||||
* preferences change.
|
||||
|
@ -129,6 +153,8 @@ function configureLogging() {
|
|||
let Policy = {
|
||||
now: () => new Date(),
|
||||
generatePingId: () => Utils.generateUUID(),
|
||||
getCachedClientID: () => ClientID.getCachedClientID(),
|
||||
isUnifiedOptin: () => IS_UNIFIED_OPTIN,
|
||||
}
|
||||
|
||||
this.EXPORTED_SYMBOLS = ["TelemetryController"];
|
||||
|
@ -304,6 +330,15 @@ this.TelemetryController = Object.freeze({
|
|||
return Impl.clientID;
|
||||
},
|
||||
|
||||
/**
|
||||
* Whether this client is part of a sample that gets opt-out Telemetry.
|
||||
*
|
||||
* @return {Boolean} Whether the client is part of the opt-out sample.
|
||||
*/
|
||||
get isInOptoutSample() {
|
||||
return Impl.isInOptoutSample;
|
||||
},
|
||||
|
||||
/**
|
||||
* The AsyncShutdown.Barrier to synchronize with TelemetryController shutdown.
|
||||
*/
|
||||
|
@ -587,6 +622,34 @@ let Impl = {
|
|||
return TelemetryStorage.removeAbortedSessionPing();
|
||||
},
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
_isInOptoutSample: function() {
|
||||
if (!Preferences.get(PREF_OPTOUT_SAMPLE, false)) {
|
||||
this._log.config("_sampleForOptoutTelemetry - optout sampling is disabled");
|
||||
return false;
|
||||
}
|
||||
|
||||
const clientId = Policy.getCachedClientID();
|
||||
if (!clientId) {
|
||||
this._log.config("_sampleForOptoutTelemetry - no cached client id available")
|
||||
return false;
|
||||
}
|
||||
|
||||
// This mimics the server-side 1% sampling, so that we can get matching populations.
|
||||
// The server samples on ((crc32(clientId) % 100) == 42), we match 42+X here to get
|
||||
// a bigger sample.
|
||||
const sample = crc32(clientId) % 100;
|
||||
const offset = 42;
|
||||
const range = 5; // sampling from 5%
|
||||
|
||||
const optout = (sample >= offset && sample < (offset + range));
|
||||
this._log.config("_sampleForOptoutTelemetry - sampling for optout Telemetry - " +
|
||||
"offset: " + offset + ", range: " + range + ", sample: " + sample);
|
||||
return optout;
|
||||
},
|
||||
|
||||
/**
|
||||
* Perform telemetry initialization for either chrome or content process.
|
||||
* @return {Boolean} True if Telemetry is allowed to record at least base (FHR) data,
|
||||
|
@ -605,9 +668,11 @@ let Impl = {
|
|||
|
||||
// Configure base Telemetry recording.
|
||||
// Unified Telemetry makes it opt-out unless the unifedOptin pref is set.
|
||||
// Additionally, we make Telemetry opt-out for a 5% sample.
|
||||
// If extended Telemetry is enabled, base recording is always on as well.
|
||||
const enabled = Preferences.get(PREF_ENABLED, false);
|
||||
Telemetry.canRecordBase = enabled || (IS_UNIFIED_TELEMETRY && !IS_UNIFIED_OPTIN);
|
||||
const isOptout = IS_UNIFIED_TELEMETRY && (!Policy.isUnifiedOptin() || this._isInOptoutSample());
|
||||
Telemetry.canRecordBase = enabled || isOptout;
|
||||
|
||||
#ifdef MOZILLA_OFFICIAL
|
||||
// Enable extended telemetry if:
|
||||
|
@ -678,7 +743,7 @@ let Impl = {
|
|||
// id from disk.
|
||||
// We try to cache it in prefs to avoid this, even though this may
|
||||
// lead to some stale client ids.
|
||||
this._clientID = Preferences.get(PREF_CACHED_CLIENTID, null);
|
||||
this._clientID = ClientID.getCachedClientID();
|
||||
|
||||
// Delay full telemetry initialization to give the browser time to
|
||||
// run various late initializers. Otherwise our gathered memory
|
||||
|
@ -691,9 +756,8 @@ let Impl = {
|
|||
|
||||
yield TelemetrySend.setup(this._testMode);
|
||||
|
||||
// Load the ClientID and update the cache.
|
||||
// Load the ClientID.
|
||||
this._clientID = yield ClientID.getClientID();
|
||||
Preferences.set(PREF_CACHED_CLIENTID, this._clientID);
|
||||
|
||||
// Purge the pings archive by removing outdated pings. We don't wait for this
|
||||
// task to complete, but TelemetryStorage blocks on it during shutdown.
|
||||
|
@ -816,6 +880,10 @@ let Impl = {
|
|||
return this._clientID;
|
||||
},
|
||||
|
||||
get isInOptoutSample() {
|
||||
return this._isInOptoutSample();
|
||||
},
|
||||
|
||||
/**
|
||||
* Get an object describing the current state of this module for AsyncShutdown diagnostics.
|
||||
*/
|
||||
|
|
|
@ -19,6 +19,7 @@ Cu.import("resource://gre/modules/PromiseUtils.jsm");
|
|||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
Cu.import("resource://gre/modules/TelemetryUtils.jsm", this);
|
||||
Cu.import("resource://gre/modules/ObjectUtils.jsm");
|
||||
Cu.import("resource://gre/modules/TelemetryController.jsm", this);
|
||||
|
||||
const Utils = TelemetryUtils;
|
||||
|
||||
|
@ -1002,6 +1003,7 @@ EnvironmentCache.prototype = {
|
|||
#endif
|
||||
e10sEnabled: Services.appinfo.browserTabsRemoteAutostart,
|
||||
telemetryEnabled: Preferences.get(PREF_TELEMETRY_ENABLED, false),
|
||||
isInOptoutSample: TelemetryController.isInOptoutSample,
|
||||
locale: getBrowserLocale(),
|
||||
update: {
|
||||
channel: updateChannel,
|
||||
|
|
|
@ -724,14 +724,6 @@ this.TelemetrySession = Object.freeze({
|
|||
observe: function (aSubject, aTopic, aData) {
|
||||
return Impl.observe(aSubject, aTopic, aData);
|
||||
},
|
||||
/**
|
||||
* The client id send with the telemetry ping.
|
||||
*
|
||||
* @return The client id as string, or null.
|
||||
*/
|
||||
get clientID() {
|
||||
return Impl.clientID;
|
||||
},
|
||||
});
|
||||
|
||||
let Impl = {
|
||||
|
|
|
@ -42,6 +42,7 @@ Structure::
|
|||
},
|
||||
e10sEnabled: <bool>, // whether e10s is on, i.e. browser tabs open by default in a different process
|
||||
telemetryEnabled: <bool>, // false on failure
|
||||
isInOptoutSample: <bool>, // whether this client is part of the opt-out sample
|
||||
locale: <string>, // e.g. "it", null on failure
|
||||
update: {
|
||||
channel: <string>, // e.g. "release", null on failure
|
||||
|
|
|
@ -274,6 +274,16 @@ function fakeGeneratePingId(func) {
|
|||
module.Policy.generatePingId = func;
|
||||
}
|
||||
|
||||
function fakeCachedClientId(uuid) {
|
||||
let module = Cu.import("resource://gre/modules/TelemetryController.jsm");
|
||||
module.Policy.getCachedClientID = () => uuid;
|
||||
}
|
||||
|
||||
function fakeIsUnifiedOptin(isOptin) {
|
||||
let module = Cu.import("resource://gre/modules/TelemetryController.jsm");
|
||||
module.Policy.isUnifiedOptin = () => isOptin;
|
||||
}
|
||||
|
||||
// Return a date that is |offset| ms in the future from |date|.
|
||||
function futureDate(date, offset) {
|
||||
return new Date(date.getTime() + offset);
|
||||
|
|
|
@ -33,6 +33,7 @@ const PREF_ARCHIVE_ENABLED = PREF_BRANCH + "archive.enabled";
|
|||
const PREF_FHR_UPLOAD_ENABLED = "datareporting.healthreport.uploadEnabled";
|
||||
const PREF_FHR_SERVICE_ENABLED = "datareporting.healthreport.service.enabled";
|
||||
const PREF_UNIFIED = PREF_BRANCH + "unified";
|
||||
const PREF_OPTOUT_SAMPLE = PREF_BRANCH + "optoutSample";
|
||||
|
||||
let gClientID = null;
|
||||
|
||||
|
@ -368,6 +369,64 @@ add_task(function* test_changePingAfterSubmission() {
|
|||
"The payload must not be changed after being submitted.");
|
||||
});
|
||||
|
||||
add_task(function* test_optoutSampling() {
|
||||
if (!Preferences.get(PREF_UNIFIED, false)) {
|
||||
dump("Unified Telemetry is disabled, skipping.\n");
|
||||
return;
|
||||
}
|
||||
|
||||
const DATA = [
|
||||
{uuid: null, sampled: false}, // not to be sampled
|
||||
{uuid: "3d38d821-14a4-3d45-ab0b-02a9fb5a7505", sampled: false}, // samples to 0
|
||||
{uuid: "1331255e-7eb5-aa4f-b04e-494a0c6da282", sampled: false}, // samples to 41
|
||||
{uuid: "35393e78-a363-ea4e-9fc9-9f9abbee2077", sampled: true }, // samples to 42
|
||||
{uuid: "4dc81df6-db03-a34e-ba79-3e877afd22c4", sampled: true }, // samples to 43
|
||||
{uuid: "79e15be6-4884-8d4f-98e5-f94790251e5f", sampled: true }, // samples to 44
|
||||
{uuid: "c3841566-e39e-384d-826f-508ab6387b21", sampled: true }, // samples to 45
|
||||
{uuid: "cc7498a4-2cde-da47-89b3-f3ce5dd7c6fc", sampled: true }, // samples to 46
|
||||
{uuid: "0750d8ed-5969-3a4f-90ba-2e85f9074309", sampled: false}, // samples to 47
|
||||
{uuid: "0dfcbce7-d82b-b144-8d77-eb15935c9a8e", sampled: false}, // samples to 99
|
||||
];
|
||||
|
||||
// Test that the opt-out pref enables us sampling on 5% of release.
|
||||
Preferences.set(PREF_ENABLED, false);
|
||||
Preferences.set(PREF_OPTOUT_SAMPLE, true);
|
||||
fakeIsUnifiedOptin(true);
|
||||
|
||||
for (let d of DATA) {
|
||||
dump("Testing sampling for uuid: " + d.uuid + "\n");
|
||||
fakeCachedClientId(d.uuid);
|
||||
yield TelemetryController.reset();
|
||||
Assert.equal(TelemetryController.isInOptoutSample, d.sampled,
|
||||
"Opt-out sampling should behave as expected");
|
||||
Assert.equal(Telemetry.canRecordBase, d.sampled,
|
||||
"Base recording setting should be correct");
|
||||
}
|
||||
|
||||
// If we disable opt-out sampling Telemetry, have the opt-in setting on and extended Telemetry off,
|
||||
// we should not enable anything.
|
||||
Preferences.set(PREF_OPTOUT_SAMPLE, false);
|
||||
fakeIsUnifiedOptin(true);
|
||||
for (let d of DATA) {
|
||||
dump("Testing sampling for uuid: " + d.uuid + "\n");
|
||||
fakeCachedClientId(d.uuid);
|
||||
yield TelemetryController.reset();
|
||||
Assert.equal(Telemetry.canRecordBase, false,
|
||||
"Sampling should not override the default opt-out behavior");
|
||||
}
|
||||
|
||||
// If we fully enable opt-out Telemetry on release, the sampling should not override that.
|
||||
Preferences.set(PREF_OPTOUT_SAMPLE, true);
|
||||
fakeIsUnifiedOptin(false);
|
||||
for (let d of DATA) {
|
||||
dump("Testing sampling for uuid: " + d.uuid + "\n");
|
||||
fakeCachedClientId(d.uuid);
|
||||
yield TelemetryController.reset();
|
||||
Assert.equal(Telemetry.canRecordBase, true,
|
||||
"Sampling should not override the default opt-out behavior");
|
||||
}
|
||||
});
|
||||
|
||||
add_task(function* stopServer(){
|
||||
yield PingServer.stop();
|
||||
do_test_finished();
|
||||
|
|
|
@ -255,6 +255,7 @@ function checkSettingsSection(data) {
|
|||
blocklistEnabled: "boolean",
|
||||
e10sEnabled: "boolean",
|
||||
telemetryEnabled: "boolean",
|
||||
isInOptoutSample: "boolean",
|
||||
locale: "string",
|
||||
update: "object",
|
||||
userPrefs: "object",
|
||||
|
|
|
@ -11,6 +11,7 @@ const {classes: Cc, interfaces: Ci, results: Cr, utils: Cu} = Components;
|
|||
Cu.import("resource://gre/modules/osfile.jsm");
|
||||
Cu.import("resource://gre/modules/Task.jsm");
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
Cu.import("resource://gre/modules/Preferences.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "CommonUtils",
|
||||
"resource://services-common/utils.js");
|
||||
|
@ -23,6 +24,8 @@ XPCOMUtils.defineLazyGetter(this, "gStateFilePath", () => {
|
|||
return OS.Path.join(gDatareportingPath, "state.json");
|
||||
});
|
||||
|
||||
const PREF_CACHED_CLIENTID = "toolkit.telemetry.cachedClientID";
|
||||
|
||||
this.ClientID = Object.freeze({
|
||||
/**
|
||||
* This returns a promise resolving to the the stable client ID we use for
|
||||
|
@ -35,6 +38,17 @@ this.ClientID = Object.freeze({
|
|||
return ClientIDImpl.getClientID();
|
||||
},
|
||||
|
||||
/**
|
||||
* Get the client id synchronously without hitting the disk.
|
||||
* This returns:
|
||||
* - the current on-disk client id if it was already loaded
|
||||
* - the client id that we cached into preferences (if any)
|
||||
* - null otherwise
|
||||
*/
|
||||
getCachedClientID: function() {
|
||||
return ClientIDImpl.getCachedClientID();
|
||||
},
|
||||
|
||||
/**
|
||||
* Only used for testing. Invalidates the client ID so that it gets read
|
||||
* again from file.
|
||||
|
@ -71,6 +85,7 @@ let ClientIDImpl = {
|
|||
let state = yield CommonUtils.readJSON(gStateFilePath);
|
||||
if (state && 'clientID' in state && typeof(state.clientID) == 'string') {
|
||||
this._clientID = state.clientID;
|
||||
Preferences.set(PREF_CACHED_CLIENTID, this._clientID);
|
||||
return this._clientID;
|
||||
}
|
||||
} catch (e) {
|
||||
|
@ -83,6 +98,7 @@ let ClientIDImpl = {
|
|||
let state = yield CommonUtils.readJSON(fhrStatePath);
|
||||
if (state && 'clientID' in state && typeof(state.clientID) == 'string') {
|
||||
this._clientID = state.clientID;
|
||||
Preferences.set(PREF_CACHED_CLIENTID, this._clientID);
|
||||
this._saveClientID();
|
||||
return this._clientID;
|
||||
}
|
||||
|
@ -92,6 +108,7 @@ let ClientIDImpl = {
|
|||
|
||||
// We dont have an id from FHR yet, generate a new ID.
|
||||
this._clientID = CommonUtils.generateUUID();
|
||||
Preferences.set(PREF_CACHED_CLIENTID, this._clientID);
|
||||
this._saveClientIdTask = this._saveClientID();
|
||||
|
||||
// Wait on persisting the id. Otherwise failure to save the ID would result in
|
||||
|
@ -130,6 +147,23 @@ let ClientIDImpl = {
|
|||
return Promise.resolve(this._clientID);
|
||||
},
|
||||
|
||||
/**
|
||||
* Get the client id synchronously without hitting the disk.
|
||||
* This returns:
|
||||
* - the current on-disk client id if it was already loaded
|
||||
* - the client id that we cached into preferences (if any)
|
||||
* - null otherwise
|
||||
*/
|
||||
getCachedClientID: function() {
|
||||
if (this._clientID) {
|
||||
// Already loaded the client id from disk.
|
||||
return this._clientID;
|
||||
}
|
||||
|
||||
// Not yet loaded, return the cached client id if we have one.
|
||||
return Preferences.get(PREF_CACHED_CLIENTID, null);
|
||||
},
|
||||
|
||||
/*
|
||||
* Resets the provider. This is for testing only.
|
||||
*/
|
||||
|
|
Загрузка…
Ссылка в новой задаче