Merge m-c to b2g-inbound. a=merge

CLOSED TREE
This commit is contained in:
Ryan VanderMeulen 2015-08-13 11:15:52 -04:00
commit f6cb1cdd44
839 changed files with 4849 additions and 1723 deletions

View File

@ -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

View File

@ -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]

View File

@ -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;
}),

View File

@ -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 };
});

View File

@ -4807,6 +4807,7 @@ var Utils = {
case "CORS":
case "Iframe Sandbox":
case "Tracking Protection":
case "Sub-resource Integrity":
return CATEGORY_SECURITY;
default:

View File

@ -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();
}

View File

@ -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()) {

View File

@ -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", "_");
}

View File

@ -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.
*

View 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();
}

View File

@ -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.

View File

@ -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.
*

View File

@ -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 \

View File

@ -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',

View File

@ -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} \

View File

@ -432,6 +432,7 @@ LOCAL_INCLUDES += [
'/layout/svg',
'/layout/xul',
'/netwerk/base',
'/security/manager/ssl',
'/widget',
'/xpcom/ds',
]

View File

@ -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) {

View File

@ -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

View File

@ -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;

View File

@ -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")

View File

@ -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

View File

@ -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;

View File

@ -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);

View File

@ -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

View File

@ -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,

View File

@ -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()

View File

@ -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,

View File

@ -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);

View File

@ -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".

View File

@ -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; }

View File

@ -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()) &&

View File

@ -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;

View File

@ -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());

View File

@ -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();

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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()) {

View File

@ -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

View File

@ -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()) {

View File

@ -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);

View File

@ -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;
}

View File

@ -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;

View File

@ -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()) {

View File

@ -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

View File

@ -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 {}

View File

@ -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(); }

View File

@ -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)
{

View File

@ -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(); }

View File

@ -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,

View File

@ -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

View File

@ -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);
}

View File

@ -27,6 +27,7 @@ extern PRLogModuleInfo* gMediaDecoderLog;
MediaOmxCommonReader::MediaOmxCommonReader(AbstractMediaDecoder *aDecoder)
: MediaDecoderReader(aDecoder)
, mStreamSource(nullptr)
{
if (!gMediaDecoderLog) {
gMediaDecoderLog = PR_NewLogModule("MediaDecoder");

View File

@ -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

View File

@ -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;

View File

@ -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

View File

@ -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:

View File

@ -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());

View File

@ -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();

View File

@ -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

View File

@ -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;

View File

@ -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

View File

@ -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);
}

View File

@ -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

View File

@ -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"));

View File

@ -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);

View File

@ -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);

View File

@ -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
View 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
View 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

View 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

View 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

View File

@ -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

View File

@ -16,6 +16,7 @@ MOCHITEST_MANIFESTS += [
'cors/mochitest.ini',
'csp/mochitest.ini',
'mixedcontentblocker/mochitest.ini',
'sri/mochitest.ini',
]
MOCHITEST_CHROME_MANIFESTS += [

View 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>

View 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>

View 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>

View 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>

View 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]

View File

@ -0,0 +1 @@
var load=true;

View File

@ -0,0 +1 @@
Cache-control: public

View File

@ -0,0 +1 @@
var load=false;

View File

@ -0,0 +1,2 @@
HTTP 302 Found
Location: /tests/dom/security/test/sri/script.js

View File

@ -0,0 +1 @@
var load=true;

View File

@ -0,0 +1,2 @@
HTTP 401 Authorization Required
Cache-control: public

View File

@ -0,0 +1,4 @@
/*
* this file should be loaded, because it has CORS enabled.
*/
window.hasCORSLoaded = true;

View File

@ -0,0 +1 @@
Access-Control-Allow-Origin: http://mochi.test:8888

View File

@ -0,0 +1,5 @@
/*
* this file should not be loaded, because it does not have CORS
* enabled.
*/
window.hasNonCORSLoaded = true;

View File

@ -0,0 +1 @@
// This script intentionally left blank

View File

@ -0,0 +1 @@
Access-Control-Allow-Origin: http://mochi.test:8888

View File

@ -0,0 +1 @@
// This script intentionally left blank

View File

@ -0,0 +1 @@
Access-Control-Allow-Origin: http://mochi.test:8888

View File

@ -0,0 +1,3 @@
#red-text {
color: red;
}

View File

@ -0,0 +1 @@
; A valid but somewhat uninteresting stylesheet

View File

@ -0,0 +1,3 @@
#black-text {
color: green;
}

View 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>

View 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>

View 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>

View 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>

View File

@ -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