зеркало из https://github.com/mozilla/gecko-dev.git
Merge inbound to central, a=merge
This commit is contained in:
Коммит
889e270547
|
@ -738,6 +738,13 @@ DocAccessible::AttributeWillChange(nsIDocument* aDocument,
|
|||
mStateBitWasOn = accessible->Unavailable();
|
||||
}
|
||||
|
||||
void
|
||||
DocAccessible::NativeAnonymousChildListChange(nsIDocument* aDocument,
|
||||
nsIContent* aContent,
|
||||
bool aIsRemove)
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
DocAccessible::AttributeChanged(nsIDocument* aDocument,
|
||||
dom::Element* aElement,
|
||||
|
@ -1357,6 +1364,11 @@ DocAccessible::ProcessInvalidationList()
|
|||
continue;
|
||||
}
|
||||
|
||||
if (!child->Parent()) {
|
||||
NS_ERROR("The accessible is in document but doesn't have a parent");
|
||||
continue;
|
||||
}
|
||||
|
||||
// XXX: update context flags
|
||||
{
|
||||
Accessible* oldParent = child->Parent();
|
||||
|
|
|
@ -529,7 +529,8 @@ class Automation(object):
|
|||
xrePath = None, certPath = None,
|
||||
debuggerInfo = None, symbolsPath = None,
|
||||
timeout = -1, maxTime = None, onLaunch = None,
|
||||
detectShutdownLeaks = False, screenshotOnFail=False, testPath=None, bisectChunk=None):
|
||||
detectShutdownLeaks = False, screenshotOnFail=False, testPath=None, bisectChunk=None,
|
||||
valgrindPath=None, valgrindArgs=None, valgrindSuppFiles=None):
|
||||
"""
|
||||
Run the app, log the duration it took to execute, return the status code.
|
||||
Kills the app if it runs for longer than |maxTime| seconds, or outputs nothing for |timeout| seconds.
|
||||
|
|
|
@ -191,15 +191,15 @@ public class FennecNativeActions implements Actions {
|
|||
}
|
||||
|
||||
public void sendPreferencesGetEvent(int requestId, String[] prefNames) {
|
||||
GeckoAppShell.sendEventToGecko(GeckoEvent.createPreferencesGetEvent(requestId, prefNames));
|
||||
PrefsHelper.getPrefsById(requestId, prefNames, /* observe */ false);
|
||||
}
|
||||
|
||||
public void sendPreferencesObserveEvent(int requestId, String[] prefNames) {
|
||||
GeckoAppShell.sendEventToGecko(GeckoEvent.createPreferencesObserveEvent(requestId, prefNames));
|
||||
PrefsHelper.getPrefsById(requestId, prefNames, /* observe */ true);
|
||||
}
|
||||
|
||||
public void sendPreferencesRemoveObserversEvent(int requestId) {
|
||||
GeckoAppShell.sendEventToGecko(GeckoEvent.createPreferencesRemoveObserversEvent(requestId));
|
||||
PrefsHelper.removePrefsObserver(requestId);
|
||||
}
|
||||
|
||||
class PaintExpecter implements RepeatedEventExpecter {
|
||||
|
|
|
@ -7641,7 +7641,6 @@ nsDocShell::EndPageLoad(nsIWebProgress* aProgress,
|
|||
aStatus == NS_ERROR_UNWANTED_URI ||
|
||||
aStatus == NS_ERROR_UNSAFE_CONTENT_TYPE ||
|
||||
aStatus == NS_ERROR_REMOTE_XUL ||
|
||||
aStatus == NS_ERROR_OFFLINE ||
|
||||
aStatus == NS_ERROR_INTERCEPTION_FAILED ||
|
||||
aStatus == NS_ERROR_OPAQUE_INTERCEPTION_DISABLED ||
|
||||
aStatus == NS_ERROR_BAD_OPAQUE_INTERCEPTION_REQUEST_MODE ||
|
||||
|
|
|
@ -332,6 +332,13 @@ nsSHEntryShared::AttributeWillChange(nsIDocument* aDocument,
|
|||
{
|
||||
}
|
||||
|
||||
void
|
||||
nsSHEntryShared::NativeAnonymousChildListChange(nsIDocument* aDocument,
|
||||
nsIContent* aContent,
|
||||
bool aIsRemove)
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
nsSHEntryShared::AttributeChanged(nsIDocument* aDocument,
|
||||
dom::Element* aElement,
|
||||
|
|
|
@ -1631,6 +1631,9 @@ Element::BindToTree(nsIDocument* aDocument, nsIContent* aParent,
|
|||
}
|
||||
|
||||
nsNodeUtils::ParentChainChanged(this);
|
||||
if (!hadParent && IsRootOfNativeAnonymousSubtree()) {
|
||||
nsNodeUtils::NativeAnonymousChildListChange(this, false);
|
||||
}
|
||||
|
||||
if (HasID()) {
|
||||
AddToIdTable(DoGetID());
|
||||
|
@ -1745,6 +1748,10 @@ Element::UnbindFromTree(bool aDeep, bool aNullParent)
|
|||
}
|
||||
}
|
||||
|
||||
if (this->IsRootOfNativeAnonymousSubtree()) {
|
||||
nsNodeUtils::NativeAnonymousChildListChange(this, true);
|
||||
}
|
||||
|
||||
if (GetParent()) {
|
||||
nsRefPtr<nsINode> p;
|
||||
p.swap(mParent);
|
||||
|
|
|
@ -114,6 +114,38 @@ nsMutationReceiver::Disconnect(bool aRemoveFromObserver)
|
|||
}
|
||||
}
|
||||
|
||||
void
|
||||
nsMutationReceiver::NativeAnonymousChildListChange(nsIDocument* aDocument,
|
||||
nsIContent* aContent,
|
||||
bool aIsRemove) {
|
||||
if (!NativeAnonymousChildList()) {
|
||||
return;
|
||||
}
|
||||
|
||||
nsINode* parent = aContent->GetParentNode();
|
||||
if (!parent ||
|
||||
(!Subtree() && Target() != parent) ||
|
||||
(Subtree() && RegisterTarget()->SubtreeRoot() != parent->SubtreeRoot())) {
|
||||
return;
|
||||
}
|
||||
|
||||
nsDOMMutationRecord* m =
|
||||
Observer()->CurrentRecord(nsGkAtoms::nativeAnonymousChildList);
|
||||
|
||||
if (m->mTarget) {
|
||||
return;
|
||||
}
|
||||
m->mTarget = parent;
|
||||
|
||||
if (aIsRemove) {
|
||||
m->mRemovedNodes = new nsSimpleContentList(parent);
|
||||
m->mRemovedNodes->AppendElement(aContent);
|
||||
} else {
|
||||
m->mAddedNodes = new nsSimpleContentList(parent);
|
||||
m->mAddedNodes->AppendElement(aContent);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
nsMutationReceiver::AttributeWillChange(nsIDocument* aDocument,
|
||||
mozilla::dom::Element* aElement,
|
||||
|
@ -586,6 +618,8 @@ nsDOMMutationObserver::Observe(nsINode& aTarget,
|
|||
bool attributeOldValue =
|
||||
aOptions.mAttributeOldValue.WasPassed() &&
|
||||
aOptions.mAttributeOldValue.Value();
|
||||
bool nativeAnonymousChildList = aOptions.mNativeAnonymousChildList &&
|
||||
nsContentUtils::ThreadsafeIsCallerChrome();
|
||||
bool characterDataOldValue =
|
||||
aOptions.mCharacterDataOldValue.WasPassed() &&
|
||||
aOptions.mCharacterDataOldValue.Value();
|
||||
|
@ -605,7 +639,8 @@ nsDOMMutationObserver::Observe(nsINode& aTarget,
|
|||
characterData = true;
|
||||
}
|
||||
|
||||
if (!(childList || attributes || characterData || animations)) {
|
||||
if (!(childList || attributes || characterData || animations ||
|
||||
nativeAnonymousChildList)) {
|
||||
aRv.Throw(NS_ERROR_DOM_TYPE_ERR);
|
||||
return;
|
||||
}
|
||||
|
@ -655,6 +690,7 @@ nsDOMMutationObserver::Observe(nsINode& aTarget,
|
|||
r->SetSubtree(subtree);
|
||||
r->SetAttributeOldValue(attributeOldValue);
|
||||
r->SetCharacterDataOldValue(characterDataOldValue);
|
||||
r->SetNativeAnonymousChildList(nativeAnonymousChildList);
|
||||
r->SetAttributeFilter(filters);
|
||||
r->SetAllAttributes(allAttrs);
|
||||
r->SetAnimations(animations);
|
||||
|
@ -715,6 +751,7 @@ nsDOMMutationObserver::GetObservingInfo(
|
|||
info.mSubtree = mr->Subtree();
|
||||
info.mAttributeOldValue.Construct(mr->AttributeOldValue());
|
||||
info.mCharacterDataOldValue.Construct(mr->CharacterDataOldValue());
|
||||
info.mNativeAnonymousChildList = mr->NativeAnonymousChildList();
|
||||
info.mAnimations.Construct(mr->Animations());
|
||||
nsCOMArray<nsIAtom>& filters = mr->AttributeFilter();
|
||||
if (filters.Count()) {
|
||||
|
|
|
@ -172,6 +172,16 @@ public:
|
|||
mCharacterDataOldValue = aOldValue;
|
||||
}
|
||||
|
||||
bool NativeAnonymousChildList()
|
||||
{
|
||||
return mParent ? mParent->NativeAnonymousChildList() : mNativeAnonymousChildList;
|
||||
}
|
||||
void SetNativeAnonymousChildList(bool aOldValue)
|
||||
{
|
||||
NS_ASSERTION(!mParent, "Shouldn't have parent");
|
||||
mNativeAnonymousChildList = aOldValue;
|
||||
}
|
||||
|
||||
bool Attributes() { return mParent ? mParent->Attributes() : mAttributes; }
|
||||
void SetAttributes(bool aAttributes)
|
||||
{
|
||||
|
@ -298,6 +308,7 @@ private:
|
|||
bool mChildList;
|
||||
bool mCharacterData;
|
||||
bool mCharacterDataOldValue;
|
||||
bool mNativeAnonymousChildList;
|
||||
bool mAttributes;
|
||||
bool mAllAttributes;
|
||||
bool mAttributeOldValue;
|
||||
|
@ -362,6 +373,7 @@ public:
|
|||
NS_DECL_ISUPPORTS
|
||||
|
||||
NS_DECL_NSIMUTATIONOBSERVER_ATTRIBUTEWILLCHANGE
|
||||
NS_DECL_NSIMUTATIONOBSERVER_NATIVEANONYMOUSCHILDLISTCHANGE
|
||||
NS_DECL_NSIMUTATIONOBSERVER_CHARACTERDATAWILLCHANGE
|
||||
NS_DECL_NSIMUTATIONOBSERVER_CONTENTAPPENDED
|
||||
NS_DECL_NSIMUTATIONOBSERVER_CONTENTINSERTED
|
||||
|
|
|
@ -514,6 +514,8 @@ nsGenericDOMDataNode::BindToTree(nsIDocument* aDocument, nsIContent* aParent,
|
|||
}
|
||||
}
|
||||
|
||||
bool hadParent = !!GetParentNode();
|
||||
|
||||
// Set parent
|
||||
if (aParent) {
|
||||
if (!GetParent()) {
|
||||
|
@ -548,6 +550,9 @@ nsGenericDOMDataNode::BindToTree(nsIDocument* aDocument, nsIContent* aParent,
|
|||
}
|
||||
|
||||
nsNodeUtils::ParentChainChanged(this);
|
||||
if (!hadParent && IsRootOfNativeAnonymousSubtree()) {
|
||||
nsNodeUtils::NativeAnonymousChildListChange(this, false);
|
||||
}
|
||||
|
||||
UpdateEditableState(false);
|
||||
|
||||
|
@ -570,6 +575,9 @@ nsGenericDOMDataNode::UnbindFromTree(bool aDeep, bool aNullParent)
|
|||
HasFlag(NODE_FORCE_XBL_BINDINGS) ? OwnerDoc() : GetComposedDoc();
|
||||
|
||||
if (aNullParent) {
|
||||
if (this->IsRootOfNativeAnonymousSubtree()) {
|
||||
nsNodeUtils::NativeAnonymousChildListChange(this, true);
|
||||
}
|
||||
if (GetParent()) {
|
||||
NS_RELEASE(mParent);
|
||||
} else {
|
||||
|
|
|
@ -633,6 +633,7 @@ GK_ATOM(_namespace, "namespace")
|
|||
GK_ATOM(namespaceAlias, "namespace-alias")
|
||||
GK_ATOM(namespaceUri, "namespace-uri")
|
||||
GK_ATOM(NaN, "NaN")
|
||||
GK_ATOM(nativeAnonymousChildList, "nativeAnonymousChildList")
|
||||
GK_ATOM(nav, "nav")
|
||||
GK_ATOM(negate, "negate")
|
||||
GK_ATOM(never, "never")
|
||||
|
|
|
@ -22,8 +22,8 @@ class Element;
|
|||
} // namespace mozilla
|
||||
|
||||
#define NS_IMUTATION_OBSERVER_IID \
|
||||
{ 0xdd74f0cc, 0x2849, 0x4d05, \
|
||||
{ 0x9c, 0xe3, 0xb0, 0x95, 0x3e, 0xc2, 0xfd, 0x44 } }
|
||||
{ 0x6d674c17, 0x0fbc, 0x4633, \
|
||||
{ 0x8f, 0x46, 0x73, 0x4e, 0x87, 0xeb, 0xf0, 0xc7 } }
|
||||
|
||||
/**
|
||||
* Information details about a characterdata change. Basically, we
|
||||
|
@ -200,6 +200,18 @@ public:
|
|||
int32_t aModType,
|
||||
const nsAttrValue* aOldValue) = 0;
|
||||
|
||||
/**
|
||||
* Notification that the root of a native anonymous has been added
|
||||
* or removed.
|
||||
*
|
||||
* @param aDocument Owner doc of aContent
|
||||
* @param aContent Anonymous node that's been added or removed
|
||||
* @param aIsRemove True if it's a removal, false if an addition
|
||||
*/
|
||||
virtual void NativeAnonymousChildListChange(nsIDocument* aDocument,
|
||||
nsIContent* aContent,
|
||||
bool aIsRemove) {}
|
||||
|
||||
/**
|
||||
* Notification that an attribute of an element has been
|
||||
* set to the value it already had.
|
||||
|
@ -346,6 +358,11 @@ NS_DEFINE_STATIC_IID_ACCESSOR(nsIMutationObserver, NS_IMUTATION_OBSERVER_IID)
|
|||
int32_t aModType, \
|
||||
const nsAttrValue* aNewValue) override;
|
||||
|
||||
#define NS_DECL_NSIMUTATIONOBSERVER_NATIVEANONYMOUSCHILDLISTCHANGE \
|
||||
virtual void NativeAnonymousChildListChange(nsIDocument* aDocument, \
|
||||
nsIContent* aContent, \
|
||||
bool aIsRemove) override;
|
||||
|
||||
#define NS_DECL_NSIMUTATIONOBSERVER_ATTRIBUTECHANGED \
|
||||
virtual void AttributeChanged(nsIDocument* aDocument, \
|
||||
mozilla::dom::Element* aElement, \
|
||||
|
@ -383,6 +400,7 @@ NS_DEFINE_STATIC_IID_ACCESSOR(nsIMutationObserver, NS_IMUTATION_OBSERVER_IID)
|
|||
NS_DECL_NSIMUTATIONOBSERVER_CHARACTERDATAWILLCHANGE \
|
||||
NS_DECL_NSIMUTATIONOBSERVER_CHARACTERDATACHANGED \
|
||||
NS_DECL_NSIMUTATIONOBSERVER_ATTRIBUTEWILLCHANGE \
|
||||
NS_DECL_NSIMUTATIONOBSERVER_NATIVEANONYMOUSCHILDLISTCHANGE \
|
||||
NS_DECL_NSIMUTATIONOBSERVER_ATTRIBUTECHANGED \
|
||||
NS_DECL_NSIMUTATIONOBSERVER_CONTENTAPPENDED \
|
||||
NS_DECL_NSIMUTATIONOBSERVER_CONTENTINSERTED \
|
||||
|
@ -419,6 +437,12 @@ _class::AttributeWillChange(nsIDocument* aDocument, \
|
|||
{ \
|
||||
} \
|
||||
void \
|
||||
_class::NativeAnonymousChildListChange(nsIDocument* aDocument, \
|
||||
nsIContent* aContent, \
|
||||
bool aIsRemove) \
|
||||
{ \
|
||||
} \
|
||||
void \
|
||||
_class::AttributeChanged(nsIDocument* aDocument, \
|
||||
mozilla::dom::Element* aElement, \
|
||||
int32_t aNameSpaceID, \
|
||||
|
|
|
@ -167,6 +167,15 @@ nsNodeUtils::ContentAppended(nsIContent* aContainer,
|
|||
aNewIndexInContainer));
|
||||
}
|
||||
|
||||
void
|
||||
nsNodeUtils::NativeAnonymousChildListChange(nsIContent* aContent,
|
||||
bool aIsRemove)
|
||||
{
|
||||
nsIDocument* doc = aContent->OwnerDoc();
|
||||
IMPL_MUTATION_NOTIFICATION(NativeAnonymousChildListChange, aContent,
|
||||
(doc, aContent, aIsRemove));
|
||||
}
|
||||
|
||||
void
|
||||
nsNodeUtils::ContentInserted(nsINode* aContainer,
|
||||
nsIContent* aChild,
|
||||
|
|
|
@ -96,6 +96,15 @@ public:
|
|||
nsIContent* aFirstNewContent,
|
||||
int32_t aNewIndexInContainer);
|
||||
|
||||
/**
|
||||
* Send NativeAnonymousChildList notifications to nsIMutationObservers
|
||||
* @param aContent Anonymous node that's been added or removed
|
||||
* @param aIsRemove True if it's a removal, false if an addition
|
||||
* @see nsIMutationObserver::NativeAnonymousChildListChange
|
||||
*/
|
||||
static void NativeAnonymousChildListChange(nsIContent* aContent,
|
||||
bool aIsRemove);
|
||||
|
||||
/**
|
||||
* Send ContentInserted notifications to nsIMutationObservers
|
||||
* @param aContainer Node into which new child was inserted
|
||||
|
|
|
@ -65,6 +65,7 @@ skip-if = buildapp == 'mulet'
|
|||
[test_cpows.xul]
|
||||
skip-if = buildapp == 'mulet'
|
||||
[test_document_register.xul]
|
||||
[test_mutationobserver_anonymous.html]
|
||||
[test_registerElement_content.xul]
|
||||
[test_registerElement_ep.xul]
|
||||
[test_domparsing.xul]
|
||||
|
|
|
@ -0,0 +1,251 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<!--
|
||||
https://bugzilla.mozilla.org/show_bug.cgi?id=1034110
|
||||
-->
|
||||
<head>
|
||||
<title>Test for Bug 1034110</title>
|
||||
<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
|
||||
<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/ChromeUtils.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/>
|
||||
</head>
|
||||
<body>
|
||||
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1034110">Mozilla Bug 1034110</a>
|
||||
<style type="text/css">
|
||||
#pseudo.before::before { content: "before"; }
|
||||
#pseudo.after::after { content: "after"; }
|
||||
</style>
|
||||
<div id="pseudo"></div>
|
||||
<video id="video"></video>
|
||||
<p id="display"></p>
|
||||
<div id="content" style="display: none">
|
||||
|
||||
</div>
|
||||
|
||||
<pre id="test">
|
||||
<script type="application/javascript;version=1.7">
|
||||
|
||||
/** Test for Bug 1034110 **/
|
||||
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
const {Cc, Ci, Cu} = SpecialPowers;
|
||||
|
||||
function getWalker(node) {
|
||||
let walker = Cc["@mozilla.org/inspector/deep-tree-walker;1"].createInstance(Ci.inIDeepTreeWalker);
|
||||
walker.showAnonymousContent = true;
|
||||
walker.init(node.ownerDocument, Ci.nsIDOMNodeFilter.SHOW_ALL);
|
||||
walker.currentNode = node;
|
||||
return walker;
|
||||
}
|
||||
|
||||
function getFirstChild(parent) {
|
||||
return SpecialPowers.unwrap(getWalker(parent).firstChild());
|
||||
}
|
||||
|
||||
function getLastChild(parent) {
|
||||
return SpecialPowers.unwrap(getWalker(parent).lastChild());
|
||||
}
|
||||
|
||||
function assertSamePseudoElement(which, node1, node2) {
|
||||
is(node1.nodeName, "_moz_generated_content_" + which,
|
||||
"Correct pseudo element type");
|
||||
is(node1, node2,
|
||||
"Referencing the same ::after element");
|
||||
}
|
||||
|
||||
window.onload = function () {
|
||||
testOneAdded();
|
||||
};
|
||||
|
||||
function testOneAdded() {
|
||||
let parent = document.getElementById("pseudo");
|
||||
var m = new MutationObserver(function(records, observer) {
|
||||
is(records.length, 1, "Correct number of records");
|
||||
is(records[0].type, "nativeAnonymousChildList", "Correct record type");
|
||||
is(records[0].target, parent, "Correct target");
|
||||
|
||||
is(records[0].addedNodes.length, 1, "Should have got addedNodes");
|
||||
assertSamePseudoElement("before", records[0].addedNodes[0], getFirstChild(parent));
|
||||
is(records[0].removedNodes.length, 0, "Shouldn't have got removedNodes");
|
||||
|
||||
observer.disconnect();
|
||||
testAddedAndRemoved();
|
||||
});
|
||||
|
||||
m.observe(parent, { nativeAnonymousChildList: true});
|
||||
parent.className = "before";
|
||||
}
|
||||
|
||||
function testAddedAndRemoved() {
|
||||
let parent = document.getElementById("pseudo");
|
||||
let originalBeforeElement = getFirstChild(parent);
|
||||
var m = new MutationObserver(function(records, observer) {
|
||||
is(records.length, 2, "Correct number of records");
|
||||
is(records[0].type, "nativeAnonymousChildList", "Correct record type (1)");
|
||||
is(records[1].type, "nativeAnonymousChildList", "Correct record type (2)");
|
||||
is(records[0].target, parent, "Correct target (1)");
|
||||
is(records[1].target, parent, "Correct target (2)");
|
||||
|
||||
// Two records are sent - one for removed and one for added.
|
||||
is(records[0].addedNodes.length, 0, "Shouldn't have got addedNodes");
|
||||
is(records[0].removedNodes.length, 1, "Should have got removedNodes");
|
||||
assertSamePseudoElement("before", records[0].removedNodes[0], originalBeforeElement);
|
||||
|
||||
is(records[1].addedNodes.length, 1, "Should have got addedNodes");
|
||||
assertSamePseudoElement("after", records[1].addedNodes[0], getLastChild(parent));
|
||||
is(records[1].removedNodes.length, 0, "Shouldn't have got removedNodes");
|
||||
|
||||
observer.disconnect();
|
||||
testRemoved();
|
||||
});
|
||||
|
||||
m.observe(parent, { nativeAnonymousChildList: true});
|
||||
parent.className = "after";
|
||||
}
|
||||
|
||||
function testRemoved() {
|
||||
let parent = document.getElementById("pseudo");
|
||||
let originalAfterElement = getLastChild(parent);
|
||||
var m = new MutationObserver(function(records, observer) {
|
||||
is(records.length, 1, "Correct number of records");
|
||||
is(records[0].type, "nativeAnonymousChildList", "Correct record type");
|
||||
is(records[0].target, parent, "Correct target");
|
||||
|
||||
is(records[0].addedNodes.length, 0, "Shouldn't have got addedNodes");
|
||||
is(records[0].removedNodes.length, 1, "Should have got removedNodes");
|
||||
assertSamePseudoElement("after", records[0].removedNodes[0], originalAfterElement);
|
||||
|
||||
observer.disconnect();
|
||||
testMultipleAdded();
|
||||
});
|
||||
|
||||
m.observe(parent, { nativeAnonymousChildList: true });
|
||||
parent.className = "";
|
||||
}
|
||||
|
||||
function testMultipleAdded() {
|
||||
let parent = document.getElementById("pseudo");
|
||||
var m = new MutationObserver(function(records, observer) {
|
||||
is(records.length, 2, "Correct number of records");
|
||||
is(records[0].type, "nativeAnonymousChildList", "Correct record type (1)");
|
||||
is(records[1].type, "nativeAnonymousChildList", "Correct record type (2)");
|
||||
is(records[0].target, parent, "Correct target (1)");
|
||||
is(records[1].target, parent, "Correct target (2)");
|
||||
|
||||
is(records[0].addedNodes.length, 1, "Should have got addedNodes");
|
||||
assertSamePseudoElement("before", records[0].addedNodes[0], getFirstChild(parent));
|
||||
is(records[0].removedNodes.length, 0, "Shouldn't have got removedNodes");
|
||||
|
||||
is(records[1].addedNodes.length, 1, "Should have got addedNodes");
|
||||
assertSamePseudoElement("after", records[1].addedNodes[0], getLastChild(parent));
|
||||
is(records[1].removedNodes.length, 0, "Shouldn't have got removedNodes");
|
||||
|
||||
observer.disconnect();
|
||||
testRemovedDueToDisplay();
|
||||
});
|
||||
|
||||
m.observe(parent, { nativeAnonymousChildList: true });
|
||||
parent.className = "before after";
|
||||
}
|
||||
|
||||
function testRemovedDueToDisplay() {
|
||||
let parent = document.getElementById("pseudo");
|
||||
let originalBeforeElement = getFirstChild(parent);
|
||||
let originalAfterElement = getLastChild(parent);
|
||||
var m = new MutationObserver(function(records, observer) {
|
||||
is(records.length, 2, "Correct number of records");
|
||||
is(records[0].type, "nativeAnonymousChildList", "Correct record type (1)");
|
||||
is(records[1].type, "nativeAnonymousChildList", "Correct record type (2)");
|
||||
is(records[0].target, parent, "Correct target (1)");
|
||||
is(records[1].target, parent, "Correct target (2)");
|
||||
|
||||
is(records[0].addedNodes.length, 0, "Shouldn't have got addedNodes");
|
||||
is(records[0].removedNodes.length, 1, "Should have got removedNodes");
|
||||
assertSamePseudoElement("before", records[0].removedNodes[0], originalBeforeElement);
|
||||
|
||||
is(records[1].addedNodes.length, 0, "Shouldn't have got addedNodes");
|
||||
is(records[1].removedNodes.length, 1, "Should have got removedNodes");
|
||||
assertSamePseudoElement("after", records[1].removedNodes[0], originalAfterElement);
|
||||
|
||||
observer.disconnect();
|
||||
testAddedDueToDisplay();
|
||||
});
|
||||
|
||||
m.observe(parent, { nativeAnonymousChildList: true });
|
||||
parent.style.display = "none";
|
||||
}
|
||||
|
||||
function testAddedDueToDisplay() {
|
||||
let parent = document.getElementById("pseudo");
|
||||
var m = new MutationObserver(function(records, observer) {
|
||||
is(records.length, 2, "Correct number of records");
|
||||
is(records[0].type, "nativeAnonymousChildList", "Correct record type (1)");
|
||||
is(records[1].type, "nativeAnonymousChildList", "Correct record type (2)");
|
||||
is(records[0].target, parent, "Correct target (1)");
|
||||
is(records[1].target, parent, "Correct target (2)");
|
||||
|
||||
is(records[0].addedNodes.length, 1, "Should have got addedNodes");
|
||||
assertSamePseudoElement("before", records[0].addedNodes[0], getFirstChild(parent));
|
||||
is(records[0].removedNodes.length, 0, "Shouldn't have got removedNodes");
|
||||
|
||||
is(records[1].addedNodes.length, 1, "Should have got addedNodes");
|
||||
assertSamePseudoElement("after", records[1].addedNodes[0], getLastChild(parent));
|
||||
is(records[1].removedNodes.length, 0, "Shouldn't have got removedNodes");
|
||||
|
||||
observer.disconnect();
|
||||
testDifferentTargetNoSubtree();
|
||||
});
|
||||
|
||||
m.observe(parent, { nativeAnonymousChildList: true });
|
||||
parent.style.display = "block";
|
||||
}
|
||||
|
||||
function testDifferentTargetNoSubtree() {
|
||||
let parent = document.getElementById("pseudo");
|
||||
var m = new MutationObserver(function(records, observer) {
|
||||
ok(false,
|
||||
"No mutation should fire when observing on a parent without subtree option.");
|
||||
});
|
||||
m.observe(document, { nativeAnonymousChildList: true });
|
||||
parent.style.display = "none";
|
||||
|
||||
// Wait for the actual mutation to come through, making sure that
|
||||
// the original observer never fires.
|
||||
var m2 = new MutationObserver(function(records, observer) {
|
||||
ok(!getFirstChild(parent), "Pseudo element has been removed, but no mutation");
|
||||
ok(!getLastChild(parent), "Pseudo element has been removed, but no mutation");
|
||||
observer.disconnect();
|
||||
testSubtree();
|
||||
});
|
||||
m2.observe(parent, { nativeAnonymousChildList: true });
|
||||
}
|
||||
|
||||
function testSubtree() {
|
||||
let parent = document.getElementById("pseudo");
|
||||
var m = new MutationObserver(function(records, observer) {
|
||||
is(records.length, 2, "Correct number of records");
|
||||
is(records[0].type, "nativeAnonymousChildList", "Correct record type (1)");
|
||||
is(records[1].type, "nativeAnonymousChildList", "Correct record type (2)");
|
||||
is(records[0].target, parent, "Correct target (1)");
|
||||
is(records[1].target, parent, "Correct target (2)");
|
||||
|
||||
is(records[0].addedNodes.length, 1, "Should have got addedNodes");
|
||||
assertSamePseudoElement("before", records[0].addedNodes[0], getFirstChild(parent));
|
||||
is(records[0].removedNodes.length, 0, "Shouldn't have got removedNodes");
|
||||
|
||||
is(records[1].addedNodes.length, 1, "Should have got addedNodes");
|
||||
assertSamePseudoElement("after", records[1].addedNodes[0], getLastChild(parent));
|
||||
is(records[1].removedNodes.length, 0, "Shouldn't have got removedNodes");
|
||||
|
||||
observer.disconnect();
|
||||
SimpleTest.finish();
|
||||
});
|
||||
m.observe(document, { nativeAnonymousChildList: true, subtree: true });
|
||||
parent.style.display = "block";
|
||||
}
|
||||
|
||||
</script>
|
||||
</pre>
|
||||
</body>
|
||||
</html>
|
|
@ -909,6 +909,19 @@ function testAttributeRecordMerging4() {
|
|||
m.disconnect();
|
||||
div.innerHTML = "";
|
||||
div.removeAttribute("foo");
|
||||
then(testChromeOnly);
|
||||
}
|
||||
|
||||
function testChromeOnly() {
|
||||
// Content can't access nativeAnonymousChildList
|
||||
try {
|
||||
var mo = new M(function(records, observer) { });
|
||||
mo.observe(div, { nativeAnonymousChildList: true });
|
||||
ok(false, "Should have thrown when trying to observe with chrome-only init");
|
||||
} catch (e) {
|
||||
ok(true, "Throws when trying to observe with chrome-only init");
|
||||
}
|
||||
|
||||
then();
|
||||
}
|
||||
|
||||
|
|
|
@ -409,7 +409,7 @@ public:
|
|||
NS_IMETHOD GetItemId(nsAString& aId) final override {
|
||||
nsString id;
|
||||
GetItemId(id);
|
||||
aId.Assign(aId);
|
||||
aId.Assign(id);
|
||||
return NS_OK;
|
||||
}
|
||||
NS_IMETHOD SetItemId(const nsAString& aId) final override {
|
||||
|
|
|
@ -169,6 +169,10 @@ ScreenManagerParent::RecvScreenForBrowser(const TabId& aTabId,
|
|||
bool
|
||||
ScreenManagerParent::ExtractScreenDetails(nsIScreen* aScreen, ScreenDetails &aDetails)
|
||||
{
|
||||
if (!aScreen) {
|
||||
return false;
|
||||
}
|
||||
|
||||
uint32_t id;
|
||||
nsresult rv = aScreen->GetId(&id);
|
||||
NS_ENSURE_SUCCESS(rv, false);
|
||||
|
|
|
@ -9,11 +9,11 @@
|
|||
#include <stdint.h>
|
||||
|
||||
#include "MP4Demuxer.h"
|
||||
#include "mp4_demuxer/Index.h"
|
||||
#include "mp4_demuxer/MoofParser.h"
|
||||
#include "mp4_demuxer/MP4Metadata.h"
|
||||
#include "mp4_demuxer/ResourceStream.h"
|
||||
#include "mp4_demuxer/BufferStream.h"
|
||||
#include "mp4_demuxer/Index.h"
|
||||
|
||||
// Used for telemetry
|
||||
#include "mozilla/Telemetry.h"
|
||||
|
@ -30,6 +30,51 @@ PRLogModuleInfo* GetDemuxerLog() {
|
|||
|
||||
namespace mozilla {
|
||||
|
||||
class MP4TrackDemuxer : public MediaTrackDemuxer
|
||||
{
|
||||
public:
|
||||
MP4TrackDemuxer(MP4Demuxer* aParent,
|
||||
UniquePtr<TrackInfo>&& aInfo,
|
||||
const nsTArray<mp4_demuxer::Index::Indice>& indices);
|
||||
|
||||
virtual UniquePtr<TrackInfo> GetInfo() const override;
|
||||
|
||||
virtual nsRefPtr<SeekPromise> Seek(media::TimeUnit aTime) override;
|
||||
|
||||
virtual nsRefPtr<SamplesPromise> GetSamples(int32_t aNumSamples = 1) override;
|
||||
|
||||
virtual void Reset() override;
|
||||
|
||||
virtual nsresult GetNextRandomAccessPoint(media::TimeUnit* aTime) override;
|
||||
|
||||
nsRefPtr<SkipAccessPointPromise> SkipToNextRandomAccessPoint(media::TimeUnit aTimeThreshold) override;
|
||||
|
||||
virtual media::TimeIntervals GetBuffered() override;
|
||||
|
||||
virtual void BreakCycles() override;
|
||||
|
||||
private:
|
||||
friend class MP4Demuxer;
|
||||
void NotifyDataArrived();
|
||||
void UpdateSamples(nsTArray<nsRefPtr<MediaRawData>>& aSamples);
|
||||
void EnsureUpToDateIndex();
|
||||
void SetNextKeyFrameTime();
|
||||
nsRefPtr<MP4Demuxer> mParent;
|
||||
nsRefPtr<mp4_demuxer::ResourceStream> mStream;
|
||||
UniquePtr<TrackInfo> mInfo;
|
||||
// We do not actually need a monitor, however MoofParser (in mIndex) will
|
||||
// assert if a monitor isn't held.
|
||||
Monitor mMonitor;
|
||||
nsRefPtr<mp4_demuxer::Index> mIndex;
|
||||
UniquePtr<mp4_demuxer::SampleIterator> mIterator;
|
||||
Maybe<media::TimeUnit> mNextKeyframeTime;
|
||||
// Queued samples extracted by the demuxer, but not yet returned.
|
||||
nsRefPtr<MediaRawData> mQueuedSample;
|
||||
bool mNeedReIndex;
|
||||
bool mNeedSPSForTelemetry;
|
||||
};
|
||||
|
||||
|
||||
// Returns true if no SPS was found and search for it should continue.
|
||||
bool
|
||||
AccumulateSPSTelemetry(const MediaByteBuffer* aExtradata)
|
||||
|
@ -120,8 +165,15 @@ MP4Demuxer::GetTrackDemuxer(TrackInfo::TrackType aType, uint32_t aTrackNumber)
|
|||
if (mMetadata->GetNumberTracks(aType) <= aTrackNumber) {
|
||||
return nullptr;
|
||||
}
|
||||
nsRefPtr<MP4TrackDemuxer> e =
|
||||
new MP4TrackDemuxer(this, aType, aTrackNumber);
|
||||
UniquePtr<TrackInfo> info = mMetadata->GetTrackInfo(aType, aTrackNumber);
|
||||
if (!info) {
|
||||
return nullptr;
|
||||
}
|
||||
FallibleTArray<mp4_demuxer::Index::Indice> indices;
|
||||
if (!mMetadata->ReadTrackIndex(indices, info->mTrackId)) {
|
||||
return nullptr;
|
||||
}
|
||||
nsRefPtr<MP4TrackDemuxer> e = new MP4TrackDemuxer(this, Move(info), indices);
|
||||
mDemuxers.AppendElement(e);
|
||||
|
||||
return e.forget();
|
||||
|
@ -174,27 +226,20 @@ MP4Demuxer::GetCrypto()
|
|||
}
|
||||
|
||||
MP4TrackDemuxer::MP4TrackDemuxer(MP4Demuxer* aParent,
|
||||
TrackInfo::TrackType aType,
|
||||
uint32_t aTrackNumber)
|
||||
UniquePtr<TrackInfo>&& aInfo,
|
||||
const nsTArray<mp4_demuxer::Index::Indice>& indices)
|
||||
: mParent(aParent)
|
||||
, mStream(new mp4_demuxer::ResourceStream(mParent->mResource))
|
||||
, mNeedReIndex(true)
|
||||
, mInfo(Move(aInfo))
|
||||
, mMonitor("MP4TrackDemuxer")
|
||||
{
|
||||
mInfo = mParent->mMetadata->GetTrackInfo(aType, aTrackNumber);
|
||||
|
||||
MOZ_ASSERT(mInfo);
|
||||
|
||||
FallibleTArray<mp4_demuxer::Index::Indice> indices;
|
||||
if (!mParent->mMetadata->ReadTrackIndex(indices, mInfo->mTrackId)) {
|
||||
MOZ_ASSERT(false);
|
||||
}
|
||||
mIndex = new mp4_demuxer::Index(indices,
|
||||
, mIndex(new mp4_demuxer::Index(indices,
|
||||
mStream,
|
||||
mInfo->mTrackId,
|
||||
mInfo->IsAudio(),
|
||||
&mMonitor);
|
||||
mIterator = MakeUnique<mp4_demuxer::SampleIterator>(mIndex);
|
||||
&mMonitor))
|
||||
, mIterator(MakeUnique<mp4_demuxer::SampleIterator>(mIndex))
|
||||
, mNeedReIndex(true)
|
||||
{
|
||||
EnsureUpToDateIndex(); // Force update of index
|
||||
|
||||
// Collect telemetry from h264 AVCC SPS.
|
||||
|
|
|
@ -13,7 +13,6 @@
|
|||
#include "MediaResource.h"
|
||||
|
||||
namespace mp4_demuxer {
|
||||
class Index;
|
||||
class MP4Metadata;
|
||||
class ResourceStream;
|
||||
class SampleIterator;
|
||||
|
@ -54,51 +53,6 @@ private:
|
|||
nsTArray<nsRefPtr<MP4TrackDemuxer>> mDemuxers;
|
||||
};
|
||||
|
||||
class MP4TrackDemuxer : public MediaTrackDemuxer
|
||||
{
|
||||
public:
|
||||
MP4TrackDemuxer(MP4Demuxer* aParent,
|
||||
TrackInfo::TrackType aType,
|
||||
uint32_t aTrackNumber);
|
||||
|
||||
virtual UniquePtr<TrackInfo> GetInfo() const override;
|
||||
|
||||
virtual nsRefPtr<SeekPromise> Seek(media::TimeUnit aTime) override;
|
||||
|
||||
virtual nsRefPtr<SamplesPromise> GetSamples(int32_t aNumSamples = 1) override;
|
||||
|
||||
virtual void Reset() override;
|
||||
|
||||
virtual nsresult GetNextRandomAccessPoint(media::TimeUnit* aTime) override;
|
||||
|
||||
nsRefPtr<SkipAccessPointPromise> SkipToNextRandomAccessPoint(media::TimeUnit aTimeThreshold) override;
|
||||
|
||||
virtual media::TimeIntervals GetBuffered() override;
|
||||
|
||||
virtual void BreakCycles() override;
|
||||
|
||||
private:
|
||||
friend class MP4Demuxer;
|
||||
void NotifyDataArrived();
|
||||
void UpdateSamples(nsTArray<nsRefPtr<MediaRawData>>& aSamples);
|
||||
void EnsureUpToDateIndex();
|
||||
void SetNextKeyFrameTime();
|
||||
nsRefPtr<MP4Demuxer> mParent;
|
||||
nsRefPtr<mp4_demuxer::Index> mIndex;
|
||||
UniquePtr<mp4_demuxer::SampleIterator> mIterator;
|
||||
UniquePtr<TrackInfo> mInfo;
|
||||
nsRefPtr<mp4_demuxer::ResourceStream> mStream;
|
||||
Maybe<media::TimeUnit> mNextKeyframeTime;
|
||||
// Queued samples extracted by the demuxer, but not yet returned.
|
||||
nsRefPtr<MediaRawData> mQueuedSample;
|
||||
bool mNeedReIndex;
|
||||
bool mNeedSPSForTelemetry;
|
||||
|
||||
// We do not actually need a monitor, however MoofParser will assert
|
||||
// if a monitor isn't held.
|
||||
Monitor mMonitor;
|
||||
};
|
||||
|
||||
} // namespace mozilla
|
||||
|
||||
#endif
|
||||
|
|
|
@ -172,10 +172,6 @@ skip-if = (toolkit == 'gonk' && !debug) || android_version == '10' || android_ve
|
|||
[test_stereoPanningWithGain.html]
|
||||
[test_waveDecoder.html]
|
||||
[test_waveShaper.html]
|
||||
skip-if = os == 'win' && debug #Bug 1202564
|
||||
[test_waveShaperNoCurve.html]
|
||||
skip-if = os == 'win' && debug #Bug 1202565
|
||||
[test_waveShaperPassThrough.html]
|
||||
skip-if = os == 'win' && debug #Bug 1196084
|
||||
[test_waveShaperInvalidLengthCurve.html]
|
||||
skip-if = os == 'win' && debug #Bug 1202567
|
||||
|
|
|
@ -971,7 +971,7 @@ NPP_New(NPMIMEType pluginType, NPP instance, uint16_t mode, int16_t argc, char*
|
|||
NPError err = pluginInstanceInit(instanceData);
|
||||
if (err != NPERR_NO_ERROR) {
|
||||
NPN_ReleaseObject(scriptableObject);
|
||||
free(instanceData);
|
||||
delete instanceData;
|
||||
return err;
|
||||
}
|
||||
|
||||
|
|
|
@ -105,7 +105,7 @@ PresentationResponderLoadingCallback::Init(nsIDocShell* aDocShell)
|
|||
return rv;
|
||||
}
|
||||
|
||||
if ((busyFlags & nsIDocShell::BUSY_FLAGS_NONE) ||
|
||||
if ((busyFlags == nsIDocShell::BUSY_FLAGS_NONE) ||
|
||||
(busyFlags & nsIDocShell::BUSY_FLAGS_PAGE_LOADING)) {
|
||||
// The docshell has finished loading or is receiving data (|STATE_TRANSFERRING|
|
||||
// has already been fired), so the page is ready for presentation use.
|
||||
|
|
|
@ -301,15 +301,15 @@ private:
|
|||
// List of scopes having data, for optimization purposes only
|
||||
nsTHashtable<nsCStringHashKey> mScopesHavingData;
|
||||
|
||||
StatementCache mWorkerStatements;
|
||||
StatementCache mReaderStatements;
|
||||
|
||||
// Connection used by the worker thread for all read and write ops
|
||||
nsCOMPtr<mozIStorageConnection> mWorkerConnection;
|
||||
|
||||
// Connection used only on the main thread for sync read operations
|
||||
nsCOMPtr<mozIStorageConnection> mReaderConnection;
|
||||
|
||||
StatementCache mWorkerStatements;
|
||||
StatementCache mReaderStatements;
|
||||
|
||||
// Time the first pending operation has been added to the pending operations
|
||||
// list
|
||||
PRIntervalTime mDirtyEpoch;
|
||||
|
|
|
@ -61,6 +61,8 @@ dictionary MutationObserverInit {
|
|||
boolean attributeOldValue;
|
||||
boolean characterDataOldValue;
|
||||
// [ChromeOnly]
|
||||
boolean nativeAnonymousChildList = false;
|
||||
// [ChromeOnly]
|
||||
boolean animations;
|
||||
sequence<DOMString> attributeFilter;
|
||||
};
|
||||
|
|
|
@ -2873,7 +2873,7 @@ nsHTMLEditRules::MoveBlock(nsIDOMNode *aLeftBlock, nsIDOMNode *aRightBlock, int3
|
|||
// GetNodesFromPoint is the workhorse that figures out what we wnat to move.
|
||||
nsresult res = GetNodesFromPoint(::DOMPoint(aRightBlock,aRightOffset),
|
||||
EditAction::makeList, arrayOfNodes,
|
||||
TouchContent::no);
|
||||
TouchContent::yes);
|
||||
NS_ENSURE_SUCCESS(res, res);
|
||||
for (auto& curNode : arrayOfNodes) {
|
||||
// get the node to act on
|
||||
|
@ -5614,6 +5614,27 @@ nsHTMLEditRules::GetPromotedPoint(RulesEndpoint aWhere, nsIDOMNode* aNode,
|
|||
if (mHTMLEditor->IsVisBreak(nextNode->AsDOMNode())) {
|
||||
break;
|
||||
}
|
||||
|
||||
// Check for newlines in pre-formatted text nodes.
|
||||
bool isPRE;
|
||||
mHTMLEditor->IsPreformatted(nextNode->AsDOMNode(), &isPRE);
|
||||
if (isPRE) {
|
||||
nsCOMPtr<nsIDOMText> textNode = do_QueryInterface(nextNode);
|
||||
if (textNode) {
|
||||
nsAutoString tempString;
|
||||
textNode->GetData(tempString);
|
||||
int32_t newlinePos = tempString.FindChar(nsCRT::LF);
|
||||
if (newlinePos >= 0) {
|
||||
if ((uint32_t)newlinePos + 1 == tempString.Length()) {
|
||||
// No need for special processing if the newline is at the end.
|
||||
break;
|
||||
}
|
||||
*outNode = nextNode->AsDOMNode();
|
||||
*outOffset = newlinePos + 1;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
NS_ENSURE_TRUE(mHTMLEditor, /* void */);
|
||||
nextNode = mHTMLEditor->GetNextHTMLNode(node, offset, true);
|
||||
}
|
||||
|
@ -5781,6 +5802,38 @@ nsHTMLEditRules::GetNodesForOperation(nsTArray<nsRefPtr<nsRange>>& aArrayOfRange
|
|||
int32_t rangeCount = aArrayOfRanges.Length();
|
||||
nsresult res = NS_OK;
|
||||
|
||||
if (aTouchContent == TouchContent::yes) {
|
||||
// Split text nodes. This is necessary, since GetPromotedPoint() may return a
|
||||
// range ending in a text node in case where part of a pre-formatted
|
||||
// elements needs to be moved.
|
||||
for (int32_t i = 0; i < rangeCount; i++) {
|
||||
nsRefPtr<nsRange> r = aArrayOfRanges[i];
|
||||
nsCOMPtr<nsIContent> endParent = do_QueryInterface(r->GetEndParent());
|
||||
if (!mHTMLEditor->IsTextNode(endParent)) {
|
||||
continue;
|
||||
}
|
||||
nsCOMPtr<nsIDOMText> textNode = do_QueryInterface(endParent);
|
||||
if (textNode) {
|
||||
int32_t offset = r->EndOffset();
|
||||
nsAutoString tempString;
|
||||
textNode->GetData(tempString);
|
||||
|
||||
if (0 < offset && offset < (int32_t)(tempString.Length())) {
|
||||
// Split the text node.
|
||||
nsCOMPtr<nsIDOMNode> tempNode;
|
||||
res = mHTMLEditor->SplitNode(endParent->AsDOMNode(), offset,
|
||||
getter_AddRefs(tempNode));
|
||||
NS_ENSURE_SUCCESS(res, res);
|
||||
|
||||
// Correct the range.
|
||||
// The new end parent becomes the parent node of the text.
|
||||
nsCOMPtr<nsIContent> newParent = endParent->GetParent();
|
||||
r->SetEnd(newParent, newParent->IndexOf(endParent));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Bust up any inlines that cross our range endpoints, but only if we are
|
||||
// allowed to touch content.
|
||||
|
||||
|
|
|
@ -130,6 +130,7 @@ skip-if = toolkit == 'android' || e10s
|
|||
[test_bug757371.html]
|
||||
[test_bug757771.html]
|
||||
[test_bug767684.html]
|
||||
[test_bug772796.html]
|
||||
[test_bug773262.html]
|
||||
[test_bug780035.html]
|
||||
[test_bug787432.html]
|
||||
|
|
|
@ -0,0 +1,223 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<!--
|
||||
https://bugzilla.mozilla.org/show_bug.cgi?id=772796
|
||||
-->
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Test for Bug 772796</title>
|
||||
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
|
||||
<style> .pre { white-space: pre } </style>
|
||||
</head>
|
||||
<body>
|
||||
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=772796">Mozilla Bug 772796</a>
|
||||
<p id="display"></p>
|
||||
<div id="content" style="display: none">
|
||||
</div>
|
||||
|
||||
<div id="editable" contenteditable></div>
|
||||
|
||||
<pre id="test">
|
||||
|
||||
<script type="application/javascript">
|
||||
var tests = [
|
||||
/*00*/[ "<div>test</div><pre>foobar\nbaz</pre>", "<div>testfoobar\n</div><pre>baz</pre>" ],
|
||||
/*01*/[ "<div>test</div><pre><b>foobar\nbaz</b></pre>", "<div>test<b>foobar\n</b></div><pre><b>baz</b></pre>" ],
|
||||
/*02*/[ "<div>test</div><pre><b>foo</b>bar\nbaz</pre>", "<div>test<b>foo</b>bar\n</div><pre>baz</pre>" ],
|
||||
/*03*/[ "<div>test</div><pre><b>foo</b>\nbar</pre>", "<div>test<b>foo</b>\n</div><pre>bar</pre>" ],
|
||||
/*04*/[ "<div>test</div><pre><b>foo\n</b>bar\nbaz</pre>", "<div>test<b>foo\n</b></div><pre>bar\nbaz</pre>" ],
|
||||
/* The <br> after the foobar is unfortunate but is behaviour that hasn't changed in bug 772796. */
|
||||
/*05*/[ "<div>test</div><pre>foobar<br>baz</pre>", "<div>testfoobar<br></div><pre>baz</pre>" ],
|
||||
/*06*/[ "<div>test</div><pre><b>foobar<br>baz</b></pre>", "<div>test<b>foobar</b><br></div><pre><b>baz</b></pre>" ],
|
||||
|
||||
/*
|
||||
* Some tests with block elements.
|
||||
* Tests 07, 09 and 11 don't use "MoveBlock", they use "JoinNodesSmart".
|
||||
* Test 11 is a pain: <div>foo\bar</div> is be joined to "test", losing the visible line break.
|
||||
*/
|
||||
/*07*/[ "<div>test</div><pre><div>foobar</div>baz</pre>", "<div>testfoobar</div><pre>baz</pre>" ],
|
||||
/*08*/[ "<div>test</div><pre>foobar<div>baz</div></pre>", "<div>testfoobar</div><pre><div>baz</div></pre>" ],
|
||||
/*09*/[ "<div>test</div><pre><div>foobar</div>baz\nfred</pre>", "<div>testfoobar</div><pre>baz\nfred</pre>" ],
|
||||
/*10*/[ "<div>test</div><pre>foobar<div>baz</div>\nfred</pre>", "<div>testfoobar</div><pre><div>baz</div>\nfred</pre>" ],
|
||||
/*11*/[ "<div>test</div><pre><div>foo\nbar</div>baz\nfred</pre>", "<div>testfoo\nbar</div><pre>baz\nfred</pre>" ], // BAD
|
||||
/*12*/[ "<div>test</div><pre>foo<div>bar</div>baz\nfred</pre>", "<div>testfoo</div><pre><div>bar</div>baz\nfred</pre>" ],
|
||||
|
||||
/*
|
||||
* Repeating all tests above with the <pre> on a new line.
|
||||
* We know that backspace doesn't work (bug 1190161). Third argument shows the current outcome.
|
||||
*/
|
||||
/*13-00*/[ "<div>test</div>\n<pre>foobar\nbaz</pre>", "<div>testfoobar\n</div><pre>baz</pre>",
|
||||
"<div>test</div>foobar\n<pre>baz</pre>" ],
|
||||
/*14-01*/[ "<div>test</div>\n<pre><b>foobar\nbaz</b></pre>", "<div>test<b>foobar\n</b></div><pre><b>baz</b></pre>",
|
||||
"<div>test</div><b>foobar\n</b><pre><b>baz</b></pre>" ],
|
||||
/*15-02*/[ "<div>test</div>\n<pre><b>foo</b>bar\nbaz</pre>", "<div>test<b>foo</b>bar\n</div><pre>baz</pre>",
|
||||
"<div>test</div><b>foo</b>bar\n<pre>baz</pre>" ],
|
||||
/*16-03*/[ "<div>test</div>\n<pre><b>foo</b>\nbar</pre>", "<div>test<b>foo</b>\n</div><pre>bar</pre>",
|
||||
"<div>test</div><b>foo</b>\n<pre>bar</pre>" ],
|
||||
/*17-04*/[ "<div>test</div>\n<pre><b>foo\n</b>bar\nbaz</pre>", "<div>test<b>foo\n</b></div><pre>bar\nbaz</pre>",
|
||||
"<div>test</div><b>foo\n</b><pre>bar\nbaz</pre>" ],
|
||||
/*18-05*/[ "<div>test</div>\n<pre>foobar<br>baz</pre>", "<div>testfoobar<br></div><pre>baz</pre>",
|
||||
"<div>test</div>foobar<br><pre>baz</pre>" ],
|
||||
/*19-06*/[ "<div>test</div>\n<pre><b>foobar<br>baz</b></pre>", "<div>test<b>foobar</b><br></div><pre><b>baz</b></pre>",
|
||||
"<div>test</div><b>foobar</b><br><pre><b>baz</b></pre>" ],
|
||||
/*20-07*/[ "<div>test</div>\n<pre><div>foobar</div>baz</pre>", "<div>testfoobar</div><pre>baz</pre>",
|
||||
"<div>test</div>foobar<pre>baz</pre>" ],
|
||||
/*21-08*/[ "<div>test</div>\n<pre>foobar<div>baz</div></pre>", "<div>testfoobar</div><pre><div>baz</div></pre>",
|
||||
"<div>test</div>foobar<pre><div>baz</div></pre>" ],
|
||||
/*22-09*/[ "<div>test</div>\n<pre><div>foobar</div>baz\nfred</pre>", "<div>testfoobar</div><pre>baz\nfred</pre>",
|
||||
"<div>test</div>foobar<pre>baz\nfred</pre>" ],
|
||||
/*23-10*/[ "<div>test</div>\n<pre>foobar<div>baz</div>\nfred</pre>", "<div>testfoobar</div><pre><div>baz</div>\nfred</pre>",
|
||||
"<div>test</div>foobar<pre><div>baz</div>\nfred</pre>" ],
|
||||
/*24-11*/[ "<div>test</div>\n<pre><div>foo\nbar</div>baz\nfred</pre>", "<div>testfoo\nbar</div><pre>baz\nfred</pre>", // BAD
|
||||
"<div>test</div>foo\n<pre><div>bar</div>baz\nfred</pre>" ],
|
||||
/*25-12*/[ "<div>test</div>\n<pre>foo<div>bar</div>baz\nfred</pre>", "<div>testfoo</div><pre><div>bar</div>baz\nfred</pre>",
|
||||
"<div>test</div>foo<pre><div>bar</div>baz\nfred</pre>" ],
|
||||
|
||||
/* Some tests without <div>. These exercise the MoveBlock "right in left" */
|
||||
/*26-00*/[ "test<pre>foobar\nbaz</pre>", "testfoobar\n<pre>baz</pre>" ],
|
||||
/*27-01*/[ "test<pre><b>foobar\nbaz</b></pre>", "test<b>foobar\n</b><pre><b>baz</b></pre>" ],
|
||||
/*28-02*/[ "test<pre><b>foo</b>bar\nbaz</pre>", "test<b>foo</b>bar\n<pre>baz</pre>" ],
|
||||
/*29-03*/[ "test<pre><b>foo</b>\nbar</pre>", "test<b>foo</b>\n<pre>bar</pre>" ],
|
||||
/*30-04*/[ "test<pre><b>foo\n</b>bar\nbaz</pre>", "test<b>foo\n</b><pre>bar\nbaz</pre>" ],
|
||||
/*31-05*/[ "test<pre>foobar<br>baz</pre>", "testfoobar<br><pre>baz</pre>" ],
|
||||
/*32-06*/[ "test<pre><b>foobar<br>baz</b></pre>", "test<b>foobar</b><br><pre><b>baz</b></pre>" ],
|
||||
/*33-07*/[ "test<pre><div>foobar</div>baz</pre>", "testfoobar<pre>baz</pre>" ],
|
||||
/*34-08*/[ "test<pre>foobar<div>baz</div></pre>", "testfoobar<pre><div>baz</div></pre>" ],
|
||||
/*35-09*/[ "test<pre><div>foobar</div>baz\nfred</pre>", "testfoobar<pre>baz\nfred</pre>" ],
|
||||
/*36-10*/[ "test<pre>foobar<div>baz</div>\nfred</pre>", "testfoobar<pre><div>baz</div>\nfred</pre>" ],
|
||||
/*37-11*/[ "test<pre><div>foo\nbar</div>baz\nfred</pre>", "testfoo\n<pre><div>bar</div>baz\nfred</pre>" ],
|
||||
/*38-12*/[ "test<pre>foo<div>bar</div>baz\nfred</pre>", "testfoo<pre><div>bar</div>baz\nfred</pre>" ],
|
||||
|
||||
/*
|
||||
* Some tests with <span class="pre">. Again 07, 09 and 11 use "JoinNodesSmart".
|
||||
* All these exercise MoveBlock "left in right". The "right" is the surrounding "contenteditable" div.
|
||||
*/
|
||||
/*39-00*/[ "<div>test</div><span class=\"pre\">foobar\nbaz</span>", "<div>test<span class=\"pre\">foobar\n</span></div><span class=\"pre\">baz</span>" ],
|
||||
/*40-01*/[ "<div>test</div><span class=\"pre\"><b>foobar\nbaz</b></span>", "<div>test<span class=\"pre\"><b>foobar\n</b></span></div><span class=\"pre\"><b>baz</b></span>" ],
|
||||
/*41-02*/[ "<div>test</div><span class=\"pre\"><b>foo</b>bar\nbaz</span>", "<div>test<span class=\"pre\"><b>foo</b>bar\n</span></div><span class=\"pre\">baz</span>" ],
|
||||
/*42-03*/[ "<div>test</div><span class=\"pre\"><b>foo</b>\nbar</span>", "<div>test<span class=\"pre\"><b>foo</b>\n</span></div><span class=\"pre\">bar</span>" ],
|
||||
/*43-04*/[ "<div>test</div><span class=\"pre\"><b>foo\n</b>bar\nbaz</span>", "<div>test<span class=\"pre\"><b>foo\n</b></span></div><span class=\"pre\">bar\nbaz</span>" ],
|
||||
/*44-05*/[ "<div>test</div><span class=\"pre\">foobar<br>baz</span>", "<div>test<span class=\"pre\">foobar</span><br><span class=\"pre\"></span></div><span class=\"pre\">baz</span>" ],
|
||||
/*45-06*/[ "<div>test</div><span class=\"pre\"><b>foobar<br>baz</b></span>", "<div>test<span class=\"pre\"><b>foobar</b></span><br><span class=\"pre\"></span></div><span class=\"pre\"><b>baz</b></span>" ],
|
||||
/*46-07*/[ "<div>test</div><span class=\"pre\"><div>foobar</div>baz</span>", "<div>testfoobar</div><span class=\"pre\">baz</span>" ],
|
||||
/*47-08*/[ "<div>test</div><span class=\"pre\">foobar<div>baz</div></span>", "<div>test<span class=\"pre\">foobar</span></div><span class=\"pre\"><div>baz</div></span>" ],
|
||||
/*48-09*/[ "<div>test</div><span class=\"pre\"><div>foobar</div>baz\nfred</span>", "<div>testfoobar</div><span class=\"pre\">baz\nfred</span>" ],
|
||||
/*49-10*/[ "<div>test</div><span class=\"pre\">foobar<div>baz</div>\nfred</span>", "<div>test<span class=\"pre\">foobar</span></div><span class=\"pre\"><div>baz</div>\nfred</span>" ],
|
||||
/*50-11*/[ "<div>test</div><span class=\"pre\"><div>foo\nbar</div>baz\nfred</span>", "<div>testfoo\nbar</div><span class=\"pre\">baz\nfred</span>" ], // BAD
|
||||
/*51-12*/[ "<div>test</div><span class=\"pre\">foo<div>bar</div>baz\nfred</span>", "<div>test<span class=\"pre\">foo</span></div><span class=\"pre\"><div>bar</div>baz\nfred</span>" ],
|
||||
|
||||
/* Some tests with <div class="pre">. */
|
||||
/*
|
||||
* The results are pretty ugly, since joining two <divs> sadly carrys the properties of the right to the left,
|
||||
* but not in all cases: 07, 09, 11 are actually right. All cases use "JoinNodesSmart".
|
||||
* Here we merely document the ugly behaviour. See bug 1191875 for more information.
|
||||
*/
|
||||
/*52-00*/[ "<div>test</div><div class=\"pre\">foobar\nbaz</div>", "<div class=\"pre\">testfoobar\nbaz</div>" ],
|
||||
/*53-01*/[ "<div>test</div><div class=\"pre\"><b>foobar\nbaz</b></div>", "<div class=\"pre\">test<b>foobar\nbaz</b></div>" ],
|
||||
/*54-02*/[ "<div>test</div><div class=\"pre\"><b>foo</b>bar\nbaz</div>", "<div class=\"pre\">test<b>foo</b>bar\nbaz</div>" ],
|
||||
/*55-03*/[ "<div>test</div><div class=\"pre\"><b>foo</b>\nbar</div>", "<div class=\"pre\">test<b>foo</b>\nbar</div>" ],
|
||||
/*56-04*/[ "<div>test</div><div class=\"pre\"><b>foo\n</b>bar\nbaz</div>", "<div class=\"pre\">test<b>foo\n</b>bar\nbaz</div>" ],
|
||||
/*57-05*/[ "<div>test</div><div class=\"pre\">foobar<br>baz</div>", "<div class=\"pre\">testfoobar<br>baz</div>" ],
|
||||
/*58-06*/[ "<div>test</div><div class=\"pre\"><b>foobar<br>baz</b></div>", "<div class=\"pre\">test<b>foobar<br>baz</b></div>" ],
|
||||
/*59-07*/[ "<div>test</div><div class=\"pre\"><div>foobar</div>baz</div>", "<div>testfoobar</div><div class=\"pre\">baz</div>" ],
|
||||
/*60-08*/[ "<div>test</div><div class=\"pre\">foobar<div>baz</div></div>", "<div class=\"pre\">testfoobar<div>baz</div></div>" ],
|
||||
/*61-09*/[ "<div>test</div><div class=\"pre\"><div>foobar</div>baz\nfred</div>", "<div>testfoobar</div><div class=\"pre\">baz\nfred</div>" ],
|
||||
/*62-10*/[ "<div>test</div><div class=\"pre\">foobar<div>baz</div>\nfred</div>", "<div class=\"pre\">testfoobar<div>baz</div>\nfred</div>" ],
|
||||
/*63-11*/[ "<div>test</div><div class=\"pre\"><div>foo\nbar</div>baz\nfred</div>", "<div>testfoo\nbar</div><div class=\"pre\">baz\nfred</div>" ], // BAD
|
||||
/*64-12*/[ "<div>test</div><div class=\"pre\">foo<div>bar</div>baz\nfred</div>", "<div class=\"pre\">testfoo<div>bar</div>baz\nfred</div>" ],
|
||||
|
||||
/* Some tests with lists. These exercise the MoveBlock "left in right". */
|
||||
/*65*/[ "<ul><pre><li>test</li>foobar\nbaz</pre></ul>", "<ul><pre><li>testfoobar\n</li>baz</pre></ul>" ],
|
||||
/*66*/[ "<ul><pre><li>test</li><b>foobar\nbaz</b></pre></ul>", "<ul><pre><li>test<b>foobar\n</b></li><b>baz</b></pre></ul>" ],
|
||||
/*67*/[ "<ul><pre><li>test</li><b>foo</b>bar\nbaz</pre></ul>", "<ul><pre><li>test<b>foo</b>bar\n</li>baz</pre></ul>" ],
|
||||
/*68*/[ "<ul><pre><li>test</li><b>foo</b>\nbar</pre></ul>", "<ul><pre><li>test<b>foo</b>\n</li>bar</pre></ul>" ],
|
||||
/*69*/[ "<ul><pre><li>test</li><b>foo\n</b>bar\nbaz</pre></ul>", "<ul><pre><li>test<b>foo\n</b></li>bar\nbaz</pre></ul>" ],
|
||||
|
||||
/* Last not least, some simple edge case tests. */
|
||||
/*70*/[ "<div>test</div><pre>foobar\n</pre>baz", "<div>testfoobar\n</div>baz" ],
|
||||
/*71*/[ "<div>test</div><pre>\nfoo\nbar</pre>", "<div>testfoo\n</div><pre>bar</pre>" ],
|
||||
/*72*/[ "<div>test</div><pre>\n\nfoo\nbar</pre>", "<div>test\n</div><pre>foo\nbar</pre>" ],
|
||||
];
|
||||
|
||||
/** Test for Bug 772796 **/
|
||||
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
|
||||
SimpleTest.waitForFocus(function() {
|
||||
|
||||
var sel = window.getSelection();
|
||||
var theEdit = document.getElementById("editable");
|
||||
var testName;
|
||||
var theDiv;
|
||||
|
||||
for (i = 0; i < tests.length; i++) {
|
||||
testName = "test" + i.toString();
|
||||
dump (testName+"\n");
|
||||
dump (tests[i][0]+"\n");
|
||||
|
||||
/* Set up the selection. */
|
||||
theEdit.innerHTML = "<div id=\"" + testName + "\">" + tests[i][0] + "</div>";
|
||||
theDiv = document.getElementById(testName);
|
||||
theDiv.focus();
|
||||
sel.collapse(theDiv, 0);
|
||||
synthesizeMouse(theDiv, 100, 2, {}); /* click behind and down */
|
||||
|
||||
/** First round: Forward delete. **/
|
||||
synthesizeKey("VK_DELETE", {});
|
||||
is(theDiv.innerHTML, tests[i][1], "delete(collapsed): inner HTML for " + testName);
|
||||
|
||||
/* Set up the selection. */
|
||||
theEdit.innerHTML = "<div id=\"" + testName + "\">" + tests[i][0] + "</div>";
|
||||
theDiv = document.getElementById(testName);
|
||||
theDiv.focus();
|
||||
sel.collapse(theDiv, 0);
|
||||
synthesizeMouse(theDiv, 100, 2, {}); /* click behind and down */
|
||||
|
||||
/** Second round: Backspace. **/
|
||||
synthesizeKey("VK_RIGHT", {});
|
||||
synthesizeKey("VK_BACK_SPACE", {});
|
||||
if (tests[i].length == 2) {
|
||||
is(theDiv.innerHTML, tests[i][1], "backspace: inner HTML for " + testName);
|
||||
} else {
|
||||
todo_is(theDiv.innerHTML, tests[i][1], "backspace(should be): inner HTML for " + testName);
|
||||
is(theDiv.innerHTML, tests[i][2], "backspace(currently is): inner HTML for " + testName);
|
||||
}
|
||||
|
||||
/* Set up the selection. */
|
||||
theEdit.innerHTML = "<div id=\"" + testName + "\">" + tests[i][0] + "</div>";
|
||||
theDiv = document.getElementById(testName);
|
||||
theDiv.focus();
|
||||
sel.collapse(theDiv, 0);
|
||||
synthesizeMouse(theDiv, 100, 2, {}); /* click behind and down */
|
||||
|
||||
/** Third round: Delete with non-collapsed selection. **/
|
||||
if (i == 72) {
|
||||
// This test doesn't work, since we can't select only one newline using the right arrow key.
|
||||
continue;
|
||||
}
|
||||
synthesizeKey("VK_LEFT", {});
|
||||
/* Strangely enough we need to hit "right arrow" three times to select two characters. */
|
||||
synthesizeKey("VK_RIGHT", {shiftKey:true});
|
||||
synthesizeKey("VK_RIGHT", {shiftKey:true});
|
||||
synthesizeKey("VK_RIGHT", {shiftKey:true});
|
||||
synthesizeKey("VK_DELETE", {});
|
||||
|
||||
/* We always expect to the delete the "tf" in "testfoo". */
|
||||
var expected = tests[i][1].replace("testfoo", "tesoo")
|
||||
.replace("test<b>foo", "tes<b>oo")
|
||||
.replace("test<span class=\"pre\">foo", "tes<span class=\"pre\">oo")
|
||||
.replace("test<span class=\"pre\"><b>foo", "tes<span class=\"pre\"><b>oo");
|
||||
is(theDiv.innerHTML, expected, "delete(non-collapsed): inner HTML for " + testName);
|
||||
}
|
||||
|
||||
SimpleTest.finish();
|
||||
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
</pre>
|
||||
</body>
|
||||
</html>
|
|
@ -16,6 +16,7 @@
|
|||
#include "nsContentUtils.h"
|
||||
#include "nsIScriptSecurityManager.h"
|
||||
#include "nsJSPrincipals.h"
|
||||
#include "nsIScriptError.h"
|
||||
#include "jswrapper.h"
|
||||
|
||||
extern PRLogModuleInfo *MCD;
|
||||
|
@ -106,7 +107,18 @@ nsresult EvaluateAdminConfigScript(const char *js_buffer, size_t length,
|
|||
|
||||
nsAutoCString script(js_buffer, length);
|
||||
JS::RootedValue v(cx);
|
||||
rv = xpc->EvalInSandboxObject(NS_ConvertUTF8toUTF16(script), filename, cx,
|
||||
|
||||
nsString convertedScript = NS_ConvertUTF8toUTF16(script);
|
||||
if (convertedScript.Length() == 0) {
|
||||
nsContentUtils::ReportToConsoleNonLocalized(
|
||||
NS_LITERAL_STRING("Your AutoConfig file is ASCII. Please convert it to UTF-8."),
|
||||
nsIScriptError::warningFlag,
|
||||
NS_LITERAL_CSTRING("autoconfig"),
|
||||
nullptr);
|
||||
/* If the length is 0, the conversion failed. Fallback to ASCII */
|
||||
convertedScript = NS_ConvertASCIItoUTF16(script);
|
||||
}
|
||||
rv = xpc->EvalInSandboxObject(convertedScript, filename, cx,
|
||||
autoconfigSb, JSVERSION_LATEST, &v);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
|
|
|
@ -7,7 +7,6 @@
|
|||
include('/ipc/chromium/chromium-config.mozbuild')
|
||||
SOURCES += [
|
||||
'mozEnglishWordUtils.cpp',
|
||||
'mozGenericWordUtils.cpp',
|
||||
'mozInlineSpellChecker.cpp',
|
||||
'mozInlineSpellWordUtil.cpp',
|
||||
'mozPersonalDictionary.cpp',
|
||||
|
|
|
@ -1,40 +0,0 @@
|
|||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
#include "mozGenericWordUtils.h"
|
||||
|
||||
NS_IMPL_ISUPPORTS(mozGenericWordUtils, mozISpellI18NUtil)
|
||||
|
||||
// do something sensible but generic ... eventually. For now whine.
|
||||
|
||||
mozGenericWordUtils::mozGenericWordUtils()
|
||||
{
|
||||
/* member initializers and constructor code */
|
||||
}
|
||||
|
||||
mozGenericWordUtils::~mozGenericWordUtils()
|
||||
{
|
||||
/* destructor code */
|
||||
}
|
||||
|
||||
NS_IMETHODIMP mozGenericWordUtils::GetLanguage(char16_t * *aLanguage)
|
||||
{
|
||||
return NS_ERROR_NOT_IMPLEMENTED;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP mozGenericWordUtils::GetRootForm(const char16_t *word, uint32_t type, char16_t ***words, uint32_t *count)
|
||||
{
|
||||
return NS_ERROR_NOT_IMPLEMENTED;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP mozGenericWordUtils::FromRootForm(const char16_t *word, const char16_t **iwords, uint32_t icount, char16_t ***owords, uint32_t *ocount)
|
||||
{
|
||||
return NS_ERROR_NOT_IMPLEMENTED;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP mozGenericWordUtils::FindNextWord(const char16_t *word, uint32_t length, uint32_t offset, int32_t *begin, int32_t *end)
|
||||
{
|
||||
return NS_ERROR_NOT_IMPLEMENTED;
|
||||
}
|
||||
|
|
@ -1,24 +0,0 @@
|
|||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#ifndef mozGenericWordUtils_h__
|
||||
#define mozGenericWordUtils_h__
|
||||
|
||||
#include "nsCOMPtr.h"
|
||||
#include "mozISpellI18NUtil.h"
|
||||
#include "nsCycleCollectionParticipant.h"
|
||||
|
||||
class mozGenericWordUtils : public mozISpellI18NUtil
|
||||
{
|
||||
protected:
|
||||
virtual ~mozGenericWordUtils();
|
||||
public:
|
||||
NS_DECL_ISUPPORTS
|
||||
NS_DECL_MOZISPELLI18NUTIL
|
||||
|
||||
mozGenericWordUtils();
|
||||
};
|
||||
|
||||
#endif
|
|
@ -5,36 +5,28 @@
|
|||
|
||||
#include "mozSpellI18NManager.h"
|
||||
#include "mozEnglishWordUtils.h"
|
||||
#include "mozGenericWordUtils.h"
|
||||
#include "nsString.h"
|
||||
#include "mozilla/nsRefPtr.h"
|
||||
|
||||
NS_IMPL_ISUPPORTS(mozSpellI18NManager, mozISpellI18NManager)
|
||||
|
||||
mozSpellI18NManager::mozSpellI18NManager()
|
||||
{
|
||||
/* member initializers and constructor code */
|
||||
}
|
||||
|
||||
mozSpellI18NManager::~mozSpellI18NManager()
|
||||
{
|
||||
/* destructor code */
|
||||
}
|
||||
|
||||
NS_IMETHODIMP mozSpellI18NManager::GetUtil(const char16_t *aLanguage, mozISpellI18NUtil **_retval)
|
||||
{
|
||||
if( nullptr == _retval) {
|
||||
if (!_retval) {
|
||||
return NS_ERROR_NULL_POINTER;
|
||||
}
|
||||
*_retval = nullptr;
|
||||
nsAutoString lang;
|
||||
lang.Assign(aLanguage);
|
||||
if(lang.EqualsLiteral("en")){
|
||||
*_retval = new mozEnglishWordUtils;
|
||||
}
|
||||
else{
|
||||
*_retval = new mozEnglishWordUtils;
|
||||
}
|
||||
|
||||
NS_IF_ADDREF(*_retval);
|
||||
// XXX TODO Actually handle multiple languages.
|
||||
nsRefPtr<mozEnglishWordUtils> utils = new mozEnglishWordUtils;
|
||||
utils.forget(_retval);
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,80 @@
|
|||
/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#ifndef MOZILLA_GFX_CRITICALSECTION_H_
|
||||
#define MOZILLA_GFX_CRITICALSECTION_H_
|
||||
|
||||
#ifdef WIN32
|
||||
#include <windows.h>
|
||||
#else
|
||||
#include <pthread.h>
|
||||
#include "mozilla/DebugOnly.h"
|
||||
#endif
|
||||
|
||||
namespace mozilla {
|
||||
namespace gfx {
|
||||
|
||||
#ifdef WIN32
|
||||
|
||||
class CriticalSection {
|
||||
public:
|
||||
CriticalSection() { ::InitializeCriticalSection(&mCriticalSection); }
|
||||
|
||||
~CriticalSection() { ::DeleteCriticalSection(&mCriticalSection); }
|
||||
|
||||
void Enter() { ::EnterCriticalSection(&mCriticalSection); }
|
||||
|
||||
void Leave() { ::LeaveCriticalSection(&mCriticalSection); }
|
||||
|
||||
protected:
|
||||
CRITICAL_SECTION mCriticalSection;
|
||||
};
|
||||
|
||||
#else
|
||||
// posix
|
||||
|
||||
class PosixCondvar;
|
||||
class CriticalSection {
|
||||
public:
|
||||
CriticalSection() {
|
||||
DebugOnly<int> err = pthread_mutex_init(&mMutex, nullptr);
|
||||
MOZ_ASSERT(!err);
|
||||
}
|
||||
|
||||
~CriticalSection() {
|
||||
DebugOnly<int> err = pthread_mutex_destroy(&mMutex);
|
||||
MOZ_ASSERT(!err);
|
||||
}
|
||||
|
||||
void Enter() {
|
||||
DebugOnly<int> err = pthread_mutex_lock(&mMutex);
|
||||
MOZ_ASSERT(!err);
|
||||
}
|
||||
|
||||
void Leave() {
|
||||
DebugOnly<int> err = pthread_mutex_unlock(&mMutex);
|
||||
MOZ_ASSERT(!err);
|
||||
}
|
||||
|
||||
protected:
|
||||
pthread_mutex_t mMutex;
|
||||
friend class PosixCondVar;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
/// RAII helper.
|
||||
struct CriticalSectionAutoEnter {
|
||||
explicit CriticalSectionAutoEnter(CriticalSection* aSection) : mSection(aSection) { mSection->Enter(); }
|
||||
~CriticalSectionAutoEnter() { mSection->Leave(); }
|
||||
protected:
|
||||
CriticalSection* mSection;
|
||||
};
|
||||
|
||||
|
||||
} // namespace
|
||||
} // namespace
|
||||
|
||||
#endif
|
|
@ -6,6 +6,10 @@
|
|||
#ifndef MOZILLA_GFX_DRAWCOMMAND_H_
|
||||
#define MOZILLA_GFX_DRAWCOMMAND_H_
|
||||
|
||||
|
||||
#define _USE_MATH_DEFINES
|
||||
#include <math.h>
|
||||
|
||||
#include "2D.h"
|
||||
#include "Filters.h"
|
||||
#include <vector>
|
||||
|
@ -31,7 +35,8 @@ enum class CommandType : int8_t {
|
|||
PUSHCLIP,
|
||||
PUSHCLIPRECT,
|
||||
POPCLIP,
|
||||
SETTRANSFORM
|
||||
SETTRANSFORM,
|
||||
FLUSH
|
||||
};
|
||||
|
||||
class DrawingCommand
|
||||
|
@ -39,7 +44,9 @@ class DrawingCommand
|
|||
public:
|
||||
virtual ~DrawingCommand() {}
|
||||
|
||||
virtual void ExecuteOnDT(DrawTarget* aDT, const Matrix& aTransform) = 0;
|
||||
virtual void ExecuteOnDT(DrawTarget* aDT, const Matrix* aTransform = nullptr) const = 0;
|
||||
|
||||
virtual bool GetAffectedRect(Rect& aDeviceRect, const Matrix& aTransform) const { return false; }
|
||||
|
||||
protected:
|
||||
explicit DrawingCommand(CommandType aType)
|
||||
|
@ -130,7 +137,7 @@ public:
|
|||
{
|
||||
}
|
||||
|
||||
virtual void ExecuteOnDT(DrawTarget* aDT, const Matrix&)
|
||||
virtual void ExecuteOnDT(DrawTarget* aDT, const Matrix*) const
|
||||
{
|
||||
aDT->DrawSurface(mSurface, mDest, mSource, mSurfOptions, mOptions);
|
||||
}
|
||||
|
@ -154,7 +161,7 @@ public:
|
|||
{
|
||||
}
|
||||
|
||||
virtual void ExecuteOnDT(DrawTarget* aDT, const Matrix&)
|
||||
virtual void ExecuteOnDT(DrawTarget* aDT, const Matrix*) const
|
||||
{
|
||||
aDT->DrawFilter(mFilter, mSourceRect, mDestPoint, mOptions);
|
||||
}
|
||||
|
@ -175,7 +182,7 @@ public:
|
|||
{
|
||||
}
|
||||
|
||||
virtual void ExecuteOnDT(DrawTarget* aDT, const Matrix&)
|
||||
virtual void ExecuteOnDT(DrawTarget* aDT, const Matrix*) const
|
||||
{
|
||||
aDT->ClearRect(mRect);
|
||||
}
|
||||
|
@ -197,11 +204,13 @@ public:
|
|||
{
|
||||
}
|
||||
|
||||
virtual void ExecuteOnDT(DrawTarget* aDT, const Matrix& aTransform)
|
||||
virtual void ExecuteOnDT(DrawTarget* aDT, const Matrix* aTransform) const
|
||||
{
|
||||
MOZ_ASSERT(!aTransform.HasNonIntegerTranslation());
|
||||
MOZ_ASSERT(!aTransform || !aTransform->HasNonIntegerTranslation());
|
||||
Point dest(Float(mDestination.x), Float(mDestination.y));
|
||||
dest = aTransform * dest;
|
||||
if (aTransform) {
|
||||
dest = (*aTransform) * dest;
|
||||
}
|
||||
aDT->CopySurface(mSurface, mSourceRect, IntPoint(uint32_t(dest.x), uint32_t(dest.y)));
|
||||
}
|
||||
|
||||
|
@ -224,11 +233,17 @@ public:
|
|||
{
|
||||
}
|
||||
|
||||
virtual void ExecuteOnDT(DrawTarget* aDT, const Matrix&)
|
||||
virtual void ExecuteOnDT(DrawTarget* aDT, const Matrix*) const
|
||||
{
|
||||
aDT->FillRect(mRect, mPattern, mOptions);
|
||||
}
|
||||
|
||||
bool GetAffectedRect(Rect& aDeviceRect, const Matrix& aTransform) const
|
||||
{
|
||||
aDeviceRect = aTransform.TransformBounds(mRect);
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
Rect mRect;
|
||||
StoredPattern mPattern;
|
||||
|
@ -248,9 +263,14 @@ public:
|
|||
, mStrokeOptions(aStrokeOptions)
|
||||
, mOptions(aOptions)
|
||||
{
|
||||
if (aStrokeOptions.mDashLength) {
|
||||
mDashes.resize(aStrokeOptions.mDashLength);
|
||||
mStrokeOptions.mDashPattern = &mDashes.front();
|
||||
memcpy(&mDashes.front(), aStrokeOptions.mDashPattern, mStrokeOptions.mDashLength * sizeof(Float));
|
||||
}
|
||||
}
|
||||
|
||||
virtual void ExecuteOnDT(DrawTarget* aDT, const Matrix&)
|
||||
virtual void ExecuteOnDT(DrawTarget* aDT, const Matrix*) const
|
||||
{
|
||||
aDT->StrokeRect(mRect, mPattern, mStrokeOptions, mOptions);
|
||||
}
|
||||
|
@ -260,6 +280,7 @@ private:
|
|||
StoredPattern mPattern;
|
||||
StrokeOptions mStrokeOptions;
|
||||
DrawOptions mOptions;
|
||||
std::vector<Float> mDashes;
|
||||
};
|
||||
|
||||
class StrokeLineCommand : public DrawingCommand
|
||||
|
@ -279,7 +300,7 @@ public:
|
|||
{
|
||||
}
|
||||
|
||||
virtual void ExecuteOnDT(DrawTarget* aDT, const Matrix&)
|
||||
virtual void ExecuteOnDT(DrawTarget* aDT, const Matrix*) const
|
||||
{
|
||||
aDT->StrokeLine(mStart, mEnd, mPattern, mStrokeOptions, mOptions);
|
||||
}
|
||||
|
@ -305,17 +326,58 @@ public:
|
|||
{
|
||||
}
|
||||
|
||||
virtual void ExecuteOnDT(DrawTarget* aDT, const Matrix&)
|
||||
virtual void ExecuteOnDT(DrawTarget* aDT, const Matrix*) const
|
||||
{
|
||||
aDT->Fill(mPath, mPattern, mOptions);
|
||||
}
|
||||
|
||||
bool GetAffectedRect(Rect& aDeviceRect, const Matrix& aTransform) const
|
||||
{
|
||||
aDeviceRect = mPath->GetBounds(aTransform);
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
RefPtr<Path> mPath;
|
||||
StoredPattern mPattern;
|
||||
DrawOptions mOptions;
|
||||
};
|
||||
|
||||
#ifndef M_SQRT2
|
||||
#define M_SQRT2 1.41421356237309504880
|
||||
#endif
|
||||
|
||||
#ifndef M_SQRT1_2
|
||||
#define M_SQRT1_2 0.707106781186547524400844362104849039
|
||||
#endif
|
||||
|
||||
// The logic for this comes from _cairo_stroke_style_max_distance_from_path
|
||||
static Rect
|
||||
PathExtentsToMaxStrokeExtents(const StrokeOptions &aStrokeOptions,
|
||||
const Rect &aRect,
|
||||
const Matrix &aTransform)
|
||||
{
|
||||
double styleExpansionFactor = 0.5f;
|
||||
|
||||
if (aStrokeOptions.mLineCap == CapStyle::SQUARE) {
|
||||
styleExpansionFactor = M_SQRT1_2;
|
||||
}
|
||||
|
||||
if (aStrokeOptions.mLineJoin == JoinStyle::MITER &&
|
||||
styleExpansionFactor < M_SQRT2 * aStrokeOptions.mMiterLimit) {
|
||||
styleExpansionFactor = M_SQRT2 * aStrokeOptions.mMiterLimit;
|
||||
}
|
||||
|
||||
styleExpansionFactor *= aStrokeOptions.mLineWidth;
|
||||
|
||||
double dx = styleExpansionFactor * hypot(aTransform._11, aTransform._21);
|
||||
double dy = styleExpansionFactor * hypot(aTransform._22, aTransform._12);
|
||||
|
||||
Rect result = aRect;
|
||||
result.Inflate(dx, dy);
|
||||
return result;
|
||||
}
|
||||
|
||||
class StrokeCommand : public DrawingCommand
|
||||
{
|
||||
public:
|
||||
|
@ -329,18 +391,30 @@ public:
|
|||
, mStrokeOptions(aStrokeOptions)
|
||||
, mOptions(aOptions)
|
||||
{
|
||||
if (aStrokeOptions.mDashLength) {
|
||||
mDashes.resize(aStrokeOptions.mDashLength);
|
||||
mStrokeOptions.mDashPattern = &mDashes.front();
|
||||
memcpy(&mDashes.front(), aStrokeOptions.mDashPattern, mStrokeOptions.mDashLength * sizeof(Float));
|
||||
}
|
||||
}
|
||||
|
||||
virtual void ExecuteOnDT(DrawTarget* aDT, const Matrix&)
|
||||
virtual void ExecuteOnDT(DrawTarget* aDT, const Matrix*) const
|
||||
{
|
||||
aDT->Stroke(mPath, mPattern, mStrokeOptions, mOptions);
|
||||
}
|
||||
|
||||
bool GetAffectedRect(Rect& aDeviceRect, const Matrix& aTransform) const
|
||||
{
|
||||
aDeviceRect = PathExtentsToMaxStrokeExtents(mStrokeOptions, mPath->GetBounds(aTransform), aTransform);
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
RefPtr<Path> mPath;
|
||||
StoredPattern mPattern;
|
||||
StrokeOptions mStrokeOptions;
|
||||
DrawOptions mOptions;
|
||||
std::vector<Float> mDashes;
|
||||
};
|
||||
|
||||
class FillGlyphsCommand : public DrawingCommand
|
||||
|
@ -361,7 +435,7 @@ public:
|
|||
memcpy(&mGlyphs.front(), aBuffer.mGlyphs, sizeof(Glyph) * aBuffer.mNumGlyphs);
|
||||
}
|
||||
|
||||
virtual void ExecuteOnDT(DrawTarget* aDT, const Matrix&)
|
||||
virtual void ExecuteOnDT(DrawTarget* aDT, const Matrix*) const
|
||||
{
|
||||
GlyphBuffer buf;
|
||||
buf.mNumGlyphs = mGlyphs.size();
|
||||
|
@ -390,7 +464,7 @@ public:
|
|||
{
|
||||
}
|
||||
|
||||
virtual void ExecuteOnDT(DrawTarget* aDT, const Matrix&)
|
||||
virtual void ExecuteOnDT(DrawTarget* aDT, const Matrix*) const
|
||||
{
|
||||
aDT->Mask(mSource, mMask, mOptions);
|
||||
}
|
||||
|
@ -416,7 +490,7 @@ public:
|
|||
{
|
||||
}
|
||||
|
||||
virtual void ExecuteOnDT(DrawTarget* aDT, const Matrix&)
|
||||
virtual void ExecuteOnDT(DrawTarget* aDT, const Matrix*) const
|
||||
{
|
||||
aDT->MaskSurface(mSource, mMask, mOffset, mOptions);
|
||||
}
|
||||
|
@ -437,7 +511,7 @@ public:
|
|||
{
|
||||
}
|
||||
|
||||
virtual void ExecuteOnDT(DrawTarget* aDT, const Matrix&)
|
||||
virtual void ExecuteOnDT(DrawTarget* aDT, const Matrix*) const
|
||||
{
|
||||
aDT->PushClip(mPath);
|
||||
}
|
||||
|
@ -455,7 +529,7 @@ public:
|
|||
{
|
||||
}
|
||||
|
||||
virtual void ExecuteOnDT(DrawTarget* aDT, const Matrix&)
|
||||
virtual void ExecuteOnDT(DrawTarget* aDT, const Matrix*) const
|
||||
{
|
||||
aDT->PushClipRect(mRect);
|
||||
}
|
||||
|
@ -472,7 +546,7 @@ public:
|
|||
{
|
||||
}
|
||||
|
||||
virtual void ExecuteOnDT(DrawTarget* aDT, const Matrix&)
|
||||
virtual void ExecuteOnDT(DrawTarget* aDT, const Matrix*) const
|
||||
{
|
||||
aDT->PopClip();
|
||||
}
|
||||
|
@ -487,17 +561,33 @@ public:
|
|||
{
|
||||
}
|
||||
|
||||
virtual void ExecuteOnDT(DrawTarget* aDT, const Matrix& aMatrix)
|
||||
virtual void ExecuteOnDT(DrawTarget* aDT, const Matrix* aMatrix) const
|
||||
{
|
||||
Matrix transform = mTransform;
|
||||
transform *= aMatrix;
|
||||
aDT->SetTransform(transform);
|
||||
if (aMatrix) {
|
||||
aDT->SetTransform(mTransform * (*aMatrix));
|
||||
} else {
|
||||
aDT->SetTransform(mTransform);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
Matrix mTransform;
|
||||
};
|
||||
|
||||
class FlushCommand : public DrawingCommand
|
||||
{
|
||||
public:
|
||||
explicit FlushCommand()
|
||||
: DrawingCommand(CommandType::FLUSH)
|
||||
{
|
||||
}
|
||||
|
||||
virtual void ExecuteOnDT(DrawTarget* aDT, const Matrix*) const
|
||||
{
|
||||
aDT->Flush();
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace gfx
|
||||
|
||||
} // namespace mozilla
|
||||
|
|
|
@ -188,7 +188,7 @@ DrawTargetCaptureImpl::ReplayToDrawTarget(DrawTarget* aDT, const Matrix& aTransf
|
|||
uint8_t* current = start;
|
||||
|
||||
while (current < start + mDrawCommandStorage.size()) {
|
||||
reinterpret_cast<DrawingCommand*>(current + sizeof(uint32_t))->ExecuteOnDT(aDT, aTransform);
|
||||
reinterpret_cast<DrawingCommand*>(current + sizeof(uint32_t))->ExecuteOnDT(aDT, &aTransform);
|
||||
current += *(uint32_t*)current;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,120 @@
|
|||
/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "DrawingJob.h"
|
||||
#include "JobScheduler.h"
|
||||
#include "mozilla/gfx/2D.h"
|
||||
|
||||
namespace mozilla {
|
||||
namespace gfx {
|
||||
|
||||
DrawingJobBuilder::DrawingJobBuilder()
|
||||
{}
|
||||
|
||||
DrawingJobBuilder::~DrawingJobBuilder()
|
||||
{
|
||||
MOZ_ASSERT(!mDrawTarget);
|
||||
}
|
||||
|
||||
void
|
||||
DrawingJob::Clear()
|
||||
{
|
||||
mCommandBuffer = nullptr;
|
||||
mCursor = 0;
|
||||
}
|
||||
|
||||
void
|
||||
DrawingJobBuilder::BeginDrawingJob(DrawTarget* aTarget, IntPoint aOffset,
|
||||
SyncObject* aStart)
|
||||
{
|
||||
MOZ_ASSERT(mCommandOffsets.empty());
|
||||
MOZ_ASSERT(aTarget);
|
||||
mDrawTarget = aTarget;
|
||||
mOffset = aOffset;
|
||||
mStart = aStart;
|
||||
}
|
||||
|
||||
DrawingJob*
|
||||
DrawingJobBuilder::EndDrawingJob(CommandBuffer* aCmdBuffer,
|
||||
SyncObject* aCompletion,
|
||||
WorkerThread* aPinToWorker)
|
||||
{
|
||||
MOZ_ASSERT(mDrawTarget);
|
||||
DrawingJob* task = new DrawingJob(mDrawTarget, mOffset, mStart, aCompletion, aPinToWorker);
|
||||
task->mCommandBuffer = aCmdBuffer;
|
||||
task->mCommandOffsets = Move(mCommandOffsets);
|
||||
|
||||
mDrawTarget = nullptr;
|
||||
mOffset = IntPoint();
|
||||
mStart = nullptr;
|
||||
|
||||
return task;
|
||||
}
|
||||
|
||||
DrawingJob::DrawingJob(DrawTarget* aTarget, IntPoint aOffset,
|
||||
SyncObject* aStart, SyncObject* aCompletion,
|
||||
WorkerThread* aPinToWorker)
|
||||
: Job(aStart, aCompletion, aPinToWorker)
|
||||
, mCommandBuffer(nullptr)
|
||||
, mCursor(0)
|
||||
, mDrawTarget(aTarget)
|
||||
, mOffset(aOffset)
|
||||
{
|
||||
mCommandOffsets.reserve(64);
|
||||
}
|
||||
|
||||
JobStatus
|
||||
DrawingJob::Run()
|
||||
{
|
||||
while (mCursor < mCommandOffsets.size()) {
|
||||
|
||||
const DrawingCommand* cmd = mCommandBuffer->GetDrawingCommand(mCommandOffsets[mCursor]);
|
||||
|
||||
if (!cmd) {
|
||||
return JobStatus::Error;
|
||||
}
|
||||
|
||||
cmd->ExecuteOnDT(mDrawTarget);
|
||||
|
||||
++mCursor;
|
||||
}
|
||||
|
||||
return JobStatus::Complete;
|
||||
}
|
||||
|
||||
DrawingJob::~DrawingJob()
|
||||
{
|
||||
Clear();
|
||||
}
|
||||
|
||||
const DrawingCommand*
|
||||
CommandBuffer::GetDrawingCommand(ptrdiff_t aId)
|
||||
{
|
||||
return static_cast<DrawingCommand*>(mStorage.GetStorage(aId));
|
||||
}
|
||||
|
||||
CommandBuffer::~CommandBuffer()
|
||||
{
|
||||
mStorage.ForEach([](void* item){
|
||||
static_cast<DrawingCommand*>(item)->~DrawingCommand();
|
||||
});
|
||||
mStorage.Clear();
|
||||
}
|
||||
|
||||
void
|
||||
CommandBufferBuilder::BeginCommandBuffer(size_t aBufferSize)
|
||||
{
|
||||
MOZ_ASSERT(!mCommands);
|
||||
mCommands = new CommandBuffer(aBufferSize);
|
||||
}
|
||||
|
||||
already_AddRefed<CommandBuffer>
|
||||
CommandBufferBuilder::EndCommandBuffer()
|
||||
{
|
||||
return mCommands.forget();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace
|
|
@ -0,0 +1,157 @@
|
|||
/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#ifndef MOZILLA_GFX_COMMANDBUFFER_H_
|
||||
#define MOZILLA_GFX_COMMANDBUFFER_H_
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include "mozilla/RefPtr.h"
|
||||
#include "mozilla/Assertions.h"
|
||||
#include "mozilla/gfx/Matrix.h"
|
||||
#include "mozilla/gfx/JobScheduler.h"
|
||||
#include "mozilla/gfx/IterableArena.h"
|
||||
#include "DrawCommand.h"
|
||||
|
||||
namespace mozilla {
|
||||
namespace gfx {
|
||||
|
||||
class DrawingCommand;
|
||||
class PrintCommand;
|
||||
class SignalCommand;
|
||||
class DrawingJob;
|
||||
class WaitCommand;
|
||||
|
||||
class SyncObject;
|
||||
class MultiThreadedJobQueue;
|
||||
|
||||
class DrawTarget;
|
||||
|
||||
class DrawingJobBuilder;
|
||||
class CommandBufferBuilder;
|
||||
|
||||
/// Contains a sequence of immutable drawing commands that are typically used by
|
||||
/// several DrawingJobs.
|
||||
///
|
||||
/// CommandBuffer objects are built using CommandBufferBuilder.
|
||||
class CommandBuffer : public external::AtomicRefCounted<CommandBuffer>
|
||||
{
|
||||
public:
|
||||
MOZ_DECLARE_REFCOUNTED_TYPENAME(CommandBuffer)
|
||||
|
||||
~CommandBuffer();
|
||||
|
||||
const DrawingCommand* GetDrawingCommand(ptrdiff_t aId);
|
||||
|
||||
protected:
|
||||
explicit CommandBuffer(size_t aSize = 256)
|
||||
: mStorage(IterableArena::GROWABLE, aSize)
|
||||
{}
|
||||
|
||||
IterableArena mStorage;
|
||||
friend class CommandBufferBuilder;
|
||||
};
|
||||
|
||||
/// Generates CommandBuffer objects.
|
||||
///
|
||||
/// The builder is a separate object to ensure that commands are not added to a
|
||||
/// submitted CommandBuffer.
|
||||
class CommandBufferBuilder
|
||||
{
|
||||
public:
|
||||
void BeginCommandBuffer(size_t aBufferSize = 256);
|
||||
|
||||
already_AddRefed<CommandBuffer> EndCommandBuffer();
|
||||
|
||||
/// Build the CommandBuffer, command after command.
|
||||
/// This must be used between BeginCommandBuffer and EndCommandBuffer.
|
||||
template<typename T, typename... Args>
|
||||
ptrdiff_t AddCommand(Args&&... aArgs)
|
||||
{
|
||||
static_assert(IsBaseOf<DrawingCommand, T>::value,
|
||||
"T must derive from DrawingCommand");
|
||||
return mCommands->mStorage.Alloc<T>(Forward<Args>(aArgs)...);
|
||||
}
|
||||
|
||||
bool HasCommands() const { return !!mCommands; }
|
||||
|
||||
protected:
|
||||
RefPtr<CommandBuffer> mCommands;
|
||||
};
|
||||
|
||||
/// Stores multiple commands to be executed sequencially.
|
||||
class DrawingJob : public Job {
|
||||
public:
|
||||
~DrawingJob();
|
||||
|
||||
virtual JobStatus Run() override;
|
||||
|
||||
protected:
|
||||
DrawingJob(DrawTarget* aTarget,
|
||||
IntPoint aOffset,
|
||||
SyncObject* aStart,
|
||||
SyncObject* aCompletion,
|
||||
WorkerThread* aPinToWorker = nullptr);
|
||||
|
||||
/// Runs the tasks's destructors and resets the buffer.
|
||||
void Clear();
|
||||
|
||||
std::vector<ptrdiff_t> mCommandOffsets;
|
||||
RefPtr<CommandBuffer> mCommandBuffer;
|
||||
uint32_t mCursor;
|
||||
|
||||
RefPtr<DrawTarget> mDrawTarget;
|
||||
IntPoint mOffset;
|
||||
|
||||
friend class DrawingJobBuilder;
|
||||
};
|
||||
|
||||
/// Generates DrawingJob objects.
|
||||
///
|
||||
/// The builder is a separate object to ensure that commands are not added to a
|
||||
/// submitted DrawingJob.
|
||||
class DrawingJobBuilder {
|
||||
public:
|
||||
DrawingJobBuilder();
|
||||
|
||||
~DrawingJobBuilder();
|
||||
|
||||
/// Allocates a DrawingJob.
|
||||
///
|
||||
/// call this method before starting to add commands.
|
||||
void BeginDrawingJob(DrawTarget* aTarget, IntPoint aOffset,
|
||||
SyncObject* aStart = nullptr);
|
||||
|
||||
/// Build the DrawingJob, command after command.
|
||||
/// This must be used between BeginDrawingJob and EndDrawingJob.
|
||||
void AddCommand(ptrdiff_t offset)
|
||||
{
|
||||
mCommandOffsets.push_back(offset);
|
||||
}
|
||||
|
||||
/// Finalizes and returns the drawing task.
|
||||
///
|
||||
/// If aCompletion is not null, the sync object will be signaled after the
|
||||
/// task buffer is destroyed (and after the destructor of the tasks have run).
|
||||
/// In most cases this means after the completion of all tasks in the task buffer,
|
||||
/// but also when the task buffer is destroyed due to an error.
|
||||
DrawingJob* EndDrawingJob(CommandBuffer* aCmdBuffer,
|
||||
SyncObject* aCompletion = nullptr,
|
||||
WorkerThread* aPinToWorker = nullptr);
|
||||
|
||||
/// Returns true between BeginDrawingJob and EndDrawingJob, false otherwise.
|
||||
bool HasDrawingJob() const { return !!mDrawTarget; }
|
||||
|
||||
protected:
|
||||
std::vector<ptrdiff_t> mCommandOffsets;
|
||||
RefPtr<DrawTarget> mDrawTarget;
|
||||
IntPoint mOffset;
|
||||
RefPtr<SyncObject> mStart;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
} // namespace
|
||||
|
||||
#endif
|
|
@ -0,0 +1,193 @@
|
|||
/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#ifndef MOZILLA_GFX_ITERABLEARENA_H_
|
||||
#define MOZILLA_GFX_ITERABLEARENA_H_
|
||||
|
||||
#include "mozilla/Move.h"
|
||||
#include "mozilla/Assertions.h"
|
||||
#include "mozilla/gfx/Logging.h"
|
||||
|
||||
#include <string.h>
|
||||
#include <vector>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
|
||||
namespace mozilla {
|
||||
namespace gfx {
|
||||
|
||||
/// A simple pool allocator for plain data structures.
|
||||
///
|
||||
/// Beware that the pool will not attempt to run the destructors. It is the
|
||||
/// responsibility of the user of this class to either use objects with no
|
||||
/// destructor or to manually call the allocated objects destructors.
|
||||
/// If the pool is growable, its allocated objects must be safely moveable in
|
||||
/// in memory (through memcpy).
|
||||
class IterableArena {
|
||||
protected:
|
||||
struct Header
|
||||
{
|
||||
size_t mBlocSize;
|
||||
};
|
||||
public:
|
||||
enum ArenaType {
|
||||
FIXED_SIZE,
|
||||
GROWABLE
|
||||
};
|
||||
|
||||
IterableArena(ArenaType aType, size_t aStorageSize)
|
||||
: mSize(aStorageSize)
|
||||
, mCursor(0)
|
||||
, mIsGrowable(aType == GROWABLE)
|
||||
{
|
||||
if (mSize == 0) {
|
||||
mSize = 128;
|
||||
}
|
||||
|
||||
mStorage = (uint8_t*)malloc(mSize);
|
||||
if (mStorage == nullptr) {
|
||||
gfxCriticalError() << "Not enough Memory allocate a memory pool of size " << aStorageSize;
|
||||
MOZ_CRASH();
|
||||
}
|
||||
}
|
||||
|
||||
~IterableArena()
|
||||
{
|
||||
free(mStorage);
|
||||
}
|
||||
|
||||
/// Constructs a new item in the pool and returns a positive offset in case of
|
||||
/// success.
|
||||
///
|
||||
/// The offset never changes even if the storage is reallocated, so users
|
||||
/// of this class should prefer storing offsets rather than direct pointers
|
||||
/// to the allocated objects.
|
||||
/// Alloc can cause the storage to be reallocated if the pool was initialized
|
||||
/// with IterableArena::GROWABLE.
|
||||
/// If for any reason the pool fails to allocate enough space for the new item
|
||||
/// Alloc returns a negative offset and the object's constructor is not called.
|
||||
template<typename T, typename... Args>
|
||||
ptrdiff_t
|
||||
Alloc(Args&&... aArgs)
|
||||
{
|
||||
void* storage = nullptr;
|
||||
auto offset = AllocRaw(sizeof(T), &storage);
|
||||
if (offset < 0) {
|
||||
return offset;
|
||||
}
|
||||
new (storage) T(Forward<Args>(aArgs)...);
|
||||
return offset;
|
||||
}
|
||||
|
||||
ptrdiff_t AllocRaw(size_t aSize, void** aOutPtr = nullptr)
|
||||
{
|
||||
const size_t blocSize = AlignedSize(sizeof(Header) + aSize);
|
||||
|
||||
if (AlignedSize(mCursor + blocSize) > mSize) {
|
||||
if (!mIsGrowable) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
size_t newSize = mSize * 2;
|
||||
while (AlignedSize(mCursor + blocSize) > newSize) {
|
||||
newSize *= 2;
|
||||
}
|
||||
|
||||
uint8_t* newStorage = (uint8_t*)realloc(mStorage, newSize);
|
||||
if (!newStorage) {
|
||||
gfxCriticalError() << "Not enough Memory to grow the memory pool, size: " << newSize;
|
||||
return -1;
|
||||
}
|
||||
|
||||
mStorage = newStorage;
|
||||
mSize = newSize;
|
||||
}
|
||||
ptrdiff_t offset = mCursor;
|
||||
GetHeader(offset)->mBlocSize = blocSize;
|
||||
mCursor += blocSize;
|
||||
if (aOutPtr) {
|
||||
*aOutPtr = GetStorage(offset);
|
||||
}
|
||||
return offset;
|
||||
}
|
||||
|
||||
/// Get access to an allocated item at a given offset (only use offsets returned
|
||||
/// by Alloc or AllocRaw).
|
||||
///
|
||||
/// If the pool is growable, the returned pointer is only valid temporarily. The
|
||||
/// underlying storage can be reallocated in Alloc or AllocRaw, so do not keep
|
||||
/// these pointers around and store the offset instead.
|
||||
void* GetStorage(ptrdiff_t offset = 0)
|
||||
{
|
||||
MOZ_ASSERT(offset >= 0);
|
||||
MOZ_ASSERT(offset < mCursor);
|
||||
return offset >= 0 ? mStorage + offset + sizeof(Header) : nullptr;
|
||||
}
|
||||
|
||||
/// Clears the storage without running any destructor and without deallocating it.
|
||||
void Clear()
|
||||
{
|
||||
mCursor = 0;
|
||||
}
|
||||
|
||||
/// Iterate over the elements allocated in this pool.
|
||||
///
|
||||
/// Takes a lambda or function object accepting a void* as parameter.
|
||||
template<typename Func>
|
||||
void ForEach(Func cb)
|
||||
{
|
||||
Iterator it;
|
||||
while (void* ptr = it.Next(this)) {
|
||||
cb(ptr);
|
||||
}
|
||||
}
|
||||
|
||||
/// A simple iterator over an arena.
|
||||
class Iterator {
|
||||
public:
|
||||
Iterator()
|
||||
: mCursor(0)
|
||||
{}
|
||||
|
||||
void* Next(IterableArena* aArena)
|
||||
{
|
||||
if (mCursor >= aArena->mCursor) {
|
||||
return nullptr;
|
||||
}
|
||||
void* result = aArena->GetStorage(mCursor);
|
||||
const size_t blocSize = aArena->GetHeader(mCursor)->mBlocSize;
|
||||
MOZ_ASSERT(blocSize != 0);
|
||||
mCursor += blocSize;
|
||||
return result;
|
||||
}
|
||||
|
||||
private:
|
||||
ptrdiff_t mCursor;
|
||||
};
|
||||
|
||||
protected:
|
||||
Header* GetHeader(ptrdiff_t offset)
|
||||
{
|
||||
return (Header*) (mStorage + offset);
|
||||
}
|
||||
|
||||
size_t AlignedSize(size_t aSize) const
|
||||
{
|
||||
const size_t alignment = sizeof(uintptr_t);
|
||||
return aSize + (alignment - (aSize % alignment)) % alignment;
|
||||
}
|
||||
|
||||
uint8_t* mStorage;
|
||||
uint32_t mSize;
|
||||
ptrdiff_t mCursor;
|
||||
bool mIsGrowable;
|
||||
|
||||
friend class Iterator;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
} // namespace
|
||||
|
||||
#endif
|
|
@ -0,0 +1,279 @@
|
|||
/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "JobScheduler.h"
|
||||
|
||||
namespace mozilla {
|
||||
namespace gfx {
|
||||
|
||||
JobScheduler* JobScheduler::sSingleton = nullptr;
|
||||
|
||||
bool JobScheduler::Init(uint32_t aNumThreads, uint32_t aNumQueues)
|
||||
{
|
||||
MOZ_ASSERT(!sSingleton);
|
||||
MOZ_ASSERT(aNumThreads >= aNumQueues);
|
||||
|
||||
sSingleton = new JobScheduler();
|
||||
sSingleton->mNextQueue = 0;
|
||||
|
||||
for (uint32_t i = 0; i < aNumQueues; ++i) {
|
||||
sSingleton->mDrawingQueues.push_back(new MultiThreadedJobQueue());
|
||||
}
|
||||
|
||||
for (uint32_t i = 0; i < aNumThreads; ++i) {
|
||||
sSingleton->mWorkerThreads.push_back(WorkerThread::Create(sSingleton->mDrawingQueues[i%aNumQueues]));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void JobScheduler::ShutDown()
|
||||
{
|
||||
MOZ_ASSERT(IsEnabled());
|
||||
if (!IsEnabled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (auto queue : sSingleton->mDrawingQueues) {
|
||||
queue->ShutDown();
|
||||
delete queue;
|
||||
}
|
||||
|
||||
for (WorkerThread* thread : sSingleton->mWorkerThreads) {
|
||||
// this will block until the thread is joined.
|
||||
delete thread;
|
||||
}
|
||||
|
||||
sSingleton->mWorkerThreads.clear();
|
||||
delete sSingleton;
|
||||
sSingleton = nullptr;
|
||||
}
|
||||
|
||||
JobStatus
|
||||
JobScheduler::ProcessJob(Job* aJob)
|
||||
{
|
||||
MOZ_ASSERT(aJob);
|
||||
auto status = aJob->Run();
|
||||
if (status == JobStatus::Error || status == JobStatus::Complete) {
|
||||
delete aJob;
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
void
|
||||
JobScheduler::SubmitJob(Job* aJob)
|
||||
{
|
||||
MOZ_ASSERT(aJob);
|
||||
RefPtr<SyncObject> start = aJob->GetStartSync();
|
||||
if (start && start->Register(aJob)) {
|
||||
// The Job buffer starts with a non-signaled sync object, it
|
||||
// is now registered in the list of task buffers waiting on the
|
||||
// sync object, so we should not place it in the queue.
|
||||
return;
|
||||
}
|
||||
|
||||
GetQueueForJob(aJob)->SubmitJob(aJob);
|
||||
}
|
||||
|
||||
MultiThreadedJobQueue*
|
||||
JobScheduler::GetQueueForJob(Job* aJob)
|
||||
{
|
||||
return aJob->IsPinnedToAThread() ? aJob->GetWorkerThread()->GetJobQueue()
|
||||
: GetDrawingQueue();
|
||||
}
|
||||
|
||||
Job::Job(SyncObject* aStart, SyncObject* aCompletion, WorkerThread* aThread)
|
||||
: mNextWaitingJob(nullptr)
|
||||
, mStartSync(aStart)
|
||||
, mCompletionSync(aCompletion)
|
||||
, mPinToThread(aThread)
|
||||
{
|
||||
if (mStartSync) {
|
||||
mStartSync->AddSubsequent(this);
|
||||
}
|
||||
if (mCompletionSync) {
|
||||
mCompletionSync->AddPrerequisite(this);
|
||||
}
|
||||
}
|
||||
|
||||
Job::~Job()
|
||||
{
|
||||
if (mCompletionSync) {
|
||||
//printf(" -- Job %p dtor completion %p\n", this, mCompletionSync);
|
||||
mCompletionSync->Signal();
|
||||
mCompletionSync = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
JobStatus
|
||||
SetEventJob::Run()
|
||||
{
|
||||
mEvent->Set();
|
||||
return JobStatus::Complete;
|
||||
}
|
||||
|
||||
SetEventJob::SetEventJob(EventObject* aEvent,
|
||||
SyncObject* aStart, SyncObject* aCompletion,
|
||||
WorkerThread* aWorker)
|
||||
: Job(aStart, aCompletion, aWorker)
|
||||
, mEvent(aEvent)
|
||||
{}
|
||||
|
||||
SetEventJob::~SetEventJob()
|
||||
{}
|
||||
|
||||
SyncObject::SyncObject(uint32_t aNumPrerequisites)
|
||||
: mSignals(aNumPrerequisites)
|
||||
, mFirstWaitingJob(nullptr)
|
||||
#ifdef DEBUG
|
||||
, mNumPrerequisites(aNumPrerequisites)
|
||||
, mAddedPrerequisites(0)
|
||||
#endif
|
||||
{}
|
||||
|
||||
SyncObject::~SyncObject()
|
||||
{
|
||||
MOZ_ASSERT(mFirstWaitingJob == nullptr);
|
||||
}
|
||||
|
||||
bool
|
||||
SyncObject::Register(Job* aJob)
|
||||
{
|
||||
MOZ_ASSERT(aJob);
|
||||
|
||||
// For now, ensure that when we schedule the first subsequent, we have already
|
||||
// created all of the prerequisites. This is an arbitrary restriction because
|
||||
// we specify the number of prerequisites in the constructor, but in the typical
|
||||
// scenario, if the assertion FreezePrerequisite blows up here it probably means
|
||||
// we got the initial nmber of prerequisites wrong. We can decide to remove
|
||||
// this restriction if needed.
|
||||
FreezePrerequisites();
|
||||
|
||||
int32_t signals = mSignals;
|
||||
|
||||
if (signals > 0) {
|
||||
AddWaitingJob(aJob);
|
||||
// Since Register and Signal can be called concurrently, it can happen that
|
||||
// reading mSignals in Register happens before decrementing mSignals in Signal,
|
||||
// but SubmitWaitingJobs happens before AddWaitingJob. This ordering means
|
||||
// the SyncObject ends up in the signaled state with a task sitting in the
|
||||
// waiting list. To prevent that we check mSignals a second time and submit
|
||||
// again if signals reached zero in the mean time.
|
||||
// We do this instead of holding a mutex around mSignals+mJobs to reduce
|
||||
// lock contention.
|
||||
int32_t signals2 = mSignals;
|
||||
if (signals2 == 0) {
|
||||
SubmitWaitingJobs();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void
|
||||
SyncObject::Signal()
|
||||
{
|
||||
int32_t signals = --mSignals;
|
||||
MOZ_ASSERT(signals >= 0);
|
||||
|
||||
if (signals == 0) {
|
||||
SubmitWaitingJobs();
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
SyncObject::AddWaitingJob(Job* aJob)
|
||||
{
|
||||
// Push (using atomics) the task into the list of waiting tasks.
|
||||
for (;;) {
|
||||
Job* first = mFirstWaitingJob;
|
||||
aJob->mNextWaitingJob = first;
|
||||
if (mFirstWaitingJob.compareExchange(first, aJob)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SyncObject::SubmitWaitingJobs()
|
||||
{
|
||||
// Scheduling the tasks can cause code that modifies <this>'s reference
|
||||
// count to run concurrently, and cause the caller of this function to
|
||||
// be owned by another thread. We need to make sure the reference count
|
||||
// does not reach 0 on another thread before the end of this method, so
|
||||
// hold a strong ref to prevent that!
|
||||
RefPtr<SyncObject> kungFuDeathGrip(this);
|
||||
|
||||
// First atomically swap mFirstWaitingJob and waitingJobs...
|
||||
Job* waitingJobs = nullptr;
|
||||
for (;;) {
|
||||
waitingJobs = mFirstWaitingJob;
|
||||
if (mFirstWaitingJob.compareExchange(waitingJobs, nullptr)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// ... and submit all of the waiting tasks in waitingJob now that they belong
|
||||
// to this thread.
|
||||
while (waitingJobs) {
|
||||
Job* next = waitingJobs->mNextWaitingJob;
|
||||
waitingJobs->mNextWaitingJob = nullptr;
|
||||
JobScheduler::GetQueueForJob(waitingJobs)->SubmitJob(waitingJobs);
|
||||
waitingJobs = next;
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
SyncObject::IsSignaled()
|
||||
{
|
||||
return mSignals == 0;
|
||||
}
|
||||
|
||||
void
|
||||
SyncObject::FreezePrerequisites()
|
||||
{
|
||||
MOZ_ASSERT(mAddedPrerequisites == mNumPrerequisites);
|
||||
}
|
||||
|
||||
void
|
||||
SyncObject::AddPrerequisite(Job* aJob)
|
||||
{
|
||||
MOZ_ASSERT(++mAddedPrerequisites <= mNumPrerequisites);
|
||||
}
|
||||
|
||||
void
|
||||
SyncObject::AddSubsequent(Job* aJob)
|
||||
{
|
||||
}
|
||||
|
||||
WorkerThread::WorkerThread(MultiThreadedJobQueue* aJobQueue)
|
||||
: mQueue(aJobQueue)
|
||||
{
|
||||
aJobQueue->RegisterThread();
|
||||
}
|
||||
|
||||
void
|
||||
WorkerThread::Run()
|
||||
{
|
||||
SetName("gfx worker");
|
||||
|
||||
for (;;) {
|
||||
Job* commands = nullptr;
|
||||
if (!mQueue->WaitForJob(commands)) {
|
||||
mQueue->UnregisterThread();
|
||||
return;
|
||||
}
|
||||
|
||||
JobStatus status = JobScheduler::ProcessJob(commands);
|
||||
|
||||
if (status == JobStatus::Error) {
|
||||
// Don't try to handle errors for now, but that's open to discussions.
|
||||
// I expect errors to be mostly OOM issues.
|
||||
MOZ_CRASH();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} //namespace
|
||||
} //namespace
|
|
@ -0,0 +1,250 @@
|
|||
/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#ifndef MOZILLA_GFX_TASKSCHEDULER_H_
|
||||
#define MOZILLA_GFX_TASKSCHEDULER_H_
|
||||
|
||||
#include "mozilla/RefPtr.h"
|
||||
#include "mozilla/gfx/Types.h"
|
||||
|
||||
#ifdef WIN32
|
||||
#include "mozilla/gfx/JobScheduler_win32.h"
|
||||
#else
|
||||
#include "mozilla/gfx/JobScheduler_posix.h"
|
||||
#endif
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace mozilla {
|
||||
namespace gfx {
|
||||
|
||||
class MultiThreadedJobQueue;
|
||||
class SyncObject;
|
||||
class WorkerThread;
|
||||
|
||||
class JobScheduler {
|
||||
public:
|
||||
/// Return one of the queues that the drawing worker threads pull from, chosen
|
||||
/// pseudo-randomly.
|
||||
static MultiThreadedJobQueue* GetDrawingQueue()
|
||||
{
|
||||
return sSingleton->mDrawingQueues[
|
||||
sSingleton->mNextQueue++ % sSingleton->mDrawingQueues.size()
|
||||
];
|
||||
}
|
||||
|
||||
/// Return one of the queues that the drawing worker threads pull from with a
|
||||
/// hash to choose the queue.
|
||||
///
|
||||
/// Calling this function several times with the same hash will yield the same queue.
|
||||
static MultiThreadedJobQueue* GetDrawingQueue(uint32_t aHash)
|
||||
{
|
||||
return sSingleton->mDrawingQueues[
|
||||
aHash % sSingleton->mDrawingQueues.size()
|
||||
];
|
||||
}
|
||||
|
||||
/// Return the task queue associated to the worker the task is pinned to if
|
||||
/// the task is pinned to a worker, or a random queue.
|
||||
static MultiThreadedJobQueue* GetQueueForJob(Job* aJob);
|
||||
|
||||
/// Initialize the task scheduler with aNumThreads worker threads for drawing
|
||||
/// and aNumQueues task queues.
|
||||
///
|
||||
/// The number of threads must be superior or equal to the number of queues
|
||||
/// (since for now a worker thread only pulls from one queue).
|
||||
static bool Init(uint32_t aNumThreads, uint32_t aNumQueues);
|
||||
|
||||
/// Shut the scheduler down.
|
||||
///
|
||||
/// This will block until worker threads are joined and deleted.
|
||||
static void ShutDown();
|
||||
|
||||
/// Returns true if there is a successfully initialized JobScheduler singleton.
|
||||
static bool IsEnabled() { return !!sSingleton; }
|
||||
|
||||
/// Submit a task buffer to its associated queue.
|
||||
///
|
||||
/// The caller looses ownership of the task buffer.
|
||||
static void SubmitJob(Job* aJobs);
|
||||
|
||||
/// Process commands until the command buffer needs to block on a sync object,
|
||||
/// completes, yields, or encounters an error.
|
||||
///
|
||||
/// Can be used on any thread. Worker threads basically loop over this, but the
|
||||
/// main thread can also dequeue pending task buffers and process them alongside
|
||||
/// the worker threads if it is about to block until completion anyway.
|
||||
///
|
||||
/// The caller looses ownership of the task buffer.
|
||||
static JobStatus ProcessJob(Job* aJobs);
|
||||
|
||||
protected:
|
||||
static JobScheduler* sSingleton;
|
||||
|
||||
// queues of Job that are ready to be processed
|
||||
std::vector<MultiThreadedJobQueue*> mDrawingQueues;
|
||||
std::vector<WorkerThread*> mWorkerThreads;
|
||||
Atomic<uint32_t> mNextQueue;
|
||||
};
|
||||
|
||||
/// Jobs are not reference-counted because they don't have shared ownership.
|
||||
/// The ownership of tasks can change when they are passed to certain methods
|
||||
/// of JobScheduler and SyncObject. See the docuumentaion of these classes.
|
||||
class Job {
|
||||
public:
|
||||
Job(SyncObject* aStart, SyncObject* aCompletion, WorkerThread* aThread = nullptr);
|
||||
|
||||
virtual ~Job();
|
||||
|
||||
virtual JobStatus Run() = 0;
|
||||
|
||||
/// For use in JobScheduler::SubmitJob. Don't use it anywhere else.
|
||||
//already_AddRefed<SyncObject> GetAndResetStartSync();
|
||||
SyncObject* GetStartSync() { return mStartSync; }
|
||||
|
||||
bool IsPinnedToAThread() const { return !!mPinToThread; }
|
||||
|
||||
WorkerThread* GetWorkerThread() { return mPinToThread; }
|
||||
|
||||
protected:
|
||||
// An intrusive linked list of tasks waiting for a sync object to enter the
|
||||
// signaled state. When the task is not waiting for a sync object, mNextWaitingJob
|
||||
// should be null. This is only accessed from the thread that owns the task.
|
||||
Job* mNextWaitingJob;
|
||||
|
||||
RefPtr<SyncObject> mStartSync;
|
||||
RefPtr<SyncObject> mCompletionSync;
|
||||
WorkerThread* mPinToThread;
|
||||
|
||||
friend class SyncObject;
|
||||
};
|
||||
|
||||
class EventObject;
|
||||
|
||||
/// This task will set an EventObject.
|
||||
///
|
||||
/// Typically used as the final task, so that the main thread can block on the
|
||||
/// corresponfing EventObject until all of the tasks are processed.
|
||||
class SetEventJob : public Job
|
||||
{
|
||||
public:
|
||||
explicit SetEventJob(EventObject* aEvent,
|
||||
SyncObject* aStart, SyncObject* aCompletion = nullptr,
|
||||
WorkerThread* aPinToWorker = nullptr);
|
||||
|
||||
~SetEventJob();
|
||||
|
||||
JobStatus Run() override;
|
||||
|
||||
EventObject* GetEvent() { return mEvent; }
|
||||
|
||||
protected:
|
||||
RefPtr<EventObject> mEvent;
|
||||
};
|
||||
|
||||
/// A synchronization object that can be used to express dependencies and ordering between
|
||||
/// tasks.
|
||||
///
|
||||
/// Jobs can register to SyncObjects in order to asynchronously wait for a signal.
|
||||
/// In practice, Job objects usually start with a sync object (startSyc) and end
|
||||
/// with another one (completionSync).
|
||||
/// a Job never gets processed before its startSync is in the signaled state, and
|
||||
/// signals its completionSync as soon as it finishes. This is how dependencies
|
||||
/// between tasks is expressed.
|
||||
class SyncObject final : public external::AtomicRefCounted<SyncObject> {
|
||||
public:
|
||||
MOZ_DECLARE_REFCOUNTED_TYPENAME(SyncObject)
|
||||
|
||||
/// Create a synchronization object.
|
||||
///
|
||||
/// aNumPrerequisites represents the number of times the object must be signaled
|
||||
/// before actually entering the signaled state (in other words, it means the
|
||||
/// number of dependencies of this sync object).
|
||||
///
|
||||
/// Explicitly specifying the number of prerequisites when creating sync objects
|
||||
/// makes it easy to start scheduling some of the prerequisite tasks while
|
||||
/// creating the others, which is how we typically use the task scheduler.
|
||||
/// Automatically determining the number of prerequisites using Job's constructor
|
||||
/// brings the risk that the sync object enters the signaled state while we
|
||||
/// are still adding prerequisites which is hard to fix without using muteces.
|
||||
explicit SyncObject(uint32_t aNumPrerequisites = 1);
|
||||
|
||||
~SyncObject();
|
||||
|
||||
/// Attempt to register a task.
|
||||
///
|
||||
/// If the sync object is already in the signaled state, the buffer is *not*
|
||||
/// registered and the sync object does not take ownership of the task.
|
||||
/// If the object is not yet in the signaled state, it takes ownership of
|
||||
/// the task and places it in a list of pending tasks.
|
||||
/// Pending tasks will not be processed by the worker thread.
|
||||
/// When the SyncObject reaches the signaled state, it places the pending
|
||||
/// tasks back in the available buffer queue, so that they can be
|
||||
/// scheduled again.
|
||||
///
|
||||
/// Returns true if the SyncOject is not already in the signaled state.
|
||||
/// This means that if this method returns true, the SyncObject has taken
|
||||
/// ownership of the Job.
|
||||
bool Register(Job* aJob);
|
||||
|
||||
/// Signal the SyncObject.
|
||||
///
|
||||
/// This decrements an internal counter. The sync object reaches the signaled
|
||||
/// state when the counter gets to zero.
|
||||
void Signal();
|
||||
|
||||
/// Returns true if mSignals is equal to zero. In other words, returns true
|
||||
/// if all prerequisite tasks have already signaled the sync object.
|
||||
bool IsSignaled();
|
||||
|
||||
/// Asserts that the number of added prerequisites is equal to the number
|
||||
/// specified in the constructor (does nothin in release builds).
|
||||
void FreezePrerequisites();
|
||||
|
||||
private:
|
||||
// Called by Job's constructor
|
||||
void AddSubsequent(Job* aJob);
|
||||
void AddPrerequisite(Job* aJob);
|
||||
|
||||
void AddWaitingJob(Job* aJob);
|
||||
|
||||
void SubmitWaitingJobs();
|
||||
|
||||
Atomic<int32_t> mSignals;
|
||||
Atomic<Job*> mFirstWaitingJob;
|
||||
|
||||
#ifdef DEBUG
|
||||
uint32_t mNumPrerequisites;
|
||||
Atomic<uint32_t> mAddedPrerequisites;
|
||||
#endif
|
||||
|
||||
friend class Job;
|
||||
friend class JobScheduler;
|
||||
};
|
||||
|
||||
/// Base class for worker threads.
|
||||
class WorkerThread
|
||||
{
|
||||
public:
|
||||
static WorkerThread* Create(MultiThreadedJobQueue* aJobQueue);
|
||||
|
||||
virtual ~WorkerThread() {}
|
||||
|
||||
void Run();
|
||||
|
||||
MultiThreadedJobQueue* GetJobQueue() { return mQueue; }
|
||||
|
||||
protected:
|
||||
explicit WorkerThread(MultiThreadedJobQueue* aJobQueue);
|
||||
|
||||
virtual void SetName(const char* aName) {}
|
||||
|
||||
MultiThreadedJobQueue* mQueue;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
} // namespace
|
||||
|
||||
#endif
|
|
@ -0,0 +1,194 @@
|
|||
/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "JobScheduler.h"
|
||||
#include "mozilla/gfx/Logging.h"
|
||||
|
||||
using namespace std;
|
||||
|
||||
namespace mozilla {
|
||||
namespace gfx {
|
||||
|
||||
void* ThreadCallback(void* threadData);
|
||||
|
||||
class WorkerThreadPosix : public WorkerThread {
|
||||
public:
|
||||
explicit WorkerThreadPosix(MultiThreadedJobQueue* aJobQueue)
|
||||
: WorkerThread(aJobQueue)
|
||||
{
|
||||
pthread_create(&mThread, nullptr, ThreadCallback, static_cast<WorkerThread*>(this));
|
||||
}
|
||||
|
||||
~WorkerThreadPosix()
|
||||
{
|
||||
pthread_join(mThread, nullptr);
|
||||
}
|
||||
|
||||
virtual void SetName(const char*) override
|
||||
{
|
||||
// XXX - temporarily disabled, see bug 1209039
|
||||
//
|
||||
// // Call this from the thread itself because of Mac.
|
||||
//#ifdef XP_MACOSX
|
||||
// pthread_setname_np(aName);
|
||||
//#elif defined(__DragonFly__) || defined(__FreeBSD__) || defined(__OpenBSD__)
|
||||
// pthread_set_name_np(mThread, aName);
|
||||
//#elif defined(__NetBSD__)
|
||||
// pthread_setname_np(mThread, "%s", (void*)aName);
|
||||
//#else
|
||||
// pthread_setname_np(mThread, aName);
|
||||
//#endif
|
||||
}
|
||||
|
||||
protected:
|
||||
pthread_t mThread;
|
||||
};
|
||||
|
||||
void* ThreadCallback(void* threadData)
|
||||
{
|
||||
WorkerThread* thread = static_cast<WorkerThread*>(threadData);
|
||||
thread->Run();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
WorkerThread*
|
||||
WorkerThread::Create(MultiThreadedJobQueue* aJobQueue)
|
||||
{
|
||||
return new WorkerThreadPosix(aJobQueue);
|
||||
}
|
||||
|
||||
MultiThreadedJobQueue::MultiThreadedJobQueue()
|
||||
: mThreadsCount(0)
|
||||
, mShuttingDown(false)
|
||||
{}
|
||||
|
||||
MultiThreadedJobQueue::~MultiThreadedJobQueue()
|
||||
{
|
||||
MOZ_ASSERT(mJobs.empty());
|
||||
}
|
||||
|
||||
bool
|
||||
MultiThreadedJobQueue::WaitForJob(Job*& aOutJob)
|
||||
{
|
||||
return PopJob(aOutJob, BLOCKING);
|
||||
}
|
||||
|
||||
bool
|
||||
MultiThreadedJobQueue::PopJob(Job*& aOutJobs, AccessType aAccess)
|
||||
{
|
||||
for (;;) {
|
||||
MutexAutoLock lock(&mMutex);
|
||||
|
||||
while (aAccess == BLOCKING && !mShuttingDown && mJobs.empty()) {
|
||||
mAvailableCondvar.Wait(&mMutex);
|
||||
}
|
||||
|
||||
if (mShuttingDown) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (mJobs.empty()) {
|
||||
if (aAccess == NON_BLOCKING) {
|
||||
return false;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
Job* task = mJobs.front();
|
||||
MOZ_ASSERT(task);
|
||||
|
||||
mJobs.pop_front();
|
||||
|
||||
aOutJobs = task;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
MultiThreadedJobQueue::SubmitJob(Job* aJobs)
|
||||
{
|
||||
MOZ_ASSERT(aJobs);
|
||||
MutexAutoLock lock(&mMutex);
|
||||
mJobs.push_back(aJobs);
|
||||
mAvailableCondvar.Broadcast();
|
||||
}
|
||||
|
||||
size_t
|
||||
MultiThreadedJobQueue::NumJobs()
|
||||
{
|
||||
MutexAutoLock lock(&mMutex);
|
||||
return mJobs.size();
|
||||
}
|
||||
|
||||
bool
|
||||
MultiThreadedJobQueue::IsEmpty()
|
||||
{
|
||||
MutexAutoLock lock(&mMutex);
|
||||
return mJobs.empty();
|
||||
}
|
||||
|
||||
void
|
||||
MultiThreadedJobQueue::ShutDown()
|
||||
{
|
||||
MutexAutoLock lock(&mMutex);
|
||||
mShuttingDown = true;
|
||||
while (mThreadsCount) {
|
||||
mAvailableCondvar.Broadcast();
|
||||
mShutdownCondvar.Wait(&mMutex);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
MultiThreadedJobQueue::RegisterThread()
|
||||
{
|
||||
mThreadsCount += 1;
|
||||
}
|
||||
|
||||
void
|
||||
MultiThreadedJobQueue::UnregisterThread()
|
||||
{
|
||||
MutexAutoLock lock(&mMutex);
|
||||
mThreadsCount -= 1;
|
||||
if (mThreadsCount == 0) {
|
||||
mShutdownCondvar.Broadcast();
|
||||
}
|
||||
}
|
||||
|
||||
EventObject::EventObject()
|
||||
: mIsSet(false)
|
||||
{}
|
||||
|
||||
EventObject::~EventObject()
|
||||
{}
|
||||
|
||||
bool
|
||||
EventObject::Peak()
|
||||
{
|
||||
MutexAutoLock lock(&mMutex);
|
||||
return mIsSet;
|
||||
}
|
||||
|
||||
void
|
||||
EventObject::Set()
|
||||
{
|
||||
MutexAutoLock lock(&mMutex);
|
||||
if (!mIsSet) {
|
||||
mIsSet = true;
|
||||
mCond.Broadcast();
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
EventObject::Wait()
|
||||
{
|
||||
MutexAutoLock lock(&mMutex);
|
||||
if (mIsSet) {
|
||||
return;
|
||||
}
|
||||
mCond.Wait(&mMutex);
|
||||
}
|
||||
|
||||
} // namespce
|
||||
} // namespce
|
|
@ -0,0 +1,144 @@
|
|||
/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#ifndef WIN32
|
||||
#ifndef MOZILLA_GFX_TASKSCHEDULER_POSIX_H_
|
||||
#define MOZILLA_GFX_TASKSCHEDULER_POSIX_H_
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <list>
|
||||
#include <pthread.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#include "mozilla/RefPtr.h"
|
||||
#include "mozilla/DebugOnly.h"
|
||||
#include "mozilla/gfx/CriticalSection.h"
|
||||
|
||||
namespace mozilla {
|
||||
namespace gfx {
|
||||
|
||||
class Job;
|
||||
class PosixCondVar;
|
||||
class WorkerThread;
|
||||
|
||||
typedef mozilla::gfx::CriticalSection Mutex;
|
||||
typedef mozilla::gfx::CriticalSectionAutoEnter MutexAutoLock;
|
||||
|
||||
// posix platforms only!
|
||||
class PosixCondVar {
|
||||
public:
|
||||
PosixCondVar() {
|
||||
DebugOnly<int> err = pthread_cond_init(&mCond, nullptr);
|
||||
MOZ_ASSERT(!err);
|
||||
}
|
||||
|
||||
~PosixCondVar() {
|
||||
DebugOnly<int> err = pthread_cond_destroy(&mCond);
|
||||
MOZ_ASSERT(!err);
|
||||
}
|
||||
|
||||
void Wait(Mutex* aMutex) {
|
||||
DebugOnly<int> err = pthread_cond_wait(&mCond, &aMutex->mMutex);
|
||||
MOZ_ASSERT(!err);
|
||||
}
|
||||
|
||||
void Broadcast() {
|
||||
DebugOnly<int> err = pthread_cond_broadcast(&mCond);
|
||||
MOZ_ASSERT(!err);
|
||||
}
|
||||
|
||||
protected:
|
||||
pthread_cond_t mCond;
|
||||
};
|
||||
|
||||
|
||||
/// A simple and naive multithreaded task queue
|
||||
///
|
||||
/// The public interface of this class must remain identical to its equivalent
|
||||
/// in JobScheduler_win32.h
|
||||
class MultiThreadedJobQueue {
|
||||
public:
|
||||
enum AccessType {
|
||||
BLOCKING,
|
||||
NON_BLOCKING
|
||||
};
|
||||
|
||||
// Producer thread
|
||||
MultiThreadedJobQueue();
|
||||
|
||||
// Producer thread
|
||||
~MultiThreadedJobQueue();
|
||||
|
||||
// Worker threads
|
||||
bool WaitForJob(Job*& aOutJob);
|
||||
|
||||
// Any thread
|
||||
bool PopJob(Job*& aOutJob, AccessType aAccess);
|
||||
|
||||
// Any threads
|
||||
void SubmitJob(Job* aJob);
|
||||
|
||||
// Producer thread
|
||||
void ShutDown();
|
||||
|
||||
// Any thread
|
||||
size_t NumJobs();
|
||||
|
||||
// Any thread
|
||||
bool IsEmpty();
|
||||
|
||||
// Producer thread
|
||||
void RegisterThread();
|
||||
|
||||
// Worker threads
|
||||
void UnregisterThread();
|
||||
|
||||
protected:
|
||||
|
||||
std::list<Job*> mJobs;
|
||||
Mutex mMutex;
|
||||
PosixCondVar mAvailableCondvar;
|
||||
PosixCondVar mShutdownCondvar;
|
||||
int32_t mThreadsCount;
|
||||
bool mShuttingDown;
|
||||
|
||||
friend class WorkerThread;
|
||||
};
|
||||
|
||||
/// An object that a thread can synchronously wait on.
|
||||
/// Usually set by a SetEventJob.
|
||||
class EventObject : public external::AtomicRefCounted<EventObject>
|
||||
{
|
||||
public:
|
||||
MOZ_DECLARE_REFCOUNTED_TYPENAME(EventObject)
|
||||
|
||||
EventObject();
|
||||
|
||||
~EventObject();
|
||||
|
||||
/// Synchronously wait until the event is set.
|
||||
void Wait();
|
||||
|
||||
/// Return true if the event is set, without blocking.
|
||||
bool Peak();
|
||||
|
||||
/// Set the event.
|
||||
void Set();
|
||||
|
||||
protected:
|
||||
Mutex mMutex;
|
||||
PosixCondVar mCond;
|
||||
bool mIsSet;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
} // namespace
|
||||
|
||||
#include "JobScheduler.h"
|
||||
|
||||
#endif
|
||||
#endif
|
|
@ -0,0 +1,143 @@
|
|||
/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "JobScheduler.h"
|
||||
#include "mozilla/gfx/Logging.h"
|
||||
|
||||
using namespace std;
|
||||
|
||||
namespace mozilla {
|
||||
namespace gfx {
|
||||
|
||||
DWORD __stdcall ThreadCallback(void* threadData);
|
||||
|
||||
class WorkerThreadWin32 : public WorkerThread {
|
||||
public:
|
||||
explicit WorkerThreadWin32(MultiThreadedJobQueue* aJobQueue)
|
||||
: WorkerThread(aJobQueue)
|
||||
{
|
||||
mThread = ::CreateThread(nullptr, 0, ThreadCallback, static_cast<WorkerThread*>(this), 0, nullptr);
|
||||
}
|
||||
|
||||
~WorkerThreadWin32()
|
||||
{
|
||||
::WaitForSingleObject(mThread, INFINITE);
|
||||
::CloseHandle(mThread);
|
||||
}
|
||||
|
||||
protected:
|
||||
HANDLE mThread;
|
||||
};
|
||||
|
||||
DWORD __stdcall ThreadCallback(void* threadData)
|
||||
{
|
||||
WorkerThread* thread = static_cast<WorkerThread*>(threadData);
|
||||
thread->Run();
|
||||
return 0;
|
||||
}
|
||||
|
||||
WorkerThread*
|
||||
WorkerThread::Create(MultiThreadedJobQueue* aJobQueue)
|
||||
{
|
||||
return new WorkerThreadWin32(aJobQueue);
|
||||
}
|
||||
|
||||
bool
|
||||
MultiThreadedJobQueue::PopJob(Job*& aOutJob, AccessType aAccess)
|
||||
{
|
||||
for (;;) {
|
||||
while (aAccess == BLOCKING && mJobs.empty()) {
|
||||
{
|
||||
CriticalSectionAutoEnter lock(&mSection);
|
||||
if (mShuttingDown) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
HANDLE handles[] = { mAvailableEvent, mShutdownEvent };
|
||||
::WaitForMultipleObjects(2, handles, FALSE, INFINITE);
|
||||
}
|
||||
|
||||
CriticalSectionAutoEnter lock(&mSection);
|
||||
|
||||
if (mShuttingDown) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (mJobs.empty()) {
|
||||
if (aAccess == NON_BLOCKING) {
|
||||
return false;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
Job* task = mJobs.front();
|
||||
MOZ_ASSERT(task);
|
||||
|
||||
mJobs.pop_front();
|
||||
|
||||
if (mJobs.empty()) {
|
||||
::ResetEvent(mAvailableEvent);
|
||||
}
|
||||
|
||||
aOutJob = task;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
MultiThreadedJobQueue::SubmitJob(Job* aJob)
|
||||
{
|
||||
MOZ_ASSERT(aJob);
|
||||
CriticalSectionAutoEnter lock(&mSection);
|
||||
mJobs.push_back(aJob);
|
||||
::SetEvent(mAvailableEvent);
|
||||
}
|
||||
|
||||
void
|
||||
MultiThreadedJobQueue::ShutDown()
|
||||
{
|
||||
{
|
||||
CriticalSectionAutoEnter lock(&mSection);
|
||||
mShuttingDown = true;
|
||||
}
|
||||
while (mThreadsCount) {
|
||||
::SetEvent(mAvailableEvent);
|
||||
::WaitForSingleObject(mShutdownEvent, INFINITE);
|
||||
}
|
||||
}
|
||||
|
||||
size_t
|
||||
MultiThreadedJobQueue::NumJobs()
|
||||
{
|
||||
CriticalSectionAutoEnter lock(&mSection);
|
||||
return mJobs.size();
|
||||
}
|
||||
|
||||
bool
|
||||
MultiThreadedJobQueue::IsEmpty()
|
||||
{
|
||||
CriticalSectionAutoEnter lock(&mSection);
|
||||
return mJobs.empty();
|
||||
}
|
||||
|
||||
void
|
||||
MultiThreadedJobQueue::RegisterThread()
|
||||
{
|
||||
mThreadsCount += 1;
|
||||
}
|
||||
|
||||
void
|
||||
MultiThreadedJobQueue::UnregisterThread()
|
||||
{
|
||||
CriticalSectionAutoEnter lock(&mSection);
|
||||
mThreadsCount -= 1;
|
||||
if (mThreadsCount == 0) {
|
||||
::SetEvent(mShutdownEvent);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace
|
|
@ -0,0 +1,98 @@
|
|||
/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#ifdef WIN32
|
||||
#ifndef MOZILLA_GFX_TASKSCHEDULER_WIN32_H_
|
||||
#define MOZILLA_GFX_TASKSCHEDULER_WIN32_H_
|
||||
|
||||
#include <windows.h>
|
||||
#include <list>
|
||||
|
||||
#include "mozilla/RefPtr.h"
|
||||
#include "mozilla/gfx/CriticalSection.h"
|
||||
|
||||
namespace mozilla {
|
||||
namespace gfx {
|
||||
|
||||
class WorkerThread;
|
||||
class Job;
|
||||
|
||||
// The public interface of this class must remain identical to its equivalent
|
||||
// in JobScheduler_posix.h
|
||||
class MultiThreadedJobQueue {
|
||||
public:
|
||||
enum AccessType {
|
||||
BLOCKING,
|
||||
NON_BLOCKING
|
||||
};
|
||||
|
||||
MultiThreadedJobQueue()
|
||||
: mThreadsCount(0)
|
||||
, mShuttingDown(false)
|
||||
{
|
||||
mAvailableEvent = ::CreateEventW(nullptr, TRUE, FALSE, nullptr);
|
||||
mShutdownEvent = ::CreateEventW(nullptr, TRUE, FALSE, nullptr);
|
||||
}
|
||||
|
||||
~MultiThreadedJobQueue()
|
||||
{
|
||||
::CloseHandle(mAvailableEvent);
|
||||
::CloseHandle(mShutdownEvent);
|
||||
}
|
||||
|
||||
bool WaitForJob(Job*& aOutJob) { return PopJob(aOutJob, BLOCKING); }
|
||||
|
||||
bool PopJob(Job*& aOutJob, AccessType aAccess);
|
||||
|
||||
void SubmitJob(Job* aJob);
|
||||
|
||||
void ShutDown();
|
||||
|
||||
size_t NumJobs();
|
||||
|
||||
bool IsEmpty();
|
||||
|
||||
void RegisterThread();
|
||||
|
||||
void UnregisterThread();
|
||||
|
||||
protected:
|
||||
std::list<Job*> mJobs;
|
||||
CriticalSection mSection;
|
||||
HANDLE mAvailableEvent;
|
||||
HANDLE mShutdownEvent;
|
||||
int32_t mThreadsCount;
|
||||
bool mShuttingDown;
|
||||
|
||||
friend class WorkerThread;
|
||||
};
|
||||
|
||||
|
||||
// The public interface of this class must remain identical to its equivalent
|
||||
// in JobScheduler_posix.h
|
||||
class EventObject : public external::AtomicRefCounted<EventObject>
|
||||
{
|
||||
public:
|
||||
MOZ_DECLARE_REFCOUNTED_TYPENAME(EventObject)
|
||||
|
||||
EventObject() { mEvent = ::CreateEventW(nullptr, TRUE, FALSE, nullptr); }
|
||||
|
||||
~EventObject() { ::CloseHandle(mEvent); }
|
||||
|
||||
void Wait() { ::WaitForSingleObject(mEvent, INFINITE); }
|
||||
|
||||
bool Peak() { return ::WaitForSingleObject(mEvent, 0) == WAIT_OBJECT_0; }
|
||||
|
||||
void Set() { ::SetEvent(mEvent); }
|
||||
protected:
|
||||
// TODO: it's expensive to create events so we should try to reuse them
|
||||
HANDLE mEvent;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
} // namespace
|
||||
|
||||
#endif
|
||||
#endif
|
|
@ -289,6 +289,13 @@ struct GradientStop
|
|||
Color color;
|
||||
};
|
||||
|
||||
enum class JobStatus {
|
||||
Complete,
|
||||
Wait,
|
||||
Yield,
|
||||
Error
|
||||
};
|
||||
|
||||
} // namespace gfx
|
||||
} // namespace mozilla
|
||||
|
||||
|
|
|
@ -20,11 +20,16 @@ EXPORTS.mozilla.gfx += [
|
|||
'Blur.h',
|
||||
'BorrowedContext.h',
|
||||
'Coord.h',
|
||||
'CriticalSection.h',
|
||||
'DataSurfaceHelpers.h',
|
||||
'DrawTargetTiled.h',
|
||||
'Filters.h',
|
||||
'Helpers.h',
|
||||
'HelpersCairo.h',
|
||||
'IterableArena.h',
|
||||
'JobScheduler.h',
|
||||
'JobScheduler_posix.h',
|
||||
'JobScheduler_win32.h',
|
||||
'Logging.h',
|
||||
'Matrix.h',
|
||||
'NumericTools.h',
|
||||
|
@ -60,6 +65,7 @@ elif CONFIG['MOZ_WIDGET_TOOLKIT'] == 'windows':
|
|||
'DrawTargetD2D1.cpp',
|
||||
'ExtendInputEffectD2D1.cpp',
|
||||
'FilterNodeD2D1.cpp',
|
||||
'JobScheduler_win32.cpp',
|
||||
'PathD2D.cpp',
|
||||
'RadialGradientEffectD2D1.cpp',
|
||||
'ScaledFontDWrite.cpp',
|
||||
|
@ -70,6 +76,11 @@ elif CONFIG['MOZ_WIDGET_TOOLKIT'] == 'windows':
|
|||
]
|
||||
DEFINES['WIN32'] = True
|
||||
|
||||
if CONFIG['MOZ_WIDGET_TOOLKIT'] != 'windows':
|
||||
SOURCES += [
|
||||
'JobScheduler_posix.cpp',
|
||||
]
|
||||
|
||||
if CONFIG['MOZ_ENABLE_SKIA']:
|
||||
UNIFIED_SOURCES += [
|
||||
'convolver.cpp',
|
||||
|
@ -118,6 +129,7 @@ UNIFIED_SOURCES += [
|
|||
'DataSourceSurface.cpp',
|
||||
'DataSurfaceHelpers.cpp',
|
||||
'DrawEventRecorder.cpp',
|
||||
'DrawingJob.cpp',
|
||||
'DrawTarget.cpp',
|
||||
'DrawTargetCairo.cpp',
|
||||
'DrawTargetCapture.cpp',
|
||||
|
@ -129,6 +141,7 @@ UNIFIED_SOURCES += [
|
|||
'FilterProcessing.cpp',
|
||||
'FilterProcessingScalar.cpp',
|
||||
'ImageScaling.cpp',
|
||||
'JobScheduler.cpp',
|
||||
'Matrix.cpp',
|
||||
'Path.cpp',
|
||||
'PathCairo.cpp',
|
||||
|
|
|
@ -7,16 +7,21 @@
|
|||
#define GFX_LAYERSTYPES_H
|
||||
|
||||
#include <stdint.h> // for uint32_t
|
||||
#include "mozilla/gfx/Point.h" // for IntPoint
|
||||
#include "nsRegion.h"
|
||||
|
||||
#include "mozilla/TypedEnumBits.h"
|
||||
|
||||
#ifdef MOZ_WIDGET_GONK
|
||||
#include <utils/RefBase.h>
|
||||
#if ANDROID_VERSION >= 21
|
||||
#include <utils/NativeHandle.h>
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#include "mozilla/gfx/Point.h" // for IntPoint
|
||||
#include "mozilla/TypedEnumBits.h"
|
||||
#include "nsRegion.h"
|
||||
|
||||
#include <stdio.h> // FILE
|
||||
#include "mozilla/Logging.h" // for PR_LOG
|
||||
|
||||
#ifndef MOZ_LAYERS_HAVE_LOG
|
||||
# define MOZ_LAYERS_HAVE_LOG
|
||||
#endif
|
||||
|
@ -110,6 +115,14 @@ struct LayerRenderState {
|
|||
|
||||
void SetOverlayId(const int32_t& aId)
|
||||
{ mOverlayId = aId; }
|
||||
|
||||
android::GraphicBuffer* GetGrallocBuffer() const
|
||||
{ return mSurface.get(); }
|
||||
|
||||
#if ANDROID_VERSION >= 21
|
||||
android::NativeHandle* GetSidebandStream() const
|
||||
{ return mSidebandStream.get(); }
|
||||
#endif
|
||||
#endif
|
||||
|
||||
void SetOffset(const nsIntPoint& aOffset)
|
||||
|
@ -133,6 +146,9 @@ struct LayerRenderState {
|
|||
// size of mSurface
|
||||
gfx::IntSize mSize;
|
||||
TextureHost* mTexture;
|
||||
#if ANDROID_VERSION >= 21
|
||||
android::sp<android::NativeHandle> mSidebandStream;
|
||||
#endif
|
||||
#endif
|
||||
};
|
||||
|
||||
|
|
|
@ -417,13 +417,11 @@ ClientTiledPaintedLayer::RenderLayer()
|
|||
}
|
||||
|
||||
if (!mContentClient) {
|
||||
#if defined(MOZ_B2G) || defined(XP_MACOSX)
|
||||
if (mCreationHint == LayerManager::NONE &&
|
||||
SingleTiledContentClient::ClientSupportsLayerSize(layerSize, ClientManager())) {
|
||||
SingleTiledContentClient::ClientSupportsLayerSize(layerSize, ClientManager()) &&
|
||||
gfxPrefs::LayersSingleTileEnabled()) {
|
||||
mContentClient = new SingleTiledContentClient(this, ClientManager());
|
||||
} else
|
||||
#endif
|
||||
{
|
||||
} else {
|
||||
mContentClient = new MultiTiledContentClient(this, ClientManager());
|
||||
}
|
||||
|
||||
|
@ -567,7 +565,6 @@ ClientTiledPaintedLayer::RenderLayer()
|
|||
bool
|
||||
ClientTiledPaintedLayer::IsOptimizedFor(LayerManager::PaintedLayerCreationHint aHint)
|
||||
{
|
||||
#if defined(MOZ_B2G) || defined(XP_MACOSX)
|
||||
// The only creation hint is whether the layer is scrollable or not, and this
|
||||
// is only respected on B2G and OSX, where it's used to determine whether to
|
||||
// use a tiled content client or not.
|
||||
|
@ -575,9 +572,6 @@ ClientTiledPaintedLayer::IsOptimizedFor(LayerManager::PaintedLayerCreationHint a
|
|||
// large, scrollable layers, so we want the layer to be recreated in this
|
||||
// situation.
|
||||
return aHint == GetCreationHint();
|
||||
#else
|
||||
return true;
|
||||
#endif
|
||||
}
|
||||
|
||||
void
|
||||
|
|
|
@ -0,0 +1,188 @@
|
|||
/* vim:set ts=2 sw=2 sts=2 et: */
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/
|
||||
*/
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
#include "gmock/gmock.h"
|
||||
|
||||
#include "mozilla/gfx/IterableArena.h"
|
||||
#include <string>
|
||||
|
||||
using namespace mozilla;
|
||||
using namespace mozilla::gfx;
|
||||
|
||||
#ifdef A
|
||||
#undef A
|
||||
#endif
|
||||
|
||||
#ifdef B
|
||||
#undef B
|
||||
#endif
|
||||
|
||||
// to avoid having symbols that collide easily like A and B in the global namespace
|
||||
namespace test_arena {
|
||||
|
||||
class A;
|
||||
class B;
|
||||
|
||||
class Base {
|
||||
public:
|
||||
virtual ~Base() {}
|
||||
virtual A* AsA() { return nullptr; }
|
||||
virtual B* AsB() { return nullptr; }
|
||||
};
|
||||
|
||||
static int sDtorItemA = 0;
|
||||
static int sDtorItemB = 0;
|
||||
|
||||
class A : public Base {
|
||||
public:
|
||||
virtual A* AsA() override { return this; }
|
||||
|
||||
explicit A(uint64_t val) : mVal(val) {}
|
||||
~A() { ++sDtorItemA; }
|
||||
|
||||
uint64_t mVal;
|
||||
};
|
||||
|
||||
class B : public Base {
|
||||
public:
|
||||
virtual B* AsB() override { return this; }
|
||||
|
||||
explicit B(const string& str) : mVal(str) {}
|
||||
~B() { ++sDtorItemB; }
|
||||
|
||||
std::string mVal;
|
||||
};
|
||||
|
||||
struct BigStruct {
|
||||
uint64_t mVal;
|
||||
uint8_t data[120];
|
||||
|
||||
explicit BigStruct(uint64_t val) : mVal(val) {}
|
||||
};
|
||||
|
||||
void TestArenaAlloc(IterableArena::ArenaType aType)
|
||||
{
|
||||
sDtorItemA = 0;
|
||||
sDtorItemB = 0;
|
||||
IterableArena arena(aType, 256);
|
||||
|
||||
// An empty arena has no items to iterate over.
|
||||
{
|
||||
int iterations = 0;
|
||||
arena.ForEach([&](void* item){
|
||||
iterations++;
|
||||
});
|
||||
ASSERT_EQ(iterations, 0);
|
||||
}
|
||||
|
||||
auto a1 = arena.Alloc<A>(42);
|
||||
auto b1 = arena.Alloc<B>("Obladi oblada");
|
||||
auto a2 = arena.Alloc<A>(1337);
|
||||
auto b2 = arena.Alloc<B>("Yellow submarine");
|
||||
auto b3 = arena.Alloc<B>("She's got a ticket to ride");
|
||||
|
||||
// Alloc returns a non-negative offset if the allocation succeeded.
|
||||
ASSERT_TRUE(a1 >= 0);
|
||||
ASSERT_TRUE(a2 >= 0);
|
||||
ASSERT_TRUE(b1 >= 0);
|
||||
ASSERT_TRUE(b2 >= 0);
|
||||
ASSERT_TRUE(b3 >= 0);
|
||||
|
||||
ASSERT_TRUE(arena.GetStorage(a1) != nullptr);
|
||||
ASSERT_TRUE(arena.GetStorage(a2) != nullptr);
|
||||
ASSERT_TRUE(arena.GetStorage(b1) != nullptr);
|
||||
ASSERT_TRUE(arena.GetStorage(b2) != nullptr);
|
||||
ASSERT_TRUE(arena.GetStorage(b3) != nullptr);
|
||||
|
||||
ASSERT_TRUE(((Base*)arena.GetStorage(a1))->AsA() != nullptr);
|
||||
ASSERT_TRUE(((Base*)arena.GetStorage(a2))->AsA() != nullptr);
|
||||
|
||||
ASSERT_TRUE(((Base*)arena.GetStorage(b1))->AsB() != nullptr);
|
||||
ASSERT_TRUE(((Base*)arena.GetStorage(b2))->AsB() != nullptr);
|
||||
ASSERT_TRUE(((Base*)arena.GetStorage(b3))->AsB() != nullptr);
|
||||
|
||||
ASSERT_EQ(((Base*)arena.GetStorage(a1))->AsA()->mVal, (uint64_t)42);
|
||||
ASSERT_EQ(((Base*)arena.GetStorage(a2))->AsA()->mVal, (uint64_t)1337);
|
||||
|
||||
ASSERT_EQ(((Base*)arena.GetStorage(b1))->AsB()->mVal, std::string("Obladi oblada"));
|
||||
ASSERT_EQ(((Base*)arena.GetStorage(b2))->AsB()->mVal, std::string("Yellow submarine"));
|
||||
ASSERT_EQ(((Base*)arena.GetStorage(b3))->AsB()->mVal, std::string("She's got a ticket to ride"));
|
||||
|
||||
{
|
||||
int iterations = 0;
|
||||
arena.ForEach([&](void* item){
|
||||
iterations++;
|
||||
});
|
||||
ASSERT_EQ(iterations, 5);
|
||||
}
|
||||
|
||||
// Typically, running the destructors of the elements in the arena will is done
|
||||
// manually like this:
|
||||
arena.ForEach([](void* item){
|
||||
((Base*)item)->~Base();
|
||||
});
|
||||
arena.Clear();
|
||||
ASSERT_EQ(sDtorItemA, 2);
|
||||
ASSERT_EQ(sDtorItemB, 3);
|
||||
|
||||
// An empty arena has no items to iterate over (we just cleared it).
|
||||
{
|
||||
int iterations = 0;
|
||||
arena.ForEach([&](void* item){
|
||||
iterations++;
|
||||
});
|
||||
ASSERT_EQ(iterations, 0);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void TestArenaLimit(IterableArena::ArenaType aType, bool aShouldReachLimit)
|
||||
{
|
||||
IterableArena arena(aType, 128);
|
||||
|
||||
// A non-growable arena should return a negative offset when running out
|
||||
// of space, without crashing.
|
||||
// We should not run out of space with a growable arena (unless the os is
|
||||
// running out of memory but this isn't expected for this test).
|
||||
bool reachedLimit = false;
|
||||
for (int i = 0; i < 100; ++i) {
|
||||
auto offset = arena.Alloc<A>(42);
|
||||
if (offset < 0) {
|
||||
reachedLimit = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
ASSERT_EQ(reachedLimit, aShouldReachLimit);
|
||||
}
|
||||
|
||||
} // namespace test_arena
|
||||
|
||||
using namespace test_arena;
|
||||
|
||||
TEST(Moz2D, FixedArena) {
|
||||
TestArenaAlloc(IterableArena::FIXED_SIZE);
|
||||
TestArenaLimit(IterableArena::FIXED_SIZE, true);
|
||||
}
|
||||
|
||||
TEST(Moz2D, GrowableArena) {
|
||||
TestArenaAlloc(IterableArena::GROWABLE);
|
||||
TestArenaLimit(IterableArena::GROWABLE, false);
|
||||
|
||||
IterableArena arena(IterableArena::GROWABLE, 16);
|
||||
// sizeof(BigStruct) is more than twice the initial capacity, make sure that
|
||||
// this doesn't blow everything up, since the arena doubles its storage size each
|
||||
// time it grows (until it finds a size that fits).
|
||||
auto a = arena.Alloc<BigStruct>(1);
|
||||
auto b = arena.Alloc<BigStruct>(2);
|
||||
auto c = arena.Alloc<BigStruct>(3);
|
||||
|
||||
// Offsets should also still point to the appropriate values after reallocation.
|
||||
ASSERT_EQ(((BigStruct*)arena.GetStorage(a))->mVal, (uint64_t)1);
|
||||
ASSERT_EQ(((BigStruct*)arena.GetStorage(b))->mVal, (uint64_t)2);
|
||||
ASSERT_EQ(((BigStruct*)arena.GetStorage(c))->mVal, (uint64_t)3);
|
||||
|
||||
arena.Clear();
|
||||
}
|
|
@ -0,0 +1,247 @@
|
|||
/* vim:set ts=2 sw=2 sts=2 et: */
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/
|
||||
*/
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
#include "gmock/gmock.h"
|
||||
|
||||
#include "mozilla/gfx/JobScheduler.h"
|
||||
|
||||
#ifndef WIN32
|
||||
#include <pthread.h>
|
||||
#include <sched.h>
|
||||
#endif
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <time.h>
|
||||
|
||||
namespace test_scheduler {
|
||||
|
||||
using namespace mozilla::gfx;
|
||||
using namespace mozilla;
|
||||
|
||||
// Artificially cause threads to yield randomly in an attempt to make racy
|
||||
// things more apparent (if any).
|
||||
void MaybeYieldThread()
|
||||
{
|
||||
#ifndef WIN32
|
||||
if (rand() % 5 == 0) {
|
||||
sched_yield();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
/// Used by the TestCommand to check that tasks are processed in the right order.
|
||||
struct SanityChecker {
|
||||
std::vector<uint64_t> mAdvancements;
|
||||
mozilla::gfx::CriticalSection mSection;
|
||||
|
||||
explicit SanityChecker(uint64_t aNumCmdBuffers)
|
||||
{
|
||||
for (uint32_t i = 0; i < aNumCmdBuffers; ++i) {
|
||||
mAdvancements.push_back(0);
|
||||
}
|
||||
}
|
||||
|
||||
virtual void Check(uint64_t aJobId, uint64_t aCmdId)
|
||||
{
|
||||
MaybeYieldThread();
|
||||
CriticalSectionAutoEnter lock(&mSection);
|
||||
ASSERT_EQ(mAdvancements[aJobId], aCmdId-1);
|
||||
mAdvancements[aJobId] = aCmdId;
|
||||
}
|
||||
};
|
||||
|
||||
/// Run checks that are specific to TestSchulerJoin.
|
||||
struct JoinTestSanityCheck : public SanityChecker {
|
||||
bool mSpecialJobHasRun;
|
||||
|
||||
explicit JoinTestSanityCheck(uint64_t aNumCmdBuffers)
|
||||
: SanityChecker(aNumCmdBuffers)
|
||||
, mSpecialJobHasRun(false)
|
||||
{}
|
||||
|
||||
virtual void Check(uint64_t aJobId, uint64_t aCmdId) override
|
||||
{
|
||||
// Job 0 is the special task executed when everything is joined after task 1
|
||||
if (aCmdId == 0) {
|
||||
ASSERT_FALSE(mSpecialJobHasRun);
|
||||
mSpecialJobHasRun = true;
|
||||
for (auto advancement : mAdvancements) {
|
||||
// Because of the synchronization point (beforeFilter), all
|
||||
// task buffers should have run task 1 when task 0 is run.
|
||||
ASSERT_EQ(advancement, (uint32_t)1);
|
||||
}
|
||||
} else {
|
||||
// This check does not apply to task 0.
|
||||
SanityChecker::Check(aJobId, aCmdId);
|
||||
}
|
||||
|
||||
if (aCmdId == 2) {
|
||||
ASSERT_TRUE(mSpecialJobHasRun);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
class TestJob : public Job
|
||||
{
|
||||
public:
|
||||
TestJob(uint64_t aCmdId, uint64_t aJobId, SanityChecker* aChecker,
|
||||
SyncObject* aStart, SyncObject* aCompletion)
|
||||
: Job(aStart, aCompletion, nullptr)
|
||||
, mCmdId(aCmdId)
|
||||
, mCmdBufferId(aJobId)
|
||||
, mSanityChecker(aChecker)
|
||||
{}
|
||||
|
||||
JobStatus Run()
|
||||
{
|
||||
MaybeYieldThread();
|
||||
mSanityChecker->Check(mCmdBufferId, mCmdId);
|
||||
MaybeYieldThread();
|
||||
return JobStatus::Complete;
|
||||
}
|
||||
|
||||
uint64_t mCmdId;
|
||||
uint64_t mCmdBufferId;
|
||||
SanityChecker* mSanityChecker;
|
||||
};
|
||||
|
||||
/// This test creates aNumCmdBuffers task buffers with sync objects set up
|
||||
/// so that all tasks will join after command 5 before a task buffer runs
|
||||
/// a special task (task 0) after which all task buffers fork again.
|
||||
/// This simulates the kind of scenario where all tiles must join at
|
||||
/// a certain point to execute, say, a filter, and fork again after the filter
|
||||
/// has been processed.
|
||||
/// The main thread is only blocked when waiting for the completion of the entire
|
||||
/// task stream (it doesn't have to wait at the filter's sync points to orchestrate it).
|
||||
void TestSchedulerJoin(uint32_t aNumThreads, uint32_t aNumCmdBuffers)
|
||||
{
|
||||
JoinTestSanityCheck check(aNumCmdBuffers);
|
||||
|
||||
RefPtr<SyncObject> beforeFilter = new SyncObject(aNumCmdBuffers);
|
||||
RefPtr<SyncObject> afterFilter = new SyncObject();
|
||||
RefPtr<SyncObject> completion = new SyncObject(aNumCmdBuffers);
|
||||
|
||||
|
||||
for (uint32_t i = 0; i < aNumCmdBuffers; ++i) {
|
||||
Job* t1 = new TestJob(1, i, &check, nullptr, beforeFilter);
|
||||
JobScheduler::SubmitJob(t1);
|
||||
MaybeYieldThread();
|
||||
}
|
||||
beforeFilter->FreezePrerequisites();
|
||||
|
||||
// This task buffer is executed when all other tasks have joined after task 1
|
||||
JobScheduler::SubmitJob(
|
||||
new TestJob(0, 0, &check, beforeFilter, afterFilter)
|
||||
);
|
||||
afterFilter->FreezePrerequisites();
|
||||
|
||||
for (uint32_t i = 0; i < aNumCmdBuffers; ++i) {
|
||||
Job* t2 = new TestJob(2, i, &check, afterFilter, completion);
|
||||
JobScheduler::SubmitJob(t2);
|
||||
MaybeYieldThread();
|
||||
}
|
||||
completion->FreezePrerequisites();
|
||||
|
||||
RefPtr<EventObject> waitForCompletion = new EventObject();
|
||||
auto evtJob = new SetEventJob(waitForCompletion, completion);
|
||||
JobScheduler::SubmitJob(evtJob);
|
||||
|
||||
MaybeYieldThread();
|
||||
|
||||
waitForCompletion->Wait();
|
||||
|
||||
MaybeYieldThread();
|
||||
|
||||
for (auto advancement : check.mAdvancements) {
|
||||
ASSERT_TRUE(advancement == 2);
|
||||
}
|
||||
}
|
||||
|
||||
/// This test creates several chains of 10 task, tasks of a given chain are executed
|
||||
/// sequentially, and chains are exectuted in parallel.
|
||||
/// This simulates the typical scenario where we want to process sequences of drawing
|
||||
/// commands for several tiles in parallel.
|
||||
void TestSchedulerChain(uint32_t aNumThreads, uint32_t aNumCmdBuffers)
|
||||
{
|
||||
SanityChecker check(aNumCmdBuffers);
|
||||
|
||||
RefPtr<SyncObject> completion = new SyncObject(aNumCmdBuffers);
|
||||
|
||||
uint32_t numJobs = 10;
|
||||
|
||||
for (uint32_t i = 0; i < aNumCmdBuffers; ++i) {
|
||||
|
||||
std::vector<RefPtr<SyncObject>> syncs;
|
||||
std::vector<Job*> tasks;
|
||||
syncs.reserve(numJobs);
|
||||
tasks.reserve(numJobs);
|
||||
|
||||
for (uint32_t t = 0; t < numJobs-1; ++t) {
|
||||
syncs.push_back(new SyncObject());
|
||||
tasks.push_back(new TestJob(t+1, i, &check, t == 0 ? nullptr
|
||||
: syncs[t-1].get(),
|
||||
syncs[t]));
|
||||
syncs.back()->FreezePrerequisites();
|
||||
}
|
||||
|
||||
tasks.push_back(new TestJob(numJobs, i, &check, syncs.back(), completion));
|
||||
|
||||
if (i % 2 == 0) {
|
||||
// submit half of the tasks in order
|
||||
for (Job* task : tasks) {
|
||||
JobScheduler::SubmitJob(task);
|
||||
MaybeYieldThread();
|
||||
}
|
||||
} else {
|
||||
// ... and submit the other half in reverse order
|
||||
for (int32_t reverse = numJobs-1; reverse >= 0; --reverse) {
|
||||
JobScheduler::SubmitJob(tasks[reverse]);
|
||||
MaybeYieldThread();
|
||||
}
|
||||
}
|
||||
}
|
||||
completion->FreezePrerequisites();
|
||||
|
||||
RefPtr<EventObject> waitForCompletion = new EventObject();
|
||||
auto evtJob = new SetEventJob(waitForCompletion, completion);
|
||||
JobScheduler::SubmitJob(evtJob);
|
||||
|
||||
MaybeYieldThread();
|
||||
|
||||
waitForCompletion->Wait();
|
||||
|
||||
for (auto advancement : check.mAdvancements) {
|
||||
ASSERT_TRUE(advancement == numJobs);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace test_scheduler
|
||||
|
||||
TEST(Moz2D, JobScheduler_Join) {
|
||||
srand(time(nullptr));
|
||||
for (uint32_t threads = 1; threads < 8; ++threads) {
|
||||
for (uint32_t queues = 1; queues < threads; ++queues) {
|
||||
for (uint32_t buffers = 1; buffers < 100; buffers += 3) {
|
||||
mozilla::gfx::JobScheduler::Init(threads, queues);
|
||||
test_scheduler::TestSchedulerJoin(threads, buffers);
|
||||
mozilla::gfx::JobScheduler::ShutDown();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST(Moz2D, JobScheduler_Chain) {
|
||||
srand(time(nullptr));
|
||||
for (uint32_t threads = 1; threads < 8; ++threads) {
|
||||
for (uint32_t queues = 1; queues < threads; ++queues) {
|
||||
for (uint32_t buffers = 1; buffers < 100; buffers += 3) {
|
||||
mozilla::gfx::JobScheduler::Init(threads, queues);
|
||||
test_scheduler::TestSchedulerChain(threads, buffers);
|
||||
mozilla::gfx::JobScheduler::ShutDown();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -8,11 +8,13 @@ UNIFIED_SOURCES += [
|
|||
'gfxSurfaceRefCountTest.cpp',
|
||||
# Disabled on suspicion of causing bug 904227
|
||||
#'gfxWordCacheTest.cpp',
|
||||
'TestArena.cpp',
|
||||
'TestBufferRotation.cpp',
|
||||
'TestColorNames.cpp',
|
||||
'TestCompositor.cpp',
|
||||
'TestGfxPrefs.cpp',
|
||||
'TestGfxWidgets.cpp',
|
||||
'TestJobScheduler.cpp',
|
||||
'TestLayers.cpp',
|
||||
'TestMoz2D.cpp',
|
||||
'TestQcms.cpp',
|
||||
|
|
|
@ -4,5 +4,5 @@ fuzzy-if(winWidget,175,443) == 611498-1.html 611498-ref.html
|
|||
skip-if(B2G) fuzzy-if(Android&&AndroidVersion>=15,8,1000) == 709477-1.html 709477-1-ref.html # bug 773482
|
||||
skip-if(!asyncPan) == 1086723.html 1086723-ref.html
|
||||
== 853889-1.html 853889-1-ref.html
|
||||
== 1143303-1.svg pass.svg
|
||||
fuzzy(100,30) == 1149923.html 1149923-ref.html # use fuzzy due to few distorted pixels caused by border-radius
|
||||
skip-if(Android) == 1143303-1.svg pass.svg
|
||||
fuzzy(100,30) == 1149923.html 1149923-ref.html # use fuzzy due to few distorted pixels caused by border-radius
|
||||
|
|
|
@ -100,13 +100,7 @@ gfxAndroidPlatform::gfxAndroidPlatform()
|
|||
|
||||
RegisterStrongMemoryReporter(new FreetypeReporter());
|
||||
|
||||
nsCOMPtr<nsIScreenManager> screenMgr = do_GetService("@mozilla.org/gfx/screenmanager;1");
|
||||
nsCOMPtr<nsIScreen> screen;
|
||||
screenMgr->GetPrimaryScreen(getter_AddRefs(screen));
|
||||
mScreenDepth = 24;
|
||||
screen->GetColorDepth(&mScreenDepth);
|
||||
|
||||
mOffscreenFormat = mScreenDepth == 16
|
||||
mOffscreenFormat = GetScreenDepth() == 16
|
||||
? gfxImageFormat::RGB16_565
|
||||
: gfxImageFormat::RGB24;
|
||||
|
||||
|
@ -418,12 +412,6 @@ gfxAndroidPlatform::RequiresLinearZoom()
|
|||
return gfxPlatform::RequiresLinearZoom();
|
||||
}
|
||||
|
||||
int
|
||||
gfxAndroidPlatform::GetScreenDepth() const
|
||||
{
|
||||
return mScreenDepth;
|
||||
}
|
||||
|
||||
bool
|
||||
gfxAndroidPlatform::UseAcceleratedSkiaCanvas()
|
||||
{
|
||||
|
|
|
@ -80,8 +80,6 @@ public:
|
|||
|
||||
FT_Library GetFTLibrary();
|
||||
|
||||
virtual int GetScreenDepth() const;
|
||||
|
||||
virtual bool CanRenderContentToDataSurface() const override {
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -52,6 +52,7 @@
|
|||
#include "gfxGraphiteShaper.h"
|
||||
#include "gfx2DGlue.h"
|
||||
#include "gfxGradientCache.h"
|
||||
#include "gfxUtils.h" // for NextPowerOfTwo
|
||||
|
||||
#include "nsUnicodeRange.h"
|
||||
#include "nsServiceManagerUtils.h"
|
||||
|
@ -394,6 +395,7 @@ gfxPlatform::gfxPlatform()
|
|||
, mAzureCanvasBackendCollector(this, &gfxPlatform::GetAzureBackendInfo)
|
||||
, mApzSupportCollector(this, &gfxPlatform::GetApzSupportInfo)
|
||||
, mCompositorBackend(layers::LayersBackend::LAYERS_NONE)
|
||||
, mScreenDepth(0)
|
||||
{
|
||||
mAllowDownloadableFonts = UNINITIALIZED_VALUE;
|
||||
mFallbackUsesCmaps = UNINITIALIZED_VALUE;
|
||||
|
@ -514,6 +516,7 @@ gfxPlatform::Init()
|
|||
InitLayersAccelerationPrefs();
|
||||
InitLayersIPC();
|
||||
|
||||
gPlatform->PopulateScreenInfo();
|
||||
gPlatform->ComputeTileSize();
|
||||
|
||||
nsresult rv;
|
||||
|
@ -1024,45 +1027,49 @@ gfxPlatform::ComputeTileSize()
|
|||
int32_t w = gfxPrefs::LayersTileWidth();
|
||||
int32_t h = gfxPrefs::LayersTileHeight();
|
||||
|
||||
// TODO We may want to take the screen size into consideration here.
|
||||
if (gfxPrefs::LayersTilesAdjust()) {
|
||||
gfx::IntSize screenSize = GetScreenSize();
|
||||
if (screenSize.width > 0) {
|
||||
// FIXME: we should probably make sure this is within the max texture size,
|
||||
// but I think everything should at least support 1024
|
||||
w = h = std::max(std::min(NextPowerOfTwo(screenSize.width) / 2, 1024), 256);
|
||||
}
|
||||
|
||||
#ifdef MOZ_WIDGET_GONK
|
||||
int32_t format = android::PIXEL_FORMAT_RGBA_8888;
|
||||
android::sp<android::GraphicBuffer> alloc =
|
||||
new android::GraphicBuffer(gfxPrefs::LayersTileWidth(), gfxPrefs::LayersTileHeight(),
|
||||
format,
|
||||
android::GraphicBuffer::USAGE_SW_READ_OFTEN |
|
||||
android::GraphicBuffer::USAGE_SW_WRITE_OFTEN |
|
||||
android::GraphicBuffer::USAGE_HW_TEXTURE);
|
||||
new android::GraphicBuffer(w, h, android::PIXEL_FORMAT_RGBA_8888,
|
||||
android::GraphicBuffer::USAGE_SW_READ_OFTEN |
|
||||
android::GraphicBuffer::USAGE_SW_WRITE_OFTEN |
|
||||
android::GraphicBuffer::USAGE_HW_TEXTURE);
|
||||
|
||||
if (alloc.get()) {
|
||||
w = alloc->getStride(); // We want the tiles to be gralloc stride aligned.
|
||||
// No need to adjust the height here.
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifdef XP_MACOSX
|
||||
// Use double sized tiles for HiDPI screens.
|
||||
nsCOMPtr<nsIScreenManager> screenManager =
|
||||
do_GetService("@mozilla.org/gfx/screenmanager;1");
|
||||
if (screenManager) {
|
||||
nsCOMPtr<nsIScreen> primaryScreen;
|
||||
screenManager->GetPrimaryScreen(getter_AddRefs(primaryScreen));
|
||||
double scaleFactor = 1.0;
|
||||
if (primaryScreen) {
|
||||
primaryScreen->GetContentsScaleFactor(&scaleFactor);
|
||||
}
|
||||
if (scaleFactor > 1.0) {
|
||||
w *= 2;
|
||||
h *= 2;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
SetTileSize(w, h);
|
||||
}
|
||||
|
||||
void
|
||||
gfxPlatform::PopulateScreenInfo()
|
||||
{
|
||||
nsCOMPtr<nsIScreenManager> manager = do_GetService("@mozilla.org/gfx/screenmanager;1");
|
||||
MOZ_ASSERT(manager, "failed to get nsIScreenManager");
|
||||
|
||||
nsCOMPtr<nsIScreen> screen;
|
||||
manager->GetPrimaryScreen(getter_AddRefs(screen));
|
||||
if (!screen) {
|
||||
// This can happen in xpcshell, for instance
|
||||
return;
|
||||
}
|
||||
|
||||
screen->GetColorDepth(&mScreenDepth);
|
||||
|
||||
int left, top;
|
||||
screen->GetRect(&left, &top, &mScreenSize.width, &mScreenSize.height);
|
||||
}
|
||||
|
||||
bool
|
||||
gfxPlatform::SupportsAzureContentForDrawTarget(DrawTarget* aTarget)
|
||||
{
|
||||
|
@ -2093,13 +2100,6 @@ gfxPlatform::GetLog(eGfxLog aWhichLog)
|
|||
return nullptr;
|
||||
}
|
||||
|
||||
int
|
||||
gfxPlatform::GetScreenDepth() const
|
||||
{
|
||||
NS_WARNING("GetScreenDepth not implemented on this platform -- returning 0!");
|
||||
return 0;
|
||||
}
|
||||
|
||||
mozilla::gfx::SurfaceFormat
|
||||
gfxPlatform::Optimal2DFormatForContent(gfxContentType aContent)
|
||||
{
|
||||
|
|
|
@ -568,7 +568,8 @@ public:
|
|||
*/
|
||||
static PRLogModuleInfo* GetLog(eGfxLog aWhichLog);
|
||||
|
||||
virtual int GetScreenDepth() const;
|
||||
int GetScreenDepth() const { return mScreenDepth; }
|
||||
mozilla::gfx::IntSize GetScreenSize() const { return mScreenSize; }
|
||||
|
||||
/**
|
||||
* Return the layer debugging options to use browser-wide.
|
||||
|
@ -777,6 +778,11 @@ private:
|
|||
*/
|
||||
void ComputeTileSize();
|
||||
|
||||
/**
|
||||
* This uses nsIScreenManager to determine the screen size and color depth
|
||||
*/
|
||||
void PopulateScreenInfo();
|
||||
|
||||
nsRefPtr<gfxASurface> mScreenReferenceSurface;
|
||||
nsTArray<uint32_t> mCJKPrefLangs;
|
||||
nsCOMPtr<nsIObserver> mSRGBOverrideObserver;
|
||||
|
@ -804,6 +810,9 @@ private:
|
|||
// Backend that we are compositing with. NONE, if no compositor has been
|
||||
// created yet.
|
||||
mozilla::layers::LayersBackend mCompositorBackend;
|
||||
|
||||
int32_t mScreenDepth;
|
||||
mozilla::gfx::IntSize mScreenSize;
|
||||
};
|
||||
|
||||
#endif /* GFX_PLATFORM_H */
|
||||
|
|
|
@ -363,24 +363,6 @@ gfxPlatformGtk::GetOffscreenFormat()
|
|||
return gfxImageFormat::RGB24;
|
||||
}
|
||||
|
||||
static int sDepth = 0;
|
||||
|
||||
int
|
||||
gfxPlatformGtk::GetScreenDepth() const
|
||||
{
|
||||
if (!sDepth) {
|
||||
GdkScreen *screen = gdk_screen_get_default();
|
||||
if (screen) {
|
||||
sDepth = gdk_visual_get_depth(gdk_visual_get_system());
|
||||
} else {
|
||||
sDepth = 24;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return sDepth;
|
||||
}
|
||||
|
||||
void
|
||||
gfxPlatformGtk::GetPlatformCMSOutputProfile(void *&mem, size_t &size)
|
||||
{
|
||||
|
|
|
@ -122,8 +122,6 @@ public:
|
|||
|
||||
virtual gfxImageFormat GetOffscreenFormat() override;
|
||||
|
||||
virtual int GetScreenDepth() const override;
|
||||
|
||||
bool SupportsApzWheelInput() const override {
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -355,6 +355,7 @@ private:
|
|||
DECL_GFX_PREF(Live, "layers.transaction.warning-ms", LayerTransactionWarning, uint32_t, 200);
|
||||
DECL_GFX_PREF(Once, "layers.uniformity-info", UniformityInfo, bool, false);
|
||||
DECL_GFX_PREF(Once, "layers.use-image-offscreen-surfaces", UseImageOffscreenSurfaces, bool, false);
|
||||
DECL_GFX_PREF(Live, "layers.single-tile.enabled", LayersSingleTileEnabled, bool, true);
|
||||
|
||||
DECL_GFX_PREF(Live, "layout.css.scroll-behavior.damping-ratio", ScrollBehaviorDampingRatio, float, 1.0f);
|
||||
DECL_GFX_PREF(Live, "layout.css.scroll-behavior.enabled", ScrollBehaviorEnabled, bool, false);
|
||||
|
|
|
@ -53,8 +53,8 @@ gfxQtPlatform::gfxQtPlatform()
|
|||
if (!sFontconfigUtils)
|
||||
sFontconfigUtils = gfxFontconfigUtils::GetFontconfigUtils();
|
||||
|
||||
mScreenDepth = qApp->primaryScreen()->depth();
|
||||
if (mScreenDepth == 16) {
|
||||
int32_t depth = GetScreenDepth();
|
||||
if (depth == 16) {
|
||||
sOffscreenFormat = gfxImageFormat::RGB16_565;
|
||||
}
|
||||
uint32_t canvasMask = BackendTypeBit(BackendType::CAIRO) | BackendTypeBit(BackendType::SKIA);
|
||||
|
@ -195,12 +195,6 @@ gfxQtPlatform::GetOffscreenFormat()
|
|||
return sOffscreenFormat;
|
||||
}
|
||||
|
||||
int
|
||||
gfxQtPlatform::GetScreenDepth() const
|
||||
{
|
||||
return mScreenDepth;
|
||||
}
|
||||
|
||||
already_AddRefed<ScaledFont>
|
||||
gfxQtPlatform::GetScaledFontForFont(DrawTarget* aTarget, gfxFont* aFont)
|
||||
{
|
||||
|
|
|
@ -82,8 +82,6 @@ public:
|
|||
static Screen* GetXScreen(QWindow* aWindow = 0);
|
||||
#endif
|
||||
|
||||
virtual int GetScreenDepth() const override;
|
||||
|
||||
bool AccelerateLayersByDefault() override {
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -1592,26 +1592,6 @@ gfxWindowsPlatform::IsOptimus()
|
|||
return knowIsOptimus;
|
||||
}
|
||||
|
||||
int
|
||||
gfxWindowsPlatform::GetScreenDepth() const
|
||||
{
|
||||
// if the system doesn't have all displays with the same
|
||||
// pixel format, just return 24 and move on with life.
|
||||
if (!GetSystemMetrics(SM_SAMEDISPLAYFORMAT))
|
||||
return 24;
|
||||
|
||||
HDC hdc = GetDC(nullptr);
|
||||
if (!hdc)
|
||||
return 24;
|
||||
|
||||
int depth = GetDeviceCaps(hdc, BITSPIXEL) *
|
||||
GetDeviceCaps(hdc, PLANES);
|
||||
|
||||
ReleaseDC(nullptr, hdc);
|
||||
|
||||
return depth;
|
||||
}
|
||||
|
||||
IDXGIAdapter1*
|
||||
gfxWindowsPlatform::GetDXGIAdapter()
|
||||
{
|
||||
|
|
|
@ -140,8 +140,6 @@ public:
|
|||
RENDER_MODE_MAX
|
||||
};
|
||||
|
||||
int GetScreenDepth() const;
|
||||
|
||||
RenderMode GetRenderMode() { return mRenderMode; }
|
||||
void SetRenderMode(RenderMode rmode) { mRenderMode = rmode; }
|
||||
|
||||
|
|
|
@ -5140,9 +5140,9 @@ class _GenerateProtocolActorCode(ipdl.ast.Visitor):
|
|||
+ self.invokeRecvHandler(md, implicit=0)
|
||||
+ [ Whitespace.NL ]
|
||||
+ saveIdStmts
|
||||
+ self.dtorEpilogue(md, md.actorDecl().var())
|
||||
+ [ Whitespace.NL ]
|
||||
+ self.makeReply(md, errfnRecv, routingId=idvar)
|
||||
+ [ Whitespace.NL ]
|
||||
+ self.dtorEpilogue(md, md.actorDecl().var())
|
||||
+ [ Whitespace.NL,
|
||||
StmtReturn(_Result.Processed) ])
|
||||
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
try {
|
||||
evalInWorker(`
|
||||
function f() { f(); }
|
||||
try { f(); } catch(e) {}
|
||||
`);
|
||||
} catch(e) {
|
||||
assertEq(e.toString().includes("--no-threads"), true);
|
||||
}
|
|
@ -2165,6 +2165,19 @@ IonBuilder::inlineIsPossiblyWrappedTypedArray(CallInfo& callInfo)
|
|||
return inlineIsTypedArrayHelper(callInfo, AllowWrappedTypedArrays);
|
||||
}
|
||||
|
||||
static bool
|
||||
IsTypedArrayObject(CompilerConstraintList* constraints, MDefinition* def)
|
||||
{
|
||||
MOZ_ASSERT(def->type() == MIRType_Object);
|
||||
|
||||
TemporaryTypeSet* types = def->resultTypeSet();
|
||||
if (!types)
|
||||
return false;
|
||||
|
||||
return types->forAllClasses(constraints, IsTypedArrayClass) ==
|
||||
TemporaryTypeSet::ForAllResult::ALL_TRUE;
|
||||
}
|
||||
|
||||
IonBuilder::InliningStatus
|
||||
IonBuilder::inlineTypedArrayLength(CallInfo& callInfo)
|
||||
{
|
||||
|
@ -2175,8 +2188,10 @@ IonBuilder::inlineTypedArrayLength(CallInfo& callInfo)
|
|||
if (getInlineReturnType() != MIRType_Int32)
|
||||
return InliningStatus_NotInlined;
|
||||
|
||||
// We assume that when calling this function we always
|
||||
// have a TypedArray. The native asserts that as well.
|
||||
// Note that the argument we see here is not necessarily a typed array.
|
||||
// If it's not, this call should be unreachable though.
|
||||
if (!IsTypedArrayObject(constraints(), callInfo.getArg(0)))
|
||||
return InliningStatus_NotInlined;
|
||||
|
||||
MInstruction* length = addTypedArrayLength(callInfo.getArg(0));
|
||||
current->push(length);
|
||||
|
@ -2210,19 +2225,10 @@ IonBuilder::inlineSetDisjointTypedElements(CallInfo& callInfo)
|
|||
// Only attempt to optimize if |target| and |sourceTypedArray| are both
|
||||
// definitely typed arrays. (The former always is. The latter is not,
|
||||
// necessarily, because of wrappers.)
|
||||
|
||||
MDefinition* arrays[] = { target, sourceTypedArray };
|
||||
|
||||
for (MDefinition* def : arrays) {
|
||||
TemporaryTypeSet* types = def->resultTypeSet();
|
||||
if (!types)
|
||||
return InliningStatus_NotInlined;
|
||||
|
||||
if (types->forAllClasses(constraints(), IsTypedArrayClass) !=
|
||||
TemporaryTypeSet::ForAllResult::ALL_TRUE)
|
||||
{
|
||||
return InliningStatus_NotInlined;
|
||||
}
|
||||
if (!IsTypedArrayObject(constraints(), target) ||
|
||||
!IsTypedArrayObject(constraints(), sourceTypedArray))
|
||||
{
|
||||
return InliningStatus_NotInlined;
|
||||
}
|
||||
|
||||
auto sets = MSetDisjointTypedElements::New(alloc(), target, targetOffset, sourceTypedArray);
|
||||
|
|
|
@ -153,7 +153,7 @@ MacroAssembler::guardObjectType(Register obj, const TypeSet* types,
|
|||
{
|
||||
MOZ_ASSERT(!types->unknown());
|
||||
MOZ_ASSERT(!types->hasType(TypeSet::AnyObjectType()));
|
||||
MOZ_ASSERT(scratch != InvalidReg);
|
||||
MOZ_ASSERT_IF(types->getObjectCount() > 0, scratch != InvalidReg);
|
||||
|
||||
// Note: this method elides read barriers on values read from type sets, as
|
||||
// this may be called off the main thread during Ion compilation. This is
|
||||
|
@ -215,7 +215,6 @@ MacroAssembler::guardObjectType(Register obj, const TypeSet* types,
|
|||
lastBranch.emit(*this);
|
||||
|
||||
bind(&matched);
|
||||
return;
|
||||
}
|
||||
|
||||
template void MacroAssembler::guardTypeSet(const Address& address, const TypeSet* types,
|
||||
|
|
|
@ -2604,7 +2604,8 @@ EvalInWorker(JSContext* cx, unsigned argc, Value* vp)
|
|||
return false;
|
||||
|
||||
PRThread* thread = PR_CreateThread(PR_USER_THREAD, WorkerMain, input,
|
||||
PR_PRIORITY_NORMAL, PR_GLOBAL_THREAD, PR_JOINABLE_THREAD, 0);
|
||||
PR_PRIORITY_NORMAL, PR_GLOBAL_THREAD, PR_JOINABLE_THREAD,
|
||||
gMaxStackSize + 128 * 1024);
|
||||
if (!thread || !workerThreads.append(thread))
|
||||
return false;
|
||||
|
||||
|
@ -6129,6 +6130,8 @@ SetWorkerRuntimeOptions(JSRuntime* rt)
|
|||
if (*gZealStr)
|
||||
rt->gc.parseAndSetZeal(gZealStr);
|
||||
#endif
|
||||
|
||||
JS_SetNativeStackQuota(rt, gMaxStackSize);
|
||||
}
|
||||
|
||||
static int
|
||||
|
|
|
@ -335,7 +335,7 @@ TemporaryTypeSet::TemporaryTypeSet(LifoAlloc* alloc, Type type)
|
|||
}
|
||||
|
||||
bool
|
||||
TypeSet::mightBeMIRType(jit::MIRType type)
|
||||
TypeSet::mightBeMIRType(jit::MIRType type) const
|
||||
{
|
||||
if (unknown())
|
||||
return true;
|
||||
|
|
|
@ -475,7 +475,7 @@ class TypeSet
|
|||
}
|
||||
|
||||
/* Whether any values in this set might have the specified type. */
|
||||
bool mightBeMIRType(jit::MIRType type);
|
||||
bool mightBeMIRType(jit::MIRType type) const;
|
||||
|
||||
/*
|
||||
* Get whether this type set is known to be a subset of other.
|
||||
|
|
|
@ -2094,9 +2094,6 @@ ContainerState::GetLayerCreationHint(const nsIFrame* aAnimatedGeometryRoot)
|
|||
{
|
||||
// Check whether the layer will be scrollable. This is used as a hint to
|
||||
// influence whether tiled layers are used or not.
|
||||
if (mParameters.mInLowPrecisionDisplayPort) {
|
||||
return LayerManager::SCROLLABLE;
|
||||
}
|
||||
|
||||
// Check whether there's any active scroll frame on the animated geometry
|
||||
// root chain.
|
||||
|
|
|
@ -69,7 +69,7 @@ fuzzy(3,7860) fuzzy-if(cocoaWidget,5,89041) fuzzy-if(azureSkiaGL,4,90000) == rad
|
|||
== radial-position-1a.html radial-position-1-ref.html
|
||||
fuzzy-if(cocoaWidget,1,28) fuzzy-if(winWidget,1,18) == radial-position-1b.html radial-position-1-ref.html
|
||||
fuzzy-if(cocoaWidget,4,22317) fuzzy-if(Android,8,771) == radial-shape-closest-corner-1a.html radial-shape-closest-corner-1-ref.html
|
||||
fuzzy(1,238) fuzzy-if(cocoaWidget,4,22608) fuzzy-if((/^Windows\x20NT\x2010\.0/.test(http.oscpu)||/^Windows\x20NT\x206\./.test(http.oscpu))&&d2d,1,336) fuzzy-if(Android,8,787) == radial-shape-closest-corner-1b.html radial-shape-closest-corner-1-ref.html
|
||||
fuzzy(1,238) fuzzy-if(cocoaWidget,4,22608) fuzzy-if((/^Windows\x20NT\x2010\.0/.test(http.oscpu)||/^Windows\x20NT\x206\./.test(http.oscpu))&&d2d,1,336) fuzzy-if(Android,8,787) fuzzy-if(B2G,1,242) == radial-shape-closest-corner-1b.html radial-shape-closest-corner-1-ref.html
|
||||
fuzzy-if(azureQuartz,2,41171) fuzzy-if(Android,8,771) == radial-shape-closest-corner-1c.html radial-shape-closest-corner-1-ref.html
|
||||
fuzzy-if(/^Windows\x20NT\x2010\.0/.test(http.oscpu)||/^Windows\x20NT\x206\.2/.test(http.oscpu),1,5) fuzzy-if(Android,17,3880) == radial-shape-closest-side-1a.html radial-shape-closest-side-1-ref.html
|
||||
fuzzy-if(/^Windows\x20NT\x2010\.0/.test(http.oscpu)||/^Windows\x20NT\x206\.2/.test(http.oscpu),1,5) fuzzy-if(Android,17,3880) == radial-shape-closest-side-1b.html radial-shape-closest-side-1-ref.html
|
||||
|
|
|
@ -33,7 +33,7 @@ include svg-integration/reftest.list
|
|||
== clip-02a.svg clip-02-ref.svg
|
||||
== clip-02b.svg clip-02-ref.svg
|
||||
== clipPath-advanced-01.svg pass.svg
|
||||
fuzzy-if(/^Windows\x20NT\x2010\.0/.test(http.oscpu)||/^Windows\x20NT\x206\.[12]/.test(http.oscpu),1,5) fuzzy-if(azureQuartz,1,6) fuzzy-if(OSX,1,2) == clipPath-and-shape-rendering-01.svg clipPath-and-shape-rendering-01-ref.svg # bug 614840
|
||||
fuzzy-if(/^Windows\x20NT\x2010\.0/.test(http.oscpu)||/^Windows\x20NT\x206\.[12]/.test(http.oscpu),1,5) fuzzy-if(azureQuartz,1,6) fuzzy-if(OSX,1,6) == clipPath-and-shape-rendering-01.svg clipPath-and-shape-rendering-01-ref.svg # bug 614840
|
||||
== clipPath-and-transform-01.svg pass.svg
|
||||
== clipPath-basic-01.svg pass.svg
|
||||
== clipPath-basic-02.svg pass.svg
|
||||
|
|
|
@ -4,7 +4,7 @@ fuzzy-if(Android,16,244) skip-if(B2G||Mulet) HTTP(..) == marker-basic.html marke
|
|||
skip-if(B2G||Mulet) HTTP(..) == marker-string.html marker-string-ref.html # Initial mulet triage: parity with B2G/B2G Desktop
|
||||
skip-if(Android||B2G) HTTP(..) == bidi-simple.html bidi-simple-ref.html # Fails on Android due to anti-aliasing
|
||||
skip-if(!gtkWidget) fuzzy-if(gtkWidget,2,289) HTTP(..) == bidi-simple-scrolled.html bidi-simple-scrolled-ref.html # Fails on Windows and OSX due to anti-aliasing
|
||||
skip-if(B2G||Mulet) fuzzy-if(Android&&AndroidVersion<15,9,2545) fuzzy-if(Android&&AndroidVersion>=15,24,4000) fuzzy-if(cocoaWidget,1,40) fuzzy-if(asyncPan&&!layersGPUAccelerated,102,1770) HTTP(..) == scroll-rounding.html scroll-rounding-ref.html # bug 760264 # Initial mulet triage: parity with B2G/B2G Desktop
|
||||
skip-if(B2G||Mulet) fuzzy-if(Android&&AndroidVersion<15,206,41) fuzzy-if(Android&&AndroidVersion>=15,24,4000) fuzzy-if(cocoaWidget,1,40) fuzzy-if(asyncPan&&!layersGPUAccelerated,102,1770) HTTP(..) == scroll-rounding.html scroll-rounding-ref.html # bug 760264 # Initial mulet triage: parity with B2G/B2G Desktop
|
||||
fuzzy-if(OSX==1008,1,1) HTTP(..) == anonymous-block.html anonymous-block-ref.html
|
||||
skip-if(B2G||Mulet) HTTP(..) == false-marker-overlap.html false-marker-overlap-ref.html # Initial mulet triage: parity with B2G/B2G Desktop
|
||||
HTTP(..) == visibility-hidden.html visibility-hidden-ref.html
|
||||
|
|
|
@ -262,6 +262,15 @@ nsCSSCompressedDataBlock::MapRuleInfoInto(nsRuleData *aRuleData) const
|
|||
nsCSSValue* target = aRuleData->ValueFor(iProp);
|
||||
if (target->GetUnit() == eCSSUnit_Null) {
|
||||
const nsCSSValue *val = ValueAtIndex(i);
|
||||
// In order for variable resolution to have the right information
|
||||
// about the stylesheet level of a value, that level needs to be
|
||||
// stored on the token stream. We can't do that at creation time
|
||||
// because the CSS parser (which creates the object) has no idea
|
||||
// about the stylesheet level, so we do it here instead, where
|
||||
// the rule walking will have just updated aRuleData.
|
||||
if (val->GetUnit() == eCSSUnit_TokenStream) {
|
||||
val->GetTokenStreamValue()->mLevel = aRuleData->mLevel;
|
||||
}
|
||||
MapSinglePropertyInto(iProp, val, target, aRuleData);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,6 +23,7 @@
|
|||
#include "nsPresContext.h"
|
||||
#include "nsStyleUtil.h"
|
||||
#include "nsDeviceContext.h"
|
||||
#include "nsStyleSet.h"
|
||||
|
||||
using namespace mozilla;
|
||||
|
||||
|
@ -2537,6 +2538,7 @@ nsCSSValueGradient::SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) con
|
|||
nsCSSValueTokenStream::nsCSSValueTokenStream()
|
||||
: mPropertyID(eCSSProperty_UNKNOWN)
|
||||
, mShorthandPropertyID(eCSSProperty_UNKNOWN)
|
||||
, mLevel(nsStyleSet::eSheetTypeCount)
|
||||
{
|
||||
MOZ_COUNT_CTOR(nsCSSValueTokenStream);
|
||||
}
|
||||
|
|
|
@ -1541,6 +1541,7 @@ public:
|
|||
// mozilla::CSSStyleSheet* mSheet;
|
||||
uint32_t mLineNumber;
|
||||
uint32_t mLineOffset;
|
||||
uint16_t mLevel; // an nsStyleSet::sheetType
|
||||
|
||||
private:
|
||||
nsCSSValueTokenStream(const nsCSSValueTokenStream& aOther) = delete;
|
||||
|
|
|
@ -2180,6 +2180,12 @@ nsRuleNode::ResolveVariableReferences(const nsStyleStructID aSID,
|
|||
&aContext->StyleVariables()->mVariables;
|
||||
nsCSSValueTokenStream* tokenStream = value->GetTokenStreamValue();
|
||||
|
||||
MOZ_ASSERT(tokenStream->mLevel != nsStyleSet::eSheetTypeCount,
|
||||
"Token stream should have a defined level");
|
||||
|
||||
AutoRestore<uint16_t> saveLevel(aRuleData->mLevel);
|
||||
aRuleData->mLevel = tokenStream->mLevel;
|
||||
|
||||
// Note that ParsePropertyWithVariableReferences relies on the fact
|
||||
// that the nsCSSValue in aRuleData for the property we are re-parsing
|
||||
// is still the token stream value. When
|
||||
|
|
|
@ -260,6 +260,11 @@ class RemoteReftest(RefTest):
|
|||
# Disable skia-gl: see bug 907351
|
||||
prefs["gfx.canvas.azure.accelerated"] = False
|
||||
|
||||
# Debug reftests have problems with large tile size on pandaboards
|
||||
if mozinfo.info['debug'] and self._devicemanager.shellCheckOutput(['getprop', 'ro.product.name']) == 'pandaboard':
|
||||
prefs["layers.tiles.adjust"] = False
|
||||
prefs["layers.single-tile.enabled"] = False
|
||||
|
||||
# Set the extra prefs.
|
||||
profile.set_preferences(prefs)
|
||||
|
||||
|
@ -310,7 +315,8 @@ class RemoteReftest(RefTest):
|
|||
|
||||
def runApp(self, profile, binary, cmdargs, env,
|
||||
timeout=None, debuggerInfo=None,
|
||||
symbolsPath=None, options=None):
|
||||
symbolsPath=None, options=None,
|
||||
valgrindPath=None, valgrindArgs=None, valgrindSuppFiles=None):
|
||||
status = self.automation.runApp(None, env,
|
||||
binary,
|
||||
profile.profile,
|
||||
|
|
|
@ -582,7 +582,8 @@ class RefTest(object):
|
|||
|
||||
def runApp(self, profile, binary, cmdargs, env,
|
||||
timeout=None, debuggerInfo=None,
|
||||
symbolsPath=None, options=None):
|
||||
symbolsPath=None, options=None,
|
||||
valgrindPath=None, valgrindArgs=None, valgrindSuppFiles=None):
|
||||
|
||||
def timeoutHandler():
|
||||
self.handleTimeout(
|
||||
|
|
|
@ -311,7 +311,8 @@ class B2GRemoteReftest(RefTest):
|
|||
|
||||
def runApp(self, profile, binary, cmdargs, env,
|
||||
timeout=None, debuggerInfo=None,
|
||||
symbolsPath=None, options=None):
|
||||
symbolsPath=None, options=None,
|
||||
valgrindPath=None, valgrindArgs=None, valgrindSuppFiles=None):
|
||||
status = self.automation.runApp(None, env,
|
||||
binary,
|
||||
profile.profile,
|
||||
|
|
|
@ -13,10 +13,64 @@
|
|||
using namespace mozilla;
|
||||
using namespace mp4_demuxer;
|
||||
|
||||
class TestStream : public Stream
|
||||
{
|
||||
public:
|
||||
TestStream(const uint8_t* aBuffer, size_t aSize)
|
||||
: mHighestSuccessfulEndOffset(0)
|
||||
, mBuffer(aBuffer)
|
||||
, mSize(aSize)
|
||||
{
|
||||
}
|
||||
bool ReadAt(int64_t aOffset, void* aData, size_t aLength,
|
||||
size_t* aBytesRead) override
|
||||
{
|
||||
if (aOffset < 0 || aOffset > static_cast<int64_t>(mSize)) {
|
||||
return false;
|
||||
}
|
||||
// After the test, 0 <= aOffset <= mSize <= SIZE_MAX, so it's safe to cast to size_t.
|
||||
size_t offset = static_cast<size_t>(aOffset);
|
||||
// Don't read past the end (but it's not an error to try).
|
||||
if (aLength > mSize - offset) {
|
||||
aLength = mSize - offset;
|
||||
}
|
||||
// Now, 0 <= offset <= offset + aLength <= mSize <= SIZE_MAX.
|
||||
*aBytesRead = aLength;
|
||||
memcpy(aData, mBuffer + offset, aLength);
|
||||
if (mHighestSuccessfulEndOffset < offset + aLength)
|
||||
{
|
||||
mHighestSuccessfulEndOffset = offset + aLength;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
bool CachedReadAt(int64_t aOffset, void* aData, size_t aLength,
|
||||
size_t* aBytesRead) override
|
||||
{
|
||||
return ReadAt(aOffset, aData, aLength, aBytesRead);
|
||||
}
|
||||
bool Length(int64_t* aLength) override
|
||||
{
|
||||
*aLength = mSize;
|
||||
return true;
|
||||
}
|
||||
void DiscardBefore(int64_t aOffset) override
|
||||
{
|
||||
}
|
||||
|
||||
// Offset past the last character ever read. 0 when nothing read yet.
|
||||
size_t mHighestSuccessfulEndOffset;
|
||||
protected:
|
||||
virtual ~TestStream()
|
||||
{
|
||||
}
|
||||
|
||||
const uint8_t* mBuffer;
|
||||
size_t mSize;
|
||||
};
|
||||
|
||||
TEST(stagefright_MP4Metadata, EmptyStream)
|
||||
{
|
||||
nsRefPtr<MediaByteBuffer> buffer = new MediaByteBuffer(0);
|
||||
nsRefPtr<BufferStream> stream = new BufferStream(buffer);
|
||||
nsRefPtr<Stream> stream = new TestStream(nullptr, 0);
|
||||
|
||||
EXPECT_FALSE(MP4Metadata::HasCompleteMetadata(stream));
|
||||
nsRefPtr<MediaByteBuffer> metadataBuffer = MP4Metadata::Metadata(stream);
|
||||
|
@ -40,8 +94,7 @@ TEST(stagefright_MP4Metadata, EmptyStream)
|
|||
|
||||
TEST(stagefright_MoofParser, EmptyStream)
|
||||
{
|
||||
nsRefPtr<MediaByteBuffer> buffer = new MediaByteBuffer(0);
|
||||
nsRefPtr<BufferStream> stream = new BufferStream(buffer);
|
||||
nsRefPtr<Stream> stream = new TestStream(nullptr, 0);
|
||||
|
||||
Monitor monitor("MP4Metadata::gtest");
|
||||
MonitorAutoLock mon(monitor);
|
||||
|
@ -64,344 +117,209 @@ TEST(stagefright_MoofParser, EmptyStream)
|
|||
EXPECT_TRUE(parser.FirstCompleteMediaHeader().IsNull());
|
||||
}
|
||||
|
||||
uint8_t test_case_mp4[] = {
|
||||
0x00, 0x00, 0x00, 0x20, 'f', 't', 'y', 'p', 'i', 's', 'o', 'm',
|
||||
0x00, 0x00, 0x02, 0x00, 'i', 's', 'o', 'm', 'i', 's', 'o', '2',
|
||||
'a', 'v', 'c', '1', 'm', 'p', '4', '1', 0x00, 0x00, 0x00, 0x08,
|
||||
'f', 'r', 'e', 'e', 0x00, 0x00, 0x07, 0xcc, 'm', 'd', 'a', 't',
|
||||
0x00, 0x00, 0x02, 0xaf, 0x06, 0x05, 0xff, 0xff, 0xab, 0xdc, 0x45, 0xe9,
|
||||
0xbd, 0xe6, 0xd9, 0x48, 0xb7, 0x96, 0x2c, 0xd8, 0x20, 0xd9, 0x23, 0xee,
|
||||
0xef, 0x78, 0x32, 0x36, 0x34, 0x20, 0x2d, 0x20, 0x63, 0x6f, 0x72, 0x65,
|
||||
0x20, 0x31, 0x34, 0x36, 0x20, 0x72, 0x32, 0x35, 0x33, 0x38, 0x20, 0x31,
|
||||
0x32, 0x31, 0x33, 0x39, 0x36, 0x63, 0x20, 0x2d, 0x20, 0x48, 0x2e, 0x32,
|
||||
0x36, 0x34, 0x2f, 0x4d, 0x50, 0x45, 0x47, 0x2d, 0x34, 0x20, 0x41, 0x56,
|
||||
0x43, 0x20, 0x63, 0x6f, 0x64, 0x65, 0x63, 0x20, 0x2d, 0x20, 0x43, 0x6f,
|
||||
0x70, 0x79, 0x6c, 0x65, 0x66, 0x74, 0x20, 0x32, 0x30, 0x30, 0x33, 0x2d,
|
||||
0x32, 0x30, 0x31, 0x35, 0x20, 0x2d, 0x20, 0x68, 0x74, 0x74, 0x70, 0x3a,
|
||||
0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x76, 0x69, 0x64, 0x65, 0x6f, 0x6c,
|
||||
0x61, 0x6e, 0x2e, 0x6f, 0x72, 0x67, 0x2f, 0x78, 0x32, 0x36, 0x34, 0x2e,
|
||||
0x68, 0x74, 0x6d, 0x6c, 0x20, 0x2d, 0x20, 0x6f, 0x70, 0x74, 0x69, 0x6f,
|
||||
0x6e, 0x73, 0x3a, 0x20, 0x63, 0x61, 0x62, 0x61, 0x63, 0x3d, 0x31, 0x20,
|
||||
0x72, 0x65, 0x66, 0x3d, 0x33, 0x20, 0x64, 0x65, 0x62, 0x6c, 0x6f, 0x63,
|
||||
0x6b, 0x3d, 0x31, 0x3a, 0x30, 0x3a, 0x30, 0x20, 0x61, 0x6e, 0x61, 0x6c,
|
||||
0x79, 0x73, 0x65, 0x3d, 0x30, 0x78, 0x33, 0x3a, 0x30, 0x78, 0x31, 0x31,
|
||||
0x33, 0x20, 0x6d, 0x65, 0x3d, 0x68, 0x65, 0x78, 0x20, 0x73, 0x75, 0x62,
|
||||
0x6d, 0x65, 0x3d, 0x37, 0x20, 0x70, 0x73, 0x79, 0x3d, 0x31, 0x20, 0x70,
|
||||
0x73, 0x79, 0x5f, 0x72, 0x64, 0x3d, 0x31, 0x2e, 0x30, 0x30, 0x3a, 0x30,
|
||||
0x2e, 0x30, 0x30, 0x20, 0x6d, 0x69, 0x78, 0x65, 0x64, 0x5f, 0x72, 0x65,
|
||||
0x66, 0x3d, 0x31, 0x20, 0x6d, 0x65, 0x5f, 0x72, 0x61, 0x6e, 0x67, 0x65,
|
||||
0x3d, 0x31, 0x36, 0x20, 0x63, 0x68, 0x72, 0x6f, 0x6d, 0x61, 0x5f, 0x6d,
|
||||
0x65, 0x3d, 0x31, 0x20, 0x74, 0x72, 0x65, 0x6c, 0x6c, 0x69, 0x73, 0x3d,
|
||||
0x31, 0x20, 0x38, 0x78, 0x38, 0x64, 0x63, 0x74, 0x3d, 0x31, 0x20, 0x63,
|
||||
0x71, 0x6d, 0x3d, 0x30, 0x20, 0x64, 0x65, 0x61, 0x64, 0x7a, 0x6f, 0x6e,
|
||||
0x65, 0x3d, 0x32, 0x31, 0x2c, 0x31, 0x31, 0x20, 0x66, 0x61, 0x73, 0x74,
|
||||
0x5f, 0x70, 0x73, 0x6b, 0x69, 0x70, 0x3d, 0x31, 0x20, 0x63, 0x68, 0x72,
|
||||
0x6f, 0x6d, 0x61, 0x5f, 0x71, 0x70, 0x5f, 0x6f, 0x66, 0x66, 0x73, 0x65,
|
||||
0x74, 0x3d, 0x2d, 0x32, 0x20, 0x74, 0x68, 0x72, 0x65, 0x61, 0x64, 0x73,
|
||||
0x3d, 0x31, 0x32, 0x20, 0x6c, 0x6f, 0x6f, 0x6b, 0x61, 0x68, 0x65, 0x61,
|
||||
0x64, 0x5f, 0x74, 0x68, 0x72, 0x65, 0x61, 0x64, 0x73, 0x3d, 0x31, 0x20,
|
||||
0x73, 0x6c, 0x69, 0x63, 0x65, 0x64, 0x5f, 0x74, 0x68, 0x72, 0x65, 0x61,
|
||||
0x64, 0x73, 0x3d, 0x30, 0x20, 0x6e, 0x72, 0x3d, 0x30, 0x20, 0x64, 0x65,
|
||||
0x63, 0x69, 0x6d, 0x61, 0x74, 0x65, 0x3d, 0x31, 0x20, 0x69, 0x6e, 0x74,
|
||||
0x65, 0x72, 0x6c, 0x61, 0x63, 0x65, 0x64, 0x3d, 0x30, 0x20, 0x62, 0x6c,
|
||||
0x75, 0x72, 0x61, 0x79, 0x5f, 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x74, 0x3d,
|
||||
0x30, 0x20, 0x63, 0x6f, 0x6e, 0x73, 0x74, 0x72, 0x61, 0x69, 0x6e, 0x65,
|
||||
0x64, 0x5f, 0x69, 0x6e, 0x74, 0x72, 0x61, 0x3d, 0x30, 0x20, 0x62, 0x66,
|
||||
0x72, 0x61, 0x6d, 0x65, 0x73, 0x3d, 0x33, 0x20, 0x62, 0x5f, 0x70, 0x79,
|
||||
0x72, 0x61, 0x6d, 0x69, 0x64, 0x3d, 0x32, 0x20, 0x62, 0x5f, 0x61, 0x64,
|
||||
0x61, 0x70, 0x74, 0x3d, 0x31, 0x20, 0x62, 0x5f, 0x62, 0x69, 0x61, 0x73,
|
||||
0x3d, 0x30, 0x20, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x3d, 0x31, 0x20,
|
||||
0x77, 0x65, 0x69, 0x67, 0x68, 0x74, 0x62, 0x3d, 0x31, 0x20, 0x6f, 0x70,
|
||||
0x65, 0x6e, 0x5f, 0x67, 0x6f, 0x70, 0x3d, 0x30, 0x20, 0x77, 0x65, 0x69,
|
||||
0x67, 0x68, 0x74, 0x70, 0x3d, 0x32, 0x20, 0x6b, 0x65, 0x79, 0x69, 0x6e,
|
||||
0x74, 0x3d, 0x32, 0x35, 0x30, 0x20, 0x6b, 0x65, 0x79, 0x69, 0x6e, 0x74,
|
||||
0x5f, 0x6d, 0x69, 0x6e, 0x3d, 0x32, 0x35, 0x20, 0x73, 0x63, 0x65, 0x6e,
|
||||
0x65, 0x63, 0x75, 0x74, 0x3d, 0x34, 0x30, 0x20, 0x69, 0x6e, 0x74, 0x72,
|
||||
0x61, 0x5f, 0x72, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x3d, 0x30, 0x20,
|
||||
0x72, 0x63, 0x5f, 0x6c, 0x6f, 0x6f, 0x6b, 0x61, 0x68, 0x65, 0x61, 0x64,
|
||||
0x3d, 0x34, 0x30, 0x20, 0x72, 0x63, 0x3d, 0x63, 0x72, 0x66, 0x20, 0x6d,
|
||||
0x62, 0x74, 0x72, 0x65, 0x65, 0x3d, 0x31, 0x20, 0x63, 0x72, 0x66, 0x3d,
|
||||
0x32, 0x33, 0x2e, 0x30, 0x20, 0x71, 0x63, 0x6f, 0x6d, 0x70, 0x3d, 0x30,
|
||||
0x2e, 0x36, 0x30, 0x20, 0x71, 0x70, 0x6d, 0x69, 0x6e, 0x3d, 0x30, 0x20,
|
||||
0x71, 0x70, 0x6d, 0x61, 0x78, 0x3d, 0x36, 0x39, 0x20, 0x71, 0x70, 0x73,
|
||||
0x74, 0x65, 0x70, 0x3d, 0x34, 0x20, 0x69, 0x70, 0x5f, 0x72, 0x61, 0x74,
|
||||
0x69, 0x6f, 0x3d, 0x31, 0x2e, 0x34, 0x30, 0x20, 0x61, 0x71, 0x3d, 0x31,
|
||||
0x3a, 0x31, 0x2e, 0x30, 0x30, 0x00, 0x80, 0x00, 0x00, 0x04, 0xfb, 0x65,
|
||||
0x88, 0x84, 0x00, 0x25, 0xff, 0xd7, 0x2b, 0x47, 0x53, 0x79, 0x61, 0xc8,
|
||||
0xdf, 0x88, 0x8e, 0xdc, 0xef, 0xc0, 0x85, 0x00, 0x99, 0x10, 0x58, 0x46,
|
||||
0xf1, 0x28, 0xf2, 0x0c, 0xfc, 0x41, 0x82, 0xa0, 0x45, 0x81, 0xb5, 0x7e,
|
||||
0xcc, 0x27, 0x62, 0x2f, 0xac, 0x5c, 0xe8, 0xa9, 0xcf, 0x0e, 0x54, 0x17,
|
||||
0x68, 0x13, 0x48, 0x0d, 0xa1, 0x5d, 0xd1, 0x8b, 0xdf, 0x6e, 0xd0, 0xc8,
|
||||
0x8f, 0xb3, 0xc2, 0xaf, 0xd1, 0xdc, 0x98, 0x74, 0x7e, 0x0f, 0x69, 0xbe,
|
||||
0x54, 0xa2, 0x83, 0x4a, 0xc4, 0xf1, 0x79, 0x35, 0x60, 0x10, 0xb4, 0xfb,
|
||||
0xa7, 0x52, 0x3a, 0x99, 0x5e, 0x39, 0x0d, 0x04, 0xad, 0x24, 0x49, 0xfd,
|
||||
0xbe, 0x5f, 0x49, 0xdf, 0x36, 0x91, 0x1f, 0x62, 0x7f, 0x00, 0xa1, 0xba,
|
||||
0x3c, 0xe9, 0xf3, 0x38, 0x08, 0xb7, 0x87, 0x9a, 0x11, 0xae, 0x5f, 0xf2,
|
||||
0x66, 0x14, 0xc3, 0xa9, 0xf0, 0x76, 0x69, 0x5f, 0x1d, 0x20, 0x19, 0xc0,
|
||||
0x50, 0x94, 0xb4, 0x7b, 0x44, 0xaf, 0x21, 0x24, 0x80, 0x3f, 0x2a, 0x81,
|
||||
0x1b, 0xe4, 0xae, 0xfe, 0xb0, 0x99, 0xd3, 0xa5, 0x1d, 0x3f, 0x27, 0x0c,
|
||||
0x61, 0x54, 0x4d, 0x39, 0x8b, 0x1d, 0x12, 0x65, 0xe1, 0xb3, 0x4d, 0xc1,
|
||||
0x9d, 0xef, 0xfe, 0x48, 0xc6, 0xc9, 0xf4, 0x9a, 0x09, 0xc1, 0x3c, 0x5c,
|
||||
0xe3, 0xa1, 0x16, 0xec, 0x83, 0xb6, 0xb9, 0xc2, 0x94, 0x64, 0xc9, 0xf2,
|
||||
0x91, 0x51, 0x4c, 0xaf, 0xe5, 0xc3, 0x8a, 0x99, 0x1b, 0x22, 0x86, 0x55,
|
||||
0x70, 0xbf, 0xc7, 0x99, 0xc3, 0x07, 0x15, 0xd6, 0xdb, 0x27, 0x46, 0x08,
|
||||
0x19, 0x7e, 0x0a, 0x9b, 0x82, 0xe0, 0x15, 0x1d, 0x57, 0x8a, 0x2c, 0xc4,
|
||||
0x05, 0xf8, 0x09, 0xfb, 0x3c, 0x47, 0x4f, 0x55, 0xc6, 0x3e, 0x0b, 0x9d,
|
||||
0xe5, 0x03, 0x3e, 0xc9, 0x4c, 0xeb, 0xa2, 0x1b, 0x04, 0xfa, 0x99, 0x44,
|
||||
0x29, 0x06, 0x2e, 0x57, 0x61, 0xa3, 0x63, 0x00, 0x53, 0x08, 0x20, 0xcb,
|
||||
0x15, 0x50, 0x95, 0x13, 0x24, 0x63, 0xc9, 0x4f, 0xc0, 0xb0, 0x88, 0xe6,
|
||||
0x38, 0xef, 0x96, 0x99, 0xd2, 0x96, 0x47, 0xc7, 0xc8, 0x61, 0xac, 0xcc,
|
||||
0x24, 0x0a, 0x1f, 0x79, 0xce, 0x91, 0x88, 0xf5, 0xe9, 0xe2, 0x2d, 0x88,
|
||||
0xaf, 0x37, 0xca, 0xa3, 0x90, 0x5b, 0x83, 0x1a, 0x9c, 0x92, 0xda, 0x33,
|
||||
0x69, 0xe6, 0x7c, 0x14, 0xa6, 0x85, 0x00, 0x42, 0x46, 0x51, 0xcf, 0x50,
|
||||
0xed, 0x34, 0xe8, 0x8e, 0xb5, 0x78, 0xeb, 0x09, 0x37, 0x6e, 0x82, 0xea,
|
||||
0x7b, 0xe1, 0xfd, 0x45, 0x61, 0x77, 0x14, 0x6a, 0x75, 0xbf, 0xb8, 0x13,
|
||||
0x10, 0xc0, 0x08, 0xca, 0x1c, 0x51, 0xc7, 0x3a, 0x0a, 0x2b, 0x89, 0x5d,
|
||||
0x3c, 0x8d, 0xf4, 0x11, 0xc0, 0x3a, 0x90, 0x02, 0xc0, 0x0c, 0xe8, 0x59,
|
||||
0x11, 0xac, 0xe0, 0x5d, 0xe2, 0x95, 0x78, 0x09, 0xd5, 0xfa, 0x26, 0xf5,
|
||||
0x23, 0x30, 0x0a, 0xfd, 0x54, 0x0b, 0x73, 0x1c, 0x81, 0x22, 0x48, 0x8b,
|
||||
0x79, 0xe0, 0xf9, 0xe8, 0xde, 0x7a, 0x79, 0xba, 0xef, 0xdd, 0x5a, 0xd6,
|
||||
0x44, 0xb7, 0xa8, 0x41, 0xd0, 0xa1, 0xeb, 0x45, 0xf8, 0x4d, 0x7f, 0x97,
|
||||
0x48, 0x82, 0xd2, 0x81, 0x61, 0x08, 0x15, 0x9e, 0xfe, 0x78, 0xba, 0xdf,
|
||||
0xb0, 0x13, 0x92, 0x59, 0x4e, 0x4d, 0xfd, 0xff, 0x5d, 0x66, 0x14, 0xd4,
|
||||
0x0b, 0x43, 0x9f, 0xd8, 0x62, 0x4e, 0x54, 0x83, 0xf9, 0x59, 0x48, 0x2f,
|
||||
0x26, 0x21, 0xd4, 0xf0, 0x98, 0x6b, 0x14, 0x61, 0x4b, 0xf6, 0x00, 0xcf,
|
||||
0xe3, 0x24, 0x4b, 0x2f, 0xd4, 0x5e, 0x6d, 0x40, 0x4b, 0x52, 0xea, 0xa5,
|
||||
0x89, 0x94, 0x0f, 0xd2, 0xeb, 0x02, 0x54, 0x68, 0x26, 0xcf, 0x2d, 0x83,
|
||||
0x0f, 0x62, 0x1b, 0x9e, 0x75, 0x81, 0x65, 0x30, 0xdd, 0x03, 0xb0, 0xc1,
|
||||
0xda, 0x4a, 0xc9, 0xd8, 0x64, 0x29, 0x6a, 0xe2, 0x83, 0xf1, 0xb8, 0x4e,
|
||||
0xc5, 0xaf, 0x8e, 0xf0, 0x8c, 0xbb, 0xda, 0xea, 0x8a, 0xbc, 0xcc, 0xa3,
|
||||
0xf2, 0x19, 0xe8, 0xeb, 0x7e, 0x18, 0x64, 0x91, 0x1f, 0xdd, 0xc8, 0xc8,
|
||||
0xf8, 0xad, 0xc8, 0x3b, 0xf9, 0x92, 0x74, 0x03, 0x9b, 0x90, 0x51, 0xb7,
|
||||
0xb5, 0xe3, 0x80, 0x4f, 0x8a, 0x84, 0xfa, 0xa4, 0xd6, 0x9c, 0x53, 0x0b,
|
||||
0x81, 0xc3, 0xee, 0x9e, 0x70, 0xa3, 0xbd, 0x3f, 0xbb, 0x60, 0x2d, 0x97,
|
||||
0x65, 0xa7, 0x69, 0x2f, 0x22, 0x49, 0x65, 0xf4, 0xb0, 0x33, 0xbc, 0xd2,
|
||||
0x80, 0x1b, 0x2c, 0x3a, 0xe3, 0x04, 0x8c, 0x49, 0x42, 0x25, 0xa0, 0x6d,
|
||||
0x3c, 0xfe, 0xfa, 0x70, 0x90, 0x6a, 0x30, 0xf4, 0x0c, 0xe4, 0x3f, 0x78,
|
||||
0xf9, 0xba, 0x55, 0xb9, 0xfa, 0xd7, 0xce, 0x05, 0xbc, 0xe9, 0xc9, 0xad,
|
||||
0x4a, 0x37, 0xa0, 0xf7, 0x8d, 0x96, 0x22, 0xf6, 0x38, 0x8d, 0xf4, 0xf6,
|
||||
0xe6, 0x8b, 0x45, 0xac, 0x13, 0xc5, 0xe6, 0x05, 0x1e, 0x09, 0xd7, 0x98,
|
||||
0xb3, 0xb6, 0x59, 0xe4, 0x3b, 0x47, 0x16, 0x6e, 0xdf, 0xac, 0x7f, 0x38,
|
||||
0x6e, 0xf9, 0xcf, 0xaa, 0x68, 0x98, 0xdb, 0x22, 0x89, 0x6e, 0xad, 0xbe,
|
||||
0xed, 0xb1, 0x82, 0xa0, 0xc2, 0x9b, 0xd5, 0x79, 0x89, 0x96, 0xf6, 0xd9,
|
||||
0x8f, 0x58, 0x77, 0x15, 0x2c, 0x73, 0xeb, 0x89, 0xcc, 0xf0, 0x37, 0x4d,
|
||||
0x41, 0x70, 0xc5, 0x58, 0xae, 0x77, 0xab, 0x30, 0xcf, 0x6c, 0x7c, 0x1c,
|
||||
0x52, 0x9a, 0x62, 0xf6, 0xf8, 0x0a, 0x65, 0x92, 0x83, 0x01, 0xc3, 0x60,
|
||||
0xed, 0xfd, 0x4d, 0x9a, 0x4b, 0xd4, 0xa5, 0xe1, 0xc4, 0xe2, 0xe1, 0x8c,
|
||||
0x64, 0xce, 0x54, 0x9c, 0xa9, 0x7f, 0xb9, 0x34, 0x88, 0xb1, 0x17, 0xde,
|
||||
0x85, 0x27, 0x43, 0x81, 0x3b, 0x37, 0x27, 0x25, 0xbf, 0xba, 0x6c, 0x69,
|
||||
0xb4, 0xce, 0xcd, 0xef, 0x7c, 0xee, 0x48, 0xb9, 0x8a, 0x09, 0x1f, 0x42,
|
||||
0x8e, 0xc3, 0x14, 0xe4, 0xfd, 0xda, 0xa0, 0xfa, 0x7d, 0x0b, 0x68, 0x6a,
|
||||
0x81, 0xb1, 0x96, 0xf9, 0x07, 0xf2, 0xed, 0x32, 0xf3, 0x52, 0x15, 0x00,
|
||||
0x2f, 0x5f, 0x6e, 0x55, 0xc6, 0x85, 0x4b, 0xdc, 0x3d, 0x7c, 0xa6, 0xa7,
|
||||
0xeb, 0x80, 0xab, 0xf3, 0xfe, 0x21, 0xe6, 0x1d, 0xbd, 0xca, 0x33, 0x29,
|
||||
0x9c, 0x94, 0xa4, 0x7f, 0xec, 0x67, 0x6c, 0xc0, 0x0e, 0xea, 0x5f, 0x9a,
|
||||
0x30, 0x54, 0x9c, 0xf2, 0x8f, 0xaa, 0x5f, 0xc3, 0x3e, 0x61, 0x54, 0x41,
|
||||
0x8a, 0xbf, 0x7f, 0xff, 0x8a, 0xfb, 0x7f, 0x8c, 0x40, 0xe4, 0x5a, 0xbe,
|
||||
0xe5, 0x39, 0xa4, 0xdd, 0x9b, 0xa6, 0xab, 0xd7, 0xee, 0x15, 0x32, 0x04,
|
||||
0xd1, 0xbd, 0x7c, 0xe4, 0x98, 0x3f, 0x3f, 0x40, 0x87, 0xbc, 0x01, 0x36,
|
||||
0xe7, 0xcd, 0x7b, 0xcf, 0xf7, 0xe9, 0x60, 0x1d, 0xea, 0xe2, 0x6e, 0x13,
|
||||
0x3c, 0xd0, 0x28, 0x2d, 0xa8, 0x9d, 0x25, 0x06, 0x99, 0xf8, 0xdc, 0x9f,
|
||||
0x9f, 0xc5, 0x5a, 0xfd, 0x8f, 0x31, 0xcf, 0xc3, 0xe5, 0xe6, 0xc9, 0x99,
|
||||
0xae, 0x56, 0x72, 0xe2, 0x2f, 0xdf, 0x2c, 0x0e, 0x7d, 0x51, 0xc8, 0x35,
|
||||
0x40, 0x23, 0x9e, 0x52, 0x44, 0x6f, 0x17, 0x40, 0x01, 0xd1, 0x85, 0x07,
|
||||
0x55, 0xef, 0x10, 0xc9, 0xe5, 0xb9, 0xef, 0xbf, 0xf4, 0xe5, 0x38, 0xd3,
|
||||
0x1f, 0x2b, 0x91, 0x51, 0x60, 0x75, 0xc8, 0x95, 0xcc, 0x9d, 0x5a, 0xfa,
|
||||
0x69, 0xae, 0x9a, 0x16, 0x41, 0x07, 0x9c, 0x94, 0x40, 0xb7, 0xa9, 0xdd,
|
||||
0x8d, 0xcb, 0xce, 0xc5, 0xf2, 0x5e, 0x59, 0x67, 0x69, 0x07, 0xd3, 0x04,
|
||||
0xa4, 0x99, 0x56, 0xd9, 0xdc, 0x04, 0x9a, 0x66, 0x62, 0x7b, 0x67, 0xc8,
|
||||
0xd0, 0x34, 0x69, 0x5b, 0x4a, 0xce, 0x6e, 0x53, 0x0e, 0x62, 0xf7, 0x85,
|
||||
0xe0, 0xd7, 0xb7, 0x27, 0x55, 0x3a, 0x4d, 0x36, 0x47, 0xf2, 0x74, 0x02,
|
||||
0xb3, 0x66, 0x2c, 0xda, 0xc3, 0xb3, 0x38, 0x94, 0x67, 0x82, 0x44, 0xf4,
|
||||
0x12, 0xe4, 0x1c, 0x8f, 0x22, 0x4d, 0x32, 0x35, 0xf0, 0xe3, 0x41, 0x0a,
|
||||
0x7d, 0xe4, 0xb4, 0x6e, 0x10, 0x4f, 0xa9, 0x46, 0xd5, 0xab, 0x90, 0x4c,
|
||||
0xad, 0x2c, 0x30, 0xd0, 0x9e, 0x68, 0x2c, 0xc4, 0x3c, 0xf7, 0x05, 0xdf,
|
||||
0x22, 0xaa, 0xb0, 0x82, 0xbb, 0x2c, 0x67, 0x8c, 0xfd, 0x1b, 0x04, 0x41,
|
||||
0xf1, 0x4f, 0x77, 0xa4, 0xdb, 0xfb, 0xca, 0x1d, 0xd7, 0x61, 0x8a, 0x3e,
|
||||
0x89, 0x40, 0x88, 0xf2, 0xda, 0x35, 0x2b, 0x9d, 0xbf, 0xd8, 0x98, 0x55,
|
||||
0x4e, 0x60, 0xac, 0xc1, 0x1b, 0xd4, 0xe0, 0xb8, 0x6d, 0x13, 0xa0, 0xa3,
|
||||
0x24, 0x80, 0xa0, 0xe6, 0x12, 0xad, 0x27, 0x36, 0xee, 0xd7, 0x55, 0x4b,
|
||||
0xb4, 0x1a, 0xd2, 0x87, 0x31, 0x1a, 0x00, 0x53, 0xe9, 0x0f, 0xb7, 0x50,
|
||||
0xeb, 0xdb, 0x63, 0xfe, 0xc3, 0xd0, 0xb1, 0x25, 0xdc, 0x63, 0x66, 0xcc,
|
||||
0xe6, 0x99, 0xa3, 0x34, 0x0b, 0x1d, 0xdd, 0x84, 0x88, 0x3c, 0xfc, 0x79,
|
||||
0xf5, 0x13, 0x0a, 0xe0, 0xca, 0x9e, 0x02, 0xeb, 0x06, 0xab, 0x6d, 0x80,
|
||||
0xeb, 0x06, 0x3d, 0x9a, 0xbb, 0x97, 0xd5, 0xd2, 0x23, 0x22, 0x17, 0xca,
|
||||
0x7a, 0x34, 0x09, 0xfe, 0x53, 0xfa, 0xc1, 0x34, 0x2a, 0x2c, 0xcb, 0x07,
|
||||
0xd3, 0x92, 0x86, 0x9c, 0x7b, 0xd6, 0xdc, 0xe9, 0x5d, 0xa9, 0xcd, 0xb3,
|
||||
0x72, 0xc1, 0x5d, 0xcd, 0x3f, 0xc2, 0x9b, 0xcf, 0x5a, 0x54, 0xbe, 0x50,
|
||||
0x84, 0xbc, 0xe1, 0x33, 0xbd, 0xfd, 0xb6, 0x59, 0x49, 0x11, 0x25, 0xc9,
|
||||
0x01, 0x57, 0x78, 0x8a, 0xef, 0x16, 0x7e, 0x15, 0x2d, 0x9d, 0x30, 0x4b,
|
||||
0x68, 0xa4, 0x3b, 0x99, 0xbf, 0x11, 0x70, 0x0f, 0x17, 0x33, 0xd5, 0x6e,
|
||||
0x31, 0x86, 0x7a, 0xea, 0x12, 0xdb, 0xb9, 0xbc, 0x67, 0x4e, 0x79, 0x58,
|
||||
0x2f, 0x81, 0x00, 0x00, 0x00, 0x0e, 0x41, 0x9a, 0x21, 0x6c, 0x42, 0xdf,
|
||||
0x18, 0xd3, 0x2d, 0x01, 0x43, 0x7f, 0x24, 0x38, 0x00, 0x00, 0x03, 0x1f,
|
||||
'm', 'o', 'o', 'v', 0x00, 0x00, 0x00, 0x6c, 'm', 'v', 'h', 'd',
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x03, 0xe8, 0x00, 0x00, 0x00, 0x50, 0x00, 0x01, 0x00, 0x00,
|
||||
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x02, 0x49, 't', 'r', 'a', 'k',
|
||||
0x00, 0x00, 0x00, 0x5c, 't', 'k', 'h', 'd', 0x00, 0x00, 0x00, 0x03,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x50, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00,
|
||||
0x00, 0xa0, 0x00, 0x00, 0x00, 0x5a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x24,
|
||||
'e', 'd', 't', 's', 0x00, 0x00, 0x00, 0x1c, 'e', 'l', 's', 't',
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x50,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x01, 0xc1,
|
||||
'm', 'd', 'i', 'a', 0x00, 0x00, 0x00, 0x20, 'm', 'd', 'h', 'd',
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x32, 0x00, 0x00, 0x00, 0x04, 0x00, 0x15, 0xc7, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x2d, 0x68, 0x64, 0x6c, 0x72, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x76, 0x69, 0x64, 0x65, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x56, 0x69, 0x64, 0x65,
|
||||
0x6f, 0x48, 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x72, 0x00, 0x00, 0x00, 0x01,
|
||||
0x6c, 0x6d, 0x69, 0x6e, 0x66, 0x00, 0x00, 0x00, 0x14, 0x76, 0x6d, 0x68,
|
||||
0x64, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x24, 0x64, 0x69, 0x6e, 0x66, 0x00, 0x00, 0x00,
|
||||
0x1c, 0x64, 0x72, 0x65, 0x66, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x01, 0x00, 0x00, 0x00, 0x0c, 0x75, 0x72, 0x6c, 0x20, 0x00, 0x00, 0x00,
|
||||
0x01, 0x00, 0x00, 0x01, 0x2c, 0x73, 0x74, 0x62, 0x6c, 0x00, 0x00, 0x00,
|
||||
0xac, 0x73, 0x74, 0x73, 0x64, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x01, 0x00, 0x00, 0x00, 0x9c, 0x61, 0x76, 0x63, 0x31, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00,
|
||||
0x5a, 0x00, 0x48, 0x00, 0x00, 0x00, 0x48, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x18, 0xff, 0xff, 0x00, 0x00, 0x00, 0x36, 0x61, 0x76, 0x63, 0x43, 0x01,
|
||||
0x64, 0x00, 0x0a, 0xff, 0xe1, 0x00, 0x1d, 0x67, 0x64, 0x00, 0x0a, 0xac,
|
||||
0xd9, 0x42, 0x0d, 0xf9, 0x3f, 0xf0, 0x00, 0x50, 0x00, 0x41, 0x00, 0x00,
|
||||
0x03, 0x00, 0x01, 0x00, 0x00, 0x03, 0x00, 0x32, 0x0f, 0x12, 0x25, 0x96,
|
||||
0x01, 0x00, 0x06, 0x68, 0xeb, 0xe3, 0xcb, 0x22, 0xc0, 0x00, 0x00, 0x00,
|
||||
0x10, 0x70, 0x61, 0x73, 0x70, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00,
|
||||
0x04, 0x00, 0x00, 0x00, 0x18, 0x73, 0x74, 0x74, 0x73, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x02,
|
||||
0x00, 0x00, 0x00, 0x00, 0x14, 0x73, 0x74, 0x73, 0x73, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
|
||||
0x1c, 0x73, 0x74, 0x73, 0x63, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
|
||||
0x01, 0x00, 0x00, 0x00, 0x1c, 0x73, 0x74, 0x73, 0x7a, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x07,
|
||||
0xb2, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x14, 0x73, 0x74, 0x63,
|
||||
0x6f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
|
||||
0x30, 0x00, 0x00, 0x00, 0x62, 0x75, 0x64, 0x74, 0x61, 0x00, 0x00, 0x00,
|
||||
0x5a, 0x6d, 0x65, 0x74, 0x61, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x21, 0x68, 0x64, 0x6c, 0x72, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x6d, 0x64, 0x69, 0x72, 0x61, 0x70, 0x70, 0x6c, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2d, 0x69, 0x6c,
|
||||
0x73, 0x74, 0x00, 0x00, 0x00, 0x25, 0xa9, 0x74, 0x6f, 0x6f, 0x00, 0x00,
|
||||
0x00, 0x1d, 0x64, 0x61, 0x74, 0x61, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00,
|
||||
0x00, 0x00, 0x4c, 0x61, 0x76, 0x66, 0x35, 0x36, 0x2e, 0x33, 0x33, 0x2e,
|
||||
0x31, 0x30, 0x31
|
||||
nsTArray<uint8_t>
|
||||
ReadTestFile(const char* aFilename)
|
||||
{
|
||||
if (!aFilename) {
|
||||
return {};
|
||||
}
|
||||
FILE* f = fopen(aFilename, "rb");
|
||||
if (!f) {
|
||||
return {};
|
||||
}
|
||||
|
||||
if (fseek(f, 0, SEEK_END) != 0) {
|
||||
fclose(f);
|
||||
return {};
|
||||
}
|
||||
long position = ftell(f);
|
||||
// I know EOF==-1, so this test is made obsolete by '<0', but I don't want
|
||||
// the code to rely on that.
|
||||
if (position == 0 || position == EOF || position < 0) {
|
||||
fclose(f);
|
||||
return {};
|
||||
}
|
||||
if (fseek(f, 0, SEEK_SET) != 0) {
|
||||
fclose(f);
|
||||
return {};
|
||||
}
|
||||
|
||||
size_t len = static_cast<size_t>(position);
|
||||
nsTArray<uint8_t> buffer(len);
|
||||
buffer.SetLength(len);
|
||||
size_t read = fread(buffer.Elements(), 1, len, f);
|
||||
fclose(f);
|
||||
if (read != len) {
|
||||
return {};
|
||||
}
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
struct TestFileData
|
||||
{
|
||||
const char* mFilename;
|
||||
uint32_t mNumberVideoTracks;
|
||||
int32_t mWidth;
|
||||
int32_t mHeight;
|
||||
};
|
||||
static const TestFileData testFiles[] = {
|
||||
{ "test_case_1187067.mp4", 1, 160, 90 },
|
||||
{ "test_case_1200326.mp4", 0, 0, 0 }
|
||||
};
|
||||
static const size_t test_case_mp4_len = ArrayLength(test_case_mp4);
|
||||
|
||||
TEST(stagefright_MPEG4Metadata, test_case_mp4)
|
||||
{
|
||||
nsRefPtr<MediaByteBuffer> buffer = new MediaByteBuffer(test_case_mp4_len);
|
||||
buffer->AppendElements(test_case_mp4, test_case_mp4_len);
|
||||
nsRefPtr<BufferStream> stream = new BufferStream(buffer);
|
||||
for (size_t test = 0; test < ArrayLength(testFiles); ++test) {
|
||||
nsTArray<uint8_t> buffer = ReadTestFile(testFiles[test].mFilename);
|
||||
ASSERT_FALSE(buffer.IsEmpty());
|
||||
nsRefPtr<Stream> stream = new TestStream(buffer.Elements(), buffer.Length());
|
||||
|
||||
EXPECT_TRUE(MP4Metadata::HasCompleteMetadata(stream));
|
||||
nsRefPtr<MediaByteBuffer> metadataBuffer = MP4Metadata::Metadata(stream);
|
||||
EXPECT_TRUE(metadataBuffer);
|
||||
EXPECT_TRUE(MP4Metadata::HasCompleteMetadata(stream));
|
||||
nsRefPtr<MediaByteBuffer> metadataBuffer = MP4Metadata::Metadata(stream);
|
||||
EXPECT_TRUE(metadataBuffer);
|
||||
|
||||
MP4Metadata metadata(stream);
|
||||
EXPECT_EQ(0u, metadata.GetNumberTracks(TrackInfo::kUndefinedTrack));
|
||||
EXPECT_EQ(0u, metadata.GetNumberTracks(TrackInfo::kAudioTrack));
|
||||
EXPECT_EQ(1u, metadata.GetNumberTracks(TrackInfo::kVideoTrack));
|
||||
EXPECT_EQ(0u, metadata.GetNumberTracks(TrackInfo::kTextTrack));
|
||||
EXPECT_EQ(0u, metadata.GetNumberTracks(static_cast<TrackInfo::TrackType>(-1)));
|
||||
EXPECT_FALSE(metadata.GetTrackInfo(TrackInfo::kUndefinedTrack, 0));
|
||||
EXPECT_FALSE(metadata.GetTrackInfo(TrackInfo::kAudioTrack, 0));
|
||||
UniquePtr<TrackInfo> track = metadata.GetTrackInfo(TrackInfo::kVideoTrack, 0);
|
||||
EXPECT_TRUE(!!track);
|
||||
EXPECT_FALSE(metadata.GetTrackInfo(TrackInfo::kTextTrack, 0));
|
||||
EXPECT_FALSE(metadata.GetTrackInfo(static_cast<TrackInfo::TrackType>(-1), 0));
|
||||
// We can see anywhere in any MPEG4.
|
||||
EXPECT_TRUE(metadata.CanSeek());
|
||||
EXPECT_FALSE(metadata.Crypto().valid);
|
||||
MP4Metadata metadata(stream);
|
||||
EXPECT_EQ(0u, metadata.GetNumberTracks(TrackInfo::kUndefinedTrack));
|
||||
EXPECT_EQ(0u, metadata.GetNumberTracks(TrackInfo::kAudioTrack));
|
||||
EXPECT_EQ(testFiles[test].mNumberVideoTracks,
|
||||
metadata.GetNumberTracks(TrackInfo::kVideoTrack));
|
||||
EXPECT_EQ(0u, metadata.GetNumberTracks(TrackInfo::kTextTrack));
|
||||
EXPECT_EQ(0u, metadata.GetNumberTracks(static_cast<TrackInfo::TrackType>(-1)));
|
||||
EXPECT_FALSE(metadata.GetTrackInfo(TrackInfo::kUndefinedTrack, 0));
|
||||
EXPECT_FALSE(metadata.GetTrackInfo(TrackInfo::kAudioTrack, 0));
|
||||
UniquePtr<TrackInfo> trackInfo = metadata.GetTrackInfo(TrackInfo::kVideoTrack, 0);
|
||||
if (testFiles[test].mNumberVideoTracks == 0) {
|
||||
EXPECT_TRUE(!trackInfo);
|
||||
} else {
|
||||
EXPECT_TRUE(!!trackInfo);
|
||||
if (trackInfo) {
|
||||
const VideoInfo* videoInfo = trackInfo->GetAsVideoInfo();
|
||||
EXPECT_TRUE(!!videoInfo);
|
||||
if (videoInfo) {
|
||||
EXPECT_TRUE(videoInfo->IsValid());
|
||||
EXPECT_TRUE(videoInfo->IsVideo());
|
||||
EXPECT_EQ(testFiles[test].mWidth, videoInfo->mDisplay.width);
|
||||
EXPECT_EQ(testFiles[test].mHeight, videoInfo->mDisplay.height);
|
||||
FallibleTArray<mp4_demuxer::Index::Indice> indices;
|
||||
EXPECT_TRUE(metadata.ReadTrackIndex(indices, videoInfo->mTrackId));
|
||||
}
|
||||
}
|
||||
}
|
||||
EXPECT_FALSE(metadata.GetTrackInfo(TrackInfo::kTextTrack, 0));
|
||||
EXPECT_FALSE(metadata.GetTrackInfo(static_cast<TrackInfo::TrackType>(-1), 0));
|
||||
// We can see anywhere in any MPEG4.
|
||||
EXPECT_TRUE(metadata.CanSeek());
|
||||
EXPECT_FALSE(metadata.Crypto().valid);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(stagefright_MPEG4Metadata, test_case_mp4_skimming)
|
||||
TEST(stagefright_MPEG4Metadata, test_case_mp4_subsets)
|
||||
{
|
||||
static const size_t step = 4u;
|
||||
nsRefPtr<MediaByteBuffer> buffer = new MediaByteBuffer(test_case_mp4_len);
|
||||
buffer->AppendElements(test_case_mp4, test_case_mp4_len);
|
||||
for (size_t offset = 0; offset < test_case_mp4_len - step; offset += step) {
|
||||
nsRefPtr<BufferStream> stream = new BufferStream(buffer);
|
||||
|
||||
static const size_t step = 1u;
|
||||
for (size_t test = 0; test < ArrayLength(testFiles); ++test) {
|
||||
nsTArray<uint8_t> buffer = ReadTestFile(testFiles[test].mFilename);
|
||||
ASSERT_FALSE(buffer.IsEmpty());
|
||||
ASSERT_LE(step, buffer.Length());
|
||||
// Just exercizing the parser starting at different points through the file,
|
||||
// making sure it doesn't crash.
|
||||
// No checks because results would differ for each position.
|
||||
MP4Metadata::HasCompleteMetadata(stream);
|
||||
nsRefPtr<MediaByteBuffer> metadataBuffer = MP4Metadata::Metadata(stream);
|
||||
MP4Metadata metadata(stream);
|
||||
for (size_t offset = 0; offset < buffer.Length() - step; offset += step) {
|
||||
size_t size = buffer.Length() - offset;
|
||||
while (size > 0) {
|
||||
nsRefPtr<TestStream> stream =
|
||||
new TestStream(buffer.Elements() + offset, size);
|
||||
|
||||
buffer->RemoveElementsAt(0, step);
|
||||
MP4Metadata::HasCompleteMetadata(stream);
|
||||
nsRefPtr<MediaByteBuffer> metadataBuffer = MP4Metadata::Metadata(stream);
|
||||
MP4Metadata metadata(stream);
|
||||
|
||||
if (stream->mHighestSuccessfulEndOffset <= 0) {
|
||||
// No successful reads -> Cutting down the size won't change anything.
|
||||
break;
|
||||
}
|
||||
if (stream->mHighestSuccessfulEndOffset < size) {
|
||||
// Read up to a point before the end -> Resize down to that point.
|
||||
size = stream->mHighestSuccessfulEndOffset;
|
||||
} else {
|
||||
// Read up to the end (or after?!) -> Just cut 1 byte.
|
||||
size -= 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST(stagefright_MoofParser, test_case_mp4)
|
||||
{
|
||||
nsRefPtr<MediaByteBuffer> buffer = new MediaByteBuffer(test_case_mp4_len);
|
||||
buffer->AppendElements(test_case_mp4, test_case_mp4_len);
|
||||
nsRefPtr<BufferStream> stream = new BufferStream(buffer);
|
||||
for (size_t test = 0; test < ArrayLength(testFiles); ++test) {
|
||||
nsTArray<uint8_t> buffer = ReadTestFile(testFiles[test].mFilename);
|
||||
ASSERT_FALSE(buffer.IsEmpty());
|
||||
nsRefPtr<Stream> stream = new TestStream(buffer.Elements(), buffer.Length());
|
||||
|
||||
Monitor monitor("MP4Metadata::HasCompleteMetadata");
|
||||
MonitorAutoLock mon(monitor);
|
||||
MoofParser parser(stream, 0, false, &monitor);
|
||||
EXPECT_EQ(0u, parser.mOffset);
|
||||
EXPECT_FALSE(parser.ReachedEnd());
|
||||
|
||||
nsTArray<MediaByteRange> byteRanges;
|
||||
byteRanges.AppendElement(MediaByteRange(0, 0));
|
||||
EXPECT_FALSE(parser.RebuildFragmentedIndex(byteRanges));
|
||||
|
||||
EXPECT_TRUE(parser.GetCompositionRange(byteRanges).IsNull());
|
||||
EXPECT_TRUE(parser.mInitRange.IsNull());
|
||||
EXPECT_EQ(0u, parser.mOffset);
|
||||
EXPECT_FALSE(parser.ReachedEnd());
|
||||
EXPECT_TRUE(parser.HasMetadata());
|
||||
nsRefPtr<MediaByteBuffer> metadataBuffer = parser.Metadata();
|
||||
EXPECT_TRUE(metadataBuffer);
|
||||
EXPECT_TRUE(parser.FirstCompleteMediaSegment().IsNull());
|
||||
EXPECT_TRUE(parser.FirstCompleteMediaHeader().IsNull());
|
||||
}
|
||||
|
||||
TEST(stagefright_MoofParser, test_case_mp4_skimming)
|
||||
{
|
||||
const size_t step = 4u;
|
||||
nsRefPtr<MediaByteBuffer> buffer = new MediaByteBuffer(test_case_mp4_len);
|
||||
buffer->AppendElements(test_case_mp4, test_case_mp4_len);
|
||||
Monitor monitor("MP4Metadata::HasCompleteMetadata");
|
||||
MonitorAutoLock mon(monitor);
|
||||
for (size_t offset = 0; offset < test_case_mp4_len - step; offset += step) {
|
||||
nsRefPtr<BufferStream> stream = new BufferStream(buffer);
|
||||
|
||||
// Just exercizing the parser starting at different points through the file,
|
||||
// making sure it doesn't crash.
|
||||
// No checks because results would differ for each position.
|
||||
Monitor monitor("MP4Metadata::HasCompleteMetadata");
|
||||
MonitorAutoLock mon(monitor);
|
||||
MoofParser parser(stream, 0, false, &monitor);
|
||||
EXPECT_EQ(0u, parser.mOffset);
|
||||
EXPECT_FALSE(parser.ReachedEnd());
|
||||
|
||||
nsTArray<MediaByteRange> byteRanges;
|
||||
byteRanges.AppendElement(MediaByteRange(0, 0));
|
||||
EXPECT_FALSE(parser.RebuildFragmentedIndex(byteRanges));
|
||||
parser.GetCompositionRange(byteRanges);
|
||||
parser.HasMetadata();
|
||||
nsRefPtr<MediaByteBuffer> metadataBuffer = parser.Metadata();
|
||||
parser.FirstCompleteMediaSegment();
|
||||
parser.FirstCompleteMediaHeader();
|
||||
|
||||
buffer->RemoveElementsAt(0, step);
|
||||
EXPECT_TRUE(parser.GetCompositionRange(byteRanges).IsNull());
|
||||
EXPECT_TRUE(parser.mInitRange.IsNull());
|
||||
EXPECT_EQ(0u, parser.mOffset);
|
||||
EXPECT_FALSE(parser.ReachedEnd());
|
||||
EXPECT_TRUE(parser.HasMetadata());
|
||||
nsRefPtr<MediaByteBuffer> metadataBuffer = parser.Metadata();
|
||||
EXPECT_TRUE(metadataBuffer);
|
||||
EXPECT_TRUE(parser.FirstCompleteMediaSegment().IsNull());
|
||||
EXPECT_TRUE(parser.FirstCompleteMediaHeader().IsNull());
|
||||
}
|
||||
}
|
||||
|
||||
TEST(stagefright_MoofParser, test_case_mp4_subsets)
|
||||
{
|
||||
const size_t step = 1u;
|
||||
for (size_t test = 0; test < ArrayLength(testFiles); ++test) {
|
||||
nsTArray<uint8_t> buffer = ReadTestFile(testFiles[test].mFilename);
|
||||
ASSERT_FALSE(buffer.IsEmpty());
|
||||
ASSERT_LE(step, buffer.Length());
|
||||
Monitor monitor("MP4Metadata::HasCompleteMetadata");
|
||||
MonitorAutoLock mon(monitor);
|
||||
// Just exercizing the parser starting at different points through the file,
|
||||
// making sure it doesn't crash.
|
||||
// No checks because results would differ for each position.
|
||||
for (size_t offset = 0; offset < buffer.Length() - step; offset += step) {
|
||||
size_t size = buffer.Length() - offset;
|
||||
while (size > 0) {
|
||||
nsRefPtr<TestStream> stream =
|
||||
new TestStream(buffer.Elements() + offset, size);
|
||||
|
||||
MoofParser parser(stream, 0, false, &monitor);
|
||||
nsTArray<MediaByteRange> byteRanges;
|
||||
byteRanges.AppendElement(MediaByteRange(0, 0));
|
||||
EXPECT_FALSE(parser.RebuildFragmentedIndex(byteRanges));
|
||||
parser.GetCompositionRange(byteRanges);
|
||||
parser.HasMetadata();
|
||||
nsRefPtr<MediaByteBuffer> metadataBuffer = parser.Metadata();
|
||||
parser.FirstCompleteMediaSegment();
|
||||
parser.FirstCompleteMediaHeader();
|
||||
|
||||
if (stream->mHighestSuccessfulEndOffset <= 0) {
|
||||
// No successful reads -> Cutting down the size won't change anything.
|
||||
break;
|
||||
}
|
||||
if (stream->mHighestSuccessfulEndOffset < size) {
|
||||
// Read up to a point before the end -> Resize down to that point.
|
||||
size = stream->mHighestSuccessfulEndOffset;
|
||||
} else {
|
||||
// Read up to the end (or after?!) -> Just cut 1 byte.
|
||||
size -= 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -11,6 +11,11 @@ SOURCES += [
|
|||
'TestParser.cpp',
|
||||
]
|
||||
|
||||
TEST_HARNESS_FILES.gtest += [
|
||||
'test_case_1187067.mp4',
|
||||
'test_case_1200326.mp4',
|
||||
]
|
||||
|
||||
if CONFIG['MOZ_RUST']:
|
||||
UNIFIED_SOURCES += ['TestMP4Rust.cpp',]
|
||||
TEST_HARNESS_FILES.gtest += [
|
||||
|
|
Двоичный файл не отображается.
Двоичный файл не отображается.
|
@ -84,6 +84,7 @@ import android.graphics.RectF;
|
|||
import android.graphics.SurfaceTexture;
|
||||
import android.graphics.drawable.BitmapDrawable;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.hardware.display.DisplayManager;
|
||||
import android.hardware.Sensor;
|
||||
import android.hardware.SensorEventListener;
|
||||
import android.hardware.SensorManager;
|
||||
|
@ -110,6 +111,7 @@ import android.util.Base64;
|
|||
import android.util.DisplayMetrics;
|
||||
import android.util.Log;
|
||||
import android.view.ContextThemeWrapper;
|
||||
import android.view.Display;
|
||||
import android.view.HapticFeedbackConstants;
|
||||
import android.view.Surface;
|
||||
import android.view.SurfaceView;
|
||||
|
@ -2740,4 +2742,10 @@ public class GeckoAppShell
|
|||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
@WrapForJNI
|
||||
static Rect getScreenSize() {
|
||||
Display disp = getGeckoInterface().getActivity().getWindowManager().getDefaultDisplay();
|
||||
return new Rect(0, 0, disp.getWidth(), disp.getHeight());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -97,9 +97,6 @@ public class GeckoEvent {
|
|||
LOW_MEMORY(35),
|
||||
NETWORK_LINK_CHANGE(36),
|
||||
TELEMETRY_HISTOGRAM_ADD(37),
|
||||
PREFERENCES_OBSERVE(39),
|
||||
PREFERENCES_GET(40),
|
||||
PREFERENCES_REMOVE_OBSERVERS(41),
|
||||
TELEMETRY_UI_SESSION_START(42),
|
||||
TELEMETRY_UI_SESSION_STOP(43),
|
||||
TELEMETRY_UI_EVENT(44),
|
||||
|
@ -225,8 +222,6 @@ public class GeckoEvent {
|
|||
private float mGamepadButtonValue;
|
||||
private float[] mGamepadValues;
|
||||
|
||||
private String[] mPrefNames;
|
||||
|
||||
private Object mObject;
|
||||
|
||||
private GeckoEvent(NativeGeckoEvent event) {
|
||||
|
@ -767,29 +762,6 @@ public class GeckoEvent {
|
|||
return event;
|
||||
}
|
||||
|
||||
@RobocopTarget
|
||||
public static GeckoEvent createPreferencesObserveEvent(int requestId, String[] prefNames) {
|
||||
GeckoEvent event = GeckoEvent.get(NativeGeckoEvent.PREFERENCES_OBSERVE);
|
||||
event.mCount = requestId;
|
||||
event.mPrefNames = prefNames;
|
||||
return event;
|
||||
}
|
||||
|
||||
@RobocopTarget
|
||||
public static GeckoEvent createPreferencesGetEvent(int requestId, String[] prefNames) {
|
||||
GeckoEvent event = GeckoEvent.get(NativeGeckoEvent.PREFERENCES_GET);
|
||||
event.mCount = requestId;
|
||||
event.mPrefNames = prefNames;
|
||||
return event;
|
||||
}
|
||||
|
||||
@RobocopTarget
|
||||
public static GeckoEvent createPreferencesRemoveObserversEvent(int requestId) {
|
||||
GeckoEvent event = GeckoEvent.get(NativeGeckoEvent.PREFERENCES_REMOVE_OBSERVERS);
|
||||
event.mCount = requestId;
|
||||
return event;
|
||||
}
|
||||
|
||||
public static GeckoEvent createLowMemoryEvent(int level) {
|
||||
GeckoEvent event = GeckoEvent.get(NativeGeckoEvent.LOW_MEMORY);
|
||||
event.mMetaState = level;
|
||||
|
|
|
@ -5,6 +5,8 @@
|
|||
|
||||
package org.mozilla.gecko;
|
||||
|
||||
import org.mozilla.gecko.annotation.RobocopTarget;
|
||||
import org.mozilla.gecko.annotation.WrapForJNI;
|
||||
import org.mozilla.gecko.util.GeckoEventListener;
|
||||
|
||||
import org.json.JSONArray;
|
||||
|
@ -26,6 +28,11 @@ public final class PrefsHelper {
|
|||
private static int sUniqueRequestId = 1;
|
||||
static final SparseArray<PrefHandler> sCallbacks = new SparseArray<PrefHandler>();
|
||||
|
||||
@WrapForJNI @RobocopTarget
|
||||
/* package */ static native void getPrefsById(int requestId, String[] prefNames, boolean observe);
|
||||
@WrapForJNI @RobocopTarget
|
||||
/* package */ static native void removePrefsObserver(int requestId);
|
||||
|
||||
public static int getPref(String prefName, PrefHandler callback) {
|
||||
return getPrefsInternal(new String[] { prefName }, callback);
|
||||
}
|
||||
|
@ -47,14 +54,15 @@ public final class PrefsHelper {
|
|||
sCallbacks.put(requestId, callback);
|
||||
}
|
||||
|
||||
GeckoEvent event;
|
||||
if (callback.isObserver()) {
|
||||
event = GeckoEvent.createPreferencesObserveEvent(requestId, prefNames);
|
||||
// Because we use JS to handle pref events, we need to wait until the RUNNING state.
|
||||
// If we ever convert that to native code, we can switch to using the JNI_READY state.
|
||||
if (GeckoThread.isStateAtLeast(GeckoThread.State.RUNNING)) {
|
||||
getPrefsById(requestId, prefNames, callback.isObserver());
|
||||
} else {
|
||||
event = GeckoEvent.createPreferencesGetEvent(requestId, prefNames);
|
||||
GeckoThread.queueNativeCallUntil(
|
||||
GeckoThread.State.RUNNING, PrefsHelper.class, "getPrefsById",
|
||||
requestId, prefNames, callback.isObserver());
|
||||
}
|
||||
GeckoAppShell.sendEventToGecko(event);
|
||||
|
||||
return requestId;
|
||||
}
|
||||
|
||||
|
|
|
@ -959,6 +959,7 @@ abstract class BaseTest extends BaseRobocopTest {
|
|||
* Set the preference and wait for it to change before proceeding with the test.
|
||||
*/
|
||||
public void setPreferenceAndWaitForChange(final JSONObject jsonPref) {
|
||||
blockForGeckoReady();
|
||||
mActions.sendGeckoEvent("Preferences:Set", jsonPref.toString());
|
||||
|
||||
// Get the preference name from the json and store it in an array. This array
|
||||
|
|
|
@ -4272,6 +4272,11 @@ pref("layers.tiles.edge-padding", false);
|
|||
pref("layers.tiled-drawtarget.enabled", true);
|
||||
#endif
|
||||
|
||||
#ifdef MOZ_WIDGET_ANDROID
|
||||
pref("layers.tiled-drawtarget.enabled", true);
|
||||
pref("layers.tiles.edge-padding", true);
|
||||
#endif
|
||||
|
||||
// same effect as layers.offmainthreadcomposition.enabled, but specifically for
|
||||
// use with tests.
|
||||
pref("layers.offmainthreadcomposition.testing.enabled", false);
|
||||
|
|
|
@ -59,11 +59,11 @@ build() {
|
|||
|
||||
if [ -f $folder/build.sh ]; then
|
||||
shift
|
||||
$folder/build.sh -t $tag $*
|
||||
$folder/build.sh -t $tag $* || exit 1
|
||||
else
|
||||
# use --no-cache so that we always get the latest updates from yum
|
||||
# and use the latest version of system-setup.sh
|
||||
docker build --no-cache -t $tag $folder
|
||||
docker build --no-cache -t $tag $folder || exit 1
|
||||
fi
|
||||
|
||||
echo "Success built $folder and tagged with $tag"
|
||||
|
|
|
@ -1 +1 @@
|
|||
0.0.2
|
||||
0.1.0
|
||||
|
|
|
@ -24,30 +24,220 @@ install sed
|
|||
install tar
|
||||
install util-linux
|
||||
install autoconf213
|
||||
install zlib-devel
|
||||
install perl-Test-Simple
|
||||
install perl-Config-General
|
||||
install xorg-x11-font* # fonts required for PGO
|
||||
install dbus-x11
|
||||
install libstdc++
|
||||
|
||||
# fonts required for PGO
|
||||
install xorg-x11-font*
|
||||
|
||||
# lots of required packages that we build against. We need the i686 and x86_64
|
||||
# versions of each, along with -devel packages, and yum does a poor job of
|
||||
# figuring out the interdependencies so we list all four.
|
||||
|
||||
install alsa-lib-devel.i686
|
||||
install alsa-lib-devel.x86_64
|
||||
install alsa-lib.i686
|
||||
install alsa-lib.x86_64
|
||||
install atk-devel.i686
|
||||
install atk-devel.x86_64
|
||||
install atk.i686
|
||||
install atk.x86_64
|
||||
install cairo-devel.i686
|
||||
install cairo-devel.x86_64
|
||||
install cairo.i686
|
||||
install cairo.x86_64
|
||||
install check-devel.i686
|
||||
install check-devel.x86_64
|
||||
install check.i686
|
||||
install check.x86_64
|
||||
install dbus-glib-devel.i686
|
||||
install dbus-glib-devel.x86_64
|
||||
install dbus-glib.i686
|
||||
install dbus-glib.x86_64
|
||||
install fontconfig-devel.i686
|
||||
install fontconfig-devel.x86_64
|
||||
install fontconfig.i686
|
||||
install fontconfig.x86_64
|
||||
install freetype-devel.i686
|
||||
install freetype-devel.x86_64
|
||||
install freetype.i686
|
||||
install freetype.x86_64
|
||||
install GConf2-devel.i686
|
||||
install GConf2-devel.x86_64
|
||||
install GConf2.i686
|
||||
install GConf2.x86_64
|
||||
install glib2-devel.i686
|
||||
install glib2-devel.x86_64
|
||||
install glib2.i686
|
||||
install glib2.x86_64
|
||||
install glibc-devel.i686
|
||||
install glibc-devel.x86_64
|
||||
install glibc.i686
|
||||
install glibc.x86_64
|
||||
install gnome-vfs2-devel.i686
|
||||
install gnome-vfs2-devel.x86_64
|
||||
install gnome-vfs2.i686
|
||||
install gnome-vfs2.x86_64
|
||||
install gstreamer-devel.i686
|
||||
install gstreamer-devel.x86_64
|
||||
install gstreamer.i686
|
||||
install gstreamer-plugins-base-devel.i686
|
||||
install gstreamer-plugins-base-devel.x86_64
|
||||
install gstreamer-plugins-base.i686
|
||||
install gstreamer-plugins-base.x86_64
|
||||
install gstreamer.x86_64
|
||||
install gtk2-devel.i686
|
||||
install gtk2-devel.x86_64
|
||||
install gtk2.i686
|
||||
install gtk2.x86_64
|
||||
install libcurl-devel.i686
|
||||
install libcurl-devel.x86_64
|
||||
install libcurl.i686
|
||||
install libcurl.x86_64
|
||||
install libdrm-devel.i686
|
||||
install libdrm-devel.x86_64
|
||||
install libdrm.i686
|
||||
install libdrm.x86_64
|
||||
install libICE-devel.i686
|
||||
install libICE-devel.x86_64
|
||||
install libICE.i686
|
||||
install libICE.x86_64
|
||||
install libIDL-devel.i686
|
||||
install libIDL-devel.x86_64
|
||||
install libIDL.i686
|
||||
install libIDL.x86_64
|
||||
install libidn-devel.i686
|
||||
install libidn-devel.x86_64
|
||||
install libidn.i686
|
||||
install libidn.x86_64
|
||||
install libnotify-devel.i686
|
||||
install libnotify-devel.x86_64
|
||||
install libnotify.i686
|
||||
install libnotify.x86_64
|
||||
install libpng-devel.i686
|
||||
install libpng-devel.x86_64
|
||||
install libpng.i686
|
||||
install libpng.x86_64
|
||||
install libSM-devel.i686
|
||||
install libSM-devel.x86_64
|
||||
install libSM.i686
|
||||
install libSM.x86_64
|
||||
install libstdc++-devel.i686
|
||||
install libstdc++-devel.x86_64
|
||||
install libstdc++.i686
|
||||
install libstdc++.x86_64
|
||||
install libX11-devel.i686
|
||||
install libX11-devel.x86_64
|
||||
install libX11.i686
|
||||
install libX11.x86_64
|
||||
install libXau-devel.i686
|
||||
install libXau-devel.x86_64
|
||||
install libXau.i686
|
||||
install libXau.x86_64
|
||||
install libxcb-devel.i686
|
||||
install libxcb-devel.x86_64
|
||||
install libxcb.i686
|
||||
install libxcb.x86_64
|
||||
install libXcomposite-devel.i686
|
||||
install libXcomposite-devel.x86_64
|
||||
install libXcomposite.i686
|
||||
install libXcomposite.x86_64
|
||||
install libXcursor-devel.i686
|
||||
install libXcursor-devel.x86_64
|
||||
install libXcursor.i686
|
||||
install libXcursor.x86_64
|
||||
install libXdamage-devel.i686
|
||||
install libXdamage-devel.x86_64
|
||||
install libXdamage.i686
|
||||
install libXdamage.x86_64
|
||||
install libXdmcp-devel.i686
|
||||
install libXdmcp-devel.x86_64
|
||||
install libXdmcp.i686
|
||||
install libXdmcp.x86_64
|
||||
install libXext-devel.i686
|
||||
install libXext-devel.x86_64
|
||||
install libXext.i686
|
||||
install libXext.x86_64
|
||||
install libXfixes-devel.i686
|
||||
install libXfixes-devel.x86_64
|
||||
install libXfixes.i686
|
||||
install libXfixes.x86_64
|
||||
install libXft-devel.i686
|
||||
install libXft-devel.x86_64
|
||||
install libXft.i686
|
||||
install libXft.x86_64
|
||||
install libXi-devel.i686
|
||||
install libXi-devel.x86_64
|
||||
install libXi.i686
|
||||
install libXinerama-devel.i686
|
||||
install libXinerama-devel.x86_64
|
||||
install libXinerama.i686
|
||||
install libXinerama.x86_64
|
||||
install libXi.x86_64
|
||||
install libxml2-devel.i686
|
||||
install libxml2-devel.x86_64
|
||||
install libxml2.i686
|
||||
install libxml2.x86_64
|
||||
install libXrandr-devel.i686
|
||||
install libXrandr-devel.x86_64
|
||||
install libXrandr.i686
|
||||
install libXrandr.x86_64
|
||||
install libXrender-devel.i686
|
||||
install libXrender-devel.x86_64
|
||||
install libXrender.i686
|
||||
install libXrender.x86_64
|
||||
install libXt-devel.i686
|
||||
install libXt-devel.x86_64
|
||||
install libXt.i686
|
||||
install libXt.x86_64
|
||||
install libXxf86vm-devel.i686
|
||||
install libXxf86vm-devel.x86_64
|
||||
install libXxf86vm.i686
|
||||
install libXxf86vm.x86_64
|
||||
install mesa-libGL-devel.i686
|
||||
install mesa-libGL-devel.x86_64
|
||||
install mesa-libGL.i686
|
||||
install mesa-libGL.x86_64
|
||||
install ORBit2-devel.i686
|
||||
install ORBit2-devel.x86_64
|
||||
install ORBit2.i686
|
||||
install ORBit2.x86_64
|
||||
install pango-devel.i686
|
||||
install pango-devel.x86_64
|
||||
install pango.i686
|
||||
install pango.x86_64
|
||||
install pixman-devel.i686
|
||||
install pixman-devel.x86_64
|
||||
install pixman.i686
|
||||
install pixman.x86_64
|
||||
install pulseaudio-libs-devel.i686
|
||||
install pulseaudio-libs-devel.x86_64
|
||||
install pulseaudio-libs.i686
|
||||
install pulseaudio-libs.x86_64
|
||||
install wireless-tools-devel.i686
|
||||
install wireless-tools-devel.x86_64
|
||||
install wireless-tools.i686
|
||||
install wireless-tools.x86_64
|
||||
install zlib-devel.i686
|
||||
install zlib-devel.x86_64
|
||||
install zlib.i686
|
||||
install zlib.x86_64
|
||||
|
||||
# lots of required -devel packages
|
||||
install libnotify-devel
|
||||
install alsa-lib-devel
|
||||
install libcurl-devel
|
||||
install wireless-tools-devel
|
||||
install libX11-devel
|
||||
install libXt-devel
|
||||
install mesa-libGL-devel
|
||||
install GConf2-devel
|
||||
install pulseaudio-libs-devel
|
||||
install gstreamer-devel
|
||||
install gstreamer-plugins-base-devel
|
||||
# x86_64 only packages
|
||||
install hal-devel.x86_64
|
||||
install hal.x86_64
|
||||
install perl-devel.x86_64
|
||||
install perl.x86_64
|
||||
install dbus-x11.x86_64
|
||||
|
||||
# Prerequisites for GNOME that are not included in tooltool
|
||||
install libpng-devel
|
||||
install libXrandr-devel
|
||||
# glibc-static has no -devel
|
||||
install glibc-static.i686
|
||||
install glibc-static.x86_64
|
||||
|
||||
# dbus-devel comes in two architectures, although dbus does not
|
||||
install dbus-devel.i686
|
||||
install dbus-devel.x86_64
|
||||
install dbus.x86_64
|
||||
|
||||
# required for the Python build, below
|
||||
install bzip2-devel
|
||||
|
@ -57,6 +247,7 @@ install sqlite-devel
|
|||
|
||||
# required for the git build, below
|
||||
install autoconf
|
||||
install perl-devel
|
||||
install perl-ExtUtils-MakeMaker
|
||||
install gettext-devel
|
||||
|
||||
|
@ -76,6 +267,7 @@ install patch
|
|||
install libuuid-devel
|
||||
install openssl-static
|
||||
install cmake
|
||||
install subversion
|
||||
run
|
||||
EOF
|
||||
|
||||
|
@ -105,18 +297,6 @@ tooltool_fetch() {
|
|||
cd $BUILD
|
||||
tooltool_fetch <<'EOF'
|
||||
[
|
||||
{
|
||||
"size": 383372,
|
||||
"digest": "486c34aeb01d2d083fdccd043fb8e3af93efddc7c185f38b5d7e90199733424c538ac2a7dfc19999c82ff00b84de5df75aa6f891e622cf1a86d698ed0bee16e2",
|
||||
"algorithm": "sha512",
|
||||
"filename": "freetype-2.4.12-6.el6.1.x86_64.rpm"
|
||||
},
|
||||
{
|
||||
"size": 397772,
|
||||
"digest": "79c7ddc943394509b96593f3aeac8946acd9c9dc10a3c248a783cb282781cbc9e4e7c47b98121dd7849d39aa3bd29313234ae301ab80f769010c2bacf41e6d40",
|
||||
"algorithm": "sha512",
|
||||
"filename": "freetype-devel-2.4.12-6.el6.1.x86_64.rpm"
|
||||
},
|
||||
{
|
||||
"size": 17051332,
|
||||
"digest": "57c816a6df9731aa5f34678abb59ea560bbdb5abd01df3f3a001dc94a3695d3190b1121caba483f8d8c4a405f4e53fde63a628527aca73f05652efeaec9621c4",
|
||||
|
@ -131,11 +311,10 @@ tooltool_fetch <<'EOF'
|
|||
}
|
||||
]
|
||||
EOF
|
||||
yum install -y freetype-*.rpm
|
||||
yum install -y valgrind-*.rpm
|
||||
yum install -y yasm-*.rpm
|
||||
|
||||
# The source RPMs for some of those; not used here, but included for reference
|
||||
# The source RPM for valgrind; not used here, but included for reference
|
||||
: <<'EOF'
|
||||
[
|
||||
{
|
||||
|
@ -143,12 +322,6 @@ yum install -y yasm-*.rpm
|
|||
"digest": "d435897b602f7bdf77fabf1c80bbd06ba4f7288ad0ef31d19a863546d4651172421b45f2f090bad3c3355c9fa2a00352066f18d99bf994838579b768b90553d3",
|
||||
"algorithm": "sha512",
|
||||
"filename": "valgrind-3.10.0-1.src.rpm"
|
||||
},
|
||||
{
|
||||
"size": 1906249,
|
||||
"digest": "c3811c74e4ca3c342e6625cdb80a62903a14c56436774149754de67f41f79ee63248c71d039dd9341d22bdf4ef8b7dc8c90fe63dd9a3df44197d38a2d3d8f4a2",
|
||||
"algorithm": "sha512",
|
||||
"filename": "freetype-2.4.12-6.el6.1.src.rpm"
|
||||
}
|
||||
]
|
||||
EOF
|
||||
|
@ -271,6 +444,7 @@ remove autoconf
|
|||
remove perl-ExtUtils-MakeMaker
|
||||
remove gettext-devel
|
||||
remove sqlite-devel
|
||||
remove perl-devel
|
||||
EOF
|
||||
|
||||
# clean up caches from all that downloading and building
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
FROM taskcluster/centos6-build:0.0.2
|
||||
FROM taskcluster/centos6-build:0.1.0
|
||||
MAINTAINER Dustin J. Mitchell <dustin@mozilla.com>
|
||||
|
||||
# Add build scripts; these are the entry points from the taskcluster worker, and
|
||||
|
|
|
@ -1 +1 @@
|
|||
0.1.3
|
||||
0.1.4
|
||||
|
|
|
@ -30,6 +30,26 @@ except ImportError:
|
|||
conditions = None
|
||||
|
||||
|
||||
def get_default_valgrind_suppression_files():
|
||||
# We are trying to locate files in the source tree. So if we
|
||||
# don't know where the source tree is, we must give up.
|
||||
if build_obj is None or build_obj.topsrcdir is None:
|
||||
return []
|
||||
|
||||
supps_path = os.path.join(build_obj.topsrcdir, "build", "valgrind")
|
||||
|
||||
rv = []
|
||||
if mozinfo.os == "linux":
|
||||
if mozinfo.processor == "x86_64":
|
||||
rv.append(os.path.join(supps_path, "x86_64-redhat-linux-gnu.sup"))
|
||||
rv.append(os.path.join(supps_path, "cross-architecture.sup"))
|
||||
elif mozinfo.processor == "x86":
|
||||
rv.append(os.path.join(supps_path, "i386-redhat-linux-gnu.sup"))
|
||||
rv.append(os.path.join(supps_path, "cross-architecture.sup"))
|
||||
|
||||
return rv
|
||||
|
||||
|
||||
class ArgumentContainer():
|
||||
__metaclass__ = ABCMeta
|
||||
|
||||
|
@ -475,6 +495,20 @@ class MochitestArguments(ArgumentContainer):
|
|||
"default": None,
|
||||
"help": "Arguments to pass to the debugger.",
|
||||
}],
|
||||
[["--valgrind"],
|
||||
{"default": None,
|
||||
"help": "Valgrind binary to run tests with. Program name or path.",
|
||||
}],
|
||||
[["--valgrind-args"],
|
||||
{"dest": "valgrindArgs",
|
||||
"default": None,
|
||||
"help": "Extra arguments to pass to Valgrind.",
|
||||
}],
|
||||
[["--valgrind-supp-files"],
|
||||
{"dest": "valgrindSuppFiles",
|
||||
"default": None,
|
||||
"help": "Comma-separated list of suppression files to pass to Valgrind.",
|
||||
}],
|
||||
[["--debugger-interactive"],
|
||||
{"action": "store_true",
|
||||
"dest": "debuggerInteractive",
|
||||
|
|
|
@ -45,7 +45,9 @@ from manifestparser.filters import (
|
|||
tags,
|
||||
)
|
||||
from leaks import ShutdownLeaks, LSANLeaks
|
||||
from mochitest_options import MochitestArgumentParser, build_obj
|
||||
from mochitest_options import (
|
||||
MochitestArgumentParser, build_obj, get_default_valgrind_suppression_files
|
||||
)
|
||||
from mozprofile import Profile, Preferences
|
||||
from mozprofile.permissions import ServerLocations
|
||||
from urllib import quote_plus as encodeURIComponent
|
||||
|
@ -1626,6 +1628,9 @@ class Mochitest(MochitestUtilsMixin):
|
|||
extraArgs,
|
||||
utilityPath,
|
||||
debuggerInfo=None,
|
||||
valgrindPath=None,
|
||||
valgrindArgs=None,
|
||||
valgrindSuppFiles=None,
|
||||
symbolsPath=None,
|
||||
timeout=-1,
|
||||
onLaunch=None,
|
||||
|
@ -1641,6 +1646,11 @@ class Mochitest(MochitestUtilsMixin):
|
|||
# configure the message logger buffering
|
||||
self.message_logger.buffering = quiet
|
||||
|
||||
# It can't be the case that both a with-debugger and an
|
||||
# on-Valgrind run have been requested. doTests() should have
|
||||
# already excluded this possibility.
|
||||
assert not(valgrindPath and debuggerInfo)
|
||||
|
||||
# debugger information
|
||||
interactive = False
|
||||
debug_args = None
|
||||
|
@ -1648,10 +1658,32 @@ class Mochitest(MochitestUtilsMixin):
|
|||
interactive = debuggerInfo.interactive
|
||||
debug_args = [debuggerInfo.path] + debuggerInfo.args
|
||||
|
||||
# Set up Valgrind arguments.
|
||||
if valgrindPath:
|
||||
interactive = False
|
||||
valgrindArgs_split = ([] if valgrindArgs is None
|
||||
else valgrindArgs.split())
|
||||
valgrindSuppFiles_split = ([] if valgrindSuppFiles is None
|
||||
else valgrindSuppFiles.split(","))
|
||||
|
||||
valgrindSuppFiles_final = []
|
||||
if valgrindSuppFiles is not None:
|
||||
valgrindSuppFiles_final = ["--suppressions=" + path for path in valgrindSuppFiles.split(",")]
|
||||
|
||||
debug_args = ([valgrindPath]
|
||||
+ mozdebug.get_default_valgrind_args()
|
||||
+ valgrindArgs_split
|
||||
+ valgrindSuppFiles_final)
|
||||
|
||||
# fix default timeout
|
||||
if timeout == -1:
|
||||
timeout = self.DEFAULT_TIMEOUT
|
||||
|
||||
# Note in the log if running on Valgrind
|
||||
if valgrindPath:
|
||||
self.log.info("runtests.py | Running on Valgrind. "
|
||||
+ "Using timeout of %d seconds." % timeout)
|
||||
|
||||
# copy env so we don't munge the caller's environment
|
||||
env = env.copy()
|
||||
|
||||
|
@ -2120,6 +2152,29 @@ class Mochitest(MochitestUtilsMixin):
|
|||
return 1
|
||||
self.mediaDevices = devices
|
||||
|
||||
# See if we were asked to run on Valgrind
|
||||
valgrindPath = None
|
||||
valgrindArgs = None
|
||||
valgrindSuppFiles = None
|
||||
if options.valgrind:
|
||||
valgrindPath = options.valgrind
|
||||
if options.valgrindArgs:
|
||||
valgrindArgs = options.valgrindArgs
|
||||
if options.valgrindSuppFiles:
|
||||
valgrindSuppFiles = options.valgrindSuppFiles
|
||||
|
||||
if (valgrindArgs or valgrindSuppFiles) and not valgrindPath:
|
||||
self.log.error("Specified --valgrind-args or --valgrind-supp-files,"
|
||||
" but not --valgrind")
|
||||
return 1
|
||||
|
||||
if valgrindPath and debuggerInfo:
|
||||
self.log.error("Can't use both --debugger and --valgrind together")
|
||||
return 1
|
||||
|
||||
if valgrindPath and not valgrindSuppFiles:
|
||||
valgrindSuppFiles = ",".join(get_default_valgrind_suppression_files())
|
||||
|
||||
# buildProfile sets self.profile .
|
||||
# This relies on sideeffects and isn't very stateful:
|
||||
# https://bugzilla.mozilla.org/show_bug.cgi?id=919300
|
||||
|
@ -2218,6 +2273,9 @@ class Mochitest(MochitestUtilsMixin):
|
|||
extraArgs=options.browserArgs,
|
||||
utilityPath=options.utilityPath,
|
||||
debuggerInfo=debuggerInfo,
|
||||
valgrindPath=valgrindPath,
|
||||
valgrindArgs=valgrindArgs,
|
||||
valgrindSuppFiles=valgrindSuppFiles,
|
||||
symbolsPath=options.symbolsPath,
|
||||
timeout=timeout,
|
||||
onLaunch=onLaunch,
|
||||
|
@ -2513,7 +2571,8 @@ class Mochitest(MochitestUtilsMixin):
|
|||
|
||||
def run_test_harness(options):
|
||||
logger_options = {
|
||||
key: value for key, value in vars(options).iteritems() if key.startswith('log')}
|
||||
key: value for key, value in vars(options).iteritems()
|
||||
if key.startswith('log') or key == 'valgrind' }
|
||||
runner = Mochitest(logger_options)
|
||||
|
||||
options.runByDir = False
|
||||
|
@ -2524,9 +2583,6 @@ def run_test_harness(options):
|
|||
if mozinfo.info['asan'] and options.e10s:
|
||||
options.runByDir = False
|
||||
|
||||
if mozinfo.isMac and mozinfo.info['debug']:
|
||||
options.runByDir = False
|
||||
|
||||
if runner.getTestFlavor(options) == 'browser-chrome':
|
||||
options.runByDir = True
|
||||
|
||||
|
|
|
@ -11,7 +11,8 @@ from distutils.spawn import find_executable
|
|||
|
||||
__all__ = ['get_debugger_info',
|
||||
'get_default_debugger_name',
|
||||
'DebuggerSearch']
|
||||
'DebuggerSearch',
|
||||
'get_default_valgrind_args']
|
||||
|
||||
'''
|
||||
Map of debugging programs to information about them, like default arguments
|
||||
|
@ -49,20 +50,6 @@ _DEBUGGER_INFO = {
|
|||
'wdexpress.exe': {
|
||||
'interactive': True,
|
||||
'args': ['-debugexe']
|
||||
},
|
||||
|
||||
# valgrind doesn't explain much about leaks unless you set the
|
||||
# '--leak-check=full' flag. But there are a lot of objects that are
|
||||
# semi-deliberately leaked, so we set '--show-possibly-lost=no' to avoid
|
||||
# uninteresting output from those objects. We set '--smc-check==all-non-file'
|
||||
# and '--vex-iropt-register-updates=allregs-at-mem-access' so that valgrind
|
||||
# deals properly with JIT'd JavaScript code.
|
||||
'valgrind': {
|
||||
'interactive': False,
|
||||
'args': ['--leak-check=full',
|
||||
'--show-possibly-lost=no',
|
||||
'--smc-check=all-non-file',
|
||||
'--vex-iropt-register-updates=allregs-at-mem-access']
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -166,3 +153,67 @@ def get_default_debugger_name(search=DebuggerSearch.OnlyFirst):
|
|||
return None
|
||||
|
||||
return None
|
||||
|
||||
# Defines default values for Valgrind flags.
|
||||
#
|
||||
# --smc-check=all-non-file is required to deal with code generation and
|
||||
# patching by the various JITS. Note that this is only necessary on
|
||||
# x86 and x86_64, but not on ARM. This flag is only necessary for
|
||||
# Valgrind versions prior to 3.11.
|
||||
#
|
||||
# --vex-iropt-register-updates=allregs-at-mem-access is required so that
|
||||
# Valgrind generates correct register values whenever there is a
|
||||
# segfault that is caught and handled. In particular OdinMonkey
|
||||
# requires this. More recent Valgrinds (3.11 and later) provide
|
||||
# --px-default=allregs-at-mem-access and
|
||||
# --px-file-backed=unwindregs-at-mem-access
|
||||
# which provide a significantly cheaper alternative, by restricting the
|
||||
# precise exception behaviour to JIT generated code only.
|
||||
#
|
||||
# --trace-children=yes is required to get Valgrind to follow into
|
||||
# content and other child processes. The resulting output can be
|
||||
# difficult to make sense of, and --child-silent-after-fork=yes
|
||||
# helps by causing Valgrind to be silent for the child in the period
|
||||
# after fork() but before its subsequent exec().
|
||||
#
|
||||
# --trace-children-skip lists processes that we are not interested
|
||||
# in tracing into.
|
||||
#
|
||||
# --leak-check=full requests full stack traces for all leaked blocks
|
||||
# detected at process exit.
|
||||
#
|
||||
# --show-possibly-lost=no requests blocks for which only an interior
|
||||
# pointer was found to be considered not leaked.
|
||||
#
|
||||
#
|
||||
# TODO: pass in the user supplied args for V (--valgrind-args=) and
|
||||
# use this to detect if a different tool has been selected. If so
|
||||
# adjust tool-specific args appropriately.
|
||||
#
|
||||
# TODO: pass in the path to the Valgrind to be used (--valgrind=), and
|
||||
# check what flags it accepts. Possible args that might be beneficial:
|
||||
#
|
||||
# --num-transtab-sectors=24 [reduces re-jitting overheads in long runs]
|
||||
# --px-default=allregs-at-mem-access
|
||||
# --px-file-backed=unwindregs-at-mem-access
|
||||
# [these reduce PX overheads as described above]
|
||||
#
|
||||
def get_default_valgrind_args():
|
||||
return (['--fair-sched=yes',
|
||||
'--smc-check=all-non-file',
|
||||
'--vex-iropt-register-updates=allregs-at-mem-access',
|
||||
'--trace-children=yes',
|
||||
'--child-silent-after-fork=yes',
|
||||
'--leak-check=full',
|
||||
'--show-possibly-lost=no',
|
||||
('--trace-children-skip='
|
||||
+ '/usr/bin/hg,/bin/rm,*/bin/certutil,*/bin/pk12util,'
|
||||
+ '*/bin/ssltunnel,*/bin/uname,*/bin/which,*/bin/ps,'
|
||||
+ '*/bin/grep,*/bin/java'),
|
||||
]
|
||||
+ get_default_valgrind_tool_specific_args())
|
||||
|
||||
def get_default_valgrind_tool_specific_args():
|
||||
return [
|
||||
'--partial-loads-ok=yes'
|
||||
]
|
||||
|
|
|
@ -39,6 +39,9 @@ def buffer_handler_wrapper(handler, buffer_limit):
|
|||
buffer_limit = int(buffer_limit)
|
||||
return handlers.BufferHandler(handler, buffer_limit)
|
||||
|
||||
def valgrind_handler_wrapper(handler):
|
||||
return handlers.ValgrindHandler(handler)
|
||||
|
||||
def default_formatter_options(log_type, overrides):
|
||||
formatter_option_defaults = {
|
||||
"raw": {
|
||||
|
@ -151,17 +154,24 @@ def setup_handlers(logger, formatters, formatter_options):
|
|||
for fmt, streams in formatters.iteritems():
|
||||
formatter_cls = log_formatters[fmt][0]
|
||||
formatter = formatter_cls()
|
||||
handler_wrapper, handler_option = None, ""
|
||||
handler_wrappers_and_options = []
|
||||
|
||||
for option, value in formatter_options[fmt].iteritems():
|
||||
if option == "buffer":
|
||||
handler_wrapper, handler_option = fmt_options[option][0], value
|
||||
wrapper, wrapper_args = None, ()
|
||||
if option == "valgrind":
|
||||
wrapper = valgrind_handler_wrapper
|
||||
elif option == "buffer":
|
||||
wrapper, wrapper_args = fmt_options[option][0], (value,)
|
||||
else:
|
||||
formatter = fmt_options[option][0](formatter, value)
|
||||
|
||||
if wrapper is not None:
|
||||
handler_wrappers_and_options.append((wrapper, wrapper_args))
|
||||
|
||||
for value in streams:
|
||||
handler = handlers.StreamHandler(stream=value, formatter=formatter)
|
||||
if handler_wrapper:
|
||||
handler = handler_wrapper(handler, handler_option)
|
||||
for wrapper, wrapper_args in handler_wrappers_and_options:
|
||||
handler = wrapper(handler, *wrapper_args)
|
||||
logger.add_handler(handler)
|
||||
|
||||
|
||||
|
@ -211,7 +221,9 @@ def setup_logging(logger, args, defaults=None, formatter_defaults=None):
|
|||
parts = name.split('_')
|
||||
if len(parts) > 3:
|
||||
continue
|
||||
# Our args will be ['log', <formatter>] or ['log', <formatter>, <option>].
|
||||
# Our args will be ['log', <formatter>]
|
||||
# or ['log', <formatter>, <option>]
|
||||
# or ['valgrind']
|
||||
if parts[0] == 'log' and values is not None:
|
||||
if len(parts) == 1 or parts[1] not in log_formatters:
|
||||
continue
|
||||
|
@ -245,6 +257,10 @@ def setup_logging(logger, args, defaults=None, formatter_defaults=None):
|
|||
if name not in formatter_options:
|
||||
formatter_options[name] = default_formatter_options(name, formatter_defaults)
|
||||
|
||||
# If the user specified --valgrind, add it as an option for all formatters
|
||||
if args.get('valgrind', None) is not None:
|
||||
for name in formatters:
|
||||
formatter_options[name]['valgrind'] = True
|
||||
setup_handlers(logger, formatters, formatter_options)
|
||||
set_default_logger(logger)
|
||||
|
||||
|
|
Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше
Загрузка…
Ссылка в новой задаче