зеркало из https://github.com/mozilla/gecko-dev.git
Merge m-c to b2g-inbound. a=merge
This commit is contained in:
Коммит
6c7a0d5cef
|
@ -192,11 +192,10 @@
|
|||
// update the annotation based on this event, then update the
|
||||
// icon as well
|
||||
this.isMarked = JSON.parse(event.detail).marked;
|
||||
let uri = Services.io.newURI(this.pageData.url, null, null);
|
||||
if (this.isMarked) {
|
||||
Social.markURI(provider.origin, uri);
|
||||
Social.markURI(provider.origin, gBrowser.currentURI);
|
||||
} else {
|
||||
Social.unmarkURI(provider.origin, uri, () => {
|
||||
Social.unmarkURI(provider.origin, gBrowser.currentURI, () => {
|
||||
this.update();
|
||||
});
|
||||
}
|
||||
|
|
|
@ -131,10 +131,15 @@ let PositionHandler = {
|
|||
// Center the window horizontally on the screen (not the available area).
|
||||
// Until we have moved the window to y=0, 'screen.width' may give a value
|
||||
// for a secondary screen, so use values from the screen manager instead.
|
||||
let primaryScreen = Cc["@mozilla.org/gfx/screenmanager;1"]
|
||||
.getService(Ci.nsIScreenManager)
|
||||
.primaryScreen;
|
||||
let width = {};
|
||||
Cc["@mozilla.org/gfx/screenmanager;1"].getService(Ci.nsIScreenManager)
|
||||
.primaryScreen.GetRectDisplayPix({}, {}, width, {});
|
||||
window.moveTo((width.value - document.documentElement.clientWidth) / 2, 0);
|
||||
primaryScreen.GetRectDisplayPix({}, {}, width, {});
|
||||
let availTop = {};
|
||||
primaryScreen.GetAvailRectDisplayPix({}, availTop, {}, {});
|
||||
window.moveTo((width.value - document.documentElement.clientWidth) / 2,
|
||||
availTop.value);
|
||||
} else {
|
||||
// This will ensure we're at y=0.
|
||||
this.setXPosition(window.screenX);
|
||||
|
@ -145,7 +150,7 @@ let PositionHandler = {
|
|||
let desiredX = Math.max(desiredX, screen.availLeft);
|
||||
let maxX =
|
||||
screen.availLeft + screen.availWidth - document.documentElement.clientWidth;
|
||||
window.moveTo(Math.min(desiredX, maxX), 0);
|
||||
window.moveTo(Math.min(desiredX, maxX), screen.availTop);
|
||||
},
|
||||
handleEvent: function(aEvent) {
|
||||
switch (aEvent.type) {
|
||||
|
|
|
@ -9,6 +9,7 @@ support-files =
|
|||
doc_destroy-nodes.html
|
||||
doc_connect-toggle.html
|
||||
doc_connect-param.html
|
||||
doc_connect-multi-param.html
|
||||
440hz_sine.ogg
|
||||
head.js
|
||||
|
||||
|
@ -33,6 +34,7 @@ support-files =
|
|||
[browser_wa_graph-render-01.js]
|
||||
[browser_wa_graph-render-02.js]
|
||||
[browser_wa_graph-render-03.js]
|
||||
[browser_wa_graph-render-04.js]
|
||||
[browser_wa_graph-markers.js]
|
||||
[browser_wa_graph-selected.js]
|
||||
[browser_wa_graph-zoom.js]
|
||||
|
|
|
@ -0,0 +1,40 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
/**
|
||||
* Tests audio param connection rendering.
|
||||
*/
|
||||
|
||||
function spawnTest() {
|
||||
let [target, debuggee, panel] = yield initWebAudioEditor(CONNECT_MULTI_PARAM_URL);
|
||||
let { panelWin } = panel;
|
||||
let { gFront, $, $$, EVENTS } = panelWin;
|
||||
|
||||
let started = once(gFront, "start-context");
|
||||
|
||||
reload(target);
|
||||
|
||||
let [actors] = yield Promise.all([
|
||||
getN(gFront, "create-node", 5),
|
||||
waitForGraphRendered(panelWin, 5, 2, 3)
|
||||
]);
|
||||
|
||||
let nodeIDs = actors.map(actor => actor.actorID);
|
||||
|
||||
let [, carrier, gain, mod1, mod2] = nodeIDs;
|
||||
|
||||
let edges = [
|
||||
[mod1, gain, "gain", "mod1 -> gain[gain]"],
|
||||
[mod2, carrier, "frequency", "mod2 -> carrier[frequency]"],
|
||||
[mod2, carrier, "detune", "mod2 -> carrier[detune]"]
|
||||
];
|
||||
|
||||
edges.forEach(([source, target, param, msg], i) => {
|
||||
let edge = findGraphEdge(panelWin, source, target, param);
|
||||
ok(edge.classList.contains("param-connection"), "edge is classified as a param-connection");
|
||||
});
|
||||
|
||||
yield teardown(panel);
|
||||
finish();
|
||||
}
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
<!-- Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ -->
|
||||
<!doctype html>
|
||||
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
<title>Web Audio Editor test page</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<script type="text/javascript;version=1.8">
|
||||
"use strict";
|
||||
|
||||
let ctx = new AudioContext();
|
||||
let carrier = ctx.createOscillator();
|
||||
let gain = ctx.createGain();
|
||||
let modulator = ctx.createOscillator();
|
||||
let modulator2 = ctx.createOscillator();
|
||||
carrier.connect(gain);
|
||||
gain.connect(ctx.destination);
|
||||
modulator.connect(gain.gain);
|
||||
modulator2.connect(carrier.frequency);
|
||||
modulator2.connect(carrier.detune);
|
||||
modulator.start(0);
|
||||
modulator2.start(0);
|
||||
carrier.start(0);
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
|
@ -29,6 +29,7 @@ const BUFFER_AND_ARRAY_URL = EXAMPLE_URL + "doc_buffer-and-array.html";
|
|||
const DESTROY_NODES_URL = EXAMPLE_URL + "doc_destroy-nodes.html";
|
||||
const CONNECT_TOGGLE_URL = EXAMPLE_URL + "doc_connect-toggle.html";
|
||||
const CONNECT_PARAM_URL = EXAMPLE_URL + "doc_connect-param.html";
|
||||
const CONNECT_MULTI_PARAM_URL = EXAMPLE_URL + "doc_connect-multi-param.html";
|
||||
|
||||
// All tests are asynchronous.
|
||||
waitForExplicitFinish();
|
||||
|
@ -204,11 +205,15 @@ function getNSpread (front, eventName, count) { return getN(front, eventName, co
|
|||
* resolves when the graph was rendered with the correct count of
|
||||
* nodes and edges.
|
||||
*/
|
||||
function waitForGraphRendered (front, nodeCount, edgeCount) {
|
||||
function waitForGraphRendered (front, nodeCount, edgeCount, paramEdgeCount) {
|
||||
let deferred = Promise.defer();
|
||||
let eventName = front.EVENTS.UI_GRAPH_RENDERED;
|
||||
front.on(eventName, function onGraphRendered (_, nodes, edges) {
|
||||
if (nodes === nodeCount && edges === edgeCount) {
|
||||
front.on(eventName, function onGraphRendered (_, nodes, edges, pEdges) {
|
||||
info(nodes);
|
||||
info(edges)
|
||||
info(pEdges);
|
||||
let paramEdgesDone = paramEdgeCount ? paramEdgeCount === pEdges : true;
|
||||
if (nodes === nodeCount && edges === edgeCount && paramEdgesDone) {
|
||||
front.off(eventName, onGraphRendered);
|
||||
deferred.resolve();
|
||||
}
|
||||
|
@ -290,8 +295,11 @@ function modifyVariableView (win, view, index, prop, value) {
|
|||
return deferred.promise;
|
||||
}
|
||||
|
||||
function findGraphEdge (win, source, target) {
|
||||
function findGraphEdge (win, source, target, param) {
|
||||
let selector = ".edgePaths .edgePath[data-source='" + source + "'][data-target='" + target + "']";
|
||||
if (param) {
|
||||
selector += "[data-param='" + param + "']";
|
||||
}
|
||||
return win.document.querySelector(selector);
|
||||
}
|
||||
|
||||
|
|
|
@ -64,7 +64,8 @@ const EVENTS = {
|
|||
|
||||
// When the Audio Context graph finishes rendering.
|
||||
// Is called with two arguments, first representing number of nodes
|
||||
// rendered, second being the number of edges rendered.
|
||||
// rendered, second being the number of edge connections rendering (not counting
|
||||
// param edges), followed by the count of the param edges rendered.
|
||||
UI_GRAPH_RENDERED: "WebAudioEditor:UIGraphRendered"
|
||||
};
|
||||
|
||||
|
@ -77,8 +78,8 @@ let gToolbox, gTarget, gFront;
|
|||
* Track an array of audio nodes
|
||||
*/
|
||||
let AudioNodes = [];
|
||||
let AudioNodeConnections = new WeakMap();
|
||||
|
||||
let AudioNodeConnections = new WeakMap(); // <AudioNodeView, Set<AudioNodeView>>
|
||||
let AudioParamConnections = new WeakMap(); // <AudioNodeView, Object>
|
||||
|
||||
// Light representation wrapping an AudioNode actor with additional properties
|
||||
function AudioNodeView (actor) {
|
||||
|
@ -109,9 +110,27 @@ AudioNodeView.prototype.connect = function (destination) {
|
|||
return false;
|
||||
};
|
||||
|
||||
// Helper method to create connections in the AudioNodeConnections
|
||||
// WeakMap for rendering. Returns a boolean indicating
|
||||
// if the connection was successfully created. Will return `false`
|
||||
// when the connection was previously made.
|
||||
AudioNodeView.prototype.connectParam = function (destination, param) {
|
||||
let connections = AudioParamConnections.get(this) || {};
|
||||
AudioParamConnections.set(this, connections);
|
||||
|
||||
let params = connections[destination.id] = connections[destination.id] || [];
|
||||
|
||||
if (!~params.indexOf(param)) {
|
||||
params.push(param);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
// Helper method to remove audio connections from the current AudioNodeView
|
||||
AudioNodeView.prototype.disconnect = function () {
|
||||
AudioNodeConnections.set(this, new Set());
|
||||
AudioParamConnections.set(this, {});
|
||||
};
|
||||
|
||||
// Returns a promise that resolves to an array of objects containing
|
||||
|
@ -159,6 +178,7 @@ let WebAudioEditorController = {
|
|||
gFront.on("start-context", this._onStartContext);
|
||||
gFront.on("create-node", this._onCreateNode);
|
||||
gFront.on("connect-node", this._onConnectNode);
|
||||
gFront.on("connect-param", this._onConnectParam);
|
||||
gFront.on("disconnect-node", this._onDisconnectNode);
|
||||
gFront.on("change-param", this._onChangeParam);
|
||||
gFront.on("destroy-node", this._onDestroyNode);
|
||||
|
@ -173,6 +193,7 @@ let WebAudioEditorController = {
|
|||
window.on(EVENTS.CONNECT_NODE, this._onUpdatedContext);
|
||||
window.on(EVENTS.DISCONNECT_NODE, this._onUpdatedContext);
|
||||
window.on(EVENTS.DESTROY_NODE, this._onUpdatedContext);
|
||||
window.on(EVENTS.CONNECT_PARAM, this._onUpdatedContext);
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -185,6 +206,7 @@ let WebAudioEditorController = {
|
|||
gFront.off("start-context", this._onStartContext);
|
||||
gFront.off("create-node", this._onCreateNode);
|
||||
gFront.off("connect-node", this._onConnectNode);
|
||||
gFront.off("connect-param", this._onConnectParam);
|
||||
gFront.off("disconnect-node", this._onDisconnectNode);
|
||||
gFront.off("change-param", this._onChangeParam);
|
||||
gFront.off("destroy-node", this._onDestroyNode);
|
||||
|
@ -192,6 +214,7 @@ let WebAudioEditorController = {
|
|||
window.off(EVENTS.CONNECT_NODE, this._onUpdatedContext);
|
||||
window.off(EVENTS.DISCONNECT_NODE, this._onUpdatedContext);
|
||||
window.off(EVENTS.DESTROY_NODE, this._onUpdatedContext);
|
||||
window.off(EVENTS.CONNECT_PARAM, this._onUpdatedContext);
|
||||
gDevTools.off("pref-changed", this._onThemeChange);
|
||||
},
|
||||
|
||||
|
@ -291,38 +314,22 @@ let WebAudioEditorController = {
|
|||
* Called when a node is connected to another node.
|
||||
*/
|
||||
_onConnectNode: Task.async(function* ({ source: sourceActor, dest: destActor }) {
|
||||
// Since node create and connect are probably executed back to back,
|
||||
// and the controller's `_onCreateNode` needs to look up type,
|
||||
// the edge creation could be called before the graph node is actually
|
||||
// created. This way, we can check and listen for the event before
|
||||
// adding an edge.
|
||||
let [source, dest] = yield waitForNodeCreation(sourceActor, destActor);
|
||||
|
||||
// Connect nodes, and only emit if it's a new connection.
|
||||
if (source.connect(dest)) {
|
||||
window.emit(EVENTS.CONNECT_NODE, source.id, dest.id);
|
||||
}
|
||||
}),
|
||||
|
||||
function waitForNodeCreation (sourceActor, destActor) {
|
||||
let deferred = defer();
|
||||
let source = getViewNodeByActor(sourceActor);
|
||||
let dest = getViewNodeByActor(destActor);
|
||||
/**
|
||||
* Called when a node is conneceted to another node's AudioParam.
|
||||
*/
|
||||
_onConnectParam: Task.async(function* ({ source: sourceActor, dest: destActor, param }) {
|
||||
let [source, dest] = yield waitForNodeCreation(sourceActor, destActor);
|
||||
|
||||
if (!source || !dest)
|
||||
window.on(EVENTS.CREATE_NODE, function createNodeListener (_, id) {
|
||||
let createdNode = getViewNodeById(id);
|
||||
if (equalActors(sourceActor, createdNode.actor))
|
||||
source = createdNode;
|
||||
if (equalActors(destActor, createdNode.actor))
|
||||
dest = createdNode;
|
||||
if (source && dest) {
|
||||
window.off(EVENTS.CREATE_NODE, createNodeListener);
|
||||
deferred.resolve([source, dest]);
|
||||
}
|
||||
});
|
||||
else
|
||||
deferred.resolve([source, dest]);
|
||||
return deferred.promise;
|
||||
if (source.connectParam(dest, param)) {
|
||||
window.emit(EVENTS.CONNECT_PARAM, source.id, dest.id, param);
|
||||
}
|
||||
}),
|
||||
|
||||
|
@ -379,3 +386,31 @@ function getViewNodeByActor (actor) {
|
|||
function getViewNodeById (id) {
|
||||
return getViewNodeByActor({ actorID: id });
|
||||
}
|
||||
|
||||
// Since node create and connect are probably executed back to back,
|
||||
// and the controller's `_onCreateNode` needs to look up type,
|
||||
// the edge creation could be called before the graph node is actually
|
||||
// created. This way, we can check and listen for the event before
|
||||
// adding an edge.
|
||||
function waitForNodeCreation (sourceActor, destActor) {
|
||||
let deferred = defer();
|
||||
let eventName = EVENTS.CREATE_NODE;
|
||||
let source = getViewNodeByActor(sourceActor);
|
||||
let dest = getViewNodeByActor(destActor);
|
||||
|
||||
if (!source || !dest)
|
||||
window.on(eventName, function createNodeListener (_, id) {
|
||||
let createdNode = getViewNodeById(id);
|
||||
if (equalActors(sourceActor, createdNode.actor))
|
||||
source = createdNode;
|
||||
if (equalActors(destActor, createdNode.actor))
|
||||
dest = createdNode;
|
||||
if (source && dest) {
|
||||
window.off(eventName, createNodeListener);
|
||||
deferred.resolve([source, dest]);
|
||||
}
|
||||
});
|
||||
else
|
||||
deferred.resolve([source, dest]);
|
||||
return deferred.promise;
|
||||
}
|
||||
|
|
|
@ -144,14 +144,18 @@ let WebAudioGraphView = {
|
|||
|
||||
/**
|
||||
* `draw` renders the ViewNodes currently available in `AudioNodes` with `AudioNodeConnections`,
|
||||
* and is throttled to be called at most every `GRAPH_DEBOUNCE_TIMER` milliseconds. Is called
|
||||
* whenever the audio context routing changes, after being debounced.
|
||||
* and `AudioParamConnections` and is throttled to be called at most every
|
||||
* `GRAPH_DEBOUNCE_TIMER` milliseconds. Is called whenever the audio context routing changes,
|
||||
* after being debounced.
|
||||
*/
|
||||
draw: function () {
|
||||
// Clear out previous SVG information
|
||||
this.clearGraph();
|
||||
|
||||
let graph = new dagreD3.Digraph();
|
||||
// An array of duples/tuples of pairs [sourceNode, destNode, param].
|
||||
// `param` is optional, indicating a connection to an AudioParam, rather than
|
||||
// an other AudioNode.
|
||||
let edges = [];
|
||||
|
||||
AudioNodes.forEach(node => {
|
||||
|
@ -166,12 +170,30 @@ let WebAudioGraphView = {
|
|||
// after all the nodes are added, otherwise edges will attempted to be created
|
||||
// for nodes that have not yet been added
|
||||
AudioNodeConnections.get(node, new Set()).forEach(dest => edges.push([node, dest]));
|
||||
let paramConnections = AudioParamConnections.get(node, {});
|
||||
Object.keys(paramConnections).forEach(destId => {
|
||||
let dest = getViewNodeById(destId);
|
||||
let connections = paramConnections[destId] || [];
|
||||
connections.forEach(param => edges.push([node, dest, param]));
|
||||
});
|
||||
});
|
||||
|
||||
edges.forEach(([node, dest]) => graph.addEdge(null, node.id, dest.id, {
|
||||
source: node.id,
|
||||
target: dest.id
|
||||
}));
|
||||
edges.forEach(([node, dest, param]) => {
|
||||
let options = {
|
||||
source: node.id,
|
||||
target: dest.id
|
||||
};
|
||||
|
||||
// Only add `label` if `param` specified, as this is an AudioParam connection then.
|
||||
// `label` adds the magic to render with dagre-d3, and `param` is just more explicitly
|
||||
// the param, ignoring implementation details.
|
||||
if (param) {
|
||||
options.label = param;
|
||||
options.param = param;
|
||||
}
|
||||
|
||||
graph.addEdge(null, node.id, dest.id, options);
|
||||
});
|
||||
|
||||
let renderer = new dagreD3.Renderer();
|
||||
|
||||
|
@ -191,18 +213,33 @@ let WebAudioGraphView = {
|
|||
});
|
||||
|
||||
// Post-render manipulation of edges
|
||||
// TODO do all of this more efficiently, rather than
|
||||
// using the direct D3 helper utilities to loop over each
|
||||
// edge several times
|
||||
let oldDrawEdgePaths = renderer.drawEdgePaths();
|
||||
renderer.drawEdgePaths(function(graph, root) {
|
||||
let svgNodes = oldDrawEdgePaths(graph, root);
|
||||
svgNodes.attr("data-source", (n) => {
|
||||
let svgEdges = oldDrawEdgePaths(graph, root);
|
||||
svgEdges.attr("data-source", (n) => {
|
||||
let edge = graph.edge(n);
|
||||
return edge.source;
|
||||
});
|
||||
svgNodes.attr("data-target", (n) => {
|
||||
svgEdges.attr("data-target", (n) => {
|
||||
let edge = graph.edge(n);
|
||||
return edge.target;
|
||||
});
|
||||
return svgNodes;
|
||||
svgEdges.attr("data-param", (n) => {
|
||||
let edge = graph.edge(n);
|
||||
return edge.param ? edge.param : null;
|
||||
});
|
||||
// We have to manually specify the default classes on the edges
|
||||
// as to not overwrite them
|
||||
let defaultClasses = "edgePath enter";
|
||||
svgEdges.attr("class", (n) => {
|
||||
let edge = graph.edge(n);
|
||||
return defaultClasses + (edge.param ? (" param-connection " + edge.param) : "");
|
||||
});
|
||||
|
||||
return svgEdges;
|
||||
});
|
||||
|
||||
// Override Dagre-d3's post render function by passing in our own.
|
||||
|
@ -240,7 +277,8 @@ let WebAudioGraphView = {
|
|||
}
|
||||
|
||||
// Fire an event upon completed rendering
|
||||
window.emit(EVENTS.UI_GRAPH_RENDERED, AudioNodes.length, edges.length);
|
||||
let paramEdgeCount = edges.filter(p => !!p[2]).length;
|
||||
window.emit(EVENTS.UI_GRAPH_RENDERED, AudioNodes.length, edges.length - paramEdgeCount, paramEdgeCount);
|
||||
});
|
||||
|
||||
let layout = dagreD3.layout().rankDir("LR");
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
This is the pdf.js project output, https://github.com/mozilla/pdf.js
|
||||
|
||||
Current extension version is: 1.0.645
|
||||
Current extension version is: 1.0.712
|
||||
|
||||
|
|
|
@ -22,8 +22,8 @@ if (typeof PDFJS === 'undefined') {
|
|||
(typeof window !== 'undefined' ? window : this).PDFJS = {};
|
||||
}
|
||||
|
||||
PDFJS.version = '1.0.645';
|
||||
PDFJS.build = '66bfb9c';
|
||||
PDFJS.version = '1.0.712';
|
||||
PDFJS.build = '6969ed4';
|
||||
|
||||
(function pdfjsWrapper() {
|
||||
// Use strict in our context only - users might not want it
|
||||
|
@ -2239,7 +2239,7 @@ var WorkerTransport = (function WorkerTransportClosure() {
|
|||
messageHandler.on('PageError', function transportError(data) {
|
||||
var page = this.pageCache[data.pageNum - 1];
|
||||
var intentState = page.intentStates[data.intent];
|
||||
if (intentState.displayReadyCapability.promise) {
|
||||
if (intentState.displayReadyCapability) {
|
||||
intentState.displayReadyCapability.reject(data.error);
|
||||
} else {
|
||||
error(data.error);
|
||||
|
@ -4181,7 +4181,7 @@ var CanvasGraphics = (function CanvasGraphicsClosure() {
|
|||
ctx.transform.apply(ctx, current.textMatrix);
|
||||
ctx.translate(current.x, current.y);
|
||||
|
||||
ctx.scale(textHScale, 1);
|
||||
ctx.scale(textHScale, fontDirection);
|
||||
|
||||
for (i = 0; i < glyphsLength; ++i) {
|
||||
glyph = glyphs[i];
|
||||
|
@ -4211,7 +4211,7 @@ var CanvasGraphics = (function CanvasGraphicsClosure() {
|
|||
this.restore();
|
||||
|
||||
var transformed = Util.applyTransform([glyph.width, 0], fontMatrix);
|
||||
width = ((transformed[0] * fontSize + charSpacing) * fontDirection);
|
||||
width = transformed[0] * fontSize + charSpacing;
|
||||
|
||||
ctx.translate(width, 0);
|
||||
current.x += width * textHScale;
|
||||
|
@ -5988,6 +5988,8 @@ var AnnotationUtils = (function AnnotationUtilsClosure() {
|
|||
PDFJS.AnnotationUtils = AnnotationUtils;
|
||||
|
||||
|
||||
|
||||
|
||||
}).call((typeof window === 'undefined') ? this : window);
|
||||
|
||||
|
||||
|
|
|
@ -22,8 +22,8 @@ if (typeof PDFJS === 'undefined') {
|
|||
(typeof window !== 'undefined' ? window : this).PDFJS = {};
|
||||
}
|
||||
|
||||
PDFJS.version = '1.0.645';
|
||||
PDFJS.build = '66bfb9c';
|
||||
PDFJS.version = '1.0.712';
|
||||
PDFJS.build = '6969ed4';
|
||||
|
||||
(function pdfjsWrapper() {
|
||||
// Use strict in our context only - users might not want it
|
||||
|
@ -1351,7 +1351,7 @@ var ChunkedStream = (function ChunkedStreamClosure() {
|
|||
}
|
||||
},
|
||||
|
||||
ensureByte: function ChunkedStream_ensureRange(pos) {
|
||||
ensureByte: function ChunkedStream_ensureByte(pos) {
|
||||
var chunk = Math.floor(pos / this.chunkSize);
|
||||
if (chunk === this.lastSuccessfulEnsureByteChunk) {
|
||||
return;
|
||||
|
@ -3312,8 +3312,6 @@ var XRef = (function XRefClosure() {
|
|||
// Validate entry obj
|
||||
if (!isInt(entry.offset) || !isInt(entry.gen) ||
|
||||
!(entry.free || entry.uncompressed)) {
|
||||
console.log(entry.offset, entry.gen, entry.free,
|
||||
entry.uncompressed);
|
||||
error('Invalid entry in XRef subsection: ' + first + ', ' + count);
|
||||
}
|
||||
|
||||
|
@ -4707,7 +4705,7 @@ var PDFFunction = (function PDFFunctionClosure() {
|
|||
}
|
||||
length *= outputSize;
|
||||
|
||||
var array = [];
|
||||
var array = new Array(length);
|
||||
var codeSize = 0;
|
||||
var codeBuf = 0;
|
||||
// 32 is a valid bps so shifting won't work
|
||||
|
@ -4722,7 +4720,7 @@ var PDFFunction = (function PDFFunctionClosure() {
|
|||
codeSize += 8;
|
||||
}
|
||||
codeSize -= bps;
|
||||
array.push((codeBuf >> codeSize) * sampleMul);
|
||||
array[i] = (codeBuf >> codeSize) * sampleMul;
|
||||
codeBuf &= (1 << codeSize) - 1;
|
||||
}
|
||||
return array;
|
||||
|
@ -5962,7 +5960,7 @@ var ColorSpace = (function ColorSpaceClosure() {
|
|||
|
||||
ColorSpace.fromIR = function ColorSpace_fromIR(IR) {
|
||||
var name = isArray(IR) ? IR[0] : IR;
|
||||
var whitePoint, blackPoint;
|
||||
var whitePoint, blackPoint, gamma;
|
||||
|
||||
switch (name) {
|
||||
case 'DeviceGrayCS':
|
||||
|
@ -5974,8 +5972,14 @@ var ColorSpace = (function ColorSpaceClosure() {
|
|||
case 'CalGrayCS':
|
||||
whitePoint = IR[1].WhitePoint;
|
||||
blackPoint = IR[1].BlackPoint;
|
||||
var gamma = IR[1].Gamma;
|
||||
gamma = IR[1].Gamma;
|
||||
return new CalGrayCS(whitePoint, blackPoint, gamma);
|
||||
case 'CalRGBCS':
|
||||
whitePoint = IR[1].WhitePoint;
|
||||
blackPoint = IR[1].BlackPoint;
|
||||
gamma = IR[1].Gamma;
|
||||
var matrix = IR[1].Matrix;
|
||||
return new CalRGBCS(whitePoint, blackPoint, gamma, matrix);
|
||||
case 'PatternCS':
|
||||
var basePatternCS = IR[1];
|
||||
if (basePatternCS) {
|
||||
|
@ -6057,7 +6061,8 @@ var ColorSpace = (function ColorSpaceClosure() {
|
|||
params = cs[1].getAll();
|
||||
return ['CalGrayCS', params];
|
||||
case 'CalRGB':
|
||||
return 'DeviceRgbCS';
|
||||
params = cs[1].getAll();
|
||||
return ['CalRGBCS', params];
|
||||
case 'ICCBased':
|
||||
var stream = xref.fetchIfRef(cs[1]);
|
||||
var dict = stream.dict;
|
||||
|
@ -6578,6 +6583,308 @@ var CalGrayCS = (function CalGrayCSClosure() {
|
|||
return CalGrayCS;
|
||||
})();
|
||||
|
||||
//
|
||||
// CalRGBCS: Based on "PDF Reference, Sixth Ed", p.247
|
||||
//
|
||||
var CalRGBCS = (function CalRGBCSClosure() {
|
||||
|
||||
// See http://www.brucelindbloom.com/index.html?Eqn_ChromAdapt.html for these
|
||||
// matrices.
|
||||
var BRADFORD_SCALE_MATRIX = new Float32Array([
|
||||
0.8951, 0.2664, -0.1614,
|
||||
-0.7502, 1.7135, 0.0367,
|
||||
0.0389, -0.0685, 1.0296]);
|
||||
|
||||
var BRADFORD_SCALE_INVERSE_MATRIX = new Float32Array([
|
||||
0.9869929, -0.1470543, 0.1599627,
|
||||
0.4323053, 0.5183603, 0.0492912,
|
||||
-0.0085287, 0.0400428, 0.9684867]);
|
||||
|
||||
// See http://www.brucelindbloom.com/index.html?Eqn_RGB_XYZ_Matrix.html.
|
||||
var SRGB_D65_XYZ_TO_RGB_MATRIX = new Float32Array([
|
||||
3.2404542, -1.5371385, -0.4985314,
|
||||
-0.9692660, 1.8760108, 0.0415560,
|
||||
0.0556434, -0.2040259, 1.0572252]);
|
||||
|
||||
var FLAT_WHITEPOINT_MATRIX = new Float32Array([1, 1, 1]);
|
||||
|
||||
var tempNormalizeMatrix = new Float32Array(3);
|
||||
var tempConvertMatrix1 = new Float32Array(3);
|
||||
var tempConvertMatrix2 = new Float32Array(3);
|
||||
|
||||
var DECODE_L_CONSTANT = Math.pow(((8 + 16) / 116), 3) / 8.0;
|
||||
|
||||
function CalRGBCS(whitePoint, blackPoint, gamma, matrix) {
|
||||
this.name = 'CalRGB';
|
||||
this.numComps = 3;
|
||||
this.defaultColor = new Float32Array(3);
|
||||
|
||||
if (!whitePoint) {
|
||||
error('WhitePoint missing - required for color space CalRGB');
|
||||
}
|
||||
blackPoint = blackPoint || new Float32Array(3);
|
||||
gamma = gamma || new Float32Array([1, 1, 1]);
|
||||
matrix = matrix || new Float32Array([1, 0, 0, 0, 1, 0, 0, 0, 1]);
|
||||
|
||||
// Translate arguments to spec variables.
|
||||
var XW = whitePoint[0];
|
||||
var YW = whitePoint[1];
|
||||
var ZW = whitePoint[2];
|
||||
this.whitePoint = whitePoint;
|
||||
|
||||
var XB = blackPoint[0];
|
||||
var YB = blackPoint[1];
|
||||
var ZB = blackPoint[2];
|
||||
this.blackPoint = blackPoint;
|
||||
|
||||
this.GR = gamma[0];
|
||||
this.GG = gamma[1];
|
||||
this.GB = gamma[2];
|
||||
|
||||
this.MXA = matrix[0];
|
||||
this.MYA = matrix[1];
|
||||
this.MZA = matrix[2];
|
||||
this.MXB = matrix[3];
|
||||
this.MYB = matrix[4];
|
||||
this.MZB = matrix[5];
|
||||
this.MXC = matrix[6];
|
||||
this.MYC = matrix[7];
|
||||
this.MZC = matrix[8];
|
||||
|
||||
// Validate variables as per spec.
|
||||
if (XW < 0 || ZW < 0 || YW !== 1) {
|
||||
error('Invalid WhitePoint components for ' + this.name +
|
||||
', no fallback available');
|
||||
}
|
||||
|
||||
if (XB < 0 || YB < 0 || ZB < 0) {
|
||||
info('Invalid BlackPoint for ' + this.name + ' [' + XB + ', ' + YB +
|
||||
', ' + ZB + '], falling back to default');
|
||||
this.blackPoint = new Float32Array(3);
|
||||
}
|
||||
|
||||
if (this.GR < 0 || this.GG < 0 || this.GB < 0) {
|
||||
info('Invalid Gamma [' + this.GR + ', ' + this.GG + ', ' + this.GB +
|
||||
'] for ' + this.name + ', falling back to default');
|
||||
this.GR = this.GG = this.GB = 1;
|
||||
}
|
||||
|
||||
if (this.MXA < 0 || this.MYA < 0 || this.MZA < 0 ||
|
||||
this.MXB < 0 || this.MYB < 0 || this.MZB < 0 ||
|
||||
this.MXC < 0 || this.MYC < 0 || this.MZC < 0) {
|
||||
info('Invalid Matrix for ' + this.name + ' [' +
|
||||
this.MXA + ', ' + this.MYA + ', ' + this.MZA +
|
||||
this.MXB + ', ' + this.MYB + ', ' + this.MZB +
|
||||
this.MXC + ', ' + this.MYC + ', ' + this.MZC +
|
||||
'], falling back to default');
|
||||
this.MXA = this.MYB = this.MZC = 1;
|
||||
this.MXB = this.MYA = this.MZA = this.MXC = this.MYC = this.MZB = 0;
|
||||
}
|
||||
}
|
||||
|
||||
function matrixProduct(a, b, result) {
|
||||
result[0] = a[0] * b[0] + a[1] * b[1] + a[2] * b[2];
|
||||
result[1] = a[3] * b[0] + a[4] * b[1] + a[5] * b[2];
|
||||
result[2] = a[6] * b[0] + a[7] * b[1] + a[8] * b[2];
|
||||
}
|
||||
|
||||
function convertToFlat(sourceWhitePoint, LMS, result) {
|
||||
result[0] = LMS[0] * 1 / sourceWhitePoint[0];
|
||||
result[1] = LMS[1] * 1 / sourceWhitePoint[1];
|
||||
result[2] = LMS[2] * 1 / sourceWhitePoint[2];
|
||||
}
|
||||
|
||||
function convertToD65(sourceWhitePoint, LMS, result) {
|
||||
var D65X = 0.95047;
|
||||
var D65Y = 1;
|
||||
var D65Z = 1.08883;
|
||||
|
||||
result[0] = LMS[0] * D65X / sourceWhitePoint[0];
|
||||
result[1] = LMS[1] * D65Y / sourceWhitePoint[1];
|
||||
result[2] = LMS[2] * D65Z / sourceWhitePoint[2];
|
||||
}
|
||||
|
||||
function sRGBTransferFunction(color) {
|
||||
// See http://en.wikipedia.org/wiki/SRGB.
|
||||
if (color <= 0.0031308){
|
||||
return adjustToRange(0, 1, 12.92 * color);
|
||||
}
|
||||
|
||||
return adjustToRange(0, 1, (1 + 0.055) * Math.pow(color, 1 / 2.4) - 0.055);
|
||||
}
|
||||
|
||||
function adjustToRange(min, max, value) {
|
||||
return Math.max(min, Math.min(max, value));
|
||||
}
|
||||
|
||||
function decodeL(L) {
|
||||
if (L < 0) {
|
||||
return -decodeL(-L);
|
||||
}
|
||||
|
||||
if (L > 8.0) {
|
||||
return Math.pow(((L + 16) / 116), 3);
|
||||
}
|
||||
|
||||
return L * DECODE_L_CONSTANT;
|
||||
}
|
||||
|
||||
function compensateBlackPoint(sourceBlackPoint, XYZ_Flat, result) {
|
||||
|
||||
// In case the blackPoint is already the default blackPoint then there is
|
||||
// no need to do compensation.
|
||||
if (sourceBlackPoint[0] === 0 &&
|
||||
sourceBlackPoint[1] === 0 &&
|
||||
sourceBlackPoint[2] === 0) {
|
||||
result[0] = XYZ_Flat[0];
|
||||
result[1] = XYZ_Flat[1];
|
||||
result[2] = XYZ_Flat[2];
|
||||
return;
|
||||
}
|
||||
|
||||
// For the blackPoint calculation details, please see
|
||||
// http://www.adobe.com/content/dam/Adobe/en/devnet/photoshop/sdk/
|
||||
// AdobeBPC.pdf.
|
||||
// The destination blackPoint is the default blackPoint [0, 0, 0].
|
||||
var zeroDecodeL = decodeL(0);
|
||||
|
||||
var X_DST = zeroDecodeL;
|
||||
var X_SRC = decodeL(sourceBlackPoint[0]);
|
||||
|
||||
var Y_DST = zeroDecodeL;
|
||||
var Y_SRC = decodeL(sourceBlackPoint[1]);
|
||||
|
||||
var Z_DST = zeroDecodeL;
|
||||
var Z_SRC = decodeL(sourceBlackPoint[2]);
|
||||
|
||||
var X_Scale = (1 - X_DST) / (1 - X_SRC);
|
||||
var X_Offset = 1 - X_Scale;
|
||||
|
||||
var Y_Scale = (1 - Y_DST) / (1 - Y_SRC);
|
||||
var Y_Offset = 1 - Y_Scale;
|
||||
|
||||
var Z_Scale = (1 - Z_DST) / (1 - Z_SRC);
|
||||
var Z_Offset = 1 - Z_Scale;
|
||||
|
||||
result[0] = XYZ_Flat[0] * X_Scale + X_Offset;
|
||||
result[1] = XYZ_Flat[1] * Y_Scale + Y_Offset;
|
||||
result[2] = XYZ_Flat[2] * Z_Scale + Z_Offset;
|
||||
}
|
||||
|
||||
function normalizeWhitePointToFlat(sourceWhitePoint, XYZ_In, result) {
|
||||
|
||||
// In case the whitePoint is already flat then there is no need to do
|
||||
// normalization.
|
||||
if (sourceWhitePoint[0] === 1 && sourceWhitePoint[2] === 1) {
|
||||
result[0] = XYZ_In[0];
|
||||
result[1] = XYZ_In[1];
|
||||
result[2] = XYZ_In[2];
|
||||
return;
|
||||
}
|
||||
|
||||
var LMS = result;
|
||||
matrixProduct(BRADFORD_SCALE_MATRIX, XYZ_In, LMS);
|
||||
|
||||
var LMS_Flat = tempNormalizeMatrix;
|
||||
convertToFlat(sourceWhitePoint, LMS, LMS_Flat);
|
||||
|
||||
matrixProduct(BRADFORD_SCALE_INVERSE_MATRIX, LMS_Flat, result);
|
||||
}
|
||||
|
||||
function normalizeWhitePointToD65(sourceWhitePoint, XYZ_In, result) {
|
||||
|
||||
var LMS = result;
|
||||
matrixProduct(BRADFORD_SCALE_MATRIX, XYZ_In, LMS);
|
||||
|
||||
var LMS_D65 = tempNormalizeMatrix;
|
||||
convertToD65(sourceWhitePoint, LMS, LMS_D65);
|
||||
|
||||
matrixProduct(BRADFORD_SCALE_INVERSE_MATRIX, LMS_D65, result);
|
||||
}
|
||||
|
||||
function convertToRgb(cs, src, srcOffset, dest, destOffset, scale) {
|
||||
// A, B and C represent a red, green and blue components of a calibrated
|
||||
// rgb space.
|
||||
var A = adjustToRange(0, 1, src[srcOffset] * scale);
|
||||
var B = adjustToRange(0, 1, src[srcOffset + 1] * scale);
|
||||
var C = adjustToRange(0, 1, src[srcOffset + 2] * scale);
|
||||
|
||||
// A <---> AGR in the spec
|
||||
// B <---> BGG in the spec
|
||||
// C <---> CGB in the spec
|
||||
var AGR = Math.pow(A, cs.GR);
|
||||
var BGG = Math.pow(B, cs.GG);
|
||||
var CGB = Math.pow(C, cs.GB);
|
||||
|
||||
// Computes intermediate variables L, M, N as per spec.
|
||||
// To decode X, Y, Z values map L, M, N directly to them.
|
||||
var X = cs.MXA * AGR + cs.MXB * BGG + cs.MXC * CGB;
|
||||
var Y = cs.MYA * AGR + cs.MYB * BGG + cs.MYC * CGB;
|
||||
var Z = cs.MZA * AGR + cs.MZB * BGG + cs.MZC * CGB;
|
||||
|
||||
// The following calculations are based on this document:
|
||||
// http://www.adobe.com/content/dam/Adobe/en/devnet/photoshop/sdk/
|
||||
// AdobeBPC.pdf.
|
||||
var XYZ = tempConvertMatrix1;
|
||||
XYZ[0] = X;
|
||||
XYZ[1] = Y;
|
||||
XYZ[2] = Z;
|
||||
var XYZ_Flat = tempConvertMatrix2;
|
||||
|
||||
normalizeWhitePointToFlat(cs.whitePoint, XYZ, XYZ_Flat);
|
||||
|
||||
var XYZ_Black = tempConvertMatrix1;
|
||||
compensateBlackPoint(cs.blackPoint, XYZ_Flat, XYZ_Black);
|
||||
|
||||
var XYZ_D65 = tempConvertMatrix2;
|
||||
normalizeWhitePointToD65(FLAT_WHITEPOINT_MATRIX, XYZ_Black, XYZ_D65);
|
||||
|
||||
var SRGB = tempConvertMatrix1;
|
||||
matrixProduct(SRGB_D65_XYZ_TO_RGB_MATRIX, XYZ_D65, SRGB);
|
||||
|
||||
var sR = sRGBTransferFunction(SRGB[0]);
|
||||
var sG = sRGBTransferFunction(SRGB[1]);
|
||||
var sB = sRGBTransferFunction(SRGB[2]);
|
||||
|
||||
// Convert the values to rgb range [0, 255].
|
||||
dest[destOffset] = Math.round(sR * 255);
|
||||
dest[destOffset + 1] = Math.round(sG * 255);
|
||||
dest[destOffset + 2] = Math.round(sB * 255);
|
||||
}
|
||||
|
||||
CalRGBCS.prototype = {
|
||||
getRgb: function CalRGBCS_getRgb(src, srcOffset) {
|
||||
var rgb = new Uint8Array(3);
|
||||
this.getRgbItem(src, srcOffset, rgb, 0);
|
||||
return rgb;
|
||||
},
|
||||
getRgbItem: function CalRGBCS_getRgbItem(src, srcOffset,
|
||||
dest, destOffset) {
|
||||
convertToRgb(this, src, srcOffset, dest, destOffset, 1);
|
||||
},
|
||||
getRgbBuffer: function CalRGBCS_getRgbBuffer(src, srcOffset, count,
|
||||
dest, destOffset, bits) {
|
||||
var scale = 1 / ((1 << bits) - 1);
|
||||
|
||||
for (var i = 0; i < count; ++i) {
|
||||
convertToRgb(this, src, srcOffset, dest, destOffset, scale);
|
||||
srcOffset += 3;
|
||||
destOffset += 3;
|
||||
}
|
||||
},
|
||||
getOutputLength: function CalRGBCS_getOutputLength(inputLength) {
|
||||
return inputLength;
|
||||
},
|
||||
isPassthrough: ColorSpace.prototype.isPassthrough,
|
||||
createRgbBuffer: ColorSpace.prototype.createRgbBuffer,
|
||||
isDefaultDecode: function CalRGBCS_isDefaultDecode(decodeMap) {
|
||||
return ColorSpace.isDefaultDecode(decodeMap, this.numComps);
|
||||
},
|
||||
usesZeroToOneRange: true
|
||||
};
|
||||
return CalRGBCS;
|
||||
})();
|
||||
|
||||
//
|
||||
// LabCS: Based on "PDF Reference, Sixth Ed", p.250
|
||||
//
|
||||
|
@ -9856,10 +10163,13 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
|
|||
setGState: function PartialEvaluator_setGState(resources, gState,
|
||||
operatorList, xref,
|
||||
stateManager) {
|
||||
|
||||
// TODO(mack): This should be rewritten so that this function returns
|
||||
// what should be added to the queue during each iteration
|
||||
function setGStateForKey(gStateObj, key, value) {
|
||||
// This array holds the converted/processed state data.
|
||||
var gStateObj = [];
|
||||
var gStateMap = gState.map;
|
||||
var self = this;
|
||||
var promise = Promise.resolve();
|
||||
for (var key in gStateMap) {
|
||||
var value = gStateMap[key];
|
||||
switch (key) {
|
||||
case 'Type':
|
||||
break;
|
||||
|
@ -9905,7 +10215,7 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
|
|||
|
||||
break;
|
||||
// Only generate info log messages for the following since
|
||||
// they are unlikey to have a big impact on the rendering.
|
||||
// they are unlikely to have a big impact on the rendering.
|
||||
case 'OP':
|
||||
case 'op':
|
||||
case 'OPM':
|
||||
|
@ -9928,18 +10238,10 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
|
|||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// This array holds the converted/processed state data.
|
||||
var gStateObj = [];
|
||||
var gStateMap = gState.map;
|
||||
var self = this;
|
||||
var promise = Promise.resolve();
|
||||
for (var key in gStateMap) {
|
||||
var value = gStateMap[key];
|
||||
setGStateForKey(gStateObj, key, value);
|
||||
}
|
||||
return promise.then(function () {
|
||||
operatorList.addOp(OPS.setGState, [gStateObj]);
|
||||
if (gStateObj.length >= 0) {
|
||||
operatorList.addOp(OPS.setGState, [gStateObj]);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
|
@ -10135,8 +10437,15 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
|
|||
return new Promise(function next(resolve, reject) {
|
||||
timeSlotManager.reset();
|
||||
var stop, operation = {}, i, ii, cs;
|
||||
while (!(stop = timeSlotManager.check()) &&
|
||||
preprocessor.read(operation)) {
|
||||
while (!(stop = timeSlotManager.check())) {
|
||||
// The arguments parsed by read() are used beyond this loop, so we
|
||||
// cannot reuse the same array on each iteration. Therefore we pass
|
||||
// in |null| as the initial value (see the comment on
|
||||
// EvaluatorPreprocessor_read() for why).
|
||||
operation.args = null;
|
||||
if (!(preprocessor.read(operation))) {
|
||||
break;
|
||||
}
|
||||
var args = operation.args;
|
||||
var fn = operation.fn;
|
||||
|
||||
|
@ -10530,12 +10839,20 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
|
|||
|
||||
return new Promise(function next(resolve, reject) {
|
||||
timeSlotManager.reset();
|
||||
var stop, operation = {};
|
||||
while (!(stop = timeSlotManager.check()) &&
|
||||
(preprocessor.read(operation))) {
|
||||
var stop, operation = {}, args = [];
|
||||
while (!(stop = timeSlotManager.check())) {
|
||||
// The arguments parsed by read() are not used beyond this loop, so
|
||||
// we can reuse the same array on every iteration, thus avoiding
|
||||
// unnecessary allocations.
|
||||
args.length = 0;
|
||||
operation.args = args;
|
||||
if (!(preprocessor.read(operation))) {
|
||||
break;
|
||||
}
|
||||
textState = stateManager.state;
|
||||
var fn = operation.fn;
|
||||
var args = operation.args;
|
||||
args = operation.args;
|
||||
|
||||
switch (fn | 0) {
|
||||
case OPS.setFont:
|
||||
textState.fontSize = args[1];
|
||||
|
@ -11605,10 +11922,29 @@ var EvaluatorPreprocessor = (function EvaluatorPreprocessorClosure() {
|
|||
return this.stateManager.stateStack.length;
|
||||
},
|
||||
|
||||
// |operation| is an object with two fields:
|
||||
//
|
||||
// - |fn| is an out param.
|
||||
//
|
||||
// - |args| is an inout param. On entry, it should have one of two values.
|
||||
//
|
||||
// - An empty array. This indicates that the caller is providing the
|
||||
// array in which the args will be stored in. The caller should use
|
||||
// this value if it can reuse a single array for each call to read().
|
||||
//
|
||||
// - |null|. This indicates that the caller needs this function to create
|
||||
// the array in which any args are stored in. If there are zero args,
|
||||
// this function will leave |operation.args| as |null| (thus avoiding
|
||||
// allocations that would occur if we used an empty array to represent
|
||||
// zero arguments). Otherwise, it will replace |null| with a new array
|
||||
// containing the arguments. The caller should use this value if it
|
||||
// cannot reuse an array for each call to read().
|
||||
//
|
||||
// These two modes are present because this function is very hot and so
|
||||
// avoiding allocations where possible is worthwhile.
|
||||
//
|
||||
read: function EvaluatorPreprocessor_read(operation) {
|
||||
// We use an array to represent args, except we use |null| to represent
|
||||
// no args because it's more compact than an empty array.
|
||||
var args = null;
|
||||
var args = operation.args;
|
||||
while (true) {
|
||||
var obj = this.parser.getObj();
|
||||
if (isCmd(obj)) {
|
||||
|
@ -12362,7 +12698,7 @@ var CMap = (function CMapClosure() {
|
|||
return this._map;
|
||||
},
|
||||
|
||||
readCharCode: function(str, offset) {
|
||||
readCharCode: function(str, offset, out) {
|
||||
var c = 0;
|
||||
var codespaceRanges = this.codespaceRanges;
|
||||
var codespaceRangesLen = this.codespaceRanges.length;
|
||||
|
@ -12376,12 +12712,14 @@ var CMap = (function CMapClosure() {
|
|||
var low = codespaceRange[k++];
|
||||
var high = codespaceRange[k++];
|
||||
if (c >= low && c <= high) {
|
||||
return [c, n + 1];
|
||||
out.charcode = c;
|
||||
out.length = n + 1;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return [0, 1];
|
||||
out.charcode = 0;
|
||||
out.length = 1;
|
||||
}
|
||||
};
|
||||
return CMap;
|
||||
|
@ -12466,7 +12804,7 @@ var BinaryCMapReader = (function BinaryCMapReaderClosure() {
|
|||
request.overrideMimeType('text/plain; charset=x-user-defined');
|
||||
}
|
||||
request.send(null);
|
||||
if (request.status === 0 && /^https?:/i.test(url)) {
|
||||
if (nonBinaryRequest ? !request.responseText : !request.response) {
|
||||
error('Unable to get binary cMap at: ' + url);
|
||||
}
|
||||
if (nonBinaryRequest) {
|
||||
|
@ -13006,7 +13344,7 @@ var CMapFactory = (function CMapFactoryClosure() {
|
|||
var url = builtInCMapParams.url + name;
|
||||
request.open('GET', url, false);
|
||||
request.send(null);
|
||||
if (request.status === 0 && /^https?:/i.test(url)) {
|
||||
if (!request.responseText) {
|
||||
error('Unable to get cMap at: ' + url);
|
||||
}
|
||||
var cMap = new CMap(true);
|
||||
|
@ -17319,12 +17657,16 @@ var Font = (function FontClosure() {
|
|||
// Horizontal metrics
|
||||
builder.addTable('hmtx', (function fontFieldsHmtx() {
|
||||
var charstrings = font.charstrings;
|
||||
var cffWidths = font.cff ? font.cff.widths : null;
|
||||
var hmtx = '\x00\x00\x00\x00'; // Fake .notdef
|
||||
for (var i = 1, ii = numGlyphs; i < ii; i++) {
|
||||
// TODO: For CFF fonts the width should technically match th x in
|
||||
// the glyph, but it doesn't seem to matter.
|
||||
var charstring = charstrings ? charstrings[i - 1] : {};
|
||||
var width = 'width' in charstring ? charstring.width : 0;
|
||||
var width = 0;
|
||||
if (charstrings) {
|
||||
var charstring = charstrings[i - 1];
|
||||
width = 'width' in charstring ? charstring.width : 0;
|
||||
} else if (cffWidths) {
|
||||
width = Math.ceil(cffWidths[i] || 0);
|
||||
}
|
||||
hmtx += string16(width) + string16(0);
|
||||
}
|
||||
return hmtx;
|
||||
|
@ -17587,10 +17929,11 @@ var Font = (function FontClosure() {
|
|||
if (this.cMap) {
|
||||
// composite fonts have multi-byte strings convert the string from
|
||||
// single-byte to multi-byte
|
||||
var c = {};
|
||||
while (i < chars.length) {
|
||||
var c = this.cMap.readCharCode(chars, i);
|
||||
charcode = c[0];
|
||||
var length = c[1];
|
||||
this.cMap.readCharCode(chars, i, c);
|
||||
charcode = c.charcode;
|
||||
var length = c.length;
|
||||
i += length;
|
||||
glyph = this.charToGlyph(charcode);
|
||||
glyphs.push(glyph);
|
||||
|
@ -17836,8 +18179,8 @@ var Type1CharString = (function Type1CharStringClosure() {
|
|||
sbx = this.stack.pop();
|
||||
this.lsb = sbx;
|
||||
this.width = wx;
|
||||
this.stack.push(sbx);
|
||||
error = this.executeCommand(1, COMMAND_MAP.hmoveto);
|
||||
this.stack.push(wx, sbx);
|
||||
error = this.executeCommand(2, COMMAND_MAP.hmoveto);
|
||||
break;
|
||||
case 14: // endchar
|
||||
this.output.push(COMMAND_MAP.endchar[0]);
|
||||
|
@ -17911,8 +18254,8 @@ var Type1CharString = (function Type1CharStringClosure() {
|
|||
sbx = this.stack.pop();
|
||||
this.lsb = sbx;
|
||||
this.width = wx;
|
||||
this.stack.push(sbx, sby);
|
||||
error = this.executeCommand(2, COMMAND_MAP.rmoveto);
|
||||
this.stack.push(wx, sbx, sby);
|
||||
error = this.executeCommand(3, COMMAND_MAP.rmoveto);
|
||||
break;
|
||||
case (12 << 8) + 12: // div
|
||||
if (this.stack.length < 2) {
|
||||
|
@ -18725,10 +19068,10 @@ var CFFFont = (function CFFFontClosure() {
|
|||
var CFFParser = (function CFFParserClosure() {
|
||||
var CharstringValidationData = [
|
||||
null,
|
||||
{ id: 'hstem', min: 2, resetStack: true, stem: true },
|
||||
{ id: 'hstem', min: 2, stackClearing: true, stem: true },
|
||||
null,
|
||||
{ id: 'vstem', min: 2, resetStack: true, stem: true },
|
||||
{ id: 'vmoveto', min: 1, resetStack: true },
|
||||
{ id: 'vstem', min: 2, stackClearing: true, stem: true },
|
||||
{ id: 'vmoveto', min: 1, stackClearing: true },
|
||||
{ id: 'rlineto', min: 2, resetStack: true },
|
||||
{ id: 'hlineto', min: 1, resetStack: true },
|
||||
{ id: 'vlineto', min: 1, resetStack: true },
|
||||
|
@ -18738,16 +19081,16 @@ var CFFParser = (function CFFParserClosure() {
|
|||
{ id: 'return', min: 0, undefStack: true },
|
||||
null, // 12
|
||||
null,
|
||||
null, // endchar
|
||||
{ id: 'endchar', min: 0, stackClearing: true },
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
{ id: 'hstemhm', min: 2, resetStack: true, stem: true },
|
||||
null, // hintmask
|
||||
null, // cntrmask
|
||||
{ id: 'rmoveto', min: 2, resetStack: true },
|
||||
{ id: 'hmoveto', min: 1, resetStack: true },
|
||||
{ id: 'vstemhm', min: 2, resetStack: true, stem: true },
|
||||
{ id: 'hstemhm', min: 2, stackClearing: true, stem: true },
|
||||
{ id: 'hintmask', min: 0, stackClearing: true },
|
||||
{ id: 'cntrmask', min: 0, stackClearing: true },
|
||||
{ id: 'rmoveto', min: 2, stackClearing: true },
|
||||
{ id: 'hmoveto', min: 1, stackClearing: true },
|
||||
{ id: 'vstemhm', min: 2, stackClearing: true, stem: true },
|
||||
{ id: 'rcurveline', min: 8, resetStack: true },
|
||||
{ id: 'rlinecurve', min: 8, resetStack: true },
|
||||
{ id: 'vvcurveto', min: 4, resetStack: true },
|
||||
|
@ -18853,6 +19196,7 @@ var CFFParser = (function CFFParserClosure() {
|
|||
var charStringsAndSeacs = this.parseCharStrings(charStringOffset);
|
||||
cff.charStrings = charStringsAndSeacs.charStrings;
|
||||
cff.seacs = charStringsAndSeacs.seacs;
|
||||
cff.widths = charStringsAndSeacs.widths;
|
||||
|
||||
var fontMatrix = topDict.getByName('FontMatrix');
|
||||
if (fontMatrix) {
|
||||
|
@ -19070,6 +19414,7 @@ var CFFParser = (function CFFParserClosure() {
|
|||
parseCharStrings: function CFFParser_parseCharStrings(charStringOffset) {
|
||||
var charStrings = this.parseIndex(charStringOffset).obj;
|
||||
var seacs = [];
|
||||
var widths = [];
|
||||
var count = charStrings.count;
|
||||
for (var i = 0; i < count; i++) {
|
||||
var charstring = charStrings.get(i);
|
||||
|
@ -19081,6 +19426,7 @@ var CFFParser = (function CFFParserClosure() {
|
|||
var valid = true;
|
||||
var data = charstring;
|
||||
var length = data.length;
|
||||
var firstStackClearing = true;
|
||||
for (var j = 0; j < length;) {
|
||||
var value = data[j++];
|
||||
var validationCommand = null;
|
||||
|
@ -19110,6 +19456,7 @@ var CFFParser = (function CFFParserClosure() {
|
|||
valid = false;
|
||||
}
|
||||
}
|
||||
validationCommand = CharstringValidationData[value];
|
||||
} else if (value >= 32 && value <= 246) { // number
|
||||
stack[stackSize] = value - 139;
|
||||
stackSize++;
|
||||
|
@ -19127,7 +19474,8 @@ var CFFParser = (function CFFParserClosure() {
|
|||
} else if (value === 19 || value === 20) {
|
||||
hints += stackSize >> 1;
|
||||
j += (hints + 7) >> 3; // skipping right amount of hints flag data
|
||||
stackSize = 0;
|
||||
stackSize %= 2;
|
||||
validationCommand = CharstringValidationData[value];
|
||||
} else {
|
||||
validationCommand = CharstringValidationData[value];
|
||||
}
|
||||
|
@ -19144,17 +19492,35 @@ var CFFParser = (function CFFParserClosure() {
|
|||
break;
|
||||
}
|
||||
}
|
||||
if (firstStackClearing && validationCommand.stackClearing) {
|
||||
firstStackClearing = false;
|
||||
// the optional character width can be found before the first
|
||||
// stack-clearing command arguments
|
||||
stackSize -= validationCommand.min;
|
||||
if (stackSize >= 2 && validationCommand.stem) {
|
||||
// there are even amount of arguments for stem commands
|
||||
stackSize %= 2;
|
||||
} else if (stackSize > 1) {
|
||||
warn('Found too many parameters for stack-clearing command');
|
||||
}
|
||||
if (stackSize > 0 && stack[stackSize - 1] >= 0) {
|
||||
widths[i] = stack[stackSize - 1];
|
||||
}
|
||||
}
|
||||
if ('stackDelta' in validationCommand) {
|
||||
if ('stackFn' in validationCommand) {
|
||||
validationCommand.stackFn(stack, stackSize);
|
||||
}
|
||||
stackSize += validationCommand.stackDelta;
|
||||
} else if (validationCommand.stackClearing) {
|
||||
stackSize = 0;
|
||||
} else if (validationCommand.resetStack) {
|
||||
stackSize = 0;
|
||||
undefStack = false;
|
||||
} else if (validationCommand.undefStack) {
|
||||
stackSize = 0;
|
||||
undefStack = true;
|
||||
firstStackClearing = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -19163,7 +19529,7 @@ var CFFParser = (function CFFParserClosure() {
|
|||
charStrings.set(i, new Uint8Array([14]));
|
||||
}
|
||||
}
|
||||
return { charStrings: charStrings, seacs: seacs };
|
||||
return { charStrings: charStrings, seacs: seacs, widths: widths };
|
||||
},
|
||||
emptyPrivateDictionary:
|
||||
function CFFParser_emptyPrivateDictionary(parentDict) {
|
||||
|
@ -25547,10 +25913,11 @@ var PDFImage = (function PDFImageClosure() {
|
|||
var kind;
|
||||
if (this.colorSpace.name === 'DeviceGray' && bpc === 1) {
|
||||
kind = ImageKind.GRAYSCALE_1BPP;
|
||||
} else if (this.colorSpace.name === 'DeviceRGB' && bpc === 8) {
|
||||
} else if (this.colorSpace.name === 'DeviceRGB' && bpc === 8 &&
|
||||
!this.needsDecode) {
|
||||
kind = ImageKind.RGB_24BPP;
|
||||
}
|
||||
if (kind && !this.smask && !this.mask && !this.needsDecode &&
|
||||
if (kind && !this.smask && !this.mask &&
|
||||
drawWidth === originalWidth && drawHeight === originalHeight) {
|
||||
imgData.kind = kind;
|
||||
|
||||
|
@ -25567,6 +25934,14 @@ var PDFImage = (function PDFImageClosure() {
|
|||
newArray.set(imgArray);
|
||||
imgData.data = newArray;
|
||||
}
|
||||
if (this.needsDecode) {
|
||||
// Invert the buffer (which must be grayscale if we reached here).
|
||||
assert(kind === ImageKind.GRAYSCALE_1BPP);
|
||||
var buffer = imgData.data;
|
||||
for (var i = 0, ii = buffer.length; i < ii; i++) {
|
||||
buffer[i] ^= 0xff;
|
||||
}
|
||||
}
|
||||
return imgData;
|
||||
}
|
||||
if (this.image instanceof JpegStream && !this.smask && !this.mask) {
|
||||
|
@ -28749,32 +29124,33 @@ var Parser = (function ParserClosure() {
|
|||
|
||||
// searching for the /EI\s/
|
||||
var state = 0, ch, i, ii;
|
||||
while (state !== 4 && (ch = stream.getByte()) !== -1) {
|
||||
switch (ch | 0) {
|
||||
case 0x20:
|
||||
case 0x0D:
|
||||
case 0x0A:
|
||||
// let's check next five bytes to be ASCII... just be sure
|
||||
var followingBytes = stream.peekBytes(5);
|
||||
for (i = 0, ii = followingBytes.length; i < ii; i++) {
|
||||
var E = 0x45, I = 0x49, SPACE = 0x20, NL = 0xA, CR = 0xD;
|
||||
while ((ch = stream.getByte()) !== -1) {
|
||||
if (state === 0) {
|
||||
state = (ch === E) ? 1 : 0;
|
||||
} else if (state === 1) {
|
||||
state = (ch === I) ? 2 : 0;
|
||||
} else {
|
||||
assert(state === 2);
|
||||
if (ch === SPACE || ch === NL || ch === CR) {
|
||||
// Let's check the next five bytes are ASCII... just be sure.
|
||||
var n = 5;
|
||||
var followingBytes = stream.peekBytes(n);
|
||||
for (i = 0; i < n; i++) {
|
||||
ch = followingBytes[i];
|
||||
if (ch !== 0x0A && ch !== 0x0D && (ch < 0x20 || ch > 0x7F)) {
|
||||
// not a LF, CR, SPACE or any visible ASCII character
|
||||
if (ch !== NL && ch !== CR && (ch < SPACE || ch > 0x7F)) {
|
||||
// Not a LF, CR, SPACE or any visible ASCII character, i.e.
|
||||
// it's binary stuff. Resetting the state.
|
||||
state = 0;
|
||||
break; // some binary stuff found, resetting the state
|
||||
break;
|
||||
}
|
||||
}
|
||||
state = (state === 3 ? 4 : 0);
|
||||
break;
|
||||
case 0x45:
|
||||
state = 2;
|
||||
break;
|
||||
case 0x49:
|
||||
state = (state === 2 ? 3 : 0);
|
||||
break;
|
||||
default:
|
||||
if (state === 2) {
|
||||
break; // finished!
|
||||
}
|
||||
} else {
|
||||
state = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -271,7 +271,7 @@ html[dir='rtl'] #toolbarContainer, .findbar, .secondaryToolbar {
|
|||
#loadingBar {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 6px;
|
||||
height: 4px;
|
||||
background-color: #333;
|
||||
border-bottom: 1px solid #333;
|
||||
}
|
||||
|
@ -1306,7 +1306,7 @@ canvas {
|
|||
font-size: 0.8em;
|
||||
}
|
||||
.loadingInProgress #errorWrapper {
|
||||
top: 39px;
|
||||
top: 37px;
|
||||
}
|
||||
|
||||
#errorMessageLeft {
|
||||
|
@ -1774,7 +1774,7 @@ html[dir='rtl'] #documentPropertiesOverlay .row > * {
|
|||
z-index: 100;
|
||||
}
|
||||
.loadingInProgress #sidebarContainer {
|
||||
top: 39px;
|
||||
top: 37px;
|
||||
}
|
||||
#sidebarContent {
|
||||
top: 32px;
|
||||
|
|
|
@ -2363,8 +2363,7 @@ var PasswordPrompt = {
|
|||
|
||||
var DocumentProperties = {
|
||||
overlayName: null,
|
||||
fileName: '',
|
||||
fileSize: '',
|
||||
rawFileSize: 0,
|
||||
|
||||
// Document property fields (in the viewer).
|
||||
fileNameField: null,
|
||||
|
@ -2415,20 +2414,21 @@ var DocumentProperties = {
|
|||
// don't bother updating the properties.
|
||||
return;
|
||||
}
|
||||
// Get the file name.
|
||||
this.fileName = getPDFFileNameFromURL(PDFView.url);
|
||||
|
||||
// Get the file size.
|
||||
// Get the file size (if it hasn't already been set).
|
||||
PDFView.pdfDocument.getDownloadInfo().then(function(data) {
|
||||
if (data.length === this.rawFileSize) {
|
||||
return;
|
||||
}
|
||||
this.setFileSize(data.length);
|
||||
this.updateUI(this.fileSizeField, this.fileSize);
|
||||
this.updateUI(this.fileSizeField, this.parseFileSize());
|
||||
}.bind(this));
|
||||
|
||||
// Get the other document properties.
|
||||
// Get the document properties.
|
||||
PDFView.pdfDocument.getMetadata().then(function(data) {
|
||||
var fields = [
|
||||
{ field: this.fileNameField, content: this.fileName },
|
||||
// The fileSize field is updated once getDownloadInfo is resolved.
|
||||
{ field: this.fileNameField,
|
||||
content: getPDFFileNameFromURL(PDFView.url) },
|
||||
{ field: this.fileSizeField, content: this.parseFileSize() },
|
||||
{ field: this.titleField, content: data.info.Title },
|
||||
{ field: this.authorField, content: data.info.Author },
|
||||
{ field: this.subjectField, content: data.info.Subject },
|
||||
|
@ -2458,14 +2458,22 @@ var DocumentProperties = {
|
|||
},
|
||||
|
||||
setFileSize: function documentPropertiesSetFileSize(fileSize) {
|
||||
var kb = fileSize / 1024;
|
||||
if (kb < 1024) {
|
||||
this.fileSize = mozL10n.get('document_properties_kb', {
|
||||
if (fileSize > 0) {
|
||||
this.rawFileSize = fileSize;
|
||||
}
|
||||
},
|
||||
|
||||
parseFileSize: function documentPropertiesParseFileSize() {
|
||||
var fileSize = this.rawFileSize, kb = fileSize / 1024;
|
||||
if (!kb) {
|
||||
return;
|
||||
} else if (kb < 1024) {
|
||||
return mozL10n.get('document_properties_kb', {
|
||||
size_kb: (+kb.toPrecision(3)).toLocaleString(),
|
||||
size_b: fileSize.toLocaleString()
|
||||
}, '{{size_kb}} KB ({{size_b}} bytes)');
|
||||
} else {
|
||||
this.fileSize = mozL10n.get('document_properties_mb', {
|
||||
return mozL10n.get('document_properties_mb', {
|
||||
size_mb: (+(kb / 1024).toPrecision(3)).toLocaleString(),
|
||||
size_b: fileSize.toLocaleString()
|
||||
}, '{{size_mb}} MB ({{size_b}} bytes)');
|
||||
|
@ -2702,20 +2710,28 @@ var PDFView = {
|
|||
watchScroll: function pdfViewWatchScroll(viewAreaElement, state, callback) {
|
||||
state.down = true;
|
||||
state.lastY = viewAreaElement.scrollTop;
|
||||
viewAreaElement.addEventListener('scroll', function webViewerScroll(evt) {
|
||||
if (!PDFView.pdfDocument) {
|
||||
state.rAF = null;
|
||||
viewAreaElement.addEventListener('scroll', function debounceScroll(evt) {
|
||||
if (state.rAF) {
|
||||
return;
|
||||
}
|
||||
var currentY = viewAreaElement.scrollTop;
|
||||
var lastY = state.lastY;
|
||||
if (currentY > lastY) {
|
||||
state.down = true;
|
||||
} else if (currentY < lastY) {
|
||||
state.down = false;
|
||||
}
|
||||
// else do nothing and use previous value
|
||||
state.lastY = currentY;
|
||||
callback();
|
||||
// schedule an invocation of webViewerScrolled for next animation frame.
|
||||
state.rAF = window.requestAnimationFrame(function webViewerScrolled() {
|
||||
state.rAF = null;
|
||||
if (!PDFView.pdfDocument) {
|
||||
return;
|
||||
}
|
||||
var currentY = viewAreaElement.scrollTop;
|
||||
var lastY = state.lastY;
|
||||
if (currentY > lastY) {
|
||||
state.down = true;
|
||||
} else if (currentY < lastY) {
|
||||
state.down = false;
|
||||
}
|
||||
// else do nothing and use previous value
|
||||
state.lastY = currentY;
|
||||
callback();
|
||||
});
|
||||
}, true);
|
||||
},
|
||||
|
||||
|
@ -3102,6 +3118,10 @@ var PDFView = {
|
|||
self.loading = false;
|
||||
}
|
||||
);
|
||||
|
||||
if (args && args.length) {
|
||||
DocumentProperties.setFileSize(args.length);
|
||||
}
|
||||
},
|
||||
|
||||
download: function pdfViewDownload() {
|
||||
|
@ -5121,14 +5141,18 @@ var TextLayerBuilder = (function TextLayerBuilderClosure() {
|
|||
left = tx[4] + (fontAscent * Math.sin(angle));
|
||||
top = tx[5] - (fontAscent * Math.cos(angle));
|
||||
}
|
||||
textDiv.style.position = 'absolute';
|
||||
textDiv.style.left = left + 'px';
|
||||
textDiv.style.top = top + 'px';
|
||||
textDiv.style.fontSize = fontHeight + 'px';
|
||||
textDiv.style.fontFamily = style.fontFamily;
|
||||
|
||||
textDiv.textContent = geom.str;
|
||||
textDiv.dataset.fontName = geom.fontName;
|
||||
// |fontName| is only used by the Font Inspector. This test will succeed
|
||||
// when e.g. the Font Inspector is off but the Stepper is on, but it's
|
||||
// not worth the effort to do a more accurate test.
|
||||
if (PDFJS.pdfBug) {
|
||||
textDiv.dataset.fontName = geom.fontName;
|
||||
}
|
||||
// Storing into dataset will convert number into string.
|
||||
if (angle !== 0) {
|
||||
textDiv.dataset.angle = angle * (180 / Math.PI);
|
||||
|
@ -5808,8 +5832,13 @@ window.addEventListener('pagechange', function pagechange(evt) {
|
|||
}
|
||||
}
|
||||
}
|
||||
var numPages = PDFView.pages.length;
|
||||
|
||||
document.getElementById('previous').disabled = (page <= 1);
|
||||
document.getElementById('next').disabled = (page >= PDFView.pages.length);
|
||||
document.getElementById('next').disabled = (page >= numPages);
|
||||
|
||||
document.getElementById('firstPage').disabled = (page <= 1);
|
||||
document.getElementById('lastPage').disabled = (page >= numPages);
|
||||
}, true);
|
||||
|
||||
function handleMouseWheel(evt) {
|
||||
|
|
|
@ -53,6 +53,34 @@ svg {
|
|||
stroke: #aaaaaa; /* Splitters */
|
||||
}
|
||||
|
||||
/* AudioParam connection edges */
|
||||
g.edgePath.param-connection {
|
||||
stroke-dasharray: 5,5;
|
||||
}
|
||||
|
||||
.theme-dark .edgePath.param-connection path {
|
||||
stroke: #b6babf; /* Grey foreground text */
|
||||
}
|
||||
.theme-light .edgePath.param-connection path {
|
||||
stroke: #aaaaaa; /* Splitters */
|
||||
}
|
||||
|
||||
/* Labels in AudioParam connection should have background that match
|
||||
* the main background so there's whitespace around the label, on top of the
|
||||
* dotted lines. */
|
||||
.theme-dark g.edgeLabel rect {
|
||||
fill: #14171a;
|
||||
}
|
||||
.theme-light g.edgeLabel rect {
|
||||
fill: #fcfcfc; /* Background - Editor */
|
||||
}
|
||||
.theme-dark g.edgeLabel tspan {
|
||||
fill: #b6babf; /* Grey foreground text */
|
||||
}
|
||||
.theme-light g.edgeLabel tspan {
|
||||
fill: #585959; /* Grey foreground text */
|
||||
}
|
||||
|
||||
/* Audio Nodes */
|
||||
.nodes rect {
|
||||
stroke-width: 1px;
|
||||
|
|
|
@ -8336,10 +8336,6 @@ if test "$MOZ_DEBUG" -o "$NS_TRACE_MALLOC" -o "$MOZ_DMD"; then
|
|||
MOZ_COMPONENTS_VERSION_SCRIPT_LDFLAGS=
|
||||
fi
|
||||
|
||||
if test "$MOZ_APP_COMPONENT_INCLUDE"; then
|
||||
AC_DEFINE_UNQUOTED(MOZ_APP_COMPONENT_INCLUDE, "$MOZ_APP_COMPONENT_INCLUDE")
|
||||
fi
|
||||
|
||||
dnl ========================================================
|
||||
dnl =
|
||||
dnl = Maintainer debug option (no --enable equivalent)
|
||||
|
|
|
@ -112,7 +112,7 @@ interface nsIContentViewManager : nsISupports
|
|||
readonly attribute nsIContentView rootContentView;
|
||||
};
|
||||
|
||||
[scriptable, builtinclass, uuid(d52ca6a8-8237-4ae0-91d7-7be4f1db24ef)]
|
||||
[scriptable, builtinclass, uuid(55a772b8-855a-4c5f-b4a1-284b6b3bec28)]
|
||||
interface nsIFrameLoader : nsISupports
|
||||
{
|
||||
/**
|
||||
|
@ -288,8 +288,16 @@ interface nsIFrameLoader : nsISupports
|
|||
|
||||
/**
|
||||
* Find out whether the owner content really is a browser or app frame
|
||||
* Especially, a widget frame is regarded as an app frame.
|
||||
*/
|
||||
readonly attribute boolean ownerIsBrowserOrAppFrame;
|
||||
|
||||
/**
|
||||
* Find out whether the owner content really is a widget. If this attribute
|
||||
* returns true, |ownerIsBrowserOrAppFrame| must return true.
|
||||
*/
|
||||
readonly attribute boolean ownerIsWidget;
|
||||
|
||||
};
|
||||
|
||||
%{C++
|
||||
|
|
|
@ -1473,6 +1473,22 @@ nsFrameLoader::GetOwnerIsBrowserOrAppFrame(bool* aResult)
|
|||
return NS_OK;
|
||||
}
|
||||
|
||||
bool
|
||||
nsFrameLoader::OwnerIsWidget()
|
||||
{
|
||||
nsCOMPtr<nsIMozBrowserFrame> browserFrame = do_QueryInterface(mOwnerContent);
|
||||
return browserFrame ? browserFrame->GetReallyIsWidget() : false;
|
||||
}
|
||||
|
||||
|
||||
// The xpcom getter version
|
||||
NS_IMETHODIMP
|
||||
nsFrameLoader::GetOwnerIsWidget(bool* aResult)
|
||||
{
|
||||
*aResult = OwnerIsWidget();
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
bool
|
||||
nsFrameLoader::OwnerIsAppFrame()
|
||||
{
|
||||
|
|
|
@ -333,6 +333,12 @@ private:
|
|||
*/
|
||||
bool OwnerIsBrowserOrAppFrame();
|
||||
|
||||
/**
|
||||
* Is this a frameloader for a bona fide <iframe mozwidget>? (I.e., does the
|
||||
* frame return true for nsIMozBrowserFrame::GetReallyIsWidget()?)
|
||||
*/
|
||||
bool OwnerIsWidget();
|
||||
|
||||
/**
|
||||
* Is this a frameloader for a bona fide <iframe mozapp>? (I.e., does the
|
||||
* frame return true for nsIMozBrowserFrame::GetReallyIsApp()?)
|
||||
|
|
|
@ -91,6 +91,7 @@ GK_ATOM(_and, "and")
|
|||
GK_ATOM(anonid, "anonid")
|
||||
GK_ATOM(any, "any")
|
||||
GK_ATOM(mozapp, "mozapp")
|
||||
GK_ATOM(mozwidget, "mozwidget")
|
||||
GK_ATOM(applet, "applet")
|
||||
GK_ATOM(applyImports, "apply-imports")
|
||||
GK_ATOM(applyTemplates, "apply-templates")
|
||||
|
|
|
@ -319,6 +319,44 @@ nsGenericHTMLFrameElement::GetReallyIsApp(bool *aOut)
|
|||
return NS_OK;
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
bool WidgetsEnabled()
|
||||
{
|
||||
static bool sMozWidgetsEnabled = false;
|
||||
static bool sBoolVarCacheInitialized = false;
|
||||
|
||||
if (!sBoolVarCacheInitialized) {
|
||||
sBoolVarCacheInitialized = true;
|
||||
Preferences::AddBoolVarCache(&sMozWidgetsEnabled,
|
||||
"dom.enable_widgets");
|
||||
}
|
||||
|
||||
return sMozWidgetsEnabled;
|
||||
}
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
/* [infallible] */ NS_IMETHODIMP
|
||||
nsGenericHTMLFrameElement::GetReallyIsWidget(bool *aOut)
|
||||
{
|
||||
*aOut = false;
|
||||
if (!WidgetsEnabled()) {
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsAutoString appManifestURL;
|
||||
GetManifestURLByType(nsGkAtoms::mozapp, appManifestURL);
|
||||
bool isApp = !appManifestURL.IsEmpty();
|
||||
|
||||
nsAutoString widgetManifestURL;
|
||||
GetManifestURLByType(nsGkAtoms::mozwidget, widgetManifestURL);
|
||||
bool isWidget = !widgetManifestURL.IsEmpty();
|
||||
|
||||
*aOut = isWidget && !isApp;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
/* [infallible] */ NS_IMETHODIMP
|
||||
nsGenericHTMLFrameElement::GetIsExpectingSystemMessage(bool *aOut)
|
||||
{
|
||||
|
@ -332,6 +370,63 @@ nsGenericHTMLFrameElement::GetIsExpectingSystemMessage(bool *aOut)
|
|||
return NS_OK;
|
||||
}
|
||||
|
||||
/** Get manifest url of app or widget
|
||||
* @param AppType: nsGkAtoms::mozapp or nsGkAtoms::mozwidget
|
||||
*/
|
||||
void nsGenericHTMLFrameElement::GetManifestURLByType(nsIAtom *aAppType,
|
||||
nsAString& aManifestURL)
|
||||
{
|
||||
aManifestURL.Truncate();
|
||||
|
||||
if (aAppType != nsGkAtoms::mozapp && aAppType != nsGkAtoms::mozwidget) {
|
||||
return;
|
||||
}
|
||||
|
||||
nsAutoString manifestURL;
|
||||
GetAttr(kNameSpaceID_None, aAppType, manifestURL);
|
||||
if (manifestURL.IsEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check permission.
|
||||
nsCOMPtr<nsIPermissionManager> permMgr = services::GetPermissionManager();
|
||||
NS_ENSURE_TRUE_VOID(permMgr);
|
||||
nsIPrincipal *principal = NodePrincipal();
|
||||
const char* aPermissionType = (aAppType == nsGkAtoms::mozapp) ? "embed-apps"
|
||||
: "embed-widgets";
|
||||
uint32_t permission = nsIPermissionManager::DENY_ACTION;
|
||||
nsresult rv = permMgr->TestPermissionFromPrincipal(principal,
|
||||
aPermissionType,
|
||||
&permission);
|
||||
NS_ENSURE_SUCCESS_VOID(rv);
|
||||
if (permission != nsIPermissionManager::ALLOW_ACTION) {
|
||||
return;
|
||||
}
|
||||
|
||||
nsCOMPtr<nsIAppsService> appsService = do_GetService(APPS_SERVICE_CONTRACTID);
|
||||
NS_ENSURE_TRUE_VOID(appsService);
|
||||
|
||||
nsCOMPtr<mozIApplication> app;
|
||||
appsService->GetAppByManifestURL(manifestURL, getter_AddRefs(app));
|
||||
|
||||
if (!app) {
|
||||
return;
|
||||
}
|
||||
|
||||
bool hasWidgetPage = false;
|
||||
nsAutoString src;
|
||||
if (aAppType == nsGkAtoms::mozwidget) {
|
||||
GetAttr(kNameSpaceID_None, nsGkAtoms::src, src);
|
||||
nsresult rv = app->HasWidgetPage(src, &hasWidgetPage);
|
||||
|
||||
if (!NS_SUCCEEDED(rv) || !hasWidgetPage) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
aManifestURL.Assign(manifestURL);
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsGenericHTMLFrameElement::GetAppManifestURL(nsAString& aOut)
|
||||
{
|
||||
|
@ -342,35 +437,36 @@ nsGenericHTMLFrameElement::GetAppManifestURL(nsAString& aOut)
|
|||
return NS_OK;
|
||||
}
|
||||
|
||||
// Check permission.
|
||||
nsIPrincipal *principal = NodePrincipal();
|
||||
nsCOMPtr<nsIPermissionManager> permMgr =
|
||||
services::GetPermissionManager();
|
||||
NS_ENSURE_TRUE(permMgr, NS_OK);
|
||||
nsAutoString appManifestURL;
|
||||
nsAutoString widgetManifestURL;
|
||||
|
||||
uint32_t permission = nsIPermissionManager::DENY_ACTION;
|
||||
nsresult rv = permMgr->TestPermissionFromPrincipal(principal,
|
||||
"embed-apps",
|
||||
&permission);
|
||||
NS_ENSURE_SUCCESS(rv, NS_OK);
|
||||
if (permission != nsIPermissionManager::ALLOW_ACTION) {
|
||||
GetManifestURLByType(nsGkAtoms::mozapp, appManifestURL);
|
||||
|
||||
if (WidgetsEnabled()) {
|
||||
GetManifestURLByType(nsGkAtoms::mozwidget, widgetManifestURL);
|
||||
}
|
||||
|
||||
bool isApp = !appManifestURL.IsEmpty();
|
||||
bool isWidget = !widgetManifestURL.IsEmpty();
|
||||
|
||||
if (!isApp && !isWidget) {
|
||||
// No valid case to get manifest
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
if (isApp && isWidget) {
|
||||
NS_WARNING("Can not simultaneously be mozapp and mozwidget");
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsAutoString manifestURL;
|
||||
GetAttr(kNameSpaceID_None, nsGkAtoms::mozapp, manifestURL);
|
||||
if (manifestURL.IsEmpty()) {
|
||||
return NS_OK;
|
||||
if (isApp) {
|
||||
manifestURL.Assign(appManifestURL);
|
||||
} else if (isWidget) {
|
||||
manifestURL.Assign(widgetManifestURL);
|
||||
}
|
||||
|
||||
nsCOMPtr<nsIAppsService> appsService = do_GetService(APPS_SERVICE_CONTRACTID);
|
||||
NS_ENSURE_TRUE(appsService, NS_OK);
|
||||
|
||||
nsCOMPtr<mozIApplication> app;
|
||||
appsService->GetAppByManifestURL(manifestURL, getter_AddRefs(app));
|
||||
if (app) {
|
||||
aOut.Assign(manifestURL);
|
||||
}
|
||||
aOut.Assign(manifestURL);
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
|
|
@ -88,6 +88,9 @@ protected:
|
|||
{
|
||||
return this;
|
||||
}
|
||||
|
||||
private:
|
||||
void GetManifestURLByType(nsIAtom *aAppType, nsAString& aOut);
|
||||
};
|
||||
|
||||
#endif // nsGenericHTMLFrameElement_h
|
||||
|
|
|
@ -1518,6 +1518,14 @@ nsTextEditorState::UnbindFromFrame(nsTextControlFrame* aFrame)
|
|||
NS_ASSERTION(!aFrame || aFrame == mBoundFrame, "Unbinding from the wrong frame");
|
||||
NS_ENSURE_TRUE_VOID(!aFrame || aFrame == mBoundFrame);
|
||||
|
||||
// If the editor is modified but nsIEditorObserver::EditAction() hasn't been
|
||||
// called yet, we need to notify it here because editor may be destroyed
|
||||
// before EditAction() is called if selection listener causes flushing layout.
|
||||
if (mTextListener && mEditor && mEditorInitialized &&
|
||||
mEditor->GetIsInEditAction()) {
|
||||
mTextListener->EditAction();
|
||||
}
|
||||
|
||||
// We need to start storing the value outside of the editor if we're not
|
||||
// going to use it anymore, so retrieve it for now.
|
||||
nsAutoString value;
|
||||
|
|
|
@ -17,46 +17,45 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=833386
|
|||
</div>
|
||||
<pre id="test">
|
||||
<script class="testbody" type="text/javascript">
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
SpecialPowers.pushPrefEnv({"set": [["media.webvtt.enabled", true]]},
|
||||
function() {
|
||||
reflectLimitedEnumerated({
|
||||
element: document.createElement("track"),
|
||||
attribute: "kind",
|
||||
validValues: ["subtitles", "captions", "descriptions", "chapters", "metadata"],
|
||||
invalidValues: ["foo", "bar", "\u0000", "null", "", "subtitle", "caption", "description", "chapter", "meta"],
|
||||
defaultValue: "subtitles"
|
||||
});
|
||||
// Default attribute
|
||||
reflectBoolean({
|
||||
element: document.createElement("track"),
|
||||
attribute: "default"
|
||||
});
|
||||
// Label attribute
|
||||
reflectString({
|
||||
element: document.createElement("track"),
|
||||
attribute: "label",
|
||||
otherValues: [ "foo", "BAR", "_FoO", "\u0000", "null", "white space" ]
|
||||
});
|
||||
// Source attribute
|
||||
reflectURL({
|
||||
element: document.createElement("track"),
|
||||
attribute: "src",
|
||||
otherValues: ["foo", "bar", "\u0000", "null", ""]
|
||||
});
|
||||
// Source Language attribute
|
||||
reflectString({
|
||||
element: document.createElement("track"),
|
||||
attribute: "srclang",
|
||||
otherValues: ["foo", "bar", "\u0000", "null", ""]
|
||||
});
|
||||
reflectLimitedEnumerated({
|
||||
element: document.createElement("track"),
|
||||
attribute: "kind",
|
||||
validValues: ["subtitles", "captions", "descriptions", "chapters",
|
||||
"metadata"],
|
||||
invalidValues: ["foo", "bar", "\u0000", "null", "", "subtitle", "caption",
|
||||
"description", "chapter", "meta"],
|
||||
defaultValue: "subtitles"
|
||||
});
|
||||
|
||||
var track = document.createElement("track");
|
||||
is(track.readyState, 0, "Default ready state should be 0 (NONE).");
|
||||
// Default attribute
|
||||
reflectBoolean({
|
||||
element: document.createElement("track"),
|
||||
attribute: "default"
|
||||
});
|
||||
|
||||
SimpleTest.finish();
|
||||
}
|
||||
);
|
||||
// Label attribute
|
||||
reflectString({
|
||||
element: document.createElement("track"),
|
||||
attribute: "label",
|
||||
otherValues: [ "foo", "BAR", "_FoO", "\u0000", "null", "white space" ]
|
||||
});
|
||||
|
||||
// Source attribute
|
||||
reflectURL({
|
||||
element: document.createElement("track"),
|
||||
attribute: "src",
|
||||
otherValues: ["foo", "bar", "\u0000", "null", ""]
|
||||
});
|
||||
|
||||
// Source Language attribute
|
||||
reflectString({
|
||||
element: document.createElement("track"),
|
||||
attribute: "srclang",
|
||||
otherValues: ["foo", "bar", "\u0000", "null", ""]
|
||||
});
|
||||
|
||||
var track = document.createElement("track");
|
||||
is(track.readyState, 0, "Default ready state should be 0 (NONE).");
|
||||
</script>
|
||||
</pre>
|
||||
</body>
|
||||
|
|
|
@ -119,8 +119,11 @@ public:
|
|||
// required to begin playback have been acquired. Can be called on any thread.
|
||||
virtual void NotifyWaitingForResourcesStatusChanged() = 0;
|
||||
|
||||
// Called by Reader if the current audio track can be offloaded
|
||||
virtual void SetCanOffloadAudio(bool aCanOffloadAudio) {}
|
||||
// Set by Reader if the current audio track can be offloaded
|
||||
virtual void SetPlatformCanOffloadAudio(bool aCanOffloadAudio) {}
|
||||
|
||||
// Called by Decoder/State machine to check audio offload condtions are met
|
||||
virtual bool CheckDecoderCanOffloadAudio() { return false; }
|
||||
|
||||
// Called from HTMLMediaElement when owner document activity changes
|
||||
virtual void SetElementVisibility(bool aIsVisible) {}
|
||||
|
|
|
@ -324,7 +324,7 @@ AudioNodeExternalInputStream::ProcessInput(GraphTime aFrom, GraphTime aTo,
|
|||
|
||||
// GC stuff can result in our input stream being destroyed before this stream.
|
||||
// Handle that.
|
||||
if (!IsEnabled() || mInputs.IsEmpty()) {
|
||||
if (!IsEnabled() || mInputs.IsEmpty() || mPassThrough) {
|
||||
mLastChunks[0].SetNull(WEBAUDIO_BLOCK_SIZE);
|
||||
AdvanceOutputSegment();
|
||||
return;
|
||||
|
|
|
@ -1135,6 +1135,11 @@ void MediaDecoderStateMachine::StartPlayback()
|
|||
NS_ASSERTION(!IsPlaying(), "Shouldn't be playing when StartPlayback() is called");
|
||||
AssertCurrentThreadInMonitor();
|
||||
|
||||
if (mDecoder->CheckDecoderCanOffloadAudio()) {
|
||||
DECODER_LOG(PR_LOG_DEBUG, "Offloading playback");
|
||||
return;
|
||||
}
|
||||
|
||||
mDecoder->NotifyPlaybackStarted();
|
||||
SetPlayStartTime(TimeStamp::Now());
|
||||
|
||||
|
|
|
@ -70,10 +70,12 @@ NS_IMPL_RELEASE_INHERITED(MediaRecorder, DOMEventTargetHelper)
|
|||
* Release session resource and remove associated streams from MSG.
|
||||
*
|
||||
* Lifetime of MediaRecorder and Session objects.
|
||||
* 1) MediaRecorder creates a Session in MediaRecorder::Start function.
|
||||
* And the Session registers itself to ShutdownObserver and also holds a
|
||||
* MediaRecorder. Therefore, the reference dependency in gecko is:
|
||||
* ShutdownObserver -> Session -> MediaRecorder
|
||||
* 1) MediaRecorder creates a Session in MediaRecorder::Start function and holds
|
||||
* a reference to Session. Then the Session registers itself to
|
||||
* ShutdownObserver and also holds a reference to MediaRecorder.
|
||||
* Therefore, the reference dependency in gecko is:
|
||||
* ShutdownObserver -> Session <-> MediaRecorder, note that there is a cycle
|
||||
* reference between Session and MediaRecorder.
|
||||
* 2) A Session is destroyed in DestroyRunnable after MediaRecorder::Stop being called
|
||||
* _and_ all encoded media data been passed to OnDataAvailable handler.
|
||||
* 3) MediaRecorder::Stop is called by user or the document is going to
|
||||
|
@ -150,18 +152,11 @@ class MediaRecorder::Session: public nsIObserver
|
|||
class ExtractRunnable : public nsRunnable
|
||||
{
|
||||
public:
|
||||
ExtractRunnable(already_AddRefed<Session>&& aSession)
|
||||
ExtractRunnable(Session* aSession)
|
||||
: mSession(aSession) {}
|
||||
|
||||
~ExtractRunnable()
|
||||
{
|
||||
if (mSession) {
|
||||
NS_WARNING("~ExtractRunnable something wrong the mSession should null");
|
||||
nsCOMPtr<nsIThread> mainThread = do_GetMainThread();
|
||||
NS_WARN_IF_FALSE(mainThread, "Couldn't get the main thread!");
|
||||
NS_ProxyRelease(mainThread, mSession);
|
||||
}
|
||||
}
|
||||
{}
|
||||
|
||||
NS_IMETHODIMP Run()
|
||||
{
|
||||
|
@ -170,16 +165,15 @@ class MediaRecorder::Session: public nsIObserver
|
|||
LOG(PR_LOG_DEBUG, ("Session.ExtractRunnable shutdown = %d", mSession->mEncoder->IsShutdown()));
|
||||
if (!mSession->mEncoder->IsShutdown()) {
|
||||
mSession->Extract(false);
|
||||
nsRefPtr<nsIRunnable> event = new ExtractRunnable(mSession.forget());
|
||||
nsRefPtr<nsIRunnable> event = new ExtractRunnable(mSession);
|
||||
if (NS_FAILED(NS_DispatchToCurrentThread(event))) {
|
||||
NS_WARNING("Failed to dispatch ExtractRunnable to encoder thread");
|
||||
}
|
||||
} else {
|
||||
// Flush out remaining encoded data.
|
||||
mSession->Extract(true);
|
||||
// Destroy this Session object in main thread.
|
||||
if (NS_FAILED(NS_DispatchToMainThread(
|
||||
new DestroyRunnable(mSession.forget())))) {
|
||||
new DestroyRunnable(mSession)))) {
|
||||
MOZ_ASSERT(false, "NS_DispatchToMainThread DestroyRunnable failed");
|
||||
}
|
||||
}
|
||||
|
@ -224,7 +218,7 @@ class MediaRecorder::Session: public nsIObserver
|
|||
class DestroyRunnable : public nsRunnable
|
||||
{
|
||||
public:
|
||||
DestroyRunnable(already_AddRefed<Session>&& aSession)
|
||||
DestroyRunnable(Session* aSession)
|
||||
: mSession(aSession) {}
|
||||
|
||||
NS_IMETHODIMP Run()
|
||||
|
@ -246,8 +240,7 @@ class MediaRecorder::Session: public nsIObserver
|
|||
ErrorResult result;
|
||||
mSession->mStopIssued = true;
|
||||
recorder->Stop(result);
|
||||
if (NS_FAILED(NS_DispatchToMainThread(
|
||||
new DestroyRunnable(mSession.forget())))) {
|
||||
if (NS_FAILED(NS_DispatchToMainThread(new DestroyRunnable(mSession)))) {
|
||||
MOZ_ASSERT(false, "NS_DispatchToMainThread failed");
|
||||
}
|
||||
return NS_OK;
|
||||
|
@ -257,8 +250,7 @@ class MediaRecorder::Session: public nsIObserver
|
|||
mSession->mMimeType = NS_LITERAL_STRING("");
|
||||
recorder->SetMimeType(mSession->mMimeType);
|
||||
recorder->DispatchSimpleEvent(NS_LITERAL_STRING("stop"));
|
||||
recorder->RemoveSession(mSession);
|
||||
mSession->mRecorder = nullptr;
|
||||
mSession->BreakCycle();
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
|
@ -454,8 +446,7 @@ private:
|
|||
// shutdown notification and stop Read Thread.
|
||||
nsContentUtils::RegisterShutdownObserver(this);
|
||||
|
||||
already_AddRefed<Session> session(this); // addref in MediaRecorder::Start
|
||||
nsRefPtr<nsIRunnable> event = new ExtractRunnable(Move(session));
|
||||
nsRefPtr<nsIRunnable> event = new ExtractRunnable(this);
|
||||
if (NS_FAILED(mReadThread->Dispatch(event, NS_DISPATCH_NORMAL))) {
|
||||
NS_WARNING("Failed to dispatch ExtractRunnable at beginning");
|
||||
}
|
||||
|
@ -472,9 +463,7 @@ private:
|
|||
if (NS_FAILED(NS_DispatchToMainThread(new PushBlobRunnable(this)))) {
|
||||
MOZ_ASSERT(false, "NS_DispatchToMainThread PushBlobRunnable failed");
|
||||
}
|
||||
// Destroy this session object in main thread.
|
||||
already_AddRefed<Session> session(this); // addref in MediaRecorder::Start
|
||||
if (NS_FAILED(NS_DispatchToMainThread(new DestroyRunnable(Move(session))))) {
|
||||
if (NS_FAILED(NS_DispatchToMainThread(new DestroyRunnable(this)))) {
|
||||
MOZ_ASSERT(false, "NS_DispatchToMainThread DestroyRunnable failed");
|
||||
}
|
||||
}
|
||||
|
@ -502,16 +491,23 @@ private:
|
|||
mReadThread->Shutdown();
|
||||
mReadThread = nullptr;
|
||||
}
|
||||
if (mRecorder) {
|
||||
mRecorder->RemoveSession(this);
|
||||
mRecorder = nullptr;
|
||||
}
|
||||
BreakCycle();
|
||||
Stop();
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
// Break the cycle reference between Session and MediaRecorder.
|
||||
void BreakCycle()
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
if (mRecorder) {
|
||||
mRecorder->RemoveSession(this);
|
||||
mRecorder = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
// Hold reference to MediaRecoder that ensure MediaRecorder is alive
|
||||
// if there is an active session. Access ONLY on main thread.
|
||||
|
@ -640,16 +636,8 @@ MediaRecorder::Start(const Optional<int32_t>& aTimeSlice, ErrorResult& aResult)
|
|||
|
||||
mState = RecordingState::Recording;
|
||||
// Start a session.
|
||||
// Add session's reference here and pass to ExtractRunnable since the
|
||||
// MediaRecorder doesn't hold any reference to Session. Also
|
||||
// the DoSessionEndTask need this reference for DestroyRunnable.
|
||||
// Note that the reference count is not balance here due to the
|
||||
// DestroyRunnable will destroyed the last reference.
|
||||
nsRefPtr<Session> session = new Session(this, timeSlice);
|
||||
Session* rawPtr;
|
||||
session.forget(&rawPtr);
|
||||
mSessions.AppendElement();
|
||||
mSessions.LastElement() = rawPtr;
|
||||
mSessions.LastElement() = new Session(this, timeSlice);
|
||||
mSessions.LastElement()->Start();
|
||||
}
|
||||
|
||||
|
|
|
@ -111,9 +111,9 @@ protected:
|
|||
nsRefPtr<DOMMediaStream> mStream;
|
||||
// The current state of the MediaRecorder object.
|
||||
RecordingState mState;
|
||||
// Hold the sessions pointer and clean it when the DestroyRunnable for a
|
||||
// Hold the sessions reference and clean it when the DestroyRunnable for a
|
||||
// session is running.
|
||||
nsTArray<Session*> mSessions;
|
||||
nsTArray<nsRefPtr<Session> > mSessions;
|
||||
// It specifies the container format as well as the audio and video capture formats.
|
||||
nsString mMimeType;
|
||||
|
||||
|
|
|
@ -399,6 +399,32 @@ GMPChild::GetGMPTimers()
|
|||
return mTimerChild;
|
||||
}
|
||||
|
||||
PGMPStorageChild*
|
||||
GMPChild::AllocPGMPStorageChild()
|
||||
{
|
||||
return new GMPStorageChild(this);
|
||||
}
|
||||
|
||||
bool
|
||||
GMPChild::DeallocPGMPStorageChild(PGMPStorageChild* aActor)
|
||||
{
|
||||
mStorage = nullptr;
|
||||
return true;
|
||||
}
|
||||
|
||||
GMPStorageChild*
|
||||
GMPChild::GetGMPStorage()
|
||||
{
|
||||
if (!mStorage) {
|
||||
PGMPStorageChild* sc = SendPGMPStorageConstructor();
|
||||
if (!sc) {
|
||||
return nullptr;
|
||||
}
|
||||
mStorage = static_cast<GMPStorageChild*>(sc);
|
||||
}
|
||||
return mStorage;
|
||||
}
|
||||
|
||||
bool
|
||||
GMPChild::RecvCrashPluginNow()
|
||||
{
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
#include "mozilla/gmp/PGMPChild.h"
|
||||
#include "GMPSharedMemManager.h"
|
||||
#include "GMPTimerChild.h"
|
||||
#include "GMPStorageChild.h"
|
||||
#include "gmp-async-shutdown.h"
|
||||
#include "gmp-entrypoints.h"
|
||||
#include "prlink.h"
|
||||
|
@ -37,6 +38,7 @@ public:
|
|||
|
||||
// Main thread only.
|
||||
GMPTimerChild* GetGMPTimers();
|
||||
GMPStorageChild* GetGMPStorage();
|
||||
|
||||
// GMPSharedMem
|
||||
virtual void CheckThread() MOZ_OVERRIDE;
|
||||
|
@ -67,6 +69,9 @@ private:
|
|||
virtual PGMPTimerChild* AllocPGMPTimerChild() MOZ_OVERRIDE;
|
||||
virtual bool DeallocPGMPTimerChild(PGMPTimerChild* aActor) MOZ_OVERRIDE;
|
||||
|
||||
virtual PGMPStorageChild* AllocPGMPStorageChild() MOZ_OVERRIDE;
|
||||
virtual bool DeallocPGMPStorageChild(PGMPStorageChild* aActor) MOZ_OVERRIDE;
|
||||
|
||||
virtual bool RecvCrashPluginNow() MOZ_OVERRIDE;
|
||||
virtual bool RecvBeginAsyncShutdown() MOZ_OVERRIDE;
|
||||
|
||||
|
@ -75,6 +80,7 @@ private:
|
|||
|
||||
GMPAsyncShutdown* mAsyncShutdown;
|
||||
nsRefPtr<GMPTimerChild> mTimerChild;
|
||||
nsRefPtr<GMPStorageChild> mStorage;
|
||||
|
||||
PRLibrary* mLib;
|
||||
GMPGetAPIFunc mGetAPIFunc;
|
||||
|
|
|
@ -161,6 +161,12 @@ GMPParent::CloseIfUnused()
|
|||
mVideoEncoders.IsEmpty() &&
|
||||
mDecryptors.IsEmpty() &&
|
||||
mAudioDecoders.IsEmpty()) {
|
||||
|
||||
// Ensure all timers are killed.
|
||||
for (uint32_t i = mTimers.Length(); i > 0; i--) {
|
||||
mTimers[i - 1]->Shutdown();
|
||||
}
|
||||
|
||||
if (mAsyncShutdownRequired) {
|
||||
if (!mAsyncShutdownInProgress) {
|
||||
LOGD(("%s::%s: %p sending async shutdown notification", __CLASS__,
|
||||
|
@ -171,6 +177,10 @@ GMPParent::CloseIfUnused()
|
|||
}
|
||||
}
|
||||
} else {
|
||||
// Any async shutdown must be complete. Shutdown GMPStorage.
|
||||
for (size_t i = mStorage.Length(); i > 0; i--) {
|
||||
mStorage[i - 1]->Shutdown();
|
||||
}
|
||||
Shutdown();
|
||||
}
|
||||
}
|
||||
|
@ -218,24 +228,23 @@ GMPParent::CloseActive(bool aDieWhenUnloaded)
|
|||
mVideoDecoders[i - 1]->Shutdown();
|
||||
}
|
||||
|
||||
// Invalidate and remove any remaining API objects.
|
||||
for (uint32_t i = mVideoEncoders.Length(); i > 0; i--) {
|
||||
mVideoEncoders[i - 1]->Shutdown();
|
||||
}
|
||||
|
||||
// Invalidate and remove any remaining API objects.
|
||||
for (uint32_t i = mDecryptors.Length(); i > 0; i--) {
|
||||
mDecryptors[i - 1]->Shutdown();
|
||||
}
|
||||
|
||||
// Invalidate and remove any remaining API objects.
|
||||
for (uint32_t i = mAudioDecoders.Length(); i > 0; i--) {
|
||||
mAudioDecoders[i - 1]->Shutdown();
|
||||
}
|
||||
|
||||
for (uint32_t i = mTimers.Length(); i > 0; i--) {
|
||||
mTimers[i - 1]->Shutdown();
|
||||
}
|
||||
// Note: we don't shutdown timers here, we do that in CloseIfUnused(),
|
||||
// as there are multiple entry points to CloseIfUnused().
|
||||
|
||||
// Note: We don't shutdown storage API objects here, as they need to
|
||||
// work during async shutdown of GMPs.
|
||||
|
||||
// Note: the shutdown of the codecs is async! don't kill
|
||||
// the plugin-container until they're all safely shut down via
|
||||
|
@ -658,6 +667,29 @@ GMPParent::DeallocPGMPAudioDecoderParent(PGMPAudioDecoderParent* aActor)
|
|||
return true;
|
||||
}
|
||||
|
||||
PGMPStorageParent*
|
||||
GMPParent::AllocPGMPStorageParent()
|
||||
{
|
||||
GMPStorageParent* p = new GMPStorageParent(mOrigin, this);
|
||||
mStorage.AppendElement(p); // Addrefs, released in DeallocPGMPStorageParent.
|
||||
return p;
|
||||
}
|
||||
|
||||
bool
|
||||
GMPParent::DeallocPGMPStorageParent(PGMPStorageParent* aActor)
|
||||
{
|
||||
GMPStorageParent* p = static_cast<GMPStorageParent*>(aActor);
|
||||
p->Shutdown();
|
||||
mStorage.RemoveElement(p);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
GMPParent::RecvPGMPStorageConstructor(PGMPStorageParent* actor)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
GMPParent::RecvPGMPTimerConstructor(PGMPTimerParent* actor)
|
||||
{
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
#include "GMPVideoDecoderParent.h"
|
||||
#include "GMPVideoEncoderParent.h"
|
||||
#include "GMPTimerParent.h"
|
||||
#include "GMPStorageParent.h"
|
||||
#include "mozilla/gmp/PGMPParent.h"
|
||||
#include "nsCOMPtr.h"
|
||||
#include "nscore.h"
|
||||
|
@ -154,6 +155,10 @@ private:
|
|||
virtual PGMPAudioDecoderParent* AllocPGMPAudioDecoderParent() MOZ_OVERRIDE;
|
||||
virtual bool DeallocPGMPAudioDecoderParent(PGMPAudioDecoderParent* aActor) MOZ_OVERRIDE;
|
||||
|
||||
virtual bool RecvPGMPStorageConstructor(PGMPStorageParent* actor) MOZ_OVERRIDE;
|
||||
virtual PGMPStorageParent* AllocPGMPStorageParent() MOZ_OVERRIDE;
|
||||
virtual bool DeallocPGMPStorageParent(PGMPStorageParent* aActor) MOZ_OVERRIDE;
|
||||
|
||||
virtual bool RecvPGMPTimerConstructor(PGMPTimerParent* actor) MOZ_OVERRIDE;
|
||||
virtual PGMPTimerParent* AllocPGMPTimerParent() MOZ_OVERRIDE;
|
||||
virtual bool DeallocPGMPTimerParent(PGMPTimerParent* aActor) MOZ_OVERRIDE;
|
||||
|
@ -177,6 +182,7 @@ private:
|
|||
nsTArray<nsRefPtr<GMPDecryptorParent>> mDecryptors;
|
||||
nsTArray<nsRefPtr<GMPAudioDecoderParent>> mAudioDecoders;
|
||||
nsTArray<nsRefPtr<GMPTimerParent>> mTimers;
|
||||
nsTArray<nsRefPtr<GMPStorageParent>> mStorage;
|
||||
nsCOMPtr<nsIThread> mGMPThread;
|
||||
// Origin the plugin is assigned to, or empty if the the plugin is not
|
||||
// assigned to an origin.
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "GMPPlatform.h"
|
||||
#include "GMPStorageChild.h"
|
||||
#include "GMPTimerChild.h"
|
||||
#include "mozilla/Monitor.h"
|
||||
#include "nsAutoPtr.h"
|
||||
|
@ -151,6 +152,30 @@ CreateMutex(GMPMutex** aMutex)
|
|||
return GMPNoErr;
|
||||
}
|
||||
|
||||
GMPErr
|
||||
CreateRecord(const char* aRecordName,
|
||||
uint32_t aRecordNameSize,
|
||||
GMPRecord** aOutRecord,
|
||||
GMPRecordClient* aClient)
|
||||
{
|
||||
if (sMainLoop != MessageLoop::current()) {
|
||||
NS_WARNING("GMP called CreateRecord() on non-main thread!");
|
||||
return GMPGenericErr;
|
||||
}
|
||||
if (aRecordNameSize > GMP_MAX_RECORD_NAME_SIZE) {
|
||||
NS_WARNING("GMP tried to CreateRecord with too long record name");
|
||||
return GMPGenericErr;
|
||||
}
|
||||
GMPStorageChild* storage = sChild->GetGMPStorage();
|
||||
if (!storage) {
|
||||
return GMPGenericErr;
|
||||
}
|
||||
MOZ_ASSERT(storage);
|
||||
return storage->CreateRecord(nsDependentCString(aRecordName, aRecordNameSize),
|
||||
aOutRecord,
|
||||
aClient);
|
||||
}
|
||||
|
||||
GMPErr
|
||||
SetTimerOnMainThread(GMPTask* aTask, int64_t aTimeoutMS)
|
||||
{
|
||||
|
@ -184,7 +209,7 @@ InitPlatformAPI(GMPPlatformAPI& aPlatformAPI, GMPChild* aChild)
|
|||
aPlatformAPI.runonmainthread = &RunOnMainThread;
|
||||
aPlatformAPI.syncrunonmainthread = &SyncRunOnMainThread;
|
||||
aPlatformAPI.createmutex = &CreateMutex;
|
||||
aPlatformAPI.createrecord = nullptr;
|
||||
aPlatformAPI.createrecord = &CreateRecord;
|
||||
aPlatformAPI.settimer = &SetTimerOnMainThread;
|
||||
aPlatformAPI.getcurrenttime = &GetClock;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,282 @@
|
|||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "GMPStorageChild.h"
|
||||
#include "GMPChild.h"
|
||||
#include "gmp-storage.h"
|
||||
|
||||
namespace mozilla {
|
||||
namespace gmp {
|
||||
|
||||
GMPRecordImpl::GMPRecordImpl(GMPStorageChild* aOwner,
|
||||
const nsCString& aName,
|
||||
GMPRecordClient* aClient)
|
||||
: mName(aName)
|
||||
, mClient(aClient)
|
||||
, mOwner(aOwner)
|
||||
, mIsClosed(true)
|
||||
{
|
||||
}
|
||||
|
||||
GMPErr
|
||||
GMPRecordImpl::Open()
|
||||
{
|
||||
if (!mIsClosed) {
|
||||
return GMPRecordInUse;
|
||||
}
|
||||
return mOwner->Open(this);
|
||||
}
|
||||
|
||||
void
|
||||
GMPRecordImpl::OpenComplete(GMPErr aStatus)
|
||||
{
|
||||
mIsClosed = false;
|
||||
mClient->OpenComplete(aStatus);
|
||||
}
|
||||
|
||||
GMPErr
|
||||
GMPRecordImpl::Read()
|
||||
{
|
||||
if (mIsClosed) {
|
||||
return GMPClosedErr;
|
||||
}
|
||||
return mOwner->Read(this);
|
||||
}
|
||||
|
||||
void
|
||||
GMPRecordImpl::ReadComplete(GMPErr aStatus,
|
||||
const uint8_t* aBytes,
|
||||
uint32_t aLength)
|
||||
{
|
||||
mClient->ReadComplete(aStatus, aBytes, aLength);
|
||||
}
|
||||
|
||||
GMPErr
|
||||
GMPRecordImpl::Write(const uint8_t* aData, uint32_t aDataSize)
|
||||
{
|
||||
if (mIsClosed) {
|
||||
return GMPClosedErr;
|
||||
}
|
||||
return mOwner->Write(this, aData, aDataSize);
|
||||
}
|
||||
|
||||
void
|
||||
GMPRecordImpl::WriteComplete(GMPErr aStatus)
|
||||
{
|
||||
mClient->WriteComplete(aStatus);
|
||||
}
|
||||
|
||||
GMPErr
|
||||
GMPRecordImpl::Close()
|
||||
{
|
||||
nsRefPtr<GMPRecordImpl> kungfuDeathGrip(this);
|
||||
|
||||
if (!mIsClosed) {
|
||||
// Delete the storage child's reference to us.
|
||||
mOwner->Close(this);
|
||||
// Owner should callback MarkClosed().
|
||||
MOZ_ASSERT(mIsClosed);
|
||||
}
|
||||
|
||||
// Delete our self reference.
|
||||
Release();
|
||||
|
||||
return GMPNoErr;
|
||||
}
|
||||
|
||||
void
|
||||
GMPRecordImpl::MarkClosed()
|
||||
{
|
||||
mIsClosed = true;
|
||||
}
|
||||
|
||||
GMPStorageChild::GMPStorageChild(GMPChild* aPlugin)
|
||||
: mPlugin(aPlugin)
|
||||
, mShutdown(false)
|
||||
{
|
||||
MOZ_ASSERT(mPlugin->GMPMessageLoop() == MessageLoop::current());
|
||||
}
|
||||
|
||||
GMPErr
|
||||
GMPStorageChild::CreateRecord(const nsCString& aRecordName,
|
||||
GMPRecord** aOutRecord,
|
||||
GMPRecordClient* aClient)
|
||||
{
|
||||
if (mPlugin->GMPMessageLoop() != MessageLoop::current()) {
|
||||
NS_WARNING("GMP used GMPStorage on non-main thread.");
|
||||
return GMPGenericErr;
|
||||
}
|
||||
if (mShutdown) {
|
||||
NS_WARNING("GMPStorage used after it's been shutdown!");
|
||||
return GMPClosedErr;
|
||||
}
|
||||
MOZ_ASSERT(aRecordName.Length() && aOutRecord);
|
||||
nsRefPtr<GMPRecordImpl> record(new GMPRecordImpl(this, aRecordName, aClient));
|
||||
mRecords.Put(aRecordName, record); // Addrefs
|
||||
|
||||
// The GMPRecord holds a self reference until the GMP calls Close() on
|
||||
// it. This means the object is always valid (even if neutered) while
|
||||
// the GMP expects it to be.
|
||||
record.forget(aOutRecord);
|
||||
|
||||
return GMPNoErr;
|
||||
}
|
||||
|
||||
GMPErr
|
||||
GMPStorageChild::Open(GMPRecordImpl* aRecord)
|
||||
{
|
||||
if (mPlugin->GMPMessageLoop() != MessageLoop::current()) {
|
||||
NS_WARNING("GMP used GMPStorage on non-main thread.");
|
||||
return GMPGenericErr;
|
||||
}
|
||||
if (mShutdown) {
|
||||
NS_WARNING("GMPStorage used after it's been shutdown!");
|
||||
return GMPClosedErr;
|
||||
}
|
||||
if (!SendOpen(aRecord->Name())) {
|
||||
Close(aRecord);
|
||||
return GMPClosedErr;
|
||||
}
|
||||
return GMPNoErr;
|
||||
}
|
||||
|
||||
GMPErr
|
||||
GMPStorageChild::Read(GMPRecordImpl* aRecord)
|
||||
{
|
||||
if (mPlugin->GMPMessageLoop() != MessageLoop::current()) {
|
||||
NS_WARNING("GMP used GMPStorage on non-main thread.");
|
||||
return GMPGenericErr;
|
||||
}
|
||||
if (mShutdown) {
|
||||
NS_WARNING("GMPStorage used after it's been shutdown!");
|
||||
return GMPClosedErr;
|
||||
}
|
||||
if (!SendRead(aRecord->Name())) {
|
||||
Close(aRecord);
|
||||
return GMPClosedErr;
|
||||
}
|
||||
return GMPNoErr;
|
||||
}
|
||||
|
||||
GMPErr
|
||||
GMPStorageChild::Write(GMPRecordImpl* aRecord,
|
||||
const uint8_t* aData,
|
||||
uint32_t aDataSize)
|
||||
{
|
||||
if (mPlugin->GMPMessageLoop() != MessageLoop::current()) {
|
||||
NS_WARNING("GMP used GMPStorage on non-main thread.");
|
||||
return GMPGenericErr;
|
||||
}
|
||||
if (mShutdown) {
|
||||
NS_WARNING("GMPStorage used after it's been shutdown!");
|
||||
return GMPClosedErr;
|
||||
}
|
||||
if (aDataSize > GMP_MAX_RECORD_SIZE) {
|
||||
return GMPQuotaExceededErr;
|
||||
}
|
||||
nsTArray<uint8_t> data;
|
||||
data.AppendElements(aData, aDataSize);
|
||||
if (!SendWrite(aRecord->Name(), data)) {
|
||||
Close(aRecord);
|
||||
return GMPClosedErr;
|
||||
}
|
||||
return GMPNoErr;
|
||||
}
|
||||
|
||||
GMPErr
|
||||
GMPStorageChild::Close(GMPRecordImpl* aRecord)
|
||||
{
|
||||
if (mPlugin->GMPMessageLoop() != MessageLoop::current()) {
|
||||
NS_WARNING("GMP used GMPStorage on non-main thread.");
|
||||
return GMPGenericErr;
|
||||
}
|
||||
if (!mRecords.Contains(aRecord->Name())) {
|
||||
// Already closed.
|
||||
return GMPClosedErr;
|
||||
}
|
||||
|
||||
GMPErr rv = GMPNoErr;
|
||||
if (!mShutdown && !SendClose(aRecord->Name())) {
|
||||
rv = GMPGenericErr;
|
||||
}
|
||||
|
||||
aRecord->MarkClosed();
|
||||
mRecords.Remove(aRecord->Name());
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
bool
|
||||
GMPStorageChild::RecvOpenComplete(const nsCString& aRecordName,
|
||||
const GMPErr& aStatus)
|
||||
{
|
||||
if (mShutdown) {
|
||||
return true;
|
||||
}
|
||||
nsRefPtr<GMPRecordImpl> record;
|
||||
if (!mRecords.Get(aRecordName, getter_AddRefs(record)) || !record) {
|
||||
// Not fatal.
|
||||
return true;
|
||||
}
|
||||
record->OpenComplete(aStatus);
|
||||
if (GMP_FAILED(aStatus)) {
|
||||
Close(record);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
GMPStorageChild::RecvReadComplete(const nsCString& aRecordName,
|
||||
const GMPErr& aStatus,
|
||||
const InfallibleTArray<uint8_t>& aBytes)
|
||||
{
|
||||
if (mShutdown) {
|
||||
return true;
|
||||
}
|
||||
nsRefPtr<GMPRecordImpl> record;
|
||||
if (!mRecords.Get(aRecordName, getter_AddRefs(record)) || !record) {
|
||||
// Not fatal.
|
||||
return true;
|
||||
}
|
||||
record->ReadComplete(aStatus,
|
||||
aBytes.Elements(),
|
||||
aBytes.Length());
|
||||
if (GMP_FAILED(aStatus)) {
|
||||
Close(record);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
GMPStorageChild::RecvWriteComplete(const nsCString& aRecordName,
|
||||
const GMPErr& aStatus)
|
||||
{
|
||||
if (mShutdown) {
|
||||
return true;
|
||||
}
|
||||
nsRefPtr<GMPRecordImpl> record;
|
||||
if (!mRecords.Get(aRecordName, getter_AddRefs(record)) || !record) {
|
||||
// Not fatal.
|
||||
return true;
|
||||
}
|
||||
record->WriteComplete(aStatus);
|
||||
if (GMP_FAILED(aStatus)) {
|
||||
Close(record);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
GMPStorageChild::RecvShutdown()
|
||||
{
|
||||
// Block any new storage requests, and thus any messages back to the
|
||||
// parent. We don't delete any objects here, as that may invalidate
|
||||
// GMPRecord pointers held by the GMP.
|
||||
mShutdown = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace gmp
|
||||
} // namespace mozilla
|
|
@ -0,0 +1,95 @@
|
|||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#ifndef GMPStorageChild_h_
|
||||
#define GMPStorageChild_h_
|
||||
|
||||
#include "mozilla/gmp/PGMPStorageChild.h"
|
||||
#include "gmp-storage.h"
|
||||
#include "nsTHashtable.h"
|
||||
#include "nsRefPtrHashtable.h"
|
||||
|
||||
namespace mozilla {
|
||||
namespace gmp {
|
||||
|
||||
class GMPChild;
|
||||
class GMPStorageChild;
|
||||
|
||||
class GMPRecordImpl : public GMPRecord
|
||||
{
|
||||
public:
|
||||
NS_INLINE_DECL_REFCOUNTING(GMPRecordImpl)
|
||||
|
||||
GMPRecordImpl(GMPStorageChild* aOwner,
|
||||
const nsCString& aName,
|
||||
GMPRecordClient* aClient);
|
||||
|
||||
// GMPRecord.
|
||||
virtual GMPErr Open() MOZ_OVERRIDE;
|
||||
virtual GMPErr Read() MOZ_OVERRIDE;
|
||||
virtual GMPErr Write(const uint8_t* aData,
|
||||
uint32_t aDataSize) MOZ_OVERRIDE;
|
||||
virtual GMPErr Close() MOZ_OVERRIDE;
|
||||
|
||||
const nsCString& Name() const { return mName; }
|
||||
|
||||
void OpenComplete(GMPErr aStatus);
|
||||
void ReadComplete(GMPErr aStatus, const uint8_t* aBytes, uint32_t aLength);
|
||||
void WriteComplete(GMPErr aStatus);
|
||||
|
||||
void MarkClosed();
|
||||
|
||||
private:
|
||||
~GMPRecordImpl() {}
|
||||
const nsCString mName;
|
||||
GMPRecordClient* const mClient;
|
||||
GMPStorageChild* const mOwner;
|
||||
bool mIsClosed;
|
||||
};
|
||||
|
||||
class GMPStorageChild : public PGMPStorageChild
|
||||
{
|
||||
public:
|
||||
NS_INLINE_DECL_REFCOUNTING(GMPStorageChild)
|
||||
|
||||
GMPStorageChild(GMPChild* aPlugin);
|
||||
|
||||
GMPErr CreateRecord(const nsCString& aRecordName,
|
||||
GMPRecord** aOutRecord,
|
||||
GMPRecordClient* aClient);
|
||||
|
||||
GMPErr Open(GMPRecordImpl* aRecord);
|
||||
|
||||
GMPErr Read(GMPRecordImpl* aRecord);
|
||||
|
||||
GMPErr Write(GMPRecordImpl* aRecord,
|
||||
const uint8_t* aData,
|
||||
uint32_t aDataSize);
|
||||
|
||||
GMPErr Close(GMPRecordImpl* aRecord);
|
||||
|
||||
protected:
|
||||
~GMPStorageChild() {}
|
||||
|
||||
// PGMPStorageChild
|
||||
virtual bool RecvOpenComplete(const nsCString& aRecordName,
|
||||
const GMPErr& aStatus) MOZ_OVERRIDE;
|
||||
virtual bool RecvReadComplete(const nsCString& aRecordName,
|
||||
const GMPErr& aStatus,
|
||||
const InfallibleTArray<uint8_t>& aBytes) MOZ_OVERRIDE;
|
||||
virtual bool RecvWriteComplete(const nsCString& aRecordName,
|
||||
const GMPErr& aStatus) MOZ_OVERRIDE;
|
||||
virtual bool RecvShutdown() MOZ_OVERRIDE;
|
||||
|
||||
private:
|
||||
nsRefPtrHashtable<nsCStringHashKey, GMPRecordImpl> mRecords;
|
||||
GMPChild* mPlugin;
|
||||
bool mShutdown;
|
||||
};
|
||||
|
||||
} // namespace gmp
|
||||
} // namespace mozilla
|
||||
|
||||
#endif // GMPStorageChild_h_
|
|
@ -0,0 +1,287 @@
|
|||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "GMPStorageParent.h"
|
||||
#include "mozilla/SyncRunnable.h"
|
||||
#include "plhash.h"
|
||||
#include "nsDirectoryServiceUtils.h"
|
||||
#include "nsDirectoryServiceDefs.h"
|
||||
#include "GMPParent.h"
|
||||
#include "gmp-storage.h"
|
||||
#include "mozilla/unused.h"
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
#ifdef LOG
|
||||
#undef LOG
|
||||
#endif
|
||||
|
||||
#ifdef PR_LOGGING
|
||||
extern PRLogModuleInfo* GetGMPLog();
|
||||
|
||||
#define LOGD(msg) PR_LOG(GetGMPLog(), PR_LOG_DEBUG, msg)
|
||||
#define LOG(level, msg) PR_LOG(GetGMPLog(), (level), msg)
|
||||
#else
|
||||
#define LOGD(msg)
|
||||
#define LOG(level, msg)
|
||||
#endif
|
||||
|
||||
#ifdef __CLASS__
|
||||
#undef __CLASS__
|
||||
#endif
|
||||
#define __CLASS__ "GMPParent"
|
||||
|
||||
namespace gmp {
|
||||
|
||||
class GetTempDirTask : public nsRunnable
|
||||
{
|
||||
public:
|
||||
NS_IMETHOD Run() {
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
|
||||
nsCOMPtr<nsIFile> tmpFile;
|
||||
nsresult rv = NS_GetSpecialDirectory(NS_OS_TEMP_DIR, getter_AddRefs(tmpFile));
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
tmpFile->GetPath(mPath);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsString mPath;
|
||||
};
|
||||
|
||||
// We store the records in files in the system temp dir.
|
||||
static nsresult
|
||||
GetGMPStorageDir(nsIFile** aTempDir, const nsString& aOrigin)
|
||||
{
|
||||
if (NS_WARN_IF(!aTempDir)) {
|
||||
return NS_ERROR_INVALID_ARG;
|
||||
}
|
||||
|
||||
// Directory service is main thread only...
|
||||
nsRefPtr<GetTempDirTask> task = new GetTempDirTask();
|
||||
nsCOMPtr<nsIThread> mainThread = do_GetMainThread();
|
||||
mozilla::SyncRunnable::DispatchToThread(mainThread, task);
|
||||
|
||||
nsCOMPtr<nsIFile> tmpFile;
|
||||
nsresult rv = NS_NewLocalFile(task->mPath, false, getter_AddRefs(tmpFile));
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
rv = tmpFile->AppendNative(nsDependentCString("mozilla-gmp-storage"));
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
// TODO: When aOrigin is the same node-id as the GMP sees in the child
|
||||
// process (a UUID or somesuch), we can just append it un-hashed here.
|
||||
// This should reduce the chance of hash collsions exposing data.
|
||||
nsAutoString nodeIdHash;
|
||||
nodeIdHash.AppendInt(HashString(aOrigin.get()));
|
||||
rv = tmpFile->Append(nodeIdHash);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
rv = tmpFile->Create(nsIFile::DIRECTORY_TYPE, 0700);
|
||||
if (rv != NS_ERROR_FILE_ALREADY_EXISTS && NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
tmpFile.forget(aTempDir);
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
GMPStorageParent::GMPStorageParent(const nsString& aOrigin,
|
||||
GMPParent* aPlugin)
|
||||
: mOrigin(aOrigin)
|
||||
, mPlugin(aPlugin)
|
||||
, mShutdown(false)
|
||||
{
|
||||
}
|
||||
|
||||
enum OpenFileMode { ReadWrite, Truncate };
|
||||
|
||||
nsresult
|
||||
OpenStorageFile(const nsCString& aRecordName,
|
||||
const nsString& aNodeId,
|
||||
const OpenFileMode aMode,
|
||||
PRFileDesc** aOutFD)
|
||||
{
|
||||
MOZ_ASSERT(aOutFD);
|
||||
|
||||
nsCOMPtr<nsIFile> f;
|
||||
nsresult rv = GetGMPStorageDir(getter_AddRefs(f), aNodeId);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
nsAutoString recordNameHash;
|
||||
recordNameHash.AppendInt(HashString(aRecordName.get()));
|
||||
f->Append(recordNameHash);
|
||||
|
||||
auto mode = PR_RDWR | PR_CREATE_FILE;
|
||||
if (aMode == Truncate) {
|
||||
mode |= PR_TRUNCATE;
|
||||
}
|
||||
|
||||
return f->OpenNSPRFileDesc(mode, PR_IRWXU, aOutFD);
|
||||
}
|
||||
|
||||
bool
|
||||
GMPStorageParent::RecvOpen(const nsCString& aRecordName)
|
||||
{
|
||||
if (mShutdown) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (mOrigin.EqualsASCII("null")) {
|
||||
// Refuse to open storage if the page is the "null" origin; if the page
|
||||
// is opened from disk.
|
||||
NS_WARNING("Refusing to open storage for null origin");
|
||||
unused << SendOpenComplete(aRecordName, GMPGenericErr);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (aRecordName.IsEmpty() || mFiles.Contains(aRecordName)) {
|
||||
unused << SendOpenComplete(aRecordName, GMPRecordInUse);
|
||||
return true;
|
||||
}
|
||||
|
||||
PRFileDesc* fd = nullptr;
|
||||
nsresult rv = OpenStorageFile(aRecordName, mOrigin, ReadWrite, &fd);
|
||||
if (NS_FAILED(rv)) {
|
||||
NS_WARNING("Failed to open storage file.");
|
||||
unused << SendOpenComplete(aRecordName, GMPGenericErr);
|
||||
return true;
|
||||
}
|
||||
|
||||
mFiles.Put(aRecordName, fd);
|
||||
unused << SendOpenComplete(aRecordName, GMPNoErr);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
GMPStorageParent::RecvRead(const nsCString& aRecordName)
|
||||
{
|
||||
LOGD(("%s::%s: %p record=%s", __CLASS__, __FUNCTION__, this, aRecordName.get()));
|
||||
|
||||
if (mShutdown) {
|
||||
return true;
|
||||
}
|
||||
|
||||
PRFileDesc* fd = mFiles.Get(aRecordName);
|
||||
nsTArray<uint8_t> data;
|
||||
if (!fd) {
|
||||
unused << SendReadComplete(aRecordName, GMPClosedErr, data);
|
||||
return true;
|
||||
}
|
||||
|
||||
int32_t len = PR_Seek(fd, 0, PR_SEEK_END);
|
||||
PR_Seek(fd, 0, PR_SEEK_SET);
|
||||
|
||||
if (len > GMP_MAX_RECORD_SIZE) {
|
||||
// Refuse to read big records.
|
||||
unused << SendReadComplete(aRecordName, GMPQuotaExceededErr, data);
|
||||
return true;
|
||||
}
|
||||
data.SetLength(len);
|
||||
auto bytesRead = PR_Read(fd, data.Elements(), len);
|
||||
auto res = (bytesRead == len) ? GMPNoErr : GMPGenericErr;
|
||||
unused << SendReadComplete(aRecordName, res, data);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
GMPStorageParent::RecvWrite(const nsCString& aRecordName,
|
||||
const InfallibleTArray<uint8_t>& aBytes)
|
||||
{
|
||||
LOGD(("%s::%s: %p record=%s", __CLASS__, __FUNCTION__, this, aRecordName.get()));
|
||||
|
||||
if (mShutdown) {
|
||||
return true;
|
||||
}
|
||||
if (aBytes.Length() > GMP_MAX_RECORD_SIZE) {
|
||||
unused << SendWriteComplete(aRecordName, GMPQuotaExceededErr);
|
||||
return true;
|
||||
}
|
||||
|
||||
PRFileDesc* fd = mFiles.Get(aRecordName);
|
||||
if (!fd) {
|
||||
unused << SendWriteComplete(aRecordName, GMPGenericErr);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Write operations overwrite the entire record. So re-open the file
|
||||
// in truncate mode, to clear its contents.
|
||||
PR_Close(fd);
|
||||
mFiles.Remove(aRecordName);
|
||||
if (NS_FAILED(OpenStorageFile(aRecordName, mOrigin, Truncate, &fd))) {
|
||||
unused << SendWriteComplete(aRecordName, GMPGenericErr);
|
||||
return true;
|
||||
}
|
||||
mFiles.Put(aRecordName, fd);
|
||||
|
||||
int32_t bytesWritten = PR_Write(fd, aBytes.Elements(), aBytes.Length());
|
||||
auto res = (bytesWritten == (int32_t)aBytes.Length()) ? GMPNoErr : GMPGenericErr;
|
||||
unused << SendWriteComplete(aRecordName, res);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
GMPStorageParent::RecvClose(const nsCString& aRecordName)
|
||||
{
|
||||
LOGD(("%s::%s: %p record=%s", __CLASS__, __FUNCTION__, this, aRecordName.get()));
|
||||
|
||||
if (mShutdown) {
|
||||
return true;
|
||||
}
|
||||
|
||||
PRFileDesc* fd = mFiles.Get(aRecordName);
|
||||
if (!fd) {
|
||||
return true;
|
||||
}
|
||||
PR_Close(fd);
|
||||
mFiles.Remove(aRecordName);
|
||||
return true;
|
||||
}
|
||||
|
||||
void
|
||||
GMPStorageParent::ActorDestroy(ActorDestroyReason aWhy)
|
||||
{
|
||||
LOGD(("%s::%s: %p", __CLASS__, __FUNCTION__, this));
|
||||
Shutdown();
|
||||
}
|
||||
|
||||
PLDHashOperator
|
||||
CloseFile(const nsACString& key, PRFileDesc*& entry, void* cx)
|
||||
{
|
||||
PR_Close(entry);
|
||||
return PL_DHASH_REMOVE;
|
||||
}
|
||||
|
||||
void
|
||||
GMPStorageParent::Shutdown()
|
||||
{
|
||||
LOGD(("%s::%s: %p", __CLASS__, __FUNCTION__, this));
|
||||
|
||||
if (mShutdown) {
|
||||
return;
|
||||
}
|
||||
mShutdown = true;
|
||||
unused << SendShutdown();
|
||||
|
||||
mFiles.Enumerate(CloseFile, nullptr);
|
||||
MOZ_ASSERT(!mFiles.Count());
|
||||
}
|
||||
|
||||
} // namespace gmp
|
||||
} // namespace mozilla
|
|
@ -0,0 +1,47 @@
|
|||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#ifndef GMPStorageParent_h_
|
||||
#define GMPStorageParent_h_
|
||||
|
||||
#include "mozilla/gmp/PGMPStorageParent.h"
|
||||
#include "gmp-storage.h"
|
||||
#include "nsTHashtable.h"
|
||||
#include "nsDataHashtable.h"
|
||||
#include "prio.h"
|
||||
|
||||
namespace mozilla {
|
||||
namespace gmp {
|
||||
|
||||
class GMPParent;
|
||||
|
||||
class GMPStorageParent : public PGMPStorageParent {
|
||||
public:
|
||||
NS_INLINE_DECL_REFCOUNTING(GMPStorageParent)
|
||||
GMPStorageParent(const nsString& aOrigin, GMPParent* aPlugin);
|
||||
|
||||
void Shutdown();
|
||||
|
||||
protected:
|
||||
virtual bool RecvOpen(const nsCString& aRecordName) MOZ_OVERRIDE;
|
||||
virtual bool RecvRead(const nsCString& aRecordName) MOZ_OVERRIDE;
|
||||
virtual bool RecvWrite(const nsCString& aRecordName,
|
||||
const InfallibleTArray<uint8_t>& aBytes) MOZ_OVERRIDE;
|
||||
virtual bool RecvClose(const nsCString& aRecordName) MOZ_OVERRIDE;
|
||||
virtual void ActorDestroy(ActorDestroyReason aWhy) MOZ_OVERRIDE;
|
||||
|
||||
private:
|
||||
~GMPStorageParent() {}
|
||||
|
||||
nsDataHashtable<nsCStringHashKey, PRFileDesc*> mFiles;
|
||||
const nsString mOrigin;
|
||||
nsRefPtr<GMPParent> mPlugin;
|
||||
bool mShutdown;
|
||||
};
|
||||
|
||||
} // namespace gmp
|
||||
} // namespace mozilla
|
||||
|
||||
#endif // GMPStorageParent_h_
|
|
@ -9,6 +9,7 @@ include protocol PCrashReporter;
|
|||
include protocol PGMPDecryptor;
|
||||
include protocol PGMPAudioDecoder;
|
||||
include protocol PGMPTimer;
|
||||
include protocol PGMPStorage;
|
||||
|
||||
using mozilla::dom::NativeThreadId from "mozilla/dom/TabMessageUtils.h";
|
||||
|
||||
|
@ -23,10 +24,12 @@ intr protocol PGMP
|
|||
manages PGMPVideoEncoder;
|
||||
manages PCrashReporter;
|
||||
manages PGMPTimer;
|
||||
manages PGMPStorage;
|
||||
|
||||
parent:
|
||||
async PCrashReporter(NativeThreadId tid);
|
||||
async PGMPTimer();
|
||||
async PGMPStorage();
|
||||
|
||||
async AsyncShutdownComplete();
|
||||
async AsyncShutdownRequired();
|
||||
|
|
|
@ -0,0 +1,34 @@
|
|||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
include protocol PGMP;
|
||||
include GMPTypes;
|
||||
|
||||
using GMPErr from "gmp-errors.h";
|
||||
|
||||
namespace mozilla {
|
||||
namespace gmp {
|
||||
|
||||
async protocol PGMPStorage
|
||||
{
|
||||
manager PGMP;
|
||||
|
||||
child:
|
||||
OpenComplete(nsCString aRecordName, GMPErr aStatus);
|
||||
ReadComplete(nsCString aRecordName, GMPErr aStatus, uint8_t[] aBytes);
|
||||
WriteComplete(nsCString aRecordName, GMPErr aStatus);
|
||||
Shutdown();
|
||||
|
||||
parent:
|
||||
Open(nsCString aRecordName);
|
||||
Read(nsCString aRecordName);
|
||||
Write(nsCString aRecordName, uint8_t[] aBytes);
|
||||
Close(nsCString aRecordName);
|
||||
__delete__();
|
||||
|
||||
};
|
||||
|
||||
} // namespace gmp
|
||||
} // namespace mozilla
|
|
@ -69,6 +69,8 @@ typedef GMPErr (*GMPCreateThreadPtr)(GMPThread** aThread);
|
|||
typedef GMPErr (*GMPRunOnMainThreadPtr)(GMPTask* aTask);
|
||||
typedef GMPErr (*GMPSyncRunOnMainThreadPtr)(GMPTask* aTask);
|
||||
typedef GMPErr (*GMPCreateMutexPtr)(GMPMutex** aMutex);
|
||||
|
||||
// Call on main thread only.
|
||||
typedef GMPErr (*GMPCreateRecordPtr)(const char* aRecordName,
|
||||
uint32_t aRecordNameSize,
|
||||
GMPRecord** aOutRecord,
|
||||
|
|
|
@ -20,16 +20,31 @@
|
|||
#include "gmp-errors.h"
|
||||
#include <stdint.h>
|
||||
|
||||
// Maximum size of a record, in bytes.
|
||||
#define GMP_MAX_RECORD_SIZE (1024 * 1024 * 1024)
|
||||
|
||||
// Maximum length of a record name.
|
||||
#define GMP_MAX_RECORD_NAME_SIZE 200
|
||||
|
||||
// Provides basic per-origin storage for CDMs. GMPRecord instances can be
|
||||
// retrieved by calling GMPPlatformAPI->openstorage. Multiple GMPRecords
|
||||
// with different names can be open at once, but a single record can only
|
||||
// be opened by one client at a time. This interface is asynchronous, with
|
||||
// results being returned via callbacks to the GMPRecordClient pointer
|
||||
// provided to the GMPPlatformAPI->openstorage call, on the main thread.
|
||||
//
|
||||
// Lifecycle: Once opened, the GMPRecord object remains allocated until
|
||||
// GMPRecord::Close() is called. If any GMPRecord function, either
|
||||
// synchronously or asynchronously through a GMPRecordClient callback,
|
||||
// returns an error, the GMP is responsible for calling Close() on the
|
||||
// GMPRecord to delete the GMPRecord object's memory. If your GMP does not
|
||||
// call Close(), the GMPRecord's memory will leak.
|
||||
class GMPRecord {
|
||||
public:
|
||||
|
||||
// Opens the record. Calls OpenComplete() once the record is open.
|
||||
// Note: Only work when GMP is loading content from a webserver.
|
||||
// Does not work for web pages on loaded from disk.
|
||||
// Note: OpenComplete() is only called if this returns GMPNoErr.
|
||||
virtual GMPErr Open() = 0;
|
||||
|
||||
|
@ -39,13 +54,15 @@ public:
|
|||
virtual GMPErr Read() = 0;
|
||||
|
||||
// Writes aDataSize bytes of aData into the record, overwriting the
|
||||
// contents of the record. Overwriting with 0 bytes "deletes" the file.
|
||||
// contents of the record, truncating it to aDataSize length.
|
||||
// Overwriting with 0 bytes "deletes" the record.
|
||||
// Note: WriteComplete is only called if this returns GMPNoErr.
|
||||
virtual GMPErr Write(const uint8_t* aData, uint32_t aDataSize) = 0;
|
||||
|
||||
// Closes a record. GMPRecord object must not be used after this is
|
||||
// called, request a new one with GMPPlatformAPI->openstorage to re-open
|
||||
// this record. Cancels all callbacks.
|
||||
// Closes a record, deletes the GMPRecord object. The GMPRecord object
|
||||
// must not be used after this is called, request a new one with
|
||||
// GMPPlatformAPI->openstorage to re-open this record. Cancels all
|
||||
// callbacks.
|
||||
virtual GMPErr Close() = 0;
|
||||
|
||||
virtual ~GMPRecord() {}
|
||||
|
@ -61,7 +78,8 @@ class GMPRecordClient {
|
|||
// - GMPNoErr - Record opened successfully. Record may be empty.
|
||||
// - GMPRecordInUse - This record is in use by another client.
|
||||
// - GMPGenericErr - Unspecified error.
|
||||
// Do not use the GMPRecord if aStatus is not GMPNoErr.
|
||||
// If aStatus is not GMPNoErr, the GMPRecord is unusable, and you must
|
||||
// call Close() on the GMPRecord to dispose of it.
|
||||
virtual void OpenComplete(GMPErr aStatus) = 0;
|
||||
|
||||
// Response to a GMPRecord::Read() call, where aData is the record contents,
|
||||
|
@ -74,7 +92,8 @@ class GMPRecordClient {
|
|||
// - GMPRecordInUse - There are other operations or clients in use on
|
||||
// this record.
|
||||
// - GMPGenericErr - Unspecified error.
|
||||
// Do not continue to use the GMPRecord if aStatus is not GMPNoErr.
|
||||
// If aStatus is not GMPNoErr, the GMPRecord is unusable, and you must
|
||||
// call Close() on the GMPRecord to dispose of it.
|
||||
virtual void ReadComplete(GMPErr aStatus,
|
||||
const uint8_t* aData,
|
||||
uint32_t aDataSize) = 0;
|
||||
|
@ -84,7 +103,8 @@ class GMPRecordClient {
|
|||
// - GMPRecordInUse - There are other operations or clients in use on
|
||||
// this record.
|
||||
// - GMPGenericErr - Unspecified error.
|
||||
// Do not continue to use the GMPRecord if aStatus is not GMPNoErr.
|
||||
// If aStatus is not GMPNoErr, the GMPRecord is unusable, and you must
|
||||
// call Close() on the GMPRecord to dispose of it.
|
||||
virtual void WriteComplete(GMPErr aStatus) = 0;
|
||||
|
||||
virtual ~GMPRecordClient() {}
|
||||
|
|
|
@ -46,6 +46,8 @@ EXPORTS += [
|
|||
'GMPProcessParent.h',
|
||||
'GMPService.h',
|
||||
'GMPSharedMemManager.h',
|
||||
'GMPStorageChild.h',
|
||||
'GMPStorageParent.h',
|
||||
'GMPTimerChild.h',
|
||||
'GMPTimerParent.h',
|
||||
'GMPVideoDecoderChild.h',
|
||||
|
@ -74,6 +76,8 @@ UNIFIED_SOURCES += [
|
|||
'GMPProcessParent.cpp',
|
||||
'GMPService.cpp',
|
||||
'GMPSharedMemManager.cpp',
|
||||
'GMPStorageChild.cpp',
|
||||
'GMPStorageParent.cpp',
|
||||
'GMPTimerChild.cpp',
|
||||
'GMPTimerParent.cpp',
|
||||
'GMPVideoDecoderChild.cpp',
|
||||
|
@ -91,6 +95,7 @@ IPDL_SOURCES += [
|
|||
'PGMP.ipdl',
|
||||
'PGMPAudioDecoder.ipdl',
|
||||
'PGMPDecryptor.ipdl',
|
||||
'PGMPStorage.ipdl',
|
||||
'PGMPTimer.ipdl',
|
||||
'PGMPVideoDecoder.ipdl',
|
||||
'PGMPVideoEncoder.ipdl',
|
||||
|
|
|
@ -41,12 +41,19 @@ MediaOmxCommonDecoder::MediaOmxCommonDecoder()
|
|||
}
|
||||
|
||||
void
|
||||
MediaOmxCommonDecoder::SetCanOffloadAudio(bool aCanOffloadAudio)
|
||||
MediaOmxCommonDecoder::SetPlatformCanOffloadAudio(bool aCanOffloadAudio)
|
||||
{
|
||||
ReentrantMonitorAutoEnter mon(GetReentrantMonitor());
|
||||
mCanOffloadAudio = aCanOffloadAudio;
|
||||
}
|
||||
|
||||
bool
|
||||
MediaOmxCommonDecoder::CheckDecoderCanOffloadAudio()
|
||||
{
|
||||
return (mCanOffloadAudio && !mFallbackToStateMachine && !mOutputStreams.Length() &&
|
||||
mInitialPlaybackRate == 1.0);
|
||||
}
|
||||
|
||||
void
|
||||
MediaOmxCommonDecoder::MetadataLoaded(MediaInfo* aInfo,
|
||||
MetadataTags* aTags)
|
||||
|
@ -55,8 +62,7 @@ MediaOmxCommonDecoder::MetadataLoaded(MediaInfo* aInfo,
|
|||
MediaDecoder::MetadataLoaded(aInfo, aTags);
|
||||
|
||||
ReentrantMonitorAutoEnter mon(GetReentrantMonitor());
|
||||
if (!mCanOffloadAudio || mFallbackToStateMachine || mOutputStreams.Length() ||
|
||||
mInitialPlaybackRate != 1.0) {
|
||||
if (!CheckDecoderCanOffloadAudio()) {
|
||||
DECODER_LOG(PR_LOG_DEBUG, ("In %s Offload Audio check failed",
|
||||
__PRETTY_FUNCTION__));
|
||||
return;
|
||||
|
@ -79,6 +85,7 @@ MediaOmxCommonDecoder::MetadataLoaded(MediaInfo* aInfo,
|
|||
}
|
||||
|
||||
mAudioOffloadPlayer = nullptr;
|
||||
mFallbackToStateMachine = true;
|
||||
DECODER_LOG(PR_LOG_DEBUG, ("In %s Unable to start offload audio %d."
|
||||
"Switching to normal mode", __PRETTY_FUNCTION__, err));
|
||||
}
|
||||
|
|
|
@ -31,7 +31,8 @@ public:
|
|||
virtual void PlaybackPositionChanged();
|
||||
virtual void UpdateReadyStateForData();
|
||||
virtual void SetElementVisibility(bool aIsVisible);
|
||||
virtual void SetCanOffloadAudio(bool aCanOffloadAudio);
|
||||
virtual void SetPlatformCanOffloadAudio(bool aCanOffloadAudio);
|
||||
virtual bool CheckDecoderCanOffloadAudio();
|
||||
virtual void AddOutputStream(ProcessedMediaStream* aStream,
|
||||
bool aFinishWhenEnded);
|
||||
virtual void SetPlaybackRate(double aPlaybackRate);
|
||||
|
|
|
@ -73,7 +73,7 @@ void MediaOmxCommonReader::CheckAudioOffload()
|
|||
if ((meta.get()) && hasNoVideo && isNotStreaming && isTypeMusic &&
|
||||
canOffloadStream(meta, false, false, AUDIO_STREAM_MUSIC)) {
|
||||
DECODER_LOG(PR_LOG_DEBUG, ("Can offload this audio stream"));
|
||||
mDecoder->SetCanOffloadAudio(true);
|
||||
mDecoder->SetPlatformCanOffloadAudio(true);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
|
|
@ -46,11 +46,11 @@ load 880384.html
|
|||
load 880404.html
|
||||
load 880724.html
|
||||
load 881775.html
|
||||
test-pref(media.webvtt.enabled,true) load 882549.html
|
||||
load 882549.html
|
||||
load 882956.html
|
||||
load 884459.html
|
||||
load 889042.html
|
||||
test-pref(media.webvtt.enabled,true) load 894104.html
|
||||
load 894104.html
|
||||
load 907986-1.html
|
||||
load 907986-2.html
|
||||
load 907986-3.html
|
||||
|
@ -71,9 +71,9 @@ load 1015662.html
|
|||
skip-if(Android||B2G) test-pref(media.navigator.permission.disabled,true) load 1028458.html # bug 1048863
|
||||
load buffer-source-ended-1.html
|
||||
HTTP load media-element-source-seek-1.html
|
||||
load offline-buffer-source-ended-1.html
|
||||
load oscillator-ended-1.html
|
||||
load oscillator-ended-2.html
|
||||
load offline-buffer-source-ended-1.html
|
||||
include ../../mediasource/test/crashtests/crashtests.list
|
||||
|
||||
# This needs to run at the end to avoid leaking busted state into other tests.
|
||||
|
|
|
@ -17,33 +17,33 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=1018933
|
|||
<pre id="test">
|
||||
<script class="testbody" type="text/javascript">
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
SpecialPowers.pushPrefEnv({"set": [["media.webvtt.enabled", true]]},
|
||||
function() {
|
||||
var video = document.createElement("video");
|
||||
video.src = "seek.webm";
|
||||
video.preload = "auto";
|
||||
var trackElement = document.createElement("track");
|
||||
trackElement.src = "basic.vtt";
|
||||
trackElement.kind = "subtitles";
|
||||
document.getElementById("content").appendChild(video);
|
||||
video.appendChild(trackElement);
|
||||
|
||||
// Accessing the track now would have caused the bug as the track element
|
||||
// shouldn't have had time to bind to the tree yet.
|
||||
trackElement.track.mode = 'showing';
|
||||
var video = document.createElement("video");
|
||||
video.src = "seek.webm";
|
||||
video.preload = "auto";
|
||||
|
||||
video.addEventListener("loadedmetadata", function run_tests() {
|
||||
// Re-que run_tests() at the end of the event loop until the track
|
||||
// element has loaded its data.
|
||||
if (trackElement.readyState == 1) {
|
||||
setTimeout(run_tests, 0);
|
||||
return;
|
||||
}
|
||||
is(video.textTracks.length, 1, "Video should have one TextTrack.");
|
||||
SimpleTest.finish();
|
||||
});
|
||||
var trackElement = document.createElement("track");
|
||||
trackElement.src = "basic.vtt";
|
||||
trackElement.kind = "subtitles";
|
||||
|
||||
document.getElementById("content").appendChild(video);
|
||||
video.appendChild(trackElement);
|
||||
|
||||
// Accessing the track now would have caused the bug as the track element
|
||||
// shouldn't have had time to bind to the tree yet.
|
||||
trackElement.track.mode = 'showing';
|
||||
|
||||
video.addEventListener("loadedmetadata", function run_tests() {
|
||||
// Re-que run_tests() at the end of the event loop until the track
|
||||
// element has loaded its data.
|
||||
if (trackElement.readyState == 1) {
|
||||
setTimeout(run_tests, 0);
|
||||
return;
|
||||
}
|
||||
);
|
||||
|
||||
is(video.textTracks.length, 1, "Video should have one TextTrack.");
|
||||
SimpleTest.finish();
|
||||
});
|
||||
</script>
|
||||
</pre>
|
||||
</body>
|
||||
|
|
|
@ -17,43 +17,42 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=883173
|
|||
<pre id="test">
|
||||
<script class="testbody" type="text/javascript">
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
SpecialPowers.pushPrefEnv({"set": [["media.webvtt.enabled", true]]},
|
||||
function() {
|
||||
var video = document.createElement("video");
|
||||
video.src = "seek.webm";
|
||||
video.preload = "auto";
|
||||
var trackElement = document.createElement("track");
|
||||
trackElement.src = "bug883173.vtt";
|
||||
trackElement.kind = "subtitles";
|
||||
document.getElementById("content").appendChild(video);
|
||||
video.appendChild(trackElement);
|
||||
video.addEventListener("loadedmetadata",
|
||||
function run_tests() {
|
||||
// Re-queue run_tests() at the end of the event loop until the track
|
||||
// element has loaded its data.
|
||||
if (trackElement.readyState == 1) {
|
||||
setTimeout(run_tests, 0);
|
||||
return;
|
||||
}
|
||||
is(trackElement.readyState, 2, "Track::ReadyState should be set to LOADED.");
|
||||
// Set mode to hidden so that the active cue lists are being updated.
|
||||
trackElement.track.mode = "hidden";
|
||||
|
||||
var expected = [[1, 2], [1, 3], [2, 3], [2, 4], [3, 4]];
|
||||
var cueList = trackElement.track.cues;
|
||||
var video = document.createElement("video");
|
||||
video.src = "seek.webm";
|
||||
video.preload = "auto";
|
||||
|
||||
is(cueList.length, expected.length, "Cue list length should be 5.");
|
||||
for (var i = 0; i < expected.length; i++) {
|
||||
is(cueList[i].startTime, expected[i][0], "Cue's start time should be " + expected[i][0]);
|
||||
is(cueList[i].endTime, expected[i][1], "Cue's end time should be " + expected[i][1]);
|
||||
}
|
||||
var trackElement = document.createElement("track");
|
||||
trackElement.src = "bug883173.vtt";
|
||||
trackElement.kind = "subtitles";
|
||||
|
||||
SimpleTest.finish();
|
||||
}
|
||||
);
|
||||
document.getElementById("content").appendChild(video);
|
||||
video.appendChild(trackElement);
|
||||
video.addEventListener("loadedmetadata",
|
||||
function run_tests() {
|
||||
// Re-queue run_tests() at the end of the event loop until the track
|
||||
// element has loaded its data.
|
||||
if (trackElement.readyState == 1) {
|
||||
setTimeout(run_tests, 0);
|
||||
return;
|
||||
}
|
||||
is(trackElement.readyState, 2, "Track::ReadyState should be set to LOADED.");
|
||||
|
||||
// Set mode to hidden so that the active cue lists are being updated.
|
||||
trackElement.track.mode = "hidden";
|
||||
|
||||
var expected = [[1, 2], [1, 3], [2, 3], [2, 4], [3, 4]];
|
||||
var cueList = trackElement.track.cues;
|
||||
is(cueList.length, expected.length, "Cue list length should be 5.");
|
||||
|
||||
for (var i = 0; i < expected.length; i++) {
|
||||
is(cueList[i].startTime, expected[i][0], "Cue's start time should be " + expected[i][0]);
|
||||
is(cueList[i].endTime, expected[i][1], "Cue's end time should be " + expected[i][1]);
|
||||
}
|
||||
|
||||
SimpleTest.finish();
|
||||
}
|
||||
);
|
||||
|
||||
</script>
|
||||
</pre>
|
||||
</body>
|
||||
|
|
|
@ -17,47 +17,43 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=895091
|
|||
<pre id="test">
|
||||
<script class="testbody" type="text/javascript">
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
SpecialPowers.pushPrefEnv({"set": [["media.webvtt.enabled", true]]},
|
||||
function() {
|
||||
var video = document.createElement("video");
|
||||
video.src = "seek.webm";
|
||||
video.preload = "auto";
|
||||
|
||||
var trackElement = document.createElement("track");
|
||||
trackElement.src = "long.vtt";
|
||||
trackElement.kind = "subtitles";
|
||||
var video = document.createElement("video");
|
||||
video.src = "seek.webm";
|
||||
video.preload = "auto";
|
||||
|
||||
var trackElementTwo = document.createElement("track");
|
||||
trackElementTwo.src = "long.vtt";
|
||||
trackElementTwo.kind = "subtitles";
|
||||
var trackElement = document.createElement("track");
|
||||
trackElement.src = "long.vtt";
|
||||
trackElement.kind = "subtitles";
|
||||
|
||||
document.getElementById("content").appendChild(video);
|
||||
video.appendChild(trackElement);
|
||||
video.appendChild(trackElementTwo);
|
||||
var trackElementTwo = document.createElement("track");
|
||||
trackElementTwo.src = "long.vtt";
|
||||
trackElementTwo.kind = "subtitles";
|
||||
|
||||
video.addEventListener("loadedmetadata", function run_tests() {
|
||||
// Re-que run_tests() at the end of the event loop until the track
|
||||
// element has loaded its data.
|
||||
if (trackElement.readyState == 1 || trackElementTwo.readyState == 1) {
|
||||
setTimeout(run_tests, 0);
|
||||
return;
|
||||
}
|
||||
document.getElementById("content").appendChild(video);
|
||||
video.appendChild(trackElement);
|
||||
video.appendChild(trackElementTwo);
|
||||
|
||||
// Set mode to hidden so that the active cue lists are being updated.
|
||||
trackElement.track.mode = "hidden";
|
||||
trackElementTwo.track.mode = "hidden";
|
||||
|
||||
is(trackElement.readyState, 2, "Track::ReadyState should be set to LOADED.");
|
||||
is(trackElementTwo.readyState, 2, "Track::ReadyState should be set to LOADED.");
|
||||
|
||||
is(trackElement.track.cues.length, 2000, "Cue list length should be 2000.");
|
||||
is(trackElementTwo.track.cues.length, 2000, "Cue list length should be 2000.");
|
||||
|
||||
SimpleTest.finish();
|
||||
});
|
||||
video.addEventListener("loadedmetadata", function run_tests() {
|
||||
// Re-que run_tests() at the end of the event loop until the track
|
||||
// element has loaded its data.
|
||||
if (trackElement.readyState == 1 || trackElementTwo.readyState == 1) {
|
||||
setTimeout(run_tests, 0);
|
||||
return;
|
||||
}
|
||||
);
|
||||
|
||||
// Set mode to hidden so that the active cue lists are being updated.
|
||||
trackElement.track.mode = "hidden";
|
||||
trackElementTwo.track.mode = "hidden";
|
||||
|
||||
is(trackElement.readyState, 2, "Track::ReadyState should be set to LOADED.");
|
||||
is(trackElementTwo.readyState, 2, "Track::ReadyState should be set to LOADED.");
|
||||
|
||||
is(trackElement.track.cues.length, 2000, "Cue list length should be 2000.");
|
||||
is(trackElementTwo.track.cues.length, 2000, "Cue list length should be 2000.");
|
||||
|
||||
SimpleTest.finish();
|
||||
});
|
||||
</script>
|
||||
</pre>
|
||||
</body>
|
||||
|
|
|
@ -18,25 +18,23 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=905320
|
|||
<pre id="test">
|
||||
<script class="testbody" type="text/javascript">
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
SpecialPowers.pushPrefEnv({"set": [["media.webvtt.enabled", true]]},
|
||||
function() {
|
||||
var audio = document.createElement("audio");
|
||||
// Check leaking on TextTrackList objects.
|
||||
window.ttl = audio.textTracks;
|
||||
ttl.addEventListener("click", function(){}, false);
|
||||
|
||||
// Check leaking on VTTCue objects.
|
||||
window.ttc = new VTTCue(3, 4, "Test.");
|
||||
ttc.addEventListener("click", function() {}, false);
|
||||
var audio = document.createElement("audio");
|
||||
|
||||
// Check leaking on TextTrack objects.
|
||||
audio.addTextTrack("subtitles", "label", "en-CA");
|
||||
ttl[0].addEventListener("click", function() {}, false);
|
||||
// Check leaking on TextTrackList objects.
|
||||
window.ttl = audio.textTracks;
|
||||
ttl.addEventListener("click", function(){}, false);
|
||||
|
||||
ok(true); // Need to have at least one assertion for Mochitest to be happy.
|
||||
SimpleTest.finish();
|
||||
}
|
||||
);
|
||||
// Check leaking on VTTCue objects.
|
||||
window.ttc = new VTTCue(3, 4, "Test.");
|
||||
ttc.addEventListener("click", function() {}, false);
|
||||
|
||||
// Check leaking on TextTrack objects.
|
||||
audio.addTextTrack("subtitles", "label", "en-CA");
|
||||
ttl[0].addEventListener("click", function() {}, false);
|
||||
|
||||
ok(true); // Need to have at least one assertion for Mochitest to be happy.
|
||||
SimpleTest.finish();
|
||||
</script>
|
||||
</pre>
|
||||
</body>
|
||||
|
|
|
@ -17,15 +17,13 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=919265
|
|||
<pre id="test">
|
||||
<script class="testbody" type="text/javascript">
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
SpecialPowers.pushPrefEnv({"set": [["media.webvtt.enabled", true]]},
|
||||
function() {
|
||||
// We shouldn't leak upon shutdown.
|
||||
(new VTTCue(0, 0, "")).getCueAsHTML();
|
||||
// We need to assert something for Mochitest to be happy.
|
||||
ok(true);
|
||||
SimpleTest.finish();
|
||||
}
|
||||
);
|
||||
|
||||
// We shouldn't leak upon shutdown.
|
||||
(new VTTCue(0, 0, "")).getCueAsHTML();
|
||||
|
||||
// We need to assert something for Mochitest to be happy.
|
||||
ok(true);
|
||||
SimpleTest.finish();
|
||||
</script>
|
||||
</pre>
|
||||
</body>
|
||||
|
|
|
@ -17,15 +17,13 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=957847
|
|||
<pre id="test">
|
||||
<script class="testbody" type="text/javascript">
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
SpecialPowers.pushPrefEnv({"set": [["media.webvtt.enabled", true]]},
|
||||
function() {
|
||||
var trackElement = document.createElement('track');
|
||||
trackElement.track.addCue(new VTTCue(0, 1, "A"));
|
||||
// We need to assert something for Mochitest to be happy.
|
||||
ok(true);
|
||||
SimpleTest.finish();
|
||||
}
|
||||
);
|
||||
|
||||
var trackElement = document.createElement('track');
|
||||
trackElement.track.addCue(new VTTCue(0, 1, "A"));
|
||||
|
||||
// We need to assert something for Mochitest to be happy.
|
||||
ok(true);
|
||||
SimpleTest.finish();
|
||||
</script>
|
||||
</pre>
|
||||
</body>
|
||||
|
|
|
@ -26,6 +26,9 @@ function finish(evt) {
|
|||
var v = evt.target;
|
||||
is(v._error, 2, "Should have received 2 error events before loaded");
|
||||
v._finished = true;
|
||||
// remove error event handler, because this would otherwise
|
||||
// cause a failure on Windows 7, see bug 1024535
|
||||
v.onerror = null;
|
||||
v.parentNode.removeChild(v);
|
||||
manager.finished(v.token);
|
||||
}
|
||||
|
@ -45,7 +48,7 @@ var extension = {
|
|||
function startTest(test, token) {
|
||||
var v = document.createElement('video');
|
||||
v.preload = "auto";
|
||||
v.addEventListener("error", function(){ok(false,"Error events on source children should not bubble");}, false);
|
||||
v.onerror = function(){ok(false,"Error events on source children should not bubble");}
|
||||
v.token = token;
|
||||
manager.started(token);
|
||||
v._error = 0;
|
||||
|
|
|
@ -17,8 +17,7 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=833386
|
|||
<pre id="test">
|
||||
<script class="testbody" type="text/javascript">
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
SpecialPowers.pushPrefEnv({"set": [["media.webvtt.enabled", true],
|
||||
["media.webvtt.regions.enabled", true]]},
|
||||
SpecialPowers.pushPrefEnv({"set": [["media.webvtt.regions.enabled", true]]},
|
||||
function() {
|
||||
var video = document.createElement("video");
|
||||
|
||||
|
|
|
@ -16,8 +16,7 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=881976
|
|||
<pre id="test">
|
||||
<script class="testbody" type="text/javascript">
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
SpecialPowers.pushPrefEnv({"set": [["media.webvtt.enabled", true],
|
||||
["media.webvtt.regions.enabled", true]]},
|
||||
SpecialPowers.pushPrefEnv({"set": [["media.webvtt.regions.enabled", true]]},
|
||||
function() {
|
||||
var video = document.createElement("video");
|
||||
|
||||
|
|
|
@ -17,15 +17,16 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=833386
|
|||
<pre id="test">
|
||||
<script class="testbody" type="text/javascript">
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
SpecialPowers.pushPrefEnv({"set": [["media.webvtt.enabled", true],
|
||||
["media.webvtt.regions.enabled", true]]},
|
||||
SpecialPowers.pushPrefEnv({"set": [["media.webvtt.regions.enabled", true]]},
|
||||
function() {
|
||||
var video = document.createElement("video");
|
||||
video.src = "seek.webm";
|
||||
video.preload = "auto";
|
||||
|
||||
var trackElement = document.createElement("track");
|
||||
trackElement.src = "basic.vtt";
|
||||
trackElement.kind = "subtitles";
|
||||
|
||||
document.getElementById("content").appendChild(video);
|
||||
video.appendChild(trackElement);
|
||||
video.addEventListener("loadedmetadata", function run_tests() {
|
||||
|
@ -35,6 +36,7 @@ SpecialPowers.pushPrefEnv({"set": [["media.webvtt.enabled", true],
|
|||
setTimeout(run_tests, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
is(trackElement.readyState, 2, "Track::ReadyState should be set to LOADED.");
|
||||
// Set mode to hidden so that the active cue lists are being updated.
|
||||
trackElement.track.mode = "hidden";
|
||||
|
|
|
@ -16,18 +16,15 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=967157
|
|||
<pre id="test">
|
||||
<script class="testbody" type="text/javascript">
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
SpecialPowers.pushPrefEnv({"set": [["media.webvtt.enabled", true]]},
|
||||
function() {
|
||||
var cue = new VTTCue(0, 1, "Some text.");
|
||||
is(cue.hasBeenReset, false, "Cue's hasBeenReset flag should be false.");
|
||||
is(cue.displayState, null, "Cue's displayState should be null.");
|
||||
|
||||
cue.startTime = 0.5;
|
||||
is(cue.hasBeenReset, true, "Cue's hasBeenReset flag should now be true.");
|
||||
var cue = new VTTCue(0, 1, "Some text.");
|
||||
is(cue.hasBeenReset, false, "Cue's hasBeenReset flag should be false.");
|
||||
is(cue.displayState, null, "Cue's displayState should be null.");
|
||||
|
||||
cue.displayState = document.createElement("div");
|
||||
is(cue.hasBeenReset, false, "Cue's hasBeenReset flag should now be false.");
|
||||
SimpleTest.finish();
|
||||
}
|
||||
);
|
||||
cue.startTime = 0.5;
|
||||
is(cue.hasBeenReset, true, "Cue's hasBeenReset flag should now be true.");
|
||||
|
||||
cue.displayState = document.createElement("div");
|
||||
is(cue.hasBeenReset, false, "Cue's hasBeenReset flag should now be false.");
|
||||
SimpleTest.finish();
|
||||
</script>
|
||||
|
|
|
@ -8,32 +8,37 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=882703
|
|||
<title>Media test: TextTrackList change event</title>
|
||||
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
|
||||
<script type="application/javascript">
|
||||
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
video = document.createElement("video");
|
||||
|
||||
isnot(video.textTracks, null, "Video should have a list of TextTracks.");
|
||||
|
||||
video.addTextTrack("subtitles", "", "");
|
||||
|
||||
track = video.textTracks[0];
|
||||
video.textTracks.addEventListener("change", changed);
|
||||
|
||||
is(track.mode, "hidden", "New TextTrack's mode should be hidden.");
|
||||
track.mode = "showing";
|
||||
|
||||
function changed(event) {
|
||||
is(event.target, video.textTracks, "change event's target should be video.textTracks.");
|
||||
ok(event instanceof window.Event, "change event should be a simple event.");
|
||||
ok(!event.bubbles, "change event should not bubble.");
|
||||
ok(event.isTrusted, "change event should be trusted.");
|
||||
ok(!event.cancelable, "change event should not be cancelable.");
|
||||
|
||||
SimpleTest.finish();
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<p id="display"></p>
|
||||
<div id="content">
|
||||
</div>
|
||||
<pre id="test">
|
||||
<script class="testbody" type="text/javascript">
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
|
||||
video = document.createElement("video");
|
||||
|
||||
isnot(video.textTracks, null, "Video should have a list of TextTracks.");
|
||||
|
||||
video.addTextTrack("subtitles", "", "");
|
||||
|
||||
track = video.textTracks[0];
|
||||
video.textTracks.addEventListener("change", changed);
|
||||
|
||||
is(track.mode, "hidden", "New TextTrack's mode should be hidden.");
|
||||
track.mode = "showing";
|
||||
|
||||
function changed(event) {
|
||||
is(event.target, video.textTracks, "change event's target should be video.textTracks.");
|
||||
ok(event instanceof window.Event, "change event should be a simple event.");
|
||||
ok(!event.bubbles, "change event should not bubble.");
|
||||
ok(event.isTrusted, "change event should be trusted.");
|
||||
ok(!event.cancelable, "change event should not be cancelable.");
|
||||
|
||||
SimpleTest.finish();
|
||||
}
|
||||
</script>
|
||||
</pre>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -16,14 +16,10 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=881976
|
|||
<pre id="test">
|
||||
<script class="testbody" type="text/javascript">
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
SpecialPowers.pushPrefEnv({"set": [["media.webvtt.enabled", true]]},
|
||||
function() {
|
||||
var video = document.createElement("video");
|
||||
is(video.textTracks.mediaElement, video, "Video's TextTrackList's MediaElement reference should be set to the video.");
|
||||
SimpleTest.finish();
|
||||
}
|
||||
);
|
||||
|
||||
var video = document.createElement("video");
|
||||
is(video.textTracks.mediaElement, video, "Video's TextTrackList's MediaElement reference should be set to the video.");
|
||||
SimpleTest.finish();
|
||||
</script>
|
||||
</pre>
|
||||
</body>
|
||||
|
|
|
@ -17,15 +17,16 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=917945
|
|||
<pre id="test">
|
||||
<script class="testbody" type="text/javascript">
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
SpecialPowers.pushPrefEnv({"set": [["media.webvtt.enabled", true],
|
||||
["media.webvtt.regions.enabled", true]]},
|
||||
SpecialPowers.pushPrefEnv({"set": [["media.webvtt.regions.enabled", true]]},
|
||||
function() {
|
||||
var video = document.createElement("video");
|
||||
video.src = "seek.webm";
|
||||
video.preload = "auto";
|
||||
|
||||
var trackElement = document.createElement("track");
|
||||
trackElement.src = "region.vtt";
|
||||
trackElement.kind = "subtitles";
|
||||
|
||||
document.getElementById("content").appendChild(video);
|
||||
video.appendChild(trackElement);
|
||||
video.addEventListener("loadedmetadata", function run_tests() {
|
||||
|
@ -36,6 +37,7 @@ SpecialPowers.pushPrefEnv({"set": [["media.webvtt.enabled", true],
|
|||
return;
|
||||
}
|
||||
is(trackElement.readyState, 2, "Track::ReadyState should be set to LOADED.");
|
||||
|
||||
// Set mode to hidden so that the active cue lists are being updated.
|
||||
trackElement.track.mode = "hidden";
|
||||
|
||||
|
@ -56,7 +58,6 @@ SpecialPowers.pushPrefEnv({"set": [["media.webvtt.enabled", true],
|
|||
});
|
||||
}
|
||||
);
|
||||
|
||||
</script>
|
||||
</pre>
|
||||
</body>
|
||||
|
|
|
@ -13,8 +13,7 @@
|
|||
<pre id="test">
|
||||
<script class="testbody" type="text/javascript">
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
SpecialPowers.pushPrefEnv({"set": [["media.webvtt.enabled", true],
|
||||
["media.webvtt.regions.enabled", true]]},
|
||||
SpecialPowers.pushPrefEnv({"set": [["media.webvtt.regions.enabled", true]]},
|
||||
function() {
|
||||
var video = document.createElement("video");
|
||||
video.src = "seek.webm";
|
||||
|
|
|
@ -14,58 +14,55 @@
|
|||
<pre id="test">
|
||||
<script class="testbody" type="text/javascript">
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
SpecialPowers.pushPrefEnv({"set": [["media.webvtt.enabled", true]]},
|
||||
function() {
|
||||
var video = document.createElement("video");
|
||||
isnot(video.textTracks, undefined, "HTMLMediaElement::TextTrack() property should be available.")
|
||||
ok(typeof video.addTextTrack == "function", "HTMLMediaElement::AddTextTrack() function should be available.")
|
||||
|
||||
var trackList = video.textTracks;
|
||||
is(trackList.length, 0, "Length should be 0.");
|
||||
var video = document.createElement("video");
|
||||
isnot(video.textTracks, undefined, "HTMLMediaElement::TextTrack() property should be available.")
|
||||
ok(typeof video.addTextTrack == "function", "HTMLMediaElement::AddTextTrack() function should be available.")
|
||||
|
||||
var evtTextTrack, numOfCalls = 0, tt;
|
||||
trackList.onaddtrack = function(event) {
|
||||
ok(event instanceof TrackEvent, "Fired event from onaddtrack should be a TrackEvent");
|
||||
is(event.type, "addtrack", "Event type should be addtrack");
|
||||
ok(event.isTrusted, "Event should be trusted!");
|
||||
ok(!event.bubbles, "Event shouldn't bubble!");
|
||||
ok(!event.cancelable, "Event shouldn't be cancelable!");
|
||||
|
||||
evtTextTrack = event.track;
|
||||
tt = textTrack[numOfCalls].track || textTrack[numOfCalls];
|
||||
var trackList = video.textTracks;
|
||||
is(trackList.length, 0, "Length should be 0.");
|
||||
|
||||
ok(tt === evtTextTrack, "Text tracks should be the same");
|
||||
is(evtTextTrack.label, label[numOfCalls], "Label should be set to "+ label[numOfCalls]);
|
||||
is(evtTextTrack.language, language[numOfCalls], "Language should be " + language[numOfCalls]);
|
||||
is(evtTextTrack.kind, kind[numOfCalls], "Kind should be " + kind[numOfCalls]);
|
||||
var evtTextTrack, numOfCalls = 0, tt;
|
||||
trackList.onaddtrack = function(event) {
|
||||
ok(event instanceof TrackEvent, "Fired event from onaddtrack should be a TrackEvent");
|
||||
is(event.type, "addtrack", "Event type should be addtrack");
|
||||
ok(event.isTrusted, "Event should be trusted!");
|
||||
ok(!event.bubbles, "Event shouldn't bubble!");
|
||||
ok(!event.cancelable, "Event shouldn't be cancelable!");
|
||||
|
||||
if (++numOfCalls == 4) {
|
||||
SimpleTest.finish();
|
||||
}
|
||||
};
|
||||
evtTextTrack = event.track;
|
||||
tt = textTrack[numOfCalls].track || textTrack[numOfCalls];
|
||||
|
||||
var label = ["Oasis", "Coldplay", "t.A.T.u", ""];
|
||||
language = ["en-CA", "en-GB", "ru", ""];
|
||||
kind = ["subtitles", "captions", "chapters", "subtitles"];
|
||||
ok(tt === evtTextTrack, "Text tracks should be the same");
|
||||
is(evtTextTrack.label, label[numOfCalls], "Label should be set to "+ label[numOfCalls]);
|
||||
is(evtTextTrack.language, language[numOfCalls], "Language should be " + language[numOfCalls]);
|
||||
is(evtTextTrack.kind, kind[numOfCalls], "Kind should be " + kind[numOfCalls]);
|
||||
|
||||
var textTrack = new Array(4);
|
||||
for (var i = 0; i < 3; ++i) {
|
||||
textTrack[i] = video.addTextTrack(kind[i], label[i], language[i]);
|
||||
is(trackList.length, i + 1, "Length should be " + (i+1));
|
||||
}
|
||||
|
||||
video.src = "seek.webm";
|
||||
video.preload = "auto";
|
||||
var trackElement = document.createElement("track");
|
||||
trackElement.src = "basic.vtt";
|
||||
textTrack[3] = trackElement;
|
||||
|
||||
document.getElementById("content").appendChild(video);
|
||||
video.appendChild(trackElement);
|
||||
|
||||
//TODO: Tests for removetrack event to be added along with bug 882677
|
||||
if (++numOfCalls == 4) {
|
||||
SimpleTest.finish();
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
var label = ["Oasis", "Coldplay", "t.A.T.u", ""];
|
||||
language = ["en-CA", "en-GB", "ru", ""];
|
||||
kind = ["subtitles", "captions", "chapters", "subtitles"];
|
||||
|
||||
var textTrack = new Array(4);
|
||||
for (var i = 0; i < 3; ++i) {
|
||||
textTrack[i] = video.addTextTrack(kind[i], label[i], language[i]);
|
||||
is(trackList.length, i + 1, "Length should be " + (i+1));
|
||||
}
|
||||
|
||||
video.src = "seek.webm";
|
||||
video.preload = "auto";
|
||||
var trackElement = document.createElement("track");
|
||||
trackElement.src = "basic.vtt";
|
||||
textTrack[3] = trackElement;
|
||||
|
||||
document.getElementById("content").appendChild(video);
|
||||
video.appendChild(trackElement);
|
||||
|
||||
//TODO: Tests for removetrack event to be added along with bug 882677
|
||||
</script>
|
||||
</pre>
|
||||
</body>
|
||||
|
|
|
@ -14,30 +14,30 @@
|
|||
<pre id="test">
|
||||
<script class="testbody" type="text/javascript">
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
SpecialPowers.pushPrefEnv({"set": [["media.webvtt.enabled", true]]},
|
||||
function() {
|
||||
var video = document.createElement("video");
|
||||
video.src = "seek.webm";
|
||||
video.preload = "auto";
|
||||
var trackElement = document.createElement("track");
|
||||
trackElement.src = "parser.vtt";
|
||||
trackElement.kind = "subtitles";
|
||||
trackElement.default = true;
|
||||
document.getElementById("content").appendChild(video);
|
||||
video.appendChild(trackElement);
|
||||
video.addEventListener("loadedmetadata", function run_tests() {
|
||||
// Re-que run_tests() at the end of the event loop until the track
|
||||
// element has loaded its data.
|
||||
if (trackElement.readyState == 1) {
|
||||
setTimeout(run_tests, 0);
|
||||
return;
|
||||
}
|
||||
is(trackElement.readyState, 2, "Track::ReadyState should be set to LOADED.");
|
||||
is(trackElement.track.cues.length, 2, "Track should have two Cues.");
|
||||
SimpleTest.finish();
|
||||
});
|
||||
});
|
||||
|
||||
var video = document.createElement("video");
|
||||
video.src = "seek.webm";
|
||||
video.preload = "auto";
|
||||
|
||||
var trackElement = document.createElement("track");
|
||||
trackElement.src = "parser.vtt";
|
||||
trackElement.kind = "subtitles";
|
||||
trackElement.default = true;
|
||||
|
||||
document.getElementById("content").appendChild(video);
|
||||
video.appendChild(trackElement);
|
||||
video.addEventListener("loadedmetadata", function run_tests() {
|
||||
// Re-que run_tests() at the end of the event loop until the track
|
||||
// element has loaded its data.
|
||||
if (trackElement.readyState == 1) {
|
||||
setTimeout(run_tests, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
is(trackElement.readyState, 2, "Track::ReadyState should be set to LOADED.");
|
||||
is(trackElement.track.cues.length, 2, "Track should have two Cues.");
|
||||
SimpleTest.finish();
|
||||
});
|
||||
</script>
|
||||
</pre>
|
||||
</body>
|
||||
|
|
|
@ -420,14 +420,14 @@ AudioNode::RemoveOutputParam(AudioParam* aParam)
|
|||
bool
|
||||
AudioNode::PassThrough() const
|
||||
{
|
||||
MOZ_ASSERT(NumberOfInputs() == 1 && NumberOfOutputs() == 1);
|
||||
MOZ_ASSERT(NumberOfInputs() <= 1 && NumberOfOutputs() == 1);
|
||||
return mPassThrough;
|
||||
}
|
||||
|
||||
void
|
||||
AudioNode::SetPassThrough(bool aPassThrough)
|
||||
{
|
||||
MOZ_ASSERT(NumberOfInputs() == 1 && NumberOfOutputs() == 1);
|
||||
MOZ_ASSERT(NumberOfInputs() <= 1 && NumberOfOutputs() == 1);
|
||||
mPassThrough = aPassThrough;
|
||||
AudioNodeStream* ns = static_cast<AudioNodeStream*>(mStream.get());
|
||||
MOZ_ASSERT(ns, "How come we don't have a stream here?");
|
||||
|
|
|
@ -38,6 +38,7 @@ support-files =
|
|||
[test_audioBufferSourceNodeNullBuffer.html]
|
||||
[test_audioBufferSourceNodeOffset.html]
|
||||
skip-if = (toolkit == 'gonk' && !debug) || (toolkit == 'android') #bug 906752
|
||||
[test_audioBufferSourceNodePassThrough.html]
|
||||
[test_AudioContext.html]
|
||||
[test_audioDestinationNode.html]
|
||||
[test_AudioListener.html]
|
||||
|
@ -100,9 +101,11 @@ skip-if = (toolkit == 'gonk' && !debug) || (toolkit == 'android') #bug 906752
|
|||
[test_maxChannelCount.html]
|
||||
[test_mediaDecoding.html]
|
||||
[test_mediaElementAudioSourceNode.html]
|
||||
[test_mediaElementAudioSourceNodePassThrough.html]
|
||||
[test_mediaStreamAudioDestinationNode.html]
|
||||
[test_mediaStreamAudioSourceNode.html]
|
||||
[test_mediaStreamAudioSourceNodeCrossOrigin.html]
|
||||
[test_mediaStreamAudioSourceNodePassThrough.html]
|
||||
[test_mediaStreamAudioSourceNodeResampling.html]
|
||||
[test_mixingRules.html]
|
||||
[test_mozaudiochannel.html]
|
||||
|
@ -113,6 +116,7 @@ skip-if = (toolkit == 'gonk' && !debug)
|
|||
[test_offlineDestinationChannelCountMore.html]
|
||||
[test_oscillatorNode.html]
|
||||
[test_oscillatorNode2.html]
|
||||
[test_oscillatorNodePassThrough.html]
|
||||
[test_oscillatorNodeStart.html]
|
||||
[test_oscillatorTypeChange.html]
|
||||
[test_pannerNode.html]
|
||||
|
|
|
@ -0,0 +1,45 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<title>Test AudioBufferSourceNode with passthrough</title>
|
||||
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<script type="text/javascript" src="webaudio.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
|
||||
</head>
|
||||
<body>
|
||||
<pre id="test">
|
||||
<script class="testbody" type="text/javascript">
|
||||
|
||||
var gTest = {
|
||||
length: 2048,
|
||||
numberOfChannels: 1,
|
||||
createGraph: function(context) {
|
||||
var buffer = context.createBuffer(1, 2048, context.sampleRate);
|
||||
for (var i = 0; i < 2048; ++i) {
|
||||
buffer.getChannelData(0)[i] = Math.sin(440 * 2 * Math.PI * i / context.sampleRate);
|
||||
}
|
||||
|
||||
var source = context.createBufferSource();
|
||||
|
||||
source.buffer = buffer;
|
||||
|
||||
var srcWrapped = SpecialPowers.wrap(source);
|
||||
ok("passThrough" in srcWrapped, "AudioBufferSourceNode should support the passThrough API");
|
||||
srcWrapped.passThrough = true;
|
||||
|
||||
source.start(0);
|
||||
return source;
|
||||
},
|
||||
createExpectedBuffers: function(context) {
|
||||
var expectedBuffer = context.createBuffer(1, 2048, context.sampleRate);
|
||||
|
||||
return [expectedBuffer];
|
||||
},
|
||||
};
|
||||
|
||||
runTest();
|
||||
|
||||
</script>
|
||||
</pre>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,66 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<meta charset="utf-8">
|
||||
<head>
|
||||
<title>Test MediaElementAudioSourceNode with passthrough</title>
|
||||
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
|
||||
</head>
|
||||
<body>
|
||||
<pre id="test">
|
||||
<script class="testbody" type="text/javascript">
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
|
||||
function test() {
|
||||
var audio = new Audio("small-shot.ogg");
|
||||
var context = new AudioContext();
|
||||
var node = context.createMediaElementSource(audio);
|
||||
var sp = context.createScriptProcessor(2048, 1);
|
||||
node.connect(sp);
|
||||
var nonzeroSampleCount = 0;
|
||||
var complete = false;
|
||||
var iterationCount = 0;
|
||||
|
||||
var srcWrapped = SpecialPowers.wrap(node);
|
||||
ok("passThrough" in srcWrapped, "MediaElementAudioSourceNode should support the passThrough API");
|
||||
srcWrapped.passThrough = true;
|
||||
|
||||
// This test ensures we receive at least expectedSampleCount nonzero samples
|
||||
function processSamples(e) {
|
||||
if (complete) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (iterationCount == 0) {
|
||||
// Don't start playing the audio until the AudioContext stuff is connected
|
||||
// and running.
|
||||
audio.play();
|
||||
}
|
||||
++iterationCount;
|
||||
|
||||
var buf = e.inputBuffer.getChannelData(0);
|
||||
var nonzeroSamplesThisBuffer = 0;
|
||||
for (var i = 0; i < buf.length; ++i) {
|
||||
if (buf[i] != 0) {
|
||||
++nonzeroSamplesThisBuffer;
|
||||
}
|
||||
}
|
||||
nonzeroSampleCount += nonzeroSamplesThisBuffer;
|
||||
if (iterationCount == 10) {
|
||||
is(nonzeroSampleCount, 0, "The input must be silence");
|
||||
SimpleTest.finish();
|
||||
complete = true;
|
||||
}
|
||||
}
|
||||
|
||||
audio.oncanplaythrough = function() {
|
||||
sp.onaudioprocess = processSamples;
|
||||
};
|
||||
}
|
||||
|
||||
SpecialPowers.pushPrefEnv({"set": [["media.preload.default", 2], ["media.preload.auto", 3]]}, test);
|
||||
|
||||
</script>
|
||||
</pre>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,55 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<meta charset="utf-8">
|
||||
<head>
|
||||
<title>Test MediaStreamAudioSourceNode passthrough</title>
|
||||
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<script type="text/javascript" src="webaudio.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
|
||||
</head>
|
||||
<body>
|
||||
<pre id="test">
|
||||
<script class="testbody" type="text/javascript">
|
||||
|
||||
function createBuffer(context, delay) {
|
||||
var buffer = context.createBuffer(2, 2048, context.sampleRate);
|
||||
for (var i = 0; i < 2048 - delay; ++i) {
|
||||
buffer.getChannelData(0)[i + delay] = Math.sin(440 * 2 * Math.PI * i / context.sampleRate);
|
||||
buffer.getChannelData(1)[i + delay] = -buffer.getChannelData(0)[i + delay];
|
||||
}
|
||||
return buffer;
|
||||
}
|
||||
|
||||
var gTest = {
|
||||
length: 2048,
|
||||
skipOfflineContextTests: true,
|
||||
createGraph: function(context) {
|
||||
var sourceGraph = new AudioContext();
|
||||
var source = sourceGraph.createBufferSource();
|
||||
source.buffer = createBuffer(context, 0);
|
||||
var dest = sourceGraph.createMediaStreamDestination();
|
||||
source.connect(dest);
|
||||
source.start(0);
|
||||
|
||||
var mediaStreamSource = context.createMediaStreamSource(dest.stream);
|
||||
// channelCount and channelCountMode should have no effect
|
||||
mediaStreamSource.channelCount = 1;
|
||||
mediaStreamSource.channelCountMode = "explicit";
|
||||
|
||||
var srcWrapped = SpecialPowers.wrap(mediaStreamSource);
|
||||
ok("passThrough" in srcWrapped, "MediaStreamAudioSourceNode should support the passThrough API");
|
||||
srcWrapped.passThrough = true;
|
||||
|
||||
return mediaStreamSource;
|
||||
},
|
||||
createExpectedBuffers: function(context) {
|
||||
return context.createBuffer(2, 2048, context.sampleRate);
|
||||
},
|
||||
};
|
||||
|
||||
runTest();
|
||||
|
||||
</script>
|
||||
</pre>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,43 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<title>Test Oscillator with passthrough</title>
|
||||
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<script type="text/javascript" src="webaudio.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
|
||||
</head>
|
||||
<body>
|
||||
<pre id="test">
|
||||
<script class="testbody" type="text/javascript">
|
||||
|
||||
var gTest = {
|
||||
length: 2048,
|
||||
numberOfChannels: 1,
|
||||
createGraph: function(context) {
|
||||
var buffer = context.createBuffer(1, 2048, context.sampleRate);
|
||||
for (var i = 0; i < 2048; ++i) {
|
||||
buffer.getChannelData(0)[i] = Math.sin(440 * 2 * Math.PI * i / context.sampleRate);
|
||||
}
|
||||
|
||||
var source = context.createOscillator();
|
||||
|
||||
var srcWrapped = SpecialPowers.wrap(source);
|
||||
ok("passThrough" in srcWrapped, "OscillatorNode should support the passThrough API");
|
||||
srcWrapped.passThrough = true;
|
||||
|
||||
source.start(0);
|
||||
return source;
|
||||
},
|
||||
createExpectedBuffers: function(context) {
|
||||
var expectedBuffer = context.createBuffer(1, 2048, context.sampleRate);
|
||||
|
||||
return [expectedBuffer];
|
||||
},
|
||||
};
|
||||
|
||||
runTest();
|
||||
|
||||
</script>
|
||||
</pre>
|
||||
</body>
|
||||
</html>
|
|
@ -490,13 +490,11 @@ SpeechRecognition::DoNothing(SpeechEvent* aEvent)
|
|||
void
|
||||
SpeechRecognition::AbortSilently(SpeechEvent* aEvent)
|
||||
{
|
||||
bool stopRecording = StateBetween(STATE_ESTIMATING, STATE_RECOGNIZING);
|
||||
|
||||
if (mRecognitionService) {
|
||||
mRecognitionService->Abort();
|
||||
}
|
||||
|
||||
if (stopRecording) {
|
||||
if (mDOMStream) {
|
||||
StopRecording();
|
||||
}
|
||||
|
||||
|
|
|
@ -30,13 +30,40 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=650295
|
|||
return stream;
|
||||
}
|
||||
|
||||
var done = false;
|
||||
function endHandler(evt, sr) {
|
||||
if (done) {
|
||||
SimpleTest.finish();
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
sr.start(createAudioStream()); // shouldn't fail
|
||||
var stream = createAudioStream();
|
||||
sr.start(stream); // shouldn't fail
|
||||
} catch (err) {
|
||||
ok(false, "Failed to start() from end() callback");
|
||||
}
|
||||
|
||||
// calling start() may cause some callbacks to fire, but we're
|
||||
// no longer interested in them, except for onend, which is where
|
||||
// we'll conclude the test.
|
||||
sr.onstart = null;
|
||||
sr.onaudiostart = null;
|
||||
sr.onspeechstart = null;
|
||||
sr.onspeechend = null;
|
||||
sr.onaudioend = null;
|
||||
sr.onresult = null;
|
||||
|
||||
// FIXME(ggp) the state transition caused by start() is async,
|
||||
// but abort() is sync (see bug 1055093). until we normalize
|
||||
// state transitions, we need to setTimeout here to make sure
|
||||
// abort() finds the speech recognition object in the correct
|
||||
// state (namely, STATE_STARTING).
|
||||
setTimeout(function() {
|
||||
sr.abort();
|
||||
done = true;
|
||||
});
|
||||
|
||||
info("Successfully start() from end() callback");
|
||||
}
|
||||
|
||||
|
@ -64,7 +91,6 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=650295
|
|||
'result': buildResultCallback("Mock final result"),
|
||||
'end': endHandler,
|
||||
},
|
||||
doneFunc: SimpleTest.finish,
|
||||
prefs: [["media.webspeech.test.fake_fsm_events", true], ["media.webspeech.test.fake_recognition_service", true]]
|
||||
});
|
||||
|
||||
|
|
|
@ -57,6 +57,10 @@ mozIApplication.prototype = {
|
|||
return (perm === Ci.nsIPermissionManager.ALLOW_ACTION);
|
||||
},
|
||||
|
||||
hasWidgetPage: function(aPageURL) {
|
||||
return this.widgetPages.indexOf(aPageURL) != -1;
|
||||
},
|
||||
|
||||
QueryInterface: function(aIID) {
|
||||
if (aIID.equals(Ci.mozIApplication) ||
|
||||
aIID.equals(Ci.nsISupports))
|
||||
|
@ -100,6 +104,7 @@ function _setAppProperties(aObj, aApp) {
|
|||
aObj.storeVersion = aApp.storeVersion || 0;
|
||||
aObj.role = aApp.role || "";
|
||||
aObj.redirects = aApp.redirects;
|
||||
aObj.widgetPages = aApp.widgetPages || [];
|
||||
aObj.kind = aApp.kind;
|
||||
}
|
||||
|
||||
|
@ -678,6 +683,10 @@ ManifestHelper.prototype = {
|
|||
return this._localeProp("package_path");
|
||||
},
|
||||
|
||||
get widgetPages() {
|
||||
return this._localeProp("widgetPages");
|
||||
},
|
||||
|
||||
get size() {
|
||||
return this._manifest["size"] || 0;
|
||||
},
|
||||
|
|
|
@ -232,6 +232,11 @@ this.PermissionsTable = { geolocation: {
|
|||
privileged: DENY_ACTION,
|
||||
certified: ALLOW_ACTION
|
||||
},
|
||||
"embed-widgets": {
|
||||
app: DENY_ACTION,
|
||||
privileged: ALLOW_ACTION,
|
||||
certified: ALLOW_ACTION
|
||||
},
|
||||
"storage": {
|
||||
app: ALLOW_ACTION,
|
||||
privileged: ALLOW_ACTION,
|
||||
|
|
|
@ -262,6 +262,10 @@ this.DOMApplicationRegistry = {
|
|||
app.role = "";
|
||||
}
|
||||
|
||||
if (app.widgetPages === undefined) {
|
||||
app.widgetPages = [];
|
||||
}
|
||||
|
||||
// At startup we can't be downloading, and the $TMP directory
|
||||
// will be empty so we can't just apply a staged update.
|
||||
app.downloading = false;
|
||||
|
@ -321,6 +325,15 @@ this.DOMApplicationRegistry = {
|
|||
return res.length > 0 ? res : null;
|
||||
},
|
||||
|
||||
_saveWidgetsFullPath: function(aManifest, aDestApp) {
|
||||
if (aManifest.widgetPages) {
|
||||
aDestApp.widgetPages = aManifest.widgetPages.map(aManifest.resolveURL,
|
||||
aManifest/* thisArg */);
|
||||
} else {
|
||||
aDestApp.widgetPages = [];
|
||||
}
|
||||
},
|
||||
|
||||
// Registers all the activities and system messages.
|
||||
registerAppsHandlers: Task.async(function*(aRunUpdate) {
|
||||
this.notifyAppsRegistryStart();
|
||||
|
@ -345,6 +358,10 @@ this.DOMApplicationRegistry = {
|
|||
let app = this.webapps[aResult.id];
|
||||
app.csp = aResult.manifest.csp || "";
|
||||
app.role = aResult.manifest.role || "";
|
||||
|
||||
let localeManifest = new ManifestHelper(aResult.manifest, app.origin, app.manifestURL);
|
||||
this._saveWidgetsFullPath(localeManifest, app);
|
||||
|
||||
if (app.appStatus >= Ci.nsIPrincipal.APP_STATUS_PRIVILEGED) {
|
||||
app.redirects = this.sanitizeRedirects(aResult.redirects);
|
||||
}
|
||||
|
@ -984,6 +1001,8 @@ this.DOMApplicationRegistry = {
|
|||
app.name = manifest.name;
|
||||
app.csp = manifest.csp || "";
|
||||
app.role = localeManifest.role;
|
||||
this._saveWidgetsFullPath(localeManifest, app);
|
||||
|
||||
if (app.appStatus >= Ci.nsIPrincipal.APP_STATUS_PRIVILEGED) {
|
||||
app.redirects = this.sanitizeRedirects(manifest.redirects);
|
||||
}
|
||||
|
@ -2014,6 +2033,8 @@ this.DOMApplicationRegistry = {
|
|||
aApp.downloadAvailable = true;
|
||||
aApp.downloadSize = manifest.size;
|
||||
aApp.updateManifest = aNewManifest;
|
||||
this._saveWidgetsFullPath(manifest, aApp);
|
||||
|
||||
yield this._saveApps();
|
||||
|
||||
this.broadcastMessage("Webapps:UpdateState", {
|
||||
|
@ -2072,6 +2093,7 @@ this.DOMApplicationRegistry = {
|
|||
aApp.name = aNewManifest.name;
|
||||
aApp.csp = manifest.csp || "";
|
||||
aApp.role = manifest.role || "";
|
||||
this._saveWidgetsFullPath(manifest, aApp);
|
||||
aApp.updateTime = Date.now();
|
||||
} else {
|
||||
manifest =
|
||||
|
@ -2471,6 +2493,7 @@ this.DOMApplicationRegistry = {
|
|||
appObject.name = aManifest.name;
|
||||
appObject.csp = aLocaleManifest.csp || "";
|
||||
appObject.role = aLocaleManifest.role;
|
||||
this._saveWidgetsFullPath(aLocaleManifest, appObject);
|
||||
appObject.installerAppId = aData.appId;
|
||||
appObject.installerIsBrowser = aData.isBrowser;
|
||||
|
||||
|
@ -2506,6 +2529,9 @@ this.DOMApplicationRegistry = {
|
|||
|
||||
app.csp = aManifest.csp || "";
|
||||
|
||||
let aLocaleManifest = new ManifestHelper(aManifest, app.origin, app.manifestURL);
|
||||
this._saveWidgetsFullPath(aLocaleManifest, app);
|
||||
|
||||
app.appStatus = AppsUtils.getAppManifestStatus(aManifest);
|
||||
|
||||
app.removable = true;
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
var gBasePath = "tests/dom/apps/tests/";
|
||||
var gAppTemplatePath = "tests/dom/apps/tests/file_app.template.html";
|
||||
var gAppcacheTemplatePath = "tests/dom/apps/tests/file_cached_app.template.appcache";
|
||||
var gWidgetTemplatePath = "tests/dom/apps/tests/file_widget_app.template.html";
|
||||
var gDefaultIcon = "default_icon";
|
||||
|
||||
function makeResource(templatePath, version, apptype) {
|
||||
|
@ -14,7 +15,6 @@ function makeResource(templatePath, version, apptype) {
|
|||
if (templatePath == gAppTemplatePath && apptype == 'cached') {
|
||||
res = res.replace('<html>', '<html manifest="file_app.sjs?apptype=cached&getappcache=true">');
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
|
@ -46,7 +46,7 @@ function handleRequest(request, response) {
|
|||
|
||||
// Get the app type.
|
||||
var apptype = query.apptype;
|
||||
if (apptype != 'hosted' && apptype != 'cached')
|
||||
if (apptype != 'hosted' && apptype != 'cached' && apptype != 'widget' && apptype != 'invalidWidget')
|
||||
throw "Invalid app type: " + apptype;
|
||||
|
||||
// Get the version from server state and handle the etag.
|
||||
|
@ -83,7 +83,12 @@ function handleRequest(request, response) {
|
|||
response.write(makeResource(gAppcacheTemplatePath, version, apptype));
|
||||
return;
|
||||
}
|
||||
|
||||
else if (apptype == 'widget' || apptype == 'invalidWidget')
|
||||
{
|
||||
response.setHeader("Content-Type", "text/html", false);
|
||||
response.write(makeResource(gWidgetTemplatePath, version, apptype));
|
||||
return;
|
||||
}
|
||||
// Generate the app.
|
||||
response.setHeader("Content-Type", "text/html", false);
|
||||
response.write(makeResource(gAppTemplatePath, version, apptype));
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
{
|
||||
"name": "Really Rapid Release (invalid widget)",
|
||||
"description": "Updated even faster than <a href='http://mozilla.org'>Firefox</a>, just to annoy slashdotters.",
|
||||
"launch_path": "/tests/dom/apps/tests/file_app.sjs?apptype=invalidWidget",
|
||||
"icons": {
|
||||
"128": "ICONTOKEN"
|
||||
},
|
||||
"widgetPages": [
|
||||
"/tests/dom/apps/tests/file_app.sjs?apptype=widget"
|
||||
]
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
<html>
|
||||
<head>
|
||||
</head>
|
||||
<body>
|
||||
App Body. Version: VERSIONTOKEN
|
||||
<br /><br /><br /><br /><br /><br /><br /><br /><br /><br /><br /><br /><br />
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,11 @@
|
|||
{
|
||||
"name": "Really Rapid Release (widget)",
|
||||
"description": "Updated even faster than <a href='http://mozilla.org'>Firefox</a>, just to annoy slashdotters.",
|
||||
"launch_path": "/tests/dom/apps/tests/file_app.sjs?apptype=widget",
|
||||
"icons": {
|
||||
"128": "ICONTOKEN"
|
||||
},
|
||||
"widgetPages": [
|
||||
"/tests/dom/apps/tests/file_app.sjs?apptype=widget"
|
||||
]
|
||||
}
|
|
@ -7,9 +7,12 @@ support-files =
|
|||
file_cached_app.template.appcache
|
||||
file_cached_app.template.webapp
|
||||
file_hosted_app.template.webapp
|
||||
file_invalidWidget_app.template.webapp
|
||||
file_packaged_app.sjs
|
||||
file_packaged_app.template.html
|
||||
file_packaged_app.template.webapp
|
||||
file_widget_app.template.webapp
|
||||
file_widget_app.template.html
|
||||
signed_app.sjs
|
||||
signed_app_template.webapp
|
||||
signed/*
|
||||
|
@ -28,3 +31,4 @@ skip-if = buildapp == "b2g" || toolkit == "android" # see bug 989806
|
|||
[test_receipt_operations.html]
|
||||
[test_signed_pkg_install.html]
|
||||
[test_uninstall_errors.html]
|
||||
[test_widget.html]
|
||||
|
|
|
@ -0,0 +1,231 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Test for DataStore - basic operation on a readonly db</title>
|
||||
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
|
||||
</head>
|
||||
<body>
|
||||
<div id="container"></div>
|
||||
<script type="application/javascript;version=1.7">
|
||||
|
||||
var gWidgetManifestURL = 'http://test/tests/dom/apps/tests/file_app.sjs?apptype=widget&getmanifest=true';
|
||||
var gInvalidWidgetManifestURL = 'http://test/tests/dom/apps/tests/file_app.sjs?apptype=invalidWidget&getmanifest=true';
|
||||
var gApp;
|
||||
|
||||
function onError() {
|
||||
ok(false, "Error callback invoked");
|
||||
finish();
|
||||
}
|
||||
|
||||
function installApp(path) {
|
||||
var request = navigator.mozApps.install(path);
|
||||
request.onerror = onError;
|
||||
request.onsuccess = function() {
|
||||
gApp = request.result;
|
||||
|
||||
runTest();
|
||||
}
|
||||
}
|
||||
|
||||
function uninstallApp() {
|
||||
// Uninstall the app.
|
||||
var request = navigator.mozApps.mgmt.uninstall(gApp);
|
||||
request.onerror = onError;
|
||||
request.onsuccess = function() {
|
||||
// All done.
|
||||
info("All done");
|
||||
|
||||
runTest();
|
||||
}
|
||||
}
|
||||
|
||||
function testApp(isValidWidget) {
|
||||
info("Test widget feature. IsValidWidget: " + isValidWidget);
|
||||
|
||||
var ifr = document.createElement('iframe');
|
||||
ifr.setAttribute('mozbrowser', 'true');
|
||||
ifr.setAttribute('mozwidget', gApp.manifestURL);
|
||||
ifr.setAttribute('src', gApp.origin+gApp.manifest.launch_path);
|
||||
|
||||
var domParent = document.getElementById('container');
|
||||
domParent.appendChild(ifr);
|
||||
|
||||
var mm = SpecialPowers.getBrowserFrameMessageManager(ifr);
|
||||
mm.addMessageListener('OK', function(msg) {
|
||||
ok(isValidWidget, "Message from widget: " + SpecialPowers.wrap(msg).json);
|
||||
});
|
||||
mm.addMessageListener('KO', function(msg) {
|
||||
ok(!isValidWidget, "Message from widget: " + SpecialPowers.wrap(msg).json);
|
||||
});
|
||||
mm.addMessageListener('DONE', function(msg) {
|
||||
ok(true, "Message from widget complete: "+SpecialPowers.wrap(msg).json);
|
||||
domParent.removeChild(ifr);
|
||||
runTest();
|
||||
});
|
||||
|
||||
ifr.addEventListener('mozbrowserloadend', function() {
|
||||
ok(true, "receive mozbrowserloadend");
|
||||
|
||||
// Test limited browser API feature only for valid widget case
|
||||
if (isValidWidget) {
|
||||
testLimitedBrowserAPI(ifr);
|
||||
}
|
||||
SimpleTest.executeSoon(()=>loadFrameScript(mm));
|
||||
}, false);
|
||||
|
||||
// Test limited browser API feature only for valid widget case
|
||||
if (!isValidWidget) {
|
||||
return;
|
||||
}
|
||||
|
||||
[
|
||||
'mozbrowsertitlechange',
|
||||
'mozbrowseropenwindow',
|
||||
'mozbrowserscroll',
|
||||
'mozbrowserasyncscroll'
|
||||
].forEach( function(topic) {
|
||||
ifr.addEventListener(topic, function() {
|
||||
ok(false, topic + " should be hidden");
|
||||
}, false);
|
||||
});
|
||||
}
|
||||
|
||||
function testLimitedBrowserAPI(ifr) {
|
||||
var securitySensitiveCalls = [
|
||||
'sendMouseEvent',
|
||||
'sendTouchEvent',
|
||||
'goBack',
|
||||
'goForward',
|
||||
'reload',
|
||||
'stop',
|
||||
'download',
|
||||
'purgeHistory',
|
||||
'getScreenshot',
|
||||
'zoom',
|
||||
'getCanGoBack',
|
||||
'getCanGoForward'
|
||||
];
|
||||
securitySensitiveCalls.forEach( function(call) {
|
||||
is(typeof ifr[call], "undefined", call + " should be hidden for widget");
|
||||
});
|
||||
}
|
||||
|
||||
function loadFrameScript(mm) {
|
||||
var script = 'data:,\
|
||||
function ok(p, msg) { \
|
||||
if (p) { \
|
||||
sendAsyncMessage("OK", msg); \
|
||||
} else { \
|
||||
sendAsyncMessage("KO", msg); \
|
||||
} \
|
||||
} \
|
||||
\
|
||||
function is(a, b, msg) { \
|
||||
if (a == b) { \
|
||||
sendAsyncMessage("OK", a + " == " + b + " - " + msg); \
|
||||
} else { \
|
||||
sendAsyncMessage("KO", a + " != " + b + " - " + msg); \
|
||||
} \
|
||||
} \
|
||||
\
|
||||
function finish() { \
|
||||
sendAsyncMessage("DONE",""); \
|
||||
} \
|
||||
\
|
||||
function onError() { \
|
||||
ok(false, "Error callback invoked"); \
|
||||
finish(); \
|
||||
} \
|
||||
\
|
||||
function checkWidget(widget) { \
|
||||
/*For invalid widget case, ignore the following check*/\
|
||||
if (widget) { \
|
||||
var widgetName = "Really Rapid Release (APPTYPETOKEN)"; \
|
||||
is(widget.origin, "http://test", "Widget origin should be correct"); \
|
||||
is(widget.installOrigin, "http://mochi.test:8888", "Install origin should be correct"); \
|
||||
} \
|
||||
finish(); \
|
||||
} \
|
||||
\
|
||||
var request = content.window.navigator.mozApps.getSelf(); \
|
||||
request.onsuccess = function() { \
|
||||
var widget = request.result; \
|
||||
ok(widget,"Should be a widget"); \
|
||||
checkWidget(widget); \
|
||||
}; \
|
||||
request.onerror = onError; \
|
||||
content.window.open("about:blank"); /*test mozbrowseropenwindow*/ \
|
||||
content.window.scrollTo(4000, 4000); /*test mozbrowser(async)scroll*/ \
|
||||
';
|
||||
mm.loadFrameScript(script, /* allowDelayedLoad = */ false);
|
||||
}
|
||||
|
||||
var tests = [
|
||||
// Permissions
|
||||
function() {
|
||||
SpecialPowers.pushPermissions(
|
||||
[{ "type": "browser", "allow": 1, "context": document },
|
||||
{ "type": "embed-widgets", "allow": 1, "context": document },
|
||||
{ "type": "webapps-manage", "allow": 1, "context": document }], runTest);
|
||||
},
|
||||
|
||||
// Preferences
|
||||
function() {
|
||||
SpecialPowers.pushPrefEnv({"set": [["dom.mozBrowserFramesEnabled", true],
|
||||
["dom.enable_widgets", true]]}, runTest);
|
||||
},
|
||||
|
||||
function() {
|
||||
SpecialPowers.setAllAppsLaunchable(true);
|
||||
runTest();
|
||||
},
|
||||
|
||||
// No confirmation needed when an app is installed
|
||||
function() {
|
||||
SpecialPowers.autoConfirmAppInstall(runTest);
|
||||
},
|
||||
|
||||
// Installing the app
|
||||
()=>installApp(gWidgetManifestURL),
|
||||
|
||||
// Run tests in app
|
||||
()=>testApp(true),
|
||||
|
||||
// Uninstall the app
|
||||
uninstallApp,
|
||||
|
||||
// Installing the app for invalid widget case
|
||||
()=>installApp(gInvalidWidgetManifestURL),
|
||||
|
||||
// Run tests in app for invalid widget case
|
||||
()=>testApp(false),
|
||||
|
||||
// Uninstall the app
|
||||
uninstallApp
|
||||
];
|
||||
|
||||
function runTest() {
|
||||
if (!tests.length) {
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
|
||||
var test = tests.shift();
|
||||
test();
|
||||
}
|
||||
|
||||
function finish() {
|
||||
SimpleTest.finish();
|
||||
}
|
||||
|
||||
if (SpecialPowers.isMainProcess()) {
|
||||
SpecialPowers.Cu.import("resource://gre/modules/DataStoreChangeNotifier.jsm");
|
||||
}
|
||||
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
runTest();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
|
@ -428,7 +428,6 @@ static const nsConstructorFuncMapData kConstructorFuncMap[] =
|
|||
#undef NS_DEFINE_CONSTRUCTOR_FUNC_DATA
|
||||
|
||||
nsIXPConnect *nsDOMClassInfo::sXPConnect = nullptr;
|
||||
nsIScriptSecurityManager *nsDOMClassInfo::sSecMan = nullptr;
|
||||
bool nsDOMClassInfo::sIsInitialized = false;
|
||||
|
||||
|
||||
|
@ -779,19 +778,11 @@ nsDOMClassInfo::Init()
|
|||
nsScriptNameSpaceManager *nameSpaceManager = GetNameSpaceManager();
|
||||
NS_ENSURE_TRUE(nameSpaceManager, NS_ERROR_NOT_INITIALIZED);
|
||||
|
||||
nsresult rv = CallGetService(nsIXPConnect::GetCID(), &sXPConnect);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
NS_ADDREF(sXPConnect = nsContentUtils::XPConnect());
|
||||
|
||||
nsCOMPtr<nsIXPCFunctionThisTranslator> elt = new nsEventListenerThisTranslator();
|
||||
sXPConnect->SetFunctionThisTranslator(NS_GET_IID(nsIDOMEventListener), elt);
|
||||
|
||||
nsCOMPtr<nsIScriptSecurityManager> sm =
|
||||
do_GetService("@mozilla.org/scriptsecuritymanager;1", &rv);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
sSecMan = sm;
|
||||
NS_ADDREF(sSecMan);
|
||||
|
||||
AutoSafeJSContext cx;
|
||||
|
||||
DOM_CLASSINFO_MAP_BEGIN(Window, nsIDOMWindow)
|
||||
|
@ -1042,9 +1033,6 @@ nsDOMClassInfo::Init()
|
|||
|
||||
RegisterExternalClasses();
|
||||
|
||||
// Register new DOM bindings
|
||||
mozilla::dom::Register(nameSpaceManager);
|
||||
|
||||
sIsInitialized = true;
|
||||
|
||||
return NS_OK;
|
||||
|
@ -1523,11 +1511,8 @@ NS_GetDOMClassInfoInstance(nsDOMClassInfoID aID)
|
|||
return nullptr;
|
||||
}
|
||||
|
||||
if (!nsDOMClassInfo::sIsInitialized) {
|
||||
nsresult rv = nsDOMClassInfo::Init();
|
||||
|
||||
NS_ENSURE_SUCCESS(rv, nullptr);
|
||||
}
|
||||
nsresult rv = RegisterDOMNames();
|
||||
NS_ENSURE_SUCCESS(rv, nullptr);
|
||||
|
||||
if (!sClassInfoData[aID].mCachedClassInfo) {
|
||||
nsDOMClassInfoData& data = sClassInfoData[aID];
|
||||
|
@ -1588,7 +1573,6 @@ nsDOMClassInfo::ShutDown()
|
|||
sWrappedJSObject_id = JSID_VOID;
|
||||
|
||||
NS_IF_RELEASE(sXPConnect);
|
||||
NS_IF_RELEASE(sSecMan);
|
||||
sIsInitialized = false;
|
||||
}
|
||||
|
||||
|
|
|
@ -96,6 +96,7 @@ public:
|
|||
|
||||
static nsIClassInfo* GetClassInfoInstance(nsDOMClassInfoData* aData);
|
||||
|
||||
static nsresult Init();
|
||||
static void ShutDown();
|
||||
|
||||
static nsIClassInfo *doCreate(nsDOMClassInfoData* aData)
|
||||
|
@ -127,10 +128,6 @@ public:
|
|||
{
|
||||
return sXPConnect;
|
||||
}
|
||||
static nsIScriptSecurityManager *ScriptSecurityManager()
|
||||
{
|
||||
return sSecMan;
|
||||
}
|
||||
|
||||
protected:
|
||||
friend nsIClassInfo* NS_GetDOMClassInfoInstance(nsDOMClassInfoID aID);
|
||||
|
@ -146,14 +143,12 @@ protected:
|
|||
return mData->mInterfacesBitmap;
|
||||
}
|
||||
|
||||
static nsresult Init();
|
||||
static nsresult RegisterClassProtos(int32_t aDOMClassInfoID);
|
||||
static nsresult RegisterExternalClasses();
|
||||
nsresult ResolveConstructor(JSContext *cx, JSObject *obj,
|
||||
JSObject **objp);
|
||||
|
||||
static nsIXPConnect *sXPConnect;
|
||||
static nsIScriptSecurityManager *sSecMan;
|
||||
|
||||
// nsIXPCScriptable code
|
||||
static nsresult DefineStaticJSVals(JSContext *cx);
|
||||
|
|
|
@ -43,6 +43,7 @@
|
|||
#include "mozilla/dom/HTMLAppletElementBinding.h"
|
||||
#include "mozilla/dom/Promise.h"
|
||||
#include "WorkerPrivate.h"
|
||||
#include "nsDOMClassInfo.h"
|
||||
|
||||
namespace mozilla {
|
||||
namespace dom {
|
||||
|
@ -2561,11 +2562,44 @@ CreateGlobalOptions<nsGlobalWindow>::TraceGlobal(JSTracer* aTrc, JSObject* aObj)
|
|||
xpc::TraceXPCGlobal(aTrc, aObj);
|
||||
}
|
||||
|
||||
static bool sRegisteredDOMNames = false;
|
||||
|
||||
nsresult
|
||||
RegisterDOMNames()
|
||||
{
|
||||
if (sRegisteredDOMNames) {
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsresult rv = nsDOMClassInfo::Init();
|
||||
if (NS_FAILED(rv)) {
|
||||
NS_ERROR("Could not initialize nsDOMClassInfo");
|
||||
return rv;
|
||||
}
|
||||
|
||||
// Register new DOM bindings
|
||||
nsScriptNameSpaceManager* nameSpaceManager = GetNameSpaceManager();
|
||||
if (!nameSpaceManager) {
|
||||
NS_ERROR("Could not initialize nsScriptNameSpaceManager");
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
mozilla::dom::Register(nameSpaceManager);
|
||||
|
||||
sRegisteredDOMNames = true;
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
/* static */
|
||||
bool
|
||||
CreateGlobalOptions<nsGlobalWindow>::PostCreateGlobal(JSContext* aCx,
|
||||
JS::Handle<JSObject*> aGlobal)
|
||||
{
|
||||
nsresult rv = RegisterDOMNames();
|
||||
if (NS_FAILED(rv)) {
|
||||
return Throw(aCx, rv);
|
||||
}
|
||||
|
||||
// Invoking the XPCWrappedNativeScope constructor automatically hooks it
|
||||
// up to the compartment of aGlobal.
|
||||
(void) new XPCWrappedNativeScope(aCx, aGlobal);
|
||||
|
|
|
@ -2808,6 +2808,9 @@ struct CreateGlobalOptions<nsGlobalWindow>
|
|||
static bool PostCreateGlobal(JSContext* aCx, JS::Handle<JSObject*> aGlobal);
|
||||
};
|
||||
|
||||
nsresult
|
||||
RegisterDOMNames();
|
||||
|
||||
template <class T, ProtoGetter GetProto>
|
||||
bool
|
||||
CreateGlobal(JSContext* aCx, T* aNative, nsWrapperCache* aCache,
|
||||
|
|
|
@ -4098,11 +4098,12 @@ def getJSToNativeConversionInfo(type, descriptorProvider, failureCode=None,
|
|||
if nullable:
|
||||
type = type.inner
|
||||
|
||||
unionArgumentObj = "${declName}" if isMember else "${holderName}"
|
||||
isOwningUnion = isMember or isCallbackReturnValue
|
||||
unionArgumentObj = "${declName}" if isOwningUnion else "${holderName}"
|
||||
if nullable:
|
||||
# If we're a member, we're a Nullable, which hasn't been told it has
|
||||
# If we're owning, we're a Nullable, which hasn't been told it has
|
||||
# a value. Otherwise we're an already-constructed Maybe.
|
||||
unionArgumentObj += ".SetValue()" if isMember else ".ref()"
|
||||
unionArgumentObj += ".SetValue()" if isOwningUnion else ".ref()"
|
||||
|
||||
memberTypes = type.flatMemberTypes
|
||||
names = []
|
||||
|
@ -4292,7 +4293,7 @@ def getJSToNativeConversionInfo(type, descriptorProvider, failureCode=None,
|
|||
|
||||
templateBody = CGWrapper(CGIndenter(CGList([templateBody, throw])), pre="{\n", post="}\n")
|
||||
|
||||
typeName = CGUnionStruct.unionTypeDecl(type, isMember)
|
||||
typeName = CGUnionStruct.unionTypeDecl(type, isOwningUnion)
|
||||
argumentTypeName = typeName + "Argument"
|
||||
if nullable:
|
||||
typeName = "Nullable<" + typeName + " >"
|
||||
|
@ -4315,7 +4316,7 @@ def getJSToNativeConversionInfo(type, descriptorProvider, failureCode=None,
|
|||
extraConditionForNull=extraConditionForNull)
|
||||
|
||||
declType = CGGeneric(typeName)
|
||||
if isMember:
|
||||
if isOwningUnion:
|
||||
holderType = None
|
||||
else:
|
||||
holderType = CGGeneric(argumentTypeName)
|
||||
|
@ -4324,13 +4325,13 @@ def getJSToNativeConversionInfo(type, descriptorProvider, failureCode=None,
|
|||
|
||||
# If we're isOptional and not nullable the normal optional handling will
|
||||
# handle lazy construction of our holder. If we're nullable and not
|
||||
# isMember we do it all by hand because we do not want our holder
|
||||
# constructed if we're null. But if we're isMember we don't have a
|
||||
# owning we do it all by hand because we do not want our holder
|
||||
# constructed if we're null. But if we're owning we don't have a
|
||||
# holder anyway, so we can do the normal Optional codepath.
|
||||
declLoc = "${declName}"
|
||||
constructDecl = None
|
||||
if nullable:
|
||||
if isOptional and not isMember:
|
||||
if isOptional and not isOwningUnion:
|
||||
holderArgs = "${declName}.Value().SetValue()"
|
||||
declType = CGTemplatedType("Optional", declType)
|
||||
constructDecl = CGGeneric("${declName}.Construct();\n")
|
||||
|
@ -4347,6 +4348,12 @@ def getJSToNativeConversionInfo(type, descriptorProvider, failureCode=None,
|
|||
holderArgs = "${declName}"
|
||||
constructHolder = None
|
||||
|
||||
if not isMember and isCallbackReturnValue:
|
||||
declType = CGWrapper(declType, post="&")
|
||||
declArgs = "aRetVal"
|
||||
else:
|
||||
declArgs = None
|
||||
|
||||
if defaultValue and not isinstance(defaultValue, IDLNullValue):
|
||||
tag = defaultValue.type.tag()
|
||||
|
||||
|
@ -4387,7 +4394,7 @@ def getJSToNativeConversionInfo(type, descriptorProvider, failureCode=None,
|
|||
isinstance(defaultValue, IDLNullValue)):
|
||||
assert type.hasDictionaryType
|
||||
assert defaultValue.type.isDictionary()
|
||||
if not isMember and typeNeedsRooting(defaultValue.type):
|
||||
if not isOwningUnion and typeNeedsRooting(defaultValue.type):
|
||||
ctorArgs = "cx"
|
||||
else:
|
||||
ctorArgs = ""
|
||||
|
@ -4404,9 +4411,10 @@ def getJSToNativeConversionInfo(type, descriptorProvider, failureCode=None,
|
|||
|
||||
return JSToNativeConversionInfo(templateBody.define(),
|
||||
declType=declType,
|
||||
declArgs=declArgs,
|
||||
holderType=holderType,
|
||||
holderArgs=holderArgs,
|
||||
dealWithOptional=isOptional and (not nullable or isMember))
|
||||
dealWithOptional=isOptional and (not nullable or isOwningUnion))
|
||||
|
||||
if type.isGeckoInterface():
|
||||
assert not isEnforceRange and not isClamp
|
||||
|
|
|
@ -491,15 +491,15 @@ interface TestExampleInterface {
|
|||
// XXXbz no move constructor on some unions
|
||||
// void passMozMapOfUnions2(MozMap<(object or long)> arg);
|
||||
|
||||
//(CanvasPattern or CanvasGradient) receiveUnion();
|
||||
//(object or long) receiveUnion2();
|
||||
//(CanvasPattern? or CanvasGradient) receiveUnionContainingNull();
|
||||
//(CanvasPattern or CanvasGradient)? receiveNullableUnion();
|
||||
//(object or long)? receiveNullableUnion2();
|
||||
(CanvasPattern or CanvasGradient) receiveUnion();
|
||||
(object or long) receiveUnion2();
|
||||
(CanvasPattern? or CanvasGradient) receiveUnionContainingNull();
|
||||
(CanvasPattern or CanvasGradient)? receiveNullableUnion();
|
||||
(object or long)? receiveNullableUnion2();
|
||||
|
||||
//attribute (CanvasPattern or CanvasGradient) writableUnion;
|
||||
//attribute (CanvasPattern? or CanvasGradient) writableUnionContainingNull;
|
||||
//attribute (CanvasPattern or CanvasGradient)? writableNullableUnion;
|
||||
attribute (CanvasPattern or CanvasGradient) writableUnion;
|
||||
attribute (CanvasPattern? or CanvasGradient) writableUnionContainingNull;
|
||||
attribute (CanvasPattern or CanvasGradient)? writableNullableUnion;
|
||||
|
||||
// Date types
|
||||
void passDate(Date arg);
|
||||
|
|
|
@ -47,7 +47,12 @@ TestInterfaceJS.prototype = {
|
|||
|
||||
getCallerPrincipal: function() { return Cu.getWebIDLCallerPrincipal().origin; },
|
||||
|
||||
convertSVS: function(svs) { return svs; }
|
||||
convertSVS: function(svs) { return svs; },
|
||||
|
||||
pingPongUnion: function(x) { return x; },
|
||||
pingPongUnionContainingNull: function(x) { return x; },
|
||||
pingPongNullableUnion: function(x) { return x; },
|
||||
returnBadUnion: function(x) { return 3; }
|
||||
};
|
||||
|
||||
this.NSGetFactory = XPCOMUtils.generateNSGetFactory([TestInterfaceJS])
|
||||
|
|
|
@ -515,15 +515,15 @@ interface TestJSImplInterface {
|
|||
// XXXbz no move constructor on some unions
|
||||
// void passMozMapOfUnions2(MozMap<(object or long)> arg);
|
||||
|
||||
//(CanvasPattern or CanvasGradient) receiveUnion();
|
||||
//(object or long) receiveUnion2();
|
||||
//(CanvasPattern? or CanvasGradient) receiveUnionContainingNull();
|
||||
//(CanvasPattern or CanvasGradient)? receiveNullableUnion();
|
||||
//(object or long)? receiveNullableUnion2();
|
||||
(CanvasPattern or CanvasGradient) receiveUnion();
|
||||
(object or long) receiveUnion2();
|
||||
(CanvasPattern? or CanvasGradient) receiveUnionContainingNull();
|
||||
(CanvasPattern or CanvasGradient)? receiveNullableUnion();
|
||||
(object or long)? receiveNullableUnion2();
|
||||
|
||||
//attribute (CanvasPattern or CanvasGradient) writableUnion;
|
||||
//attribute (CanvasPattern? or CanvasGradient) writableUnionContainingNull;
|
||||
//attribute (CanvasPattern or CanvasGradient)? writableNullableUnion;
|
||||
attribute (CanvasPattern or CanvasGradient) writableUnion;
|
||||
attribute (CanvasPattern? or CanvasGradient) writableUnionContainingNull;
|
||||
attribute (CanvasPattern or CanvasGradient)? writableNullableUnion;
|
||||
|
||||
// Date types
|
||||
void passDate(Date arg);
|
||||
|
|
|
@ -40,6 +40,8 @@ skip-if = (toolkit == 'gonk' && debug) #debug-only failure; bug 926547
|
|||
[test_named_getter_enumerability.html]
|
||||
[test_Object.prototype_props.html]
|
||||
[test_queryInterface.html]
|
||||
[test_returnUnion.html]
|
||||
skip-if = debug == false
|
||||
[test_scalarvaluestring.html]
|
||||
skip-if = debug == false
|
||||
[test_sequence_wrapping.html]
|
||||
|
|
|
@ -0,0 +1,59 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<!--
|
||||
https://bugzilla.mozilla.org/show_bug.cgi?id=1048659
|
||||
-->
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Test for Bug 1048659</title>
|
||||
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
|
||||
<script type="application/javascript">
|
||||
|
||||
/** Test for returning unions from JS-implemented WebIDL. **/
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
SpecialPowers.pushPrefEnv({set: [['dom.expose_test_interfaces', true]]}, go);
|
||||
|
||||
function go() {
|
||||
var t = new TestInterfaceJS();
|
||||
var t2 = new TestInterfaceJS();
|
||||
|
||||
is(t.pingPongUnion(t2), t2, "ping pong union for left case should be identity");
|
||||
is(t.pingPongUnion(12), 12, "ping pong union for right case should be identity");
|
||||
|
||||
is(t.pingPongUnionContainingNull("this is not a string"), "this is not a string",
|
||||
"ping pong union containing union for left case should be identity");
|
||||
is(t.pingPongUnionContainingNull(null), null,
|
||||
"ping pong union containing null for right case null should be identity");
|
||||
is(t.pingPongUnionContainingNull(t2), t2,
|
||||
"ping pong union containing null for right case should be identity");
|
||||
|
||||
is(t.pingPongNullableUnion(t2), t2, "ping pong nullable union for left case should be identity");
|
||||
is(t.pingPongNullableUnion(12), 12, "ping pong nullable union for right case should be identity");
|
||||
is(t.pingPongNullableUnion(null), null, "ping pong nullable union for null case should be identity");
|
||||
|
||||
var rejectedBadUnion = false;
|
||||
var result = null;
|
||||
try {
|
||||
result = t.returnBadUnion();
|
||||
} catch (e) {
|
||||
rejectedBadUnion = true;
|
||||
}
|
||||
is(result, null, "bad union should not set a value for result");
|
||||
ok(rejectedBadUnion, "bad union should throw an exception");
|
||||
|
||||
SimpleTest.finish();
|
||||
}
|
||||
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1048659">Mozilla Bug 1048659</a>
|
||||
<p id="display"></p>
|
||||
<div id="content" style="display: none">
|
||||
|
||||
</div>
|
||||
<pre id="test">
|
||||
</pre>
|
||||
</body>
|
||||
</html>
|
|
@ -164,6 +164,13 @@ BrowserElementParent::DispatchOpenWindowEvent(Element* aOpenerFrameElement,
|
|||
return BrowserElementParent::OPEN_WINDOW_IGNORED;
|
||||
}
|
||||
|
||||
// Do not dispatch a mozbrowseropenwindow event of a widget to its embedder
|
||||
nsCOMPtr<nsIMozBrowserFrame> browserFrame =
|
||||
do_QueryInterface(aOpenerFrameElement);
|
||||
if (browserFrame && browserFrame->GetReallyIsWidget()) {
|
||||
return BrowserElementParent::OPEN_WINDOW_CANCELLED;
|
||||
}
|
||||
|
||||
nsEventStatus status;
|
||||
bool dispatchSucceeded =
|
||||
DispatchCustomDOMEvent(aOpenerFrameElement,
|
||||
|
@ -349,6 +356,14 @@ BrowserElementParent::DispatchAsyncScrollEvent(TabParent* aTabParent,
|
|||
const CSSRect& aContentRect,
|
||||
const CSSSize& aContentSize)
|
||||
{
|
||||
// Do not dispatch a mozbrowserasyncscroll event of a widget to its embedder
|
||||
nsCOMPtr<Element> frameElement = aTabParent->GetOwnerElement();
|
||||
NS_ENSURE_TRUE(frameElement, false);
|
||||
nsCOMPtr<nsIMozBrowserFrame> browserFrame = do_QueryInterface(frameElement);
|
||||
if (browserFrame && browserFrame->GetReallyIsWidget()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
nsRefPtr<DispatchAsyncScrollEventRunnable> runnable =
|
||||
new DispatchAsyncScrollEventRunnable(aTabParent, aContentRect,
|
||||
aContentSize);
|
||||
|
|
|
@ -116,24 +116,30 @@ function BrowserElementParent(frameLoader, hasRemoteFrame, isPendingFrame) {
|
|||
// Define methods on the frame element.
|
||||
defineNoReturnMethod('setVisible', this._setVisible);
|
||||
defineDOMRequestMethod('getVisible', 'get-visible');
|
||||
defineNoReturnMethod('sendMouseEvent', this._sendMouseEvent);
|
||||
|
||||
// 0 = disabled, 1 = enabled, 2 - auto detect
|
||||
if (getIntPref(TOUCH_EVENTS_ENABLED_PREF, 0) != 0) {
|
||||
defineNoReturnMethod('sendTouchEvent', this._sendTouchEvent);
|
||||
// Not expose security sensitive browser API for widgets
|
||||
if (!this._frameLoader.QueryInterface(Ci.nsIFrameLoader).ownerIsWidget) {
|
||||
defineNoReturnMethod('sendMouseEvent', this._sendMouseEvent);
|
||||
|
||||
// 0 = disabled, 1 = enabled, 2 - auto detect
|
||||
if (getIntPref(TOUCH_EVENTS_ENABLED_PREF, 0) != 0) {
|
||||
defineNoReturnMethod('sendTouchEvent', this._sendTouchEvent);
|
||||
}
|
||||
defineNoReturnMethod('goBack', this._goBack);
|
||||
defineNoReturnMethod('goForward', this._goForward);
|
||||
defineNoReturnMethod('reload', this._reload);
|
||||
defineNoReturnMethod('stop', this._stop);
|
||||
defineMethod('download', this._download);
|
||||
defineDOMRequestMethod('purgeHistory', 'purge-history');
|
||||
defineMethod('getScreenshot', this._getScreenshot);
|
||||
defineNoReturnMethod('zoom', this._zoom);
|
||||
|
||||
defineDOMRequestMethod('getCanGoBack', 'get-can-go-back');
|
||||
defineDOMRequestMethod('getCanGoForward', 'get-can-go-forward');
|
||||
}
|
||||
defineNoReturnMethod('goBack', this._goBack);
|
||||
defineNoReturnMethod('goForward', this._goForward);
|
||||
defineNoReturnMethod('reload', this._reload);
|
||||
defineNoReturnMethod('stop', this._stop);
|
||||
defineNoReturnMethod('zoom', this._zoom);
|
||||
defineMethod('download', this._download);
|
||||
defineDOMRequestMethod('purgeHistory', 'purge-history');
|
||||
defineMethod('getScreenshot', this._getScreenshot);
|
||||
|
||||
defineMethod('addNextPaintListener', this._addNextPaintListener);
|
||||
defineMethod('removeNextPaintListener', this._removeNextPaintListener);
|
||||
defineDOMRequestMethod('getCanGoBack', 'get-can-go-back');
|
||||
defineDOMRequestMethod('getCanGoForward', 'get-can-go-forward');
|
||||
|
||||
let principal = this._frameElement.ownerDocument.nodePrincipal;
|
||||
let perm = Services.perms
|
||||
|
@ -217,6 +223,9 @@ BrowserElementParent.prototype = {
|
|||
_setupMessageListener: function() {
|
||||
this._mm = this._frameLoader.messageManager;
|
||||
let self = this;
|
||||
let isWidget = this._frameLoader
|
||||
.QueryInterface(Ci.nsIFrameLoader)
|
||||
.ownerIsWidget;
|
||||
|
||||
// Messages we receive are handed to functions which take a (data) argument,
|
||||
// where |data| is the message manager's data object.
|
||||
|
@ -224,26 +233,14 @@ BrowserElementParent.prototype = {
|
|||
// on data.msg_name
|
||||
let mmCalls = {
|
||||
"hello": this._recvHello,
|
||||
"contextmenu": this._fireCtxMenuEvent,
|
||||
"locationchange": this._fireEventFromMsg,
|
||||
"loadstart": this._fireProfiledEventFromMsg,
|
||||
"loadend": this._fireProfiledEventFromMsg,
|
||||
"titlechange": this._fireProfiledEventFromMsg,
|
||||
"iconchange": this._fireEventFromMsg,
|
||||
"manifestchange": this._fireEventFromMsg,
|
||||
"metachange": this._fireEventFromMsg,
|
||||
"close": this._fireEventFromMsg,
|
||||
"resize": this._fireEventFromMsg,
|
||||
"activitydone": this._fireEventFromMsg,
|
||||
"opensearch": this._fireEventFromMsg,
|
||||
"securitychange": this._fireEventFromMsg,
|
||||
"error": this._fireEventFromMsg,
|
||||
"scroll": this._fireEventFromMsg,
|
||||
"firstpaint": this._fireProfiledEventFromMsg,
|
||||
"documentfirstpaint": this._fireProfiledEventFromMsg,
|
||||
"nextpaint": this._recvNextPaint,
|
||||
"keyevent": this._fireKeyEvent,
|
||||
"showmodalprompt": this._handleShowModalPrompt,
|
||||
"got-purge-history": this._gotDOMRequestResult,
|
||||
"got-screenshot": this._gotDOMRequestResult,
|
||||
"got-can-go-back": this._gotDOMRequestResult,
|
||||
|
@ -257,9 +254,31 @@ BrowserElementParent.prototype = {
|
|||
"selectionchange": this._handleSelectionChange
|
||||
};
|
||||
|
||||
let mmSecuritySensitiveCalls = {
|
||||
"showmodalprompt": this._handleShowModalPrompt,
|
||||
"contextmenu": this._fireCtxMenuEvent,
|
||||
"securitychange": this._fireEventFromMsg,
|
||||
"locationchange": this._fireEventFromMsg,
|
||||
"iconchange": this._fireEventFromMsg,
|
||||
"titlechange": this._fireProfiledEventFromMsg,
|
||||
"opensearch": this._fireEventFromMsg,
|
||||
"manifestchange": this._fireEventFromMsg,
|
||||
"metachange": this._fireEventFromMsg,
|
||||
"resize": this._fireEventFromMsg,
|
||||
"activitydone": this._fireEventFromMsg,
|
||||
"scroll": this._fireEventFromMsg
|
||||
};
|
||||
|
||||
this._mm.addMessageListener('browser-element-api:call', function(aMsg) {
|
||||
if (self._isAlive() && (aMsg.data.msg_name in mmCalls)) {
|
||||
if (!self._isAlive()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (aMsg.data.msg_name in mmCalls) {
|
||||
return mmCalls[aMsg.data.msg_name].apply(self, arguments);
|
||||
} else if (!isWidget && aMsg.data.msg_name in mmSecuritySensitiveCalls) {
|
||||
return mmSecuritySensitiveCalls[aMsg.data.msg_name]
|
||||
.apply(self, arguments);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
@ -294,26 +313,29 @@ BrowserElementParent.prototype = {
|
|||
}
|
||||
};
|
||||
|
||||
if (authDetail.isOnlyPassword) {
|
||||
// We don't handle password-only prompts, so just cancel it.
|
||||
// 1. We don't handle password-only prompts.
|
||||
// 2. We don't handle for widget case because of security concern.
|
||||
if (authDetail.isOnlyPassword ||
|
||||
this._frameLoader.QueryInterface(Ci.nsIFrameLoader).ownerIsWidget) {
|
||||
cancelCallback();
|
||||
return;
|
||||
} else { /* username and password */
|
||||
let detail = {
|
||||
host: authDetail.host,
|
||||
realm: authDetail.realm
|
||||
};
|
||||
|
||||
evt = this._createEvent('usernameandpasswordrequired', detail,
|
||||
/* cancelable */ true);
|
||||
Cu.exportFunction(function(username, password) {
|
||||
if (callbackCalled)
|
||||
return;
|
||||
callbackCalled = true;
|
||||
callback(true, username, password);
|
||||
}, evt.detail, { defineAs: 'authenticate' });
|
||||
}
|
||||
|
||||
/* username and password */
|
||||
let detail = {
|
||||
host: authDetail.host,
|
||||
realm: authDetail.realm
|
||||
};
|
||||
|
||||
evt = this._createEvent('usernameandpasswordrequired', detail,
|
||||
/* cancelable */ true);
|
||||
Cu.exportFunction(function(username, password) {
|
||||
if (callbackCalled)
|
||||
return;
|
||||
callbackCalled = true;
|
||||
callback(true, username, password);
|
||||
}, evt.detail, { defineAs: 'authenticate' });
|
||||
|
||||
Cu.exportFunction(cancelCallback, evt.detail, { defineAs: 'cancel' });
|
||||
|
||||
this._frameElement.dispatchEvent(evt);
|
||||
|
|
|
@ -84,7 +84,6 @@ NS_IMPL_CYCLE_COLLECTING_RELEASE(IMEContentObserver)
|
|||
IMEContentObserver::IMEContentObserver()
|
||||
: mESM(nullptr)
|
||||
, mPreCharacterDataChangeLength(-1)
|
||||
, mIsEditorInTransaction(false)
|
||||
, mIsSelectionChangeEventPending(false)
|
||||
, mSelectionChangeCausedOnlyByComposition(false)
|
||||
, mIsPositionChangeEventPending(false)
|
||||
|
@ -911,7 +910,6 @@ IMEContentObserver::AttributeChanged(nsIDocument* aDocument,
|
|||
NS_IMETHODIMP
|
||||
IMEContentObserver::EditAction()
|
||||
{
|
||||
mIsEditorInTransaction = false;
|
||||
mEndOfAddedTextCache.Clear();
|
||||
mStartOfRemovingTextRangeCache.Clear();
|
||||
FlushMergeableNotifications();
|
||||
|
@ -921,7 +919,6 @@ IMEContentObserver::EditAction()
|
|||
NS_IMETHODIMP
|
||||
IMEContentObserver::BeforeEditAction()
|
||||
{
|
||||
mIsEditorInTransaction = true;
|
||||
mEndOfAddedTextCache.Clear();
|
||||
mStartOfRemovingTextRangeCache.Clear();
|
||||
return NS_OK;
|
||||
|
@ -930,7 +927,6 @@ IMEContentObserver::BeforeEditAction()
|
|||
NS_IMETHODIMP
|
||||
IMEContentObserver::CancelEditAction()
|
||||
{
|
||||
mIsEditorInTransaction = false;
|
||||
mEndOfAddedTextCache.Clear();
|
||||
mStartOfRemovingTextRangeCache.Clear();
|
||||
FlushMergeableNotifications();
|
||||
|
@ -988,10 +984,14 @@ private:
|
|||
void
|
||||
IMEContentObserver::FlushMergeableNotifications()
|
||||
{
|
||||
// If we're in handling an edit action, this method will be called later.
|
||||
// If this is already detached from the widget, this doesn't need to notify
|
||||
// anything.
|
||||
if (mIsEditorInTransaction || !mWidget) {
|
||||
if (!mWidget) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If we're in handling an edit action, this method will be called later.
|
||||
if (mEditor && mEditor->GetIsInEditAction()) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -217,7 +217,6 @@ private:
|
|||
uint32_t mPreAttrChangeLength;
|
||||
int64_t mPreCharacterDataChangeLength;
|
||||
|
||||
bool mIsEditorInTransaction;
|
||||
bool mIsSelectionChangeEventPending;
|
||||
bool mSelectionChangeCausedOnlyByComposition;
|
||||
bool mIsPositionChangeEventPending;
|
||||
|
|
|
@ -11,12 +11,18 @@
|
|||
* We expose Gecko-internal helpers related to "web apps" through this
|
||||
* sub-interface.
|
||||
*/
|
||||
[scriptable, uuid(7bd62430-c374-49eb-be1b-ce821a180360)]
|
||||
[scriptable, uuid(1d856b11-ac29-47d3-bd52-a86e3d45fcf4)]
|
||||
interface mozIApplication: nsISupports
|
||||
{
|
||||
/* Return true if this app has |permission|. */
|
||||
boolean hasPermission(in string permission);
|
||||
|
||||
/**
|
||||
* Return true if this app can be a widget and
|
||||
* its |widgetPages| contains |page|
|
||||
*/
|
||||
boolean hasWidgetPage(in DOMString pageURL);
|
||||
|
||||
/* Application status as defined in nsIPrincipal. */
|
||||
readonly attribute unsigned short appStatus;
|
||||
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
|
||||
interface nsITabParent;
|
||||
|
||||
[scriptable, builtinclass, uuid(929AED00-3E15-49B7-8CA2-75003715B7E7)]
|
||||
[scriptable, builtinclass, uuid(0c0a862c-1a47-43c0-ae9e-d51835e3e1a6)]
|
||||
interface nsIMozBrowserFrame : nsIDOMMozBrowserFrame
|
||||
{
|
||||
/**
|
||||
|
@ -25,11 +25,24 @@ interface nsIMozBrowserFrame : nsIDOMMozBrowserFrame
|
|||
* Gets whether this frame really is an app frame.
|
||||
*
|
||||
* In order to really be an app frame, this frame must really be a browser
|
||||
* frame (this requirement will go away eventually), and the frame's mozapp
|
||||
* attribute must point to the manifest of a valid app.
|
||||
* frame (this requirement will go away eventually), and must satisfy one
|
||||
* and only one of the following conditions:
|
||||
* 1. the frame's mozapp attribute must point to the manifest of a valid app
|
||||
* 2. the frame's mozwidget attribute must point to the manifest of a valid
|
||||
* app, and the src should be in the |widgetPages| specified by the manifest.
|
||||
*/
|
||||
[infallible] readonly attribute boolean reallyIsApp;
|
||||
|
||||
/**
|
||||
* Gets whether this frame really is a widget frame.
|
||||
*
|
||||
* In order to really be a frame, this frame must really be a browser
|
||||
* frame (this requirement will go away eventually), the frame's mozwidget
|
||||
* attribute must point to the manifest of a valid app, and the src should
|
||||
* be in the |widgetPages| specified by the manifest.
|
||||
*/
|
||||
[infallible] readonly attribute boolean reallyIsWidget;
|
||||
|
||||
/**
|
||||
* This corresponds to the expecting-system-message attribute, which tells us
|
||||
* whether we should expect that this frame will receive a system message once
|
||||
|
@ -41,7 +54,8 @@ interface nsIMozBrowserFrame : nsIDOMMozBrowserFrame
|
|||
[infallible] readonly attribute boolean isExpectingSystemMessage;
|
||||
|
||||
/**
|
||||
* Gets this frame's app manifest URL, if the frame really is an app frame.
|
||||
* Gets this frame's app manifest URL or widget manifest URL, if the frame
|
||||
* really is an app frame.
|
||||
* Otherwise, returns the empty string.
|
||||
*
|
||||
* This method is guaranteed not to fail.
|
||||
|
|
|
@ -88,6 +88,7 @@ var ecmaGlobals =
|
|||
"Uint8ClampedArray",
|
||||
"URIError",
|
||||
"WeakMap",
|
||||
"WeakSet",
|
||||
];
|
||||
// IMPORTANT: Do not change the list above without review from
|
||||
// a JavaScript Engine peer!
|
||||
|
|
|
@ -28,3 +28,6 @@ interface AudioBufferSourceNode : AudioNode {
|
|||
|
||||
attribute EventHandler onended;
|
||||
};
|
||||
|
||||
// Mozilla extensions
|
||||
AudioBufferSourceNode implements AudioNodePassThrough;
|
||||
|
|
|
@ -14,3 +14,6 @@ interface MediaElementAudioSourceNode : AudioNode {
|
|||
|
||||
};
|
||||
|
||||
// Mozilla extensions
|
||||
MediaElementAudioSourceNode implements AudioNodePassThrough;
|
||||
|
||||
|
|
|
@ -14,3 +14,6 @@ interface MediaStreamAudioSourceNode : AudioNode {
|
|||
|
||||
};
|
||||
|
||||
// Mozilla extensions
|
||||
MediaStreamAudioSourceNode implements AudioNodePassThrough;
|
||||
|
||||
|
|
Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше
Загрузка…
Ссылка в новой задаче