mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-27 06:43:32 +00:00
Merge m-c to b2g-inbound. a=merge
CLOSED TREE
This commit is contained in:
commit
f6cb1cdd44
1
AUTHORS
1
AUTHORS
@ -359,6 +359,7 @@ Florian Scholz <elchi3@elchi3.de>
|
||||
<flying@dom.natm.ru>
|
||||
France Telecom Research and Development
|
||||
Franck
|
||||
Francois Marier <francois@fmarier.org>
|
||||
Frank Tang <ftang@netscape.com>
|
||||
Frank Yan <fyan@mozilla.com>
|
||||
Franky Braem
|
||||
|
@ -134,6 +134,7 @@ skip-if = e10s # Bug 1093153 - no about:home support yet
|
||||
[browser_search_favicon.js]
|
||||
[browser_alltabslistener.js]
|
||||
[browser_audioTabIcon.js]
|
||||
skip-if = e10s # Bug 1192449
|
||||
[browser_autocomplete_a11y_label.js]
|
||||
skip-if = e10s # Bug 1101993 - times out for unknown reasons when run in the dir (works on its own)
|
||||
[browser_autocomplete_cursor.js]
|
||||
|
@ -411,15 +411,13 @@ Toolbox.prototype = {
|
||||
|
||||
// Lazily connect to the profiler here and don't wait for it to complete,
|
||||
// used to intercept console.profile calls before the performance tools are open.
|
||||
let profilerReady = this.initPerformance();
|
||||
let performanceFrontConnection = this.initPerformance();
|
||||
|
||||
// However, while testing, we must wait for the performance connection to
|
||||
// finish, as most tests shut down without waiting for a toolbox
|
||||
// destruction event, resulting in the shared profiler connection being
|
||||
// opened and closed outside of the test that originally opened the
|
||||
// toolbox.
|
||||
// If in testing environment, wait for performance connection to finish,
|
||||
// so we don't have to explicitly wait for this in tests; ideally, all tests
|
||||
// will handle this on their own, but each have their own tear down function.
|
||||
if (DevToolsUtils.testing) {
|
||||
yield profilerReady;
|
||||
yield performanceFrontConnection;
|
||||
}
|
||||
|
||||
this.emit("ready");
|
||||
@ -1986,17 +1984,21 @@ Toolbox.prototype = {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.performance) {
|
||||
yield this.performance.open();
|
||||
return this.performance;
|
||||
if (this._performanceFrontConnection) {
|
||||
return this._performanceFrontConnection.promise;
|
||||
}
|
||||
|
||||
this._performance = getPerformanceFront(this.target);
|
||||
this._performanceFrontConnection = promise.defer();
|
||||
|
||||
this._performance = getPerformanceFront(this._target);
|
||||
|
||||
yield this.performance.open();
|
||||
|
||||
// Emit an event when connected, but don't wait on startup for this.
|
||||
this.emit("profiler-connected");
|
||||
|
||||
return this.performance;
|
||||
this._performanceFrontConnection.resolve(this.performance);
|
||||
return this._performanceFrontConnection.promise;
|
||||
}),
|
||||
|
||||
/**
|
||||
@ -2008,6 +2010,11 @@ Toolbox.prototype = {
|
||||
if (!this.performance) {
|
||||
return;
|
||||
}
|
||||
// If still connecting to performance actor, allow the
|
||||
// actor to resolve its connection before attempting to destroy.
|
||||
if (this._performanceFrontConnection) {
|
||||
yield this._performanceFrontConnection.promise;
|
||||
}
|
||||
yield this.performance.destroy();
|
||||
this._performance = null;
|
||||
}),
|
||||
|
@ -222,6 +222,10 @@ function initPerformance(aUrl, tool="performance", targetOps={}) {
|
||||
merge(target, targetOps);
|
||||
|
||||
let toolbox = yield gDevTools.showToolbox(target, tool);
|
||||
|
||||
// Wait for the performance tool to be spun up
|
||||
yield toolbox.initPerformance();
|
||||
|
||||
let panel = toolbox.getCurrentPanel();
|
||||
return { target, panel, toolbox };
|
||||
});
|
||||
|
@ -4807,6 +4807,7 @@ var Utils = {
|
||||
case "CORS":
|
||||
case "Iframe Sandbox":
|
||||
case "Tracking Protection":
|
||||
case "Sub-resource Integrity":
|
||||
return CATEGORY_SECURITY;
|
||||
|
||||
default:
|
||||
|
@ -26,6 +26,17 @@ this.ContentWebRTC = {
|
||||
Services.obs.addObserver(handlePCRequest, "PeerConnection:request", false);
|
||||
Services.obs.addObserver(updateIndicators, "recording-device-events", false);
|
||||
Services.obs.addObserver(removeBrowserSpecificIndicator, "recording-window-ended", false);
|
||||
|
||||
if (Services.appinfo.processType == Services.appinfo.PROCESS_TYPE_CONTENT)
|
||||
Services.obs.addObserver(processShutdown, "content-child-shutdown", false);
|
||||
},
|
||||
|
||||
uninit: function() {
|
||||
Services.obs.removeObserver(handleRequest, "getUserMedia:request");
|
||||
Services.obs.removeObserver(updateIndicators, "recording-device-events");
|
||||
Services.obs.removeObserver(removeBrowserSpecificIndicator, "recording-window-ended");
|
||||
Services.obs.removeObserver(processShutdown, "content-child-shutdown");
|
||||
this._initialized = false;
|
||||
},
|
||||
|
||||
// Called only for 'unload' to remove pending gUM prompts in reloaded frames.
|
||||
@ -316,3 +327,7 @@ function getMessageManagerForWindow(aContentWindow) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function processShutdown() {
|
||||
ContentWebRTC.uninit();
|
||||
}
|
||||
|
@ -26,6 +26,7 @@ this.webrtcUI = {
|
||||
.getService(Ci.nsIMessageBroadcaster);
|
||||
ppmm.addMessageListener("webrtc:UpdatingIndicators", this);
|
||||
ppmm.addMessageListener("webrtc:UpdateGlobalIndicators", this);
|
||||
ppmm.addMessageListener("child-process-shutdown", this);
|
||||
|
||||
let mm = Cc["@mozilla.org/globalmessagemanager;1"]
|
||||
.getService(Ci.nsIMessageListenerManager);
|
||||
@ -53,10 +54,47 @@ this.webrtcUI = {
|
||||
mm.removeMessageListener("webrtc:UpdateBrowserIndicators", this);
|
||||
},
|
||||
|
||||
showGlobalIndicator: false,
|
||||
showCameraIndicator: false,
|
||||
showMicrophoneIndicator: false,
|
||||
showScreenSharingIndicator: "", // either "Application", "Screen", "Window" or "Browser"
|
||||
processIndicators: new Map(),
|
||||
|
||||
get showGlobalIndicator() {
|
||||
for (let [, indicators] of this.processIndicators) {
|
||||
if (indicators.showGlobalIndicator)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
||||
get showCameraIndicator() {
|
||||
for (let [, indicators] of this.processIndicators) {
|
||||
if (indicators.showCameraIndicator)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
||||
get showMicrophoneIndicator() {
|
||||
for (let [, indicators] of this.processIndicators) {
|
||||
if (indicators.showMicrophoneIndicator)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
||||
get showScreenSharingIndicator() {
|
||||
let list = [""];
|
||||
for (let [, indicators] of this.processIndicators) {
|
||||
if (indicators.showScreenSharingIndicator)
|
||||
list.push(indicators.showScreenSharingIndicator);
|
||||
}
|
||||
|
||||
let precedence =
|
||||
["Screen", "Window", "Application", "Browser", ""];
|
||||
|
||||
list.sort((a, b) => { return precedence.indexOf(a) -
|
||||
precedence.indexOf(b); });
|
||||
|
||||
return list[0];
|
||||
},
|
||||
|
||||
_streams: [],
|
||||
// The boolean parameters indicate which streams should be included in the result.
|
||||
@ -179,12 +217,16 @@ this.webrtcUI = {
|
||||
webrtcUI._streams = [];
|
||||
break;
|
||||
case "webrtc:UpdateGlobalIndicators":
|
||||
updateIndicators(aMessage.data)
|
||||
updateIndicators(aMessage.data, aMessage.target);
|
||||
break;
|
||||
case "webrtc:UpdateBrowserIndicators":
|
||||
webrtcUI._streams.push({browser: aMessage.target, state: aMessage.data});
|
||||
updateBrowserSpecificIndicator(aMessage.target, aMessage.data);
|
||||
break;
|
||||
case "child-process-shutdown":
|
||||
webrtcUI.processIndicators.delete(aMessage.target);
|
||||
updateIndicators(null, null);
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
@ -741,11 +783,22 @@ function maybeAddMenuIndicator(window) {
|
||||
|
||||
var gIndicatorWindow = null;
|
||||
|
||||
function updateIndicators(data) {
|
||||
webrtcUI.showGlobalIndicator = data.showGlobalIndicator;
|
||||
webrtcUI.showCameraIndicator = data.showCameraIndicator;
|
||||
webrtcUI.showMicrophoneIndicator = data.showMicrophoneIndicator;
|
||||
webrtcUI.showScreenSharingIndicator = data.showScreenSharingIndicator;
|
||||
function updateIndicators(data, target) {
|
||||
if (data) {
|
||||
// the global indicators specific to this process
|
||||
let indicators;
|
||||
if (webrtcUI.processIndicators.has(target)) {
|
||||
indicators = webrtcUI.processIndicators.get(target);
|
||||
} else {
|
||||
indicators = {};
|
||||
webrtcUI.processIndicators.set(target, indicators);
|
||||
}
|
||||
|
||||
indicators.showGlobalIndicator = data.showGlobalIndicator;
|
||||
indicators.showCameraIndicator = data.showCameraIndicator;
|
||||
indicators.showMicrophoneIndicator = data.showMicrophoneIndicator;
|
||||
indicators.showScreenSharingIndicator = data.showScreenSharingIndicator;
|
||||
}
|
||||
|
||||
let browserWindowEnum = Services.wm.getEnumerator("navigator:browser");
|
||||
while (browserWindowEnum.hasMoreElements()) {
|
||||
|
@ -27,6 +27,10 @@ public class AnnotationProcessor {
|
||||
"// will cause your build to fail.\n" +
|
||||
"\n";
|
||||
|
||||
private static final StringBuilder headerFile = new StringBuilder(GENERATED_COMMENT);
|
||||
private static final StringBuilder implementationFile = new StringBuilder(GENERATED_COMMENT);
|
||||
private static final StringBuilder nativesFile = new StringBuilder(GENERATED_COMMENT);
|
||||
|
||||
public static void main(String[] args) {
|
||||
// We expect a list of jars on the commandline. If missing, whinge about it.
|
||||
if (args.length <= 1) {
|
||||
@ -46,7 +50,6 @@ public class AnnotationProcessor {
|
||||
// Get an iterator over the classes in the jar files given...
|
||||
Iterator<ClassWithOptions> jarClassIterator = IterableJarLoadingURLClassLoader.getIteratorOverJars(args);
|
||||
|
||||
StringBuilder headerFile = new StringBuilder(GENERATED_COMMENT);
|
||||
headerFile.append(
|
||||
"#ifndef " + getHeaderGuardName(HEADER_FILE) + "\n" +
|
||||
"#define " + getHeaderGuardName(HEADER_FILE) + "\n" +
|
||||
@ -57,7 +60,6 @@ public class AnnotationProcessor {
|
||||
"namespace widget {\n" +
|
||||
"\n");
|
||||
|
||||
StringBuilder implementationFile = new StringBuilder(GENERATED_COMMENT);
|
||||
implementationFile.append(
|
||||
"#include \"GeneratedJNIWrappers.h\"\n" +
|
||||
"#include \"mozilla/jni/Accessors.h\"\n" +
|
||||
@ -66,7 +68,6 @@ public class AnnotationProcessor {
|
||||
"namespace widget {\n" +
|
||||
"\n");
|
||||
|
||||
StringBuilder nativesFile = new StringBuilder(GENERATED_COMMENT);
|
||||
nativesFile.append(
|
||||
"#ifndef " + getHeaderGuardName(NATIVES_FILE) + "\n" +
|
||||
"#define " + getHeaderGuardName(NATIVES_FILE) + "\n" +
|
||||
@ -79,40 +80,7 @@ public class AnnotationProcessor {
|
||||
"\n");
|
||||
|
||||
while (jarClassIterator.hasNext()) {
|
||||
ClassWithOptions aClassTuple = jarClassIterator.next();
|
||||
|
||||
CodeGenerator generatorInstance;
|
||||
|
||||
// Get an iterator over the appropriately generated methods of this class
|
||||
Iterator<AnnotatableEntity> methodIterator = new GeneratableElementIterator(aClassTuple.wrappedClass);
|
||||
|
||||
if (!methodIterator.hasNext()) {
|
||||
continue;
|
||||
}
|
||||
generatorInstance = new CodeGenerator(aClassTuple);
|
||||
|
||||
// Iterate all annotated members in this class..
|
||||
while (methodIterator.hasNext()) {
|
||||
AnnotatableEntity aElementTuple = methodIterator.next();
|
||||
switch (aElementTuple.mEntityType) {
|
||||
case METHOD:
|
||||
generatorInstance.generateMethod(aElementTuple);
|
||||
break;
|
||||
case NATIVE:
|
||||
generatorInstance.generateNative(aElementTuple);
|
||||
break;
|
||||
case FIELD:
|
||||
generatorInstance.generateField(aElementTuple);
|
||||
break;
|
||||
case CONSTRUCTOR:
|
||||
generatorInstance.generateConstructor(aElementTuple);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
headerFile.append(generatorInstance.getHeaderFileContents());
|
||||
implementationFile.append(generatorInstance.getWrapperFileContents());
|
||||
nativesFile.append(generatorInstance.getNativesFileContents());
|
||||
generateClass(jarClassIterator.next());
|
||||
}
|
||||
|
||||
implementationFile.append(
|
||||
@ -132,10 +100,52 @@ public class AnnotationProcessor {
|
||||
writeOutputFile(SOURCE_FILE, implementationFile);
|
||||
writeOutputFile(HEADER_FILE, headerFile);
|
||||
writeOutputFile(NATIVES_FILE, nativesFile);
|
||||
|
||||
long e = System.currentTimeMillis();
|
||||
System.out.println("Annotation processing complete in " + (e - s) + "ms");
|
||||
}
|
||||
|
||||
private static void generateClass(final ClassWithOptions annotatedClass) {
|
||||
// Get an iterator over the appropriately generated methods of this class
|
||||
final GeneratableElementIterator methodIterator
|
||||
= new GeneratableElementIterator(annotatedClass);
|
||||
final ClassWithOptions[] innerClasses = methodIterator.getInnerClasses();
|
||||
|
||||
if (!methodIterator.hasNext() && innerClasses.length == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
final CodeGenerator generatorInstance = new CodeGenerator(annotatedClass);
|
||||
generatorInstance.generateClasses(innerClasses);
|
||||
|
||||
// Iterate all annotated members in this class..
|
||||
while (methodIterator.hasNext()) {
|
||||
AnnotatableEntity aElementTuple = methodIterator.next();
|
||||
switch (aElementTuple.mEntityType) {
|
||||
case METHOD:
|
||||
generatorInstance.generateMethod(aElementTuple);
|
||||
break;
|
||||
case NATIVE:
|
||||
generatorInstance.generateNative(aElementTuple);
|
||||
break;
|
||||
case FIELD:
|
||||
generatorInstance.generateField(aElementTuple);
|
||||
break;
|
||||
case CONSTRUCTOR:
|
||||
generatorInstance.generateConstructor(aElementTuple);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
headerFile.append(generatorInstance.getHeaderFileContents());
|
||||
implementationFile.append(generatorInstance.getWrapperFileContents());
|
||||
nativesFile.append(generatorInstance.getNativesFileContents());
|
||||
|
||||
for (ClassWithOptions innerClass : innerClasses) {
|
||||
generateClass(innerClass);
|
||||
}
|
||||
}
|
||||
|
||||
private static String getHeaderGuardName(final String name) {
|
||||
return name.replaceAll("\\W", "_");
|
||||
}
|
||||
|
@ -34,20 +34,21 @@ public class CodeGenerator {
|
||||
this.cls = annotatedClass.wrappedClass;
|
||||
this.clsName = annotatedClass.generatedName;
|
||||
|
||||
final String unqualifiedName = Utils.getUnqualifiedName(clsName);
|
||||
header.append(
|
||||
"class " + clsName + " : public mozilla::jni::Class<" + clsName + ">\n" +
|
||||
"class " + clsName + " : public mozilla::jni::Class<" + unqualifiedName + ">\n" +
|
||||
"{\n" +
|
||||
"public:\n" +
|
||||
" typedef mozilla::jni::Ref<" + clsName + "> Ref;\n" +
|
||||
" typedef mozilla::jni::LocalRef<" + clsName + "> LocalRef;\n" +
|
||||
" typedef mozilla::jni::GlobalRef<" + clsName + "> GlobalRef;\n" +
|
||||
" typedef const mozilla::jni::Param<" + clsName + ">& Param;\n" +
|
||||
" typedef mozilla::jni::Ref<" + unqualifiedName + "> Ref;\n" +
|
||||
" typedef mozilla::jni::LocalRef<" + unqualifiedName + "> LocalRef;\n" +
|
||||
" typedef mozilla::jni::GlobalRef<" + unqualifiedName + "> GlobalRef;\n" +
|
||||
" typedef const mozilla::jni::Param<" + unqualifiedName + ">& Param;\n" +
|
||||
"\n" +
|
||||
" static constexpr char name[] =\n" +
|
||||
" \"" + cls.getName().replace('.', '/') + "\";\n" +
|
||||
"\n" +
|
||||
"protected:\n" +
|
||||
" " + clsName + "(jobject instance) : Class(instance) {}\n" +
|
||||
" using Class::Class;\n" +
|
||||
"\n");
|
||||
|
||||
cpp.append(
|
||||
@ -57,7 +58,7 @@ public class CodeGenerator {
|
||||
natives.append(
|
||||
"template<class Impl>\n" +
|
||||
"class " + clsName + "::Natives : " +
|
||||
"public mozilla::jni::NativeImpl<" + clsName + ", Impl>\n" +
|
||||
"public mozilla::jni::NativeImpl<" + unqualifiedName + ", Impl>\n" +
|
||||
"{\n");
|
||||
}
|
||||
|
||||
@ -67,14 +68,14 @@ public class CodeGenerator {
|
||||
|
||||
private String getNativeParameterType(Class<?> type, AnnotationInfo info) {
|
||||
if (type == cls) {
|
||||
return clsName + "::Param";
|
||||
return Utils.getUnqualifiedName(clsName) + "::Param";
|
||||
}
|
||||
return Utils.getNativeParameterType(type, info);
|
||||
}
|
||||
|
||||
private String getNativeReturnType(Class<?> type, AnnotationInfo info) {
|
||||
if (type == cls) {
|
||||
return clsName + "::LocalRef";
|
||||
return Utils.getUnqualifiedName(clsName) + "::LocalRef";
|
||||
}
|
||||
return Utils.getNativeReturnType(type, info);
|
||||
}
|
||||
@ -92,7 +93,7 @@ public class CodeGenerator {
|
||||
header.append(
|
||||
"public:\n" +
|
||||
" struct " + getTraitsName(uniqueName, /* includeScope */ false) + " {\n" +
|
||||
" typedef " + clsName + " Owner;\n" +
|
||||
" typedef " + Utils.getUnqualifiedName(clsName) + " Owner;\n" +
|
||||
" typedef " + getNativeReturnType(type, info) + " ReturnType;\n" +
|
||||
" typedef " + getNativeParameterType(type, info) + " SetterType;\n" +
|
||||
" typedef mozilla::jni::Args<" + args + "> Args;\n" +
|
||||
@ -136,16 +137,13 @@ public class CodeGenerator {
|
||||
*/
|
||||
private String generatePrototype(String name, Class<?>[] argTypes,
|
||||
Class<?> returnType, AnnotationInfo info,
|
||||
boolean includeScope, boolean includeArgName) {
|
||||
boolean includeScope, boolean includeArgName,
|
||||
boolean isConst) {
|
||||
|
||||
final StringBuilder proto = new StringBuilder();
|
||||
int argIndex = 0;
|
||||
|
||||
if (info.catchException) {
|
||||
proto.append("nsresult ");
|
||||
} else {
|
||||
proto.append(getNativeReturnType(returnType, info)).append(' ');
|
||||
}
|
||||
proto.append("auto ");
|
||||
|
||||
if (includeScope) {
|
||||
proto.append(clsName).append("::");
|
||||
@ -173,7 +171,18 @@ public class CodeGenerator {
|
||||
proto.setLength(proto.length() - 2);
|
||||
}
|
||||
|
||||
return proto.append(')').toString();
|
||||
proto.append(')');
|
||||
|
||||
if (isConst) {
|
||||
proto.append(" const");
|
||||
}
|
||||
|
||||
if (info.catchException) {
|
||||
proto.append(" -> nsresult");
|
||||
} else {
|
||||
proto.append(" -> ").append(getNativeReturnType(returnType, info));
|
||||
}
|
||||
return proto.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -186,8 +195,8 @@ public class CodeGenerator {
|
||||
|
||||
return (isStatic ? "static " : "") +
|
||||
generatePrototype(name, argTypes, returnType, info,
|
||||
/* includeScope */ false, /* includeArgName */ false) +
|
||||
(isStatic ? ";" : " const;");
|
||||
/* includeScope */ false, /* includeArgName */ false,
|
||||
/* isConst */ !isStatic) + ';';
|
||||
}
|
||||
|
||||
/**
|
||||
@ -199,11 +208,8 @@ public class CodeGenerator {
|
||||
|
||||
final StringBuilder def = new StringBuilder(
|
||||
generatePrototype(name, argTypes, returnType, info,
|
||||
/* includeScope */ true, /* includeArgName */ true));
|
||||
|
||||
if (!isStatic) {
|
||||
def.append(" const");
|
||||
}
|
||||
/* includeScope */ true, /* includeArgName */ true,
|
||||
/* isConst */ !isStatic));
|
||||
def.append("\n{\n");
|
||||
|
||||
|
||||
@ -485,6 +491,21 @@ public class CodeGenerator {
|
||||
}
|
||||
}
|
||||
|
||||
public void generateClasses(final ClassWithOptions[] classes) {
|
||||
if (classes.length == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
header.append(
|
||||
"public:\n");
|
||||
for (final ClassWithOptions cls : classes) {
|
||||
// Extract "Inner" from "Outer::Inner".
|
||||
header.append(
|
||||
" class " + Utils.getUnqualifiedName(cls.generatedName) + ";\n");
|
||||
}
|
||||
header.append('\n');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the finalised bytes to go into the generated wrappers file.
|
||||
*
|
||||
|
@ -28,20 +28,21 @@ public class JarClassIterator implements Iterator<ClassWithOptions> {
|
||||
String className = mTargetClassListIterator.next();
|
||||
try {
|
||||
Class<?> ret = mTarget.loadClass(className);
|
||||
final String canonicalName;
|
||||
|
||||
// Incremental builds can leave stale classfiles in the jar. Such classfiles will cause
|
||||
// an exception at this point. We can safely ignore these classes - they cannot possibly
|
||||
// ever be loaded as they conflict with their parent class and will be killed by Proguard
|
||||
// later on anyway.
|
||||
// ever be loaded as they conflict with their parent class and will be killed by
|
||||
// Proguard later on anyway.
|
||||
final Class<?> enclosingClass;
|
||||
try {
|
||||
canonicalName = ret.getCanonicalName();
|
||||
enclosingClass = ret.getEnclosingClass();
|
||||
} catch (IncompatibleClassChangeError e) {
|
||||
return next();
|
||||
}
|
||||
|
||||
if (canonicalName == null || "null".equals(canonicalName)) {
|
||||
if (enclosingClass != null) {
|
||||
// Anonymous inner class - unsupported.
|
||||
// Or named inner class, which will be processed when we process the outer class.
|
||||
return next();
|
||||
}
|
||||
|
||||
|
@ -6,6 +6,7 @@ package org.mozilla.gecko.annotationProcessors.utils;
|
||||
|
||||
import org.mozilla.gecko.annotationProcessors.AnnotationInfo;
|
||||
import org.mozilla.gecko.annotationProcessors.classloader.AnnotatableEntity;
|
||||
import org.mozilla.gecko.annotationProcessors.classloader.ClassWithOptions;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.AnnotatedElement;
|
||||
@ -13,6 +14,7 @@ import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Member;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Arrays;
|
||||
import java.util.Comparator;
|
||||
import java.util.Iterator;
|
||||
|
||||
/**
|
||||
@ -21,13 +23,17 @@ import java.util.Iterator;
|
||||
* parameters) and the argument.
|
||||
*/
|
||||
public class GeneratableElementIterator implements Iterator<AnnotatableEntity> {
|
||||
private final ClassWithOptions mClass;
|
||||
private final Member[] mObjects;
|
||||
private AnnotatableEntity mNextReturnValue;
|
||||
private int mElementIndex;
|
||||
|
||||
private boolean mIterateEveryEntry;
|
||||
|
||||
public GeneratableElementIterator(Class<?> aClass) {
|
||||
public GeneratableElementIterator(ClassWithOptions annotatedClass) {
|
||||
mClass = annotatedClass;
|
||||
|
||||
final Class<?> aClass = annotatedClass.wrappedClass;
|
||||
// Get all the elements of this class as AccessibleObjects.
|
||||
Member[] aMethods = aClass.getDeclaredMethods();
|
||||
Member[] aFields = aClass.getDeclaredFields();
|
||||
@ -59,6 +65,56 @@ public class GeneratableElementIterator implements Iterator<AnnotatableEntity> {
|
||||
findNextValue();
|
||||
}
|
||||
|
||||
private Class<?>[] getFilteredInnerClasses() {
|
||||
// Go through all inner classes and see which ones we want to generate.
|
||||
final Class<?>[] candidates = mClass.wrappedClass.getDeclaredClasses();
|
||||
int count = 0;
|
||||
|
||||
for (int i = 0; i < candidates.length; ++i) {
|
||||
final GeneratableElementIterator testIterator
|
||||
= new GeneratableElementIterator(new ClassWithOptions(candidates[i], null));
|
||||
if (testIterator.hasNext()
|
||||
|| testIterator.getFilteredInnerClasses() != null) {
|
||||
count++;
|
||||
continue;
|
||||
}
|
||||
// Clear out ones that don't match.
|
||||
candidates[i] = null;
|
||||
}
|
||||
return count > 0 ? candidates : null;
|
||||
}
|
||||
|
||||
public ClassWithOptions[] getInnerClasses() {
|
||||
final Class<?>[] candidates = getFilteredInnerClasses();
|
||||
if (candidates == null) {
|
||||
return new ClassWithOptions[0];
|
||||
}
|
||||
|
||||
int count = 0;
|
||||
for (Class<?> candidate : candidates) {
|
||||
if (candidate != null) {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
|
||||
final ClassWithOptions[] ret = new ClassWithOptions[count];
|
||||
count = 0;
|
||||
for (Class<?> candidate : candidates) {
|
||||
if (candidate != null) {
|
||||
ret[count++] = new ClassWithOptions(
|
||||
candidate, mClass.generatedName + "::" + candidate.getSimpleName());
|
||||
}
|
||||
}
|
||||
assert ret.length == count;
|
||||
|
||||
Arrays.sort(ret, new Comparator<ClassWithOptions>() {
|
||||
@Override public int compare(ClassWithOptions lhs, ClassWithOptions rhs) {
|
||||
return lhs.generatedName.compareTo(rhs.generatedName);
|
||||
}
|
||||
});
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find and cache the next appropriately annotated method, plus the annotation parameter, if
|
||||
* one exists. Otherwise cache null, so hasNext returns false.
|
||||
|
@ -225,6 +225,10 @@ public class Utils {
|
||||
return member.getName();
|
||||
}
|
||||
|
||||
public static String getUnqualifiedName(String name) {
|
||||
return name.substring(name.lastIndexOf(':') + 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if a member is declared static.
|
||||
*
|
||||
|
@ -11,8 +11,6 @@ ANDROID_EXTRA_JARS += \
|
||||
$(srcdir)/robotium-solo-4.3.1.jar \
|
||||
$(NULL)
|
||||
|
||||
ANDROID_ASSETS_DIR := $(TESTPATH)/assets
|
||||
|
||||
_JAVA_HARNESS := \
|
||||
Actions.java \
|
||||
Assert.java \
|
||||
|
@ -7,6 +7,9 @@
|
||||
DEFINES['ANDROID_PACKAGE_NAME'] = CONFIG['ANDROID_PACKAGE_NAME']
|
||||
|
||||
base = '/mobile/android/tests/browser/robocop/'
|
||||
|
||||
ANDROID_ASSETS_DIRS += [base + 'assets']
|
||||
|
||||
TEST_HARNESS_FILES.testing.mochitest += [
|
||||
base + 'robocop.ini',
|
||||
base + 'robocop_autophone.ini',
|
||||
|
@ -16,9 +16,9 @@ endif #} JAVAFILES
|
||||
|
||||
|
||||
ifdef ANDROID_APK_NAME #{
|
||||
android_res_dirs := $(addprefix $(srcdir)/,$(or $(ANDROID_RES_DIRS),res))
|
||||
android_res_dirs := $(or $(ANDROID_RES_DIRS),$(srcdir)/res)
|
||||
_ANDROID_RES_FLAG := $(addprefix -S ,$(android_res_dirs))
|
||||
_ANDROID_ASSETS_FLAG := $(addprefix -A ,$(ANDROID_ASSETS_DIR))
|
||||
_ANDROID_ASSETS_FLAG := $(if $(ANDROID_ASSETS_DIRS),$(addprefix -A ,$(ANDROID_ASSETS_DIRS)))
|
||||
android_manifest := $(or $(ANDROID_MANIFEST_FILE),AndroidManifest.xml)
|
||||
|
||||
GENERATED_DIRS += classes
|
||||
@ -45,7 +45,7 @@ $(ANDROID_APK_NAME).ap_: .aapt.deps ;
|
||||
# resource files one subdirectory below the parent resource directory.
|
||||
android_res_files := $(wildcard $(addsuffix /*,$(wildcard $(addsuffix /*,$(android_res_dirs)))))
|
||||
|
||||
.aapt.deps: $(android_manifest) $(android_res_files) $(wildcard $(ANDROID_ASSETS_DIR))
|
||||
.aapt.deps: $(android_manifest) $(android_res_files) $(wildcard $(ANDROID_ASSETS_DIRS))
|
||||
@$(TOUCH) $@
|
||||
$(AAPT) package -f -M $< -I $(ANDROID_SDK)/android.jar $(_ANDROID_RES_FLAG) $(_ANDROID_ASSETS_FLAG) \
|
||||
-J ${@D} \
|
||||
|
@ -432,6 +432,7 @@ LOCAL_INCLUDES += [
|
||||
'/layout/svg',
|
||||
'/layout/xul',
|
||||
'/netwerk/base',
|
||||
'/security/manager/ssl',
|
||||
'/widget',
|
||||
'/xpcom/ds',
|
||||
]
|
||||
|
@ -51,6 +51,16 @@
|
||||
#include "nsParserConstants.h"
|
||||
#include "nsSandboxFlags.h"
|
||||
|
||||
static PRLogModuleInfo*
|
||||
GetSriLog()
|
||||
{
|
||||
static PRLogModuleInfo *gSriPRLog;
|
||||
if (!gSriPRLog) {
|
||||
gSriPRLog = PR_NewLogModule("SRI");
|
||||
}
|
||||
return gSriPRLog;
|
||||
}
|
||||
|
||||
using namespace mozilla;
|
||||
|
||||
PRLogModuleInfo* gContentSinkLogModuleInfo;
|
||||
@ -750,12 +760,23 @@ nsContentSink::ProcessStyleLink(nsIContent* aElement,
|
||||
aElement->NodeType() == nsIDOMNode::PROCESSING_INSTRUCTION_NODE,
|
||||
"We only expect processing instructions here");
|
||||
|
||||
nsAutoString integrity;
|
||||
if (aElement) {
|
||||
aElement->GetAttr(kNameSpaceID_None, nsGkAtoms::integrity, integrity);
|
||||
}
|
||||
if (!integrity.IsEmpty()) {
|
||||
MOZ_LOG(GetSriLog(), mozilla::LogLevel::Debug,
|
||||
("nsContentSink::ProcessStyleLink, integrity=%s",
|
||||
NS_ConvertUTF16toUTF8(integrity).get()));
|
||||
}
|
||||
|
||||
// If this is a fragment parser, we don't want to observe.
|
||||
// We don't support CORS for processing instructions
|
||||
bool isAlternate;
|
||||
rv = mCSSLoader->LoadStyleLink(aElement, url, aTitle, aMedia, aAlternate,
|
||||
CORS_NONE, mDocument->GetReferrerPolicy(),
|
||||
mRunsToCompletion ? nullptr : this, &isAlternate);
|
||||
integrity, mRunsToCompletion ? nullptr : this,
|
||||
&isAlternate);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
if (!isAlternate && !mRunsToCompletion) {
|
||||
|
@ -9876,7 +9876,8 @@ NS_IMPL_ISUPPORTS(StubCSSLoaderObserver, nsICSSLoaderObserver)
|
||||
void
|
||||
nsDocument::PreloadStyle(nsIURI* uri, const nsAString& charset,
|
||||
const nsAString& aCrossOriginAttr,
|
||||
const ReferrerPolicy aReferrerPolicy)
|
||||
const ReferrerPolicy aReferrerPolicy,
|
||||
const nsAString& aIntegrity)
|
||||
{
|
||||
// The CSSLoader will retain this object after we return.
|
||||
nsCOMPtr<nsICSSLoaderObserver> obs = new StubCSSLoaderObserver();
|
||||
@ -9886,7 +9887,7 @@ nsDocument::PreloadStyle(nsIURI* uri, const nsAString& charset,
|
||||
NS_LossyConvertUTF16toASCII(charset),
|
||||
obs,
|
||||
Element::StringToCORSMode(aCrossOriginAttr),
|
||||
aReferrerPolicy);
|
||||
aReferrerPolicy, aIntegrity);
|
||||
}
|
||||
|
||||
nsresult
|
||||
|
@ -1147,7 +1147,8 @@ public:
|
||||
|
||||
virtual void PreloadStyle(nsIURI* uri, const nsAString& charset,
|
||||
const nsAString& aCrossOriginAttr,
|
||||
ReferrerPolicy aReferrerPolicy) override;
|
||||
ReferrerPolicy aReferrerPolicy,
|
||||
const nsAString& aIntegrity) override;
|
||||
|
||||
virtual nsresult LoadChromeSheetSync(nsIURI* uri, bool isAgentSheet,
|
||||
mozilla::CSSStyleSheet** sheet) override;
|
||||
|
@ -489,6 +489,7 @@ GK_ATOM(instanceOf, "instanceOf")
|
||||
GK_ATOM(int32, "int32")
|
||||
GK_ATOM(int64, "int64")
|
||||
GK_ATOM(integer, "integer")
|
||||
GK_ATOM(integrity, "integrity")
|
||||
GK_ATOM(intersection, "intersection")
|
||||
GK_ATOM(is, "is")
|
||||
GK_ATOM(iscontainer, "iscontainer")
|
||||
|
@ -152,8 +152,8 @@ typedef CallbackObjectHolder<NodeFilter, nsIDOMNodeFilter> NodeFilterHolder;
|
||||
} // namespace mozilla
|
||||
|
||||
#define NS_IDOCUMENT_IID \
|
||||
{ 0xbbce44c8, 0x22fe, 0x404f, \
|
||||
{ 0x9e, 0x71, 0x23, 0x1d, 0xf4, 0xcc, 0x8e, 0x34 } }
|
||||
{ 0x6d18ec0b, 0x1f68, 0x4ae6, \
|
||||
{ 0x8b, 0x3d, 0x8d, 0x7d, 0x8b, 0x8e, 0x28, 0xd4 } }
|
||||
|
||||
// Enum for requesting a particular type of document when creating a doc
|
||||
enum DocumentFlavor {
|
||||
@ -2023,7 +2023,8 @@ public:
|
||||
*/
|
||||
virtual void PreloadStyle(nsIURI* aURI, const nsAString& aCharset,
|
||||
const nsAString& aCrossOriginAttr,
|
||||
ReferrerPolicyEnum aReferrerPolicy) = 0;
|
||||
ReferrerPolicyEnum aReferrerPolicy,
|
||||
const nsAString& aIntegrity) = 0;
|
||||
|
||||
/**
|
||||
* Called by the chrome registry to load style sheets. Can be put
|
||||
|
@ -53,9 +53,21 @@
|
||||
|
||||
#include "mozilla/Attributes.h"
|
||||
#include "mozilla/unused.h"
|
||||
#include "mozilla/dom/SRICheck.h"
|
||||
#include "nsIScriptError.h"
|
||||
|
||||
static PRLogModuleInfo* gCspPRLog;
|
||||
|
||||
static PRLogModuleInfo*
|
||||
GetSriLog()
|
||||
{
|
||||
static PRLogModuleInfo *gSriPRLog;
|
||||
if (!gSriPRLog) {
|
||||
gSriPRLog = PR_NewLogModule("SRI");
|
||||
}
|
||||
return gSriPRLog;
|
||||
}
|
||||
|
||||
using namespace mozilla;
|
||||
using namespace mozilla::dom;
|
||||
|
||||
@ -606,7 +618,22 @@ nsScriptLoader::ProcessScriptElement(nsIScriptElement *aElement)
|
||||
|
||||
if (!request) {
|
||||
// no usable preload
|
||||
request = new nsScriptLoadRequest(aElement, version, ourCORSMode);
|
||||
|
||||
SRIMetadata sriMetadata;
|
||||
{
|
||||
nsAutoString integrity;
|
||||
scriptContent->GetAttr(kNameSpaceID_None, nsGkAtoms::integrity,
|
||||
integrity);
|
||||
if (!integrity.IsEmpty()) {
|
||||
MOZ_LOG(GetSriLog(), mozilla::LogLevel::Debug,
|
||||
("nsScriptLoader::ProcessScriptElement, integrity=%s",
|
||||
NS_ConvertUTF16toUTF8(integrity).get()));
|
||||
SRICheck::IntegrityMetadata(integrity, mDocument, &sriMetadata);
|
||||
}
|
||||
}
|
||||
|
||||
request = new nsScriptLoadRequest(aElement, version, ourCORSMode,
|
||||
sriMetadata);
|
||||
request->mURI = scriptURI;
|
||||
request->mIsInline = false;
|
||||
request->mLoading = true;
|
||||
@ -720,7 +747,8 @@ nsScriptLoader::ProcessScriptElement(nsIScriptElement *aElement)
|
||||
}
|
||||
|
||||
// Inline scripts ignore ther CORS mode and are always CORS_NONE
|
||||
request = new nsScriptLoadRequest(aElement, version, CORS_NONE);
|
||||
request = new nsScriptLoadRequest(aElement, version, CORS_NONE,
|
||||
SRIMetadata()); // SRI doesn't apply
|
||||
request->mJSVersion = version;
|
||||
request->mLoading = false;
|
||||
request->mIsInline = true;
|
||||
@ -1408,8 +1436,15 @@ nsScriptLoader::OnStreamComplete(nsIStreamLoader* aLoader,
|
||||
NS_ASSERTION(request, "null request in stream complete handler");
|
||||
NS_ENSURE_TRUE(request, NS_ERROR_FAILURE);
|
||||
|
||||
nsresult rv = PrepareLoadedRequest(request, aLoader, aStatus, aStringLen,
|
||||
aString);
|
||||
nsresult rv = NS_ERROR_SRI_CORRUPT;
|
||||
if (request->mIntegrity.IsEmpty() ||
|
||||
NS_SUCCEEDED(SRICheck::VerifyIntegrity(request->mIntegrity,
|
||||
request->mURI,
|
||||
request->mCORSMode, aStringLen,
|
||||
aString, mDocument))) {
|
||||
rv = PrepareLoadedRequest(request, aLoader, aStatus, aStringLen, aString);
|
||||
}
|
||||
|
||||
if (NS_FAILED(rv)) {
|
||||
/*
|
||||
* Handle script not loading error because source was a tracking URL.
|
||||
@ -1603,6 +1638,7 @@ void
|
||||
nsScriptLoader::PreloadURI(nsIURI *aURI, const nsAString &aCharset,
|
||||
const nsAString &aType,
|
||||
const nsAString &aCrossOrigin,
|
||||
const nsAString& aIntegrity,
|
||||
bool aScriptFromHead,
|
||||
const mozilla::net::ReferrerPolicy aReferrerPolicy)
|
||||
{
|
||||
@ -1611,9 +1647,18 @@ nsScriptLoader::PreloadURI(nsIURI *aURI, const nsAString &aCharset,
|
||||
return;
|
||||
}
|
||||
|
||||
SRIMetadata sriMetadata;
|
||||
if (!aIntegrity.IsEmpty()) {
|
||||
MOZ_LOG(GetSriLog(), mozilla::LogLevel::Debug,
|
||||
("nsScriptLoader::PreloadURI, integrity=%s",
|
||||
NS_ConvertUTF16toUTF8(aIntegrity).get()));
|
||||
SRICheck::IntegrityMetadata(aIntegrity, mDocument, &sriMetadata);
|
||||
}
|
||||
|
||||
nsRefPtr<nsScriptLoadRequest> request =
|
||||
new nsScriptLoadRequest(nullptr, 0,
|
||||
Element::StringToCORSMode(aCrossOrigin));
|
||||
Element::StringToCORSMode(aCrossOrigin),
|
||||
sriMetadata);
|
||||
request->mURI = aURI;
|
||||
request->mIsInline = false;
|
||||
request->mLoading = true;
|
||||
|
@ -19,6 +19,7 @@
|
||||
#include "nsIDocument.h"
|
||||
#include "nsIStreamLoader.h"
|
||||
#include "mozilla/CORSMode.h"
|
||||
#include "mozilla/dom/SRIMetadata.h"
|
||||
#include "mozilla/LinkedList.h"
|
||||
#include "mozilla/net/ReferrerPolicy.h"
|
||||
|
||||
@ -56,7 +57,8 @@ class nsScriptLoadRequest final : public nsISupports,
|
||||
public:
|
||||
nsScriptLoadRequest(nsIScriptElement* aElement,
|
||||
uint32_t aVersion,
|
||||
mozilla::CORSMode aCORSMode)
|
||||
mozilla::CORSMode aCORSMode,
|
||||
const mozilla::dom::SRIMetadata &aIntegrity)
|
||||
: mElement(aElement),
|
||||
mLoading(true),
|
||||
mIsInline(true),
|
||||
@ -71,6 +73,7 @@ public:
|
||||
mJSVersion(aVersion),
|
||||
mLineNo(1),
|
||||
mCORSMode(aCORSMode),
|
||||
mIntegrity(aIntegrity),
|
||||
mReferrerPolicy(mozilla::net::RP_Default)
|
||||
{
|
||||
}
|
||||
@ -122,6 +125,7 @@ public:
|
||||
nsAutoCString mURL; // Keep the URI's filename alive during off thread parsing.
|
||||
int32_t mLineNo;
|
||||
const mozilla::CORSMode mCORSMode;
|
||||
const mozilla::dom::SRIMetadata mIntegrity;
|
||||
mozilla::net::ReferrerPolicy mReferrerPolicy;
|
||||
};
|
||||
|
||||
@ -367,11 +371,13 @@ public:
|
||||
* @param aType The type parameter for the script.
|
||||
* @param aCrossOrigin The crossorigin attribute for the script.
|
||||
* Void if not present.
|
||||
* @param aIntegrity The expect hash url, if avail, of the request
|
||||
* @param aScriptFromHead Whether or not the script was a child of head
|
||||
*/
|
||||
virtual void PreloadURI(nsIURI *aURI, const nsAString &aCharset,
|
||||
const nsAString &aType,
|
||||
const nsAString &aCrossOrigin,
|
||||
const nsAString& aIntegrity,
|
||||
bool aScriptFromHead,
|
||||
const mozilla::net::ReferrerPolicy aReferrerPolicy);
|
||||
|
||||
|
@ -421,13 +421,21 @@ nsStyleLinkElement::DoUpdateStyleSheet(nsIDocument* aOldDocument,
|
||||
scopeElement, aObserver, &doneLoading, &isAlternate);
|
||||
}
|
||||
else {
|
||||
nsAutoString integrity;
|
||||
thisContent->GetAttr(kNameSpaceID_None, nsGkAtoms::integrity, integrity);
|
||||
if (!integrity.IsEmpty()) {
|
||||
MOZ_LOG(GetSriLog(), mozilla::LogLevel::Debug,
|
||||
("nsStyleLinkElement::DoUpdateStyleSheet, integrity=%s",
|
||||
NS_ConvertUTF16toUTF8(integrity).get()));
|
||||
}
|
||||
|
||||
// XXXbz clone the URI here to work around content policies modifying URIs.
|
||||
nsCOMPtr<nsIURI> clonedURI;
|
||||
uri->Clone(getter_AddRefs(clonedURI));
|
||||
NS_ENSURE_TRUE(clonedURI, NS_ERROR_OUT_OF_MEMORY);
|
||||
rv = doc->CSSLoader()->
|
||||
LoadStyleLink(thisContent, clonedURI, title, media, isAlternate,
|
||||
GetCORSMode(), doc->GetReferrerPolicy(),
|
||||
GetCORSMode(), doc->GetReferrerPolicy(), integrity,
|
||||
aObserver, &isAlternate);
|
||||
if (NS_FAILED(rv)) {
|
||||
// Don't propagate LoadStyleLink() errors further than this, since some
|
||||
|
@ -214,6 +214,11 @@ HTMLLinkElement::ParseAttribute(int32_t aNamespaceID,
|
||||
aResult.ParseAtomArray(aValue);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (aAttribute == nsGkAtoms::integrity) {
|
||||
aResult.ParseStringOrAtom(aValue);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return nsGenericHTMLElement::ParseAttribute(aNamespaceID, aAttribute, aValue,
|
||||
|
@ -143,6 +143,14 @@ public:
|
||||
{
|
||||
SetHTMLAttr(nsGkAtoms::target, aTarget, aRv);
|
||||
}
|
||||
void GetIntegrity(nsAString& aIntegrity) const
|
||||
{
|
||||
GetHTMLAttr(nsGkAtoms::integrity, aIntegrity);
|
||||
}
|
||||
void SetIntegrity(const nsAString& aIntegrity, ErrorResult& aRv)
|
||||
{
|
||||
SetHTMLAttr(nsGkAtoms::integrity, aIntegrity, aRv);
|
||||
}
|
||||
|
||||
already_AddRefed<nsIDocument> GetImport();
|
||||
already_AddRefed<ImportLoader> GetImportLoader()
|
||||
|
@ -75,10 +75,16 @@ HTMLScriptElement::ParseAttribute(int32_t aNamespaceID,
|
||||
const nsAString& aValue,
|
||||
nsAttrValue& aResult)
|
||||
{
|
||||
if (aNamespaceID == kNameSpaceID_None &&
|
||||
aAttribute == nsGkAtoms::crossorigin) {
|
||||
ParseCORSValue(aValue, aResult);
|
||||
return true;
|
||||
if (aNamespaceID == kNameSpaceID_None) {
|
||||
if (aAttribute == nsGkAtoms::crossorigin) {
|
||||
ParseCORSValue(aValue, aResult);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (aAttribute == nsGkAtoms::integrity) {
|
||||
aResult.ParseStringOrAtom(aValue);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return nsGenericHTMLElement::ParseAttribute(aNamespaceID, aAttribute, aValue,
|
||||
|
@ -79,6 +79,14 @@ public:
|
||||
{
|
||||
SetOrRemoveNullableStringAttr(nsGkAtoms::crossorigin, aCrossOrigin, aError);
|
||||
}
|
||||
void GetIntegrity(nsAString& aIntegrity)
|
||||
{
|
||||
GetHTMLAttr(nsGkAtoms::integrity, aIntegrity);
|
||||
}
|
||||
void SetIntegrity(const nsAString& aIntegrity, ErrorResult& rv)
|
||||
{
|
||||
SetHTMLAttr(nsGkAtoms::integrity, aIntegrity, rv);
|
||||
}
|
||||
bool Async();
|
||||
void SetAsync(bool aValue, ErrorResult& rv);
|
||||
|
||||
|
@ -53,6 +53,21 @@ LoadingMixedDisplayContent2=Loading mixed (insecure) display content "%1$S" on a
|
||||
# LOCALIZATION NOTE: Do not translate "allow-scripts", "allow-same-origin", "sandbox" or "iframe"
|
||||
BothAllowScriptsAndSameOriginPresent=An iframe which has both allow-scripts and allow-same-origin for its sandbox attribute can remove its sandboxing.
|
||||
|
||||
# Sub-Resource Integrity
|
||||
# LOCALIZATION NOTE: Do not translate "script" or "integrity"
|
||||
MalformedIntegrityURI=The script element has a malformed URI in its integrity attribute: "%1$S". The correct format is "<hash algorithm>-<hash value>".
|
||||
# LOCALIZATION NOTE: Do not translate "integrity"
|
||||
InvalidIntegrityLength=The hash contained in the integrity attribute has the wrong length.
|
||||
# LOCALIZATION NOTE: Do not translate "integrity"
|
||||
InvalidIntegrityBase64=The hash contained in the integrity attribute could not be decoded.
|
||||
# LOCALIZATION NOTE: Do not translate "integrity"
|
||||
IntegrityMismatch=None of the "%1$S" hashes in the integrity attribute match the content of the subresource.
|
||||
IneligibleResource="%1$S" is not eligible for integrity checks since it's neither CORS-enabled nor same-origin.
|
||||
# LOCALIZATION NOTE: Do not translate "integrity"
|
||||
UnsupportedHashAlg=Unsupported hash algorithm in the integrity attribute: "%1$S"
|
||||
# LOCALIZATION NOTE: Do not translate "integrity"
|
||||
NoValidMetadata=The integrity attribute does not contain any valid metadata.
|
||||
|
||||
# LOCALIZATION NOTE: Do not translate "SSL 3.0".
|
||||
WeakProtocolVersionWarning=This site uses the protocol SSL 3.0 for encryption, which is deprecated and insecure.
|
||||
# LOCALIZATION NOTE: Do not translate "RC4".
|
||||
|
@ -26,7 +26,6 @@ public:
|
||||
const nsACString& aContentType) :
|
||||
mBuffer(aBuffer),
|
||||
mLength(aLength),
|
||||
mOffset(0),
|
||||
mPrincipal(aPrincipal),
|
||||
mContentType(aContentType)
|
||||
{
|
||||
@ -59,45 +58,15 @@ private:
|
||||
// The mode is initially MODE_PLAYBACK.
|
||||
virtual void SetReadMode(MediaCacheStream::ReadMode aMode) override {}
|
||||
virtual void SetPlaybackRate(uint32_t aBytesPerSecond) override {}
|
||||
virtual nsresult Read(char* aBuffer, uint32_t aCount, uint32_t* aBytes) override
|
||||
{
|
||||
*aBytes = std::min(mLength - mOffset, aCount);
|
||||
memcpy(aBuffer, mBuffer + mOffset, *aBytes);
|
||||
mOffset += *aBytes;
|
||||
MOZ_ASSERT(mOffset <= mLength);
|
||||
return NS_OK;
|
||||
}
|
||||
virtual nsresult ReadAt(int64_t aOffset, char* aBuffer,
|
||||
uint32_t aCount, uint32_t* aBytes) override
|
||||
{
|
||||
nsresult rv = Seek(nsISeekableStream::NS_SEEK_SET, aOffset);
|
||||
if (NS_FAILED(rv)) return rv;
|
||||
return Read(aBuffer, aCount, aBytes);
|
||||
}
|
||||
virtual nsresult Seek(int32_t aWhence, int64_t aOffset) override
|
||||
{
|
||||
MOZ_ASSERT(aOffset <= UINT32_MAX);
|
||||
switch (aWhence) {
|
||||
case nsISeekableStream::NS_SEEK_SET:
|
||||
if (aOffset < 0 || aOffset > mLength) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
mOffset = static_cast<uint32_t> (aOffset);
|
||||
break;
|
||||
case nsISeekableStream::NS_SEEK_CUR:
|
||||
if (aOffset >= mLength - mOffset) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
mOffset += static_cast<uint32_t> (aOffset);
|
||||
break;
|
||||
case nsISeekableStream::NS_SEEK_END:
|
||||
if (aOffset < 0 || aOffset > mLength) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
mOffset = mLength - aOffset;
|
||||
break;
|
||||
if (aOffset < 0 || aOffset > mLength) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
*aBytes = std::min(mLength - static_cast<uint32_t>(aOffset), aCount);
|
||||
memcpy(aBuffer, mBuffer + aOffset, *aBytes);
|
||||
mOffset = aOffset + *aBytes;
|
||||
return NS_OK;
|
||||
}
|
||||
virtual int64_t Tell() override { return mOffset; }
|
||||
|
@ -426,6 +426,13 @@ DecodedStream::SetVolume(double aVolume)
|
||||
mVolume = aVolume;
|
||||
}
|
||||
|
||||
void
|
||||
DecodedStream::SetSameOrigin(bool aSameOrigin)
|
||||
{
|
||||
ReentrantMonitorAutoEnter mon(GetReentrantMonitor());
|
||||
mSameOrigin = aSameOrigin;
|
||||
}
|
||||
|
||||
void
|
||||
DecodedStream::InitTracks()
|
||||
{
|
||||
@ -674,14 +681,14 @@ DecodedStream::AdvanceTracks()
|
||||
}
|
||||
|
||||
bool
|
||||
DecodedStream::SendData(bool aIsSameOrigin)
|
||||
DecodedStream::SendData()
|
||||
{
|
||||
ReentrantMonitorAutoEnter mon(GetReentrantMonitor());
|
||||
MOZ_ASSERT(mStartTime.isSome(), "Must be called after StartPlayback()");
|
||||
|
||||
InitTracks();
|
||||
SendAudio(mVolume, aIsSameOrigin);
|
||||
SendVideo(aIsSameOrigin);
|
||||
SendAudio(mVolume, mSameOrigin);
|
||||
SendVideo(mSameOrigin);
|
||||
AdvanceTracks();
|
||||
|
||||
bool finished = (!mInfo.HasAudio() || mAudioQueue.IsFinished()) &&
|
||||
|
@ -64,6 +64,7 @@ public:
|
||||
|
||||
void SetPlaying(bool aPlaying);
|
||||
void SetVolume(double aVolume);
|
||||
void SetSameOrigin(bool aSameOrigin);
|
||||
|
||||
int64_t AudioEndTime() const;
|
||||
int64_t GetPosition() const;
|
||||
@ -71,7 +72,7 @@ public:
|
||||
bool HasConsumers() const;
|
||||
|
||||
// Return true if stream is finished.
|
||||
bool SendData(bool aIsSameOrigin);
|
||||
bool SendData();
|
||||
|
||||
protected:
|
||||
virtual ~DecodedStream();
|
||||
@ -101,6 +102,7 @@ private:
|
||||
|
||||
bool mPlaying;
|
||||
double mVolume;
|
||||
bool mSameOrigin;
|
||||
|
||||
Maybe<int64_t> mStartTime;
|
||||
MediaInfo mInfo;
|
||||
|
@ -332,6 +332,7 @@ MediaDecoderStateMachine::InitializationTask()
|
||||
mWatchManager.Watch(mObservedDuration, &MediaDecoderStateMachine::RecomputeDuration);
|
||||
mWatchManager.Watch(mPlayState, &MediaDecoderStateMachine::PlayStateChanged);
|
||||
mWatchManager.Watch(mLogicallySeeking, &MediaDecoderStateMachine::LogicallySeekingChanged);
|
||||
mWatchManager.Watch(mSameOriginMedia, &MediaDecoderStateMachine::SameOriginMediaChanged);
|
||||
}
|
||||
|
||||
bool MediaDecoderStateMachine::HasFutureAudio()
|
||||
@ -375,7 +376,7 @@ void MediaDecoderStateMachine::SendStreamData()
|
||||
AssertCurrentThreadInMonitor();
|
||||
MOZ_ASSERT(!mAudioSink, "Should've been stopped in RunStateMachine()");
|
||||
|
||||
bool finished = mDecodedStream->SendData(mSameOriginMedia);
|
||||
bool finished = mDecodedStream->SendData();
|
||||
|
||||
const auto clockTime = GetClock();
|
||||
while (true) {
|
||||
@ -1408,6 +1409,13 @@ void MediaDecoderStateMachine::LogicallySeekingChanged()
|
||||
ScheduleStateMachine();
|
||||
}
|
||||
|
||||
void MediaDecoderStateMachine::SameOriginMediaChanged()
|
||||
{
|
||||
MOZ_ASSERT(OnTaskQueue());
|
||||
ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
|
||||
mDecodedStream->SetSameOrigin(mSameOriginMedia);
|
||||
}
|
||||
|
||||
void MediaDecoderStateMachine::BufferedRangeUpdated()
|
||||
{
|
||||
MOZ_ASSERT(OnTaskQueue());
|
||||
|
@ -524,6 +524,9 @@ protected:
|
||||
// Notification method invoked when mLogicallySeeking changes.
|
||||
void LogicallySeekingChanged();
|
||||
|
||||
// Notification method invoked when mSameOriginMedia changes.
|
||||
void SameOriginMediaChanged();
|
||||
|
||||
// Sets internal state which causes playback of media to pause.
|
||||
// The decoder monitor must be held.
|
||||
void StopPlayback();
|
||||
|
@ -724,20 +724,6 @@ nsresult ChannelMediaResource::ReadFromCache(char* aBuffer,
|
||||
return mCacheStream.ReadFromCache(aBuffer, aOffset, aCount);
|
||||
}
|
||||
|
||||
nsresult ChannelMediaResource::Read(char* aBuffer,
|
||||
uint32_t aCount,
|
||||
uint32_t* aBytes)
|
||||
{
|
||||
NS_ASSERTION(!NS_IsMainThread(), "Don't call on main thread");
|
||||
|
||||
int64_t offset = mCacheStream.Tell();
|
||||
nsresult rv = mCacheStream.Read(aBuffer, aCount, aBytes);
|
||||
if (NS_SUCCEEDED(rv)) {
|
||||
DispatchBytesConsumed(*aBytes, offset);
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
nsresult ChannelMediaResource::ReadAt(int64_t aOffset,
|
||||
char* aBuffer,
|
||||
uint32_t aCount,
|
||||
@ -777,15 +763,6 @@ ChannelMediaResource::MediaReadAt(int64_t aOffset, uint32_t aCount)
|
||||
return bytes.forget();
|
||||
}
|
||||
|
||||
nsresult ChannelMediaResource::Seek(int32_t aWhence, int64_t aOffset)
|
||||
{
|
||||
NS_ASSERTION(!NS_IsMainThread(), "Don't call on main thread");
|
||||
|
||||
CMLOG("Seek requested for aOffset [%lld] for decoder [%p]",
|
||||
aOffset, mDecoder);
|
||||
return mCacheStream.Seek(aWhence, aOffset);
|
||||
}
|
||||
|
||||
int64_t ChannelMediaResource::Tell()
|
||||
{
|
||||
NS_ASSERTION(!NS_IsMainThread(), "Don't call on main thread");
|
||||
@ -1209,12 +1186,9 @@ public:
|
||||
// Other thread
|
||||
virtual void SetReadMode(MediaCacheStream::ReadMode aMode) override {}
|
||||
virtual void SetPlaybackRate(uint32_t aBytesPerSecond) override {}
|
||||
virtual nsresult Read(char* aBuffer, uint32_t aCount, uint32_t* aBytes) override;
|
||||
virtual nsresult ReadAt(int64_t aOffset, char* aBuffer,
|
||||
uint32_t aCount, uint32_t* aBytes) override;
|
||||
virtual already_AddRefed<MediaByteBuffer> MediaReadAt(int64_t aOffset, uint32_t aCount) override;
|
||||
virtual already_AddRefed<MediaByteBuffer> SilentReadAt(int64_t aOffset, uint32_t aCount) override;
|
||||
virtual nsresult Seek(int32_t aWhence, int64_t aOffset) override;
|
||||
virtual int64_t Tell() override;
|
||||
|
||||
// Any thread
|
||||
@ -1500,21 +1474,6 @@ nsresult FileMediaResource::ReadFromCache(char* aBuffer, int64_t aOffset, uint32
|
||||
return seekres;
|
||||
}
|
||||
|
||||
nsresult FileMediaResource::Read(char* aBuffer, uint32_t aCount, uint32_t* aBytes)
|
||||
{
|
||||
nsresult rv;
|
||||
int64_t offset = 0;
|
||||
{
|
||||
MutexAutoLock lock(mLock);
|
||||
mSeekable->Tell(&offset);
|
||||
rv = UnsafeRead(aBuffer, aCount, aBytes);
|
||||
}
|
||||
if (NS_SUCCEEDED(rv)) {
|
||||
DispatchBytesConsumed(*aBytes, offset);
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
nsresult FileMediaResource::UnsafeRead(char* aBuffer, uint32_t aCount, uint32_t* aBytes)
|
||||
{
|
||||
EnsureSizeInitialized();
|
||||
@ -1572,30 +1531,6 @@ FileMediaResource::UnsafeMediaReadAt(int64_t aOffset, uint32_t aCount)
|
||||
return bytes.forget();
|
||||
}
|
||||
|
||||
already_AddRefed<MediaByteBuffer>
|
||||
FileMediaResource::SilentReadAt(int64_t aOffset, uint32_t aCount)
|
||||
{
|
||||
NS_ASSERTION(!NS_IsMainThread(), "Don't call on main thread");
|
||||
|
||||
MutexAutoLock lock(mLock);
|
||||
int64_t pos = 0;
|
||||
NS_ENSURE_TRUE(mSeekable, nullptr);
|
||||
nsresult rv = mSeekable->Tell(&pos);
|
||||
NS_ENSURE_SUCCESS(rv, nullptr);
|
||||
nsRefPtr<MediaByteBuffer> bytes = UnsafeMediaReadAt(aOffset, aCount);
|
||||
UnsafeSeek(nsISeekableStream::NS_SEEK_SET, pos);
|
||||
NS_ENSURE_TRUE(bytes && bytes->Length() == aCount, nullptr);
|
||||
return bytes.forget();
|
||||
}
|
||||
|
||||
nsresult FileMediaResource::Seek(int32_t aWhence, int64_t aOffset)
|
||||
{
|
||||
NS_ASSERTION(!NS_IsMainThread(), "Don't call on main thread");
|
||||
|
||||
MutexAutoLock lock(mLock);
|
||||
return UnsafeSeek(aWhence, aOffset);
|
||||
}
|
||||
|
||||
nsresult FileMediaResource::UnsafeSeek(int32_t aWhence, int64_t aOffset)
|
||||
{
|
||||
NS_ASSERTION(!NS_IsMainThread(), "Don't call on main thread");
|
||||
@ -1752,5 +1687,68 @@ void BaseMediaResource::DispatchBytesConsumed(int64_t aNumBytes, int64_t aOffset
|
||||
NS_DispatchToMainThread(event);
|
||||
}
|
||||
|
||||
nsresult
|
||||
MediaResourceIndex::Read(char* aBuffer, uint32_t aCount, uint32_t* aBytes)
|
||||
{
|
||||
NS_ASSERTION(!NS_IsMainThread(), "Don't call on main thread");
|
||||
|
||||
// We purposefuly don't check that we may attempt to read past
|
||||
// mResource->GetLength() as the resource's length may change over time.
|
||||
|
||||
nsresult rv = ReadAt(mOffset, aBuffer, aCount, aBytes);
|
||||
if (NS_FAILED(rv)) {
|
||||
return rv;
|
||||
}
|
||||
mOffset += *aBytes;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsresult
|
||||
MediaResourceIndex::ReadAt(int64_t aOffset, char* aBuffer,
|
||||
uint32_t aCount, uint32_t* aBytes) const
|
||||
{
|
||||
*aBytes = 0;
|
||||
while (aCount > 0) {
|
||||
uint32_t bytesRead = 0;
|
||||
nsresult rv = mResource->ReadAt(aOffset, aBuffer, aCount, &bytesRead);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
if (!bytesRead) {
|
||||
break;
|
||||
}
|
||||
*aBytes += bytesRead;
|
||||
aOffset += bytesRead;
|
||||
aBuffer += bytesRead;
|
||||
aCount -= bytesRead;
|
||||
}
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsresult
|
||||
MediaResourceIndex::Seek(int32_t aWhence, int64_t aOffset)
|
||||
{
|
||||
switch (aWhence) {
|
||||
case SEEK_SET:
|
||||
break;
|
||||
case SEEK_CUR:
|
||||
aOffset += mOffset;
|
||||
break;
|
||||
case SEEK_END:
|
||||
{
|
||||
int64_t length = mResource->GetLength();
|
||||
if (length == -1 || length - aOffset < 0) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
aOffset = mResource->GetLength() - aOffset;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
mOffset = aOffset;
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
} // namespace mozilla
|
||||
|
||||
|
@ -277,13 +277,6 @@ public:
|
||||
// the media plays continuously. The cache can't guess this itself
|
||||
// because it doesn't know when the decoder was paused, buffering, etc.
|
||||
virtual void SetPlaybackRate(uint32_t aBytesPerSecond) = 0;
|
||||
// Read up to aCount bytes from the stream. The buffer must have
|
||||
// enough room for at least aCount bytes. Stores the number of
|
||||
// actual bytes read in aBytes (0 on end of file).
|
||||
// May read less than aCount bytes if the number of
|
||||
// available bytes is less than aCount. Always check *aBytes after
|
||||
// read, and call again if necessary.
|
||||
virtual nsresult Read(char* aBuffer, uint32_t aCount, uint32_t* aBytes) = 0;
|
||||
// Read up to aCount bytes from the stream. The read starts at
|
||||
// aOffset in the stream, seeking to that location initially if
|
||||
// it is not the current stream offset. The remaining arguments,
|
||||
@ -316,59 +309,10 @@ public:
|
||||
return bytes.forget();
|
||||
}
|
||||
|
||||
// ReadAt without side-effects. Given that our MediaResource infrastructure
|
||||
// is very side-effecty, this accomplishes its job by checking the initial
|
||||
// position and seeking back to it. If the seek were to fail, a side-effect
|
||||
// might be observable.
|
||||
//
|
||||
// This method returns null if anything fails, including the failure to read
|
||||
// aCount bytes. Otherwise, it returns an owned buffer.
|
||||
virtual already_AddRefed<MediaByteBuffer> SilentReadAt(int64_t aOffset, uint32_t aCount)
|
||||
{
|
||||
nsRefPtr<MediaByteBuffer> bytes = new MediaByteBuffer(aCount);
|
||||
bytes->SetLength(aCount);
|
||||
nsresult rv =
|
||||
ReadFromCache(reinterpret_cast<char*>(bytes->Elements()), aOffset, aCount);
|
||||
if (NS_SUCCEEDED(rv)) {
|
||||
return bytes.forget();
|
||||
}
|
||||
int64_t pos = Tell();
|
||||
// Free our buffer first to minimize memory usage.
|
||||
bytes = nullptr;
|
||||
bytes = MediaReadAt(aOffset, aCount);
|
||||
Seek(nsISeekableStream::NS_SEEK_SET, pos);
|
||||
NS_ENSURE_TRUE(bytes && bytes->Length() == aCount, nullptr);
|
||||
return bytes.forget();
|
||||
}
|
||||
|
||||
// Seek to the given bytes offset in the stream. aWhence can be
|
||||
// one of:
|
||||
// NS_SEEK_SET
|
||||
// NS_SEEK_CUR
|
||||
// NS_SEEK_END
|
||||
//
|
||||
// In the Http strategy case the cancel will cause the http
|
||||
// channel's listener to close the pipe, forcing an i/o error on any
|
||||
// blocked read. This will allow the decode thread to complete the
|
||||
// event.
|
||||
//
|
||||
// In the case of a seek in progress, the byte range request creates
|
||||
// a new listener. This is done on the main thread via seek
|
||||
// synchronously dispatching an event. This avoids the issue of us
|
||||
// closing the listener but an outstanding byte range request
|
||||
// creating a new one. They run on the same thread so no explicit
|
||||
// synchronisation is required. The byte range request checks for
|
||||
// the cancel flag and does not create a new channel or listener if
|
||||
// we are cancelling.
|
||||
//
|
||||
// The default strategy does not do any seeking - the only issue is
|
||||
// a blocked read which it handles by causing the listener to close
|
||||
// the pipe, as per the http case.
|
||||
//
|
||||
// The file strategy doesn't block for any great length of time so
|
||||
// is fine for a no-op cancel.
|
||||
virtual nsresult Seek(int32_t aWhence, int64_t aOffset) = 0;
|
||||
// Report the current offset in bytes from the start of the stream.
|
||||
// This is used to approximate where we currently are in the playback of a
|
||||
// media.
|
||||
// A call to ReadAt will update this position.
|
||||
virtual int64_t Tell() = 0;
|
||||
// Moves any existing channel loads into or out of background. Background
|
||||
// loads don't block the load event. This also determines whether or not any
|
||||
@ -670,12 +614,10 @@ public:
|
||||
// Other thread
|
||||
virtual void SetReadMode(MediaCacheStream::ReadMode aMode) override;
|
||||
virtual void SetPlaybackRate(uint32_t aBytesPerSecond) override;
|
||||
virtual nsresult Read(char* aBuffer, uint32_t aCount, uint32_t* aBytes) override;
|
||||
virtual nsresult ReadAt(int64_t offset, char* aBuffer,
|
||||
uint32_t aCount, uint32_t* aBytes) override;
|
||||
virtual already_AddRefed<MediaByteBuffer> MediaReadAt(int64_t aOffset, uint32_t aCount) override;
|
||||
virtual nsresult Seek(int32_t aWhence, int64_t aOffset) override;
|
||||
virtual int64_t Tell() override;
|
||||
virtual int64_t Tell() override;
|
||||
|
||||
// Any thread
|
||||
virtual void Pin() override;
|
||||
@ -834,6 +776,99 @@ private:
|
||||
MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
|
||||
};
|
||||
|
||||
/*
|
||||
* MediaResourceIndex provides a way to access MediaResource objects.
|
||||
* Read, Seek and Tell must only be called on non-main threads.
|
||||
* In the case of the Ogg Decoder they are called on the Decode thread for
|
||||
* example. You must ensure that no threads are calling these methods once
|
||||
* the MediaResource has been Closed.
|
||||
*/
|
||||
|
||||
class MediaResourceIndex
|
||||
{
|
||||
public:
|
||||
explicit MediaResourceIndex(MediaResource* aResource)
|
||||
: mResource(aResource)
|
||||
, mOffset(0)
|
||||
{}
|
||||
|
||||
// Read up to aCount bytes from the stream. The buffer must have
|
||||
// enough room for at least aCount bytes. Stores the number of
|
||||
// actual bytes read in aBytes (0 on end of file).
|
||||
// May read less than aCount bytes if the number of
|
||||
// available bytes is less than aCount. Always check *aBytes after
|
||||
// read, and call again if necessary.
|
||||
nsresult Read(char* aBuffer, uint32_t aCount, uint32_t* aBytes);
|
||||
// Seek to the given bytes offset in the stream. aWhence can be
|
||||
// one of:
|
||||
// NS_SEEK_SET
|
||||
// NS_SEEK_CUR
|
||||
// NS_SEEK_END
|
||||
//
|
||||
// In the Http strategy case the cancel will cause the http
|
||||
// channel's listener to close the pipe, forcing an i/o error on any
|
||||
// blocked read. This will allow the decode thread to complete the
|
||||
// event.
|
||||
//
|
||||
// In the case of a seek in progress, the byte range request creates
|
||||
// a new listener. This is done on the main thread via seek
|
||||
// synchronously dispatching an event. This avoids the issue of us
|
||||
// closing the listener but an outstanding byte range request
|
||||
// creating a new one. They run on the same thread so no explicit
|
||||
// synchronisation is required. The byte range request checks for
|
||||
// the cancel flag and does not create a new channel or listener if
|
||||
// we are cancelling.
|
||||
//
|
||||
// The default strategy does not do any seeking - the only issue is
|
||||
// a blocked read which it handles by causing the listener to close
|
||||
// the pipe, as per the http case.
|
||||
//
|
||||
// The file strategy doesn't block for any great length of time so
|
||||
// is fine for a no-op cancel.
|
||||
nsresult Seek(int32_t aWhence, int64_t aOffset);
|
||||
// Report the current offset in bytes from the start of the stream.
|
||||
int64_t Tell() const { return mOffset; }
|
||||
|
||||
// Return the underlying MediaResource.
|
||||
MediaResource* GetResource() const { return mResource; }
|
||||
|
||||
// Read up to aCount bytes from the stream. The read starts at
|
||||
// aOffset in the stream, seeking to that location initially if
|
||||
// it is not the current stream offset.
|
||||
// Unlike MediaResource::ReadAt, ReadAt only returns fewer bytes than
|
||||
// requested if end of stream or an error is encountered. There is no need to
|
||||
// call it again to get more data.
|
||||
// *aBytes will contain the number of bytes copied, even if an error occurred.
|
||||
// ReadAt doesn't have an impact on the offset returned by Tell().
|
||||
nsresult ReadAt(int64_t aOffset, char* aBuffer,
|
||||
uint32_t aCount, uint32_t* aBytes) const;
|
||||
|
||||
// Convenience methods, directly calling the MediaResource method of the same
|
||||
// name.
|
||||
// Those functions do not update the MediaResource offset as returned
|
||||
// by Tell().
|
||||
|
||||
// This method returns nullptr if anything fails.
|
||||
// Otherwise, it returns an owned buffer.
|
||||
// MediaReadAt may return fewer bytes than requested if end of stream is
|
||||
// encountered. There is no need to call it again to get more data.
|
||||
already_AddRefed<MediaByteBuffer> MediaReadAt(int64_t aOffset, uint32_t aCount) const
|
||||
{
|
||||
return mResource->MediaReadAt(aOffset, aCount);
|
||||
}
|
||||
// Get the length of the stream in bytes. Returns -1 if not known.
|
||||
// This can change over time; after a seek operation, a misbehaving
|
||||
// server may give us a resource of a different length to what it had
|
||||
// reported previously --- or it may just lie in its Content-Length
|
||||
// header and give us more or less data than it reported. We will adjust
|
||||
// the result of GetLength to reflect the data that's actually arriving.
|
||||
int64_t GetLength() const { return mResource->GetLength(); }
|
||||
|
||||
private:
|
||||
nsRefPtr<MediaResource> mResource;
|
||||
int64_t mOffset;
|
||||
};
|
||||
|
||||
} // namespace mozilla
|
||||
|
||||
#endif
|
||||
|
@ -133,15 +133,6 @@ public:
|
||||
// dummy
|
||||
virtual void SetPlaybackRate(uint32_t aBytesPerSecond) override {}
|
||||
// dummy
|
||||
virtual nsresult Read(char* aBuffer, uint32_t aCount, uint32_t* aBytes)
|
||||
override {
|
||||
return NS_OK;
|
||||
}
|
||||
// dummy
|
||||
virtual nsresult Seek(int32_t aWhence, int64_t aOffset) override {
|
||||
return NS_OK;
|
||||
}
|
||||
// dummy
|
||||
virtual int64_t Tell() override { return 0; }
|
||||
|
||||
// Any thread
|
||||
|
@ -44,6 +44,7 @@ AppleMP3Reader::AppleMP3Reader(AbstractMediaDecoder *aDecoder)
|
||||
, mAudioFileStream(nullptr)
|
||||
, mAudioConverter(nullptr)
|
||||
, mMP3FrameParser(mDecoder->GetResource()->GetLength())
|
||||
, mResource(mDecoder->GetResource())
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread(), "Should be on main thread");
|
||||
}
|
||||
@ -90,27 +91,15 @@ static void _AudioSampleCallback(void *aThis,
|
||||
nsresult
|
||||
AppleMP3Reader::Read(uint32_t *aNumBytes, char *aData)
|
||||
{
|
||||
MediaResource *resource = mDecoder->GetResource();
|
||||
nsresult rv = mResource.Read(aData, *aNumBytes, aNumBytes);
|
||||
|
||||
// Loop until we have all the data asked for, or we've reached EOS
|
||||
uint32_t totalBytes = 0;
|
||||
uint32_t numBytes;
|
||||
do {
|
||||
uint32_t bytesWanted = *aNumBytes - totalBytes;
|
||||
nsresult rv = resource->Read(aData + totalBytes, bytesWanted, &numBytes);
|
||||
totalBytes += numBytes;
|
||||
if (NS_FAILED(rv)) {
|
||||
*aNumBytes = 0;
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
if (NS_FAILED(rv)) {
|
||||
*aNumBytes = 0;
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
} while(totalBytes < *aNumBytes && numBytes);
|
||||
|
||||
*aNumBytes = totalBytes;
|
||||
|
||||
// We will have read some data in the last iteration iff we filled the buffer.
|
||||
// XXX Maybe return a better value than NS_ERROR_FAILURE?
|
||||
return numBytes ? NS_OK : NS_ERROR_FAILURE;
|
||||
return *aNumBytes ? NS_OK : NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
nsresult
|
||||
@ -257,7 +246,7 @@ AppleMP3Reader::AudioSampleCallback(UInt32 aNumBytes,
|
||||
LOGD("pushed audio at time %lfs; duration %lfs\n",
|
||||
(double)time / USECS_PER_S, (double)duration / USECS_PER_S);
|
||||
|
||||
AudioData *audio = new AudioData(mDecoder->GetResource()->Tell(),
|
||||
AudioData *audio = new AudioData(mResource.Tell(),
|
||||
time, duration, numFrames,
|
||||
reinterpret_cast<AudioDataValue *>(decoded.forget()),
|
||||
mAudioChannels, mAudioSampleRate);
|
||||
@ -517,7 +506,7 @@ AppleMP3Reader::Seek(int64_t aTime, int64_t aEndTime)
|
||||
byteOffset,
|
||||
(flags & kAudioFileStreamSeekFlag_OffsetIsEstimated) ? "YES" : "NO");
|
||||
|
||||
mDecoder->GetResource()->Seek(nsISeekableStream::NS_SEEK_SET, byteOffset);
|
||||
mResource.Seek(nsISeekableStream::NS_SEEK_SET, byteOffset);
|
||||
|
||||
ResetDecode();
|
||||
|
||||
@ -532,7 +521,8 @@ AppleMP3Reader::NotifyDataArrivedInternal(uint32_t aLength, int64_t aOffset)
|
||||
return;
|
||||
}
|
||||
|
||||
nsRefPtr<MediaByteBuffer> bytes = mDecoder->GetResource()->SilentReadAt(aOffset, aLength);
|
||||
nsRefPtr<MediaByteBuffer> bytes =
|
||||
mDecoder->GetResource()->MediaReadAt(aOffset, aLength);
|
||||
NS_ENSURE_TRUE_VOID(bytes);
|
||||
mMP3FrameParser.Parse(bytes->Elements(), aLength, aOffset);
|
||||
if (!mMP3FrameParser.IsMP3()) {
|
||||
|
@ -6,6 +6,7 @@
|
||||
#define __AppleMP3Reader_h__
|
||||
|
||||
#include "MediaDecoderReader.h"
|
||||
#include "MediaResource.h"
|
||||
#include "MP3FrameParser.h"
|
||||
#include "VideoUtils.h"
|
||||
|
||||
@ -79,6 +80,8 @@ private:
|
||||
AudioConverterRef mAudioConverter;
|
||||
|
||||
MP3FrameParser mMP3FrameParser;
|
||||
|
||||
MediaResourceIndex mResource;
|
||||
};
|
||||
|
||||
} // namespace mozilla
|
||||
|
@ -408,7 +408,7 @@ DirectShowReader::NotifyDataArrivedInternal(uint32_t aLength, int64_t aOffset)
|
||||
return;
|
||||
}
|
||||
|
||||
nsRefPtr<MediaByteBuffer> bytes = mDecoder->GetResource()->SilentReadAt(aOffset, aLength);
|
||||
nsRefPtr<MediaByteBuffer> bytes = mDecoder->GetResource()->MediaReadAt(aOffset, aLength);
|
||||
NS_ENSURE_TRUE_VOID(bytes);
|
||||
mMP3FrameParser.Parse(bytes->Elements(), aLength, aOffset);
|
||||
if (!mMP3FrameParser.IsMP3()) {
|
||||
|
@ -76,7 +76,7 @@ public:
|
||||
{}
|
||||
|
||||
int64_t GetLength() {
|
||||
int64_t len = mResource->GetLength();
|
||||
int64_t len = mResource.GetLength();
|
||||
if (len == -1) {
|
||||
return len;
|
||||
}
|
||||
@ -85,19 +85,20 @@ public:
|
||||
nsresult ReadAt(int64_t aOffset, char* aBuffer,
|
||||
uint32_t aCount, uint32_t* aBytes)
|
||||
{
|
||||
return mResource->ReadAt(aOffset + mDataOffset,
|
||||
aBuffer,
|
||||
aCount,
|
||||
aBytes);
|
||||
return mResource.ReadAt(aOffset + mDataOffset,
|
||||
aBuffer,
|
||||
aCount,
|
||||
aBytes);
|
||||
}
|
||||
int64_t GetCachedDataEnd() {
|
||||
int64_t tell = mResource->Tell();
|
||||
int64_t dataEnd = mResource->GetCachedDataEnd(tell) - mDataOffset;
|
||||
int64_t tell = mResource.GetResource()->Tell();
|
||||
int64_t dataEnd =
|
||||
mResource.GetResource()->GetCachedDataEnd(tell) - mDataOffset;
|
||||
return dataEnd;
|
||||
}
|
||||
private:
|
||||
// MediaResource from which we read data.
|
||||
RefPtr<MediaResource> mResource;
|
||||
MediaResourceIndex mResource;
|
||||
int64_t mDataOffset;
|
||||
};
|
||||
|
||||
@ -559,23 +560,13 @@ OutputPin::SyncRead(LONGLONG aPosition,
|
||||
}
|
||||
}
|
||||
|
||||
// Read in a loop to ensure we fill the buffer, when possible.
|
||||
LONG totalBytesRead = 0;
|
||||
while (totalBytesRead < aLength) {
|
||||
BYTE* readBuffer = aBuffer + totalBytesRead;
|
||||
uint32_t bytesRead = 0;
|
||||
LONG length = aLength - totalBytesRead;
|
||||
nsresult rv = mResource.ReadAt(aPosition + totalBytesRead,
|
||||
reinterpret_cast<char*>(readBuffer),
|
||||
length,
|
||||
&bytesRead);
|
||||
if (NS_FAILED(rv)) {
|
||||
return E_FAIL;
|
||||
}
|
||||
totalBytesRead += bytesRead;
|
||||
if (bytesRead == 0) {
|
||||
break;
|
||||
}
|
||||
uint32_t totalBytesRead = 0;
|
||||
nsresult rv = mResource.ReadAt(aPosition,
|
||||
reinterpret_cast<char*>(aBuffer),
|
||||
aLength,
|
||||
&totalBytesRead);
|
||||
if (NS_FAILED(rv)) {
|
||||
return E_FAIL;
|
||||
}
|
||||
if (totalBytesRead > 0) {
|
||||
CriticalSectionAutoEnter lock(*mLock);
|
||||
|
@ -32,22 +32,17 @@ MP4Stream::BlockingReadIntoCache(int64_t aOffset, size_t aCount, Monitor* aToUnl
|
||||
return false;
|
||||
}
|
||||
|
||||
uint32_t sum = 0;
|
||||
uint32_t bytesRead = 0;
|
||||
do {
|
||||
uint64_t offset = aOffset + sum;
|
||||
char* buffer = block.Buffer() + sum;
|
||||
uint32_t toRead = aCount - sum;
|
||||
{
|
||||
MonitorAutoUnlock unlock(*aToUnlock);
|
||||
nsresult rv = mResource->ReadAt(offset, buffer, toRead, &bytesRead);
|
||||
nsresult rv = mResource.ReadAt(aOffset, block.Buffer(), aCount, &bytesRead);
|
||||
if (NS_FAILED(rv)) {
|
||||
return false;
|
||||
}
|
||||
sum += bytesRead;
|
||||
} while (sum < aCount && bytesRead > 0);
|
||||
}
|
||||
|
||||
MOZ_ASSERT(block.mCount >= sum);
|
||||
block.mCount = sum;
|
||||
MOZ_ASSERT(block.mCount >= bytesRead);
|
||||
block.mCount = bytesRead;
|
||||
|
||||
mCache.AppendElement(block);
|
||||
return true;
|
||||
@ -85,8 +80,9 @@ MP4Stream::CachedReadAt(int64_t aOffset, void* aBuffer, size_t aCount,
|
||||
}
|
||||
}
|
||||
|
||||
nsresult rv = mResource->ReadFromCache(reinterpret_cast<char*>(aBuffer),
|
||||
aOffset, aCount);
|
||||
nsresult rv =
|
||||
mResource.GetResource()->ReadFromCache(reinterpret_cast<char*>(aBuffer),
|
||||
aOffset, aCount);
|
||||
if (NS_FAILED(rv)) {
|
||||
*aBytesRead = 0;
|
||||
return false;
|
||||
@ -98,9 +94,9 @@ MP4Stream::CachedReadAt(int64_t aOffset, void* aBuffer, size_t aCount,
|
||||
bool
|
||||
MP4Stream::Length(int64_t* aSize)
|
||||
{
|
||||
if (mResource->GetLength() < 0)
|
||||
if (mResource.GetLength() < 0)
|
||||
return false;
|
||||
*aSize = mResource->GetLength();
|
||||
*aSize = mResource.GetLength();
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -49,13 +49,13 @@ public:
|
||||
|
||||
void Pin()
|
||||
{
|
||||
mResource->Pin();
|
||||
mResource.GetResource()->Pin();
|
||||
++mPinCount;
|
||||
}
|
||||
|
||||
void Unpin()
|
||||
{
|
||||
mResource->Unpin();
|
||||
mResource.GetResource()->Unpin();
|
||||
MOZ_ASSERT(mPinCount);
|
||||
--mPinCount;
|
||||
if (mPinCount == 0) {
|
||||
@ -64,7 +64,7 @@ public:
|
||||
}
|
||||
|
||||
private:
|
||||
nsRefPtr<MediaResource> mResource;
|
||||
MediaResourceIndex mResource;
|
||||
Maybe<ReadRecord> mFailedRead;
|
||||
uint32_t mPinCount;
|
||||
|
||||
|
@ -8,7 +8,6 @@
|
||||
#include "nsMimeTypes.h"
|
||||
#include "MediaDecoderStateMachine.h"
|
||||
#include "AbstractMediaDecoder.h"
|
||||
#include "MediaResource.h"
|
||||
#include "GStreamerReader.h"
|
||||
#if GST_VERSION_MAJOR >= 1
|
||||
#include "GStreamerAllocator.h"
|
||||
@ -88,7 +87,8 @@ GStreamerReader::GStreamerReader(AbstractMediaDecoder* aDecoder)
|
||||
mConfigureAlignment(true),
|
||||
#endif
|
||||
fpsNum(0),
|
||||
fpsDen(0)
|
||||
fpsDen(0),
|
||||
mResource(aDecoder->GetResource())
|
||||
{
|
||||
MOZ_COUNT_CTOR(GStreamerReader);
|
||||
|
||||
@ -281,20 +281,19 @@ void GStreamerReader::PlayBinSourceSetup(GstAppSrc* aSource)
|
||||
{
|
||||
mSource = GST_APP_SRC(aSource);
|
||||
gst_app_src_set_callbacks(mSource, &mSrcCallbacks, (gpointer) this, nullptr);
|
||||
MediaResource* resource = mDecoder->GetResource();
|
||||
|
||||
/* do a short read to trigger a network request so that GetLength() below
|
||||
* returns something meaningful and not -1
|
||||
*/
|
||||
char buf[512];
|
||||
unsigned int size = 0;
|
||||
resource->Read(buf, sizeof(buf), &size);
|
||||
resource->Seek(SEEK_SET, 0);
|
||||
mResource.Read(buf, sizeof(buf), &size);
|
||||
mResource.Seek(SEEK_SET, 0);
|
||||
|
||||
/* now we should have a length */
|
||||
int64_t resourceLength = GetDataLength();
|
||||
gst_app_src_set_size(mSource, resourceLength);
|
||||
if (resource->IsDataCachedToEndOfResource(0) ||
|
||||
if (mResource.GetResource()->IsDataCachedToEndOfResource(0) ||
|
||||
(resourceLength != -1 && resourceLength <= SHORT_FILE_SIZE)) {
|
||||
/* let the demuxer work in pull mode for local files (or very short files)
|
||||
* so that we get optimal seeking accuracy/performance
|
||||
@ -323,15 +322,13 @@ void GStreamerReader::PlayBinSourceSetup(GstAppSrc* aSource)
|
||||
*/
|
||||
nsresult GStreamerReader::ParseMP3Headers()
|
||||
{
|
||||
MediaResource *resource = mDecoder->GetResource();
|
||||
|
||||
const uint32_t MAX_READ_BYTES = 4096;
|
||||
|
||||
uint64_t offset = 0;
|
||||
char bytes[MAX_READ_BYTES];
|
||||
uint32_t bytesRead;
|
||||
do {
|
||||
nsresult rv = resource->ReadAt(offset, bytes, MAX_READ_BYTES, &bytesRead);
|
||||
nsresult rv = mResource.ReadAt(offset, bytes, MAX_READ_BYTES, &bytesRead);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
NS_ENSURE_TRUE(bytesRead, NS_ERROR_FAILURE);
|
||||
|
||||
@ -354,7 +351,7 @@ nsresult GStreamerReader::ParseMP3Headers()
|
||||
int64_t
|
||||
GStreamerReader::GetDataLength()
|
||||
{
|
||||
int64_t streamLen = mDecoder->GetResource()->GetLength();
|
||||
int64_t streamLen = mResource.GetLength();
|
||||
|
||||
if (streamLen < 0) {
|
||||
return streamLen;
|
||||
@ -834,7 +831,7 @@ bool GStreamerReader::DecodeVideoFrame(bool &aKeyFrameSkip,
|
||||
buffer = tmp;
|
||||
}
|
||||
|
||||
int64_t offset = mDecoder->GetResource()->Tell(); // Estimate location in media.
|
||||
int64_t offset = mResource.Tell(); // Estimate location in media.
|
||||
nsRefPtr<VideoData> video = VideoData::CreateFromImage(mInfo.mVideo,
|
||||
mDecoder->GetImageContainer(),
|
||||
offset, timestamp, duration,
|
||||
@ -934,9 +931,7 @@ media::TimeIntervals GStreamerReader::GetBuffered()
|
||||
|
||||
void GStreamerReader::ReadAndPushData(guint aLength)
|
||||
{
|
||||
MediaResource* resource = mDecoder->GetResource();
|
||||
NS_ASSERTION(resource, "Decoder has no media resource");
|
||||
int64_t offset1 = resource->Tell();
|
||||
int64_t offset1 = mResource.Tell();
|
||||
unused << offset1;
|
||||
nsresult rv = NS_OK;
|
||||
|
||||
@ -950,15 +945,15 @@ void GStreamerReader::ReadAndPushData(guint aLength)
|
||||
#endif
|
||||
uint32_t size = 0, bytesRead = 0;
|
||||
while(bytesRead < aLength) {
|
||||
rv = resource->Read(reinterpret_cast<char*>(data + bytesRead),
|
||||
aLength - bytesRead, &size);
|
||||
rv = mResource.Read(reinterpret_cast<char*>(data + bytesRead),
|
||||
aLength - bytesRead, &size);
|
||||
if (NS_FAILED(rv) || size == 0)
|
||||
break;
|
||||
|
||||
bytesRead += size;
|
||||
}
|
||||
|
||||
int64_t offset2 = resource->Tell();
|
||||
int64_t offset2 = mResource.Tell();
|
||||
unused << offset2;
|
||||
|
||||
#if GST_VERSION_MAJOR >= 1
|
||||
@ -1032,8 +1027,7 @@ gboolean GStreamerReader::SeekData(GstAppSrc* aSrc, guint64 aOffset)
|
||||
aOffset += mDataOffset;
|
||||
|
||||
ReentrantMonitorAutoEnter mon(mGstThreadsMonitor);
|
||||
MediaResource* resource = mDecoder->GetResource();
|
||||
int64_t resourceLength = resource->GetLength();
|
||||
int64_t resourceLength = mResource.GetLength();
|
||||
|
||||
if (gst_app_src_get_size(mSource) == -1) {
|
||||
/* It's possible that we didn't know the length when we initialized mSource
|
||||
@ -1044,13 +1038,7 @@ gboolean GStreamerReader::SeekData(GstAppSrc* aSrc, guint64 aOffset)
|
||||
|
||||
nsresult rv = NS_ERROR_FAILURE;
|
||||
if (aOffset < static_cast<guint64>(resourceLength)) {
|
||||
rv = resource->Seek(SEEK_SET, aOffset);
|
||||
}
|
||||
|
||||
if (NS_FAILED(rv)) {
|
||||
LOG(LogLevel::Error, "seek at %lu failed", aOffset);
|
||||
} else {
|
||||
MOZ_ASSERT(aOffset == static_cast<guint64>(resource->Tell()));
|
||||
rv = mResource.Seek(SEEK_SET, aOffset);
|
||||
}
|
||||
|
||||
return NS_SUCCEEDED(rv);
|
||||
@ -1284,7 +1272,8 @@ void GStreamerReader::NotifyDataArrivedInternal(uint32_t aLength,
|
||||
return;
|
||||
}
|
||||
|
||||
nsRefPtr<MediaByteBuffer> bytes = mDecoder->GetResource()->SilentReadAt(aOffset, aLength);
|
||||
nsRefPtr<MediaByteBuffer> bytes =
|
||||
mResource.MediaReadAt(aOffset, aLength);
|
||||
NS_ENSURE_TRUE_VOID(bytes);
|
||||
mMP3FrameParser.Parse(bytes->Elements(), aLength, aOffset);
|
||||
if (!mMP3FrameParser.IsMP3()) {
|
||||
|
@ -21,6 +21,7 @@
|
||||
#pragma GCC diagnostic pop
|
||||
|
||||
#include "MediaDecoderReader.h"
|
||||
#include "MediaResource.h"
|
||||
#include "MP3FrameParser.h"
|
||||
#include "ImageContainer.h"
|
||||
#include "nsRect.h"
|
||||
@ -262,6 +263,8 @@ private:
|
||||
#endif
|
||||
int fpsNum;
|
||||
int fpsDen;
|
||||
|
||||
MediaResourceIndex mResource;
|
||||
};
|
||||
|
||||
} // namespace mozilla
|
||||
|
@ -32,17 +32,8 @@ public:
|
||||
}
|
||||
virtual void SetReadMode(MediaCacheStream::ReadMode aMode) override {}
|
||||
virtual void SetPlaybackRate(uint32_t aBytesPerSecond) override {}
|
||||
virtual nsresult Read(char* aBuffer, uint32_t aCount, uint32_t* aBytes)
|
||||
override
|
||||
{
|
||||
return NS_OK;
|
||||
}
|
||||
virtual nsresult ReadAt(int64_t aOffset, char* aBuffer, uint32_t aCount,
|
||||
uint32_t* aBytes) override;
|
||||
virtual nsresult Seek(int32_t aWhence, int64_t aOffset) override
|
||||
{
|
||||
return NS_OK;
|
||||
}
|
||||
virtual int64_t Tell() override { return 0; }
|
||||
virtual void Pin() override {}
|
||||
virtual void Unpin() override {}
|
||||
|
@ -35,9 +35,7 @@ public:
|
||||
virtual already_AddRefed<MediaResource> CloneData(MediaDecoder* aDecoder) override { UNIMPLEMENTED(); return nullptr; }
|
||||
virtual void SetReadMode(MediaCacheStream::ReadMode aMode) override { UNIMPLEMENTED(); }
|
||||
virtual void SetPlaybackRate(uint32_t aBytesPerSecond) override { UNIMPLEMENTED(); }
|
||||
virtual nsresult Read(char* aBuffer, uint32_t aCount, uint32_t* aBytes) override { UNIMPLEMENTED(); return NS_ERROR_FAILURE; }
|
||||
virtual nsresult ReadAt(int64_t aOffset, char* aBuffer, uint32_t aCount, uint32_t* aBytes) override { UNIMPLEMENTED(); return NS_ERROR_FAILURE; }
|
||||
virtual nsresult Seek(int32_t aWhence, int64_t aOffset) override { UNIMPLEMENTED(); return NS_ERROR_FAILURE; }
|
||||
virtual int64_t Tell() override { UNIMPLEMENTED(); return -1; }
|
||||
virtual void Pin() override { UNIMPLEMENTED(); }
|
||||
virtual void Unpin() override { UNIMPLEMENTED(); }
|
||||
|
@ -38,16 +38,6 @@ SourceBufferResource::Close()
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsresult
|
||||
SourceBufferResource::Read(char* aBuffer, uint32_t aCount, uint32_t* aBytes)
|
||||
{
|
||||
SBR_DEBUGV("Read(aBuffer=%p, aCount=%u, aBytes=%p)",
|
||||
aBuffer, aCount, aBytes);
|
||||
ReentrantMonitorAutoEnter mon(mMonitor);
|
||||
|
||||
return ReadInternal(aBuffer, aCount, aBytes, /* aMayBlock = */ true);
|
||||
}
|
||||
|
||||
nsresult
|
||||
SourceBufferResource::ReadInternal(char* aBuffer, uint32_t aCount, uint32_t* aBytes, bool aMayBlock)
|
||||
{
|
||||
@ -114,33 +104,6 @@ SourceBufferResource::ReadAtInternal(int64_t aOffset, char* aBuffer, uint32_t aC
|
||||
return ReadInternal(aBuffer, aCount, aBytes, aMayBlock);
|
||||
}
|
||||
|
||||
nsresult
|
||||
SourceBufferResource::Seek(int32_t aWhence, int64_t aOffset)
|
||||
{
|
||||
SBR_DEBUG("Seek(aWhence=%d, aOffset=%lld)",
|
||||
aWhence, aOffset);
|
||||
ReentrantMonitorAutoEnter mon(mMonitor);
|
||||
|
||||
int64_t newOffset = mOffset;
|
||||
switch (aWhence) {
|
||||
case nsISeekableStream::NS_SEEK_END:
|
||||
newOffset = GetLength() - aOffset;
|
||||
break;
|
||||
case nsISeekableStream::NS_SEEK_CUR:
|
||||
newOffset += aOffset;
|
||||
break;
|
||||
case nsISeekableStream::NS_SEEK_SET:
|
||||
newOffset = aOffset;
|
||||
break;
|
||||
}
|
||||
|
||||
SBR_DEBUGV("newOffset=%lld GetOffset()=%llu GetLength()=%llu)",
|
||||
newOffset, mInputBuffer.GetOffset(), GetLength());
|
||||
nsresult rv = SeekInternal(newOffset);
|
||||
mon.NotifyAll();
|
||||
return rv;
|
||||
}
|
||||
|
||||
nsresult
|
||||
SourceBufferResource::SeekInternal(int64_t aOffset)
|
||||
{
|
||||
|
@ -46,9 +46,7 @@ public:
|
||||
virtual already_AddRefed<MediaResource> CloneData(MediaDecoder* aDecoder) override { UNIMPLEMENTED(); return nullptr; }
|
||||
virtual void SetReadMode(MediaCacheStream::ReadMode aMode) override { UNIMPLEMENTED(); }
|
||||
virtual void SetPlaybackRate(uint32_t aBytesPerSecond) override { UNIMPLEMENTED(); }
|
||||
virtual nsresult Read(char* aBuffer, uint32_t aCount, uint32_t* aBytes) override;
|
||||
virtual nsresult ReadAt(int64_t aOffset, char* aBuffer, uint32_t aCount, uint32_t* aBytes) override;
|
||||
virtual nsresult Seek(int32_t aWhence, int64_t aOffset) override;
|
||||
virtual int64_t Tell() override { return mOffset; }
|
||||
virtual void Pin() override { UNIMPLEMENTED(); }
|
||||
virtual void Unpin() override { UNIMPLEMENTED(); }
|
||||
|
@ -65,7 +65,7 @@ enum PageSyncResult {
|
||||
|
||||
// Reads a page from the media resource.
|
||||
static PageSyncResult
|
||||
PageSync(MediaResource* aResource,
|
||||
PageSync(MediaResourceIndex* aResource,
|
||||
ogg_sync_state* aState,
|
||||
bool aCachedDataOnly,
|
||||
int64_t aOffset,
|
||||
@ -139,7 +139,8 @@ OggReader::OggReader(AbstractMediaDecoder* aDecoder)
|
||||
mTheoraSerial(0),
|
||||
mOpusPreSkip(0),
|
||||
mIsChained(false),
|
||||
mDecodedAudioFrames(0)
|
||||
mDecodedAudioFrames(0),
|
||||
mResource(aDecoder->GetResource())
|
||||
{
|
||||
MOZ_COUNT_CTOR(OggReader);
|
||||
memset(&mTheoraInfo, 0, sizeof(mTheoraInfo));
|
||||
@ -471,13 +472,12 @@ nsresult OggReader::ReadMetadata(MediaInfo* aInfo,
|
||||
if (HasAudio() || HasVideo()) {
|
||||
ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
|
||||
|
||||
MediaResource* resource = mDecoder->GetResource();
|
||||
if (mInfo.mMetadataDuration.isNothing() && !mDecoder->IsOggDecoderShutdown() &&
|
||||
resource->GetLength() >= 0 && mDecoder->IsMediaSeekable())
|
||||
mResource.GetLength() >= 0 && mDecoder->IsMediaSeekable())
|
||||
{
|
||||
// We didn't get a duration from the index or a Content-Duration header.
|
||||
// Seek to the end of file to find the end time.
|
||||
int64_t length = resource->GetLength();
|
||||
int64_t length = mResource.GetLength();
|
||||
|
||||
NS_ASSERTION(length > 0, "Must have a content length to get end time");
|
||||
|
||||
@ -541,7 +541,7 @@ nsresult OggReader::DecodeVorbis(ogg_packet* aPacket) {
|
||||
|
||||
int64_t duration = mVorbisState->Time((int64_t)frames);
|
||||
int64_t startTime = mVorbisState->Time(endFrame - frames);
|
||||
mAudioQueue.Push(new AudioData(mDecoder->GetResource()->Tell(),
|
||||
mAudioQueue.Push(new AudioData(mResource.Tell(),
|
||||
startTime,
|
||||
duration,
|
||||
frames,
|
||||
@ -658,7 +658,7 @@ nsresult OggReader::DecodeOpus(ogg_packet* aPacket) {
|
||||
LOG(LogLevel::Debug, ("Opus decoder pushing %d frames", frames));
|
||||
int64_t startTime = mOpusState->Time(startFrame);
|
||||
int64_t endTime = mOpusState->Time(endFrame);
|
||||
mAudioQueue.Push(new AudioData(mDecoder->GetResource()->Tell(),
|
||||
mAudioQueue.Push(new AudioData(mResource.Tell(),
|
||||
startTime,
|
||||
endTime - startTime,
|
||||
frames,
|
||||
@ -866,7 +866,7 @@ nsresult OggReader::DecodeTheora(ogg_packet* aPacket, int64_t aTimeThreshold)
|
||||
|
||||
nsRefPtr<VideoData> v = VideoData::Create(mInfo.mVideo,
|
||||
mDecoder->GetImageContainer(),
|
||||
mDecoder->GetResource()->Tell(),
|
||||
mResource.Tell(),
|
||||
time,
|
||||
endTime - time,
|
||||
b,
|
||||
@ -949,9 +949,9 @@ bool OggReader::ReadOggPage(ogg_page* aPage)
|
||||
// Read from the resource into the buffer
|
||||
uint32_t bytesRead = 0;
|
||||
|
||||
nsresult rv = mDecoder->GetResource()->Read(buffer, 4096, &bytesRead);
|
||||
if (NS_FAILED(rv) || (bytesRead == 0 && ret == 0)) {
|
||||
// End of file.
|
||||
nsresult rv = mResource.Read(buffer, 4096, &bytesRead);
|
||||
if (NS_FAILED(rv) || !bytesRead) {
|
||||
// End of file or error.
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -1010,9 +1010,7 @@ GetChecksum(ogg_page* page)
|
||||
int64_t OggReader::RangeStartTime(int64_t aOffset)
|
||||
{
|
||||
MOZ_ASSERT(OnTaskQueue());
|
||||
MediaResource* resource = mDecoder->GetResource();
|
||||
NS_ENSURE_TRUE(resource != nullptr, 0);
|
||||
nsresult res = resource->Seek(nsISeekableStream::NS_SEEK_SET, aOffset);
|
||||
nsresult res = mResource.Seek(nsISeekableStream::NS_SEEK_SET, aOffset);
|
||||
NS_ENSURE_SUCCESS(res, 0);
|
||||
int64_t startTime = 0;
|
||||
FindStartTime(startTime);
|
||||
@ -1033,11 +1031,9 @@ int64_t OggReader::RangeEndTime(int64_t aEndOffset)
|
||||
{
|
||||
MOZ_ASSERT(OnTaskQueue() || mDecoder->OnStateMachineTaskQueue());
|
||||
|
||||
MediaResource* resource = mDecoder->GetResource();
|
||||
NS_ENSURE_TRUE(resource != nullptr, -1);
|
||||
int64_t position = resource->Tell();
|
||||
int64_t position = mResource.Tell();
|
||||
int64_t endTime = RangeEndTime(0, aEndOffset, false);
|
||||
nsresult res = resource->Seek(nsISeekableStream::NS_SEEK_SET, position);
|
||||
nsresult res = mResource.Seek(nsISeekableStream::NS_SEEK_SET, position);
|
||||
NS_ENSURE_SUCCESS(res, -1);
|
||||
return endTime;
|
||||
}
|
||||
@ -1046,7 +1042,6 @@ int64_t OggReader::RangeEndTime(int64_t aStartOffset,
|
||||
int64_t aEndOffset,
|
||||
bool aCachedDataOnly)
|
||||
{
|
||||
MediaResource* resource = mDecoder->GetResource();
|
||||
nsAutoOggSyncState sync;
|
||||
|
||||
// We need to find the last page which ends before aEndOffset that
|
||||
@ -1099,15 +1094,15 @@ int64_t OggReader::RangeEndTime(int64_t aStartOffset,
|
||||
NS_ASSERTION(buffer, "Must have buffer");
|
||||
nsresult res;
|
||||
if (aCachedDataOnly) {
|
||||
res = resource->ReadFromCache(buffer, readHead, bytesToRead);
|
||||
res = mResource.GetResource()->ReadFromCache(buffer, readHead, bytesToRead);
|
||||
NS_ENSURE_SUCCESS(res, -1);
|
||||
bytesRead = bytesToRead;
|
||||
} else {
|
||||
NS_ASSERTION(readHead < aEndOffset,
|
||||
"resource pos must be before range end");
|
||||
res = resource->Seek(nsISeekableStream::NS_SEEK_SET, readHead);
|
||||
res = mResource.Seek(nsISeekableStream::NS_SEEK_SET, readHead);
|
||||
NS_ENSURE_SUCCESS(res, -1);
|
||||
res = resource->Read(buffer, bytesToRead, &bytesRead);
|
||||
res = mResource.Read(buffer, bytesToRead, &bytesRead);
|
||||
NS_ENSURE_SUCCESS(res, -1);
|
||||
}
|
||||
readHead += bytesRead;
|
||||
@ -1213,7 +1208,7 @@ OggReader::SelectSeekRange(const nsTArray<SeekRange>& ranges,
|
||||
{
|
||||
MOZ_ASSERT(OnTaskQueue());
|
||||
int64_t so = 0;
|
||||
int64_t eo = mDecoder->GetResource()->GetLength();
|
||||
int64_t eo = mResource.GetLength();
|
||||
int64_t st = aStartTime;
|
||||
int64_t et = aEndTime;
|
||||
for (uint32_t i = 0; i < ranges.Length(); i++) {
|
||||
@ -1243,17 +1238,13 @@ OggReader::IndexedSeekResult OggReader::RollbackIndexedSeek(int64_t aOffset)
|
||||
if (mSkeletonState) {
|
||||
mSkeletonState->Deactivate();
|
||||
}
|
||||
MediaResource* resource = mDecoder->GetResource();
|
||||
NS_ENSURE_TRUE(resource != nullptr, SEEK_FATAL_ERROR);
|
||||
nsresult res = resource->Seek(nsISeekableStream::NS_SEEK_SET, aOffset);
|
||||
nsresult res = mResource.Seek(nsISeekableStream::NS_SEEK_SET, aOffset);
|
||||
NS_ENSURE_SUCCESS(res, SEEK_FATAL_ERROR);
|
||||
return SEEK_INDEX_FAIL;
|
||||
}
|
||||
|
||||
OggReader::IndexedSeekResult OggReader::SeekToKeyframeUsingIndex(int64_t aTarget)
|
||||
{
|
||||
MediaResource* resource = mDecoder->GetResource();
|
||||
NS_ENSURE_TRUE(resource != nullptr, SEEK_FATAL_ERROR);
|
||||
if (!HasSkeleton() || !mSkeletonState->HasIndex()) {
|
||||
return SEEK_INDEX_FAIL;
|
||||
}
|
||||
@ -1270,10 +1261,10 @@ OggReader::IndexedSeekResult OggReader::SeekToKeyframeUsingIndex(int64_t aTarget
|
||||
}
|
||||
|
||||
// Remember original resource read cursor position so we can rollback on failure.
|
||||
int64_t tell = resource->Tell();
|
||||
int64_t tell = mResource.Tell();
|
||||
|
||||
// Seek to the keypoint returned by the index.
|
||||
if (keyframe.mKeyPoint.mOffset > resource->GetLength() ||
|
||||
if (keyframe.mKeyPoint.mOffset > mResource.GetLength() ||
|
||||
keyframe.mKeyPoint.mOffset < 0)
|
||||
{
|
||||
// Index must be invalid.
|
||||
@ -1281,8 +1272,8 @@ OggReader::IndexedSeekResult OggReader::SeekToKeyframeUsingIndex(int64_t aTarget
|
||||
}
|
||||
LOG(LogLevel::Debug, ("Seeking using index to keyframe at offset %lld\n",
|
||||
keyframe.mKeyPoint.mOffset));
|
||||
nsresult res = resource->Seek(nsISeekableStream::NS_SEEK_SET,
|
||||
keyframe.mKeyPoint.mOffset);
|
||||
nsresult res = mResource.Seek(nsISeekableStream::NS_SEEK_SET,
|
||||
keyframe.mKeyPoint.mOffset);
|
||||
NS_ENSURE_SUCCESS(res, SEEK_FATAL_ERROR);
|
||||
|
||||
// We've moved the read set, so reset decode.
|
||||
@ -1293,11 +1284,11 @@ OggReader::IndexedSeekResult OggReader::SeekToKeyframeUsingIndex(int64_t aTarget
|
||||
// here. If not, the index is invalid.
|
||||
ogg_page page;
|
||||
int skippedBytes = 0;
|
||||
PageSyncResult syncres = PageSync(resource,
|
||||
PageSyncResult syncres = PageSync(&mResource,
|
||||
&mOggState,
|
||||
false,
|
||||
keyframe.mKeyPoint.mOffset,
|
||||
resource->GetLength(),
|
||||
mResource.GetLength(),
|
||||
&page,
|
||||
skippedBytes);
|
||||
NS_ENSURE_TRUE(syncres != PAGE_SYNC_ERROR, SEEK_FATAL_ERROR);
|
||||
@ -1430,8 +1421,6 @@ nsresult OggReader::SeekInternal(int64_t aTarget, int64_t aEndTime)
|
||||
return NS_ERROR_FAILURE;
|
||||
LOG(LogLevel::Debug, ("%p About to seek to %lld", mDecoder, aTarget));
|
||||
nsresult res;
|
||||
MediaResource* resource = mDecoder->GetResource();
|
||||
NS_ENSURE_TRUE(resource != nullptr, NS_ERROR_FAILURE);
|
||||
int64_t adjustedTarget = aTarget;
|
||||
if (HasAudio() && mOpusState){
|
||||
adjustedTarget = std::max(StartTime(), aTarget - SEEK_OPUS_PREROLL);
|
||||
@ -1440,7 +1429,7 @@ nsresult OggReader::SeekInternal(int64_t aTarget, int64_t aEndTime)
|
||||
if (adjustedTarget == StartTime()) {
|
||||
// We've seeked to the media start. Just seek to the offset of the first
|
||||
// content page.
|
||||
res = resource->Seek(nsISeekableStream::NS_SEEK_SET, 0);
|
||||
res = mResource.Seek(nsISeekableStream::NS_SEEK_SET, 0);
|
||||
NS_ENSURE_SUCCESS(res,res);
|
||||
|
||||
res = ResetDecode(true);
|
||||
@ -1513,7 +1502,7 @@ nsresult OggReader::SeekInternal(int64_t aTarget, int64_t aEndTime)
|
||||
|
||||
// Reads a page from the media resource.
|
||||
static PageSyncResult
|
||||
PageSync(MediaResource* aResource,
|
||||
PageSync(MediaResourceIndex* aResource,
|
||||
ogg_sync_state* aState,
|
||||
bool aCachedDataOnly,
|
||||
int64_t aOffset,
|
||||
@ -1541,8 +1530,8 @@ PageSync(MediaResource* aResource,
|
||||
}
|
||||
nsresult rv = NS_OK;
|
||||
if (aCachedDataOnly) {
|
||||
rv = aResource->ReadFromCache(buffer, readHead,
|
||||
static_cast<uint32_t>(bytesToRead));
|
||||
rv = aResource->GetResource()->ReadFromCache(buffer, readHead,
|
||||
static_cast<uint32_t>(bytesToRead));
|
||||
NS_ENSURE_SUCCESS(rv,PAGE_SYNC_ERROR);
|
||||
bytesRead = static_cast<uint32_t>(bytesToRead);
|
||||
} else {
|
||||
@ -1583,13 +1572,12 @@ nsresult OggReader::SeekBisection(int64_t aTarget,
|
||||
{
|
||||
MOZ_ASSERT(OnTaskQueue());
|
||||
nsresult res;
|
||||
MediaResource* resource = mDecoder->GetResource();
|
||||
|
||||
if (aTarget == aRange.mTimeStart) {
|
||||
if (NS_FAILED(ResetDecode())) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
res = resource->Seek(nsISeekableStream::NS_SEEK_SET, 0);
|
||||
res = mResource.Seek(nsISeekableStream::NS_SEEK_SET, 0);
|
||||
NS_ENSURE_SUCCESS(res,res);
|
||||
return NS_OK;
|
||||
}
|
||||
@ -1695,7 +1683,7 @@ nsresult OggReader::SeekBisection(int64_t aTarget,
|
||||
// Locate the next page after our seek guess, and then figure out the
|
||||
// granule time of the audio and video bitstreams there. We can then
|
||||
// make a bisection decision based on our location in the media.
|
||||
PageSyncResult res = PageSync(resource,
|
||||
PageSyncResult res = PageSync(&mResource,
|
||||
&mOggState,
|
||||
false,
|
||||
guess,
|
||||
@ -1794,7 +1782,7 @@ nsresult OggReader::SeekBisection(int64_t aTarget,
|
||||
// last page before the target, and the first page after the target.
|
||||
SEEK_LOG(LogLevel::Debug, ("Terminating seek at offset=%lld", startOffset));
|
||||
NS_ASSERTION(startTime < aTarget, "Start time must always be less than target");
|
||||
res = resource->Seek(nsISeekableStream::NS_SEEK_SET, startOffset);
|
||||
res = mResource.Seek(nsISeekableStream::NS_SEEK_SET, startOffset);
|
||||
NS_ENSURE_SUCCESS(res,res);
|
||||
if (NS_FAILED(ResetDecode())) {
|
||||
return NS_ERROR_FAILURE;
|
||||
@ -1805,7 +1793,7 @@ nsresult OggReader::SeekBisection(int64_t aTarget,
|
||||
SEEK_LOG(LogLevel::Debug, ("Time at offset %lld is %lld", guess, granuleTime));
|
||||
if (granuleTime < seekTarget && granuleTime > seekLowerBound) {
|
||||
// We're within the fuzzy region in which we want to terminate the search.
|
||||
res = resource->Seek(nsISeekableStream::NS_SEEK_SET, pageOffset);
|
||||
res = mResource.Seek(nsISeekableStream::NS_SEEK_SET, pageOffset);
|
||||
NS_ENSURE_SUCCESS(res,res);
|
||||
if (NS_FAILED(ResetDecode())) {
|
||||
return NS_ERROR_FAILURE;
|
||||
@ -1889,7 +1877,7 @@ media::TimeIntervals OggReader::GetBuffered()
|
||||
while (startTime == -1) {
|
||||
ogg_page page;
|
||||
int32_t discard;
|
||||
PageSyncResult res = PageSync(resource,
|
||||
PageSyncResult res = PageSync(&mResource,
|
||||
&sync.mState,
|
||||
true,
|
||||
startOffset,
|
||||
|
@ -14,6 +14,7 @@
|
||||
#include <vorbis/codec.h>
|
||||
#endif
|
||||
#include "MediaDecoderReader.h"
|
||||
#include "MediaResource.h"
|
||||
#include "OggCodecState.h"
|
||||
#include "VideoUtils.h"
|
||||
#include "mozilla/Monitor.h"
|
||||
@ -316,6 +317,8 @@ private:
|
||||
|
||||
// Number of audio frames decoded so far.
|
||||
int64_t mDecodedAudioFrames;
|
||||
|
||||
MediaResourceIndex mResource;
|
||||
};
|
||||
|
||||
} // namespace mozilla
|
||||
|
@ -416,8 +416,9 @@ MediaCodecReader::DecodeAudioDataSync()
|
||||
AudioQueue().Finish();
|
||||
} else if (bufferInfo.mBuffer != nullptr && bufferInfo.mSize > 0 &&
|
||||
bufferInfo.mBuffer->data() != nullptr) {
|
||||
MOZ_ASSERT(mStreamSource);
|
||||
// This is the approximate byte position in the stream.
|
||||
int64_t pos = mDecoder->GetResource()->Tell();
|
||||
int64_t pos = mStreamSource->Tell();
|
||||
|
||||
uint32_t frames = bufferInfo.mSize /
|
||||
(mInfo.mAudio.mChannels * sizeof(AudioDataValue));
|
||||
@ -526,7 +527,8 @@ void
|
||||
MediaCodecReader::NotifyDataArrivedInternal(uint32_t aLength,
|
||||
int64_t aOffset)
|
||||
{
|
||||
nsRefPtr<MediaByteBuffer> bytes = mDecoder->GetResource()->SilentReadAt(aOffset, aLength);
|
||||
nsRefPtr<MediaByteBuffer> bytes =
|
||||
mDecoder->GetResource()->MediaReadAt(aOffset, aLength);
|
||||
NS_ENSURE_TRUE_VOID(bytes);
|
||||
|
||||
MonitorAutoLock monLock(mParserMonitor);
|
||||
@ -937,8 +939,9 @@ MediaCodecReader::DecodeVideoFrameSync(int64_t aTimeThreshold)
|
||||
RefPtr<TextureClient> textureClient;
|
||||
sp<GraphicBuffer> graphicBuffer;
|
||||
if (bufferInfo.mBuffer != nullptr) {
|
||||
MOZ_ASSERT(mStreamSource);
|
||||
// This is the approximate byte position in the stream.
|
||||
int64_t pos = mDecoder->GetResource()->Tell();
|
||||
int64_t pos = mStreamSource->Tell();
|
||||
|
||||
if (mVideoTrack.mNativeWindow != nullptr &&
|
||||
mVideoTrack.mCodec->getOutputGraphicBufferFromIndex(bufferInfo.mIndex, &graphicBuffer) == OK &&
|
||||
@ -1196,7 +1199,7 @@ MediaCodecReader::CreateExtractor()
|
||||
if (dataSource->initCheck() != OK) {
|
||||
return false;
|
||||
}
|
||||
|
||||
mStreamSource = static_cast<MediaStreamSource*>(dataSource.get());
|
||||
mExtractor = MediaExtractor::Create(dataSource);
|
||||
}
|
||||
|
||||
|
@ -27,6 +27,7 @@ extern PRLogModuleInfo* gMediaDecoderLog;
|
||||
|
||||
MediaOmxCommonReader::MediaOmxCommonReader(AbstractMediaDecoder *aDecoder)
|
||||
: MediaDecoderReader(aDecoder)
|
||||
, mStreamSource(nullptr)
|
||||
{
|
||||
if (!gMediaDecoderLog) {
|
||||
gMediaDecoderLog = PR_NewLogModule("MediaDecoder");
|
||||
|
@ -15,6 +15,7 @@
|
||||
|
||||
namespace android {
|
||||
struct MOZ_EXPORT MediaSource;
|
||||
class MediaStreamSource;
|
||||
} // namespace android
|
||||
|
||||
namespace mozilla {
|
||||
@ -43,6 +44,9 @@ public:
|
||||
|
||||
protected:
|
||||
dom::AudioChannel mAudioChannel;
|
||||
// Weak reference to the MediaStreamSource that will be created by either
|
||||
// MediaOmxReader or MediaCodecReader.
|
||||
android::MediaStreamSource* mStreamSource;
|
||||
};
|
||||
|
||||
} // namespace mozilla
|
||||
|
@ -213,6 +213,7 @@ nsresult MediaOmxReader::InitOmxDecoder()
|
||||
if (!mOmxDecoder->Init(mExtractor)) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
mStreamSource = static_cast<MediaStreamSource*>(dataSource.get());
|
||||
}
|
||||
return NS_OK;
|
||||
}
|
||||
@ -392,8 +393,9 @@ bool MediaOmxReader::DecodeVideoFrame(bool &aKeyframeSkip,
|
||||
picture.height = (frame.Y.mHeight * mPicture.height) / mInitialFrame.height;
|
||||
}
|
||||
|
||||
MOZ_ASSERT(mStreamSource);
|
||||
// This is the approximate byte position in the stream.
|
||||
int64_t pos = mDecoder->GetResource()->Tell();
|
||||
int64_t pos = mStreamSource->Tell();
|
||||
|
||||
nsRefPtr<VideoData> v;
|
||||
if (!frame.mGraphicBuffer) {
|
||||
@ -471,7 +473,8 @@ void MediaOmxReader::NotifyDataArrivedInternal(uint32_t aLength, int64_t aOffset
|
||||
return;
|
||||
}
|
||||
|
||||
nsRefPtr<MediaByteBuffer> bytes = mDecoder->GetResource()->SilentReadAt(aOffset, aLength);
|
||||
nsRefPtr<MediaByteBuffer> bytes =
|
||||
mDecoder->GetResource()->MediaReadAt(aOffset, aLength);
|
||||
NS_ENSURE_TRUE_VOID(bytes);
|
||||
mMP3FrameParser.Parse(bytes->Elements(), aLength, aOffset);
|
||||
if (!mMP3FrameParser.IsMP3()) {
|
||||
@ -490,8 +493,9 @@ bool MediaOmxReader::DecodeAudioData()
|
||||
MOZ_ASSERT(OnTaskQueue());
|
||||
EnsureActive();
|
||||
|
||||
MOZ_ASSERT(mStreamSource);
|
||||
// This is the approximate byte position in the stream.
|
||||
int64_t pos = mDecoder->GetResource()->Tell();
|
||||
int64_t pos = mStreamSource->Tell();
|
||||
|
||||
// Read next frame
|
||||
MPAPI::AudioFrame source;
|
||||
|
@ -33,9 +33,9 @@ ssize_t MediaStreamSource::readAt(off64_t offset, void *data, size_t size)
|
||||
while (todo > 0) {
|
||||
Mutex::Autolock autoLock(mLock);
|
||||
uint32_t bytesRead;
|
||||
if ((offset != mResource->Tell() &&
|
||||
NS_FAILED(mResource->Seek(nsISeekableStream::NS_SEEK_SET, offset))) ||
|
||||
NS_FAILED(mResource->Read(ptr, todo, &bytesRead))) {
|
||||
if ((offset != mResource.Tell() &&
|
||||
NS_FAILED(mResource.Seek(nsISeekableStream::NS_SEEK_SET, offset))) ||
|
||||
NS_FAILED(mResource.Read(ptr, todo, &bytesRead))) {
|
||||
return ERROR_IO;
|
||||
}
|
||||
|
||||
@ -52,7 +52,7 @@ ssize_t MediaStreamSource::readAt(off64_t offset, void *data, size_t size)
|
||||
|
||||
status_t MediaStreamSource::getSize(off64_t *size)
|
||||
{
|
||||
uint64_t length = mResource->GetLength();
|
||||
uint64_t length = mResource.GetLength();
|
||||
if (length == static_cast<uint64_t>(-1))
|
||||
return ERROR_UNSUPPORTED;
|
||||
|
||||
@ -61,4 +61,11 @@ status_t MediaStreamSource::getSize(off64_t *size)
|
||||
return OK;
|
||||
}
|
||||
|
||||
int64_t
|
||||
MediaStreamSource::Tell()
|
||||
{
|
||||
Mutex::Autolock autoLock(mLock);
|
||||
return mResource.Tell();
|
||||
}
|
||||
|
||||
} // namespace android
|
||||
|
@ -20,9 +20,10 @@ namespace android {
|
||||
// MediaStreamSource is a DataSource that reads from a MPAPI media stream.
|
||||
class MediaStreamSource : public DataSource {
|
||||
typedef mozilla::MediaResource MediaResource;
|
||||
typedef mozilla::MediaResourceIndex MediaResourceIndex;
|
||||
|
||||
Mutex mLock;
|
||||
nsRefPtr<MediaResource> mResource;
|
||||
MediaResourceIndex mResource;
|
||||
public:
|
||||
MediaStreamSource(MediaResource* aResource);
|
||||
|
||||
@ -42,6 +43,8 @@ public:
|
||||
return kWantsPrefetching;
|
||||
}
|
||||
|
||||
int64_t Tell();
|
||||
|
||||
virtual ~MediaStreamSource();
|
||||
|
||||
private:
|
||||
|
@ -206,7 +206,7 @@ public:
|
||||
AudioDataDecoder(const AudioInfo& aConfig, MediaFormat::Param aFormat, MediaDataDecoderCallback* aCallback)
|
||||
: MediaCodecDataDecoder(MediaData::Type::AUDIO_DATA, aConfig.mMimeType, aFormat, aCallback)
|
||||
{
|
||||
JNIEnv* env = GetJNIForThread();
|
||||
JNIEnv* const env = jni::GetEnvForThread();
|
||||
|
||||
jni::Object::LocalRef buffer(env);
|
||||
NS_ENSURE_SUCCESS_VOID(aFormat->GetByteBuffer(NS_LITERAL_STRING("csd-0"), &buffer));
|
||||
@ -427,7 +427,7 @@ void MediaCodecDataDecoder::DecoderLoop()
|
||||
bool draining = false;
|
||||
bool waitingEOF = false;
|
||||
|
||||
AutoLocalJNIFrame frame(GetJNIForThread(), 1);
|
||||
AutoLocalJNIFrame frame(jni::GetEnvForThread(), 1);
|
||||
nsRefPtr<MediaRawData> sample;
|
||||
|
||||
MediaFormat::LocalRef outputFormat(frame.GetEnv());
|
||||
|
@ -16,7 +16,7 @@ using namespace mozilla::media;
|
||||
|
||||
RawReader::RawReader(AbstractMediaDecoder* aDecoder)
|
||||
: MediaDecoderReader(aDecoder),
|
||||
mCurrentFrame(0), mFrameSize(0)
|
||||
mCurrentFrame(0), mFrameSize(0), mResource(aDecoder->GetResource())
|
||||
{
|
||||
MOZ_COUNT_CTOR(RawReader);
|
||||
}
|
||||
@ -42,10 +42,7 @@ nsresult RawReader::ReadMetadata(MediaInfo* aInfo,
|
||||
{
|
||||
MOZ_ASSERT(OnTaskQueue());
|
||||
|
||||
MediaResource* resource = mDecoder->GetResource();
|
||||
NS_ASSERTION(resource, "Decoder has no media resource");
|
||||
|
||||
if (!ReadFromResource(resource, reinterpret_cast<uint8_t*>(&mMetadata),
|
||||
if (!ReadFromResource(reinterpret_cast<uint8_t*>(&mMetadata),
|
||||
sizeof(mMetadata)))
|
||||
return NS_ERROR_FAILURE;
|
||||
|
||||
@ -96,7 +93,7 @@ nsresult RawReader::ReadMetadata(MediaInfo* aInfo,
|
||||
(mMetadata.lumaChannelBpp + mMetadata.chromaChannelBpp) / 8.0 +
|
||||
sizeof(RawPacketHeader);
|
||||
|
||||
int64_t length = resource->GetLength();
|
||||
int64_t length = mResource.GetLength();
|
||||
if (length != -1) {
|
||||
mInfo.mMetadataDuration.emplace(TimeUnit::FromSeconds((length - sizeof(RawVideoHeader)) /
|
||||
(mFrameSize * mFrameRate)));
|
||||
@ -124,22 +121,15 @@ RawReader::IsMediaSeekable()
|
||||
|
||||
// Helper method that either reads until it gets aLength bytes
|
||||
// or returns false
|
||||
bool RawReader::ReadFromResource(MediaResource *aResource, uint8_t* aBuf,
|
||||
uint32_t aLength)
|
||||
bool RawReader::ReadFromResource(uint8_t* aBuf, uint32_t aLength)
|
||||
{
|
||||
while (aLength > 0) {
|
||||
uint32_t bytesRead = 0;
|
||||
nsresult rv;
|
||||
uint32_t bytesRead = 0;
|
||||
nsresult rv;
|
||||
|
||||
rv = aResource->Read(reinterpret_cast<char*>(aBuf), aLength, &bytesRead);
|
||||
NS_ENSURE_SUCCESS(rv, false);
|
||||
|
||||
if (bytesRead == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
aLength -= bytesRead;
|
||||
aBuf += bytesRead;
|
||||
rv = mResource.Read(reinterpret_cast<char*>(aBuf), aLength, &bytesRead);
|
||||
NS_ENSURE_SUCCESS(rv, false);
|
||||
if (bytesRead == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
@ -161,21 +151,19 @@ bool RawReader::DecodeVideoFrame(bool &aKeyframeSkip,
|
||||
uint32_t length = mFrameSize - sizeof(RawPacketHeader);
|
||||
|
||||
nsAutoArrayPtr<uint8_t> buffer(new uint8_t[length]);
|
||||
MediaResource* resource = mDecoder->GetResource();
|
||||
NS_ASSERTION(resource, "Decoder has no media resource");
|
||||
|
||||
// We're always decoding one frame when called
|
||||
while(true) {
|
||||
RawPacketHeader header;
|
||||
|
||||
// Read in a packet header and validate
|
||||
if (!(ReadFromResource(resource, reinterpret_cast<uint8_t*>(&header),
|
||||
if (!(ReadFromResource(reinterpret_cast<uint8_t*>(&header),
|
||||
sizeof(header))) ||
|
||||
!(header.packetID == 0xFF && header.codecID == RAW_ID /* "YUV" */)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ReadFromResource(resource, buffer, length)) {
|
||||
if (!ReadFromResource(buffer, length)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -233,9 +221,6 @@ RawReader::Seek(int64_t aTime, int64_t aEndTime)
|
||||
{
|
||||
MOZ_ASSERT(OnTaskQueue());
|
||||
|
||||
MediaResource *resource = mDecoder->GetResource();
|
||||
NS_ASSERTION(resource, "Decoder has no media resource");
|
||||
|
||||
uint32_t frame = mCurrentFrame;
|
||||
if (aTime >= UINT_MAX)
|
||||
return SeekPromise::CreateAndReject(NS_ERROR_FAILURE, __func__);
|
||||
@ -245,7 +230,7 @@ RawReader::Seek(int64_t aTime, int64_t aEndTime)
|
||||
offset += sizeof(RawVideoHeader);
|
||||
NS_ENSURE_TRUE(offset.isValid(), SeekPromise::CreateAndReject(NS_ERROR_FAILURE, __func__));
|
||||
|
||||
nsresult rv = resource->Seek(nsISeekableStream::NS_SEEK_SET, offset.value());
|
||||
nsresult rv = mResource.Seek(nsISeekableStream::NS_SEEK_SET, offset.value());
|
||||
NS_ENSURE_SUCCESS(rv, SeekPromise::CreateAndReject(rv, __func__));
|
||||
|
||||
mVideoQueue.Reset();
|
||||
|
@ -47,13 +47,14 @@ public:
|
||||
virtual bool IsMediaSeekable() override;
|
||||
|
||||
private:
|
||||
bool ReadFromResource(MediaResource *aResource, uint8_t *aBuf, uint32_t aLength);
|
||||
bool ReadFromResource(uint8_t *aBuf, uint32_t aLength);
|
||||
|
||||
RawVideoHeader mMetadata;
|
||||
uint32_t mCurrentFrame;
|
||||
double mFrameRate;
|
||||
uint32_t mFrameSize;
|
||||
nsIntRect mPicture;
|
||||
MediaResourceIndex mResource;
|
||||
};
|
||||
|
||||
} // namespace mozilla
|
||||
|
@ -5,7 +5,6 @@
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
#include "nsError.h"
|
||||
#include "AbstractMediaDecoder.h"
|
||||
#include "MediaResource.h"
|
||||
#include "WaveReader.h"
|
||||
#include "MediaDecoderStateMachine.h"
|
||||
#include "VideoUtils.h"
|
||||
@ -107,6 +106,7 @@ namespace {
|
||||
|
||||
WaveReader::WaveReader(AbstractMediaDecoder* aDecoder)
|
||||
: MediaDecoderReader(aDecoder)
|
||||
, mResource(aDecoder->GetResource())
|
||||
{
|
||||
MOZ_COUNT_CTOR(WaveReader);
|
||||
}
|
||||
@ -265,7 +265,7 @@ WaveReader::Seek(int64_t aTarget, int64_t aEndTime)
|
||||
int64_t position = RoundDownToFrame(static_cast<int64_t>(TimeToBytes(seekTime)));
|
||||
NS_ASSERTION(INT64_MAX - mWavePCMOffset > position, "Integer overflow during wave seek");
|
||||
position += mWavePCMOffset;
|
||||
nsresult res = mDecoder->GetResource()->Seek(nsISeekableStream::NS_SEEK_SET, position);
|
||||
nsresult res = mResource.Seek(nsISeekableStream::NS_SEEK_SET, position);
|
||||
if (NS_FAILED(res)) {
|
||||
return SeekPromise::CreateAndReject(res, __func__);
|
||||
} else {
|
||||
@ -302,24 +302,20 @@ media::TimeIntervals WaveReader::GetBuffered()
|
||||
bool
|
||||
WaveReader::ReadAll(char* aBuf, int64_t aSize, int64_t* aBytesRead)
|
||||
{
|
||||
uint32_t got = 0;
|
||||
if (aBytesRead) {
|
||||
*aBytesRead = 0;
|
||||
}
|
||||
do {
|
||||
uint32_t read = 0;
|
||||
if (NS_FAILED(mDecoder->GetResource()->Read(aBuf + got, uint32_t(aSize - got), &read))) {
|
||||
NS_WARNING("Resource read failed");
|
||||
return false;
|
||||
}
|
||||
if (read == 0) {
|
||||
return false;
|
||||
}
|
||||
got += read;
|
||||
if (aBytesRead) {
|
||||
*aBytesRead = got;
|
||||
}
|
||||
} while (got != aSize);
|
||||
uint32_t read = 0;
|
||||
if (NS_FAILED(mResource.Read(aBuf, uint32_t(aSize), &read))) {
|
||||
NS_WARNING("Resource read failed");
|
||||
return false;
|
||||
}
|
||||
if (!read) {
|
||||
return false;
|
||||
}
|
||||
if (aBytesRead) {
|
||||
*aBytesRead = read;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -329,7 +325,7 @@ WaveReader::LoadRIFFChunk()
|
||||
char riffHeader[RIFF_INITIAL_SIZE];
|
||||
const char* p = riffHeader;
|
||||
|
||||
MOZ_ASSERT(mDecoder->GetResource()->Tell() == 0,
|
||||
MOZ_ASSERT(mResource.Tell() == 0,
|
||||
"LoadRIFFChunk called when resource in invalid state");
|
||||
|
||||
if (!ReadAll(riffHeader, sizeof(riffHeader))) {
|
||||
@ -362,7 +358,7 @@ WaveReader::LoadFormatChunk(uint32_t aChunkSize)
|
||||
const char* p = waveFormat;
|
||||
|
||||
// RIFF chunks are always word (two byte) aligned.
|
||||
MOZ_ASSERT(mDecoder->GetResource()->Tell() % 2 == 0,
|
||||
MOZ_ASSERT(mResource.Tell() % 2 == 0,
|
||||
"LoadFormatChunk called with unaligned resource");
|
||||
|
||||
if (!ReadAll(waveFormat, sizeof(waveFormat))) {
|
||||
@ -424,7 +420,7 @@ WaveReader::LoadFormatChunk(uint32_t aChunkSize)
|
||||
}
|
||||
|
||||
// RIFF chunks are always word (two byte) aligned.
|
||||
MOZ_ASSERT(mDecoder->GetResource()->Tell() % 2 == 0,
|
||||
MOZ_ASSERT(mResource.Tell() % 2 == 0,
|
||||
"LoadFormatChunk left resource unaligned");
|
||||
|
||||
// Make sure metadata is fairly sane. The rate check is fairly arbitrary,
|
||||
@ -458,10 +454,10 @@ bool
|
||||
WaveReader::FindDataOffset(uint32_t aChunkSize)
|
||||
{
|
||||
// RIFF chunks are always word (two byte) aligned.
|
||||
MOZ_ASSERT(mDecoder->GetResource()->Tell() % 2 == 0,
|
||||
MOZ_ASSERT(mResource.Tell() % 2 == 0,
|
||||
"FindDataOffset called with unaligned resource");
|
||||
|
||||
int64_t offset = mDecoder->GetResource()->Tell();
|
||||
int64_t offset = mResource.Tell();
|
||||
if (offset <= 0 || offset > UINT32_MAX) {
|
||||
NS_WARNING("PCM data offset out of range");
|
||||
return false;
|
||||
@ -512,7 +508,7 @@ WaveReader::GetDataLength()
|
||||
int64_t
|
||||
WaveReader::GetPosition()
|
||||
{
|
||||
return mDecoder->GetResource()->Tell();
|
||||
return mResource.Tell();
|
||||
}
|
||||
|
||||
bool
|
||||
@ -520,7 +516,7 @@ WaveReader::GetNextChunk(uint32_t* aChunk, uint32_t* aChunkSize)
|
||||
{
|
||||
MOZ_ASSERT(aChunk, "Must have aChunk");
|
||||
MOZ_ASSERT(aChunkSize, "Must have aChunkSize");
|
||||
MOZ_ASSERT(mDecoder->GetResource()->Tell() % 2 == 0,
|
||||
MOZ_ASSERT(mResource.Tell() % 2 == 0,
|
||||
"GetNextChunk called with unaligned resource");
|
||||
|
||||
char chunkHeader[CHUNK_HEADER_SIZE];
|
||||
@ -543,7 +539,7 @@ WaveReader::LoadListChunk(uint32_t aChunkSize,
|
||||
nsAutoPtr<dom::HTMLMediaElement::MetadataTags> &aTags)
|
||||
{
|
||||
// List chunks are always word (two byte) aligned.
|
||||
MOZ_ASSERT(mDecoder->GetResource()->Tell() % 2 == 0,
|
||||
MOZ_ASSERT(mResource.Tell() % 2 == 0,
|
||||
"LoadListChunk called with unaligned resource");
|
||||
|
||||
static const unsigned int MAX_CHUNK_SIZE = 1 << 16;
|
||||
@ -619,7 +615,7 @@ bool
|
||||
WaveReader::LoadAllChunks(nsAutoPtr<dom::HTMLMediaElement::MetadataTags> &aTags)
|
||||
{
|
||||
// Chunks are always word (two byte) aligned.
|
||||
MOZ_ASSERT(mDecoder->GetResource()->Tell() % 2 == 0,
|
||||
MOZ_ASSERT(mResource.Tell() % 2 == 0,
|
||||
"LoadAllChunks called with unaligned resource");
|
||||
|
||||
bool loadFormatChunk = false;
|
||||
|
@ -7,6 +7,8 @@
|
||||
#define WaveReader_h_
|
||||
|
||||
#include "MediaDecoderReader.h"
|
||||
#include "MediaResource.h"
|
||||
|
||||
#include "mozilla/dom/HTMLMediaElement.h"
|
||||
|
||||
namespace mozilla {
|
||||
@ -98,6 +100,8 @@ private:
|
||||
// Start offset of the PCM data in the media stream. Extends mWaveLength
|
||||
// bytes.
|
||||
int64_t mWavePCMOffset;
|
||||
|
||||
MediaResourceIndex mResource;
|
||||
};
|
||||
|
||||
} // namespace mozilla
|
||||
|
@ -6,7 +6,6 @@
|
||||
#include "nsError.h"
|
||||
#include "MediaDecoderStateMachine.h"
|
||||
#include "AbstractMediaDecoder.h"
|
||||
#include "MediaResource.h"
|
||||
#include "SoftwareWebMVideoDecoder.h"
|
||||
#include "WebMReader.h"
|
||||
#include "WebMBufferedParser.h"
|
||||
@ -54,25 +53,15 @@ PRLogModuleInfo* gNesteggLog;
|
||||
static int webm_read(void *aBuffer, size_t aLength, void *aUserData)
|
||||
{
|
||||
MOZ_ASSERT(aUserData);
|
||||
AbstractMediaDecoder* decoder =
|
||||
reinterpret_cast<AbstractMediaDecoder*>(aUserData);
|
||||
MediaResource* resource = decoder->GetResource();
|
||||
NS_ASSERTION(resource, "Decoder has no media resource");
|
||||
MediaResourceIndex* resource =
|
||||
reinterpret_cast<MediaResourceIndex*>(aUserData);
|
||||
|
||||
nsresult rv = NS_OK;
|
||||
bool eof = false;
|
||||
uint32_t bytes = 0;
|
||||
|
||||
char *p = static_cast<char *>(aBuffer);
|
||||
while (NS_SUCCEEDED(rv) && aLength > 0) {
|
||||
uint32_t bytes = 0;
|
||||
rv = resource->Read(p, aLength, &bytes);
|
||||
if (bytes == 0) {
|
||||
eof = true;
|
||||
break;
|
||||
}
|
||||
aLength -= bytes;
|
||||
p += bytes;
|
||||
}
|
||||
rv = resource->Read(static_cast<char *>(aBuffer), aLength, &bytes);
|
||||
|
||||
bool eof = !bytes;
|
||||
|
||||
return NS_FAILED(rv) ? -1 : eof ? 0 : 1;
|
||||
}
|
||||
@ -80,10 +69,8 @@ static int webm_read(void *aBuffer, size_t aLength, void *aUserData)
|
||||
static int webm_seek(int64_t aOffset, int aWhence, void *aUserData)
|
||||
{
|
||||
MOZ_ASSERT(aUserData);
|
||||
AbstractMediaDecoder* decoder =
|
||||
reinterpret_cast<AbstractMediaDecoder*>(aUserData);
|
||||
MediaResource* resource = decoder->GetResource();
|
||||
NS_ASSERTION(resource, "Decoder has no media resource");
|
||||
MediaResourceIndex* resource =
|
||||
reinterpret_cast<MediaResourceIndex*>(aUserData);
|
||||
nsresult rv = resource->Seek(aWhence, aOffset);
|
||||
return NS_SUCCEEDED(rv) ? 0 : -1;
|
||||
}
|
||||
@ -91,10 +78,8 @@ static int webm_seek(int64_t aOffset, int aWhence, void *aUserData)
|
||||
static int64_t webm_tell(void *aUserData)
|
||||
{
|
||||
MOZ_ASSERT(aUserData);
|
||||
AbstractMediaDecoder* decoder =
|
||||
reinterpret_cast<AbstractMediaDecoder*>(aUserData);
|
||||
MediaResource* resource = decoder->GetResource();
|
||||
NS_ASSERTION(resource, "Decoder has no media resource");
|
||||
MediaResourceIndex* resource =
|
||||
reinterpret_cast<MediaResourceIndex*>(aUserData);
|
||||
return resource->Tell();
|
||||
}
|
||||
|
||||
@ -158,6 +143,7 @@ WebMReader::WebMReader(AbstractMediaDecoder* aDecoder, TaskQueue* aBorrowedTaskQ
|
||||
, mLayersBackendType(layers::LayersBackend::LAYERS_NONE)
|
||||
, mHasVideo(false)
|
||||
, mHasAudio(false)
|
||||
, mResource(aDecoder->GetResource())
|
||||
{
|
||||
MOZ_COUNT_CTOR(WebMReader);
|
||||
if (!gNesteggLog) {
|
||||
@ -302,7 +288,7 @@ WebMReader::RetrieveWebMMetadata(MediaInfo* aInfo)
|
||||
io.read = webm_read;
|
||||
io.seek = webm_seek;
|
||||
io.tell = webm_tell;
|
||||
io.userdata = mDecoder;
|
||||
io.userdata = &mResource;
|
||||
int64_t maxOffset = mDecoder->HasInitializationData() ?
|
||||
mBufferedState->GetInitEndOffset() : -1;
|
||||
int r = nestegg_init(&mContext, io, &webm_log, maxOffset);
|
||||
@ -635,7 +621,7 @@ WebMReader::DemuxPacket()
|
||||
isKeyframe = si.is_kf;
|
||||
}
|
||||
|
||||
int64_t offset = mDecoder->GetResource()->Tell();
|
||||
int64_t offset = mResource.Tell();
|
||||
nsRefPtr<NesteggPacketHolder> holder = new NesteggPacketHolder();
|
||||
if (!holder->Init(packet, offset, track, isKeyframe)) {
|
||||
return nullptr;
|
||||
@ -855,7 +841,8 @@ media::TimeIntervals WebMReader::GetBuffered()
|
||||
void WebMReader::NotifyDataArrivedInternal(uint32_t aLength, int64_t aOffset)
|
||||
{
|
||||
MOZ_ASSERT(OnTaskQueue());
|
||||
nsRefPtr<MediaByteBuffer> bytes = mDecoder->GetResource()->SilentReadAt(aOffset, aLength);
|
||||
nsRefPtr<MediaByteBuffer> bytes =
|
||||
mDecoder->GetResource()->MediaReadAt(aOffset, aLength);
|
||||
NS_ENSURE_TRUE_VOID(bytes);
|
||||
mBufferedState->NotifyDataArrived(bytes->Elements(), aLength, aOffset);
|
||||
}
|
||||
|
@ -10,6 +10,7 @@
|
||||
|
||||
#include "FlushableTaskQueue.h"
|
||||
#include "MediaDecoderReader.h"
|
||||
#include "MediaResource.h"
|
||||
#include "PlatformDecoderModule.h"
|
||||
#include "nsAutoRef.h"
|
||||
#include "nestegg/nestegg.h"
|
||||
@ -219,6 +220,8 @@ private:
|
||||
bool mHasVideo;
|
||||
bool mHasAudio;
|
||||
|
||||
MediaResourceIndex mResource;
|
||||
|
||||
};
|
||||
|
||||
} // namespace mozilla
|
||||
|
@ -130,7 +130,9 @@ MediaEngineWebRTC::EnumerateVideoDevices(dom::MediaSourceEnum aMediaSource,
|
||||
|
||||
#ifdef MOZ_WIDGET_ANDROID
|
||||
// get the JVM
|
||||
JavaVM *jvm = mozilla::AndroidBridge::Bridge()->GetVM();
|
||||
JavaVM* jvm;
|
||||
JNIEnv* const env = jni::GetEnvForThread();
|
||||
MOZ_ALWAYS_TRUE(!env->GetJavaVM(&jvm));
|
||||
|
||||
if (webrtc::VideoEngine::SetAndroidObjects(jvm) != 0) {
|
||||
LOG(("VieCapture:SetAndroidObjects Failed"));
|
||||
@ -302,8 +304,9 @@ MediaEngineWebRTC::EnumerateAudioDevices(dom::MediaSourceEnum aMediaSource,
|
||||
jobject context = mozilla::AndroidBridge::Bridge()->GetGlobalContextRef();
|
||||
|
||||
// get the JVM
|
||||
JavaVM *jvm = mozilla::AndroidBridge::Bridge()->GetVM();
|
||||
JNIEnv *env = GetJNIForThread();
|
||||
JavaVM* jvm;
|
||||
JNIEnv* const env = jni::GetEnvForThread();
|
||||
MOZ_ALWAYS_TRUE(!env->GetJavaVM(&jvm));
|
||||
|
||||
if (webrtc::VoiceEngine::SetAndroidObjects(jvm, env, (void*)context) != 0) {
|
||||
LOG(("VoiceEngine:SetAndroidObjects Failed"));
|
||||
|
@ -37,7 +37,7 @@ nsresult
|
||||
PluginPRLibrary::NP_Initialize(NPNetscapeFuncs* bFuncs,
|
||||
NPPluginFuncs* pFuncs, NPError* error)
|
||||
{
|
||||
JNIEnv* env = GetJNIForThread();
|
||||
JNIEnv* env = jni::GetEnvForThread();
|
||||
|
||||
mozilla::AutoLocalJNIFrame jniFrame(env);
|
||||
|
||||
|
@ -124,7 +124,7 @@ AudioRunnable::Run()
|
||||
{
|
||||
PR_SetCurrentThreadName("Android Audio");
|
||||
|
||||
JNIEnv* jenv = GetJNIForThread();
|
||||
JNIEnv* const jenv = mozilla::jni::GetEnvForThread();
|
||||
|
||||
mozilla::AutoLocalJNIFrame autoFrame(jenv, 2);
|
||||
|
||||
@ -207,7 +207,7 @@ anp_audio_newTrack(uint32_t sampleRate, // sampling rate in Hz
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
JNIEnv *jenv = GetJNIForThread();
|
||||
JNIEnv* const jenv = mozilla::jni::GetEnvForThread();
|
||||
|
||||
s->at_class = init_jni_bindings(jenv);
|
||||
s->rate = sampleRate;
|
||||
@ -303,7 +303,7 @@ anp_audio_start(ANPAudioTrack* s)
|
||||
return;
|
||||
}
|
||||
|
||||
JNIEnv *jenv = GetJNIForThread();
|
||||
JNIEnv* const jenv = mozilla::jni::GetEnvForThread();
|
||||
|
||||
mozilla::AutoLocalJNIFrame autoFrame(jenv, 0);
|
||||
jenv->CallVoidMethod(s->output_unit, at.play);
|
||||
@ -331,7 +331,7 @@ anp_audio_pause(ANPAudioTrack* s)
|
||||
return;
|
||||
}
|
||||
|
||||
JNIEnv *jenv = GetJNIForThread();
|
||||
JNIEnv* const jenv = mozilla::jni::GetEnvForThread();
|
||||
|
||||
mozilla::AutoLocalJNIFrame autoFrame(jenv, 0);
|
||||
jenv->CallVoidMethod(s->output_unit, at.pause);
|
||||
@ -345,7 +345,7 @@ anp_audio_stop(ANPAudioTrack* s)
|
||||
}
|
||||
|
||||
s->isStopped = true;
|
||||
JNIEnv *jenv = GetJNIForThread();
|
||||
JNIEnv* const jenv = mozilla::jni::GetEnvForThread();
|
||||
|
||||
mozilla::AutoLocalJNIFrame autoFrame(jenv, 0);
|
||||
jenv->CallVoidMethod(s->output_unit, at.stop);
|
||||
|
@ -1393,7 +1393,7 @@ bool nsPluginInstanceOwner::AddPluginView(const LayoutDeviceRect& aRect /* = Lay
|
||||
if (!mJavaView)
|
||||
return false;
|
||||
|
||||
mJavaView = (void*)AndroidBridge::GetJNIEnv()->NewGlobalRef((jobject)mJavaView);
|
||||
mJavaView = (void*)jni::GetGeckoThreadEnv()->NewGlobalRef((jobject)mJavaView);
|
||||
}
|
||||
|
||||
if (AndroidBridge::Bridge())
|
||||
@ -1412,7 +1412,7 @@ void nsPluginInstanceOwner::RemovePluginView()
|
||||
|
||||
widget::GeckoAppShell::RemovePluginView(
|
||||
jni::Object::Ref::From(jobject(mJavaView)), mFullScreen);
|
||||
AndroidBridge::GetJNIEnv()->DeleteGlobalRef((jobject)mJavaView);
|
||||
jni::GetGeckoThreadEnv()->DeleteGlobalRef((jobject)mJavaView);
|
||||
mJavaView = nullptr;
|
||||
|
||||
if (mFullScreen)
|
||||
@ -1493,7 +1493,7 @@ void nsPluginInstanceOwner::ExitFullScreen() {
|
||||
}
|
||||
|
||||
void nsPluginInstanceOwner::ExitFullScreen(jobject view) {
|
||||
JNIEnv* env = AndroidBridge::GetJNIEnv();
|
||||
JNIEnv* env = jni::GetGeckoThreadEnv();
|
||||
|
||||
if (sFullScreenInstance && sFullScreenInstance->mInstance &&
|
||||
env->IsSameObject(view, (jobject)sFullScreenInstance->mInstance->GetJavaSurface())) {
|
||||
|
295
dom/security/SRICheck.cpp
Normal file
295
dom/security/SRICheck.cpp
Normal file
@ -0,0 +1,295 @@
|
||||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
||||
/* 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 "SRICheck.h"
|
||||
|
||||
#include "mozilla/Base64.h"
|
||||
#include "mozilla/Logging.h"
|
||||
#include "mozilla/Preferences.h"
|
||||
#include "nsContentUtils.h"
|
||||
#include "nsICryptoHash.h"
|
||||
#include "nsIDocument.h"
|
||||
#include "nsIProtocolHandler.h"
|
||||
#include "nsIScriptError.h"
|
||||
#include "nsIScriptSecurityManager.h"
|
||||
#include "nsIURI.h"
|
||||
#include "nsNetUtil.h"
|
||||
#include "nsWhitespaceTokenizer.h"
|
||||
|
||||
static PRLogModuleInfo*
|
||||
GetSriLog()
|
||||
{
|
||||
static PRLogModuleInfo *gSriPRLog;
|
||||
if (!gSriPRLog) {
|
||||
gSriPRLog = PR_NewLogModule("SRI");
|
||||
}
|
||||
return gSriPRLog;
|
||||
}
|
||||
|
||||
#define SRILOG(args) MOZ_LOG(GetSriLog(), mozilla::LogLevel::Debug, args)
|
||||
#define SRIERROR(args) MOZ_LOG(GetSriLog(), mozilla::LogLevel::Error, args)
|
||||
|
||||
namespace mozilla {
|
||||
namespace dom {
|
||||
|
||||
/**
|
||||
* Returns whether or not the sub-resource about to be loaded is eligible
|
||||
* for integrity checks. If it's not, the checks will be skipped and the
|
||||
* sub-resource will be loaded.
|
||||
*/
|
||||
static nsresult
|
||||
IsEligible(nsIURI* aRequestURI, const CORSMode aCORSMode,
|
||||
const nsIDocument* aDocument)
|
||||
{
|
||||
NS_ENSURE_ARG_POINTER(aRequestURI);
|
||||
NS_ENSURE_ARG_POINTER(aDocument);
|
||||
|
||||
nsAutoCString requestSpec;
|
||||
nsresult rv = aRequestURI->GetSpec(requestSpec);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
NS_ConvertUTF8toUTF16 requestSpecUTF16(requestSpec);
|
||||
|
||||
// Was the sub-resource loaded via CORS?
|
||||
if (aCORSMode != CORS_NONE) {
|
||||
SRILOG(("SRICheck::IsEligible, CORS mode"));
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
// Is the sub-resource same-origin?
|
||||
nsIScriptSecurityManager* ssm = nsContentUtils::GetSecurityManager();
|
||||
if (NS_SUCCEEDED(ssm->CheckSameOriginURI(aDocument->GetDocumentURI(),
|
||||
aRequestURI, false))) {
|
||||
SRILOG(("SRICheck::IsEligible, same-origin"));
|
||||
return NS_OK;
|
||||
}
|
||||
if (MOZ_LOG_TEST(GetSriLog(), mozilla::LogLevel::Debug)) {
|
||||
nsAutoCString documentURI;
|
||||
aDocument->GetDocumentURI()->GetAsciiSpec(documentURI);
|
||||
// documentURI will be empty if GetAsciiSpec failed
|
||||
SRILOG(("SRICheck::IsEligible, NOT same origin: documentURI=%s; requestURI=%s",
|
||||
documentURI.get(), requestSpec.get()));
|
||||
}
|
||||
|
||||
const char16_t* params[] = { requestSpecUTF16.get() };
|
||||
nsContentUtils::ReportToConsole(nsIScriptError::errorFlag,
|
||||
NS_LITERAL_CSTRING("Sub-resource Integrity"),
|
||||
aDocument,
|
||||
nsContentUtils::eSECURITY_PROPERTIES,
|
||||
"IneligibleResource",
|
||||
params, ArrayLength(params));
|
||||
return NS_ERROR_SRI_NOT_ELIGIBLE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute the hash of a sub-resource and compare it with the expected
|
||||
* value.
|
||||
*/
|
||||
static nsresult
|
||||
VerifyHash(const SRIMetadata& aMetadata, uint32_t aHashIndex,
|
||||
uint32_t aStringLen, const uint8_t* aString,
|
||||
const nsIDocument* aDocument)
|
||||
{
|
||||
NS_ENSURE_ARG_POINTER(aString);
|
||||
NS_ENSURE_ARG_POINTER(aDocument);
|
||||
|
||||
nsAutoCString base64Hash;
|
||||
aMetadata.GetHash(aHashIndex, &base64Hash);
|
||||
SRILOG(("SRICheck::VerifyHash, hash[%u]=%s", aHashIndex, base64Hash.get()));
|
||||
|
||||
nsAutoCString binaryHash;
|
||||
if (NS_WARN_IF(NS_FAILED(Base64Decode(base64Hash, binaryHash)))) {
|
||||
nsContentUtils::ReportToConsole(nsIScriptError::errorFlag,
|
||||
NS_LITERAL_CSTRING("Sub-resource Integrity"),
|
||||
aDocument,
|
||||
nsContentUtils::eSECURITY_PROPERTIES,
|
||||
"InvalidIntegrityBase64");
|
||||
return NS_ERROR_SRI_CORRUPT;
|
||||
}
|
||||
|
||||
uint32_t hashLength;
|
||||
int8_t hashType;
|
||||
aMetadata.GetHashType(&hashType, &hashLength);
|
||||
if (binaryHash.Length() != hashLength) {
|
||||
nsContentUtils::ReportToConsole(nsIScriptError::errorFlag,
|
||||
NS_LITERAL_CSTRING("Sub-resource Integrity"),
|
||||
aDocument,
|
||||
nsContentUtils::eSECURITY_PROPERTIES,
|
||||
"InvalidIntegrityLength");
|
||||
return NS_ERROR_SRI_CORRUPT;
|
||||
}
|
||||
|
||||
nsresult rv;
|
||||
nsCOMPtr<nsICryptoHash> cryptoHash =
|
||||
do_CreateInstance("@mozilla.org/security/hash;1", &rv);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
rv = cryptoHash->Init(hashType);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
rv = cryptoHash->Update(aString, aStringLen);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
nsAutoCString computedHash;
|
||||
rv = cryptoHash->Finish(false, computedHash);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
if (!binaryHash.Equals(computedHash)) {
|
||||
SRILOG(("SRICheck::VerifyHash, hash[%u] did not match", aHashIndex));
|
||||
return NS_ERROR_SRI_CORRUPT;
|
||||
}
|
||||
|
||||
SRILOG(("SRICheck::VerifyHash, hash[%u] verified successfully", aHashIndex));
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
/* static */ nsresult
|
||||
SRICheck::IntegrityMetadata(const nsAString& aMetadataList,
|
||||
const nsIDocument* aDocument,
|
||||
SRIMetadata* outMetadata)
|
||||
{
|
||||
NS_ENSURE_ARG_POINTER(outMetadata);
|
||||
NS_ENSURE_ARG_POINTER(aDocument);
|
||||
MOZ_ASSERT(outMetadata->IsEmpty()); // caller must pass empty metadata
|
||||
|
||||
if (!Preferences::GetBool("security.sri.enable", false)) {
|
||||
SRILOG(("SRICheck::IntegrityMetadata, sri is disabled (pref)"));
|
||||
return NS_ERROR_SRI_DISABLED;
|
||||
}
|
||||
|
||||
// put a reasonable bound on the length of the metadata
|
||||
NS_ConvertUTF16toUTF8 metadataList(aMetadataList);
|
||||
if (metadataList.Length() > SRICheck::MAX_METADATA_LENGTH) {
|
||||
metadataList.Truncate(SRICheck::MAX_METADATA_LENGTH);
|
||||
}
|
||||
MOZ_ASSERT(metadataList.Length() <= aMetadataList.Length());
|
||||
|
||||
// the integrity attribute is a list of whitespace-separated hashes
|
||||
// and options so we need to look at them one by one and pick the
|
||||
// strongest (valid) one
|
||||
nsCWhitespaceTokenizer tokenizer(metadataList);
|
||||
nsAutoCString token;
|
||||
for (uint32_t i=0; tokenizer.hasMoreTokens() &&
|
||||
i < SRICheck::MAX_METADATA_TOKENS; ++i) {
|
||||
token = tokenizer.nextToken();
|
||||
|
||||
SRIMetadata metadata(token);
|
||||
if (metadata.IsMalformed()) {
|
||||
NS_ConvertUTF8toUTF16 tokenUTF16(token);
|
||||
const char16_t* params[] = { tokenUTF16.get() };
|
||||
nsContentUtils::ReportToConsole(nsIScriptError::warningFlag,
|
||||
NS_LITERAL_CSTRING("Sub-resource Integrity"),
|
||||
aDocument,
|
||||
nsContentUtils::eSECURITY_PROPERTIES,
|
||||
"MalformedIntegrityURI",
|
||||
params, ArrayLength(params));
|
||||
} else if (!metadata.IsAlgorithmSupported()) {
|
||||
nsAutoCString alg;
|
||||
metadata.GetAlgorithm(&alg);
|
||||
NS_ConvertUTF8toUTF16 algUTF16(alg);
|
||||
const char16_t* params[] = { algUTF16.get() };
|
||||
nsContentUtils::ReportToConsole(nsIScriptError::warningFlag,
|
||||
NS_LITERAL_CSTRING("Sub-resource Integrity"),
|
||||
aDocument,
|
||||
nsContentUtils::eSECURITY_PROPERTIES,
|
||||
"UnsupportedHashAlg",
|
||||
params, ArrayLength(params));
|
||||
}
|
||||
|
||||
nsAutoCString alg1, alg2;
|
||||
if (MOZ_LOG_TEST(GetSriLog(), mozilla::LogLevel::Debug)) {
|
||||
outMetadata->GetAlgorithm(&alg1);
|
||||
metadata.GetAlgorithm(&alg2);
|
||||
}
|
||||
if (*outMetadata == metadata) {
|
||||
SRILOG(("SRICheck::IntegrityMetadata, alg '%s' is the same as '%s'",
|
||||
alg1.get(), alg2.get()));
|
||||
*outMetadata += metadata; // add new hash to strongest metadata
|
||||
} else if (*outMetadata < metadata) {
|
||||
SRILOG(("SRICheck::IntegrityMetadata, alg '%s' is weaker than '%s'",
|
||||
alg1.get(), alg2.get()));
|
||||
*outMetadata = metadata; // replace strongest metadata with current
|
||||
}
|
||||
}
|
||||
|
||||
if (MOZ_LOG_TEST(GetSriLog(), mozilla::LogLevel::Debug)) {
|
||||
if (outMetadata->IsValid()) {
|
||||
nsAutoCString alg;
|
||||
outMetadata->GetAlgorithm(&alg);
|
||||
SRILOG(("SRICheck::IntegrityMetadata, using a '%s' hash", alg.get()));
|
||||
} else if (outMetadata->IsEmpty()) {
|
||||
SRILOG(("SRICheck::IntegrityMetadata, no metadata"));
|
||||
} else {
|
||||
SRILOG(("SRICheck::IntegrityMetadata, no valid metadata found"));
|
||||
}
|
||||
}
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
/* static */ nsresult
|
||||
SRICheck::VerifyIntegrity(const SRIMetadata& aMetadata,
|
||||
nsIURI* aRequestURI,
|
||||
const CORSMode aCORSMode,
|
||||
const nsAString& aString,
|
||||
const nsIDocument* aDocument)
|
||||
{
|
||||
NS_ConvertUTF16toUTF8 utf8Hash(aString);
|
||||
return VerifyIntegrity(aMetadata, aRequestURI, aCORSMode, utf8Hash.Length(),
|
||||
(uint8_t*)utf8Hash.get(), aDocument);
|
||||
}
|
||||
|
||||
/* static */ nsresult
|
||||
SRICheck::VerifyIntegrity(const SRIMetadata& aMetadata,
|
||||
nsIURI* aRequestURI,
|
||||
const CORSMode aCORSMode,
|
||||
uint32_t aStringLen,
|
||||
const uint8_t* aString,
|
||||
const nsIDocument* aDocument)
|
||||
{
|
||||
if (MOZ_LOG_TEST(GetSriLog(), mozilla::LogLevel::Debug)) {
|
||||
nsAutoCString requestURL;
|
||||
aRequestURI->GetAsciiSpec(requestURL);
|
||||
// requestURL will be empty if GetAsciiSpec fails
|
||||
SRILOG(("SRICheck::VerifyIntegrity, url=%s (length=%u)",
|
||||
requestURL.get(), aStringLen));
|
||||
}
|
||||
|
||||
MOZ_ASSERT(!aMetadata.IsEmpty()); // should be checked by caller
|
||||
|
||||
// IntegrityMetadata() checks this and returns "no metadata" if
|
||||
// it's disabled so we should never make it this far
|
||||
MOZ_ASSERT(Preferences::GetBool("security.sri.enable", false));
|
||||
|
||||
if (NS_FAILED(IsEligible(aRequestURI, aCORSMode, aDocument))) {
|
||||
return NS_OK; // ignore non-CORS resources for forward-compatibility
|
||||
}
|
||||
if (!aMetadata.IsValid()) {
|
||||
nsContentUtils::ReportToConsole(nsIScriptError::warningFlag,
|
||||
NS_LITERAL_CSTRING("Sub-resource Integrity"),
|
||||
aDocument,
|
||||
nsContentUtils::eSECURITY_PROPERTIES,
|
||||
"NoValidMetadata");
|
||||
return NS_OK; // ignore invalid metadata for forward-compatibility
|
||||
}
|
||||
|
||||
for (uint32_t i = 0; i < aMetadata.HashCount(); i++) {
|
||||
if (NS_SUCCEEDED(VerifyHash(aMetadata, i, aStringLen,
|
||||
aString, aDocument))) {
|
||||
return NS_OK; // stop at the first valid hash
|
||||
}
|
||||
}
|
||||
|
||||
nsAutoCString alg;
|
||||
aMetadata.GetAlgorithm(&alg);
|
||||
NS_ConvertUTF8toUTF16 algUTF16(alg);
|
||||
const char16_t* params[] = { algUTF16.get() };
|
||||
nsContentUtils::ReportToConsole(nsIScriptError::errorFlag,
|
||||
NS_LITERAL_CSTRING("Sub-resource Integrity"),
|
||||
aDocument,
|
||||
nsContentUtils::eSECURITY_PROPERTIES,
|
||||
"IntegrityMismatch",
|
||||
params, ArrayLength(params));
|
||||
return NS_ERROR_SRI_CORRUPT;
|
||||
}
|
||||
|
||||
} // namespace dom
|
||||
} // namespace mozilla
|
62
dom/security/SRICheck.h
Normal file
62
dom/security/SRICheck.h
Normal file
@ -0,0 +1,62 @@
|
||||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
||||
/* 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 mozilla_dom_SRICheck_h
|
||||
#define mozilla_dom_SRICheck_h
|
||||
|
||||
#include "mozilla/CORSMode.h"
|
||||
#include "nsCOMPtr.h"
|
||||
#include "SRIMetadata.h"
|
||||
|
||||
class nsIDocument;
|
||||
class nsIHttpChannel;
|
||||
class nsIScriptSecurityManager;
|
||||
class nsIStreamLoader;
|
||||
class nsIURI;
|
||||
|
||||
namespace mozilla {
|
||||
namespace dom {
|
||||
|
||||
class SRICheck final
|
||||
{
|
||||
public:
|
||||
static const uint32_t MAX_METADATA_LENGTH = 24*1024;
|
||||
static const uint32_t MAX_METADATA_TOKENS = 512;
|
||||
|
||||
/**
|
||||
* Parse the multiple hashes specified in the integrity attribute and
|
||||
* return the strongest supported hash.
|
||||
*/
|
||||
static nsresult IntegrityMetadata(const nsAString& aMetadataList,
|
||||
const nsIDocument* aDocument,
|
||||
SRIMetadata* outMetadata);
|
||||
|
||||
/**
|
||||
* Process the integrity attribute of the element. A result of false
|
||||
* must prevent the resource from loading.
|
||||
*/
|
||||
static nsresult VerifyIntegrity(const SRIMetadata& aMetadata,
|
||||
nsIURI* aRequestURI,
|
||||
const CORSMode aCORSMode,
|
||||
const nsAString& aString,
|
||||
const nsIDocument* aDocument);
|
||||
|
||||
/**
|
||||
* Process the integrity attribute of the element. A result of false
|
||||
* must prevent the resource from loading.
|
||||
*/
|
||||
static nsresult VerifyIntegrity(const SRIMetadata& aMetadata,
|
||||
nsIURI* aRequestURI,
|
||||
const CORSMode aCORSMode,
|
||||
uint32_t aStringLen,
|
||||
const uint8_t* aString,
|
||||
const nsIDocument* aDocument);
|
||||
};
|
||||
|
||||
} // namespace dom
|
||||
} // namespace mozilla
|
||||
|
||||
#endif // mozilla_dom_SRICheck_h
|
172
dom/security/SRIMetadata.cpp
Normal file
172
dom/security/SRIMetadata.cpp
Normal file
@ -0,0 +1,172 @@
|
||||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
||||
/* 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 "SRIMetadata.h"
|
||||
|
||||
#include "hasht.h"
|
||||
#include "mozilla/dom/URLSearchParams.h"
|
||||
#include "mozilla/Logging.h"
|
||||
#include "nsICryptoHash.h"
|
||||
|
||||
static PRLogModuleInfo*
|
||||
GetSriMetadataLog()
|
||||
{
|
||||
static PRLogModuleInfo *gSriMetadataPRLog;
|
||||
if (!gSriMetadataPRLog) {
|
||||
gSriMetadataPRLog = PR_NewLogModule("SRIMetadata");
|
||||
}
|
||||
return gSriMetadataPRLog;
|
||||
}
|
||||
|
||||
#define SRIMETADATALOG(args) MOZ_LOG(GetSriMetadataLog(), mozilla::LogLevel::Debug, args)
|
||||
#define SRIMETADATAERROR(args) MOZ_LOG(GetSriMetadataLog(), mozilla::LogLevel::Error, args)
|
||||
|
||||
namespace mozilla {
|
||||
namespace dom {
|
||||
|
||||
SRIMetadata::SRIMetadata(const nsACString& aToken)
|
||||
: mAlgorithmType(SRIMetadata::UNKNOWN_ALGORITHM), mEmpty(false)
|
||||
{
|
||||
MOZ_ASSERT(!aToken.IsEmpty()); // callers should check this first
|
||||
|
||||
SRIMETADATALOG(("SRIMetadata::SRIMetadata, aToken='%s'",
|
||||
PromiseFlatCString(aToken).get()));
|
||||
|
||||
int32_t hyphen = aToken.FindChar('-');
|
||||
if (hyphen == -1) {
|
||||
SRIMETADATAERROR(("SRIMetadata::SRIMetadata, invalid (no hyphen)"));
|
||||
return; // invalid metadata
|
||||
}
|
||||
|
||||
// split the token into its components
|
||||
mAlgorithm = Substring(aToken, 0, hyphen);
|
||||
uint32_t hashStart = hyphen + 1;
|
||||
if (hashStart >= aToken.Length()) {
|
||||
SRIMETADATAERROR(("SRIMetadata::SRIMetadata, invalid (missing digest)"));
|
||||
return; // invalid metadata
|
||||
}
|
||||
int32_t question = aToken.FindChar('?');
|
||||
if (question == -1) {
|
||||
mHashes.AppendElement(Substring(aToken, hashStart,
|
||||
aToken.Length() - hashStart));
|
||||
} else {
|
||||
MOZ_ASSERT(question > 0);
|
||||
if (static_cast<uint32_t>(question) <= hashStart) {
|
||||
SRIMETADATAERROR(("SRIMetadata::SRIMetadata, invalid (options w/o digest)"));
|
||||
return; // invalid metadata
|
||||
}
|
||||
mHashes.AppendElement(Substring(aToken, hashStart,
|
||||
question - hashStart));
|
||||
}
|
||||
|
||||
if (mAlgorithm.EqualsLiteral("sha256")) {
|
||||
mAlgorithmType = nsICryptoHash::SHA256;
|
||||
} else if (mAlgorithm.EqualsLiteral("sha384")) {
|
||||
mAlgorithmType = nsICryptoHash::SHA384;
|
||||
} else if (mAlgorithm.EqualsLiteral("sha512")) {
|
||||
mAlgorithmType = nsICryptoHash::SHA512;
|
||||
}
|
||||
|
||||
SRIMETADATALOG(("SRIMetadata::SRIMetadata, hash='%s'; alg='%s'",
|
||||
mHashes[0].get(), mAlgorithm.get()));
|
||||
}
|
||||
|
||||
bool
|
||||
SRIMetadata::operator<(const SRIMetadata& aOther) const
|
||||
{
|
||||
static_assert(nsICryptoHash::SHA256 < nsICryptoHash::SHA384,
|
||||
"We rely on the order indicating relative alg strength");
|
||||
static_assert(nsICryptoHash::SHA384 < nsICryptoHash::SHA512,
|
||||
"We rely on the order indicating relative alg strength");
|
||||
MOZ_ASSERT(mAlgorithmType == SRIMetadata::UNKNOWN_ALGORITHM ||
|
||||
mAlgorithmType == nsICryptoHash::SHA256 ||
|
||||
mAlgorithmType == nsICryptoHash::SHA384 ||
|
||||
mAlgorithmType == nsICryptoHash::SHA512);
|
||||
MOZ_ASSERT(aOther.mAlgorithmType == SRIMetadata::UNKNOWN_ALGORITHM ||
|
||||
aOther.mAlgorithmType == nsICryptoHash::SHA256 ||
|
||||
aOther.mAlgorithmType == nsICryptoHash::SHA384 ||
|
||||
aOther.mAlgorithmType == nsICryptoHash::SHA512);
|
||||
|
||||
if (mEmpty) {
|
||||
SRIMETADATALOG(("SRIMetadata::operator<, first metadata is empty"));
|
||||
return true; // anything beats the empty metadata (incl. invalid ones)
|
||||
}
|
||||
|
||||
SRIMETADATALOG(("SRIMetadata::operator<, alg1='%d'; alg2='%d'",
|
||||
mAlgorithmType, aOther.mAlgorithmType));
|
||||
return (mAlgorithmType < aOther.mAlgorithmType);
|
||||
}
|
||||
|
||||
bool
|
||||
SRIMetadata::operator>(const SRIMetadata& aOther) const
|
||||
{
|
||||
MOZ_ASSERT(false);
|
||||
return false;
|
||||
}
|
||||
|
||||
SRIMetadata&
|
||||
SRIMetadata::operator+=(const SRIMetadata& aOther)
|
||||
{
|
||||
MOZ_ASSERT(!aOther.IsEmpty() && !IsEmpty());
|
||||
MOZ_ASSERT(aOther.IsValid() && IsValid());
|
||||
MOZ_ASSERT(mAlgorithmType == aOther.mAlgorithmType);
|
||||
|
||||
// We only pull in the first element of the other metadata
|
||||
MOZ_ASSERT(aOther.mHashes.Length() == 1);
|
||||
if (mHashes.Length() < SRIMetadata::MAX_ALTERNATE_HASHES) {
|
||||
SRIMETADATALOG(("SRIMetadata::operator+=, appending another '%s' hash (new length=%d)",
|
||||
mAlgorithm.get(), mHashes.Length()));
|
||||
mHashes.AppendElement(aOther.mHashes[0]);
|
||||
}
|
||||
|
||||
MOZ_ASSERT(mHashes.Length() > 1);
|
||||
MOZ_ASSERT(mHashes.Length() <= SRIMetadata::MAX_ALTERNATE_HASHES);
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool
|
||||
SRIMetadata::operator==(const SRIMetadata& aOther) const
|
||||
{
|
||||
if (IsEmpty() || !IsValid()) {
|
||||
return false;
|
||||
}
|
||||
return mAlgorithmType == aOther.mAlgorithmType;
|
||||
}
|
||||
|
||||
void
|
||||
SRIMetadata::GetHash(uint32_t aIndex, nsCString* outHash) const
|
||||
{
|
||||
MOZ_ASSERT(aIndex < SRIMetadata::MAX_ALTERNATE_HASHES);
|
||||
if (NS_WARN_IF(aIndex >= mHashes.Length())) {
|
||||
*outHash = nullptr;
|
||||
return;
|
||||
}
|
||||
*outHash = mHashes[aIndex];
|
||||
}
|
||||
|
||||
void
|
||||
SRIMetadata::GetHashType(int8_t* outType, uint32_t* outLength) const
|
||||
{
|
||||
// these constants are defined in security/nss/lib/util/hasht.h and
|
||||
// netwerk/base/public/nsICryptoHash.idl
|
||||
switch (mAlgorithmType) {
|
||||
case nsICryptoHash::SHA256:
|
||||
*outLength = SHA256_LENGTH;
|
||||
break;
|
||||
case nsICryptoHash::SHA384:
|
||||
*outLength = SHA384_LENGTH;
|
||||
break;
|
||||
case nsICryptoHash::SHA512:
|
||||
*outLength = SHA512_LENGTH;
|
||||
break;
|
||||
default:
|
||||
*outLength = 0;
|
||||
}
|
||||
*outType = mAlgorithmType;
|
||||
}
|
||||
|
||||
} // namespace dom
|
||||
} // namespace mozilla
|
74
dom/security/SRIMetadata.h
Normal file
74
dom/security/SRIMetadata.h
Normal file
@ -0,0 +1,74 @@
|
||||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
||||
/* 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 mozilla_dom_SRIMetadata_h
|
||||
#define mozilla_dom_SRIMetadata_h
|
||||
|
||||
#include "nsTArray.h"
|
||||
#include "nsString.h"
|
||||
|
||||
namespace mozilla {
|
||||
namespace dom {
|
||||
|
||||
class SRIMetadata final
|
||||
{
|
||||
public:
|
||||
static const uint32_t MAX_ALTERNATE_HASHES = 256;
|
||||
static const int8_t UNKNOWN_ALGORITHM = -1;
|
||||
|
||||
/**
|
||||
* Create an empty metadata object.
|
||||
*/
|
||||
SRIMetadata() : mAlgorithmType(UNKNOWN_ALGORITHM), mEmpty(true) {}
|
||||
|
||||
/**
|
||||
* Split a string token into the components of an SRI metadata
|
||||
* attribute.
|
||||
*/
|
||||
explicit SRIMetadata(const nsACString& aToken);
|
||||
|
||||
/**
|
||||
* Returns true when this object's hash algorithm is weaker than the
|
||||
* other object's hash algorithm.
|
||||
*/
|
||||
bool operator<(const SRIMetadata& aOther) const;
|
||||
|
||||
/**
|
||||
* Not implemented. Should not be used.
|
||||
*/
|
||||
bool operator>(const SRIMetadata& aOther) const;
|
||||
|
||||
/**
|
||||
* Add another metadata's hash to this one.
|
||||
*/
|
||||
SRIMetadata& operator+=(const SRIMetadata& aOther);
|
||||
|
||||
/**
|
||||
* Returns true when the two metadata use the same hash algorithm.
|
||||
*/
|
||||
bool operator==(const SRIMetadata& aOther) const;
|
||||
|
||||
bool IsEmpty() const { return mEmpty; }
|
||||
bool IsMalformed() const { return mHashes.IsEmpty() || mAlgorithm.IsEmpty(); }
|
||||
bool IsAlgorithmSupported() const { return mAlgorithmType != UNKNOWN_ALGORITHM; }
|
||||
bool IsValid() const { return !IsMalformed() && IsAlgorithmSupported(); }
|
||||
|
||||
uint32_t HashCount() const { return mHashes.Length(); }
|
||||
void GetHash(uint32_t aIndex, nsCString* outHash) const;
|
||||
void GetAlgorithm(nsCString* outAlg) const { *outAlg = mAlgorithm; }
|
||||
void GetHashType(int8_t* outType, uint32_t* outLength) const;
|
||||
|
||||
private:
|
||||
nsTArray<nsCString> mHashes;
|
||||
nsCString mAlgorithm;
|
||||
int8_t mAlgorithmType;
|
||||
bool mEmpty;
|
||||
};
|
||||
|
||||
} // namespace dom
|
||||
} // namespace mozilla
|
||||
|
||||
#endif // mozilla_dom_SRIMetadata_h
|
@ -12,6 +12,8 @@ EXPORTS.mozilla.dom += [
|
||||
'nsCSPService.h',
|
||||
'nsCSPUtils.h',
|
||||
'nsMixedContentBlocker.h',
|
||||
'SRICheck.h',
|
||||
'SRIMetadata.h',
|
||||
]
|
||||
|
||||
EXPORTS += [
|
||||
@ -27,6 +29,8 @@ UNIFIED_SOURCES += [
|
||||
'nsCSPService.cpp',
|
||||
'nsCSPUtils.cpp',
|
||||
'nsMixedContentBlocker.cpp',
|
||||
'SRICheck.cpp',
|
||||
'SRIMetadata.cpp',
|
||||
]
|
||||
|
||||
FAIL_ON_WARNINGS = True
|
||||
|
@ -16,6 +16,7 @@ MOCHITEST_MANIFESTS += [
|
||||
'cors/mochitest.ini',
|
||||
'csp/mochitest.ini',
|
||||
'mixedcontentblocker/mochitest.ini',
|
||||
'sri/mochitest.ini',
|
||||
]
|
||||
|
||||
MOCHITEST_CHROME_MANIFESTS += [
|
||||
|
83
dom/security/test/sri/iframe_script_crossdomain.html
Normal file
83
dom/security/test/sri/iframe_script_crossdomain.html
Normal file
@ -0,0 +1,83 @@
|
||||
<!DOCTYPE HTML>
|
||||
<!-- Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ -->
|
||||
<html>
|
||||
<head>
|
||||
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
|
||||
</head>
|
||||
<body>
|
||||
<p id="display"></p>
|
||||
<div id="content" style="display: none">
|
||||
</div>
|
||||
<pre id="test">
|
||||
</pre>
|
||||
|
||||
<script type="application/javascript">
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
|
||||
window.hasCORSLoaded = false;
|
||||
window.hasNonCORSLoaded = false;
|
||||
|
||||
function good_nonsriLoaded() {
|
||||
ok(true, "Non-eligible non-SRI resource was loaded correctly.");
|
||||
}
|
||||
function bad_nonsriBlocked() {
|
||||
ok(false, "Non-eligible non-SRI resources should be loaded!");
|
||||
}
|
||||
|
||||
function good_nonCORSInvalidLoaded() {
|
||||
ok(true, "A non-CORS resource with invalid metadata was correctly loaded.");
|
||||
}
|
||||
function bad_nonCORSInvalidBlocked() {
|
||||
ok(false, "Non-CORS resources with invalid metadata should be loaded!");
|
||||
}
|
||||
|
||||
window.onerrorCalled = false;
|
||||
window.onloadCalled = false;
|
||||
|
||||
function bad_onloadCalled() {
|
||||
window.onloadCalled = true;
|
||||
}
|
||||
|
||||
function good_onerrorCalled() {
|
||||
window.onerrorCalled = true;
|
||||
}
|
||||
|
||||
window.onload = function() {
|
||||
SimpleTest.finish()
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- cors-enabled. should be loaded -->
|
||||
<script src="http://example.com/tests/dom/security/test/sri/script_crossdomain1.js"
|
||||
crossorigin=""
|
||||
integrity="sha512-9Tv2DL1fHvmPQa1RviwKleE/jq72jgxj8XGLyWn3H6Xp/qbtfK/jZINoPFAv2mf0Nn1TxhZYMFULAbzJNGkl4Q=="></script>
|
||||
|
||||
<!-- not cors-enabled. should be blocked -->
|
||||
<script src="http://example.com/tests/dom/security/test/sri/script_crossdomain2.js"
|
||||
crossorigin="anonymous"
|
||||
integrity="sha256-ntgU2U1xv7HfK1XWMTSWz6vJkyVtGzMrIAxQkux1I94="
|
||||
onload="bad_onloadCalled()"
|
||||
onerror="good_onerrorCalled()"></script>
|
||||
|
||||
<!-- non-cors but not actually using SRI. should trigger onload -->
|
||||
<script src="http://example.com/tests/dom/security/test/sri/script_crossdomain3.js"
|
||||
integrity=" "
|
||||
onload="good_nonsriLoaded()"
|
||||
onerror="bad_nonsriBlocked()"></script>
|
||||
|
||||
<!-- non-cors with invalid metadata. should trigger onload -->
|
||||
<script src="http://example.com/tests/dom/security/test/sri/script_crossdomain4.js"
|
||||
integrity="sha256-bogus"
|
||||
onload="good_nonCORSInvalidLoaded()"
|
||||
onerror="bad_nonCORSInvalidBlocked()"></script>
|
||||
|
||||
<script>
|
||||
ok(window.hasCORSLoaded, "CORS-enabled resource with a correct hash");
|
||||
ok(!window.hasNonCORSLoaded, "Correct hash, but non-CORS, should be blocked");
|
||||
ok(!window.onloadCalled, "Failed loads should not call onload when they're cross-domain");
|
||||
ok(window.onerrorCalled, "Failed loads should call onerror when they're cross-domain");
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
209
dom/security/test/sri/iframe_script_sameorigin.html
Normal file
209
dom/security/test/sri/iframe_script_sameorigin.html
Normal file
@ -0,0 +1,209 @@
|
||||
<!DOCTYPE HTML>
|
||||
<!-- Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ -->
|
||||
<html>
|
||||
<head>
|
||||
<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();
|
||||
window.onload = function() {
|
||||
SimpleTest.finish();
|
||||
}
|
||||
</script>
|
||||
<script>
|
||||
function good_correctHashLoaded() {
|
||||
ok(true, "A script was correctly loaded when integrity matched")
|
||||
}
|
||||
function bad_correctHashBlocked() {
|
||||
ok(false, "We should load scripts with hashes that match!");
|
||||
}
|
||||
|
||||
function good_correctHashArrayLoaded() {
|
||||
ok(true, "A script was correctly loaded when one of the hashes in the integrity attribute matched")
|
||||
}
|
||||
function bad_correctHashArrayBlocked() {
|
||||
ok(false, "We should load scripts with at least one hash that match!");
|
||||
}
|
||||
|
||||
function good_emptyIntegrityLoaded() {
|
||||
ok(true, "A script was correctly loaded when the integrity attribute was empty")
|
||||
}
|
||||
function bad_emptyIntegrityBlocked() {
|
||||
ok(false, "We should load scripts with empty integrity attributes!");
|
||||
}
|
||||
|
||||
function good_whitespaceIntegrityLoaded() {
|
||||
ok(true, "A script was correctly loaded when the integrity attribute only contained whitespace")
|
||||
}
|
||||
function bad_whitespaceIntegrityBlocked() {
|
||||
ok(false, "We should load scripts with integrity attributes containing only whitespace!");
|
||||
}
|
||||
|
||||
function good_incorrectHashBlocked() {
|
||||
ok(true, "A script was correctly blocked, because the hash digest was wrong");
|
||||
}
|
||||
function bad_incorrectHashLoaded() {
|
||||
ok(false, "We should not load scripts with hashes that do not match the content!");
|
||||
}
|
||||
|
||||
function good_incorrectHashArrayBlocked() {
|
||||
ok(true, "A script was correctly blocked, because all the hashes were wrong");
|
||||
}
|
||||
function bad_incorrectHashArrayLoaded() {
|
||||
ok(false, "We should not load scripts when none of the hashes match the content!");
|
||||
}
|
||||
|
||||
function good_incorrectHashLengthBlocked() {
|
||||
ok(true, "A script was correctly blocked, because the hash length was wrong");
|
||||
}
|
||||
function bad_incorrectHashLengthLoaded() {
|
||||
ok(false, "We should not load scripts with hashes that don't have the right length!");
|
||||
}
|
||||
|
||||
function bad_incorrectHashFunctionBlocked() {
|
||||
ok(false, "We should load scripts with invalid/unsupported hash functions!");
|
||||
}
|
||||
function good_incorrectHashFunctionLoaded() {
|
||||
ok(true, "A script was correctly loaded, despite the hash function being invalid/unsupported.");
|
||||
}
|
||||
|
||||
function bad_missingHashFunctionBlocked() {
|
||||
ok(false, "We should load scripts with missing hash functions!");
|
||||
}
|
||||
function good_missingHashFunctionLoaded() {
|
||||
ok(true, "A script was correctly loaded, despite a missing hash function.");
|
||||
}
|
||||
|
||||
function bad_missingHashValueBlocked() {
|
||||
ok(false, "We should load scripts with missing hash digests!");
|
||||
}
|
||||
function good_missingHashValueLoaded() {
|
||||
ok(true, "A script was correctly loaded, despite the missing hash digest.");
|
||||
}
|
||||
|
||||
function good_401Blocked() {
|
||||
ok(true, "A script was not loaded because of 401 response.");
|
||||
}
|
||||
function bad_401Loaded() {
|
||||
ok(false, "We should nt load scripts with a 401 response!");
|
||||
}
|
||||
|
||||
function good_valid302Loaded() {
|
||||
ok(true, "A script was loaded successfully despite a 302 response.");
|
||||
}
|
||||
function bad_valid302Blocked() {
|
||||
ok(false, "We should load scripts with a 302 response and the right hash!");
|
||||
}
|
||||
|
||||
function good_invalid302Blocked() {
|
||||
ok(true, "A script was blocked successfully after a 302 response.");
|
||||
}
|
||||
function bad_invalid302Loaded() {
|
||||
ok(false, "We should not load scripts with a 302 response and the wrong hash!");
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<!-- valid hash. should trigger onload -->
|
||||
<!-- the hash value comes from running this command:
|
||||
cat script.js | openssl dgst -sha256 -binary | openssl enc -base64 -A
|
||||
-->
|
||||
<script src="script.js"
|
||||
integrity="sha256-RkrQYrxD/HCx+ImVLb51nvxJ6ZHfwuEm7bHppTun9oA="
|
||||
onerror="bad_correctHashBlocked()"
|
||||
onload="good_correctHashLoaded()"></script>
|
||||
|
||||
<!-- valid sha512 hash. should trigger onload -->
|
||||
<script src="script.js"
|
||||
integrity="sha512-mzSqH+vC6qrXX46JX2WEZ0FtY/lGj/5+5yYCBlk0jfYHLm0vP6XgsURbq83mwMApsnwbDLXdgjp5J8E93GT6Mw==?ignore=this"
|
||||
onerror="bad_correctHashBlocked()"
|
||||
onload="good_correctHashLoaded()"></script>
|
||||
|
||||
<!-- one valid sha256 hash. should trigger onload -->
|
||||
<script src="script.js"
|
||||
integrity="sha256-rkrQYrxD/HCx+ImVLb51nvxJ6ZHfwuEm7bHppTun9oA= sha256-RkrQYrxD/HCx+ImVLb51nvxJ6ZHfwuEm7bHppTun9oA= sha256-rkrQYrxD/HCx+ImVLb51nvxJ6ZHfwuEm7bHppTun9oA="
|
||||
onerror="bad_correctHashArrayBlocked()"
|
||||
onload="good_correctHashArrayLoaded()"></script>
|
||||
|
||||
<!-- empty integrity. should trigger onload -->
|
||||
<script src="script.js"
|
||||
integrity=""
|
||||
onerror="bad_emptyIntegrityBlocked()"
|
||||
onload="good_emptyIntegrityLoaded()"></script>
|
||||
|
||||
<!-- whitespace integrity. should trigger onload -->
|
||||
<script src="script.js"
|
||||
integrity="
|
||||
|
||||
"
|
||||
onerror="bad_whitespaceIntegrityBlocked()"
|
||||
onload="good_whitespaceIntegrityLoaded()"></script>
|
||||
|
||||
<!-- invalid sha256 hash but valid sha384 hash. should trigger onload -->
|
||||
<script src="script.js"
|
||||
integrity="sha256-bogus sha384-zDCkvKOHXk8mM6Nk07oOGXGME17PA4+ydFw+hq0r9kgF6ZDYFWK3fLGPEy7FoOAo?"
|
||||
onerror="bad_correctHashBlocked()"
|
||||
onload="good_correctHashLoaded()"></script>
|
||||
|
||||
<!-- valid sha256 and invalid sha384. should trigger onerror -->
|
||||
<script src="script.js"
|
||||
integrity="sha256-RkrQYrxD/HCx+ImVLb51nvxJ6ZHfwuEm7bHppTun9oA= sha384-RkrQYrxD/HCx+ImVLb51nvxJ6ZHfwuEm7bHppTun9oA="
|
||||
onerror="good_incorrectHashLengthBlocked()"
|
||||
onload="bad_incorrectHashLengthLoaded()"></script>
|
||||
|
||||
<!-- invalid hash. should trigger onerror -->
|
||||
<script src="script.js"
|
||||
integrity="sha256-rkrQYrxD/HCx+ImVLb51nvxJ6ZHfwuEm7bHppTun9oA="
|
||||
onerror="good_incorrectHashBlocked()"
|
||||
onload="bad_incorrectHashLoaded()"></script>
|
||||
|
||||
<!-- invalid hashes. should trigger onerror -->
|
||||
<script src="script.js"
|
||||
integrity="sha256-rkrQYrxD/HCx+ImVLb51nvxJ6ZHfwuEm7bHppTun9oA= sha256-ZkrQYrxD/HCx+ImVLb51nvxJ6ZHfwuEm7bHppTun9oA= sha256-zkrQYrxD/HCx+ImVLb51nvxJ6ZHfwuEm7bHppTun9oA="
|
||||
onerror="good_incorrectHashBlocked()"
|
||||
onload="bad_incorrectHashLoaded()"></script>
|
||||
|
||||
<!-- invalid hash function. should trigger onload -->
|
||||
<script src="script.js"
|
||||
integrity="rot13-RkrQYrxD/HCx+ImVLb51nvxJ6ZHfwuEm7bHppTun9oA="
|
||||
onerror="bad_incorrectHashFunctionBlocked()"
|
||||
onload="good_incorrectHashFunctionLoaded()"></script>
|
||||
|
||||
<!-- missing hash function. should trigger onload -->
|
||||
<script src="script.js"
|
||||
integrity="RkrQYrxD/HCx+ImVLb51nvxJ6ZHfwuEm7bHppTun9oA="
|
||||
onerror="bad_missingHashFunctionBlocked()"
|
||||
onload="good_missingHashFunctionLoaded()"></script>
|
||||
|
||||
<!-- missing hash value. should trigger onload -->
|
||||
<script src="script.js"
|
||||
integrity="sha512-"
|
||||
onerror="bad_missingHashValueBlocked()"
|
||||
onload="good_missingHashValueLoaded()"></script>
|
||||
|
||||
<!-- 401 response. should trigger onerror -->
|
||||
<script src="script_401.js"
|
||||
integrity="sha256-RkrQYrxD/HCx+ImVLb51nvxJ6ZHfwuEm7bHppTun9oA="
|
||||
onerror="good_401Blocked()"
|
||||
onload="bad_401Loaded()"></script>
|
||||
|
||||
<!-- valid sha256 after a redirection. should trigger onload -->
|
||||
<script src="script_302.js"
|
||||
integrity="sha256-RkrQYrxD/HCx+ImVLb51nvxJ6ZHfwuEm7bHppTun9oA="
|
||||
onerror="bad_valid302Blocked()"
|
||||
onload="good_valid302Loaded()"></script>
|
||||
|
||||
<!-- invalid sha256 after a redirection. should trigger onerror -->
|
||||
<script src="script_302.js"
|
||||
integrity="sha256-JSi74NSN8WQNr9syBGmNg2APJp9PnHUO5ioZo5hmIiQ="
|
||||
onerror="good_invalid302Blocked()"
|
||||
onload="bad_invalid302Loaded()"></script>
|
||||
|
||||
<p id="display"></p>
|
||||
<div id="content" style="display: none">
|
||||
</div>
|
||||
<pre id="test">
|
||||
</pre>
|
||||
</body>
|
||||
</html>
|
74
dom/security/test/sri/iframe_sri_disabled.html
Normal file
74
dom/security/test/sri/iframe_sri_disabled.html
Normal file
@ -0,0 +1,74 @@
|
||||
<!DOCTYPE HTML>
|
||||
<!-- Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ -->
|
||||
<html>
|
||||
<head>
|
||||
<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();
|
||||
window.onload = function() {
|
||||
SimpleTest.finish();
|
||||
}
|
||||
</script>
|
||||
<script>
|
||||
function good_correctHashLoaded() {
|
||||
ok(true, "A script was correctly loaded when integrity matched")
|
||||
}
|
||||
function bad_correctHashBlocked() {
|
||||
ok(false, "We should load scripts with hashes that match!");
|
||||
}
|
||||
|
||||
function good_incorrectHashLoaded() {
|
||||
ok(true, "A script was correctly loaded despite the incorrect hash because SRI is disabled.");
|
||||
}
|
||||
function bad_incorrectHashBlocked() {
|
||||
ok(false, "We should load scripts with hashes that do not match the content when SRI is disabled!");
|
||||
}
|
||||
|
||||
function good_correctStyleHashLoaded() {
|
||||
ok(true, "A stylesheet was correctly loaded when integrity matched")
|
||||
}
|
||||
function bad_correctStyleHashBlocked() {
|
||||
ok(false, "We should load stylesheets with hashes that match!");
|
||||
}
|
||||
|
||||
function good_incorrectStyleHashLoaded() {
|
||||
ok(true, "A stylesheet was correctly loaded despite the incorrect hash because SRI is disabled.");
|
||||
}
|
||||
function bad_incorrectStyleHashBlocked() {
|
||||
ok(false, "We should load stylesheets with hashes that do not match the content when SRI is disabled!");
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- valid sha256 hash. should trigger onload -->
|
||||
<link rel="stylesheet" href="style1.css?disabled"
|
||||
integrity="sha256-qs8lnkunWoVldk5d5E+652yth4VTSHohlBKQvvgGwa8="
|
||||
onerror="bad_correctStyleHashBlocked()"
|
||||
onload="good_correctStyleHashLoaded()">
|
||||
|
||||
<!-- invalid sha256 hash. should trigger onerror -->
|
||||
<link rel="stylesheet" href="style2.css?disabled"
|
||||
integrity="sha256-bogus"
|
||||
onerror="bad_incorrectStyleHashBlocked()"
|
||||
onload="good_incorrectStyleHashLoaded()">
|
||||
</head>
|
||||
<body>
|
||||
<!-- valid hash. should trigger onload -->
|
||||
<script src="script.js"
|
||||
integrity="sha256-RkrQYrxD/HCx+ImVLb51nvxJ6ZHfwuEm7bHppTun9oA="
|
||||
onerror="bad_correctHashBlocked()"
|
||||
onload="good_correctHashLoaded()"></script>
|
||||
|
||||
<!-- invalid hash. should trigger onerror -->
|
||||
<script src="script.js"
|
||||
integrity="sha256-rkrQYrxD/HCx+ImVLb51nvxJ6ZHfwuEm7bHppTun9oA="
|
||||
onerror="bad_incorrectHashBlocked()"
|
||||
onload="good_incorrectHashLoaded()"></script>
|
||||
<p id="display"></p>
|
||||
<div id="content" style="display: none">
|
||||
</div>
|
||||
<pre id="test">
|
||||
</pre>
|
||||
</body>
|
||||
</html>
|
74
dom/security/test/sri/iframe_style_sameorigin.html
Normal file
74
dom/security/test/sri/iframe_style_sameorigin.html
Normal file
@ -0,0 +1,74 @@
|
||||
<!DOCTYPE HTML>
|
||||
<!-- Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ -->
|
||||
<html>
|
||||
<head>
|
||||
<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">
|
||||
function check_styles() {
|
||||
var redText = document.getElementById('red-text');
|
||||
var blackText = document.getElementById('black-text');
|
||||
var redTextColor = window.getComputedStyle(redText, null).getPropertyValue('color');
|
||||
var blackTextColor = window.getComputedStyle(blackText, null).getPropertyValue('color');
|
||||
ok(redTextColor == 'rgb(255, 0, 0)', "The first part should be red.");
|
||||
ok(blackTextColor == 'rgb(0, 0, 0)', "The second part should still be black.");
|
||||
}
|
||||
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
window.onload = function() {
|
||||
check_styles();
|
||||
SimpleTest.finish();
|
||||
}
|
||||
</script>
|
||||
<script>
|
||||
function good_correctHashLoaded() {
|
||||
ok(true, "A stylesheet was correctly loaded when integrity matched");
|
||||
}
|
||||
function bad_correctHashBlocked() {
|
||||
ok(false, "We should load stylesheets with hashes that match!");
|
||||
}
|
||||
|
||||
function good_emptyIntegrityLoaded() {
|
||||
ok(true, "A stylesheet was correctly loaded when the integrity attribute was empty");
|
||||
}
|
||||
function bad_emptyIntegrityBlocked() {
|
||||
ok(false, "We should load stylesheets with empty integrity attributes!");
|
||||
}
|
||||
|
||||
function good_incorrectHashBlocked() {
|
||||
ok(true, "A stylesheet was correctly blocked, because the hash digest was wrong");
|
||||
}
|
||||
function bad_incorrectHashLoaded() {
|
||||
ok(false, "We should not load stylesheets with hashes that do not match the content!");
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- valid sha256 hash. should trigger onload -->
|
||||
<link rel="stylesheet" href="style1.css"
|
||||
integrity="sha256-qs8lnkunWoVldk5d5E+652yth4VTSHohlBKQvvgGwa8="
|
||||
onerror="bad_correctHashBlocked()"
|
||||
onload="good_correctHashLoaded()">
|
||||
|
||||
<!-- empty metadata. should trigger onload -->
|
||||
<link rel="stylesheet" href="style2.css"
|
||||
integrity=""
|
||||
onerror="bad_emptyIntegrityBlocked()"
|
||||
onload="good_emptyIntegrityLoaded()">
|
||||
|
||||
<!-- invalid sha256 hash. should trigger onerror -->
|
||||
<link rel="stylesheet" href="style3.css"
|
||||
integrity="sha256-bogus"
|
||||
onerror="good_incorrectHashBlocked()"
|
||||
onload="bad_incorrectHashLoaded()">
|
||||
</head>
|
||||
<body>
|
||||
<p><span id="red-text">This should be red </span> and
|
||||
<span id="black-text">this should stay black.</p>
|
||||
<p id="display"></p>
|
||||
<div id="content" style="display: none">
|
||||
</div>
|
||||
<pre id="test">
|
||||
</pre>
|
||||
</body>
|
||||
</html>
|
30
dom/security/test/sri/mochitest.ini
Normal file
30
dom/security/test/sri/mochitest.ini
Normal file
@ -0,0 +1,30 @@
|
||||
[DEFAULT]
|
||||
support-files =
|
||||
iframe_script_crossdomain.html
|
||||
iframe_script_sameorigin.html
|
||||
iframe_sri_disabled.html
|
||||
iframe_style_sameorigin.html
|
||||
script_crossdomain1.js
|
||||
script_crossdomain1.js^headers^
|
||||
script_crossdomain2.js
|
||||
script_crossdomain3.js
|
||||
script_crossdomain3.js^headers^
|
||||
script_crossdomain4.js
|
||||
script_crossdomain4.js^headers^
|
||||
script.js
|
||||
script.js^headers^
|
||||
script_302.js
|
||||
script_302.js^headers^
|
||||
script_401.js
|
||||
script_401.js^headers^
|
||||
style1.css
|
||||
style2.css
|
||||
style3.css
|
||||
|
||||
[test_script_sameorigin.html]
|
||||
|
||||
[test_script_crossdomain.html]
|
||||
|
||||
[test_sri_disabled.html]
|
||||
|
||||
[test_style_sameorigin.html]
|
1
dom/security/test/sri/script.js
Normal file
1
dom/security/test/sri/script.js
Normal file
@ -0,0 +1 @@
|
||||
var load=true;
|
1
dom/security/test/sri/script.js^headers^
Normal file
1
dom/security/test/sri/script.js^headers^
Normal file
@ -0,0 +1 @@
|
||||
Cache-control: public
|
1
dom/security/test/sri/script_302.js
Normal file
1
dom/security/test/sri/script_302.js
Normal file
@ -0,0 +1 @@
|
||||
var load=false;
|
2
dom/security/test/sri/script_302.js^headers^
Normal file
2
dom/security/test/sri/script_302.js^headers^
Normal file
@ -0,0 +1,2 @@
|
||||
HTTP 302 Found
|
||||
Location: /tests/dom/security/test/sri/script.js
|
1
dom/security/test/sri/script_401.js
Normal file
1
dom/security/test/sri/script_401.js
Normal file
@ -0,0 +1 @@
|
||||
var load=true;
|
2
dom/security/test/sri/script_401.js^headers^
Normal file
2
dom/security/test/sri/script_401.js^headers^
Normal file
@ -0,0 +1,2 @@
|
||||
HTTP 401 Authorization Required
|
||||
Cache-control: public
|
4
dom/security/test/sri/script_crossdomain1.js
Normal file
4
dom/security/test/sri/script_crossdomain1.js
Normal file
@ -0,0 +1,4 @@
|
||||
/*
|
||||
* this file should be loaded, because it has CORS enabled.
|
||||
*/
|
||||
window.hasCORSLoaded = true;
|
1
dom/security/test/sri/script_crossdomain1.js^headers^
Normal file
1
dom/security/test/sri/script_crossdomain1.js^headers^
Normal file
@ -0,0 +1 @@
|
||||
Access-Control-Allow-Origin: http://mochi.test:8888
|
5
dom/security/test/sri/script_crossdomain2.js
Normal file
5
dom/security/test/sri/script_crossdomain2.js
Normal file
@ -0,0 +1,5 @@
|
||||
/*
|
||||
* this file should not be loaded, because it does not have CORS
|
||||
* enabled.
|
||||
*/
|
||||
window.hasNonCORSLoaded = true;
|
1
dom/security/test/sri/script_crossdomain3.js
Normal file
1
dom/security/test/sri/script_crossdomain3.js
Normal file
@ -0,0 +1 @@
|
||||
// This script intentionally left blank
|
1
dom/security/test/sri/script_crossdomain3.js^headers^
Normal file
1
dom/security/test/sri/script_crossdomain3.js^headers^
Normal file
@ -0,0 +1 @@
|
||||
Access-Control-Allow-Origin: http://mochi.test:8888
|
1
dom/security/test/sri/script_crossdomain4.js
Normal file
1
dom/security/test/sri/script_crossdomain4.js
Normal file
@ -0,0 +1 @@
|
||||
// This script intentionally left blank
|
1
dom/security/test/sri/script_crossdomain4.js^headers^
Normal file
1
dom/security/test/sri/script_crossdomain4.js^headers^
Normal file
@ -0,0 +1 @@
|
||||
Access-Control-Allow-Origin: http://mochi.test:8888
|
3
dom/security/test/sri/style1.css
Normal file
3
dom/security/test/sri/style1.css
Normal file
@ -0,0 +1,3 @@
|
||||
#red-text {
|
||||
color: red;
|
||||
}
|
1
dom/security/test/sri/style2.css
Normal file
1
dom/security/test/sri/style2.css
Normal file
@ -0,0 +1 @@
|
||||
; A valid but somewhat uninteresting stylesheet
|
3
dom/security/test/sri/style3.css
Normal file
3
dom/security/test/sri/style3.css
Normal file
@ -0,0 +1,3 @@
|
||||
#black-text {
|
||||
color: green;
|
||||
}
|
18
dom/security/test/sri/test_script_crossdomain.html
Normal file
18
dom/security/test/sri/test_script_crossdomain.html
Normal file
@ -0,0 +1,18 @@
|
||||
<!DOCTYPE HTML>
|
||||
<!-- Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ -->
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Cross-domain script tests for Bug 992096</title>
|
||||
<script>
|
||||
SpecialPowers.setBoolPref("security.sri.enable", true);
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=992096">Mozilla Bug 992096</a>
|
||||
<div>
|
||||
<iframe src="iframe_script_crossdomain.html" height="100%" width="90%" frameborder="0"></iframe>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
18
dom/security/test/sri/test_script_sameorigin.html
Normal file
18
dom/security/test/sri/test_script_sameorigin.html
Normal file
@ -0,0 +1,18 @@
|
||||
<!DOCTYPE HTML>
|
||||
<!-- Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ -->
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Same-origin script tests for Bug 992096</title>
|
||||
<script>
|
||||
SpecialPowers.setBoolPref("security.sri.enable", true);
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=992096">Mozilla Bug 992096</a>
|
||||
<div>
|
||||
<iframe src="iframe_script_sameorigin.html" height="100%" width="90%" frameborder="0"></iframe>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
18
dom/security/test/sri/test_sri_disabled.html
Normal file
18
dom/security/test/sri/test_sri_disabled.html
Normal file
@ -0,0 +1,18 @@
|
||||
<!DOCTYPE HTML>
|
||||
<!-- Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ -->
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>security.sri.enable tests for Bug 992096</title>
|
||||
<script>
|
||||
SpecialPowers.setBoolPref("security.sri.enable", false);
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=992096">Mozilla Bug 992096</a>
|
||||
<div>
|
||||
<iframe src="iframe_sri_disabled.html" height="100%" width="90%" frameborder="0"></iframe>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
18
dom/security/test/sri/test_style_sameorigin.html
Normal file
18
dom/security/test/sri/test_style_sameorigin.html
Normal file
@ -0,0 +1,18 @@
|
||||
<!DOCTYPE HTML>
|
||||
<!-- Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ -->
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Same-origin stylesheet tests for Bug 992096</title>
|
||||
<script>
|
||||
SpecialPowers.setBoolPref("security.sri.enable", true);
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=992096">Mozilla Bug 992096</a>
|
||||
<div>
|
||||
<iframe src="iframe_style_sameorigin.html" height="100%" width="90%" frameborder="0"></iframe>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
@ -48,3 +48,8 @@ partial interface HTMLLinkElement {
|
||||
readonly attribute Document? import;
|
||||
};
|
||||
|
||||
// https://w3c.github.io/webappsec/specs/subresourceintegrity/#htmllinkelement-1
|
||||
partial interface HTMLLinkElement {
|
||||
[SetterThrows]
|
||||
attribute DOMString integrity;
|
||||
};
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user