author | Carsten "Tomcat" Book <cbook@mozilla.com> |
Tue, 17 Mar 2015 11:36:52 +0100 | |
changeset 234042 | 008b3f65a7e0471abb6887812ee56dd87cc528df |
parent 234041 | a194e80230901e7e3644c3c16b3356c5c3099316 (current diff) |
parent 233894 | 6aaf86f2c5d85e358e01a4af8310e775c684f79d (diff) |
child 234043 | 15e49729e473ded4ea41c16a6b7dab0143674703 |
child 234075 | 655bf846ef73fe592f6c87b61dc075348615b676 |
child 234093 | c912d22bfe3deae3d34ef8d7710654c418df3362 |
push id | 57019 |
push user | cbook@mozilla.com |
push date | Tue, 17 Mar 2015 10:50:22 +0000 |
treeherder | mozilla-inbound@15e49729e473 [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
reviewers | merge |
milestone | 39.0a1 |
first release with | nightly linux32
008b3f65a7e0
/
39.0a1
/
20150317073344
/
files
nightly linux64
008b3f65a7e0
/
39.0a1
/
20150317073344
/
files
nightly mac
008b3f65a7e0
/
39.0a1
/
20150317073344
/
files
nightly win32
008b3f65a7e0
/
39.0a1
/
20150317073344
/
files
nightly win64
008b3f65a7e0
/
39.0a1
/
20150317073344
/
files
|
last release without | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
releases | nightly linux32
39.0a1
/
20150317073344
/
pushlog to previous
nightly linux64
39.0a1
/
20150317073344
/
pushlog to previous
nightly mac
39.0a1
/
20150317073344
/
pushlog to previous
nightly win32
39.0a1
/
20150317073344
/
pushlog to previous
nightly win64
39.0a1
/
20150317073344
/
pushlog to previous
|
--- a/accessible/base/nsAccessibilityService.cpp +++ b/accessible/base/nsAccessibilityService.cpp @@ -131,89 +131,89 @@ MustBeAccessible(nsIContent* aContent, D return aDocument->IsDependentID(id); return false; } //////////////////////////////////////////////////////////////////////////////// // Accessible constructors -Accessible* +static Accessible* New_HTMLLink(nsIContent* aContent, Accessible* aContext) { // Only some roles truly enjoy life as HTMLLinkAccessibles, for details // see closed bug 494807. nsRoleMapEntry* roleMapEntry = aria::GetRoleMap(aContent); if (roleMapEntry && roleMapEntry->role != roles::NOTHING && roleMapEntry->role != roles::LINK) { return new HyperTextAccessibleWrap(aContent, aContext->Document()); } return new HTMLLinkAccessible(aContent, aContext->Document()); } -Accessible* New_HyperText(nsIContent* aContent, Accessible* aContext) +static Accessible* New_HyperText(nsIContent* aContent, Accessible* aContext) { return new HyperTextAccessibleWrap(aContent, aContext->Document()); } -Accessible* New_HTMLFigcaption(nsIContent* aContent, Accessible* aContext) +static Accessible* New_HTMLFigcaption(nsIContent* aContent, Accessible* aContext) { return new HTMLFigcaptionAccessible(aContent, aContext->Document()); } -Accessible* New_HTMLFigure(nsIContent* aContent, Accessible* aContext) +static Accessible* New_HTMLFigure(nsIContent* aContent, Accessible* aContext) { return new HTMLFigureAccessible(aContent, aContext->Document()); } -Accessible* New_HTMLLegend(nsIContent* aContent, Accessible* aContext) +static Accessible* New_HTMLLegend(nsIContent* aContent, Accessible* aContext) { return new HTMLLegendAccessible(aContent, aContext->Document()); } -Accessible* New_HTMLOption(nsIContent* aContent, Accessible* aContext) +static Accessible* New_HTMLOption(nsIContent* aContent, Accessible* aContext) { return new HTMLSelectOptionAccessible(aContent, aContext->Document()); } -Accessible* New_HTMLOptgroup(nsIContent* aContent, Accessible* aContext) +static Accessible* New_HTMLOptgroup(nsIContent* aContent, Accessible* aContext) { return new HTMLSelectOptGroupAccessible(aContent, aContext->Document()); } -Accessible* New_HTMLList(nsIContent* aContent, Accessible* aContext) +static Accessible* New_HTMLList(nsIContent* aContent, Accessible* aContext) { return new HTMLListAccessible(aContent, aContext->Document()); } -Accessible* +static Accessible* New_HTMLListitem(nsIContent* aContent, Accessible* aContext) { // If list item is a child of accessible list then create an accessible for // it unconditionally by tag name. nsBlockFrame creates the list item // accessible for other elements styled as list items. if (aContext->IsList() && aContext->GetContent() == aContent->GetParent()) return new HTMLLIAccessible(aContent, aContext->Document()); return nullptr; } -Accessible* +static Accessible* New_HTMLDefinition(nsIContent* aContent, Accessible* aContext) { if (aContext->IsList()) return new HyperTextAccessibleWrap(aContent, aContext->Document()); return nullptr; } -Accessible* New_HTMLLabel(nsIContent* aContent, Accessible* aContext) +static Accessible* New_HTMLLabel(nsIContent* aContent, Accessible* aContext) { return new HTMLLabelAccessible(aContent, aContext->Document()); } -Accessible* New_HTMLOutput(nsIContent* aContent, Accessible* aContext) +static Accessible* New_HTMLOutput(nsIContent* aContent, Accessible* aContext) { return new HTMLOutputAccessible(aContent, aContext->Document()); } -Accessible* New_HTMLProgress(nsIContent* aContent, Accessible* aContext) +static Accessible* New_HTMLProgress(nsIContent* aContent, Accessible* aContext) { return new HTMLProgressMeterAccessible(aContent, aContext->Document()); } -Accessible* +static Accessible* New_HTMLTableHeaderCell(nsIContent* aContent, Accessible* aContext) { if (aContext->IsTableRow() && aContext->GetContent() == aContent->GetParent()) return new HTMLTableHeaderCellAccessibleWrap(aContent, aContext->Document()); return nullptr; } -Accessible* +static Accessible* New_HTMLTableHeaderCellIfScope(nsIContent* aContent, Accessible* aContext) { if (aContext->IsTableRow() && aContext->GetContent() == aContent->GetParent() && aContent->HasAttr(kNameSpaceID_None, nsGkAtoms::scope)) return new HTMLTableHeaderCellAccessibleWrap(aContent, aContext->Document()); return nullptr; }
--- a/accessible/ipc/ProxyAccessible.h +++ b/accessible/ipc/ProxyAccessible.h @@ -37,16 +37,19 @@ public: MOZ_ASSERT(!mWrapper); } void AddChildAt(uint32_t aIdx, ProxyAccessible* aChild) { mChildren.InsertElementAt(aIdx, aChild); } uint32_t ChildrenCount() const { return mChildren.Length(); } ProxyAccessible* ChildAt(uint32_t aIdx) const { return mChildren[aIdx]; } + + // XXX evaluate if this is fast enough. + size_t IndexInParent() const { return mParent->mChildren.IndexOf(this); } bool MustPruneChildren() const; void Shutdown(); void SetChildDoc(DocAccessibleParent*); /** * Remove The given child.
--- a/accessible/jsat/Utils.jsm +++ b/accessible/jsat/Utils.jsm @@ -296,46 +296,46 @@ this.Utils = { // jshint ignore:line getVirtualCursor: function getVirtualCursor(aDocument) { let doc = (aDocument instanceof Ci.nsIAccessible) ? aDocument : this.AccRetrieval.getAccessibleFor(aDocument); return doc.QueryInterface(Ci.nsIAccessibleDocument).virtualCursor; }, getContentResolution: function _getContentResolution(aAccessible) { - let resX = { value: 1 }, resY = { value: 1 }; + let res = { value: 1 }; aAccessible.document.window.QueryInterface( Ci.nsIInterfaceRequestor).getInterface( - Ci.nsIDOMWindowUtils).getResolution(resX, resY); - return [resX.value, resY.value]; + Ci.nsIDOMWindowUtils).getResolution(res); + return res.value; }, getBounds: function getBounds(aAccessible, aPreserveContentScale) { let objX = {}, objY = {}, objW = {}, objH = {}; aAccessible.getBounds(objX, objY, objW, objH); - let [scaleX, scaleY] = aPreserveContentScale ? [1, 1] : + let scale = aPreserveContentScale ? 1 : this.getContentResolution(aAccessible); return new Rect(objX.value, objY.value, objW.value, objH.value).scale( - scaleX, scaleY); + scale, scale); }, getTextBounds: function getTextBounds(aAccessible, aStart, aEnd, aPreserveContentScale) { let accText = aAccessible.QueryInterface(Ci.nsIAccessibleText); let objX = {}, objY = {}, objW = {}, objH = {}; accText.getRangeExtents(aStart, aEnd, objX, objY, objW, objH, Ci.nsIAccessibleCoordinateType.COORDTYPE_SCREEN_RELATIVE); - let [scaleX, scaleY] = aPreserveContentScale ? [1, 1] : + let scale = aPreserveContentScale ? 1 : this.getContentResolution(aAccessible); return new Rect(objX.value, objY.value, objW.value, objH.value).scale( - scaleX, scaleY); + scale, scale); }, /** * Get current display DPI. */ get dpi() { delete this.dpi; this.dpi = this.winUtils.displayDPI;
--- a/accessible/tests/mochitest/common.js +++ b/accessible/tests/mochitest/common.js @@ -365,17 +365,18 @@ function testAccessibleTree(aAccOrElmOrI accTree = { role: nsIAccessibleRole[roleName], children: accTree[key] }; } // Test accessible properties. for (var prop in accTree) { - var msg = "Wrong value of property '" + prop + "' for " + prettyName(acc) + "."; + var msg = "Wrong value of property '" + prop + "' for " + + prettyName(acc) + "."; switch (prop) { case "actions": { testActionNames(acc, accTree.actions); break; } case "attributes": @@ -446,20 +447,39 @@ function testAccessibleTree(aAccOrElmOrI } } // Test children. if ("children" in accTree && accTree["children"] instanceof Array) { var children = acc.children; var childCount = children.length; - is(childCount, accTree.children.length, - "Different amount of expected children of " + prettyName(acc) + "."); - if (accTree.children.length == childCount) { + if (accTree.children.length != childCount) { + for (var i = 0; i < Math.max(accTree.children.length, childCount); i++) { + var accChild; + try { + accChild = children.queryElementAt(i, nsIAccessible); + if (!accTree.children[i]) { + ok(false, prettyName(acc) + " has an extra child at index " + i + + " : " + prettyName(accChild)); + } + if (accChild.role !== accTree.children[i].role) { + ok(false, prettyName(accTree) + " and " + prettyName(acc) + + " have different children at index " + i + " : " + + prettyName(accTree.children[i]) + ", " + prettyName(accChild)); + } + info("Matching " + prettyName(accTree) + " and " + prettyName(acc) + + " child at index " + i + " : " + prettyName(accChild)); + } catch (e) { + ok(false, prettyName(accTree) + " has an extra child at index " + i + + " : " + prettyName(accTree.children[i])); + } + } + } else { if (aFlags & kSkipTreeFullCheck) { for (var i = 0; i < childCount; i++) { var child = children.queryElementAt(i, nsIAccessible); testAccessibleTree(child, accTree.children[i], aFlags); } return; } @@ -532,17 +552,18 @@ function isAccessibleInCache(aNodeOrId) * @param aNodeOrId [in] the DOM node identifier for the defunct accessible */ function testDefunctAccessible(aAcc, aNodeOrId) { if (aNodeOrId) ok(!isAccessible(aNodeOrId), "Accessible for " + aNodeOrId + " wasn't properly shut down!"); - var msg = " doesn't fail for shut down accessible " + prettyName(aNodeOrId) + "!"; + var msg = " doesn't fail for shut down accessible " + + prettyName(aNodeOrId) + "!"; // firstChild var success = false; try { aAcc.firstChild; } catch (e) { success = (e.result == Components.results.NS_ERROR_FAILURE) } @@ -715,16 +736,20 @@ function prettyName(aIdentifier) msg += "]"; return msg; } if (aIdentifier instanceof nsIDOMNode) return "[ " + getNodePrettyName(aIdentifier) + " ]"; + if (aIdentifier && typeof aIdentifier === "object" ) { + return JSON.stringify(aIdentifier); + } + return " '" + aIdentifier + "' "; } /** * Shorten a long string if it exceeds MAX_TRIM_LENGTH. * @param aString the string to shorten. * @returns the shortened string. */
--- a/accessible/windows/ia2/ia2Accessible.cpp +++ b/accessible/windows/ia2/ia2Accessible.cpp @@ -11,24 +11,27 @@ #include "AccessibleRole.h" #include "AccessibleStates.h" #include "Compatibility.h" #include "ia2AccessibleRelation.h" #include "IUnknownImpl.h" #include "nsCoreUtils.h" #include "nsIAccessibleTypes.h" +#include "mozilla/a11y/PDocAccessible.h" #include "Relation.h" #include "nsIPersistentProperties2.h" #include "nsISimpleEnumerator.h" using namespace mozilla; using namespace mozilla::a11y; +template<typename String> static void EscapeAttributeChars(String& aStr); + //////////////////////////////////////////////////////////////////////////////// // ia2Accessible //////////////////////////////////////////////////////////////////////////////// STDMETHODIMP ia2Accessible::QueryInterface(REFIID iid, void** ppv) { if (!ppv) @@ -60,16 +63,25 @@ ia2Accessible::get_nRelations(long* aNRe if (!aNRelations) return E_INVALIDARG; *aNRelations = 0; AccessibleWrap* acc = static_cast<AccessibleWrap*>(this); if (acc->IsDefunct()) return CO_E_OBJNOTCONNECTED; + if (acc->IsProxy()) { + // XXX evaluate performance of collecting all relation targets. + nsTArray<RelationType> types; + nsTArray<nsTArray<ProxyAccessible*>> targetSets; + acc->Proxy()->Relations(&types, &targetSets); + *aNRelations = types.Length(); + return S_OK; + } + for (uint32_t idx = 0; idx < ArrayLength(sRelationTypePairs); idx++) { if (sRelationTypePairs[idx].second == IA2_RELATION_NULL) continue; Relation rel = acc->RelationByType(sRelationTypePairs[idx].first); if (rel.Next()) (*aNRelations)++; } @@ -79,24 +91,52 @@ ia2Accessible::get_nRelations(long* aNRe } STDMETHODIMP ia2Accessible::get_relation(long aRelationIndex, IAccessibleRelation** aRelation) { A11Y_TRYBLOCK_BEGIN - if (!aRelation) + if (!aRelation || aRelationIndex < 0) return E_INVALIDARG; *aRelation = nullptr; AccessibleWrap* acc = static_cast<AccessibleWrap*>(this); if (acc->IsDefunct()) return CO_E_OBJNOTCONNECTED; + if (acc->IsProxy()) { + nsTArray<RelationType> types; + nsTArray<nsTArray<ProxyAccessible*>> targetSets; + acc->Proxy()->Relations(&types, &targetSets); + + size_t targetSetCount = targetSets.Length(); + for (size_t i = 0; i < targetSetCount; i++) { + uint32_t relTypeIdx = static_cast<uint32_t>(types[i]); + MOZ_ASSERT(sRelationTypePairs[relTypeIdx].first == types[i]); + if (sRelationTypePairs[relTypeIdx].second == IA2_RELATION_NULL) + continue; + + if (static_cast<size_t>(aRelationIndex) == i) { + nsTArray<nsRefPtr<Accessible>> targets; + size_t targetCount = targetSets[i].Length(); + for (size_t j = 0; j < targetCount; j++) + targets.AppendElement(WrapperFor(targetSets[i][j])); + + nsRefPtr<ia2AccessibleRelation> rel = + new ia2AccessibleRelation(types[i], Move(targets)); + rel.forget(aRelation); + return S_OK; + } + } + + return E_INVALIDARG; + } + long relIdx = 0; for (uint32_t idx = 0; idx < ArrayLength(sRelationTypePairs); idx++) { if (sRelationTypePairs[idx].second == IA2_RELATION_NULL) continue; RelationType relationType = sRelationTypePairs[idx].first; Relation rel = acc->RelationByType(relationType); nsRefPtr<ia2AccessibleRelation> ia2Relation = @@ -118,24 +158,52 @@ ia2Accessible::get_relation(long aRelati STDMETHODIMP ia2Accessible::get_relations(long aMaxRelations, IAccessibleRelation** aRelation, long *aNRelations) { A11Y_TRYBLOCK_BEGIN - if (!aRelation || !aNRelations) + if (!aRelation || !aNRelations || aMaxRelations <= 0) return E_INVALIDARG; *aNRelations = 0; AccessibleWrap* acc = static_cast<AccessibleWrap*>(this); if (acc->IsDefunct()) return CO_E_OBJNOTCONNECTED; + if (acc->IsProxy()) { + nsTArray<RelationType> types; + nsTArray<nsTArray<ProxyAccessible*>> targetSets; + acc->Proxy()->Relations(&types, &targetSets); + + size_t count = std::min(targetSets.Length(), + static_cast<size_t>(aMaxRelations)); + size_t i = 0; + while (i < count) { + uint32_t relTypeIdx = static_cast<uint32_t>(types[i]); + if (sRelationTypePairs[relTypeIdx].second == IA2_RELATION_NULL) + continue; + + size_t targetCount = targetSets[i].Length(); + nsTArray<nsRefPtr<Accessible>> targets(targetCount); + for (size_t j = 0; j < targetCount; j++) + targets.AppendElement(WrapperFor(targetSets[i][j])); + + nsRefPtr<ia2AccessibleRelation> rel = + new ia2AccessibleRelation(types[i], Move(targets)); + rel.forget(aRelation + i); + i++; + } + + *aNRelations = i; + return S_OK; + } + for (uint32_t idx = 0; idx < ArrayLength(sRelationTypePairs) && *aNRelations < aMaxRelations; idx++) { if (sRelationTypePairs[idx].second == IA2_RELATION_NULL) continue; RelationType relationType = sRelationTypePairs[idx].first; Relation rel = acc->RelationByType(relationType); nsRefPtr<ia2AccessibleRelation> ia2Rel = @@ -164,31 +232,41 @@ ia2Accessible::role(long* aRole) return CO_E_OBJNOTCONNECTED; #define ROLE(_geckoRole, stringRole, atkRole, macRole, \ msaaRole, ia2Role, nameRule) \ case roles::_geckoRole: \ *aRole = ia2Role; \ break; - a11y::role geckoRole = acc->Role(); + a11y::role geckoRole; + if (acc->IsProxy()) + geckoRole = acc->Proxy()->Role(); + else + geckoRole = acc->Role(); switch (geckoRole) { #include "RoleMap.h" default: MOZ_CRASH("Unknown role."); }; #undef ROLE // Special case, if there is a ROLE_ROW inside of a ROLE_TREE_TABLE, then call // the IA2 role a ROLE_OUTLINEITEM. - if (geckoRole == roles::ROW) { - Accessible* xpParent = acc->Parent(); - if (xpParent && xpParent->Role() == roles::TREE_TABLE) + if (acc->IsProxy()) { + if (geckoRole == roles::ROW && acc->Proxy()->Parent() && + acc->Proxy()->Parent()->Role() == roles::TREE_TABLE) *aRole = ROLE_SYSTEM_OUTLINEITEM; + } else { + if (geckoRole == roles::ROW) { + Accessible* xpParent = acc->Parent(); + if (xpParent && xpParent->Role() == roles::TREE_TABLE) + *aRole = ROLE_SYSTEM_OUTLINEITEM; + } } return S_OK; A11Y_TRYBLOCK_END } STDMETHODIMP @@ -269,17 +347,26 @@ ia2Accessible::get_states(AccessibleStat if (!aStates) return E_INVALIDARG; *aStates = 0; // XXX: bug 344674 should come with better approach that we have here. AccessibleWrap* acc = static_cast<AccessibleWrap*>(this); - uint64_t state = acc->State(); + if (acc->IsDefunct()) { + *aStates = IA2_STATE_DEFUNCT; + return CO_E_OBJNOTCONNECTED; + } + + uint64_t state; + if (acc->IsProxy()) + state = acc->Proxy()->State(); + else + state = acc->State(); if (state & states::INVALID) *aStates |= IA2_STATE_INVALID_ENTRY; if (state & states::REQUIRED) *aStates |= IA2_STATE_REQUIRED; // The following IA2 states are not supported by Gecko // IA2_STATE_ARMED @@ -441,17 +528,21 @@ ia2Accessible::get_indexInParent(long* a if (!aIndexInParent) return E_INVALIDARG; *aIndexInParent = -1; AccessibleWrap* acc = static_cast<AccessibleWrap*>(this); if (acc->IsDefunct()) return CO_E_OBJNOTCONNECTED; - *aIndexInParent = acc->IndexInParent(); + if (acc->IsProxy()) + *aIndexInParent = acc->Proxy()->IndexInParent(); + else + *aIndexInParent = acc->IndexInParent(); + if (*aIndexInParent == -1) return S_FALSE; return S_OK; A11Y_TRYBLOCK_END } @@ -516,18 +607,39 @@ ia2Accessible::get_attributes(BSTR* aAtt *aAttributes = nullptr; AccessibleWrap* acc = static_cast<AccessibleWrap*>(this); if (acc->IsDefunct()) return CO_E_OBJNOTCONNECTED; // The format is name:value;name:value; with \ for escaping these // characters ":;=,\". - nsCOMPtr<nsIPersistentProperties> attributes = acc->Attributes(); - return ConvertToIA2Attributes(attributes, aAttributes); + if (!acc->IsProxy()) { + nsCOMPtr<nsIPersistentProperties> attributes = acc->Attributes(); + return ConvertToIA2Attributes(attributes, aAttributes); + } + + nsTArray<Attribute> attrs; + acc->Proxy()->Attributes(&attrs); + nsString attrStr; + size_t attrCount = attrs.Length(); + for (size_t i = 0; i < attrCount; i++) { + EscapeAttributeChars(attrs[i].Name()); + EscapeAttributeChars(attrs[i].Value()); + AppendUTF8toUTF16(attrs[i].Name(), attrStr); + attrStr.Append(':'); + attrStr.Append(attrs[i].Value()); + attrStr.Append(';'); + } + + if (attrStr.IsEmpty()) + return S_FALSE; + + *aAttributes = ::SysAllocStringLen(attrStr.get(), attrStr.Length()); + return *aAttributes ? S_OK : E_OUTOFMEMORY; A11Y_TRYBLOCK_END } //////////////////////////////////////////////////////////////////////////////// // IAccessible2_2 STDMETHODIMP @@ -562,17 +674,17 @@ ia2Accessible::get_accessibleWithCaret(I STDMETHODIMP ia2Accessible::get_relationTargetsOfType(BSTR aType, long aMaxTargets, IUnknown*** aTargets, long* aNTargets) { A11Y_TRYBLOCK_BEGIN - if (!aTargets || !aNTargets) + if (!aTargets || !aNTargets || aMaxTargets < 0) return E_INVALIDARG; *aNTargets = 0; Maybe<RelationType> relationType; for (uint32_t idx = 0; idx < ArrayLength(sRelationTypePairs); idx++) { if (wcscmp(aType, sRelationTypePairs[idx].second) == 0) { relationType.emplace(sRelationTypePairs[idx].first); break; @@ -580,23 +692,33 @@ ia2Accessible::get_relationTargetsOfType } if (!relationType) return E_INVALIDARG; AccessibleWrap* acc = static_cast<AccessibleWrap*>(this); if (acc->IsDefunct()) return CO_E_OBJNOTCONNECTED; - Relation rel = acc->RelationByType(*relationType); + nsTArray<Accessible*> targets; + if (acc->IsProxy()) { + nsTArray<ProxyAccessible*> targetProxies = + acc->Proxy()->RelationByType(*relationType); - nsTArray<Accessible*> targets; - Accessible* target = nullptr; - while ((target = rel.Next()) && - static_cast<long>(targets.Length()) <= aMaxTargets) - targets.AppendElement(target); + size_t targetCount = aMaxTargets; + if (targetProxies.Length() < targetCount) + targetCount = targetProxies.Length(); + for (size_t i = 0; i < targetCount; i++) + targets.AppendElement(WrapperFor(targetProxies[i])); + } else { + Relation rel = acc->RelationByType(*relationType); + Accessible* target = nullptr; + while ((target = rel.Next()) && + static_cast<long>(targets.Length()) <= aMaxTargets) + targets.AppendElement(target); + } *aNTargets = targets.Length(); *aTargets = static_cast<IUnknown**>( ::CoTaskMemAlloc(sizeof(IUnknown*) * *aNTargets)); if (!*aTargets) return E_OUTOFMEMORY; for (int32_t i = 0; i < *aNTargets; i++) { @@ -608,16 +730,28 @@ ia2Accessible::get_relationTargetsOfType return S_OK; A11Y_TRYBLOCK_END } //////////////////////////////////////////////////////////////////////////////// // Helpers +template<typename String> +static inline void +EscapeAttributeChars(String& aStr) +{ + int32_t offset = 0; + static const char kCharsToEscape[] = ":;=,\\"; + while ((offset = aStr.FindCharInSet(kCharsToEscape, offset)) != kNotFound) { + aStr.Insert('\\', offset); + offset += 2; + } +} + HRESULT ia2Accessible::ConvertToIA2Attributes(nsIPersistentProperties* aAttributes, BSTR* aIA2Attributes) { *aIA2Attributes = nullptr; // The format is name:value;name:value; with \ for escaping these // characters ":;=,\". @@ -627,46 +761,36 @@ ia2Accessible::ConvertToIA2Attributes(ns nsCOMPtr<nsISimpleEnumerator> propEnum; aAttributes->Enumerate(getter_AddRefs(propEnum)); if (!propEnum) return E_FAIL; nsAutoString strAttrs; - const char kCharsToEscape[] = ":;=,\\"; - bool hasMore = false; while (NS_SUCCEEDED(propEnum->HasMoreElements(&hasMore)) && hasMore) { nsCOMPtr<nsISupports> propSupports; propEnum->GetNext(getter_AddRefs(propSupports)); nsCOMPtr<nsIPropertyElement> propElem(do_QueryInterface(propSupports)); if (!propElem) return E_FAIL; nsAutoCString name; if (NS_FAILED(propElem->GetKey(name))) return E_FAIL; - int32_t offset = 0; - while ((offset = name.FindCharInSet(kCharsToEscape, offset)) != kNotFound) { - name.Insert('\\', offset); - offset += 2; - } + EscapeAttributeChars(name); nsAutoString value; if (NS_FAILED(propElem->GetValue(value))) return E_FAIL; - offset = 0; - while ((offset = value.FindCharInSet(kCharsToEscape, offset)) != kNotFound) { - value.Insert('\\', offset); - offset += 2; - } + EscapeAttributeChars(value); AppendUTF8toUTF16(name, strAttrs); strAttrs.Append(':'); strAttrs.Append(value); strAttrs.Append(';'); } if (strAttrs.IsEmpty())
--- a/accessible/windows/ia2/ia2AccessibleRelation.h +++ b/accessible/windows/ia2/ia2AccessibleRelation.h @@ -19,16 +19,20 @@ namespace mozilla { namespace a11y { class ia2AccessibleRelation MOZ_FINAL : public IAccessibleRelation { public: ia2AccessibleRelation(RelationType aType, Relation* aRel); + ia2AccessibleRelation(RelationType aType, + nsTArray<nsRefPtr<Accessible>>&& aTargets) : + mType(aType), mTargets(Move(aTargets)) {} + // IUnknown DECL_IUNKNOWN // IAccessibleRelation virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_relationType( /* [retval][out] */ BSTR *relationType); virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_localizedRelationType(
--- a/accessible/windows/ia2/moz.build +++ b/accessible/windows/ia2/moz.build @@ -47,8 +47,10 @@ LOCAL_INCLUDES += [ FINAL_LIBRARY = 'xul' # The midl generated code include Windows headers which defines min and max # macros which conflicts with std::min/max. Suppress the macros: if CONFIG['OS_ARCH'] == 'WINNT': DEFINES['NOMINMAX'] = True FAIL_ON_WARNINGS = True + +include('/ipc/chromium/chromium-config.mozbuild')
--- a/b2g/app/b2g.js +++ b/b2g/app/b2g.js @@ -689,16 +689,19 @@ pref("javascript.options.mem.gc_max_empt pref("ui.showHideScrollbars", 1); pref("ui.useOverlayScrollbars", 1); pref("ui.scrollbarFadeBeginDelay", 450); pref("ui.scrollbarFadeDuration", 200); // Scrollbar position follows the document `dir` attribute pref("layout.scrollbar.side", 1); +// CSS Scroll Snapping +pref("layout.css.scroll-snap.enabled", true); + // Enable the ProcessPriorityManager, and give processes with no visible // documents a 1s grace period before they're eligible to be marked as // background. Background processes that are perceivable due to playing // media are given a longer grace period to accomodate changing tracks, etc. pref("dom.ipc.processPriorityManager.enabled", true); pref("dom.ipc.processPriorityManager.backgroundGracePeriodMS", 1000); pref("dom.ipc.processPriorityManager.backgroundPerceivableGracePeriodMS", 5000); pref("dom.ipc.processPriorityManager.temporaryPriorityLockMS", 5000);
--- a/browser/app/profile/firefox.js +++ b/browser/app/profile/firefox.js @@ -1226,17 +1226,17 @@ pref("security.sandbox.windows.log.stack // This pref is discussed in bug 1083344, the naming is inspired from its Windows // counterpart, but on Mac it's an integer which means: // 0 -> "no sandbox" // 1 -> "an imperfect sandbox designed to allow firefox to run reasonably well" // 2 -> "an ideal sandbox which may break many things" // This setting is read when the content process is started. On Mac the content // process is killed when all windows are closed, so a change will take effect // when the 1st window is opened. -pref("security.sandbox.content.level", 0); +pref("security.sandbox.content.level", 1); #endif // This pref governs whether we attempt to work around problems caused by // plugins using OS calls to manipulate the cursor while running out-of- // process. These workarounds all involve intercepting (hooking) certain // OS calls in the plugin process, then arranging to make certain OS calls // in the browser process. Eventually plugins will be required to use the // NPAPI to manipulate the cursor, and these workarounds will be removed.
--- a/browser/base/content/test/general/browser.ini +++ b/browser/base/content/test/general/browser.ini @@ -177,17 +177,16 @@ skip-if = buildapp == 'mulet' || e10s # skip-if = buildapp == 'mulet' || e10s # Bug 1056146 - zoom tests use FullZoomHelper and break in e10s [browser_bug455852.js] skip-if = e10s [browser_bug460146.js] skip-if = e10s # Bug 866413 - PageInfo doesn't work in e10s [browser_bug462289.js] skip-if = toolkit == "cocoa" || e10s # Bug 1102017 - middle-button mousedown on selected tab2 does not activate tab - Didn't expect [object XULElement], but got it [browser_bug462673.js] -skip-if = e10s # Bug 1093404 - test expects sync window opening from content and is disappointed in that expectation [browser_bug477014.js] [browser_bug479408.js] skip-if = buildapp == 'mulet' [browser_bug481560.js] [browser_bug484315.js] skip-if = e10s [browser_bug491431.js] skip-if = buildapp == 'mulet'
--- a/browser/base/content/test/general/browser_bug462673.js +++ b/browser/base/content/test/general/browser_bug462673.js @@ -1,61 +1,36 @@ -var runs = [ - function (win, tabbrowser, tab) { - is(tabbrowser.browsers.length, 2, "test_bug462673.html has opened a second tab"); - is(tabbrowser.selectedTab, tab.nextSibling, "dependent tab is selected"); - tabbrowser.removeTab(tab); - // Closing a tab will also close its parent chrome window, but async - executeSoon(function() { - ok(win.closed, "Window is closed"); - testComplete(win); - }); - }, - function (win, tabbrowser, tab) { - var newTab = tabbrowser.addTab(); - var newBrowser = newTab.linkedBrowser; - tabbrowser.removeTab(tab); - ok(!win.closed, "Window stays open"); - if (!win.closed) { - is(tabbrowser.tabContainer.childElementCount, 1, "Window has one tab"); - is(tabbrowser.browsers.length, 1, "Window has one browser"); - is(tabbrowser.selectedTab, newTab, "Remaining tab is selected"); - is(tabbrowser.selectedBrowser, newBrowser, "Browser for remaining tab is selected"); - is(tabbrowser.mTabBox.selectedPanel, newBrowser.parentNode.parentNode.parentNode.parentNode, "Panel for remaining tab is selected"); - } - testComplete(win); - } -]; +add_task(function* () { + var win = openDialog(getBrowserURL(), "_blank", "chrome,all,dialog=no"); + yield SimpleTest.promiseFocus(win); + + let tab = win.gBrowser.tabContainer.firstChild; + yield promiseTabLoadEvent(tab, getRootDirectory(gTestPath) + "test_bug462673.html"); + + is(win.gBrowser.browsers.length, 2, "test_bug462673.html has opened a second tab"); + is(win.gBrowser.selectedTab, tab.nextSibling, "dependent tab is selected"); + win.gBrowser.removeTab(tab); + + // Closing a tab will also close its parent chrome window, but async + yield promiseWindowWillBeClosed(win); +}); -function test() { - waitForExplicitFinish(); - runOneTest(); -} +add_task(function* () { + var win = openDialog(getBrowserURL(), "_blank", "chrome,all,dialog=no"); + yield SimpleTest.promiseFocus(win); -function testComplete(win) { - win.close(); - if (runs.length) - runOneTest(); - else - finish(); -} - -function runOneTest() { - var win = openDialog(getBrowserURL(), "_blank", "chrome,all,dialog=no"); + let tab = win.gBrowser.tabContainer.firstChild; + yield promiseTabLoadEvent(tab, getRootDirectory(gTestPath) + "test_bug462673.html"); - win.addEventListener("load", function () { - win.removeEventListener("load", arguments.callee, false); - - var tab = win.gBrowser.tabContainer.firstChild; - var browser = tab.linkedBrowser; - - browser.addEventListener("load", function () { - browser.removeEventListener("load", arguments.callee, true); + var newTab = win.gBrowser.addTab(); + var newBrowser = newTab.linkedBrowser; + win.gBrowser.removeTab(tab); + ok(!win.closed, "Window stays open"); + if (!win.closed) { + is(win.gBrowser.tabContainer.childElementCount, 1, "Window has one tab"); + is(win.gBrowser.browsers.length, 1, "Window has one browser"); + is(win.gBrowser.selectedTab, newTab, "Remaining tab is selected"); + is(win.gBrowser.selectedBrowser, newBrowser, "Browser for remaining tab is selected"); + is(win.gBrowser.mTabBox.selectedPanel, newBrowser.parentNode.parentNode.parentNode.parentNode, "Panel for remaining tab is selected"); + } - executeSoon(function () { - runs.shift()(win, win.gBrowser, tab); - }); - }, true); - - var rootDir = getRootDirectory(gTestPath); - browser.contentWindow.location = rootDir + "test_bug462673.html" - }, false); -} + yield promiseWindowClosed(win); +});
--- a/browser/devtools/debugger/test/browser_dbg_parser-09.js +++ b/browser/devtools/debugger/test/browser_dbg_parser-09.js @@ -29,262 +29,262 @@ function test() { // VariableDeclarator verify("var foo=()=>{}", e => e.type == "ArrowFunctionExpression", { name: "foo", chain: null, loc: [[1, 4], [1, 7]] }); - verify("\nvar\nfoo\n=\n(\n)\n=>\n{\n}\n", e => e.type == "ArrowFunctionExpression", { + verify("\nvar\nfoo\n=\n(\n)=>\n{\n}\n", e => e.type == "ArrowFunctionExpression", { name: "foo", chain: null, loc: [[3, 0], [3, 3]] }); // AssignmentExpression verify("foo=()=>{}", e => e.type == "ArrowFunctionExpression", { name: "foo", chain: [], loc: [[1, 0], [1, 3]] }); - verify("\nfoo\n=\n(\n)\n=>\n{\n}\n", e => e.type == "ArrowFunctionExpression", + verify("\nfoo\n=\n(\n)=>\n{\n}\n", e => e.type == "ArrowFunctionExpression", { name: "foo", chain: [], loc: [[2, 0], [2, 3]] }); verify("foo.bar=()=>{}", e => e.type == "ArrowFunctionExpression", { name: "bar", chain: ["foo"], loc: [[1, 0], [1, 7]] }); - verify("\nfoo.bar\n=\n(\n)\n=>\n{\n}\n", e => e.type == "ArrowFunctionExpression", + verify("\nfoo.bar\n=\n(\n)=>\n{\n}\n", e => e.type == "ArrowFunctionExpression", { name: "bar", chain: ["foo"], loc: [[2, 0], [2, 7]] }); verify("this.foo=()=>{}", e => e.type == "ArrowFunctionExpression", { name: "foo", chain: ["this"], loc: [[1, 0], [1, 8]] }); - verify("\nthis.foo\n=\n(\n)\n=>\n{\n}\n", e => e.type == "ArrowFunctionExpression", + verify("\nthis.foo\n=\n(\n)=>\n{\n}\n", e => e.type == "ArrowFunctionExpression", { name: "foo", chain: ["this"], loc: [[2, 0], [2, 8]] }); verify("this.foo.bar=()=>{}", e => e.type == "ArrowFunctionExpression", { name: "bar", chain: ["this", "foo"], loc: [[1, 0], [1, 12]] }); - verify("\nthis.foo.bar\n=\n(\n)\n=>\n{\n}\n", e => e.type == "ArrowFunctionExpression", + verify("\nthis.foo.bar\n=\n(\n)=>\n{\n}\n", e => e.type == "ArrowFunctionExpression", { name: "bar", chain: ["this", "foo"], loc: [[2, 0], [2, 12]] }); verify("foo.this.bar=()=>{}", e => e.type == "ArrowFunctionExpression", { name: "bar", chain: ["foo", "this"], loc: [[1, 0], [1, 12]] }); - verify("\nfoo.this.bar\n=\n(\n)\n=>\n{\n}\n", e => e.type == "ArrowFunctionExpression", + verify("\nfoo.this.bar\n=\n(\n)=>\n{\n}\n", e => e.type == "ArrowFunctionExpression", { name: "bar", chain: ["foo", "this"], loc: [[2, 0], [2, 12]] }); // ObjectExpression verify("({foo:()=>{}})", e => e.type == "ArrowFunctionExpression", { name: "foo", chain: [], loc: [[1, 2], [1, 5]] }); - verify("(\n{\nfoo\n:\n(\n)\n=>\n{\n}\n}\n)", e => e.type == "ArrowFunctionExpression", + verify("(\n{\nfoo\n:\n(\n)=>\n{\n}\n}\n)", e => e.type == "ArrowFunctionExpression", { name: "foo", chain: [], loc: [[3, 0], [3, 3]] }); verify("({foo:{bar:()=>{}}})", e => e.type == "ArrowFunctionExpression", { name: "bar", chain: ["foo"], loc: [[1, 7], [1, 10]] }); - verify("(\n{\nfoo\n:\n{\nbar\n:\n(\n)\n=>\n{\n}\n}\n}\n)", e => e.type == "ArrowFunctionExpression", + verify("(\n{\nfoo\n:\n{\nbar\n:\n(\n)=>\n{\n}\n}\n}\n)", e => e.type == "ArrowFunctionExpression", { name: "bar", chain: ["foo"], loc: [[6, 0], [6, 3]] }); // AssignmentExpression + ObjectExpression verify("foo={bar:()=>{}}", e => e.type == "ArrowFunctionExpression", { name: "bar", chain: ["foo"], loc: [[1, 5], [1, 8]] }); - verify("\nfoo\n=\n{\nbar\n:\n(\n)\n=>\n{\n}\n}\n", e => e.type == "ArrowFunctionExpression", + verify("\nfoo\n=\n{\nbar\n:\n(\n)=>\n{\n}\n}\n", e => e.type == "ArrowFunctionExpression", { name: "bar", chain: ["foo"], loc: [[5, 0], [5, 3]] }); verify("foo={bar:{baz:()=>{}}}", e => e.type == "ArrowFunctionExpression", { name: "baz", chain: ["foo", "bar"], loc: [[1, 10], [1, 13]] }); - verify("\nfoo\n=\n{\nbar\n:\n{\nbaz\n:\n(\n)\n=>\n{\n}\n}\n}\n", e => e.type == "ArrowFunctionExpression", + verify("\nfoo\n=\n{\nbar\n:\n{\nbaz\n:\n(\n)=>\n{\n}\n}\n}\n", e => e.type == "ArrowFunctionExpression", { name: "baz", chain: ["foo", "bar"], loc: [[8, 0], [8, 3]] }); verify("nested.foo={bar:()=>{}}", e => e.type == "ArrowFunctionExpression", { name: "bar", chain: ["nested", "foo"], loc: [[1, 12], [1, 15]] }); - verify("\nnested.foo\n=\n{\nbar\n:\n(\n)\n=>\n{\n}\n}\n", e => e.type == "ArrowFunctionExpression", + verify("\nnested.foo\n=\n{\nbar\n:\n(\n)=>\n{\n}\n}\n", e => e.type == "ArrowFunctionExpression", { name: "bar", chain: ["nested", "foo"], loc: [[5, 0], [5, 3]] }); verify("nested.foo={bar:{baz:()=>{}}}", e => e.type == "ArrowFunctionExpression", { name: "baz", chain: ["nested", "foo", "bar"], loc: [[1, 17], [1, 20]] }); - verify("\nnested.foo\n=\n{\nbar\n:\n{\nbaz\n:\n(\n)\n=>\n{\n}\n}\n}\n", e => e.type == "ArrowFunctionExpression", + verify("\nnested.foo\n=\n{\nbar\n:\n{\nbaz\n:\n(\n)=>\n{\n}\n}\n}\n", e => e.type == "ArrowFunctionExpression", { name: "baz", chain: ["nested", "foo", "bar"], loc: [[8, 0], [8, 3]] }); verify("this.foo={bar:()=>{}}", e => e.type == "ArrowFunctionExpression", { name: "bar", chain: ["this", "foo"], loc: [[1, 10], [1, 13]] }); - verify("\nthis.foo\n=\n{\nbar\n:\n(\n)\n=>\n{\n}\n}\n", e => e.type == "ArrowFunctionExpression", + verify("\nthis.foo\n=\n{\nbar\n:\n(\n)=>\n{\n}\n}\n", e => e.type == "ArrowFunctionExpression", { name: "bar", chain: ["this", "foo"], loc: [[5, 0], [5, 3]] }); verify("this.foo={bar:{baz:()=>{}}}", e => e.type == "ArrowFunctionExpression", { name: "baz", chain: ["this", "foo", "bar"], loc: [[1, 15], [1, 18]] }); - verify("\nthis.foo\n=\n{\nbar\n:\n{\nbaz\n:\n(\n)\n=>\n{\n}\n}\n}\n", e => e.type == "ArrowFunctionExpression", + verify("\nthis.foo\n=\n{\nbar\n:\n{\nbaz\n:\n(\n)=>\n{\n}\n}\n}\n", e => e.type == "ArrowFunctionExpression", { name: "baz", chain: ["this", "foo", "bar"], loc: [[8, 0], [8, 3]] }); verify("this.nested.foo={bar:()=>{}}", e => e.type == "ArrowFunctionExpression", { name: "bar", chain: ["this", "nested", "foo"], loc: [[1, 17], [1, 20]] }); - verify("\nthis.nested.foo\n=\n{\nbar\n:\n(\n)\n=>\n{\n}\n}\n", e => e.type == "ArrowFunctionExpression", + verify("\nthis.nested.foo\n=\n{\nbar\n:\n(\n)=>\n{\n}\n}\n", e => e.type == "ArrowFunctionExpression", { name: "bar", chain: ["this", "nested", "foo"], loc: [[5, 0], [5, 3]] }); verify("this.nested.foo={bar:{baz:()=>{}}}", e => e.type == "ArrowFunctionExpression", { name: "baz", chain: ["this", "nested", "foo", "bar"], loc: [[1, 22], [1, 25]] }); - verify("\nthis.nested.foo\n=\n{\nbar\n:\n{\nbaz\n:\n(\n)\n=>\n{\n}\n}\n}\n", e => e.type == "ArrowFunctionExpression", + verify("\nthis.nested.foo\n=\n{\nbar\n:\n{\nbaz\n:\n(\n)=>\n{\n}\n}\n}\n", e => e.type == "ArrowFunctionExpression", { name: "baz", chain: ["this", "nested", "foo", "bar"], loc: [[8, 0], [8, 3]] }); verify("nested.this.foo={bar:()=>{}}", e => e.type == "ArrowFunctionExpression", { name: "bar", chain: ["nested", "this", "foo"], loc: [[1, 17], [1, 20]] }); - verify("\nnested.this.foo\n=\n{\nbar\n:\n(\n)\n=>\n{\n}\n}\n", e => e.type == "ArrowFunctionExpression", + verify("\nnested.this.foo\n=\n{\nbar\n:\n(\n)=>\n{\n}\n}\n", e => e.type == "ArrowFunctionExpression", { name: "bar", chain: ["nested", "this", "foo"], loc: [[5, 0], [5, 3]] }); verify("nested.this.foo={bar:{baz:()=>{}}}", e => e.type == "ArrowFunctionExpression", { name: "baz", chain: ["nested", "this", "foo", "bar"], loc: [[1, 22], [1, 25]] }); - verify("\nnested.this.foo\n=\n{\nbar\n:\n{\nbaz\n:\n(\n)\n=>\n{\n}\n}\n}\n", e => e.type == "ArrowFunctionExpression", + verify("\nnested.this.foo\n=\n{\nbar\n:\n{\nbaz\n:\n(\n)=>\n{\n}\n}\n}\n", e => e.type == "ArrowFunctionExpression", { name: "baz", chain: ["nested", "this", "foo", "bar"], loc: [[8, 0], [8, 3]] }); // VariableDeclarator + AssignmentExpression + ObjectExpression verify("let foo={bar:()=>{}}", e => e.type == "ArrowFunctionExpression", { name: "bar", chain: ["foo"], loc: [[1, 9], [1, 12]] }); - verify("\nlet\nfoo\n=\n{\nbar\n:\n(\n)\n=>\n{\n}\n}\n", e => e.type == "ArrowFunctionExpression", + verify("\nlet\nfoo\n=\n{\nbar\n:\n(\n)=>\n{\n}\n}\n", e => e.type == "ArrowFunctionExpression", { name: "bar", chain: ["foo"], loc: [[6, 0], [6, 3]] }); verify("let foo={bar:{baz:()=>{}}}", e => e.type == "ArrowFunctionExpression", { name: "baz", chain: ["foo", "bar"], loc: [[1, 14], [1, 17]] }); - verify("\nlet\nfoo\n=\n{\nbar\n:\n{\nbaz\n:\n(\n)\n=>\n{\n}\n}\n}\n", e => e.type == "ArrowFunctionExpression", + verify("\nlet\nfoo\n=\n{\nbar\n:\n{\nbaz\n:\n(\n)=>\n{\n}\n}\n}\n", e => e.type == "ArrowFunctionExpression", { name: "baz", chain: ["foo", "bar"], loc: [[9, 0], [9, 3]] }); // New/CallExpression + AssignmentExpression + ObjectExpression verify("foo({bar:()=>{}})", e => e.type == "ArrowFunctionExpression", { name: "bar", chain: [], loc: [[1, 5], [1, 8]] }); - verify("\nfoo\n(\n{\nbar\n:\n(\n)\n=>\n{\n}\n}\n)\n", e => e.type == "ArrowFunctionExpression", + verify("\nfoo\n(\n{\nbar\n:\n(\n)=>\n{\n}\n}\n)\n", e => e.type == "ArrowFunctionExpression", { name: "bar", chain: [], loc: [[5, 0], [5, 3]] }); verify("foo({bar:{baz:()=>{}}})", e => e.type == "ArrowFunctionExpression", { name: "baz", chain: ["bar"], loc: [[1, 10], [1, 13]] }); - verify("\nfoo\n(\n{\nbar\n:\n{\nbaz\n:\n(\n)\n=>\n{\n}\n}\n}\n)\n", e => e.type == "ArrowFunctionExpression", + verify("\nfoo\n(\n{\nbar\n:\n{\nbaz\n:\n(\n)=>\n{\n}\n}\n}\n)\n", e => e.type == "ArrowFunctionExpression", { name: "baz", chain: ["bar"], loc: [[8, 0], [8, 3]] }); verify("nested.foo({bar:()=>{}})", e => e.type == "ArrowFunctionExpression", { name: "bar", chain: [], loc: [[1, 12], [1, 15]] }); - verify("\nnested.foo\n(\n{\nbar\n:\n(\n)\n=>\n{\n}\n}\n)\n", e => e.type == "ArrowFunctionExpression", + verify("\nnested.foo\n(\n{\nbar\n:\n(\n)=>\n{\n}\n}\n)\n", e => e.type == "ArrowFunctionExpression", { name: "bar", chain: [], loc: [[5, 0], [5, 3]] }); verify("nested.foo({bar:{baz:()=>{}}})", e => e.type == "ArrowFunctionExpression", { name: "baz", chain: ["bar"], loc: [[1, 17], [1, 20]] }); - verify("\nnested.foo\n(\n{\nbar\n:\n{\nbaz\n:\n(\n)\n=>\n{\n}\n}\n}\n)\n", e => e.type == "ArrowFunctionExpression", + verify("\nnested.foo\n(\n{\nbar\n:\n{\nbaz\n:\n(\n)=>\n{\n}\n}\n}\n)\n", e => e.type == "ArrowFunctionExpression", { name: "baz", chain: ["bar"], loc: [[8, 0], [8, 3]] }); verify("this.foo({bar:()=>{}})", e => e.type == "ArrowFunctionExpression", { name: "bar", chain: [], loc: [[1, 10], [1, 13]] }); - verify("\nthis.foo\n(\n{\nbar\n:\n(\n)\n=>\n{\n}\n}\n)\n", e => e.type == "ArrowFunctionExpression", + verify("\nthis.foo\n(\n{\nbar\n:\n(\n)=>\n{\n}\n}\n)\n", e => e.type == "ArrowFunctionExpression", { name: "bar", chain: [], loc: [[5, 0], [5, 3]] }); verify("this.foo({bar:{baz:()=>{}}})", e => e.type == "ArrowFunctionExpression", { name: "baz", chain: ["bar"], loc: [[1, 15], [1, 18]] }); - verify("\nthis.foo\n(\n{\nbar\n:\n{\nbaz\n:\n(\n)\n=>\n{\n}\n}\n}\n)\n", e => e.type == "ArrowFunctionExpression", + verify("\nthis.foo\n(\n{\nbar\n:\n{\nbaz\n:\n(\n)=>\n{\n}\n}\n}\n)\n", e => e.type == "ArrowFunctionExpression", { name: "baz", chain: ["bar"], loc: [[8, 0], [8, 3]] }); verify("this.nested.foo({bar:()=>{}})", e => e.type == "ArrowFunctionExpression", { name: "bar", chain: [], loc: [[1, 17], [1, 20]] }); - verify("\nthis.nested.foo\n(\n{\nbar\n:\n(\n)\n=>\n{\n}\n}\n)\n", e => e.type == "ArrowFunctionExpression", + verify("\nthis.nested.foo\n(\n{\nbar\n:\n(\n)=>\n{\n}\n}\n)\n", e => e.type == "ArrowFunctionExpression", { name: "bar", chain: [], loc: [[5, 0], [5, 3]] }); verify("this.nested.foo({bar:{baz:()=>{}}})", e => e.type == "ArrowFunctionExpression", { name: "baz", chain: ["bar"], loc: [[1, 22], [1, 25]] }); - verify("\nthis.nested.foo\n(\n{\nbar\n:\n{\nbaz\n:\n(\n)\n=>\n{\n}\n}\n}\n)\n", e => e.type == "ArrowFunctionExpression", + verify("\nthis.nested.foo\n(\n{\nbar\n:\n{\nbaz\n:\n(\n)=>\n{\n}\n}\n}\n)\n", e => e.type == "ArrowFunctionExpression", { name: "baz", chain: ["bar"], loc: [[8, 0], [8, 3]] }); verify("nested.this.foo({bar:()=>{}})", e => e.type == "ArrowFunctionExpression", { name: "bar", chain: [], loc: [[1, 17], [1, 20]] }); - verify("\nnested.this.foo\n(\n{\nbar\n:\n(\n)\n=>\n{\n}\n}\n)\n", e => e.type == "ArrowFunctionExpression", + verify("\nnested.this.foo\n(\n{\nbar\n:\n(\n)=>\n{\n}\n}\n)\n", e => e.type == "ArrowFunctionExpression", { name: "bar", chain: [], loc: [[5, 0], [5, 3]] }); verify("nested.this.foo({bar:{baz:()=>{}}})", e => e.type == "ArrowFunctionExpression", { name: "baz", chain: ["bar"], loc: [[1, 22], [1, 25]] }); - verify("\nnested.this.foo\n(\n{\nbar\n:\n{\nbaz\n:\n(\n)\n=>\n{\n}\n}\n}\n)\n", e => e.type == "ArrowFunctionExpression", + verify("\nnested.this.foo\n(\n{\nbar\n:\n{\nbaz\n:\n(\n)=>\n{\n}\n}\n}\n)\n", e => e.type == "ArrowFunctionExpression", { name: "baz", chain: ["bar"], loc: [[8, 0], [8, 3]] }); // New/CallExpression + VariableDeclarator + AssignmentExpression + ObjectExpression verify("let target=foo({bar:()=>{}})", e => e.type == "ArrowFunctionExpression", { name: "bar", chain: ["target"], loc: [[1, 16], [1, 19]] }); - verify("\nlet\ntarget=\nfoo\n(\n{\nbar\n:\n(\n)\n=>\n{\n}\n}\n)\n", e => e.type == "ArrowFunctionExpression", + verify("\nlet\ntarget=\nfoo\n(\n{\nbar\n:\n(\n)=>\n{\n}\n}\n)\n", e => e.type == "ArrowFunctionExpression", { name: "bar", chain: ["target"], loc: [[7, 0], [7, 3]] }); verify("let target=foo({bar:{baz:()=>{}}})", e => e.type == "ArrowFunctionExpression", { name: "baz", chain: ["target", "bar"], loc: [[1, 21], [1, 24]] }); - verify("\nlet\ntarget=\nfoo\n(\n{\nbar\n:\n{\nbaz\n:\n(\n)\n=>\n{\n}\n}\n}\n)\n", e => e.type == "ArrowFunctionExpression", + verify("\nlet\ntarget=\nfoo\n(\n{\nbar\n:\n{\nbaz\n:\n(\n)=>\n{\n}\n}\n}\n)\n", e => e.type == "ArrowFunctionExpression", { name: "baz", chain: ["target", "bar"], loc: [[10, 0], [10, 3]] }); verify("let target=nested.foo({bar:()=>{}})", e => e.type == "ArrowFunctionExpression", { name: "bar", chain: ["target"], loc: [[1, 23], [1, 26]] }); - verify("\nlet\ntarget=\nnested.foo\n(\n{\nbar\n:\n(\n)\n=>\n{\n}\n}\n)\n", e => e.type == "ArrowFunctionExpression", + verify("\nlet\ntarget=\nnested.foo\n(\n{\nbar\n:\n(\n)=>\n{\n}\n}\n)\n", e => e.type == "ArrowFunctionExpression", { name: "bar", chain: ["target"], loc: [[7, 0], [7, 3]] }); verify("let target=nested.foo({bar:{baz:()=>{}}})", e => e.type == "ArrowFunctionExpression", { name: "baz", chain: ["target", "bar"], loc: [[1, 28], [1, 31]] }); - verify("\nlet\ntarget=\nnested.foo\n(\n{\nbar\n:\n{\nbaz\n:\n(\n)\n=>\n{\n}\n}\n}\n)\n", e => e.type == "ArrowFunctionExpression", + verify("\nlet\ntarget=\nnested.foo\n(\n{\nbar\n:\n{\nbaz\n:\n(\n)=>\n{\n}\n}\n}\n)\n", e => e.type == "ArrowFunctionExpression", { name: "baz", chain: ["target", "bar"], loc: [[10, 0], [10, 3]] }); verify("let target=this.foo({bar:()=>{}})", e => e.type == "ArrowFunctionExpression", { name: "bar", chain: ["target"], loc: [[1, 21], [1, 24]] }); - verify("\nlet\ntarget=\nthis.foo\n(\n{\nbar\n:\n(\n)\n=>\n{\n}\n}\n)\n", e => e.type == "ArrowFunctionExpression", + verify("\nlet\ntarget=\nthis.foo\n(\n{\nbar\n:\n(\n)=>\n{\n}\n}\n)\n", e => e.type == "ArrowFunctionExpression", { name: "bar", chain: ["target"], loc: [[7, 0], [7, 3]] }); verify("let target=this.foo({bar:{baz:()=>{}}})", e => e.type == "ArrowFunctionExpression", { name: "baz", chain: ["target", "bar"], loc: [[1, 26], [1, 29]] }); - verify("\nlet\ntarget=\nthis.foo\n(\n{\nbar\n:\n{\nbaz\n:\n(\n)\n=>\n{\n}\n}\n}\n)\n", e => e.type == "ArrowFunctionExpression", + verify("\nlet\ntarget=\nthis.foo\n(\n{\nbar\n:\n{\nbaz\n:\n(\n)=>\n{\n}\n}\n}\n)\n", e => e.type == "ArrowFunctionExpression", { name: "baz", chain: ["target", "bar"], loc: [[10, 0], [10, 3]] }); verify("let target=this.nested.foo({bar:()=>{}})", e => e.type == "ArrowFunctionExpression", { name: "bar", chain: ["target"], loc: [[1, 28], [1, 31]] }); - verify("\nlet\ntarget=\nthis.nested.foo\n(\n{\nbar\n:\n(\n)\n=>\n{\n}\n}\n)\n", e => e.type == "ArrowFunctionExpression", + verify("\nlet\ntarget=\nthis.nested.foo\n(\n{\nbar\n:\n(\n)=>\n{\n}\n}\n)\n", e => e.type == "ArrowFunctionExpression", { name: "bar", chain: ["target"], loc: [[7, 0], [7, 3]] }); verify("let target=this.nested.foo({bar:{baz:()=>{}}})", e => e.type == "ArrowFunctionExpression", { name: "baz", chain: ["target", "bar"], loc: [[1, 33], [1, 36]] }); - verify("\nlet\ntarget=\nthis.nested.foo\n(\n{\nbar\n:\n{\nbaz\n:\n(\n)\n=>\n{\n}\n}\n}\n)\n", e => e.type == "ArrowFunctionExpression", + verify("\nlet\ntarget=\nthis.nested.foo\n(\n{\nbar\n:\n{\nbaz\n:\n(\n)=>\n{\n}\n}\n}\n)\n", e => e.type == "ArrowFunctionExpression", { name: "baz", chain: ["target", "bar"], loc: [[10, 0], [10, 3]] }); verify("let target=nested.this.foo({bar:()=>{}})", e => e.type == "ArrowFunctionExpression", { name: "bar", chain: ["target"], loc: [[1, 28], [1, 31]] }); - verify("\nlet\ntarget=\nnested.this.foo\n(\n{\nbar\n:\n(\n)\n=>\n{\n}\n}\n)\n", e => e.type == "ArrowFunctionExpression", + verify("\nlet\ntarget=\nnested.this.foo\n(\n{\nbar\n:\n(\n)=>\n{\n}\n}\n)\n", e => e.type == "ArrowFunctionExpression", { name: "bar", chain: ["target"], loc: [[7, 0], [7, 3]] }); verify("let target=nested.this.foo({bar:{baz:()=>{}}})", e => e.type == "ArrowFunctionExpression", { name: "baz", chain: ["target", "bar"], loc: [[1, 33], [1, 36]] }); - verify("\nlet\ntarget=\nnested.this.foo\n(\n{\nbar\n:\n{\nbaz\n:\n(\n)\n=>\n{\n}\n}\n}\n)\n", e => e.type == "ArrowFunctionExpression", + verify("\nlet\ntarget=\nnested.this.foo\n(\n{\nbar\n:\n{\nbaz\n:\n(\n)=>\n{\n}\n}\n}\n)\n", e => e.type == "ArrowFunctionExpression", { name: "baz", chain: ["target", "bar"], loc: [[10, 0], [10, 3]] }); finish(); }
--- a/browser/installer/package-manifest.in +++ b/browser/installer/package-manifest.in @@ -732,16 +732,28 @@ @RESPATH@/res/table-add-row-before-hover.gif @RESPATH@/res/table-add-row-before.gif @RESPATH@/res/table-remove-column-active.gif @RESPATH@/res/table-remove-column-hover.gif @RESPATH@/res/table-remove-column.gif @RESPATH@/res/table-remove-row-active.gif @RESPATH@/res/table-remove-row-hover.gif @RESPATH@/res/table-remove-row.gif +@RESPATH@/res/text_caret.png +@RESPATH@/res/text_caret@1.5x.png +@RESPATH@/res/text_caret@2.25x.png +@RESPATH@/res/text_caret@2x.png +@RESPATH@/res/text_caret_tilt_left.png +@RESPATH@/res/text_caret_tilt_left@1.5x.png +@RESPATH@/res/text_caret_tilt_left@2.25x.png +@RESPATH@/res/text_caret_tilt_left@2x.png +@RESPATH@/res/text_caret_tilt_right.png +@RESPATH@/res/text_caret_tilt_right@1.5x.png +@RESPATH@/res/text_caret_tilt_right@2.25x.png +@RESPATH@/res/text_caret_tilt_right@2x.png @RESPATH@/res/grabber.gif #ifdef XP_MACOSX @RESPATH@/res/cursors/* #endif @RESPATH@/res/fonts/* @RESPATH@/res/dtd/* @RESPATH@/res/html/* #if defined(XP_MACOSX) || defined(XP_WIN)
--- a/browser/metro/base/content/bindings/browser.xml +++ b/browser/metro/base/content/bindings/browser.xml @@ -334,20 +334,19 @@ </method> <property name="scale"> <getter><![CDATA[ let cwu = this.contentDocument .defaultView .QueryInterface(Components.interfaces.nsIInterfaceRequestor) .getInterface(Components.interfaces.nsIDOMWindowUtils); - let resx = {}, resy = {}; - cwu.getResolution(resx, resy); - // Resolution set by the apzc and is symmetric. - return resx.value; + let res = {}; + cwu.getResolution(res); + return res.value; ]]></getter> </property> <field name="_messageListenerLocal"><![CDATA[ ({ self: this, receiveMessage: function receiveMessage(aMessage) { let self = this.self;
--- a/dom/base/nsContentSink.cpp +++ b/dom/base/nsContentSink.cpp @@ -847,19 +847,25 @@ nsContentSink::PrefetchDNS(const nsAStri hostname = Substring(aHref, 2); } else { nsCOMPtr<nsIURI> uri; NS_NewURI(getter_AddRefs(uri), aHref); if (!uri) { return; } - nsAutoCString host; - uri->GetHost(host); - CopyUTF8toUTF16(host, hostname); + nsresult rv; + bool isLocalResource = false; + rv = NS_URIChainHasFlags(uri, nsIProtocolHandler::URI_IS_LOCAL_RESOURCE, + &isLocalResource); + if (NS_SUCCEEDED(rv) && !isLocalResource) { + nsAutoCString host; + uri->GetHost(host); + CopyUTF8toUTF16(host, hostname); + } } if (!hostname.IsEmpty() && nsHTMLDNSPrefetch::IsAllowed(mDocument)) { nsHTMLDNSPrefetch::PrefetchLow(hostname); } } void
--- a/dom/base/nsContentUtils.cpp +++ b/dom/base/nsContentUtils.cpp @@ -6486,39 +6486,42 @@ bool nsContentUtils::IsPatternMatching(nsAString& aValue, nsAString& aPattern, nsIDocument* aDocument) { NS_ASSERTION(aDocument, "aDocument should be a valid pointer (not null)"); AutoJSAPI jsapi; jsapi.Init(); JSContext* cx = jsapi.cx(); + + // Failure to create or run the regexp results in the invalid pattern + // matching, but we can still report the error to the console. + jsapi.TakeOwnershipOfErrorReporting(); + // We can use the junk scope here, because we're just using it for // regexp evaluation, not actual script execution. JSAutoCompartment ac(cx, xpc::UnprivilegedJunkScope()); // The pattern has to match the entire value. aPattern.Insert(NS_LITERAL_STRING("^(?:"), 0); aPattern.AppendLiteral(")$"); JS::Rooted<JSObject*> re(cx, JS_NewUCRegExpObjectNoStatics(cx, static_cast<char16_t*>(aPattern.BeginWriting()), aPattern.Length(), 0)); if (!re) { - JS_ClearPendingException(cx); return true; } JS::Rooted<JS::Value> rval(cx, JS::NullValue()); size_t idx = 0; if (!JS_ExecuteRegExpNoStatics(cx, re, static_cast<char16_t*>(aValue.BeginWriting()), aValue.Length(), &idx, true, &rval)) { - JS_ClearPendingException(cx); return true; } return !rval.isNull(); } // static nsresult
--- a/dom/base/nsDOMWindowUtils.cpp +++ b/dom/base/nsDOMWindowUtils.cpp @@ -485,75 +485,72 @@ nsDOMWindowUtils::SetDisplayPortBaseForE } nsLayoutUtils::SetDisplayPortBase(content, nsRect(aX, aY, aWidth, aHeight)); return NS_OK; } NS_IMETHODIMP -nsDOMWindowUtils::SetResolution(float aXResolution, float aYResolution) +nsDOMWindowUtils::SetResolution(float aResolution) { if (!nsContentUtils::IsCallerChrome()) { return NS_ERROR_DOM_SECURITY_ERR; } nsIPresShell* presShell = GetPresShell(); if (!presShell) { return NS_ERROR_FAILURE; } nsIScrollableFrame* sf = presShell->GetRootScrollFrameAsScrollable(); if (sf) { - sf->SetResolution(gfxSize(aXResolution, aYResolution)); - presShell->SetResolution(aXResolution, aYResolution); + sf->SetResolution(aResolution); + presShell->SetResolution(aResolution); } return NS_OK; } NS_IMETHODIMP -nsDOMWindowUtils::SetResolutionAndScaleTo(float aXResolution, float aYResolution) +nsDOMWindowUtils::SetResolutionAndScaleTo(float aResolution) { if (!nsContentUtils::IsCallerChrome()) { return NS_ERROR_DOM_SECURITY_ERR; } nsIPresShell* presShell = GetPresShell(); if (!presShell) { return NS_ERROR_FAILURE; } nsIScrollableFrame* sf = presShell->GetRootScrollFrameAsScrollable(); if (sf) { - sf->SetResolutionAndScaleTo(gfxSize(aXResolution, aYResolution)); - presShell->SetResolutionAndScaleTo(aXResolution, aYResolution); + sf->SetResolutionAndScaleTo(aResolution); + presShell->SetResolutionAndScaleTo(aResolution); } return NS_OK; } NS_IMETHODIMP -nsDOMWindowUtils::GetResolution(float* aXResolution, float* aYResolution) +nsDOMWindowUtils::GetResolution(float* aResolution) { MOZ_RELEASE_ASSERT(nsContentUtils::IsCallerChrome()); nsIPresShell* presShell = GetPresShell(); if (!presShell) { return NS_ERROR_FAILURE; } nsIScrollableFrame* sf = presShell->GetRootScrollFrameAsScrollable(); if (sf) { - const gfxSize& res = sf->GetResolution(); - *aXResolution = res.width; - *aYResolution = res.height; + *aResolution = sf->GetResolution(); } else { - *aXResolution = presShell->GetXResolution(); - *aYResolution = presShell->GetYResolution(); + *aResolution = presShell->GetResolution(); } return NS_OK; } NS_IMETHODIMP nsDOMWindowUtils::GetIsResolutionSet(bool* aIsResolutionSet) { MOZ_RELEASE_ASSERT(nsContentUtils::IsCallerChrome());
--- a/dom/base/nsDocument.cpp +++ b/dom/base/nsDocument.cpp @@ -12839,16 +12839,45 @@ nsIDocument::CreateHTMLElement(nsIAtom* DebugOnly<nsresult> rv = NS_NewHTMLElement(getter_AddRefs(element), nodeInfo.forget(), mozilla::dom::NOT_FROM_PARSER); MOZ_ASSERT(NS_SUCCEEDED(rv), "NS_NewHTMLElement should never fail"); return element.forget(); } +nsresult +nsIDocument::GetId(nsAString& aId) +{ + if (mId.IsEmpty()) { + nsresult rv; + nsCOMPtr<nsIUUIDGenerator> uuidgen = do_GetService("@mozilla.org/uuid-generator;1", &rv); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + nsID id; + rv = uuidgen->GenerateUUIDInPlace(&id); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + // Build a string in {xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx} format + char buffer[NSID_LENGTH]; + id.ToProvidedString(buffer); + NS_ConvertASCIItoUTF16 uuid(buffer); + + // Remove {} and the null terminator + mId.Assign(Substring(uuid, 1, NSID_LENGTH - 3)); + } + + aId = mId; + return NS_OK; +} + bool MarkDocumentTreeToBeInSyncOperation(nsIDocument* aDoc, void* aData) { nsCOMArray<nsIDocument>* documents = static_cast<nsCOMArray<nsIDocument>*>(aData); if (aDoc) { aDoc->SetIsInSyncOperation(true); documents->AppendObject(aDoc);
--- a/dom/base/nsIDocument.h +++ b/dom/base/nsIDocument.h @@ -11,16 +11,18 @@ #include "nsCRT.h" // for NS_DECL_AND_IMPL_ZEROING_OPERATOR_NEW #include "nsCompatibility.h" // for member #include "nsCOMPtr.h" // for member #include "nsGkAtoms.h" // for static class members #include "nsIDocumentObserver.h" // for typedef (nsUpdateType) #include "nsILoadGroup.h" // for member (in nsCOMPtr) #include "nsINode.h" // for base class #include "nsIScriptGlobalObject.h" // for member (in nsCOMPtr) +#include "nsIServiceManager.h" +#include "nsIUUIDGenerator.h" #include "nsPIDOMWindow.h" // for use in inline functions #include "nsPropertyTable.h" // for member #include "nsTHashtable.h" // for member #include "mozilla/net/ReferrerPolicy.h" // for member #include "nsWeakReference.h" #include "mozilla/dom/DocumentBinding.h" #include "mozilla/WeakPtr.h" #include "Units.h" @@ -742,16 +744,18 @@ public: InsertAnonymousContent(mozilla::dom::Element& aElement, mozilla::ErrorResult& aError); void RemoveAnonymousContent(mozilla::dom::AnonymousContent& aContent, mozilla::ErrorResult& aError); nsTArray<nsRefPtr<mozilla::dom::AnonymousContent>>& GetAnonymousContents() { return mAnonymousContents; } + nsresult GetId(nsAString& aId); + protected: virtual Element *GetRootElementInternal() const = 0; private: class SelectorCacheKey { public: explicit SelectorCacheKey(const nsAString& aString) : mKey(aString) @@ -2793,16 +2797,17 @@ protected: uint32_t mSandboxFlags; nsCString mContentLanguage; // The channel that got passed to nsDocument::StartDocumentLoad(), if any. nsCOMPtr<nsIChannel> mChannel; private: nsCString mContentType; + nsString mId; protected: // The document's security info nsCOMPtr<nsISupports> mSecurityInfo; // The channel that failed to load and resulted in an error page. // This only applies to error pages. Might be null. nsCOMPtr<nsIChannel> mFailedChannel;
--- a/dom/bluetooth/bluedroid/BluetoothDaemonHelpers.cpp +++ b/dom/bluetooth/bluedroid/BluetoothDaemonHelpers.cpp @@ -1112,19 +1112,19 @@ PackPDU(const BluetoothAvrcpElementAttri } const NS_ConvertUTF16toUTF8 cstr(aIn.mValue); if (NS_WARN_IF(cstr.Length() == PR_UINT32_MAX)) { return NS_ERROR_ILLEGAL_VALUE; /* integer overflow detected */ } - PRUint32 clen = cstr.Length() + 1; /* include \0 character */ + uint32_t clen = cstr.Length() + 1; /* include \0 character */ - rv = PackPDU(PackConversion<PRUint32, uint8_t>(clen), aPDU); + rv = PackPDU(PackConversion<uint32_t, uint8_t>(clen), aPDU); if (NS_FAILED(rv)) { return rv; } return PackPDU( PackArray<uint8_t>(reinterpret_cast<const uint8_t*>(cstr.get()), clen), aPDU); }
--- a/dom/bluetooth2/bluedroid/BluetoothDaemonHelpers.cpp +++ b/dom/bluetooth2/bluedroid/BluetoothDaemonHelpers.cpp @@ -1065,19 +1065,19 @@ PackPDU(const BluetoothAvrcpElementAttri } const NS_ConvertUTF16toUTF8 cstr(aIn.mValue); if (NS_WARN_IF(cstr.Length() == PR_UINT32_MAX)) { return NS_ERROR_ILLEGAL_VALUE; /* integer overflow detected */ } - PRUint32 clen = cstr.Length() + 1; /* include \0 character */ + uint32_t clen = cstr.Length() + 1; /* include \0 character */ - rv = PackPDU(PackConversion<PRUint32, uint8_t>(clen), aPDU); + rv = PackPDU(PackConversion<uint32_t, uint8_t>(clen), aPDU); if (NS_FAILED(rv)) { return rv; } return PackPDU( PackArray<uint8_t>(reinterpret_cast<const uint8_t*>(cstr.get()), clen), aPDU); }
--- a/dom/cache/ActorChild.cpp +++ b/dom/cache/ActorChild.cpp @@ -45,16 +45,20 @@ ActorChild::GetFeature() const } bool ActorChild::FeatureNotified() const { return mFeature && mFeature->Notified(); } +ActorChild::ActorChild() +{ +} + ActorChild::~ActorChild() { MOZ_ASSERT(!mFeature); } } // namespace cache } // namespace dom } // namespace mozilla
--- a/dom/cache/ActorChild.h +++ b/dom/cache/ActorChild.h @@ -29,16 +29,17 @@ public: Feature* GetFeature() const; bool FeatureNotified() const; protected: + ActorChild(); ~ActorChild(); private: nsRefPtr<Feature> mFeature; }; } // namespace cache } // namespace dom
--- a/dom/cache/test/mochitest/driver.js +++ b/dom/cache/test/mochitest/driver.js @@ -76,17 +76,18 @@ function runTests(testFile, order) { }; document.body.appendChild(iframe); }); } SimpleTest.waitForExplicitFinish(); if (typeof order == "undefined") { - order = "both"; // both by default + order = "sequential"; // sequential by default, see bug 1143222. + // TODO: Make this "both". } ok(order == "parallel" || order == "sequential" || order == "both", "order argument should be valid"); if (order == "both") { info("Running tests in both modes; first: sequential"); return runTests(testFile, "sequential")
--- a/dom/cache/test/mochitest/mochitest.ini +++ b/dom/cache/test/mochitest/mochitest.ini @@ -9,11 +9,9 @@ support-files = driver.js serviceworker_driver.js test_cache_match_request.js test_cache_matchAll_request.js [test_cache.html] [test_cache_add.html] [test_cache_match_request.html] -skip-if = true # bug 1143222 [test_cache_matchAll_request.html] -skip-if = true # bug 1143222
--- a/dom/cache/test/mochitest/test_cache_matchAll_request.js +++ b/dom/cache/test/mochitest/test_cache_matchAll_request.js @@ -1,19 +1,22 @@ -var request1 = new Request("//mochi.test:8888/?1&" + context); +var request1 = new Request("//mochi.test:8888/?1&" + context + "#fragment"); var request2 = new Request("//mochi.test:8888/?2&" + context); var request3 = new Request("//mochi.test:8888/?3&" + context); +var requestWithAltQS = new Request("//mochi.test:8888/?queryString"); +var unknownRequest = new Request("//mochi.test:8888/non/existing/path?" + context); var response1, response3; var c; var response1Text, response3Text; var name = "matchAll-request" + context; function checkResponse(r, response, responseText) { ok(r !== response, "The objects should not be the same"); - is(r.url, response.url, "The URLs should be the same"); + is(r.url, response.url.replace("#fragment", ""), + "The URLs should be the same"); is(r.status, response.status, "The status codes should be the same"); is(r.type, response.type, "The response types should be the same"); is(r.ok, response.ok, "Both responses should have succeeded"); is(r.statusText, response.statusText, "Both responses should have the same status text"); return r.text().then(function(text) { is(text, responseText, "The response body should be correct"); }); @@ -25,69 +28,115 @@ fetch(new Request(request1)).then(functi }).then(function(text) { response1Text = text; return fetch(new Request(request3)); }).then(function(r) { response3 = r; return response3.text(); }).then(function(text) { response3Text = text; - return caches.open(name); -}).then(function(cache) { - c = cache; - return c.add(request1); -}).then(function() { - return c.add(request3); -}).then(function() { - return c.matchAll(request1); -}).then(function(r) { - is(r.length, 1, "Should only find 1 item"); - return checkResponse(r[0], response1, response1Text); -}).then(function() { - return c.matchAll(request3); -}).then(function(r) { - is(r.length, 1, "Should only find 1 item"); - return checkResponse(r[0], response3, response3Text); -}).then(function() { - return c.matchAll(); -}).then(function(r) { - is(r.length, 2, "Should find 2 items"); - return Promise.all([ - checkResponse(r[0], response1, response1Text), - checkResponse(r[1], response3, response3Text) - ]); -}).then(function() { - return c.matchAll({cacheName: name + "mambojambo"}); -}).catch(function(err) { - is(err.name, "NotFoundError", "Searching in the wrong cache should not succeed"); + return testRequest(request1, request2, request3, unknownRequest, + requestWithAltQS, + request1.url.replace("#fragment", "#other")); }).then(function() { - return caches.delete(name); -}).then(function(success) { - ok(success, "We should be able to delete the cache successfully"); - // Make sure that the cache is still usable after deletion. - return c.matchAll(request1); -}).then(function(r) { - is(r.length, 1, "Should only find 1 item"); - return checkResponse(r[0], response1, response1Text); -}).then(function() { - return c.matchAll(request3); -}).then(function(r) { - is(r.length, 1, "Should only find 1 item"); - return checkResponse(r[0], response3, response3Text); -}).then(function() { - return c.matchAll(); -}).then(function(r) { - is(r.length, 2, "Should find 2 items"); - return Promise.all([ - checkResponse(r[0], response1, response1Text), - checkResponse(r[1], response3, response3Text) - ]); -}).then(function() { - // Now, drop the cache, reopen and verify that we can't find the request any more. - c = null; - return caches.open(name); -}).then(function(cache) { - return cache.matchAll(); -}).catch(function(err) { - is(err.name, "NotFoundError", "Searching in the cache after deletion should not succeed"); + return testRequest(request1.url, request2.url, request3.url, + unknownRequest.url, requestWithAltQS.url, + request1.url.replace("#fragment", "#other")); }).then(function() { testDone(); }); + +// The request arguments can either be a URL string, or a Request object. +function testRequest(request1, request2, request3, unknownRequest, + requestWithAlternateQueryString, + requestWithDifferentFragment) { + return caches.open(name).then(function(cache) { + c = cache; + return c.add(request1); + }).then(function() { + return c.add(request3); + }).then(function() { + return Promise.all( + ["HEAD", "POST", "PUT", "DELETE", "OPTIONS"] + .map(function(method) { + var r = new Request(request1, {method: method}); + return c.add(r) + .then(function() { + ok(false, "Promise should be rejected"); + }, function(err) { + is(err.name, "TypeError", "Adding a request with type '" + method + "' should fail"); + }); + }) + ); + }).then(function() { + return c.matchAll(request1); + }).then(function(r) { + is(r.length, 1, "Should only find 1 item"); + return checkResponse(r[0], response1, response1Text); + }).then(function() { + return c.matchAll(requestWithDifferentFragment); + }).then(function(r) { + is(r.length, 1, "Should only find 1 item"); + return checkResponse(r[0], response1, response1Text); + }).then(function() { + return c.matchAll(requestWithAlternateQueryString, + {ignoreSearch: true, cacheName: name}); + }).then(function(r) { + is(r.length, 2, "Should find 2 items"); + return Promise.all([ + checkResponse(r[0], response1, response1Text), + checkResponse(r[1], response3, response3Text) + ]); + }).then(function() { + return c.matchAll(request3); + }).then(function(r) { + is(r.length, 1, "Should only find 1 item"); + return checkResponse(r[0], response3, response3Text); + }).then(function() { + return c.matchAll(); + }).then(function(r) { + is(r.length, 2, "Should find 2 items"); + return Promise.all([ + checkResponse(r[0], response1, response1Text), + checkResponse(r[1], response3, response3Text) + ]); + }).then(function() { + return c.matchAll({cacheName: name + "mambojambo"}); + }).then(function(r) { + is(r.length, 0, "Searching in the wrong cache should not succeed"); + }).then(function() { + return c.matchAll(unknownRequest); + }).then(function(r) { + is(r.length, 0, "Searching for an unknown request should not succeed"); + return c.matchAll(unknownRequest, {cacheName: name}); + }).then(function(r) { + is(r.length, 0, "Searching for an unknown request should not succeed"); + return caches.delete(name); + }).then(function(success) { + ok(success, "We should be able to delete the cache successfully"); + // Make sure that the cache is still usable after deletion. + return c.matchAll(request1); + }).then(function(r) { + is(r.length, 1, "Should only find 1 item"); + return checkResponse(r[0], response1, response1Text); + }).then(function() { + return c.matchAll(request3); + }).then(function(r) { + is(r.length, 1, "Should only find 1 item"); + return checkResponse(r[0], response3, response3Text); + }).then(function() { + return c.matchAll(); + }).then(function(r) { + is(r.length, 2, "Should find 2 items"); + return Promise.all([ + checkResponse(r[0], response1, response1Text), + checkResponse(r[1], response3, response3Text) + ]); + }).then(function() { + // Now, drop the cache, reopen and verify that we can't find the request any more. + c = null; + return caches.open(name); + }).then(function(cache) { + return cache.matchAll(); + }).then(function(r) { + is(r.length, 0, "Searching in the cache after deletion should not succeed"); + }); +}
--- a/dom/cache/test/mochitest/test_cache_match_request.js +++ b/dom/cache/test/mochitest/test_cache_match_request.js @@ -1,63 +1,110 @@ -var request = new Request("//mochi.test:8888/?" + context); +var request = new Request("//mochi.test:8888/?" + context + "#fragment"); +var requestWithAltQS = new Request("//mochi.test:8888/?queryString"); +var unknownRequest = new Request("//mochi.test:8888/non/existing/path?" + context); var response; var c; var responseText; var name = "match-request" + context; function checkResponse(r) { ok(r !== response, "The objects should not be the same"); - is(r.url, response.url, "The URLs should be the same"); + is(r.url, response.url.replace("#fragment", ""), + "The URLs should be the same"); is(r.status, response.status, "The status codes should be the same"); is(r.type, response.type, "The response types should be the same"); is(r.ok, response.ok, "Both responses should have succeeded"); is(r.statusText, response.statusText, "Both responses should have the same status text"); return r.text().then(function(text) { is(text, responseText, "The response body should be correct"); }); } fetch(new Request(request)).then(function(r) { response = r; return response.text(); }).then(function(text) { responseText = text; - return caches.open(name); -}).then(function(cache) { - c = cache; - return c.add(request); -}).then(function() { - return c.match(request); -}).then(function(r) { - return checkResponse(r); -}).then(function() { - return caches.match(request); -}).then(function(r) { - return checkResponse(r); -}).then(function() { - return caches.match(request, {cacheName: name}); -}).then(function(r) { - return checkResponse(r); + return testRequest(request, unknownRequest, requestWithAltQS, + request.url.replace("#fragment", "#other")); }).then(function() { - return caches.match(request, {cacheName: name + "mambojambo"}); -}).catch(function(err) { - is(err.name, "NotFoundError", "Searching in the wrong cache should not succeed"); -}).then(function() { - return caches.delete(name); -}).then(function(success) { - ok(success, "We should be able to delete the cache successfully"); - // Make sure that the cache is still usable after deletion. - return c.match(request); -}).then(function(r) { - return checkResponse(r); -}).then(function() { - // Now, drop the cache, reopen and verify that we can't find the request any more. - c = null; - return caches.open(name); -}).then(function(cache) { - return cache.match(request); -}).catch(function(err) { - is(err.name, "NotFoundError", "Searching in the cache after deletion should not succeed"); + return testRequest(request.url, unknownRequest.url, requestWithAltQS.url, + request.url.replace("#fragment", "#other")); }).then(function() { testDone(); }); + +// The request argument can either be a URL string, or a Request object. +function testRequest(request, unknownRequest, requestWithAlternateQueryString, + requestWithDifferentFragment) { + return caches.open(name).then(function(cache) { + c = cache; + return c.add(request); + }).then(function() { + return Promise.all( + ["HEAD", "POST", "PUT", "DELETE", "OPTIONS"] + .map(function(method) { + var r = new Request(request, {method: method}); + return c.add(r) + .then(function() { + ok(false, "Promise should be rejected"); + }, function(err) { + is(err.name, "TypeError", "Adding a request with type '" + method + "' should fail"); + }); + }) + ); + }).then(function() { + return c.match(request); + }).then(function(r) { + return checkResponse(r); + }).then(function() { + return caches.match(request); + }).then(function(r) { + return checkResponse(r); + }).then(function() { + return caches.match(requestWithDifferentFragment); + }).then(function(r) { + return checkResponse(r); + }).then(function() { + return caches.match(requestWithAlternateQueryString, + {ignoreSearch: true, cacheName: name}); + }).then(function(r) { + return checkResponse(r); + }).then(function() { + return caches.match(request, {cacheName: name}); + }).then(function(r) { + return checkResponse(r); + }).then(function() { + return caches.match(request, {cacheName: name + "mambojambo"}) + .then(function() { + ok(false, "Promise should be rejected"); + }, function(err) { + is(err.name, "NotFoundError", "Searching in the wrong cache should not succeed"); + }); + }).then(function() { + return c.match(unknownRequest); + }).then(function(r) { + is(typeof r, "undefined", "Searching for an unknown request should not succeed"); + return caches.match(unknownRequest); + }).then(function(r) { + is(typeof r, "undefined", "Searching for an unknown request should not succeed"); + return caches.match(unknownRequest, {cacheName: name}); + }).then(function(r) { + is(typeof r, "undefined", "Searching for an unknown request should not succeed"); + return caches.delete(name); + }).then(function(success) { + ok(success, "We should be able to delete the cache successfully"); + // Make sure that the cache is still usable after deletion. + return c.match(request); + }).then(function(r) { + return checkResponse(r); + }).then(function() { + // Now, drop the cache, reopen and verify that we can't find the request any more. + c = null; + return caches.open(name); + }).then(function(cache) { + return cache.match(request); + }).then(function(r) { + is(typeof r, "undefined", "Searching in the cache after deletion should not succeed"); + }); +}
--- a/dom/canvas/WebGL2Context.cpp +++ b/dom/canvas/WebGL2Context.cpp @@ -76,17 +76,16 @@ static const gl::GLFeature kRequiredFeat gl::GLFeature::frag_depth, gl::GLFeature::framebuffer_blit, gl::GLFeature::framebuffer_multisample, gl::GLFeature::get_integer_indexed, gl::GLFeature::get_integer64_indexed, gl::GLFeature::gpu_shader4, gl::GLFeature::instanced_arrays, gl::GLFeature::instanced_non_arrays, - gl::GLFeature::invalidate_framebuffer, gl::GLFeature::map_buffer_range, gl::GLFeature::occlusion_query2, gl::GLFeature::packed_depth_stencil, gl::GLFeature::query_objects, gl::GLFeature::renderbuffer_color_float, gl::GLFeature::renderbuffer_color_half_float, gl::GLFeature::sRGB, gl::GLFeature::sampler_objects,
--- a/dom/canvas/WebGL2ContextFramebuffers.cpp +++ b/dom/canvas/WebGL2ContextFramebuffers.cpp @@ -393,16 +393,23 @@ WebGL2Context::InvalidateFramebuffer(GLe for (size_t i = 0; i < attachments.Length(); i++) { if (!ValidateFramebufferAttachment(fb, attachments[i], "invalidateFramebuffer")) { return; } } + // InvalidateFramebuffer is a hint to the driver. Should be OK to + // skip calls if not supported, for example by OSX 10.9 GL + // drivers. + static bool invalidateFBSupported = gl->IsSupported(gl::GLFeature::invalidate_framebuffer); + if (!invalidateFBSupported) + return; + if (!fb && !isDefaultFB) { dom::Sequence<GLenum> tmpAttachments; TranslateDefaultAttachments(attachments, &tmpAttachments); gl->fInvalidateFramebuffer(target, tmpAttachments.Length(), tmpAttachments.Elements()); } else { gl->fInvalidateFramebuffer(target, attachments.Length(), attachments.Elements()); } } @@ -440,16 +447,23 @@ WebGL2Context::InvalidateSubFramebuffer( for (size_t i = 0; i < attachments.Length(); i++) { if (!ValidateFramebufferAttachment(fb, attachments[i], "invalidateSubFramebuffer")) { return; } } + // InvalidateFramebuffer is a hint to the driver. Should be OK to + // skip calls if not supported, for example by OSX 10.9 GL + // drivers. + static bool invalidateFBSupported = gl->IsSupported(gl::GLFeature::invalidate_framebuffer); + if (!invalidateFBSupported) + return; + if (!fb && !isDefaultFB) { dom::Sequence<GLenum> tmpAttachments; TranslateDefaultAttachments(attachments, &tmpAttachments); gl->fInvalidateSubFramebuffer(target, tmpAttachments.Length(), tmpAttachments.Elements(), x, y, width, height); } else { gl->fInvalidateSubFramebuffer(target, attachments.Length(), attachments.Elements(), x, y, width, height);
--- a/dom/canvas/WebGLContext.cpp +++ b/dom/canvas/WebGLContext.cpp @@ -916,16 +916,18 @@ WebGLContext::SetDimensions(int32_t sign mOptions.antialias = gl->Caps().antialias; MakeContextCurrent(); gl->fViewport(0, 0, mWidth, mHeight); mViewportWidth = mWidth; mViewportHeight = mHeight; + gl->fScissor(0, 0, mWidth, mHeight); + // Make sure that we clear this out, otherwise // we'll end up displaying random memory gl->fBindFramebuffer(LOCAL_GL_FRAMEBUFFER, 0); AssertCachedBindings(); AssertCachedState(); // Clear immediately, because we need to present the cleared initial
--- a/dom/canvas/WebGLFramebuffer.cpp +++ b/dom/canvas/WebGLFramebuffer.cpp @@ -259,19 +259,16 @@ IsValidFBORenderbufferStencilFormat(GLen { return internalFormat == LOCAL_GL_STENCIL_INDEX8; } bool WebGLContext::IsFormatValidForFB(GLenum sizedFormat) const { switch (sizedFormat) { - case LOCAL_GL_ALPHA8: - case LOCAL_GL_LUMINANCE8: - case LOCAL_GL_LUMINANCE8_ALPHA8: case LOCAL_GL_RGB8: case LOCAL_GL_RGBA8: case LOCAL_GL_RGB565: case LOCAL_GL_RGB5_A1: case LOCAL_GL_RGBA4: return true; case LOCAL_GL_SRGB8:
--- a/dom/canvas/test/_webgl-conformance.ini +++ b/dom/canvas/test/_webgl-conformance.ini @@ -1,15 +1,15 @@ # This is a GENERATED FILE. Do not edit it directly. # Regenerated it by using `python generate-wrappers-and-manifest.py`. # Mark skipped tests in mochitest-errata.ini. # Mark failing tests in mochi-single.html. [DEFAULT] -skip-if = e10s || os == 'b2g' || ((os == 'linux') && (buildapp == 'b2g')) || ((os == 'linux') && (buildapp == 'mulet')) # Bug 1136181 disabled on B2G Desktop and Mulet for intermittent failures +skip-if = e10s || os == 'b2g' || ((os == 'linux') && (buildapp == 'b2g')) || ((os == 'linux') && (buildapp == 'mulet')) support-files = webgl-conformance/../webgl-mochitest/driver-info.js webgl-conformance/always-fail.html webgl-conformance/conformance/00_readme.txt webgl-conformance/conformance/00_test_list.txt webgl-conformance/conformance/LICENSE_CHROMIUM webgl-conformance/conformance/attribs/00_test_list.txt webgl-conformance/conformance/attribs/gl-enable-vertex-attrib.html @@ -521,17 +521,16 @@ skip-if = (os == 'b2g') [webgl-conformance/_wrappers/test_conformance__extensions__ext-texture-filter-anisotropic.html] [webgl-conformance/_wrappers/test_conformance__extensions__oes-texture-float.html] fail-if = (os == 'linux') [webgl-conformance/_wrappers/test_conformance__extensions__oes-vertex-array-object.html] [webgl-conformance/_wrappers/test_conformance__extensions__webgl-debug-renderer-info.html] [webgl-conformance/_wrappers/test_conformance__extensions__webgl-debug-shaders.html] [webgl-conformance/_wrappers/test_conformance__extensions__webgl-compressed-texture-etc1.html] [webgl-conformance/_wrappers/test_conformance__extensions__webgl-compressed-texture-s3tc.html] -fail-if = (os == 'mac' && os_version == '10.10') [webgl-conformance/_wrappers/test_conformance__extensions__ext-sRGB.html] [webgl-conformance/_wrappers/test_conformance__extensions__ext-shader-texture-lod.html] [webgl-conformance/_wrappers/test_conformance__glsl__functions__glsl-function.html] [webgl-conformance/_wrappers/test_conformance__glsl__functions__glsl-function-abs.html] [webgl-conformance/_wrappers/test_conformance__glsl__functions__glsl-function-acos.html] [webgl-conformance/_wrappers/test_conformance__glsl__functions__glsl-function-asin.html] [webgl-conformance/_wrappers/test_conformance__glsl__functions__glsl-function-atan.html] [webgl-conformance/_wrappers/test_conformance__glsl__functions__glsl-function-atan-xy.html]
--- a/dom/canvas/test/reftest/reftest.list +++ b/dom/canvas/test/reftest/reftest.list @@ -54,21 +54,21 @@ fuzzy(1,30000) fails-if(gtk2Widget&&brow == webgl-color-test.html?frame=1&__&________&premult&alpha wrapper.html?colors-premult.png == webgl-color-test.html?frame=1&aa&________&premult&alpha wrapper.html?colors-premult.png == webgl-color-test.html?frame=1&__&preserve&premult&alpha wrapper.html?colors-premult.png == webgl-color-test.html?frame=1&aa&preserve&premult&alpha wrapper.html?colors-premult.png == webgl-color-test.html?frame=6&__&________&_______&_____ wrapper.html?colors-no-alpha.png == webgl-color-test.html?frame=6&aa&________&_______&_____ wrapper.html?colors-no-alpha.png == webgl-color-test.html?frame=6&__&preserve&_______&_____ wrapper.html?colors-no-alpha.png -fails-if(winWidget&&layersGPUAccelerated&&d2d) == webgl-color-test.html?frame=6&aa&preserve&_______&_____ wrapper.html?colors-no-alpha.png + == webgl-color-test.html?frame=6&aa&preserve&_______&_____ wrapper.html?colors-no-alpha.png == webgl-color-test.html?frame=6&__&________&premult&_____ wrapper.html?colors-no-alpha.png == webgl-color-test.html?frame=6&aa&________&premult&_____ wrapper.html?colors-no-alpha.png == webgl-color-test.html?frame=6&__&preserve&premult&_____ wrapper.html?colors-no-alpha.png -fails-if(winWidget&&layersGPUAccelerated&&d2d) == webgl-color-test.html?frame=6&aa&preserve&premult&_____ wrapper.html?colors-no-alpha.png + == webgl-color-test.html?frame=6&aa&preserve&premult&_____ wrapper.html?colors-no-alpha.png fuzzy(1,30000) fails-if(gtk2Widget&&browserIsRemote) fails-if(winWidget&&layersGPUAccelerated&&!d2d) == webgl-color-test.html?frame=6&__&________&_______&alpha wrapper.html?colors-non-premult.png fuzzy(1,30000) fails-if(gtk2Widget&&browserIsRemote) fails-if(winWidget&&layersGPUAccelerated&&!d2d) == webgl-color-test.html?frame=6&aa&________&_______&alpha wrapper.html?colors-non-premult.png fuzzy(1,30000) fails-if(gtk2Widget&&browserIsRemote) fails-if(winWidget&&layersGPUAccelerated&&!d2d) == webgl-color-test.html?frame=6&__&preserve&_______&alpha wrapper.html?colors-non-premult.png fuzzy(1,30000) fails-if(gtk2Widget&&browserIsRemote) fails-if(winWidget&&(!layersGPUAccelerated||!d2d)) == webgl-color-test.html?frame=6&aa&preserve&_______&alpha wrapper.html?colors-non-premult.png == webgl-color-test.html?frame=6&__&________&premult&alpha wrapper.html?colors-premult.png == webgl-color-test.html?frame=6&aa&________&premult&alpha wrapper.html?colors-premult.png == webgl-color-test.html?frame=6&__&preserve&premult&alpha wrapper.html?colors-premult.png == webgl-color-test.html?frame=6&aa&preserve&premult&alpha wrapper.html?colors-premult.png @@ -88,21 +88,21 @@ fuzzy(1,30000) fails-if(gtk2Widget&&brow pref(webgl.force-layers-readback,true) == webgl-color-test.html?frame=1&readback&__&________&premult&alpha wrapper.html?colors-premult.png pref(webgl.force-layers-readback,true) == webgl-color-test.html?frame=1&readback&aa&________&premult&alpha wrapper.html?colors-premult.png pref(webgl.force-layers-readback,true) == webgl-color-test.html?frame=1&readback&__&preserve&premult&alpha wrapper.html?colors-premult.png pref(webgl.force-layers-readback,true) == webgl-color-test.html?frame=1&readback&aa&preserve&premult&alpha wrapper.html?colors-premult.png pref(webgl.force-layers-readback,true) == webgl-color-test.html?frame=6&readback&__&________&_______&_____ wrapper.html?colors-no-alpha.png pref(webgl.force-layers-readback,true) == webgl-color-test.html?frame=6&readback&aa&________&_______&_____ wrapper.html?colors-no-alpha.png pref(webgl.force-layers-readback,true) == webgl-color-test.html?frame=6&readback&__&preserve&_______&_____ wrapper.html?colors-no-alpha.png -fails-if(winWidget&&layersGPUAccelerated&&d2d) pref(webgl.force-layers-readback,true) == webgl-color-test.html?frame=6&readback&aa&preserve&_______&_____ wrapper.html?colors-no-alpha.png + pref(webgl.force-layers-readback,true) == webgl-color-test.html?frame=6&readback&aa&preserve&_______&_____ wrapper.html?colors-no-alpha.png pref(webgl.force-layers-readback,true) == webgl-color-test.html?frame=6&readback&__&________&premult&_____ wrapper.html?colors-no-alpha.png pref(webgl.force-layers-readback,true) == webgl-color-test.html?frame=6&readback&aa&________&premult&_____ wrapper.html?colors-no-alpha.png pref(webgl.force-layers-readback,true) == webgl-color-test.html?frame=6&readback&__&preserve&premult&_____ wrapper.html?colors-no-alpha.png -random-if(winWidget&&layersGPUAccelerated&&d2d) pref(webgl.force-layers-readback,true) == webgl-color-test.html?frame=6&readback&aa&preserve&premult&_____ wrapper.html?colors-no-alpha.png + pref(webgl.force-layers-readback,true) == webgl-color-test.html?frame=6&readback&aa&preserve&premult&_____ wrapper.html?colors-no-alpha.png fuzzy(1,30000) fails-if(gtk2Widget&&browserIsRemote) fails-if(winWidget&&layersGPUAccelerated&&!d2d) pref(webgl.force-layers-readback,true) == webgl-color-test.html?frame=6&readback&__&________&_______&alpha wrapper.html?colors-non-premult.png fuzzy(1,30000) fails-if(gtk2Widget&&browserIsRemote) fails-if(winWidget&&layersGPUAccelerated&&!d2d) pref(webgl.force-layers-readback,true) == webgl-color-test.html?frame=6&readback&aa&________&_______&alpha wrapper.html?colors-non-premult.png fuzzy(1,30000) fails-if(gtk2Widget&&browserIsRemote) fails-if(winWidget&&layersGPUAccelerated&&!d2d) pref(webgl.force-layers-readback,true) == webgl-color-test.html?frame=6&readback&__&preserve&_______&alpha wrapper.html?colors-non-premult.png fuzzy(1,30000) fails-if(gtk2Widget&&browserIsRemote) fails-if(winWidget&&(!layersGPUAccelerated||!d2d)) pref(webgl.force-layers-readback,true) == webgl-color-test.html?frame=6&readback&aa&preserve&_______&alpha wrapper.html?colors-non-premult.png pref(webgl.force-layers-readback,true) == webgl-color-test.html?frame=6&readback&__&________&premult&alpha wrapper.html?colors-premult.png pref(webgl.force-layers-readback,true) == webgl-color-test.html?frame=6&readback&aa&________&premult&alpha wrapper.html?colors-premult.png pref(webgl.force-layers-readback,true) == webgl-color-test.html?frame=6&readback&__&preserve&premult&alpha wrapper.html?colors-premult.png pref(webgl.force-layers-readback,true) == webgl-color-test.html?frame=6&readback&aa&preserve&premult&alpha wrapper.html?colors-premult.png
--- a/dom/canvas/test/webgl-conformance/mochitest-errata.ini +++ b/dom/canvas/test/webgl-conformance/mochitest-errata.ini @@ -1,8 +1,12 @@ +# *** WARNING! *** +# Modification to this file only take effect after running +# generate-wrappers-and-manifest.py + # See python/mozbuild/mozbuild/mozinfo.py for incoming data. [DEFAULT] # No e10s yet. # 'B2G Desktop Linux' fails to create WebGL contexts. # Also skip B2G for now, until we get a handle on the longer tail of emulator # bugs. # Bug 1136181 disabled on B2G Desktop and Mulet for intermittent failures @@ -91,19 +95,16 @@ skip-if = (os == 'b2g') # Failures after enabling color_buffer_[half_]float. fail-if = (os == 'linux') ######################################################################## # Mac [_wrappers/test_conformance__canvas__drawingbuffer-static-canvas-test.html] # Intermittent crash on OSX. skip-if = os == 'mac' -[_wrappers/test_conformance__extensions__webgl-compressed-texture-s3tc.html] -# Fails on OS X 10.10 -fail-if = (os == 'mac' && os_version == '10.10') [_wrappers/test_conformance__misc__object-deletion-behaviour.html] # Fails on OS X 10.10 fail-if = (os == 'mac' && os_version == '10.10') [_wrappers/test_conformance__rendering__line-loop-tri-fan.html] # Fails on OS X 10.10 fail-if = (os == 'mac' && os_version == '10.10') [_wrappers/test_conformance__rendering__point-size.html] # Fails on OS X 10.10
--- a/dom/events/MessageEvent.cpp +++ b/dom/events/MessageEvent.cpp @@ -8,16 +8,19 @@ #include "mozilla/dom/MessagePort.h" #include "mozilla/dom/MessagePortBinding.h" #include "mozilla/dom/MessagePortList.h" #include "mozilla/HoldDropJSObjects.h" #include "jsapi.h" #include "nsGlobalWindow.h" // So we can assign an nsGlobalWindow* to mWindowSource +#include "ServiceWorker.h" +#include "ServiceWorkerClient.h" + namespace mozilla { namespace dom { NS_IMPL_CYCLE_COLLECTION_CLASS(MessageEvent) NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(MessageEvent, Event) tmp->mData.setUndefined(); NS_IMPL_CYCLE_COLLECTION_UNLINK(mWindowSource) @@ -98,22 +101,24 @@ MessageEvent::GetLastEventId(nsAString& NS_IMETHODIMP MessageEvent::GetSource(nsIDOMWindow** aSource) { NS_IF_ADDREF(*aSource = mWindowSource); return NS_OK; } void -MessageEvent::GetSource(Nullable<OwningWindowProxyOrMessagePort>& aValue) const +MessageEvent::GetSource(Nullable<OwningWindowProxyOrMessagePortOrClient>& aValue) const { if (mWindowSource) { aValue.SetValue().SetAsWindowProxy() = mWindowSource; } else if (mPortSource) { aValue.SetValue().SetAsMessagePort() = mPortSource; + } else if (mClientSource) { + aValue.SetValue().SetAsClient() = mClientSource; } } /* static */ already_AddRefed<MessageEvent> MessageEvent::Constructor(const GlobalObject& aGlobal, const nsAString& aType, const MessageEventInit& aParam, ErrorResult& aRv) @@ -201,16 +206,22 @@ MessageEvent::SetPorts(MessagePortList* } void MessageEvent::SetSource(mozilla::dom::MessagePort* aPort) { mPortSource = aPort; } +void +MessageEvent::SetSource(mozilla::dom::workers::ServiceWorkerClient* aClient) +{ + mClientSource = aClient; +} + } // namespace dom } // namespace mozilla using namespace mozilla; using namespace mozilla::dom; nsresult NS_NewDOMMessageEvent(nsIDOMEvent** aInstancePtrResult,
--- a/dom/events/MessageEvent.h +++ b/dom/events/MessageEvent.h @@ -13,17 +13,23 @@ namespace mozilla { namespace dom { struct MessageEventInit; class MessagePort; class MessagePortBase; class MessagePortList; -class OwningWindowProxyOrMessagePort; +class OwningWindowProxyOrMessagePortOrClient; + +namespace workers { + +class ServiceWorkerClient; + +} /** * Implements the MessageEvent event, used for cross-document messaging and * server-sent events. * * See http://www.whatwg.org/specs/web-apps/current-work/#messageevent for * further details. */ @@ -43,28 +49,30 @@ public: // Forward to base class NS_FORWARD_TO_EVENT virtual JSObject* WrapObjectInternal(JSContext* aCx) MOZ_OVERRIDE; void GetData(JSContext* aCx, JS::MutableHandle<JS::Value> aData, ErrorResult& aRv); - void GetSource(Nullable<OwningWindowProxyOrMessagePort>& aValue) const; + void GetSource(Nullable<OwningWindowProxyOrMessagePortOrClient>& aValue) const; MessagePortList* GetPorts() { return mPorts; } void SetPorts(MessagePortList* aPorts); // Non WebIDL methods void SetSource(mozilla::dom::MessagePort* aPort); + void SetSource(workers::ServiceWorkerClient* aClient); + void SetSource(nsPIDOMWindow* aWindow) { mWindowSource = aWindow; } static already_AddRefed<MessageEvent> Constructor(const GlobalObject& aGlobal, const nsAString& aType, @@ -81,15 +89,16 @@ protected: ~MessageEvent(); private: JS::Heap<JS::Value> mData; nsString mOrigin; nsString mLastEventId; nsCOMPtr<nsIDOMWindow> mWindowSource; nsRefPtr<MessagePortBase> mPortSource; + nsRefPtr<workers::ServiceWorkerClient> mClientSource; nsRefPtr<MessagePortList> mPorts; }; } // namespace dom } // namespace mozilla #endif // mozilla_dom_MessageEvent_h_
--- a/dom/interfaces/base/nsIDOMWindowUtils.idl +++ b/dom/interfaces/base/nsIDOMWindowUtils.idl @@ -45,17 +45,17 @@ interface nsIFile; interface nsIDOMClientRect; interface nsIURI; interface nsIDOMEventTarget; interface nsIRunnable; interface nsITranslationNodeList; interface nsIJSRAIIHelper; interface nsIContentPermissionRequest; -[scriptable, uuid(b39cb73f-ff99-4744-9780-2c26f830c6f7)] +[scriptable, uuid(dde97573-f4cf-45ce-bbb0-5af4e5f77440)] interface nsIDOMWindowUtils : nsISupports { /** * Image animation mode of the window. When this attribute's value * is changed, the implementation should set all images in the window * to the given value. That is, when set to kDontAnimMode, all images * will stop animating. The attribute's value must be one of the * animationMode values from imgIContainer. @@ -200,38 +200,37 @@ interface nsIDOMWindowUtils : nsISupport * Get/set the resolution at which rescalable web content is drawn. * * Setting a new resolution does *not* trigger reflow. This API is * entirely separate from textZoom and fullZoom; a resolution scale * can be applied together with both textZoom and fullZoom. * * The effect of this API is for gfx code to allocate more or fewer * pixels for rescalable content by a factor of |resolution| in - * either or both dimensions. The scale at which the content is - * displayed does not change; if that is desired, use - * setResolutionAndScaleTo() instead. + * both dimensions. The scale at which the content is displayed does + * not change; if that is desired, use setResolutionAndScaleTo() instead. * * The caller of this method must have chrome privileges. */ - void setResolution(in float aXResolution, in float aYResolution); + void setResolution(in float aResolution); - void getResolution(out float aXResolution, out float aYResolution); + void getResolution(out float aResolution); /** * Similar to setResolution(), but also scales the content by the * amount of the resolution, so that it is displayed at a * correspondingly larger or smaller size, without the need for * the caller to set an additional transform. * * This can be used to implement a non-reflowing scale-zoom, e.g. * for pinch-zoom on mobile platforms. * * The caller of this method must have chrome privileges. */ - void setResolutionAndScaleTo(in float aXResolution, in float aYResolution); + void setResolutionAndScaleTo(in float aResolution); /** * Whether the resolution has been set by the user. * This gives a way to check whether the provided resolution is the default * value or restored from a previous session. * * Can only be accessed with chrome privileges. */
--- a/dom/interfaces/base/nsIServiceWorkerManager.idl +++ b/dom/interfaces/base/nsIServiceWorkerManager.idl @@ -14,28 +14,28 @@ interface nsIURI; interface nsIServiceWorkerUnregisterCallback : nsISupports { // aState is true if the unregistration succeded. // It's false if this ServiceWorkerRegistration doesn't exist. [noscript] void UnregisterSucceeded(in bool aState); [noscript] void UnregisterFailed(); }; -[builtinclass, uuid(464882c8-81c0-4620-b9c4-44c12085b65b)] +[builtinclass, uuid(706c3e6b-c9d2-4857-893d-4b4845fec48f)] interface nsIServiceWorkerManager : nsISupports { /** * Registers a ServiceWorker with script loaded from `aScriptURI` to act as * the ServiceWorker for aScope. Requires a valid entry settings object on * the stack. This means you must call this from content code 'within' * a window. * * Returns a Promise. */ - nsISupports register(in nsIDOMWindow aWindow, in DOMString aScope, in DOMString aScriptURI); + nsISupports register(in nsIDOMWindow aWindow, in nsIURI aScope, in nsIURI aScriptURI); /** * Unregister an existing ServiceWorker registration for `aScope`. * It keeps aCallback alive until the operation is concluded. */ void unregister(in nsIPrincipal aPrincipal, in nsIServiceWorkerUnregisterCallback aCallback, in DOMString aScope);
--- a/dom/ipc/TabChild.cpp +++ b/dom/ipc/TabChild.cpp @@ -385,17 +385,17 @@ TabChildBase::HandlePossibleViewportChan } metrics.SetCumulativeResolution(metrics.GetZoom() / metrics.GetDevPixelsPerCSSPixel() * ParentLayerToLayerScale(1)); // This is the root layer, so the cumulative resolution is the same // as the resolution. metrics.SetPresShellResolution(metrics.GetCumulativeResolution().ToScaleFactor().scale); - utils->SetResolutionAndScaleTo(metrics.GetPresShellResolution(), metrics.GetPresShellResolution()); + utils->SetResolutionAndScaleTo(metrics.GetPresShellResolution()); CSSSize scrollPort = metrics.CalculateCompositedSizeInCssPixels(); utils->SetScrollPositionClampingScrollPortSize(scrollPort.width, scrollPort.height); // The call to GetPageSize forces a resize event to content, so we need to // make sure that we have the right CSS viewport and // scrollPositionClampingScrollPortSize set up before that happens. @@ -912,18 +912,17 @@ TabChild::Observe(nsISupports *aSubject, mContentDocumentIsDisplayed = true; // In some cases before-first-paint gets called before // RecvUpdateDimensions is called and therefore before we have an // mInnerSize value set. In such cases defer initializing the viewport // until we we get an inner size. if (HasValidInnerSize()) { InitializeRootMetrics(); - utils->SetResolutionAndScaleTo(mLastRootMetrics.GetPresShellResolution(), - mLastRootMetrics.GetPresShellResolution()); + utils->SetResolutionAndScaleTo(mLastRootMetrics.GetPresShellResolution()); HandlePossibleViewportChange(mInnerSize); } } } } return NS_OK; } @@ -2339,17 +2338,17 @@ TabChild::CancelTapTracking() float TabChild::GetPresShellResolution() const { nsCOMPtr<nsIDocument> document(GetDocument()); nsIPresShell* shell = document->GetShell(); if (!shell) { return 1.0f; } - return shell->GetXResolution(); + return shell->GetResolution(); } bool TabChild::RecvRealTouchEvent(const WidgetTouchEvent& aEvent, const ScrollableLayerGuid& aGuid, const uint64_t& aInputBlockId) { TABC_LOG("Receiving touch event of type %d\n", aEvent.message);
--- a/dom/ipc/TabParent.cpp +++ b/dom/ipc/TabParent.cpp @@ -231,18 +231,16 @@ private: } mFD = nullptr; } }; namespace mozilla { namespace dom { -TabParent* sEventCapturer; - TabParent *TabParent::mIMETabParent = nullptr; TabParent::LayerToTabParentTable* TabParent::sLayerToTabParentTable = nullptr; NS_IMPL_ISUPPORTS(TabParent, nsITabParent, nsIAuthPromptProvider, nsISecureBrowserUI, nsISupportsWeakReference) @@ -257,17 +255,16 @@ TabParent::TabParent(nsIContentParent* a , mIMESelectionFocus(0) , mWritingMode() , mIMEComposing(false) , mIMECompositionEnding(false) , mIMEEventCountAfterEnding(0) , mIMECompositionStart(0) , mIMESeqno(0) , mIMECompositionRectOffset(0) - , mEventCaptureDepth(0) , mRect(0, 0, 0, 0) , mDimensions(0, 0) , mOrientation(0) , mDPI(0) , mDefaultScale(0) , mShown(false) , mUpdatedDimensions(false) , mManager(aManager) @@ -396,19 +393,16 @@ TabParent::Recv__delete__() } return true; } void TabParent::ActorDestroy(ActorDestroyReason why) { - if (sEventCapturer == this) { - sEventCapturer = nullptr; - } if (mIMETabParent == this) { mIMETabParent = nullptr; } nsRefPtr<nsFrameLoader> frameLoader = GetFrameLoader(); nsCOMPtr<nsIObserverService> os = services::GetObserverService(); nsRefPtr<nsFrameMessageManager> fmm; if (frameLoader) { fmm = frameLoader->GetFrameMessageManager(); @@ -1320,32 +1314,17 @@ bool TabParent::SendRealKeyEvent(WidgetK } bool TabParent::SendRealTouchEvent(WidgetTouchEvent& event) { if (mIsDestroyed) { return false; } if (event.message == NS_TOUCH_START) { - // Adjust the widget coordinates to be relative to our frame. - nsRefPtr<nsFrameLoader> frameLoader = GetFrameLoader(); - if (!frameLoader) { - // No frame anymore? - sEventCapturer = nullptr; - return false; - } - mChildProcessOffsetAtTouchStart = GetChildProcessOffset(); - - MOZ_ASSERT((!sEventCapturer && mEventCaptureDepth == 0) || - (sEventCapturer == this && mEventCaptureDepth > 0)); - // We want to capture all remaining touch events in this series - // for fast-path dispatch. - sEventCapturer = this; - ++mEventCaptureDepth; } // PresShell::HandleEventInternal adds touches on touch end/cancel. This // confuses remote content and the panning and zooming logic into thinking // that the added touches are part of the touchend/cancel, when actually // they're not. if (event.message == NS_TOUCH_END || event.message == NS_TOUCH_CANCEL) { for (int i = event.touches.Length() - 1; i >= 0; i--) { @@ -1368,51 +1347,16 @@ bool TabParent::SendRealTouchEvent(Widge event.touches[i]->mRefPoint += offset; } return (event.message == NS_TOUCH_MOVE) ? PBrowserParent::SendRealTouchMoveEvent(event, guid, blockId) : PBrowserParent::SendRealTouchEvent(event, guid, blockId); } -/*static*/ TabParent* -TabParent::GetEventCapturer() -{ - return sEventCapturer; -} - -bool -TabParent::TryCapture(const WidgetGUIEvent& aEvent) -{ - MOZ_ASSERT(sEventCapturer == this && mEventCaptureDepth > 0); - - if (aEvent.mClass != eTouchEventClass) { - // Only capture of touch events is implemented, for now. - return false; - } - - WidgetTouchEvent event(*aEvent.AsTouchEvent()); - - bool isTouchPointUp = (event.message == NS_TOUCH_END || - event.message == NS_TOUCH_CANCEL); - if (event.message == NS_TOUCH_START || isTouchPointUp) { - // Let the DOM see touch start/end events so that its touch-point - // state stays consistent. - if (isTouchPointUp && 0 == --mEventCaptureDepth) { - // All event series are un-captured, don't try to catch any - // more. - sEventCapturer = nullptr; - } - return false; - } - - SendRealTouchEvent(event); - return true; -} - bool TabParent::RecvSyncMessage(const nsString& aMessage, const ClonedMessageData& aData, InfallibleTArray<CpowEntry>&& aCpows, const IPC::Principal& aPrincipal, InfallibleTArray<nsString>* aJSONRetVal) { // FIXME Permission check for TabParent in Content process @@ -2545,21 +2489,16 @@ TabParent::GetLoadContext() mChromeFlags & nsIWebBrowserChrome::CHROME_PRIVATE_WINDOW, mChromeFlags & nsIWebBrowserChrome::CHROME_REMOTE_WINDOW, IsBrowserElement()); mLoadContext = loadContext; } return loadContext.forget(); } -/* Be careful if you call this method while proceding a real touch event. For - * example sending a touchstart during a real touchend may results into - * a busted mEventCaptureDepth and following touch events may not do what you - * expect. - */ NS_IMETHODIMP TabParent::InjectTouchEvent(const nsAString& aType, uint32_t* aIdentifiers, int32_t* aXs, int32_t* aYs, uint32_t* aRxs, uint32_t* aRys, float* aRotationAngles, @@ -2609,21 +2548,16 @@ TabParent::InjectTouchEvent(const nsAStr // Consider all injected touch events as changedTouches. For more details // about the meaning of changedTouches for each event, see // https://developer.mozilla.org/docs/Web/API/TouchEvent.changedTouches t->mChanged = true; event.touches.AppendElement(t); } - if ((msg == NS_TOUCH_END || msg == NS_TOUCH_CANCEL) && sEventCapturer) { - WidgetGUIEvent* guiEvent = event.AsGUIEvent(); - TryCapture(*guiEvent); - } - SendRealTouchEvent(event); return NS_OK; } NS_IMETHODIMP TabParent::GetUseAsyncPanZoom(bool* useAsyncPanZoom) { *useAsyncPanZoom = gfxPrefs::AsyncPanZoomEnabled();
--- a/dom/ipc/TabParent.h +++ b/dom/ipc/TabParent.h @@ -97,40 +97,16 @@ public: void SetBrowserDOMWindow(nsIBrowserDOMWindow* aBrowserDOMWindow) { mBrowserDOMWindow = aBrowserDOMWindow; } already_AddRefed<nsILoadContext> GetLoadContext(); nsIXULBrowserWindow* GetXULBrowserWindow(); - /** - * Return the TabParent that has decided it wants to capture an - * event series for fast-path dispatch to its subprocess, if one - * has. - * - * DOM event dispatch and widget are free to ignore capture - * requests from TabParents; the end result wrt remote content is - * (must be) always the same, albeit usually slower without - * subprocess capturing. This allows frontends/widget backends to - * "opt in" to faster cross-process dispatch. - */ - static TabParent* GetEventCapturer(); - /** - * If this is the current event capturer, give this a chance to - * capture the event. If it was captured, return true, false - * otherwise. Un-captured events should follow normal DOM - * dispatch; captured events should result in no further - * processing from the caller of TryCapture(). - * - * It's an error to call TryCapture() if this isn't the event - * capturer. - */ - bool TryCapture(const WidgetGUIEvent& aEvent); - void Destroy(); virtual bool RecvMoveFocus(const bool& aForward) MOZ_OVERRIDE; virtual bool RecvEvent(const RemoteDOMEvent& aEvent) MOZ_OVERRIDE; virtual bool RecvReplyKeyEvent(const WidgetKeyboardEvent& aEvent) MOZ_OVERRIDE; virtual bool RecvDispatchAfterKeyboardEvent(const WidgetKeyboardEvent& aEvent) MOZ_OVERRIDE; virtual bool RecvBrowserFrameOpenWindow(PBrowserParent* aOpener, const nsString& aURL, @@ -435,19 +411,16 @@ protected: uint32_t mIMESeqno; uint32_t mIMECompositionRectOffset; InfallibleTArray<LayoutDeviceIntRect> mIMECompositionRects; uint32_t mIMECaretOffset; LayoutDeviceIntRect mIMECaretRect; LayoutDeviceIntRect mIMEEditorRect; - // The number of event series we're currently capturing. - int32_t mEventCaptureDepth; - nsIntRect mRect; ScreenIntSize mDimensions; ScreenOrientation mOrientation; float mDPI; CSSToLayoutDeviceScale mDefaultScale; bool mShown; bool mUpdatedDimensions;
--- a/dom/media/MediaDecoder.cpp +++ b/dom/media/MediaDecoder.cpp @@ -186,38 +186,38 @@ void MediaDecoder::UpdateDormantState(bo if (prevDormant == mIsDormant) { // No update to dormant state return; } if (mIsDormant) { DECODER_LOG("UpdateDormantState() entering DORMANT state"); // enter dormant state - nsCOMPtr<nsIRunnable> event = + RefPtr<nsRunnable> event = NS_NewRunnableMethodWithArg<bool>( mDecoderStateMachine, &MediaDecoderStateMachine::SetDormant, true); - mDecoderStateMachine->GetStateMachineThread()->Dispatch(event, NS_DISPATCH_NORMAL); + mDecoderStateMachine->TaskQueue()->Dispatch(event); if (IsEnded()) { mWasEndedWhenEnteredDormant = true; } mNextState = mPlayState; ChangeState(PLAY_STATE_LOADING); } else { DECODER_LOG("UpdateDormantState() leaving DORMANT state"); // exit dormant state // trigger to state machine. - nsCOMPtr<nsIRunnable> event = + RefPtr<nsRunnable> event = NS_NewRunnableMethodWithArg<bool>( mDecoderStateMachine, &MediaDecoderStateMachine::SetDormant, false); - mDecoderStateMachine->GetStateMachineThread()->Dispatch(event, NS_DISPATCH_NORMAL); + mDecoderStateMachine->TaskQueue()->Dispatch(event); } } void MediaDecoder::DormantTimerExpired(nsITimer* aTimer, void* aClosure) { MOZ_ASSERT(aClosure); MediaDecoder* decoder = static_cast<MediaDecoder*>(aClosure); ReentrantMonitorAutoEnter mon(decoder->GetReentrantMonitor()); @@ -745,31 +745,31 @@ nsresult MediaDecoder::ScheduleStateMach MOZ_ASSERT(NS_IsMainThread()); NS_ASSERTION(mDecoderStateMachine, "Must have state machine to start state machine thread"); NS_ENSURE_STATE(mDecoderStateMachine); if (mShuttingDown) return NS_OK; ReentrantMonitorAutoEnter mon(GetReentrantMonitor()); - return mDecoderStateMachine->ScheduleStateMachine(); + mDecoderStateMachine->ScheduleStateMachine(); + return NS_OK; } nsresult MediaDecoder::Play() { MOZ_ASSERT(NS_IsMainThread()); ReentrantMonitorAutoEnter mon(GetReentrantMonitor()); UpdateDormantState(false /* aDormantTimeout */, true /* aActivity */); NS_ASSERTION(mDecoderStateMachine != nullptr, "Should have state machine."); if (mPausedForPlaybackRateNull) { return NS_OK; } - nsresult res = ScheduleStateMachineThread(); - NS_ENSURE_SUCCESS(res,res); + ScheduleStateMachineThread(); if (IsEnded()) { return Seek(0, SeekTarget::PrevSyncPoint); } else if (mPlayState == PLAY_STATE_LOADING || mPlayState == PLAY_STATE_SEEKING) { mNextState = PLAY_STATE_PLAYING; return NS_OK; } ChangeState(PLAY_STATE_PLAYING); @@ -1312,17 +1312,17 @@ void MediaDecoder::ApplyStateToStateMach GetReentrantMonitor().AssertCurrentThreadIn(); if (mDecoderStateMachine) { switch (aState) { case PLAY_STATE_PLAYING: mDecoderStateMachine->Play(); break; case PLAY_STATE_SEEKING: - mSeekRequest.Begin(ProxyMediaCall(mDecoderStateMachine->GetStateMachineThread(), + mSeekRequest.Begin(ProxyMediaCall(mDecoderStateMachine->TaskQueue(), mDecoderStateMachine.get(), __func__, &MediaDecoderStateMachine::Seek, mRequestedSeekTarget) ->RefableThen(NS_GetCurrentThread(), __func__, this, &MediaDecoder::OnSeekResolved, &MediaDecoder::OnSeekRejected)); mRequestedSeekTarget.Reset(); break; default: // The state machine checks for things like PAUSED in RunStateMachine.
--- a/dom/media/MediaDecoderStateMachine.cpp +++ b/dom/media/MediaDecoderStateMachine.cpp @@ -8,17 +8,17 @@ #include "windows.h" #include "mmsystem.h" #endif #include "mozilla/DebugOnly.h" #include <stdint.h> #include "MediaDecoderStateMachine.h" -#include "MediaDecoderStateMachineScheduler.h" +#include "MediaTimer.h" #include "AudioSink.h" #include "nsTArray.h" #include "MediaDecoder.h" #include "MediaDecoderReader.h" #include "mozilla/MathAlgorithms.h" #include "mozilla/mozalloc.h" #include "VideoUtils.h" #include "mozilla/dom/TimeRanges.h" @@ -196,19 +196,19 @@ static const uint32_t MAX_VIDEO_QUEUE_SI static uint32_t sVideoQueueDefaultSize = MAX_VIDEO_QUEUE_SIZE; static uint32_t sVideoQueueHWAccelSize = MIN_VIDEO_QUEUE_SIZE; MediaDecoderStateMachine::MediaDecoderStateMachine(MediaDecoder* aDecoder, MediaDecoderReader* aReader, bool aRealTime) : mDecoder(aDecoder), - mScheduler(new MediaDecoderStateMachineScheduler( - aDecoder->GetReentrantMonitor(), - &MediaDecoderStateMachine::TimeoutExpired, this, aRealTime)), + mRealTime(aRealTime), + mDispatchedStateMachine(false), + mDelayedScheduler(this), mState(DECODER_STATE_DECODING_NONE), mPlayDuration(0), mStartTime(-1), mEndTime(-1), mDurationSet(false), mFragmentEndTime(-1), mReader(aReader), mCurrentFrameTime(0), @@ -243,16 +243,21 @@ MediaDecoderStateMachine::MediaDecoderSt mDisabledHardwareAcceleration(false), mDecodingFrozenAtStateDecoding(false), mSentLoadedMetadataEvent(false), mSentFirstFrameLoadedEvent(false) { MOZ_COUNT_CTOR(MediaDecoderStateMachine); NS_ASSERTION(NS_IsMainThread(), "Should be on main thread."); + // Set up our task queue. + RefPtr<SharedThreadPool> threadPool( + SharedThreadPool::Get(NS_LITERAL_CSTRING("Media State Machine"), 1)); + mTaskQueue = new MediaTaskQueue(threadPool.forget()); + static bool sPrefCacheInit = false; if (!sPrefCacheInit) { sPrefCacheInit = true; Preferences::AddUintVarCache(&sVideoQueueDefaultSize, "media.video-queue.default-size", MAX_VIDEO_QUEUE_SIZE); Preferences::AddUintVarCache(&sVideoQueueHWAccelSize, "media.video-queue.hw-accel-size", @@ -382,17 +387,17 @@ static void WriteVideoToMediaStream(Medi aStream->MicrosecondsToStreamTimeRoundDown(aStartMicroseconds); aOutput->AppendFrame(image.forget(), duration, aIntrinsicSize); } void MediaDecoderStateMachine::SendStreamData() { MOZ_ASSERT(OnStateMachineThread(), "Should be on state machine thread"); AssertCurrentThreadInMonitor(); - MOZ_ASSERT(!mAudioSink, "Should've been stopped in CallRunStateMachine()"); + MOZ_ASSERT(!mAudioSink, "Should've been stopped in RunStateMachine()"); DecodedStreamData* stream = mDecoder->GetDecodedStream(); bool finished = (!mInfo.HasAudio() || AudioQueue().IsFinished()) && (!mInfo.HasVideo() || VideoQueue().IsFinished()); if (mDecoder->IsSameOriginMedia()) { SourceMediaStream* mediaStream = stream->mStream; @@ -400,26 +405,26 @@ void MediaDecoderStateMachine::SendStrea if (!stream->mStreamInitialized) { if (mInfo.HasAudio()) { TrackID audioTrackId = mInfo.mAudio.mTrackInfo.mOutputId; AudioSegment* audio = new AudioSegment(); mediaStream->AddAudioTrack(audioTrackId, mInfo.mAudio.mRate, 0, audio, SourceMediaStream::ADDTRACK_QUEUED); stream->mStream->DispatchWhenNotEnoughBuffered(audioTrackId, - GetStateMachineThread(), GetWakeDecoderRunnable()); + TaskQueue(), GetWakeDecoderRunnable()); stream->mNextAudioTime = mStartTime + stream->mInitialTime; } if (mInfo.HasVideo()) { TrackID videoTrackId = mInfo.mVideo.mTrackInfo.mOutputId; VideoSegment* video = new VideoSegment(); mediaStream->AddTrack(videoTrackId, 0, video, SourceMediaStream::ADDTRACK_QUEUED); stream->mStream->DispatchWhenNotEnoughBuffered(videoTrackId, - GetStateMachineThread(), GetWakeDecoderRunnable()); + TaskQueue(), GetWakeDecoderRunnable()); // TODO: We can't initialize |mNextVideoTime| until |mStartTime| // is set. This is a good indication that DecodedStreamData is in // deep coupling with the state machine and we should move the class // into MediaDecoderStateMachine. stream->mNextVideoTime = mStartTime + stream->mInitialTime; } mediaStream->FinishAddTracks(); @@ -565,17 +570,17 @@ bool MediaDecoderStateMachine::HaveEnoug if (stream && stream->mStreamInitialized && !stream->mHaveSentFinishAudio) { MOZ_ASSERT(mInfo.HasAudio()); TrackID audioTrackId = mInfo.mAudio.mTrackInfo.mOutputId; if (!stream->mStream->HaveEnoughBuffered(audioTrackId)) { return false; } stream->mStream->DispatchWhenNotEnoughBuffered(audioTrackId, - GetStateMachineThread(), GetWakeDecoderRunnable()); + TaskQueue(), GetWakeDecoderRunnable()); } return true; } bool MediaDecoderStateMachine::HaveEnoughDecodedVideo() { AssertCurrentThreadInMonitor(); @@ -588,17 +593,17 @@ bool MediaDecoderStateMachine::HaveEnoug if (stream && stream->mStreamInitialized && !stream->mHaveSentFinishVideo) { MOZ_ASSERT(mInfo.HasVideo()); TrackID videoTrackId = mInfo.mVideo.mTrackInfo.mOutputId; if (!stream->mStream->HaveEnoughBuffered(videoTrackId)) { return false; } stream->mStream->DispatchWhenNotEnoughBuffered(videoTrackId, - GetStateMachineThread(), GetWakeDecoderRunnable()); + TaskQueue(), GetWakeDecoderRunnable()); } return true; } bool MediaDecoderStateMachine::NeedToDecodeVideo() { @@ -850,17 +855,17 @@ MediaDecoderStateMachine::OnNotDecoded(M // If the decoder is waiting for data, we tell it to call us back when the // data arrives. if (aReason == MediaDecoderReader::WAITING_FOR_DATA) { MOZ_ASSERT(mReader->IsWaitForDataSupported(), "Readers that send WAITING_FOR_DATA need to implement WaitForData"); WaitRequestRef(aType).Begin(ProxyMediaCall(DecodeTaskQueue(), mReader.get(), __func__, &MediaDecoderReader::WaitForData, aType) - ->RefableThen(mScheduler.get(), __func__, this, + ->RefableThen(TaskQueue(), __func__, this, &MediaDecoderStateMachine::OnWaitForDataResolved, &MediaDecoderStateMachine::OnWaitForDataRejected)); return; } if (aReason == MediaDecoderReader::CANCELED) { DispatchDecodeTasksIfNeeded(); return; @@ -1079,17 +1084,17 @@ MediaDecoderStateMachine::CheckIfSeekCom SAMPLE_LOG("CheckIfSeekComplete() audioSeekComplete=%d videoSeekComplete=%d", audioSeekComplete, videoSeekComplete); if (audioSeekComplete && videoSeekComplete) { mDecodeToSeekTarget = false; RefPtr<nsIRunnable> task( NS_NewRunnableMethod(this, &MediaDecoderStateMachine::SeekCompleted)); - nsresult rv = GetStateMachineThread()->Dispatch(task, NS_DISPATCH_NORMAL); + nsresult rv = TaskQueue()->Dispatch(task); if (NS_FAILED(rv)) { DecodeError(); } } } bool MediaDecoderStateMachine::IsAudioDecoding() @@ -1141,20 +1146,17 @@ nsresult MediaDecoderStateMachine::Init( return NS_ERROR_FAILURE; } MediaDecoderReader* cloneReader = nullptr; if (aCloneDonor) { cloneReader = aCloneDonor->mReader; } - nsresult rv = mScheduler->Init(); - NS_ENSURE_SUCCESS(rv, rv); - - rv = mReader->Init(cloneReader); + nsresult rv = mReader->Init(cloneReader); NS_ENSURE_SUCCESS(rv, rv); return NS_OK; } void MediaDecoderStateMachine::StopPlayback() { MOZ_ASSERT(OnStateMachineThread()); @@ -1338,17 +1340,17 @@ int64_t MediaDecoderStateMachine::GetCur OnStateMachineThread() || OnDecodeThread(), "Should be on main, decode, or state machine thread."); return mCurrentFrameTime; } bool MediaDecoderStateMachine::IsRealTime() const { - return mScheduler->IsRealTime(); + return mRealTime; } int64_t MediaDecoderStateMachine::GetDuration() { AssertCurrentThreadInMonitor(); if (mEndTime == -1 || mStartTime == -1) return -1; @@ -1487,18 +1489,28 @@ void MediaDecoderStateMachine::SetDorman SeekTarget::Accurate, MediaDecoderEventVisibility::Suppressed); // XXXbholley - Nobody is listening to this promise. Do we need to pass it // back to MediaDecoder when we come out of dormant? nsRefPtr<MediaDecoder::SeekPromise> unused = mQueuedSeek.mPromise.Ensure(__func__); } mPendingSeek.RejectIfExists(__func__); mCurrentSeek.RejectIfExists(__func__); - ScheduleStateMachine(); SetState(DECODER_STATE_DORMANT); + if (IsPlaying()) { + StopPlayback(); + } + StopAudioThread(); + FlushDecoding(); + // Now that those threads are stopped, there's no possibility of + // mPendingWakeDecoder being needed again. Revoke it. + mPendingWakeDecoder = nullptr; + DebugOnly<nsresult> rv = DecodeTaskQueue()->Dispatch( + NS_NewRunnableMethod(mReader, &MediaDecoderReader::ReleaseMediaResources)); + MOZ_ASSERT(NS_SUCCEEDED(rv)); mDecoder->GetReentrantMonitor().NotifyAll(); } else if ((aDormant != true) && (mState == DECODER_STATE_DORMANT)) { mDecodingFrozenAtStateDecoding = true; ScheduleStateMachine(); mCurrentFrameTime = 0; SetState(DECODER_STATE_DECODING_NONE); mDecoder->GetReentrantMonitor().NotifyAll(); } @@ -1508,19 +1520,18 @@ void MediaDecoderStateMachine::Shutdown( { NS_ASSERTION(NS_IsMainThread(), "Should be on main thread."); // Once we've entered the shutdown state here there's no going back. ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor()); // Change state before issuing shutdown request to threads so those // threads can start exiting cleanly during the Shutdown call. - DECODER_LOG("Changed state to SHUTDOWN"); + ScheduleStateMachine(); SetState(DECODER_STATE_SHUTDOWN); - mScheduler->ScheduleAndShutdown(); if (mAudioSink) { mAudioSink->PrepareToShutdown(); } mDecoder->GetReentrantMonitor().NotifyAll(); } void MediaDecoderStateMachine::StartDecoding() { @@ -1769,17 +1780,17 @@ MediaDecoderStateMachine::EnqueueDecodeM nsresult MediaDecoderStateMachine::EnqueueDecodeFirstFrameTask() { AssertCurrentThreadInMonitor(); MOZ_ASSERT(mState == DECODER_STATE_DECODING_FIRSTFRAME); RefPtr<nsIRunnable> task( NS_NewRunnableMethod(this, &MediaDecoderStateMachine::CallDecodeFirstFrame)); - nsresult rv = GetStateMachineThread()->Dispatch(task, NS_DISPATCH_NORMAL); + nsresult rv = TaskQueue()->Dispatch(task); NS_ENSURE_SUCCESS(rv, rv); return NS_OK; } void MediaDecoderStateMachine::SetReaderIdle() { MOZ_ASSERT(OnDecodeThread()); @@ -1916,17 +1927,17 @@ MediaDecoderStateMachine::InitiateSeek() // Put a reset in the pipe before seek. ResetDecode(); // Do the seek. mSeekRequest.Begin(ProxyMediaCall(DecodeTaskQueue(), mReader.get(), __func__, &MediaDecoderReader::Seek, mCurrentSeek.mTarget.mTime, GetEndTime()) - ->RefableThen(mScheduler.get(), __func__, this, + ->RefableThen(TaskQueue(), __func__, this, &MediaDecoderStateMachine::OnSeekCompleted, &MediaDecoderStateMachine::OnSeekFailed)); } nsresult MediaDecoderStateMachine::DispatchAudioDecodeTaskIfNeeded() { MOZ_ASSERT(OnStateMachineThread()); @@ -1964,17 +1975,17 @@ MediaDecoderStateMachine::EnsureAudioDec return NS_OK; } SAMPLE_LOG("Queueing audio task - queued=%i, decoder-queued=%o", AudioQueue().GetSize(), mReader->SizeOfAudioQueueInFrames()); mAudioDataRequest.Begin(ProxyMediaCall(DecodeTaskQueue(), mReader.get(), __func__, &MediaDecoderReader::RequestAudioData) - ->RefableThen(mScheduler.get(), __func__, this, + ->RefableThen(TaskQueue(), __func__, this, &MediaDecoderStateMachine::OnAudioDecoded, &MediaDecoderStateMachine::OnAudioNotDecoded)); return NS_OK; } nsresult MediaDecoderStateMachine::DispatchVideoDecodeTaskIfNeeded() @@ -2024,17 +2035,17 @@ MediaDecoderStateMachine::EnsureVideoDec SAMPLE_LOG("Queueing video task - queued=%i, decoder-queued=%o, skip=%i, time=%lld", VideoQueue().GetSize(), mReader->SizeOfVideoQueueInFrames(), skipToNextKeyFrame, currentTime); mVideoDataRequest.Begin(ProxyMediaCall(DecodeTaskQueue(), mReader.get(), __func__, &MediaDecoderReader::RequestVideoData, skipToNextKeyFrame, currentTime) - ->RefableThen(mScheduler.get(), __func__, this, + ->RefableThen(TaskQueue(), __func__, this, &MediaDecoderStateMachine::OnVideoDecoded, &MediaDecoderStateMachine::OnVideoNotDecoded)); return NS_OK; } nsresult MediaDecoderStateMachine::StartAudioThread() { @@ -2162,19 +2173,19 @@ MediaDecoderStateMachine::DecodeError() DECODER_WARN("Failed to dispatch AcquireMonitorAndInvokeDecodeError"); } return; } // Change state to shutdown before sending error report to MediaDecoder // and the HTMLMediaElement, so that our pipeline can start exiting // cleanly during the sync dispatch below. - DECODER_WARN("Decode error, changed state to SHUTDOWN due to error"); + ScheduleStateMachine(); SetState(DECODER_STATE_SHUTDOWN); - mScheduler->ScheduleAndShutdown(); + DECODER_WARN("Decode error, changed state to SHUTDOWN due to error"); mDecoder->GetReentrantMonitor().NotifyAll(); // Dispatch the event to call DecodeError synchronously. This ensures // we're in shutdown state by the time we exit the decode thread. // If we just moved to shutdown state here on the decode thread, we may // cause the state machine to shutdown/free memory without closing its // media stream properly, and we'll get callbacks from the media stream // causing a crash. @@ -2317,22 +2328,22 @@ MediaDecoderStateMachine::DecodeFirstFra MOZ_ASSERT(OnStateMachineThread()); AssertCurrentThreadInMonitor(); MOZ_ASSERT(mState == DECODER_STATE_DECODING_FIRSTFRAME); DECODER_LOG("DecodeFirstFrame started"); if (HasAudio()) { RefPtr<nsIRunnable> decodeTask( NS_NewRunnableMethod(this, &MediaDecoderStateMachine::DispatchAudioDecodeTaskIfNeeded)); - AudioQueue().AddPopListener(decodeTask, GetStateMachineThread()); + AudioQueue().AddPopListener(decodeTask, TaskQueue()); } if (HasVideo()) { RefPtr<nsIRunnable> decodeTask( NS_NewRunnableMethod(this, &MediaDecoderStateMachine::DispatchVideoDecodeTaskIfNeeded)); - VideoQueue().AddPopListener(decodeTask, GetStateMachineThread()); + VideoQueue().AddPopListener(decodeTask, TaskQueue()); } if (IsRealTime()) { SetStartTime(0); nsresult res = FinishDecodeFirstFrame(); NS_ENSURE_SUCCESS(res, res); } else if (mSentFirstFrameLoadedEvent) { // We're resuming from dormant state, so we don't need to request @@ -2340,25 +2351,25 @@ MediaDecoderStateMachine::DecodeFirstFra // we have the start time from last time we loaded. SetStartTime(mStartTime); nsresult res = FinishDecodeFirstFrame(); NS_ENSURE_SUCCESS(res, res); } else { if (HasAudio()) { mAudioDataRequest.Begin(ProxyMediaCall(DecodeTaskQueue(), mReader.get(), __func__, &MediaDecoderReader::RequestAudioData) - ->RefableThen(mScheduler.get(), __func__, this, + ->RefableThen(TaskQueue(), __func__, this, &MediaDecoderStateMachine::OnAudioDecoded, &MediaDecoderStateMachine::OnAudioNotDecoded)); } if (HasVideo()) { mVideoDecodeStartTime = TimeStamp::Now(); mVideoDataRequest.Begin(ProxyMediaCall(DecodeTaskQueue(), mReader.get(), __func__, &MediaDecoderReader::RequestVideoData, false, int64_t(0)) - ->RefableThen(mScheduler.get(), __func__, this, + ->RefableThen(TaskQueue(), __func__, this, &MediaDecoderStateMachine::OnVideoDecoded, &MediaDecoderStateMachine::OnVideoNotDecoded)); } } return NS_OK; } @@ -2545,61 +2556,45 @@ MediaDecoderStateMachine::SeekCompleted( ReentrantMonitorAutoExit exitMon(mDecoder->GetReentrantMonitor()); RenderVideoFrame(video, TimeStamp::Now()); nsCOMPtr<nsIRunnable> event = NS_NewRunnableMethod(mDecoder, &MediaDecoder::Invalidate); NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL); } } -// Runnable to dispose of the decoder and state machine on the main thread. -class nsDecoderDisposeEvent : public nsRunnable { +class DecoderDisposer +{ public: - nsDecoderDisposeEvent(already_AddRefed<MediaDecoder> aDecoder, - already_AddRefed<MediaDecoderStateMachine> aStateMachine) + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(DecoderDisposer) + DecoderDisposer(MediaDecoder* aDecoder, MediaDecoderStateMachine* aStateMachine) : mDecoder(aDecoder), mStateMachine(aStateMachine) {} - NS_IMETHOD Run() { - NS_ASSERTION(NS_IsMainThread(), "Must be on main thread."); + + void OnTaskQueueShutdown() + { + MOZ_ASSERT(NS_IsMainThread()); MOZ_ASSERT(mStateMachine); MOZ_ASSERT(mDecoder); mStateMachine->BreakCycles(); mDecoder->BreakCycles(); mStateMachine = nullptr; mDecoder = nullptr; - return NS_OK; } + private: - nsRefPtr<MediaDecoder> mDecoder; - nsRefPtr<MediaDecoderStateMachine> mStateMachine; -}; - -// Runnable which dispatches an event to the main thread to dispose of the -// decoder and state machine. This runs on the state machine thread after -// the state machine has shutdown, and all events for that state machine have -// finished running. -class nsDispatchDisposeEvent : public nsRunnable { -public: - nsDispatchDisposeEvent(MediaDecoder* aDecoder, - MediaDecoderStateMachine* aStateMachine) - : mDecoder(aDecoder), mStateMachine(aStateMachine) {} - NS_IMETHOD Run() { - NS_DispatchToMainThread(new nsDecoderDisposeEvent(mDecoder.forget(), - mStateMachine.forget())); - return NS_OK; - } -private: + virtual ~DecoderDisposer() {} nsRefPtr<MediaDecoder> mDecoder; nsRefPtr<MediaDecoderStateMachine> mStateMachine; }; void MediaDecoderStateMachine::ShutdownReader() { MOZ_ASSERT(OnDecodeThread()); - mReader->Shutdown()->Then(mScheduler.get(), __func__, this, + mReader->Shutdown()->Then(TaskQueue(), __func__, this, &MediaDecoderStateMachine::FinishShutdown, &MediaDecoderStateMachine::FinishShutdown); } void MediaDecoderStateMachine::FinishShutdown() { MOZ_ASSERT(OnStateMachineThread()); @@ -2625,25 +2620,37 @@ MediaDecoderStateMachine::FinishShutdown // hold the decoder monitor here. We also want to guarantee that the // state machine is destroyed on the main thread, and so the // event runner running this function (which holds a reference to the // state machine) needs to finish and be released in order to allow // that. So we dispatch an event to run after this event runner has // finished and released its monitor/references. That event then will // dispatch an event to the main thread to release the decoder and // state machine. - GetStateMachineThread()->Dispatch( - new nsDispatchDisposeEvent(mDecoder, this), NS_DISPATCH_NORMAL); - - DECODER_LOG("Dispose Event Dispatched"); + DECODER_LOG("Shutting down state machine task queue"); + nsCOMPtr<nsIThread> mainThread; + NS_GetMainThread(getter_AddRefs(mainThread)); + RefPtr<DecoderDisposer> disposer = new DecoderDisposer(mDecoder, this); + TaskQueue()->BeginShutdown()->Then(mainThread.get(), __func__, disposer.get(), + &DecoderDisposer::OnTaskQueueShutdown, + &DecoderDisposer::OnTaskQueueShutdown); } nsresult MediaDecoderStateMachine::RunStateMachine() { - AssertCurrentThreadInMonitor(); + MOZ_ASSERT(OnStateMachineThread()); + ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor()); + + mDelayedScheduler.Reset(); // Must happen on state machine thread. + mDispatchedStateMachine = false; + + // If audio is being captured, stop the audio sink if it's running + if (mAudioCaptured) { + StopAudioThread(); + } MediaResource* resource = mDecoder->GetResource(); NS_ENSURE_TRUE(resource, NS_ERROR_NULL_POINTER); switch (mState) { case DECODER_STATE_SHUTDOWN: { mQueuedSeek.RejectIfExists(__func__); mPendingSeek.RejectIfExists(__func__); @@ -2663,27 +2670,16 @@ nsresult MediaDecoderStateMachine::RunSt DebugOnly<nsresult> rv = DecodeTaskQueue()->Dispatch(task); MOZ_ASSERT(NS_SUCCEEDED(rv)); DECODER_LOG("Shutdown started"); return NS_OK; } case DECODER_STATE_DORMANT: { - if (IsPlaying()) { - StopPlayback(); - } - StopAudioThread(); - FlushDecoding(); - // Now that those threads are stopped, there's no possibility of - // mPendingWakeDecoder being needed again. Revoke it. - mPendingWakeDecoder = nullptr; - DebugOnly<nsresult> rv = DecodeTaskQueue()->Dispatch( - NS_NewRunnableMethod(mReader, &MediaDecoderReader::ReleaseMediaResources)); - MOZ_ASSERT(NS_SUCCEEDED(rv)); return NS_OK; } case DECODER_STATE_WAIT_FOR_RESOURCES: { return NS_OK; } case DECODER_STATE_DECODING_NONE: { @@ -2734,17 +2730,17 @@ nsresult MediaDecoderStateMachine::RunSt elapsed < TimeDuration::FromSeconds(mBufferingWait * mPlaybackRate) && (mQuickBuffering ? HasLowDecodedData(mQuickBufferingLowDataThresholdUsecs) : HasLowUndecodedData(mBufferingWait * USECS_PER_S)) && mDecoder->IsExpectingMoreData()) { DECODER_LOG("Buffering: wait %ds, timeout in %.3lfs %s", mBufferingWait, mBufferingWait - elapsed.ToSeconds(), (mQuickBuffering ? "(quick exit)" : "")); - ScheduleStateMachine(USECS_PER_S); + ScheduleStateMachineIn(USECS_PER_S); return NS_OK; } } else if (OutOfDecodedAudio() || OutOfDecodedVideo()) { MOZ_ASSERT(mReader->IsWaitForDataSupported(), "Don't yet have a strategy for non-heuristic + non-WaitForData"); DispatchDecodeTasksIfNeeded(); MOZ_ASSERT_IF(!mMinimizePreroll && OutOfDecodedAudio(), mAudioDataRequest.Exists() || mAudioWaitRequest.Exists()); MOZ_ASSERT_IF(!mMinimizePreroll && OutOfDecodedVideo(), mVideoDataRequest.Exists() || mVideoWaitRequest.Exists()); @@ -3058,17 +3054,17 @@ void MediaDecoderStateMachine::AdvanceFr if (shouldBuffer) { if (currentFrame) { VideoQueue().PushFront(currentFrame); } StartBuffering(); // Don't go straight back to the state machine loop since that might // cause us to start decoding again and we could flip-flop between // decoding and quick-buffering. - ScheduleStateMachine(USECS_PER_S); + ScheduleStateMachineIn(USECS_PER_S); return; } } // We've got enough data to keep playing until at least the next frame. // Start playing now if need be. if ((mFragmentEndTime >= 0 && clock_time < mFragmentEndTime) || mFragmentEndTime < 0) { MaybeStartPlayback(); @@ -3120,17 +3116,22 @@ void MediaDecoderStateMachine::AdvanceFr } // If the number of audio/video frames queued has changed, either by // this function popping and playing a video frame, or by the audio // thread popping and playing an audio frame, we may need to update our // ready state. Post an update to do so. UpdateReadyState(); - ScheduleStateMachine(remainingTime / mPlaybackRate); + int64_t delay = remainingTime / mPlaybackRate; + if (delay > 0) { + ScheduleStateMachineIn(delay); + } else { + ScheduleStateMachine(); + } } nsresult MediaDecoderStateMachine::DropVideoUpToSeekTarget(VideoData* aSample) { nsRefPtr<VideoData> video(aSample); MOZ_ASSERT(video); DECODER_LOG("DropVideoUpToSeekTarget() frame [%lld, %lld] dup=%d", @@ -3366,63 +3367,84 @@ void MediaDecoderStateMachine::SetPlaySt } if (!mPlayStartTime.IsNull()) { mAudioSink->StartPlayback(); } else { mAudioSink->StopPlayback(); } } -nsresult MediaDecoderStateMachine::CallRunStateMachine() -{ - AssertCurrentThreadInMonitor(); - NS_ASSERTION(OnStateMachineThread(), "Should be on state machine thread."); - - // If audio is being captured, stop the audio sink if it's running - if (mAudioCaptured) { - StopAudioThread(); - } - - return RunStateMachine(); -} - -nsresult MediaDecoderStateMachine::TimeoutExpired(void* aClosure) -{ - MediaDecoderStateMachine* p = static_cast<MediaDecoderStateMachine*>(aClosure); - return p->CallRunStateMachine(); -} - void MediaDecoderStateMachine::ScheduleStateMachineWithLockAndWakeDecoder() { ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor()); DispatchAudioDecodeTaskIfNeeded(); DispatchVideoDecodeTaskIfNeeded(); } -nsresult MediaDecoderStateMachine::ScheduleStateMachine(int64_t aUsecs) { - return mScheduler->Schedule(aUsecs); +void +MediaDecoderStateMachine::ScheduleStateMachine() { + AssertCurrentThreadInMonitor(); + if (mState == DECODER_STATE_SHUTDOWN) { + NS_WARNING("Refusing to schedule shutdown state machine"); + return; + } + + if (mDispatchedStateMachine) { + return; + } + mDispatchedStateMachine = true; + + RefPtr<nsIRunnable> task = + NS_NewRunnableMethod(this, &MediaDecoderStateMachine::RunStateMachine); + nsresult rv = TaskQueue()->Dispatch(task); + MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv)); + (void) rv; +} + +void +MediaDecoderStateMachine::ScheduleStateMachineIn(int64_t aMicroseconds) +{ + AssertCurrentThreadInMonitor(); + MOZ_ASSERT(OnStateMachineThread()); // mDelayedScheduler.Ensure() may Disconnect() + // the promise, which must happen on the state + // machine thread. + MOZ_ASSERT(aMicroseconds > 0); + if (mState == DECODER_STATE_SHUTDOWN) { + NS_WARNING("Refusing to schedule shutdown state machine"); + return; + } + + if (mDispatchedStateMachine) { + return; + } + + // Real-time weirdness. + if (IsRealTime()) { + aMicroseconds = std::min(aMicroseconds, int64_t(40000)); + } + + TimeStamp now = TimeStamp::Now(); + TimeStamp target = now + TimeDuration::FromMicroseconds(aMicroseconds); + + SAMPLE_LOG("Scheduling state machine for %lf ms from now", (target - now).ToMilliseconds()); + mDelayedScheduler.Ensure(target); } bool MediaDecoderStateMachine::OnDecodeThread() const { return !DecodeTaskQueue() || DecodeTaskQueue()->IsCurrentThreadIn(); } bool MediaDecoderStateMachine::OnStateMachineThread() const { - return mScheduler->OnStateMachineThread(); -} - -nsIEventTarget* MediaDecoderStateMachine::GetStateMachineThread() const -{ - return mScheduler->GetStateMachineThread(); + return TaskQueue()->IsCurrentThreadIn(); } bool MediaDecoderStateMachine::IsStateMachineScheduled() const { - return mScheduler->IsScheduled(); + return mDispatchedStateMachine || mDelayedScheduler.IsScheduled(); } void MediaDecoderStateMachine::SetPlaybackRate(double aPlaybackRate) { NS_ASSERTION(NS_IsMainThread(), "Should be on main thread."); NS_ASSERTION(aPlaybackRate != 0, "PlaybackRate == 0 should be handled before this function."); ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
--- a/dom/media/MediaDecoderStateMachine.h +++ b/dom/media/MediaDecoderStateMachine.h @@ -84,18 +84,18 @@ hardware (via AudioStream). #include "mozilla/Attributes.h" #include "nsThreadUtils.h" #include "MediaDecoder.h" #include "mozilla/ReentrantMonitor.h" #include "MediaDecoderReader.h" #include "MediaDecoderOwner.h" #include "MediaMetadataManager.h" -#include "MediaDecoderStateMachineScheduler.h" #include "mozilla/RollingMean.h" +#include "MediaTimer.h" class nsITimer; namespace mozilla { class AudioSegment; class VideoSegment; class MediaTaskQueue; @@ -206,18 +206,18 @@ public: MediaDecoderOwner::NextFrameStatus GetNextFrameStatus(); // Cause state transitions. These methods obtain the decoder monitor // to synchronise the change of state, and to notify other threads // that the state has changed. void Play() { MOZ_ASSERT(NS_IsMainThread()); - nsRefPtr<nsRunnable> r = NS_NewRunnableMethod(this, &MediaDecoderStateMachine::PlayInternal); - GetStateMachineThread()->Dispatch(r, NS_DISPATCH_NORMAL); + RefPtr<nsRunnable> r = NS_NewRunnableMethod(this, &MediaDecoderStateMachine::PlayInternal); + TaskQueue()->Dispatch(r); } private: // The actual work for the above, which happens asynchronously on the state // machine thread. void PlayInternal(); public: @@ -306,31 +306,40 @@ public: if (mReader) { return mReader->SizeOfAudioQueueInBytes(); } return 0; } void NotifyDataArrived(const char* aBuffer, uint32_t aLength, int64_t aOffset); - // Returns the shared state machine thread. - nsIEventTarget* GetStateMachineThread() const; + // Returns the state machine task queue. + MediaTaskQueue* TaskQueue() const { return mTaskQueue; } // Calls ScheduleStateMachine() after taking the decoder lock. Also // notifies the decoder thread in case it's waiting on the decoder lock. void ScheduleStateMachineWithLockAndWakeDecoder(); - // Schedules the shared state machine thread to run the state machine - // in aUsecs microseconds from now, if it's not already scheduled to run - // earlier, in which case the request is discarded. - nsresult ScheduleStateMachine(int64_t aUsecs = 0); + // Schedules the shared state machine thread to run the state machine. + void ScheduleStateMachine(); + + // Invokes ScheduleStateMachine to run in |aMicroseconds| microseconds, + // unless it's already scheduled to run earlier, in which case the + // request is discarded. + void ScheduleStateMachineIn(int64_t aMicroseconds); - // Callback function registered with MediaDecoderStateMachineScheduler - // to run state machine cycles. - static nsresult TimeoutExpired(void* aClosure); + void OnDelayedSchedule() + { + MOZ_ASSERT(OnStateMachineThread()); + ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor()); + mDelayedScheduler.CompleteRequest(); + ScheduleStateMachine(); + } + + void NotReached() { MOZ_DIAGNOSTIC_ASSERT(false); } // Set the media fragment end time. aEndTime is in microseconds. void SetFragmentEndTime(int64_t aEndTime); // Drop reference to decoder. Only called during shutdown dance. void BreakCycles() { if (mReader) { mReader->BreakCycles(); @@ -684,19 +693,16 @@ protected: // The decoder monitor must be held. void CheckIfDecodeComplete(); // Copy audio from an AudioData packet to aOutput. This may require // inserting silence depending on the timing of the audio packet. void SendStreamAudio(AudioData* aAudio, DecodedStreamData* aStream, AudioSegment* aOutput); - // State machine thread run function. Defers to RunStateMachine(). - nsresult CallRunStateMachine(); - // Performs one "cycle" of the state machine. Polls the state, and may send // a video frame to be displayed, and generally manages the decode. Called // periodically via timer to ensure the video stays in sync. nsresult RunStateMachine(); bool IsStateMachineScheduled() const; // Returns true if we're not playing and the decode thread has filled its @@ -740,19 +746,70 @@ protected: // dropped its reference to the decoder. This enables the state machine to // keep using the decoder's monitor until the state machine has finished // shutting down, without fear of the monitor being destroyed. After // shutting down, the state machine will then release this reference, // causing the decoder to be destroyed. This is accessed on the decode, // state machine, audio and main threads. nsRefPtr<MediaDecoder> mDecoder; - // Used to schedule state machine cycles. This should never outlive - // the life cycle of the state machine. - const nsRefPtr<MediaDecoderStateMachineScheduler> mScheduler; + // Task queue for running the state machine. + nsRefPtr<MediaTaskQueue> mTaskQueue; + + // True is we are decoding a realtime stream, like a camera stream. + bool mRealTime; + + // True if we've dispatched a task to run the state machine but the task has + // yet to run. + bool mDispatchedStateMachine; + + // Class for managing delayed dispatches of the state machine. + class DelayedScheduler { + public: + explicit DelayedScheduler(MediaDecoderStateMachine* aSelf) + : mSelf(aSelf), mMediaTimer(new MediaTimer()) {} + + bool IsScheduled() const { return !mTarget.IsNull(); } + + void Reset() + { + MOZ_ASSERT(mSelf->OnStateMachineThread(), + "Must be on state machine queue to disconnect"); + if (IsScheduled()) { + mRequest.Disconnect(); + mTarget = TimeStamp(); + } + } + + void Ensure(mozilla::TimeStamp& aTarget) + { + if (IsScheduled() && mTarget <= aTarget) { + return; + } + Reset(); + mTarget = aTarget; + mRequest.Begin(mMediaTimer->WaitUntil(mTarget, __func__)->RefableThen( + mSelf->TaskQueue(), __func__, mSelf, + &MediaDecoderStateMachine::OnDelayedSchedule, + &MediaDecoderStateMachine::NotReached)); + } + + void CompleteRequest() + { + mRequest.Complete(); + mTarget = TimeStamp(); + } + + private: + MediaDecoderStateMachine* mSelf; + nsRefPtr<MediaTimer> mMediaTimer; + MediaPromiseConsumerHolder<mozilla::MediaTimerPromise> mRequest; + TimeStamp mTarget; + + } mDelayedScheduler; // Time at which the last video sample was requested. If it takes too long // before the sample arrives, we will increase the amount of audio we buffer. // This is necessary for legacy synchronous decoders to prevent underruns. TimeStamp mVideoDecodeStartTime; // Queue of audio frames. This queue is threadsafe, and is accessed from // the audio, decoder, state machine, and main threads. @@ -950,28 +1007,28 @@ protected: // At the start of decoding we want to "preroll" the decode until we've // got a few frames decoded before we consider whether decode is falling // behind. Otherwise our "we're falling behind" logic will trigger // unneccessarily if we start playing as soon as the first sample is // decoded. These two fields store how many video frames and audio // samples we must consume before are considered to be finished prerolling. uint32_t AudioPrerollUsecs() const { - if (mScheduler->IsRealTime()) { + if (IsRealTime()) { return 0; } uint32_t result = mLowAudioThresholdUsecs * 2; MOZ_ASSERT(result <= mAmpleAudioThresholdUsecs, "Prerolling will never finish"); return result; } uint32_t VideoPrerollFrames() const { - return mScheduler->IsRealTime() ? 0 : GetAmpleVideoFrames() / 2; + return IsRealTime() ? 0 : GetAmpleVideoFrames() / 2; } bool DonePrerollingAudio() { AssertCurrentThreadInMonitor(); return !IsAudioDecoding() || GetDecodedAudioDuration() >= AudioPrerollUsecs() * mPlaybackRate; }
deleted file mode 100644 --- a/dom/media/MediaDecoderStateMachineScheduler.cpp +++ /dev/null @@ -1,194 +0,0 @@ -/* vim:set ts=2 sw=2 sts=2 et cindent: */ -/* 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 "MediaDecoderStateMachineScheduler.h" -#include "SharedThreadPool.h" -#include "mozilla/Preferences.h" -#include "mozilla/ReentrantMonitor.h" -#include "nsITimer.h" -#include "nsComponentManagerUtils.h" -#include "VideoUtils.h" - -namespace { -class TimerEvent : public nsITimerCallback, public nsRunnable { - typedef mozilla::MediaDecoderStateMachineScheduler Scheduler; - NS_DECL_ISUPPORTS_INHERITED -public: - TimerEvent(Scheduler* aScheduler, int aTimerId) - : mScheduler(aScheduler), mTimerId(aTimerId) {} - - NS_IMETHOD Run() MOZ_OVERRIDE { - return mScheduler->TimeoutExpired(mTimerId); - } - - NS_IMETHOD Notify(nsITimer* aTimer) MOZ_OVERRIDE { - return mScheduler->TimeoutExpired(mTimerId); - } -private: - ~TimerEvent() {} - Scheduler* const mScheduler; - const int mTimerId; -}; - -NS_IMPL_ISUPPORTS_INHERITED(TimerEvent, nsRunnable, nsITimerCallback); -} // anonymous namespace - -static already_AddRefed<nsIEventTarget> -CreateStateMachineThread() -{ - using mozilla::SharedThreadPool; - using mozilla::RefPtr; - RefPtr<SharedThreadPool> threadPool( - SharedThreadPool::Get(NS_LITERAL_CSTRING("Media State Machine"), 1)); - nsCOMPtr<nsIEventTarget> rv = threadPool.get(); - return rv.forget(); -} - -namespace mozilla { - -MediaDecoderStateMachineScheduler::MediaDecoderStateMachineScheduler( - ReentrantMonitor& aMonitor, - nsresult (*aTimeoutCallback)(void*), - void* aClosure, bool aRealTime) - : mTimeoutCallback(aTimeoutCallback) - , mClosure(aClosure) - // Only enable realtime mode when "media.realtime_decoder.enabled" is true. - , mRealTime(aRealTime && - Preferences::GetBool("media.realtime_decoder.enabled", false)) - , mMonitor(aMonitor) - , mEventTarget(CreateStateMachineThread()) - , mTimer(do_CreateInstance("@mozilla.org/timer;1")) - , mTimerId(0) - , mState(SCHEDULER_STATE_NONE) - , mInRunningStateMachine(false) -{ - MOZ_ASSERT(NS_IsMainThread()); - MOZ_COUNT_CTOR(MediaDecoderStateMachineScheduler); -} - -MediaDecoderStateMachineScheduler::~MediaDecoderStateMachineScheduler() -{ - MOZ_ASSERT(NS_IsMainThread()); - MOZ_COUNT_DTOR(MediaDecoderStateMachineScheduler); -} - -nsresult -MediaDecoderStateMachineScheduler::Init() -{ - MOZ_ASSERT(NS_IsMainThread()); - NS_ENSURE_TRUE(mEventTarget, NS_ERROR_FAILURE); - nsresult rv = mTimer->SetTarget(mEventTarget); - NS_ENSURE_SUCCESS(rv, rv); - return NS_OK; -} - -nsresult -MediaDecoderStateMachineScheduler::Schedule(int64_t aUsecs) -{ - mMonitor.AssertCurrentThreadIn(); - - if (NS_WARN_IF(mState == SCHEDULER_STATE_SHUTDOWN)) { - return NS_ERROR_FAILURE; - } - - aUsecs = std::max<int64_t>(aUsecs, 0); - - TimeStamp timeout = TimeStamp::Now() + - TimeDuration::FromMilliseconds(static_cast<double>(aUsecs) / USECS_PER_MS); - - if (!mTimeout.IsNull() && timeout >= mTimeout) { - // We've already scheduled a timer set to expire at or before this time, - // or have an event dispatched to run the state machine. - return NS_OK; - } - - uint32_t ms = static_cast<uint32_t>((aUsecs / USECS_PER_MS) & 0xFFFFFFFF); - if (IsRealTime() && ms > 40) { - ms = 40; - } - - // Don't cancel the timer here for this function will be called from - // different threads. - - nsresult rv = NS_ERROR_FAILURE; - nsRefPtr<TimerEvent> event = new TimerEvent(this, mTimerId+1); - - if (ms == 0) { - // Dispatch a runnable to the state machine thread when delay is 0. - // It will has less latency than dispatching a runnable to the state - // machine thread which will then schedule a zero-delay timer. - rv = mEventTarget->Dispatch(event, NS_DISPATCH_NORMAL); - } else if (OnStateMachineThread()) { - rv = mTimer->InitWithCallback(event, ms, nsITimer::TYPE_ONE_SHOT); - } else { - MOZ_ASSERT(false, "non-zero delay timer should be only " - "scheduled in state machine thread"); - } - - if (NS_SUCCEEDED(rv)) { - mTimeout = timeout; - ++mTimerId; - } else { - NS_WARNING("Failed to schedule state machine"); - } - - return rv; -} - -nsresult -MediaDecoderStateMachineScheduler::TimeoutExpired(int aTimerId) -{ - ReentrantMonitorAutoEnter mon(mMonitor); - MOZ_ASSERT(OnStateMachineThread()); - MOZ_ASSERT(!mInRunningStateMachine, - "State machine cycles must run in sequence!"); - - mInRunningStateMachine = true; - // Only run state machine cycles when id matches. - nsresult rv = NS_OK; - if (mTimerId == aTimerId) { - ResetTimer(); - rv = mTimeoutCallback(mClosure); - } - mInRunningStateMachine = false; - - return rv; -} - -void -MediaDecoderStateMachineScheduler::ScheduleAndShutdown() -{ - mMonitor.AssertCurrentThreadIn(); - // Schedule next cycle to handle SHUTDOWN in state machine thread. - Schedule(); - // This must be set after calling Schedule() - // which does nothing in shutdown state. - mState = SCHEDULER_STATE_SHUTDOWN; -} - -bool -MediaDecoderStateMachineScheduler::OnStateMachineThread() const -{ - bool rv = false; - mEventTarget->IsOnCurrentThread(&rv); - return rv; -} - -bool -MediaDecoderStateMachineScheduler::IsScheduled() const -{ - mMonitor.AssertCurrentThreadIn(); - return !mTimeout.IsNull(); -} - -void -MediaDecoderStateMachineScheduler::ResetTimer() -{ - mMonitor.AssertCurrentThreadIn(); - mTimer->Cancel(); - mTimeout = TimeStamp(); -} - -} // namespace mozilla
deleted file mode 100644 --- a/dom/media/MediaDecoderStateMachineScheduler.h +++ /dev/null @@ -1,78 +0,0 @@ -/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ -/* vim:set ts=2 sw=2 sts=2 et cindent: */ -/* 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 MediaDecoderStateMachineScheduler_h__ -#define MediaDecoderStateMachineScheduler_h__ - -#include "nsCOMPtr.h" -#include "mozilla/TimeStamp.h" -#include "mozilla/DebugOnly.h" - -class nsITimer; -class nsIEventTarget; - -namespace mozilla { - -class ReentrantMonitor; - -class MediaDecoderStateMachineScheduler { - enum State { - SCHEDULER_STATE_NONE, - SCHEDULER_STATE_SHUTDOWN - }; -public: - NS_INLINE_DECL_THREADSAFE_REFCOUNTING(MediaDecoderStateMachineScheduler) - MediaDecoderStateMachineScheduler(ReentrantMonitor& aMonitor, - nsresult (*aTimeoutCallback)(void*), - void* aClosure, bool aRealTime); - nsresult Init(); - nsresult Schedule(int64_t aUsecs = 0); - void ScheduleAndShutdown(); - nsresult TimeoutExpired(int aTimerId); - - bool OnStateMachineThread() const; - bool IsScheduled() const; - - bool IsRealTime() const { - return mRealTime; - } - - nsIEventTarget* GetStateMachineThread() const { - return mEventTarget; - } - -private: - ~MediaDecoderStateMachineScheduler(); - void ResetTimer(); - - // Callback function provided by MediaDecoderStateMachine to run - // state machine cycles. - nsresult (*const mTimeoutCallback)(void*); - // Since StateMachineScheduler will never outlive the state machine, - // it is safe to keep a raw pointer only to avoid reference cycles. - void* const mClosure; - // True is we are decoding a realtime stream, like a camera stream - const bool mRealTime; - // Monitor of the decoder - ReentrantMonitor& mMonitor; - // State machine thread - const nsCOMPtr<nsIEventTarget> mEventTarget; - // Timer to schedule callbacks to run the state machine cycles. - nsCOMPtr<nsITimer> mTimer; - // Timestamp at which the next state machine cycle will run. - TimeStamp mTimeout; - // The id of timer tasks, timer callback will only run if id matches. - int mTimerId; - // No more state machine cycles in shutdown state. - State mState; - - // Used to check if state machine cycles are running in sequence. - DebugOnly<bool> mInRunningStateMachine; -}; - -} // namespace mozilla - -#endif // MediaDecoderStateMachineScheduler_h__
--- a/dom/media/MediaManager.cpp +++ b/dom/media/MediaManager.cpp @@ -107,18 +107,18 @@ using dom::GetUserMediaRequest; using dom::Sequence; using dom::OwningBooleanOrMediaTrackConstraints; using dom::SupportedAudioConstraints; using dom::SupportedVideoConstraints; static bool HostInDomain(const nsCString &aHost, const nsCString &aPattern) { - PRInt32 patternOffset = 0; - PRInt32 hostOffset = 0; + int32_t patternOffset = 0; + int32_t hostOffset = 0; // Act on '*.' wildcard in the left-most position in a domain pattern. if (aPattern.Length() > 2 && aPattern[0] == '*' && aPattern[1] == '.') { patternOffset = 2; // Ignore the lowest level sub-domain for the hostname. hostOffset = aHost.FindChar('.') + 1;
--- a/dom/media/MediaPromise.cpp +++ b/dom/media/MediaPromise.cpp @@ -1,17 +1,16 @@ /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=8 sts=2 et sw=2 tw=80: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "MediaPromise.h" -#include "MediaDecoderStateMachineScheduler.h" #include "MediaTaskQueue.h" #include "nsThreadUtils.h" namespace mozilla { namespace detail { nsresult DispatchMediaPromiseRunnable(MediaTaskQueue* aTaskQueue, nsIRunnable* aRunnable) @@ -20,35 +19,23 @@ DispatchMediaPromiseRunnable(MediaTaskQu } nsresult DispatchMediaPromiseRunnable(nsIEventTarget* aEventTarget, nsIRunnable* aRunnable) { return aEventTarget->Dispatch(aRunnable, NS_DISPATCH_NORMAL); } -nsresult -DispatchMediaPromiseRunnable(MediaDecoderStateMachineScheduler* aScheduler, nsIRunnable* aRunnable) -{ - return aScheduler->GetStateMachineThread()->Dispatch(aRunnable, NS_DISPATCH_NORMAL); -} - void AssertOnThread(MediaTaskQueue* aQueue) { MOZ_ASSERT(aQueue->IsCurrentThreadIn()); } void AssertOnThread(nsIEventTarget* aTarget) { nsCOMPtr<nsIThread> targetThread = do_QueryInterface(aTarget); MOZ_ASSERT(targetThread, "Don't know how to deal with threadpools etc here"); MOZ_ASSERT(NS_GetCurrentThread() == targetThread); } -void -AssertOnThread(MediaDecoderStateMachineScheduler* aScheduler) -{ - MOZ_ASSERT(aScheduler->OnStateMachineThread()); -} - } } // namespace mozilla
--- a/dom/media/MediaPromise.h +++ b/dom/media/MediaPromise.h @@ -11,43 +11,41 @@ #include "nsTArray.h" #include "nsThreadUtils.h" #include "mozilla/DebugOnly.h" #include "mozilla/Maybe.h" #include "mozilla/Mutex.h" #include "mozilla/Monitor.h" +#include "mozilla/unused.h" /* Polyfill __func__ on MSVC for consumers to pass to the MediaPromise API. */ #ifdef _MSC_VER #define __func__ __FUNCTION__ #endif class nsIEventTarget; namespace mozilla { extern PRLogModuleInfo* gMediaPromiseLog; #define PROMISE_LOG(x, ...) \ MOZ_ASSERT(gMediaPromiseLog); \ PR_LOG(gMediaPromiseLog, PR_LOG_DEBUG, (x, ##__VA_ARGS__)) class MediaTaskQueue; -class MediaDecoderStateMachineScheduler; namespace detail { nsresult DispatchMediaPromiseRunnable(MediaTaskQueue* aQueue, nsIRunnable* aRunnable); nsresult DispatchMediaPromiseRunnable(nsIEventTarget* aTarget, nsIRunnable* aRunnable); -nsresult DispatchMediaPromiseRunnable(MediaDecoderStateMachineScheduler* aScheduler, nsIRunnable* aRunnable); #ifdef DEBUG void AssertOnThread(MediaTaskQueue* aQueue); void AssertOnThread(nsIEventTarget* aTarget); -void AssertOnThread(MediaDecoderStateMachineScheduler* aScheduler); #endif } // namespace detail /* * A promise manages an asynchronous request that may or may not be able to be * fulfilled immediately. When an API returns a promise, the consumer may attach * callbacks to be invoked (asynchronously, on a specified thread) when the @@ -103,28 +101,21 @@ public: return Move(p); } class Consumer { public: NS_INLINE_DECL_THREADSAFE_REFCOUNTING(Consumer) - void Disconnect() - { - AssertOnDispatchThread(); - MOZ_DIAGNOSTIC_ASSERT(!mComplete); - mDisconnected = true; - } + virtual void Disconnect() = 0; -#ifdef DEBUG - virtual void AssertOnDispatchThread() = 0; -#else - void AssertOnDispatchThread() {} -#endif + // MSVC complains when an inner class (ThenValueBase::{Resolve,Reject}Runnable) + // tries to access an inherited protected member. + bool IsDisconnected() const { return mDisconnected; } protected: Consumer() : mComplete(false), mDisconnected(false) {} virtual ~Consumer() {} bool mComplete; bool mDisconnected; }; @@ -144,17 +135,17 @@ protected: { public: ResolveRunnable(ThenValueBase* aThenValue, ResolveValueType aResolveValue) : mThenValue(aThenValue) , mResolveValue(aResolveValue) {} ~ResolveRunnable() { - MOZ_ASSERT(!mThenValue); + MOZ_DIAGNOSTIC_ASSERT(!mThenValue || mThenValue->IsDisconnected()); } NS_IMETHODIMP Run() { PROMISE_LOG("ResolveRunnable::Run() [this=%p]", this); mThenValue->DoResolve(mResolveValue); mThenValue = nullptr; return NS_OK; @@ -169,17 +160,17 @@ protected: { public: RejectRunnable(ThenValueBase* aThenValue, RejectValueType aRejectValue) : mThenValue(aThenValue) , mRejectValue(aRejectValue) {} ~RejectRunnable() { - MOZ_ASSERT(!mThenValue); + MOZ_DIAGNOSTIC_ASSERT(!mThenValue || mThenValue->IsDisconnected()); } NS_IMETHODIMP Run() { PROMISE_LOG("RejectRunnable::Run() [this=%p]", this); mThenValue->DoReject(mRejectValue); mThenValue = nullptr; return NS_OK; @@ -247,71 +238,87 @@ protected: MOZ_ASSERT(!aPromise->IsPending()); bool resolved = aPromise->mResolveValue.isSome(); nsRefPtr<nsRunnable> runnable = resolved ? static_cast<nsRunnable*>(new (typename ThenValueBase::ResolveRunnable)(this, aPromise->mResolveValue.ref())) : static_cast<nsRunnable*>(new (typename ThenValueBase::RejectRunnable)(this, aPromise->mRejectValue.ref())); PROMISE_LOG("%s Then() call made from %s [Runnable=%p, Promise=%p, ThenValue=%p]", resolved ? "Resolving" : "Rejecting", ThenValueBase::mCallSite, runnable.get(), aPromise, this); - DebugOnly<nsresult> rv = detail::DispatchMediaPromiseRunnable(mResponseTarget, runnable); - MOZ_ASSERT(NS_SUCCEEDED(rv)); + nsresult rv = detail::DispatchMediaPromiseRunnable(mResponseTarget, runnable); + unused << rv; + + // NB: mDisconnected is only supposed to be accessed on the dispatch + // thread. However, we require the consumer to have disconnected any + // oustanding promise requests _before_ initiating shutdown on the + // thread or task queue. So the only non-buggy scenario for dispatch + // failing involves the target thread being unable to manipulate the + // ThenValue (since it's been disconnected), so it's safe to read here. + MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv) || Consumer::mDisconnected); } #ifdef DEBUG - virtual void AssertOnDispatchThread() MOZ_OVERRIDE + void AssertOnDispatchThread() { detail::AssertOnThread(mResponseTarget); } +#else + void AssertOnDispatchThread() {} #endif + virtual void Disconnect() MOZ_OVERRIDE + { + AssertOnDispatchThread(); + MOZ_DIAGNOSTIC_ASSERT(!Consumer::mComplete); + Consumer::mDisconnected = true; + + // If a Consumer has been disconnected, we don't guarantee that the + // resolve/reject runnable will be dispatched. Null out our refcounted + // this-value now so that it's released predictably on the dispatch thread. + mThisVal = nullptr; + } + protected: virtual void DoResolve(ResolveValueType aResolveValue) MOZ_OVERRIDE { Consumer::mComplete = true; if (Consumer::mDisconnected) { + MOZ_ASSERT(!mThisVal); PROMISE_LOG("ThenValue::DoResolve disconnected - bailing out [this=%p]", this); - // Null these out for the same reasons described below. - mResponseTarget = nullptr; - mThisVal = nullptr; return; } InvokeCallbackMethod(mThisVal.get(), mResolveMethod, aResolveValue); - // Null these out after invoking the callback so that any references are - // released predictably on the target thread. Otherwise, they would be + // Null out mThisVal after invoking the callback so that any references are + // released predictably on the dispatch thread. Otherwise, it would be // released on whatever thread last drops its reference to the ThenValue, // which may or may not be ok. - mResponseTarget = nullptr; mThisVal = nullptr; } virtual void DoReject(RejectValueType aRejectValue) MOZ_OVERRIDE { Consumer::mComplete = true; if (Consumer::mDisconnected) { + MOZ_ASSERT(!mThisVal); PROMISE_LOG("ThenValue::DoReject disconnected - bailing out [this=%p]", this); - // Null these out for the same reasons described below. - mResponseTarget = nullptr; - mThisVal = nullptr; return; } InvokeCallbackMethod(mThisVal.get(), mRejectMethod, aRejectValue); - // Null these out after invoking the callback so that any references are - // released predictably on the target thread. Otherwise, they would be + // Null out mThisVal after invoking the callback so that any references are + // released predictably on the dispatch thread. Otherwise, it would be // released on whatever thread last drops its reference to the ThenValue, // which may or may not be ok. - mResponseTarget = nullptr; mThisVal = nullptr; } private: - nsRefPtr<TargetType> mResponseTarget; - nsRefPtr<ThisType> mThisVal; + nsRefPtr<TargetType> mResponseTarget; // May be released on any thread. + nsRefPtr<ThisType> mThisVal; // Only accessed and refcounted on dispatch thread. ResolveMethodType mResolveMethod; RejectMethodType mRejectMethod; }; public: template<typename TargetType, typename ThisType, typename ResolveMethodType, typename RejectMethodType> already_AddRefed<Consumer> RefableThen(TargetType* aResponseTarget, const char* aCallSite, ThisType* aThisVal, @@ -656,17 +663,17 @@ private: template<typename PromiseType, typename TargetType> static nsRefPtr<PromiseType> ProxyInternal(TargetType* aTarget, MethodCallBase<PromiseType>* aMethodCall, const char* aCallerName) { nsRefPtr<typename PromiseType::Private> p = new (typename PromiseType::Private)(aCallerName); nsRefPtr<ProxyRunnable<PromiseType>> r = new ProxyRunnable<PromiseType>(p, aMethodCall); nsresult rv = detail::DispatchMediaPromiseRunnable(aTarget, r); MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv)); - (void) rv; // Avoid compilation failures in builds with MOZ_DIAGNOSTIC_ASSERT disabled. + unused << rv; return Move(p); } } // namespace detail template<typename PromiseType, typename TargetType, typename ThisType> static nsRefPtr<PromiseType> ProxyMediaCall(TargetType* aTarget, ThisType* aThisVal, const char* aCallerName,
--- a/dom/media/MediaQueue.h +++ b/dom/media/MediaQueue.h @@ -152,45 +152,45 @@ template <class T> class MediaQueue : pr return frames; } void ClearListeners() { ReentrantMonitorAutoEnter mon(mReentrantMonitor); mPopListeners.Clear(); } - void AddPopListener(nsIRunnable* aRunnable, nsIEventTarget* aTarget) { + void AddPopListener(nsIRunnable* aRunnable, MediaTaskQueue* aTarget) { ReentrantMonitorAutoEnter mon(mReentrantMonitor); mPopListeners.AppendElement(Listener(aRunnable, aTarget)); } private: mutable ReentrantMonitor mReentrantMonitor; struct Listener { - Listener(nsIRunnable* aRunnable, nsIEventTarget* aTarget) + Listener(nsIRunnable* aRunnable, MediaTaskQueue* aTarget) : mRunnable(aRunnable) , mTarget(aTarget) { } Listener(const Listener& aOther) : mRunnable(aOther.mRunnable) , mTarget(aOther.mTarget) { } RefPtr<nsIRunnable> mRunnable; - RefPtr<nsIEventTarget> mTarget; + RefPtr<MediaTaskQueue> mTarget; }; nsTArray<Listener> mPopListeners; void NotifyPopListeners() { for (uint32_t i = 0; i < mPopListeners.Length(); i++) { Listener& l = mPopListeners[i]; - l.mTarget->Dispatch(l.mRunnable, NS_DISPATCH_NORMAL); + l.mTarget->Dispatch(l.mRunnable); } } // True when we've decoded the last frame of data in the // bitstream for which we're queueing frame data. bool mEndOfStream; };
--- a/dom/media/MediaStreamGraph.cpp +++ b/dom/media/MediaStreamGraph.cpp @@ -273,17 +273,17 @@ MediaStreamGraphImpl::UpdateBufferSuffic data->mHaveEnough = track->GetEnd() >= desiredEnd; if (!data->mHaveEnough) { runnables.MoveElementsFrom(data->mDispatchWhenNotEnough); } } } for (uint32_t i = 0; i < runnables.Length(); ++i) { - runnables[i].mTarget->Dispatch(runnables[i].mRunnable, 0); + runnables[i].mTarget->Dispatch(runnables[i].mRunnable); } } StreamTime MediaStreamGraphImpl::GraphTimeToStreamTime(MediaStream* aStream, GraphTime aTime) { MOZ_ASSERT(aTime <= CurrentDriver()->StateComputedTime(), @@ -1079,16 +1079,33 @@ SetImageToBlackPixel(PlanarYCbCrImage* a data.mYChannel = blackPixel; data.mCbChannel = blackPixel + 1; data.mCrChannel = blackPixel + 2; data.mYStride = data.mCbCrStride = 1; data.mPicSize = data.mYSize = data.mCbCrSize = IntSize(1, 1); aImage->SetData(data); } +class VideoFrameContainerInvalidateRunnable : public nsRunnable { +public: + explicit VideoFrameContainerInvalidateRunnable(VideoFrameContainer* aVideoFrameContainer) + : mVideoFrameContainer(aVideoFrameContainer) + {} + NS_IMETHOD Run() + { + MOZ_ASSERT(NS_IsMainThread()); + + mVideoFrameContainer->Invalidate(); + + return NS_OK; + } +private: + nsRefPtr<VideoFrameContainer> mVideoFrameContainer; +}; + void MediaStreamGraphImpl::PlayVideo(MediaStream* aStream) { MOZ_ASSERT(mRealtime, "Should only attempt to play video in realtime mode"); if (aStream->mVideoOutputs.IsEmpty()) return; @@ -1142,17 +1159,17 @@ MediaStreamGraphImpl::PlayVideo(MediaStr output->SetCurrentFrame(frame->GetIntrinsicSize(), image, targetTime); } else { output->SetCurrentFrame(frame->GetIntrinsicSize(), frame->GetImage(), targetTime); } nsCOMPtr<nsIRunnable> event = - NS_NewRunnableMethod(output, &VideoFrameContainer::Invalidate); + new VideoFrameContainerInvalidateRunnable(output); DispatchToMainThreadAfterStreamStateUpdate(event.forget()); } if (!aStream->mNotifiedFinished) { aStream->mLastPlayedVideoFrame = *frame; } } bool @@ -2471,31 +2488,31 @@ SourceMediaStream::GetEndOfAppendedData( return track->mEndOfFlushedData + track->mData->GetDuration(); } NS_ERROR("Track not found"); return 0; } void SourceMediaStream::DispatchWhenNotEnoughBuffered(TrackID aID, - nsIEventTarget* aSignalThread, nsIRunnable* aSignalRunnable) + MediaTaskQueue* aSignalQueue, nsIRunnable* aSignalRunnable) { MutexAutoLock lock(mMutex); TrackData* data = FindDataForTrack(aID); if (!data) { - aSignalThread->Dispatch(aSignalRunnable, 0); + aSignalQueue->Dispatch(aSignalRunnable); return; } if (data->mHaveEnough) { if (data->mDispatchWhenNotEnough.IsEmpty()) { - data->mDispatchWhenNotEnough.AppendElement()->Init(aSignalThread, aSignalRunnable); + data->mDispatchWhenNotEnough.AppendElement()->Init(aSignalQueue, aSignalRunnable); } } else { - aSignalThread->Dispatch(aSignalRunnable, 0); + aSignalQueue->Dispatch(aSignalRunnable); } } void SourceMediaStream::EndTrack(TrackID aID) { MutexAutoLock lock(mMutex); TrackData *track = FindDataForTrack(aID);
--- a/dom/media/MediaStreamGraph.h +++ b/dom/media/MediaStreamGraph.h @@ -11,16 +11,17 @@ #include "AudioStream.h" #include "nsTArray.h" #include "nsIRunnable.h" #include "StreamBuffer.h" #include "TimeVarying.h" #include "VideoFrameContainer.h" #include "VideoSegment.h" #include "MainThreadUtils.h" +#include "MediaTaskQueue.h" #include "nsAutoRef.h" #include "GraphDriver.h" #include <speex/speex_resampler.h> #include "mozilla/dom/AudioChannelBinding.h" #include "DOMMediaStream.h" class nsIRunnable; @@ -778,17 +779,17 @@ public: StreamTime GetEndOfAppendedData(TrackID aID); /** * Ensures that aSignalRunnable will be dispatched to aSignalThread * when we don't have enough buffered data in the track (which could be * immediately). Will dispatch the runnable immediately if the track * does not exist. No op if a runnable is already present for this track. */ void DispatchWhenNotEnoughBuffered(TrackID aID, - nsIEventTarget* aSignalThread, nsIRunnable* aSignalRunnable); + MediaTaskQueue* aSignalQueue, nsIRunnable* aSignalRunnable); /** * Indicate that a track has ended. Do not do any more API calls * affecting this track. * Ignored if the track does not exist. */ void EndTrack(TrackID aID); /** * Indicate that no tracks will be added starting before time aKnownTime. @@ -842,24 +843,24 @@ public: void RegisterForAudioMixing(); // XXX need a Reset API friend class MediaStreamGraphImpl; protected: struct ThreadAndRunnable { - void Init(nsIEventTarget* aTarget, nsIRunnable* aRunnable) + void Init(MediaTaskQueue* aTarget, nsIRunnable* aRunnable) { mTarget = aTarget; mRunnable = aRunnable; } - nsCOMPtr<nsIEventTarget> mTarget; - nsCOMPtr<nsIRunnable> mRunnable; + nsRefPtr<MediaTaskQueue> mTarget; + RefPtr<nsIRunnable> mRunnable; }; enum TrackCommands { TRACK_CREATE = MediaStreamListener::TRACK_EVENT_CREATED, TRACK_END = MediaStreamListener::TRACK_EVENT_ENDED }; /** * Data for each track that hasn't ended. */
new file mode 100644 --- /dev/null +++ b/dom/media/MediaTimer.cpp @@ -0,0 +1,165 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* 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 "MediaTimer.h" + +#include <math.h> + +#include "nsComponentManagerUtils.h" +#include "nsThreadUtils.h" + +namespace mozilla { + +NS_IMPL_ADDREF(MediaTimer) +NS_IMPL_RELEASE_WITH_DESTROY(MediaTimer, DispatchDestroy()) + +MediaTimer::MediaTimer() + : mMonitor("MediaTimer Monitor") + , mTimer(do_CreateInstance("@mozilla.org/timer;1")) + , mUpdateScheduled(false) +{ + // Use the SharedThreadPool to create an nsIThreadPool with a maximum of one + // thread, which is equivalent to an nsIThread for our purposes. + RefPtr<SharedThreadPool> threadPool( + SharedThreadPool::Get(NS_LITERAL_CSTRING("MediaTimer"), 1)); + mThread = threadPool.get(); + mTimer->SetTarget(mThread); +} + +void +MediaTimer::DispatchDestroy() +{ + nsCOMPtr<nsIRunnable> task = NS_NewNonOwningRunnableMethod(this, &MediaTimer::Destroy); + nsresult rv = mThread->Dispatch(task, NS_DISPATCH_NORMAL); + MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv)); + (void) rv; +} + +void +MediaTimer::Destroy() +{ + MOZ_ASSERT(OnMediaTimerThread()); + + // Reject any outstanding entries. There's no need to acquire the monitor + // here, because we're on the timer thread and all other references to us + // must be gone. + while (!mEntries.empty()) { + mEntries.top().mPromise->Reject(false, __func__); + mEntries.pop(); + } + + // Cancel the timer if necessary. + CancelTimerIfArmed(); + + delete this; +} + +bool +MediaTimer::OnMediaTimerThread() +{ + bool rv = false; + mThread->IsOnCurrentThread(&rv); + return rv; +} + +nsRefPtr<MediaTimerPromise> +MediaTimer::WaitUntil(const TimeStamp& aTimeStamp, const char* aCallSite) +{ + MonitorAutoLock mon(mMonitor); + Entry e(aTimeStamp, aCallSite); + nsRefPtr<MediaTimerPromise> p = e.mPromise.get(); + mEntries.push(e); + ScheduleUpdate(); + return p; +} + +void +MediaTimer::ScheduleUpdate() +{ + mMonitor.AssertCurrentThreadOwns(); + if (mUpdateScheduled) { + return; + } + mUpdateScheduled = true; + + nsCOMPtr<nsIRunnable> task = NS_NewRunnableMethod(this, &MediaTimer::Update); + nsresult rv = mThread->Dispatch(task, NS_DISPATCH_NORMAL); + MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv)); + (void) rv; +} + +void +MediaTimer::Update() +{ + MonitorAutoLock mon(mMonitor); + UpdateLocked(); +} + +void +MediaTimer::UpdateLocked() +{ + MOZ_ASSERT(OnMediaTimerThread()); + mMonitor.AssertCurrentThreadOwns(); + mUpdateScheduled = false; + + // Resolve all the promises whose time is up. + TimeStamp now = TimeStamp::Now(); + while (!mEntries.empty() && mEntries.top().mTimeStamp <= now) { + mEntries.top().mPromise->Resolve(true, __func__); + mEntries.pop(); + } + + // If we've got no more entries, cancel any pending timer and bail out. + if (mEntries.empty()) { + CancelTimerIfArmed(); + return; + } + + // We've got more entries - (re)arm the timer for the soonest one. + if (!TimerIsArmed() || mEntries.top().mTimeStamp < mCurrentTimerTarget) { + CancelTimerIfArmed(); + ArmTimer(mEntries.top().mTimeStamp, now); + } +} + +/* + * We use a callback function, rather than a callback method, to ensure that + * the nsITimer does not artifically keep the refcount of the MediaTimer above + * zero. When the MediaTimer is destroyed, it safely cancels the nsITimer so that + * we never fire against a dangling closure. + */ + +/* static */ void +MediaTimer::TimerCallback(nsITimer* aTimer, void* aClosure) +{ + static_cast<MediaTimer*>(aClosure)->TimerFired(); +} + +void +MediaTimer::TimerFired() +{ + MonitorAutoLock mon(mMonitor); + MOZ_ASSERT(OnMediaTimerThread()); + mCurrentTimerTarget = TimeStamp(); + UpdateLocked(); +} + +void +MediaTimer::ArmTimer(const TimeStamp& aTarget, const TimeStamp& aNow) +{ + MOZ_DIAGNOSTIC_ASSERT(!TimerIsArmed()); + MOZ_DIAGNOSTIC_ASSERT(aTarget > aNow); + + // XPCOM timer resolution is in milliseconds. It's important to never resolve + // a timer when mTarget might compare < now (even if very close), so round up. + unsigned long delay = std::ceil((aTarget - aNow).ToMilliseconds()); + mCurrentTimerTarget = aTarget; + nsresult rv = mTimer->InitWithFuncCallback(&TimerCallback, this, delay, nsITimer::TYPE_ONE_SHOT); + MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv)); + (void) rv; +} + +} // namespace mozilla
new file mode 100644 --- /dev/null +++ b/dom/media/MediaTimer.h @@ -0,0 +1,99 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* 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/. */ + +#if !defined(MediaTimer_h_) +#define MediaTimer_h_ + +#include "MediaPromise.h" + +#include <queue> + +#include "nsITimer.h" +#include "nsRefPtr.h" + +#include "mozilla/Monitor.h" +#include "mozilla/TimeStamp.h" + +namespace mozilla { + +// This promise type is only exclusive because so far there isn't a reason for +// it not to be. Feel free to change that. +typedef MediaPromise<bool, bool, /* IsExclusive = */ true> MediaTimerPromise; + +// Timers only know how to fire at a given thread, which creates an impedence +// mismatch with code that operates with MediaTaskQueues. This class solves +// that mismatch with a dedicated (but shared) thread and a nice MediaPromise-y +// interface. +class MediaTimer +{ +public: + MediaTimer(); + + // We use a release with a custom Destroy(). + NS_IMETHOD_(MozExternalRefCountType) AddRef(void); + NS_IMETHOD_(MozExternalRefCountType) Release(void); + + nsRefPtr<MediaTimerPromise> WaitUntil(const TimeStamp& aTimeStamp, const char* aCallSite); + +private: + virtual ~MediaTimer() { MOZ_ASSERT(OnMediaTimerThread()); } + + void DispatchDestroy(); // Invoked by Release on an arbitrary thread. + void Destroy(); // Runs on the timer thread. + + bool OnMediaTimerThread(); + void ScheduleUpdate(); + void Update(); + void UpdateLocked(); + + static void TimerCallback(nsITimer* aTimer, void* aClosure); + void TimerFired(); + void ArmTimer(const TimeStamp& aTarget, const TimeStamp& aNow); + + bool TimerIsArmed() + { + return !mCurrentTimerTarget.IsNull(); + } + + void CancelTimerIfArmed() + { + MOZ_ASSERT(OnMediaTimerThread()); + if (TimerIsArmed()) { + mTimer->Cancel(); + mCurrentTimerTarget = TimeStamp(); + } + } + + + struct Entry + { + TimeStamp mTimeStamp; + nsRefPtr<MediaTimerPromise::Private> mPromise; + + explicit Entry(const TimeStamp& aTimeStamp, const char* aCallSite) + : mTimeStamp(aTimeStamp) + , mPromise(new MediaTimerPromise::Private(aCallSite)) + {} + + bool operator<(const Entry& aOther) const + { + return mTimeStamp < aOther.mTimeStamp; + } + }; + + ThreadSafeAutoRefCnt mRefCnt; + NS_DECL_OWNINGTHREAD + nsCOMPtr<nsIEventTarget> mThread; + std::priority_queue<Entry> mEntries; + Monitor mMonitor; + nsCOMPtr<nsITimer> mTimer; + TimeStamp mCurrentTimerTarget; + bool mUpdateScheduled; +}; + +} // namespace mozilla + +#endif
--- a/dom/media/fmp4/AVCCDecoderModule.cpp +++ b/dom/media/fmp4/AVCCDecoderModule.cpp @@ -131,17 +131,19 @@ AVCCMediaDataDecoder::Drain() } return mLastError; } nsresult AVCCMediaDataDecoder::Shutdown() { if (mDecoder) { - return mDecoder->Shutdown(); + nsresult rv = mDecoder->Shutdown(); + mDecoder = nullptr; + return rv; } return NS_OK; } bool AVCCMediaDataDecoder::IsWaitingMediaResources() { if (mDecoder) { @@ -160,20 +162,17 @@ void AVCCMediaDataDecoder::AllocateMediaResources() { // Nothing to do, decoder will be allocated on the fly when required. } void AVCCMediaDataDecoder::ReleaseMediaResources() { - if (mDecoder) { - mDecoder->Shutdown(); - mDecoder = nullptr; - } + Shutdown(); } nsresult AVCCMediaDataDecoder::CreateDecoder() { if (!mp4_demuxer::AnnexB::HasSPS(mCurrentConfig.extra_data)) { // nothing found yet, will try again later return NS_ERROR_NOT_INITIALIZED; @@ -275,17 +274,18 @@ already_AddRefed<MediaDataDecoder> AVCCDecoderModule::CreateVideoDecoder(const mp4_demuxer::VideoDecoderConfig& aConfig, layers::LayersBackend aLayersBackend, layers::ImageContainer* aImageContainer, FlushableMediaTaskQueue* aVideoTaskQueue, MediaDataDecoderCallback* aCallback) { nsRefPtr<MediaDataDecoder> decoder; - if (strcmp(aConfig.mime_type, "video/avc") || + if ((strcmp(aConfig.mime_type, "video/avc") && + strcmp(aConfig.mime_type, "video/mp4")) || !mPDM->DecoderNeedsAVCC(aConfig)) { // There is no need for an AVCC wrapper for non-AVC content. decoder = mPDM->CreateVideoDecoder(aConfig, aLayersBackend, aImageContainer, aVideoTaskQueue, aCallback); } else {
--- a/dom/media/fmp4/MP4Reader.cpp +++ b/dom/media/fmp4/MP4Reader.cpp @@ -34,18 +34,18 @@ using mozilla::layers::LayersBackend; #ifdef PR_LOGGING PRLogModuleInfo* GetDemuxerLog() { static PRLogModuleInfo* log = nullptr; if (!log) { log = PR_NewLogModule("MP4Demuxer"); } return log; } -#define LOG(...) PR_LOG(GetDemuxerLog(), PR_LOG_DEBUG, (__VA_ARGS__)) -#define VLOG(...) PR_LOG(GetDemuxerLog(), PR_LOG_DEBUG+1, (__VA_ARGS__)) +#define LOG(arg, ...) PR_LOG(GetDemuxerLog(), PR_LOG_DEBUG, ("MP4Reader(%p)::%s: " arg, this, __func__, ##__VA_ARGS__)) +#define VLOG(arg, ...) PR_LOG(GetDemuxerLog(), PR_LOG_DEBUG, ("MP4Reader(%p)::%s: " arg, this, __func__, ##__VA_ARGS__)) #else #define LOG(...) #define VLOG(...) #endif using namespace mp4_demuxer; namespace mozilla { @@ -317,17 +317,17 @@ bool MP4Reader::IsWaitingOnCDMResource() if (!proxy) { // We're encrypted, we need a CDMProxy to decrypt file. return true; } } // We'll keep waiting if the CDM hasn't informed Gecko of its capabilities. { CDMCaps::AutoLock caps(proxy->Capabilites()); - LOG("MP4Reader::IsWaitingMediaResources() capsKnown=%d", caps.AreCapsKnown()); + LOG("capsKnown=%d", caps.AreCapsKnown()); return !caps.AreCapsKnown(); } #else return false; #endif } bool MP4Reader::IsWaitingMediaResources() @@ -613,17 +613,17 @@ MP4Reader::ShouldSkip(bool aSkipToNextKe return nextKeyframe < aTimeThreshold; } nsRefPtr<MediaDecoderReader::VideoDataPromise> MP4Reader::RequestVideoData(bool aSkipToNextKeyframe, int64_t aTimeThreshold) { MOZ_ASSERT(GetTaskQueue()->IsCurrentThreadIn()); - VLOG("RequestVideoData skip=%d time=%lld", aSkipToNextKeyframe, aTimeThreshold); + VLOG("skip=%d time=%lld", aSkipToNextKeyframe, aTimeThreshold); if (mShutdown) { NS_WARNING("RequestVideoData on shutdown MP4Reader!"); return VideoDataPromise::CreateAndReject(CANCELED, __func__); } MOZ_ASSERT(HasVideo() && mPlatform && mVideo.mDecoder); @@ -649,17 +649,17 @@ MP4Reader::RequestVideoData(bool aSkipTo return p; } nsRefPtr<MediaDecoderReader::AudioDataPromise> MP4Reader::RequestAudioData() { MOZ_ASSERT(GetTaskQueue()->IsCurrentThreadIn()); - VLOG("RequestAudioData"); + VLOG(""); if (mShutdown) { NS_WARNING("RequestAudioData on shutdown MP4Reader!"); return AudioDataPromise::CreateAndReject(CANCELED, __func__); } MonitorAutoLock lock(mAudio.mMonitor); nsRefPtr<AudioDataPromise> p = mAudio.mPromise.Ensure(__func__); ScheduleUpdate(kAudio); @@ -784,17 +784,17 @@ MP4Reader::ReturnOutput(MediaData* aData aData->mDiscontinuity = true; } if (aTrack == kAudio) { AudioData* audioData = static_cast<AudioData*>(aData); if (audioData->mChannels != mInfo.mAudio.mChannels || audioData->mRate != mInfo.mAudio.mRate) { - LOG("MP4Reader::ReturnOutput change of sampling rate:%d->%d", + LOG("change of sampling rate:%d->%d", mInfo.mAudio.mRate, audioData->mRate); mInfo.mAudio.mRate = audioData->mRate; mInfo.mAudio.mChannels = audioData->mChannels; } mAudio.mPromise.Resolve(audioData, __func__); } else if (aTrack == kVideo) { mVideo.mPromise.Resolve(static_cast<VideoData*>(aData), __func__); @@ -994,17 +994,17 @@ MP4Reader::SkipVideoDemuxToNextKeyFrame( } return true; } nsRefPtr<MediaDecoderReader::SeekPromise> MP4Reader::Seek(int64_t aTime, int64_t aEndTime) { - LOG("MP4Reader::Seek(%lld)", aTime); + LOG("aTime=(%lld)", aTime); MOZ_ASSERT(GetTaskQueue()->IsCurrentThreadIn()); MonitorAutoLock mon(mDemuxerMonitor); if (!mDecoder->GetResource()->IsTransportSeekable() || !mDemuxer->CanSeek()) { VLOG("Seek() END (Unseekable)"); return SeekPromise::CreateAndReject(NS_ERROR_FAILURE, __func__); } int64_t seekTime = aTime; @@ -1014,17 +1014,17 @@ MP4Reader::Seek(int64_t aTime, int64_t a mQueuedVideoSample = PopSampleLocked(kVideo); if (mQueuedVideoSample) { seekTime = mQueuedVideoSample->mMp4Sample->composition_timestamp; } } if (mDemuxer->HasValidAudio()) { mAudio.mTrackDemuxer->Seek(seekTime); } - LOG("MP4Reader::Seek(%lld) exit", aTime); + LOG("aTime=%lld exit", aTime); return SeekPromise::CreateAndResolve(seekTime, __func__); } void MP4Reader::UpdateIndex() { if (!mIndexReady) { return; @@ -1078,18 +1078,19 @@ bool MP4Reader::IsDormantNeeded() { #if defined(MP4_READER_DORMANT) return #if defined(MP4_READER_DORMANT_HEURISTIC) mDormantEnabled && #endif mVideo.mDecoder && mVideo.mDecoder->IsDormantNeeded(); +#else + return false; #endif - return false; } void MP4Reader::ReleaseMediaResources() { // Before freeing a video codec, all video buffers needed to be released // even from graphics pipeline. VideoFrameContainer* container = mDecoder->GetVideoFrameContainer(); if (container) {
--- a/dom/media/fmp4/ffmpeg/FFmpegH264Decoder.cpp +++ b/dom/media/fmp4/ffmpeg/FFmpegH264Decoder.cpp @@ -284,17 +284,17 @@ FFmpegH264Decoder<LIBAV_VER>::Flush() FFmpegH264Decoder<LIBAV_VER>::~FFmpegH264Decoder() { MOZ_COUNT_DTOR(FFmpegH264Decoder); } AVCodecID FFmpegH264Decoder<LIBAV_VER>::GetCodecId(const char* aMimeType) { - if (!strcmp(aMimeType, "video/avc")) { + if (!strcmp(aMimeType, "video/avc") || !strcmp(aMimeType, "video/mp4")) { return AV_CODEC_ID_H264; } if (!strcmp(aMimeType, "video/x-vnd.on2.vp6")) { return AV_CODEC_ID_VP6F; } return AV_CODEC_ID_NONE;
--- a/dom/media/gmp/GMPChild.cpp +++ b/dom/media/gmp/GMPChild.cpp @@ -38,28 +38,43 @@ static const int MAX_VOUCHER_LENGTH = 50 #define TARGET_SANDBOX_EXPORTS #include "mozilla/sandboxTarget.h" #elif defined(XP_MACOSX) #include "mozilla/Sandbox.h" #endif #endif namespace mozilla { + +#undef LOG +#undef LOGD + +#ifdef PR_LOGGING +extern PRLogModuleInfo* GetGMPLog(); +#define LOG(level, x, ...) PR_LOG(GetGMPLog(), (level), (x, ##__VA_ARGS__)) +#define LOGD(x, ...) LOG(PR_LOG_DEBUG, "GMPChild[pid=%d] " x, (int)base::GetCurrentProcId(), ##__VA_ARGS__) +#else +#define LOG(level, x, ...) +#define LOGD(x, ...) +#endif + namespace gmp { GMPChild::GMPChild() : mAsyncShutdown(nullptr) , mGMPMessageLoop(MessageLoop::current()) , mGMPLoader(nullptr) { + LOGD("GMPChild ctor"); nsDebugImpl::SetMultiprocessMode("GMP"); } GMPChild::~GMPChild() { + LOGD("GMPChild dtor"); } static bool GetFileBase(const std::string& aPluginPath, #if defined(XP_MACOSX) nsCOMPtr<nsIFile>& aLibDirectory, #endif nsCOMPtr<nsIFile>& aFileBase, @@ -252,32 +267,36 @@ GMPChild::CheckThread() bool GMPChild::Init(const std::string& aPluginPath, const std::string& aVoucherPath, base::ProcessHandle aParentProcessHandle, MessageLoop* aIOLoop, IPC::Channel* aChannel) { - if (!Open(aChannel, aParentProcessHandle, aIOLoop)) { + LOGD("%s pluginPath=%s", __FUNCTION__, aPluginPath.c_str()); + + if (NS_WARN_IF(!Open(aChannel, aParentProcessHandle, aIOLoop))) { return false; } #ifdef MOZ_CRASHREPORTER SendPCrashReporterConstructor(CrashReporter::CurrentThreadId()); #endif mPluginPath = aPluginPath; mVoucherPath = aVoucherPath; return true; } bool GMPChild::RecvSetNodeId(const nsCString& aNodeId) { + LOGD("%s nodeId=%s", __FUNCTION__, aNodeId.Data()); + // Store the per origin salt for the node id. Note: we do this in a // separate message than RecvStartPlugin() so that the string is not // sitting in a string on the IPC code's call stack. mNodeId = std::string(aNodeId.BeginReading(), aNodeId.EndReading()); return true; } GMPErr @@ -388,16 +407,18 @@ GMPChild::GetLibPath(nsACString& aOutLib } return NS_SUCCEEDED(libFile->GetNativePath(aOutLibPath)); #endif } bool GMPChild::RecvStartPlugin() { + LOGD("%s", __FUNCTION__); + #if defined(XP_WIN) PreLoadLibraries(mPluginPath); #endif PreLoadPluginVoucher(mPluginPath); PreLoadSandboxVoucher(); nsCString libPath; if (!GetLibPath(libPath)) { @@ -442,16 +463,18 @@ MessageLoop* GMPChild::GMPMessageLoop() { return mGMPMessageLoop; } void GMPChild::ActorDestroy(ActorDestroyReason aWhy) { + LOGD("%s reason=%d", __FUNCTION__, aWhy); + if (mGMPLoader) { mGMPLoader->Shutdown(); } if (AbnormalShutdown == aWhy) { NS_WARNING("Abnormal shutdown of GMP process!"); _exit(0); } @@ -673,28 +696,31 @@ GMPChild::RecvCrashPluginNow() { MOZ_CRASH(); return true; } bool GMPChild::RecvBeginAsyncShutdown() { + LOGD("%s AsyncShutdown=%d", __FUNCTION__, mAsyncShutdown!=nullptr); + MOZ_ASSERT(mGMPMessageLoop == MessageLoop::current()); if (mAsyncShutdown) { mAsyncShutdown->BeginShutdown(); } else { ShutdownComplete(); } return true; } void GMPChild::ShutdownComplete() { + LOGD("%s", __FUNCTION__); MOZ_ASSERT(mGMPMessageLoop == MessageLoop::current()); SendAsyncShutdownComplete(); } static bool GetPluginVoucherFile(const std::string& aPluginPath, nsCOMPtr<nsIFile>& aOutVoucherFile) { @@ -770,8 +796,11 @@ GMPChild::PreLoadSandboxVoucher() if (!stream) { NS_WARNING("PreLoadSandboxVoucher failed to read plugin voucher file!"); return; } } } // namespace gmp } // namespace mozilla + +#undef LOG +#undef LOGD
--- a/dom/media/mediasource/ContainerParser.cpp +++ b/dom/media/mediasource/ContainerParser.cpp @@ -288,17 +288,17 @@ public: bool initSegment = IsInitSegmentPresent(aData); if (initSegment) { mResource = new SourceBufferResource(NS_LITERAL_CSTRING("video/mp4")); mStream = new MP4Stream(mResource); // We use a timestampOffset of 0 for ContainerParser, and require // consumers of ParseStartAndEndTimestamps to add their timestamp offset // manually. This allows the ContainerParser to be shared across different // timestampOffsets. - mParser = new mp4_demuxer::MoofParser(mStream, 0, &mMonitor); + mParser = new mp4_demuxer::MoofParser(mStream, 0, /* aIsAudio = */ false, &mMonitor); mInitData = new LargeDataBuffer(); } else if (!mStream || !mParser) { return false; } mResource->AppendData(aData); nsTArray<MediaByteRange> byteRanges; MediaByteRange mbr =
--- a/dom/media/moz.build +++ b/dom/media/moz.build @@ -97,26 +97,26 @@ EXPORTS += [ 'GraphDriver.h', 'Latency.h', 'MediaCache.h', 'MediaData.h', 'MediaDecoder.h', 'MediaDecoderOwner.h', 'MediaDecoderReader.h', 'MediaDecoderStateMachine.h', - 'MediaDecoderStateMachineScheduler.h', 'MediaInfo.h', 'MediaMetadataManager.h', 'MediaPromise.h', 'MediaQueue.h', 'MediaRecorder.h', 'MediaResource.h', 'MediaSegment.h', 'MediaStreamGraph.h', 'MediaTaskQueue.h', + 'MediaTimer.h', 'MediaTrack.h', 'MediaTrackList.h', 'MP3FrameParser.h', 'nsIDocumentActivity.h', 'RtspMediaResource.h', 'SelfRef.h', 'SharedBuffer.h', 'SharedThreadPool.h', @@ -176,27 +176,27 @@ UNIFIED_SOURCES += [ 'GetUserMediaRequest.cpp', 'GraphDriver.cpp', 'Latency.cpp', 'MediaCache.cpp', 'MediaData.cpp', 'MediaDecoder.cpp', 'MediaDecoderReader.cpp', 'MediaDecoderStateMachine.cpp', - 'MediaDecoderStateMachineScheduler.cpp', 'MediaDevices.cpp', 'MediaManager.cpp', 'MediaPromise.cpp', 'MediaRecorder.cpp', 'MediaResource.cpp', 'MediaShutdownManager.cpp', 'MediaStreamError.cpp', 'MediaStreamGraph.cpp', 'MediaStreamTrack.cpp', 'MediaTaskQueue.cpp', + 'MediaTimer.cpp', 'MediaTrack.cpp', 'MediaTrackList.cpp', 'MP3FrameParser.cpp', 'RTCIdentityProviderRegistrar.cpp', 'RtspMediaResource.cpp', 'SharedThreadPool.cpp', 'StreamBuffer.cpp', 'TextTrack.cpp',
--- a/dom/media/tests/mochitest/test_peerConnection_webAudio.html +++ b/dom/media/tests/mochitest/test_peerConnection_webAudio.html @@ -12,22 +12,34 @@ createHTML({ }); // This tests WebAudio as input to a PeerConnection and a PeerConnection as // input to WebAudio. This is done by piping a 700Hz oscillator through an // analyser on the input side, the PeerConnection, and an analyser on the // output side. We then sanity check the audio by comparing the frequency domain // data from both analysers. +/* + * Use as callback to Array.reduce to get an object { value, index } + * that contains the largest element in the array. + */ +var maxWithIndex = function(a, b, i) { + if (b >= a.value) { + return { value: b, index: i }; + } else { + return a; + } +}; + runNetworkTest(function() { var test = new PeerConnectionTest(); var audioContext = new AudioContext(); - var inputAnalyser; - var outputAnalyser; + var inputAnalyser, outputAnalyser; + var inputData, outputData; test.setMediaConstraints([{audio: true}], []); test.chain.replace("PC_LOCAL_GUM", [ function PC_LOCAL_WEBAUDIO_SOURCE(test) { var oscillator = audioContext.createOscillator(); oscillator.type = 'sine'; oscillator.frequency.value = 700; oscillator.start(); @@ -51,55 +63,48 @@ runNetworkTest(function() { source.connect(outputAnalyser); outputAnalyser.connect(dest); realAttachMedia(dest.stream, type, side); }; return Promise.resolve(); }]); test.chain.append([ - function WAIT_FOR_CLEAN_AUDIO(test) { + function GET_INPUT_DATA(test) { + inputData = new Uint8Array(inputAnalyser.frequencyBinCount); + inputAnalyser.getByteFrequencyData(inputData); + return Promise.resolve(); + }, + function GET_OUTPUT_DATA(test) { // We've seen completely silent output with e10s, suggesting that the - // machine is overloaded. Here we wait for the media element on the - // output side to progress a bit after all previous steps finish to - // ensure we have healthy data to check. - var wait = function(elem, startTime, resolve) { - elem.ontimeupdate = function(ev) { - info("Waiting... current: " + elem.currentTime + ", start: " + startTime); - if (elem.currentTime - startTime < 0.5) { + // machine is overloaded. Here we wait for actual audio data on the + // output side before we proceed. + return new Promise(resolve => { + is(test.pcRemote.mediaCheckers.length, 1, "One media element on remote side"); + var elem = test.pcRemote.mediaCheckers[0].element; + var data = new Uint8Array(outputAnalyser.frequencyBinCount); + elem.ontimeupdate = ev => { + outputAnalyser.getByteFrequencyData(data); + if (data.reduce(maxWithIndex, { value: -1, index: -1 }).value === 0) { + info("Waiting for output data... time: " + elem.currentTime); return; } elem.ontimeupdate = null; + outputData = data; resolve(); - } - }; - return Promise.all(test.pcRemote.mediaCheckers.map(function(checker) { - var elem = checker.element; - var startTime = elem.currentTime; - return new Promise((y, n) => wait(elem, startTime, y)); - })); + }; + }); }, function CHECK_AUDIO_FLOW(test) { // This is for sanity check only. We'll deem that the streams are working // if the global maxima in the frequency domain for both the input and // the output are within 10 (out of 1024) steps of each other. - var inputData = new Uint8Array(inputAnalyser.frequencyBinCount); - inputAnalyser.getByteFrequencyData(inputData); - var outputData = new Uint8Array(outputAnalyser.frequencyBinCount); - outputAnalyser.getByteFrequencyData(outputData); is(inputData.length, outputData.length, "Equally sized datasets"); - var maxWithIndex = function(a, b, i) { - if (b >= a.value) { - return { value: b, index: i }; - } else { - return a; - } - }; var initialValue = { value: -1, index: -1 }; var inputMax = inputData.reduce(maxWithIndex, initialValue); var outputMax = outputData.reduce(maxWithIndex, initialValue); // Mild paranoia isnot(inputMax, 0, "Input should have data"); isnot(outputMax, 0, "Output should have data");
--- a/dom/network/UDPSocket.cpp +++ b/dom/network/UDPSocket.cpp @@ -15,16 +15,20 @@ #include "nsComponentManagerUtils.h" #include "nsContentUtils.h" #include "nsINetAddr.h" #include "nsStringStream.h" namespace mozilla { namespace dom { +NS_IMPL_ISUPPORTS(UDPSocket::ListenerProxy, + nsIUDPSocketListener, + nsIUDPSocketInternal) + NS_IMPL_CYCLE_COLLECTION_CLASS(UDPSocket) NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(UDPSocket, DOMEventTargetHelper) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOpened) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mClosed) NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(UDPSocket, DOMEventTargetHelper) @@ -170,16 +174,21 @@ UDPSocket::CloseWithReason(nsresult aRea // reject openedPromise with AbortError if socket is closed without error nsresult openFailedReason = NS_FAILED(aReason) ? aReason : NS_ERROR_DOM_ABORT_ERR; mOpened->MaybeReject(openFailedReason); } } mReadyState = SocketReadyState::Closed; + if (mListenerProxy) { + mListenerProxy->Disconnect(); + mListenerProxy = nullptr; + } + if (mSocket) { mSocket->Close(); mSocket = nullptr; } if (mSocketChild) { mSocketChild->Close(); mSocketChild = nullptr; @@ -425,17 +434,19 @@ UDPSocket::InitLocal(const nsAString& aL uint16_t localPort; rv = localAddr->GetPort(&localPort); if (NS_FAILED(rv)) { return rv; } mLocalPort.SetValue(localPort); - rv = mSocket->AsyncListen(this); + mListenerProxy = new ListenerProxy(this); + + rv = mSocket->AsyncListen(mListenerProxy); if (NS_FAILED(rv)) { return rv; } mReadyState = SocketReadyState::Open; rv = DoPendingMcastCommand(); if (NS_FAILED(rv)) { return rv; @@ -453,17 +464,24 @@ UDPSocket::InitRemote(const nsAString& a nsresult rv; nsCOMPtr<nsIUDPSocketChild> sock = do_CreateInstance("@mozilla.org/udp-socket-child;1", &rv); if (NS_FAILED(rv)) { return rv; } - rv = sock->Bind(this, NS_ConvertUTF16toUTF8(aLocalAddress), aLocalPort, mAddressReuse, mLoopback); + mListenerProxy = new ListenerProxy(this); + + rv = sock->Bind(mListenerProxy, + NS_ConvertUTF16toUTF8(aLocalAddress), + aLocalPort, + mAddressReuse, + mLoopback); + if (NS_FAILED(rv)) { return rv; } mSocketChild = sock; return NS_OK; }
--- a/dom/network/UDPSocket.h +++ b/dom/network/UDPSocket.h @@ -123,16 +123,40 @@ public: bool Send(const StringOrBlobOrArrayBufferOrArrayBufferView& aData, const Optional<nsAString>& aRemoteAddress, const Optional<Nullable<uint16_t>>& aRemotePort, ErrorResult& aRv); private: + class ListenerProxy : public nsIUDPSocketListener + , public nsIUDPSocketInternal + { + public: + NS_DECL_ISUPPORTS + NS_FORWARD_SAFE_NSIUDPSOCKETLISTENER(mSocket) + NS_FORWARD_SAFE_NSIUDPSOCKETINTERNAL(mSocket) + + explicit ListenerProxy(UDPSocket* aSocket) + : mSocket(aSocket) + { + } + + void Disconnect() + { + mSocket = nullptr; + } + + private: + virtual ~ListenerProxy() {} + + UDPSocket* mSocket; + }; + UDPSocket(nsPIDOMWindow* aOwner, const nsCString& aRemoteAddress, const Nullable<uint16_t>& aRemotePort); virtual ~UDPSocket(); nsresult Init(const nsString& aLocalAddress, @@ -171,16 +195,17 @@ private: bool mAddressReuse; bool mLoopback; SocketReadyState mReadyState; nsRefPtr<Promise> mOpened; nsRefPtr<Promise> mClosed; nsCOMPtr<nsIUDPSocket> mSocket; nsCOMPtr<nsIUDPSocketChild> mSocketChild; + nsRefPtr<ListenerProxy> mListenerProxy; struct MulticastCommand { enum CommandType { Join, Leave }; MulticastCommand(CommandType aCommand, const nsAString& aAddress) : mCommand(aCommand), mAddress(aAddress) { }
--- a/dom/network/tests/mochitest.ini +++ b/dom/network/tests/mochitest.ini @@ -19,9 +19,8 @@ skip-if = true # Bug 958689 skip-if = true # Bug 958689, bug 858005 [test_networkstats_disabled.html] skip-if = toolkit != "gonk" [test_networkstats_enabled_no_perm.html] skip-if = true # Bug 958689 [test_networkstats_enabled_perm.html] skip-if = toolkit != "gonk" [test_udpsocket.html] -skip-if = toolkit != "gonk" || (toolkit == 'gonk' && debug) # Bug 1061174 for B2G debug
--- a/dom/network/tests/test_udpsocket.html +++ b/dom/network/tests/test_udpsocket.html @@ -141,25 +141,19 @@ function testSendBigArray(socket) { let byteReceived = 0; socket.addEventListener('message', function recv_callback(msg) { let byteBegin = byteReceived; byteReceived += msg.data.byteLength; is(msg.remotePort, socket.localPort, 'expected packet from ' + socket.localPort); ok(is_same_buffer(msg.data, BIG_TYPED_ARRAY.subarray(byteBegin, byteReceived)), 'expected same buffer data [' + byteBegin+ '-' + byteReceived + ']'); if (byteReceived >= BIG_TYPED_ARRAY.length) { socket.removeEventListener('message', recv_callback); - clearTimeout(timeout); resolve(socket); } }); - - let timeout = setTimeout(function() { - ok(false, 'timeout for sending big array'); - resolve(socket); - }, 5000); }); } function testSendBigBlob(socket) { info('test for sending Big Blob'); let blob = new Blob([BIG_TYPED_ARRAY]); socket.send(blob, '127.0.0.1', socket.localPort); @@ -168,25 +162,19 @@ function testSendBigBlob(socket) { let byteReceived = 0; socket.addEventListener('message', function recv_callback(msg) { let byteBegin = byteReceived; byteReceived += msg.data.byteLength; is(msg.remotePort, socket.localPort, 'expected packet from ' + socket.localPort); ok(is_same_buffer(msg.data, BIG_TYPED_ARRAY.subarray(byteBegin, byteReceived)), 'expected same buffer data [' + byteBegin+ '-' + byteReceived + ']'); if (byteReceived >= BIG_TYPED_ARRAY.length) { socket.removeEventListener('message', recv_callback); - clearTimeout(timeout); resolve(socket); } }); - - let timeout = setTimeout(function() { - ok(false, 'timeout for sending big blob'); - resolve(socket); - }, 5000); }); } function testUDPOptions(socket) { info('test for UDP init options'); let remoteSocket = new UDPSocket({addressReuse: false, loopback: true,
--- a/dom/plugins/base/nsJSNPRuntime.cpp +++ b/dom/plugins/base/nsJSNPRuntime.cpp @@ -117,31 +117,36 @@ NPObjectIsOutOfProcessProxy(NPObject *ob obj->_class == PluginAsyncSurrogate::GetClass(); } } // anonymous namespace // Helper class that reports any JS exceptions that were thrown while // the plugin executed JS. -class AutoJSExceptionReporter +class MOZ_STACK_CLASS AutoJSExceptionReporter { public: - explicit AutoJSExceptionReporter(JSContext* aCx) - : mCx(aCx) + AutoJSExceptionReporter(dom::AutoJSAPI& jsapi, nsJSObjWrapper* aWrapper) + : mJsapi(jsapi) + , mIsDestroyPending(aWrapper->mDestroyPending) { + jsapi.TakeOwnershipOfErrorReporting(); } ~AutoJSExceptionReporter() { - JS_ReportPendingException(mCx); + if (mIsDestroyPending) { + mJsapi.ClearException(); + } } protected: - JSContext *mCx; + dom::AutoJSAPI& mJsapi; + bool mIsDestroyPending; }; NPClass nsJSObjWrapper::sJSObjWrapperNPClass = { NP_CLASS_STRUCT_VERSION, nsJSObjWrapper::NP_Allocate, nsJSObjWrapper::NP_Deallocate, @@ -741,17 +746,17 @@ nsJSObjWrapper::NP_HasMethod(NPObject *n return false; } nsJSObjWrapper *npjsobj = (nsJSObjWrapper *)npobj; JSAutoCompartment ac(cx, npjsobj->mJSObj); - AutoJSExceptionReporter reporter(cx); + AutoJSExceptionReporter reporter(jsapi, npjsobj); JS::Rooted<JS::Value> v(cx); bool ok = GetProperty(cx, npjsobj->mJSObj, id, &v); return ok && !v.isPrimitive() && ::JS_ObjectIsFunction(cx, v.toObjectOrNull()); } @@ -781,17 +786,17 @@ doInvoke(NPObject *npobj, NPIdentifier m VOID_TO_NPVARIANT(*result); nsJSObjWrapper *npjsobj = (nsJSObjWrapper *)npobj; JS::Rooted<JSObject*> jsobj(cx, npjsobj->mJSObj); JSAutoCompartment ac(cx, jsobj); JS::Rooted<JS::Value> fv(cx); - AutoJSExceptionReporter reporter(cx); + AutoJSExceptionReporter reporter(aes, npjsobj); if (method != NPIdentifier_VOID) { if (!GetProperty(cx, jsobj, method, &fv) || ::JS_TypeOfValue(cx, fv) != JSTYPE_FUNCTION) { return false; } } else { fv.setObject(*jsobj); @@ -866,17 +871,17 @@ nsJSObjWrapper::NP_HasProperty(NPObject "Null npobj in nsJSObjWrapper::NP_HasProperty!"); return false; } nsJSObjWrapper *npjsobj = (nsJSObjWrapper *)npobj; bool found, ok = false; - AutoJSExceptionReporter reporter(cx); + AutoJSExceptionReporter reporter(jsapi, npjsobj); JS::Rooted<JSObject*> jsobj(cx, npjsobj->mJSObj); JSAutoCompartment ac(cx, jsobj); NS_ASSERTION(NPIdentifierIsInt(npid) || NPIdentifierIsString(npid), "id must be either string or int!\n"); JS::Rooted<jsid> id(cx, NPIdentifierToJSId(npid)); ok = ::JS_HasPropertyById(cx, jsobj, id, &found); return ok && found; @@ -903,17 +908,17 @@ nsJSObjWrapper::NP_GetProperty(NPObject ThrowJSException(cx, "Null npobj in nsJSObjWrapper::NP_GetProperty!"); return false; } nsJSObjWrapper *npjsobj = (nsJSObjWrapper *)npobj; - AutoJSExceptionReporter reporter(cx); + AutoJSExceptionReporter reporter(aes, npjsobj); JSAutoCompartment ac(cx, npjsobj->mJSObj); JS::Rooted<JS::Value> v(cx); return (GetProperty(cx, npjsobj->mJSObj, id, &v) && JSValToNPVariant(npp, cx, v, result)); } // static @@ -938,17 +943,17 @@ nsJSObjWrapper::NP_SetProperty(NPObject "Null npobj in nsJSObjWrapper::NP_SetProperty!"); return false; } nsJSObjWrapper *npjsobj = (nsJSObjWrapper *)npobj; bool ok = false; - AutoJSExceptionReporter reporter(cx); + AutoJSExceptionReporter reporter(aes, npjsobj); JS::Rooted<JSObject*> jsObj(cx, npjsobj->mJSObj); JSAutoCompartment ac(cx, jsObj); JS::Rooted<JS::Value> v(cx, NPVariantToJSVal(npp, cx, value)); NS_ASSERTION(NPIdentifierIsInt(npid) || NPIdentifierIsString(npid), "id must be either string or int!\n"); JS::Rooted<jsid> id(cx, NPIdentifierToJSId(npid)); @@ -972,17 +977,17 @@ nsJSObjWrapper::NP_RemoveProperty(NPObje ThrowJSException(cx, "Null npobj in nsJSObjWrapper::NP_RemoveProperty!"); return false; } nsJSObjWrapper *npjsobj = (nsJSObjWrapper *)npobj; - AutoJSExceptionReporter reporter(cx); + AutoJSExceptionReporter reporter(jsapi, npjsobj); JS::ObjectOpResult result; JS::Rooted<JSObject*> obj(cx, npjsobj->mJSObj); JSAutoCompartment ac(cx, obj); NS_ASSERTION(NPIdentifierIsInt(npid) || NPIdentifierIsString(npid), "id must be either string or int!\n"); JS::Rooted<jsid> id(cx, NPIdentifierToJSId(npid)); if (!::JS_DeletePropertyById(cx, obj, id, result)) @@ -1024,17 +1029,17 @@ nsJSObjWrapper::NP_Enumerate(NPObject *n ThrowJSException(cx, "Null npobj in nsJSObjWrapper::NP_Enumerate!"); return false; } nsJSObjWrapper *npjsobj = (nsJSObjWrapper *)npobj; - AutoJSExceptionReporter reporter(cx); + AutoJSExceptionReporter reporter(jsapi, npjsobj); JS::Rooted<JSObject*> jsobj(cx, npjsobj->mJSObj); JSAutoCompartment ac(cx, jsobj); JS::AutoIdArray ida(cx, JS_Enumerate(cx, jsobj)); if (!ida) { return false; } @@ -1115,16 +1120,27 @@ NPObject * nsJSObjWrapper::GetNewOrUsed(NPP npp, JSContext *cx, JS::Handle<JSObject*> obj) { if (!npp) { NS_ERROR("Null NPP passed to nsJSObjWrapper::GetNewOrUsed()!"); return nullptr; } + // If we're running out-of-process and initializing asynchronously, and if + // the plugin has been asked to destroy itself during initialization, + // don't return any new NPObjects. + nsNPAPIPluginInstance* inst = static_cast<nsNPAPIPluginInstance*>(npp->ndata); + if (inst->GetPlugin()->GetLibrary()->IsOOP()) { + PluginAsyncSurrogate* surrogate = PluginAsyncSurrogate::Cast(npp); + if (surrogate && surrogate->IsDestroyPending()) { + return nullptr; + } + } + if (!cx) { cx = GetJSContext(npp); if (!cx) { NS_ERROR("Unable to find a JSContext in nsJSObjWrapper::GetNewOrUsed()!"); return nullptr; } @@ -2024,16 +2040,33 @@ nsJSNPRuntime::OnPluginDestroy(NPP npp) AutoSafeJSContext cx; if (sNPObjWrappers.IsInitialized()) { NppAndCx nppcx = { npp, cx }; PL_DHashTableEnumerate(&sNPObjWrappers, NPObjWrapperPluginDestroyedCallback, &nppcx); } } +// static +void +nsJSNPRuntime::OnPluginDestroyPending(NPP npp) +{ + if (sJSObjWrappersAccessible) { + // Prevent modification of sJSObjWrappers table if we go reentrant. + sJSObjWrappersAccessible = false; + for (JSObjWrapperTable::Enum e(sJSObjWrappers); !e.empty(); e.popFront()) { + nsJSObjWrapper *npobj = e.front().value(); + MOZ_ASSERT(npobj->_class == &nsJSObjWrapper::sJSObjWrapperNPClass); + if (npobj->mNpp == npp) { + npobj->mDestroyPending = true; + } + } + sJSObjWrappersAccessible = true; + } +} // Find the NPP for a NPObject. static NPP LookupNPP(NPObject *npobj) { if (npobj->_class == &nsJSObjWrapper::sJSObjWrapperNPClass) { nsJSObjWrapper* o = static_cast<nsJSObjWrapper*>(npobj); return o->mNpp; @@ -2286,17 +2319,17 @@ nsJSObjWrapper::HasOwnProperty(NPObject "Null npobj in nsJSObjWrapper::NP_HasOwnProperty!"); return false; } nsJSObjWrapper *npjsobj = (nsJSObjWrapper *)npobj; bool found, ok = false; - AutoJSExceptionReporter reporter(cx); + AutoJSExceptionReporter reporter(jsapi, npjsobj); JS::Rooted<JSObject*> jsobj(cx, npjsobj->mJSObj); JSAutoCompartment ac(cx, jsobj); NS_ASSERTION(NPIdentifierIsInt(npid) || NPIdentifierIsString(npid), "id must be either string or int!\n"); JS::Rooted<jsid> id(cx, NPIdentifierToJSId(npid)); ok = ::JS_AlreadyHasOwnPropertyById(cx, jsobj, id, &found); return ok && found;
--- a/dom/plugins/base/nsJSNPRuntime.h +++ b/dom/plugins/base/nsJSNPRuntime.h @@ -10,16 +10,17 @@ #include "npapi.h" #include "npruntime.h" #include "pldhash.h" class nsJSNPRuntime { public: static void OnPluginDestroy(NPP npp); + static void OnPluginDestroyPending(NPP npp); }; class nsJSObjWrapperKey { public: nsJSObjWrapperKey(JSObject *obj, NPP npp) : mJSObj(obj), mNpp(npp) { @@ -36,16 +37,17 @@ public: const NPP mNpp; }; class nsJSObjWrapper : public NPObject { public: JS::Heap<JSObject *> mJSObj; const NPP mNpp; + bool mDestroyPending; static NPObject *GetNewOrUsed(NPP npp, JSContext *cx, JS::Handle<JSObject*> obj); static bool HasOwnProperty(NPObject* npobj, NPIdentifier npid); protected: explicit nsJSObjWrapper(NPP npp); ~nsJSObjWrapper();
--- a/dom/plugins/base/nsPluginInstanceOwner.cpp +++ b/dom/plugins/base/nsPluginInstanceOwner.cpp @@ -225,18 +225,18 @@ nsPluginInstanceOwner::GetImageContainer // for what we do on other versions. if (AndroidBridge::Bridge()->GetAPIVersion() < 11) return nullptr; LayoutDeviceRect r = GetPluginRect(); // NotifySize() causes Flash to do a bunch of stuff like ask for surfaces to render // into, set y-flip flags, etc, so we do this at the beginning. - gfxSize resolution = mPluginFrame->PresContext()->PresShell()->GetCumulativeResolution(); - ScreenSize screenSize = (r * LayoutDeviceToScreenScale2D(resolution.width, resolution.height)).Size(); + float resolution = mPluginFrame->PresContext()->PresShell()->GetCumulativeResolution(); + ScreenSize screenSize = (r * LayoutDeviceToScreenScale(resolution)).Size(); mInstance->NotifySize(nsIntSize(screenSize.width, screenSize.height)); container = LayerManager::CreateImageContainer(); // Try to get it as an EGLImage first. nsRefPtr<Image> img; AttachToContainerAsEGLImage(container, mInstance, r, &img); if (!img) { @@ -1463,16 +1463,33 @@ nsPluginInstanceOwner::NotifyHostCreateW if (mPluginFrame) { mPluginFrame->InvalidateFrame(); } else { CallSetWindow(); } #endif } +void +nsPluginInstanceOwner::NotifyDestroyPending() +{ + if (!mInstance) { + return; + } + bool isOOP = false; + if (NS_FAILED(mInstance->GetIsOOP(&isOOP)) || !isOOP) { + return; + } + NPP npp = nullptr; + if (NS_FAILED(mInstance->GetNPP(&npp)) || !npp) { + return; + } + PluginAsyncSurrogate::NotifyDestroyPending(npp); +} + nsresult nsPluginInstanceOwner::DispatchFocusToPlugin(nsIDOMEvent* aFocusEvent) { #ifdef MOZ_WIDGET_ANDROID if (mInstance) { ANPEvent event; event.inSize = sizeof(ANPEvent); event.eventType = kLifecycle_ANPEventType;
--- a/dom/plugins/base/nsPluginInstanceOwner.h +++ b/dom/plugins/base/nsPluginInstanceOwner.h @@ -253,16 +253,17 @@ public: void ExitFullScreen(); // Called from AndroidJNI when we removed the fullscreen view. static void ExitFullScreen(jobject view); #endif void NotifyHostAsyncInitFailed(); void NotifyHostCreateWidget(); + void NotifyDestroyPending(); private: virtual ~nsPluginInstanceOwner(); // return FALSE if LayerSurface dirty (newly created and don't have valid plugin content yet) bool IsUpToDate() { nsIntSize size;
--- a/dom/plugins/ipc/PluginAsyncSurrogate.cpp +++ b/dom/plugins/ipc/PluginAsyncSurrogate.cpp @@ -94,16 +94,17 @@ PluginAsyncSurrogate::PluginAsyncSurroga : mParent(aParent) , mInstance(nullptr) , mMode(0) , mWindow(nullptr) , mAcceptCalls(false) , mInstantiated(false) , mAsyncSetWindow(false) , mInitCancelled(false) + , mDestroyPending(false) , mAsyncCallsInFlight(0) { MOZ_ASSERT(aParent); } PluginAsyncSurrogate::~PluginAsyncSurrogate() { } @@ -174,19 +175,37 @@ PluginAsyncSurrogate::NP_GetEntryPoints( aFuncs->event = &NPP_HandleEvent; aFuncs->destroystream = &NPP_DestroyStream; // We need to set these so that content code doesn't make assumptions // about these operations not being supported aFuncs->write = &PluginModuleParent::NPP_Write; aFuncs->asfile = &PluginModuleParent::NPP_StreamAsFile; } +/* static */ void +PluginAsyncSurrogate::NotifyDestroyPending(NPP aInstance) +{ + PluginAsyncSurrogate* surrogate = Cast(aInstance); + if (!surrogate) { + return; + } + surrogate->NotifyDestroyPending(); +} + +void +PluginAsyncSurrogate::NotifyDestroyPending() +{ + mDestroyPending = true; + nsJSNPRuntime::OnPluginDestroyPending(mInstance); +} + NPError PluginAsyncSurrogate::NPP_Destroy(NPSavedData** aSave) { + NotifyDestroyPending(); if (!WaitForInit()) { return NPERR_GENERIC_ERROR; } return PluginModuleParent::NPP_Destroy(mInstance, aSave); } NPError PluginAsyncSurrogate::NPP_GetValue(NPPVariable aVariable, void* aRetval) @@ -412,17 +431,17 @@ PluginAsyncSurrogate::SetStreamType(NPSt return false; } return streamListener->SetStreamType(aStreamType); } void PluginAsyncSurrogate::OnInstanceCreated(PluginInstanceParent* aInstance) { - for (PRUint32 i = 0, len = mPendingNewStreamCalls.Length(); i < len; ++i) { + for (uint32_t i = 0, len = mPendingNewStreamCalls.Length(); i < len; ++i) { PendingNewStreamCall& curPendingCall = mPendingNewStreamCalls[i]; uint16_t streamType = NP_NORMAL; NPError curError = aInstance->NPP_NewStream( const_cast<char*>(NullableStringGet(curPendingCall.mType)), curPendingCall.mStream, curPendingCall.mSeekable, &streamType); if (curError != NPERR_NO_ERROR) { // If we failed here then the send failed and we need to clean up
--- a/dom/plugins/ipc/PluginAsyncSurrogate.h +++ b/dom/plugins/ipc/PluginAsyncSurrogate.h @@ -45,31 +45,34 @@ public: NPError NPP_DestroyStream(NPStream* aStream, NPReason aReason); void OnInstanceCreated(PluginInstanceParent* aInstance); static bool Create(PluginModuleParent* aParent, NPMIMEType aPluginType, NPP aInstance, uint16_t aMode, int16_t aArgc, char* aArgn[], char* aArgv[]); static const NPClass* GetClass() { return &sNPClass; } static void NP_GetEntryPoints(NPPluginFuncs* aFuncs); static PluginAsyncSurrogate* Cast(NPP aInstance); + static void NotifyDestroyPending(NPP aInstance); + void NotifyDestroyPending(); virtual PluginAsyncSurrogate* GetAsyncSurrogate() { return this; } virtual PluginInstanceParent* GetInstance() { return nullptr; } NPP GetNPP() { return mInstance; } bool GetPropertyHelper(NPObject* aObject, NPIdentifier aName, bool* aHasProperty, bool* aHasMethod, NPVariant* aResult); - PluginModuleParent* - GetParent() { return mParent; } + PluginModuleParent* GetParent() { return mParent; } + + bool IsDestroyPending() const { return mDestroyPending; } bool SetAcceptingCalls(bool aAccept) { bool prevState = mAcceptCalls; if (mInstantiated) { aAccept = true; } mAcceptCalls = aAccept; @@ -146,16 +149,17 @@ private: NPWindow* mWindow; nsTArray<PendingNewStreamCall> mPendingNewStreamCalls; UniquePtr<PluginDestructionGuard> mPluginDestructionGuard; bool mAcceptCalls; bool mInstantiated; bool mAsyncSetWindow; bool mInitCancelled; + bool mDestroyPending; int32_t mAsyncCallsInFlight; static const NPClass sNPClass; }; struct AsyncNPObject : NPObject { explicit AsyncNPObject(PluginAsyncSurrogate* aSurrogate);
--- a/dom/promise/PromiseNativeHandler.h +++ b/dom/promise/PromiseNativeHandler.h @@ -3,16 +3,17 @@ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this file, * You can obtain one at http://mozilla.org/MPL/2.0/. */ #ifndef mozilla_dom_PromiseNativeHandler_h #define mozilla_dom_PromiseNativeHandler_h #include "nsISupports.h" +#include "js/TypeDecls.h" namespace mozilla { namespace dom { /* * PromiseNativeHandler allows C++ to react to a Promise being rejected/resolved. * A PromiseNativeHandler can be appended to a Promise using * Promise::AppendNativeHandler().
--- a/dom/tests/mochitest/bugs/mochitest.ini +++ b/dom/tests/mochitest/bugs/mochitest.ini @@ -161,8 +161,9 @@ skip-if = toolkit == 'android' || e10s # [test_resize_move_windows.html] skip-if = buildapp == 'mulet' || buildapp == 'b2g' || toolkit == 'android' || e10s #Windows can't change size and position on Android # b2g(Windows can't change size and position on B2G) b2g-debug(Windows can't change size and position on B2G) b2g-desktop(Windows can't change size and position on B2G) [test_sizetocontent_clamp.html] skip-if = buildapp == 'mulet' || buildapp == 'b2g' || toolkit == 'android' || e10s #Windows can't change size on Android # b2g(Windows can't change size on B2G) b2g-debug(Windows can't change size on B2G) b2g-desktop(Windows can't change size on B2G) [test_toJSON.html] [test_window_bar.html] skip-if = buildapp == 'mulet' || buildapp == 'b2g' || toolkit == 'android' || e10s [test_bug1022869.html] +[test_bug1112040.html]
new file mode 100644 --- /dev/null +++ b/dom/tests/mochitest/bugs/test_bug1112040.html @@ -0,0 +1,32 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1112040 +--> +<head> + <title>Test for Bug 1112040</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <base href="http://mochi.test:8888/tests/dom/tests/mochitest/"> + <meta charset="UTF-8"> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1112040">Mozilla Bug 1112040</a> +<p id="display"> +</p> +<div id="content"> + <input id="i" type="text" pattern="^.**$"> +</div> +<pre id="test"> +<script type="application/javascript"> + +/** Test for Bug 1112040 **/ +SimpleTest.runTestExpectingConsoleMessages( + function() { $('i').value = "42"; }, + [{ errorMessage: "SyntaxError: nothing to repeat" }] +); + +</script> +</pre> +</body> +</html>
new file mode 100644 --- /dev/null +++ b/dom/tests/mochitest/fetch/fetch_test_framework.js @@ -0,0 +1,86 @@ +function testScript(script) { + function workerTest() { + return new Promise(function(resolve, reject) { + var worker = new Worker("worker_wrapper.js"); + worker.onmessage = function(event) { + if (event.data.type == 'finish') { + resolve(); + } else if (event.data.type == 'status') { + ok(event.data.status, "Worker fetch test: " + event.data.msg); + } + } + worker.onerror = function(event) { + reject("Worker error: " + event.message); + }; + + worker.postMessage({ "script": script }); + }); + } + + function windowTest() { + return new Promise(function(resolve, reject) { + var scriptEl = document.createElement("script"); + scriptEl.setAttribute("src", script); + scriptEl.onload = function() { + runTest().then(resolve, reject); + }; + document.body.appendChild(scriptEl); + }); + } + + SimpleTest.waitForExplicitFinish(); + // We have to run the window and worker tests sequentially since some tests + // set and compare cookies and running in parallel can lead to conflicting + // values. + windowTest() + .then(function() { + return workerTest(); + }) + .catch(function(e) { + ok(false, "Some test failed in " + script); + info(e); + info(e.message); + return Promise.resolve(); + }) + .then(function() { + SimpleTest.finish(); + }); +} + +// Utilities +// ========= + +// Helper that uses FileReader or FileReaderSync based on context and returns +// a Promise that resolves with the text or rejects with error. +function readAsText(blob) { + if (typeof FileReader !== "undefined") { + return new Promise(function(resolve, reject) { + var fs = new FileReader(); + fs.onload = function() { + resolve(fs.result); + } + fs.onerror = reject; + fs.readAsText(blob); + }); + } else { + var fs = new FileReaderSync(); + return Promise.resolve(fs.readAsText(blob)); + } +} + +function readAsArrayBuffer(blob) { + if (typeof FileReader !== "undefined") { + return new Promise(function(resolve, reject) { + var fs = new FileReader(); + fs.onload = function() { + resolve(fs.result); + } + fs.onerror = reject; + fs.readAsArrayBuffer(blob); + }); + } else { + var fs = new FileReaderSync(); + return Promise.resolve(fs.readAsArrayBuffer(blob)); + } +} +
--- a/dom/tests/mochitest/fetch/mochitest.ini +++ b/dom/tests/mochitest/fetch/mochitest.ini @@ -1,13 +1,18 @@ [DEFAULT] support-files = + fetch_test_framework.js + test_fetch_basic.js + test_fetch_basic_http.js + test_fetch_cors.js test_headers_common.js - test_headers_mainthread.js - worker_test_fetch_basic.js - worker_test_fetch_basic_http.js - worker_test_fetch_cors.js + test_request.js + test_response.js worker_wrapper.js [test_headers.html] +[test_headers_mainthread.html] [test_fetch_basic.html] [test_fetch_basic_http.html] [test_fetch_cors.html] +[test_request.html] +[test_response.html]
--- a/dom/tests/mochitest/fetch/test_fetch_basic.html +++ b/dom/tests/mochitest/fetch/test_fetch_basic.html @@ -8,46 +8,15 @@ <title>Bug 1039846 - Test fetch() function in worker</title> <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> </head> <body> <p id="display"></p> <div id="content" style="display: none"></div> <pre id="test"></pre> -<script type="text/javascript" src="worker_test_fetch_basic.js"> </script> +<script type="text/javascript" src="fetch_test_framework.js"> </script> <script class="testbody" type="text/javascript"> -SimpleTest.waitForExplicitFinish(); - -function testOnWorker(done) { - ok(true, "=== Start Worker Tests ==="); - var worker = new Worker("worker_test_fetch_basic.js"); - worker.onmessage = function(event) { - if (event.data.type == "finish") { - ok(true, "=== Finish Worker Tests ==="); - done(); - } else if (event.data.type == "status") { - ok(event.data.status, event.data.msg); - } - } - - worker.onerror = function(event) { - ok(false, "Worker had an error: " + event.data); - ok(true, "=== Finish Worker Tests ==="); - done(); - }; - - worker.postMessage("start"); -} - -// -// Driver -// - -testOnWorker(function() { - SimpleTest.finish(); -}); +testScript("test_fetch_basic.js"); </script> -</script> -</pre> </body> </html>
rename from dom/tests/mochitest/fetch/worker_test_fetch_basic.js rename to dom/tests/mochitest/fetch/test_fetch_basic.js --- a/dom/tests/mochitest/fetch/worker_test_fetch_basic.js +++ b/dom/tests/mochitest/fetch/test_fetch_basic.js @@ -1,22 +1,8 @@ -if (typeof ok !== "function") { - function ok(a, msg) { - dump("OK: " + !!a + " => " + a + " " + msg + "\n"); - postMessage({type: 'status', status: !!a, msg: a + ": " + msg }); - } -} - -if (typeof is !== "function") { - function is(a, b, msg) { - dump("IS: " + (a===b) + " => " + a + " | " + b + " " + msg + "\n"); - postMessage({type: 'status', status: a === b, msg: a + " === " + b + ": " + msg }); - } -} - function testAboutURL() { var p1 = fetch('about:blank').then(function(res) { is(res.status, 200, "about:blank should load a valid Response"); is(res.headers.get('content-type'), 'text/html;charset=utf-8', "about:blank content-type should be text/html;charset=utf-8"); return res.text().then(function(v) { is(v, "", "about:blank body should be empty"); }); @@ -67,29 +53,14 @@ function testSameOriginBlobURL() { is(parseInt(res.headers.get("content-length")), 16, "Content-Length should match Blob's size"); return res.text().then(function(body) { is(body, "english sentence", "Blob fetch body should match"); }); }); } function runTest() { - var done = function() { - if (typeof SimpleTest === "object") { - SimpleTest.finish(); - } else { - postMessage({ type: 'finish' }); - } - } - - Promise.resolve() + return Promise.resolve() .then(testAboutURL) .then(testDataURL) .then(testSameOriginBlobURL) // Put more promise based tests here. - .then(done) - .catch(function(e) { - ok(false, "Some Response tests failed " + e); - done(); - }) } - -onmessage = runTest;
--- a/dom/tests/mochitest/fetch/test_fetch_basic_http.html +++ b/dom/tests/mochitest/fetch/test_fetch_basic_http.html @@ -8,46 +8,15 @@ <title>Bug 1039846 - Test fetch() http fetching in worker</title> <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> </head> <body> <p id="display"></p> <div id="content" style="display: none"></div> <pre id="test"></pre> -<script type="text/javascript" src="worker_test_fetch_basic.js"> </script> +<script type="text/javascript" src="fetch_test_framework.js"> </script> <script class="testbody" type="text/javascript"> -SimpleTest.waitForExplicitFinish(); - -function testOnWorker(done) { - ok(true, "=== Start Worker Tests ==="); - var worker = new Worker("worker_test_fetch_basic_http.js"); - worker.onmessage = function(event) { - if (event.data.type == "finish") { - ok(true, "=== Finish Worker Tests ==="); - done(); - } else if (event.data.type == "status") { - ok(event.data.status, event.data.msg); - } - } - - worker.onerror = function(event) { - ok(false, "Worker had an error: " + event.message); - ok(true, "=== Finish Worker Tests ==="); - done(); - }; - - worker.postMessage("start"); -} - -// -// Driver -// - -testOnWorker(function() { - SimpleTest.finish(); -}); +testScript("test_fetch_basic_http.js"); </script> -</script> -</pre> </body> </html>
rename from dom/tests/mochitest/fetch/worker_test_fetch_basic_http.js rename to dom/tests/mochitest/fetch/test_fetch_basic_http.js --- a/dom/tests/mochitest/fetch/worker_test_fetch_basic_http.js +++ b/dom/tests/mochitest/fetch/test_fetch_basic_http.js @@ -1,20 +1,8 @@ -if (typeof ok !== "function") { - function ok(a, msg) { - postMessage({type: 'status', status: !!a, msg: a + ": " + msg }); - } -} - -if (typeof is !== "function") { - function is(a, b, msg) { - postMessage({type: 'status', status: a === b, msg: a + " === " + b + ": " + msg }); - } -} - var path = "/tests/dom/base/test/"; var passFiles = [['file_XHR_pass1.xml', 'GET', 200, 'OK', 'text/xml'], ['file_XHR_pass2.txt', 'GET', 200, 'OK', 'text/plain'], ['file_XHR_pass3.txt', 'GET', 200, 'OK', 'text/plain'], ]; function testURL() { @@ -128,45 +116,30 @@ function testResponses() { return Promise.all(fetches); } function testBlob() { return fetch(path + '/file_XHR_binary2.bin').then((r) => { ok(r.status, 200, "status should match"); return r.blob().then((b) => { ok(b.size, 65536, "blob should have size 65536"); - var frs = new FileReaderSync(); - var buf = frs.readAsArrayBuffer(b); - var u8 = new Uint8Array(buf); - for (var i = 0; i < 65536; i++) { - if (u8[i] !== (i & 255)) { - break; + return readAsArrayBuffer(b).then(function(ab) { + var u8 = new Uint8Array(ab); + for (var i = 0; i < 65536; i++) { + if (u8[i] !== (i & 255)) { + break; + } } - } - is(i, 65536, "wrong value at offset " + i); + is(i, 65536, "wrong value at offset " + i); + }); }); }); } function runTest() { - var done = function() { - if (typeof SimpleTest === "object") { - SimpleTest.finish(); - } else { - postMessage({ type: 'finish' }); - } - } - - Promise.resolve() + return Promise.resolve() .then(testURL) .then(testURLFail) .then(testRequestGET) .then(testResponses) .then(testBlob) // Put more promise based tests here. - .then(done) - .catch(function(e) { - ok(false, "Some test failed " + e); - done(); - }); } - -onmessage = runTest;
deleted file mode 100644 --- a/dom/tests/mochitest/fetch/test_fetch_basic_worker.html +++ /dev/null @@ -1,44 +0,0 @@ -<!-- - Any copyright is dedicated to the Public Domain. - http://creativecommons.org/publicdomain/zero/1.0/ ---> -<!DOCTYPE HTML> -<html> -<head> - <title>Bug 1039846 - Test fetch() function in worker</title> - <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> - <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> -</head> -<body> -<p id="display"></p> -<div id="content" style="display: none"></div> -<pre id="test"></pre> -<script class="testbody" type="text/javascript"> - - function runTest() { - var worker = new Worker("worker_test_fetch_basic.js"); - worker.onmessage = function(event) { - - if (event.data.type == 'finish') { - SimpleTest.finish(); - } else if (event.data.type == 'status') { - ok(event.data.status, event.data.msg); - } - } - - worker.onerror = function(event) { - ok(false, "Worker had an error: " + event.message + " at " + event.lineno); - SimpleTest.finish(); - }; - - worker.postMessage(true); - } - - SimpleTest.waitForExplicitFinish(); - - runTest(); -</script> -</pre> -</body> -</html> -
--- a/dom/tests/mochitest/fetch/test_fetch_cors.html +++ b/dom/tests/mochitest/fetch/test_fetch_cors.html @@ -8,46 +8,15 @@ <title>Bug 1039846 - Test fetch() CORS mode</title> <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> </head> <body> <p id="display"></p> <div id="content" style="display: none"></div> <pre id="test"></pre> +<script type="text/javascript" src="fetch_test_framework.js"> </script> <script class="testbody" type="text/javascript"> -SimpleTest.waitForExplicitFinish(); - -var worker; -function testOnWorker(done) { - ok(true, "=== Start Worker Tests ==="); - worker = new Worker("worker_test_fetch_cors.js"); - worker.onmessage = function(event) { - if (event.data.type == "finish") { - ok(true, "=== Finish Worker Tests ==="); - done(); - } else if (event.data.type == "status") { - ok(event.data.status, event.data.msg); - } - } - - worker.onerror = function(event) { - ok(false, "Worker had an error: " + event.message); - ok(true, "=== Finish Worker Tests ==="); - done(); - }; - - worker.postMessage("start"); -} - -// -// Driver -// - -testOnWorker(function() { - SimpleTest.finish(); -}); +testScript("test_fetch_cors.js"); </script> -</script> -</pre> </body> </html>
rename from dom/tests/mochitest/fetch/worker_test_fetch_cors.js rename to dom/tests/mochitest/fetch/test_fetch_cors.js --- a/dom/tests/mochitest/fetch/worker_test_fetch_cors.js +++ b/dom/tests/mochitest/fetch/test_fetch_cors.js @@ -1,20 +1,8 @@ -if (typeof ok !== "function") { - function ok(a, msg) { - postMessage({type: 'status', status: !!a, msg: a + ": " + msg }); - } -} - -if (typeof is !== "function") { - function is(a, b, msg) { - postMessage({type: 'status', status: a === b, msg: a + " === " + b + ": " + msg }); - } -} - var path = "/tests/dom/base/test/"; function isOpaqueResponse(response) { return response.type == "opaque" && response.status === 0 && response.statusText === ""; } function testModeSameOrigin() { // Fetch spec Section 4, step 4, "request's mode is same-origin". @@ -1256,34 +1244,19 @@ function testRedirects() { }); })(request, test)); } return Promise.all(fetches); } function runTest() { - var done = function() { - if (typeof SimpleTest === "object") { - SimpleTest.finish(); - } else { - postMessage({ type: 'finish' }); - } - } - testNoCorsCtor(); - Promise.resolve() + return Promise.resolve() .then(testModeSameOrigin) .then(testModeNoCors) .then(testModeCors) .then(testSameOriginCredentials) .then(testCrossOriginCredentials) .then(testRedirects) // Put more promise based tests here. - .then(done) - .catch(function(e) { - ok(false, "Some test failed " + e); - done(); - }); } - -onmessage = runTest;
--- a/dom/tests/mochitest/fetch/test_headers.html +++ b/dom/tests/mochitest/fetch/test_headers.html @@ -3,61 +3,14 @@ <!DOCTYPE HTML> <html> <head> <title>Test Fetch Headers - Basic</title> <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> </head> <body> +<script type="text/javascript" src="fetch_test_framework.js"> </script> <script class="testbody" type="text/javascript"> -SimpleTest.waitForExplicitFinish(); - -function testOnWorker(done) { - ok(true, "=== Start Worker Headers Tests ==="); - var worker = new Worker("worker_wrapper.js"); - worker.onmessage = function(event) { - if (event.data.type == "finish") { - ok(true, "=== Finish Worker Headers Tests ==="); - done(); - } else if (event.data.type == "status") { - ok(event.data.status, event.data.msg); - } - } - - worker.onerror = function(event) { - ok(false, "Worker had an error: " + event.data); - ok(true, "=== Finish Worker Headers Tests ==="); - done(); - }; - - worker.postMessage({ script: "test_headers_common.js" }); -} - -function testOnMainThread(done) { - ok(true, "=== Start Main Thread Headers Tests ==="); - var commonScript = document.createElement("script"); - commonScript.setAttribute("src", "test_headers_common.js"); - commonScript.onload = function() { - var mainThreadScript = document.createElement("script"); - mainThreadScript.setAttribute("src", "test_headers_mainthread.js"); - mainThreadScript.onload = function() { - ok(true, "=== Finish Main Thread Headers Tests ==="); - done(); - } - document.head.appendChild(mainThreadScript); - }; - document.head.appendChild(commonScript); -} - - -// -// Driver -// - -testOnMainThread(function() { - testOnWorker(function() { - SimpleTest.finish(); - }); -}); +testScript("test_headers_common.js"); </script> </body> </html>
--- a/dom/tests/mochitest/fetch/test_headers_common.js +++ b/dom/tests/mochitest/fetch/test_headers_common.js @@ -164,10 +164,13 @@ function TestFilledHeaders() { shouldThrow(function() { filled = new Headers([ ["zxy"], ["uts", "321"] ]); }, TypeError, "Fill with non-tuple sequence should throw TypeError."); } -TestEmptyHeaders(); -TestFilledHeaders(); +function runTest() { + TestEmptyHeaders(); + TestFilledHeaders(); + return Promise.resolve(); +}
new file mode 100644 --- /dev/null +++ b/dom/tests/mochitest/fetch/test_headers_mainthread.html @@ -0,0 +1,157 @@ +<!-- Any copyright is dedicated to the Public Domain. + - http://creativecommons.org/publicdomain/zero/1.0/ --> +<!DOCTYPE HTML> +<html> +<head> + <title>Test Fetch Headers - Basic</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<script type="text/javascript" src="test_headers_common.js"> </script> +<script type="text/javascript"> +// Main thread specific tests because they need SpecialPowers. Expects +// test_headers_common.js to already be loaded. + +function TestRequestHeaders() { + is(typeof Headers, "function", "Headers global constructor exists."); + var headers = new Headers(); + ok(headers, "Constructed empty Headers object"); + SpecialPowers.wrap(headers).guard = "request"; + TestCoreBehavior(headers, "foo"); + var forbidden = [ + "Accept-Charset", + "Accept-Encoding", + "Access-Control-Request-Headers", + "Access-Control-Request-Method", + "Connection", + "Content-Length", + "Cookie", + "Cookie2", + "Date", + "DNT", + "Expect", + "Host", + "Keep-Alive", + "Origin", + "Referer", + "TE", + "Trailer", + "Transfer-Encoding", + "Upgrade", + "User-Agent", + "Via", + "Proxy-Authorization", + "Proxy-blarg", + "Proxy-", + "Sec-foo", + "Sec-" + ]; + + for (var i = 0, n = forbidden.length; i < n; ++i) { + var name = forbidden[i]; + headers.append(name, "hmm"); + checkNotHas(headers, name, "Should not be able to append " + name + " to request headers"); + headers.set(name, "hmm"); + checkNotHas(headers, name, "Should not be able to set " + name + " on request headers"); + } +} + +function TestRequestNoCorsHeaders() { + is(typeof Headers, "function", "Headers global constructor exists."); + var headers = new Headers(); + ok(headers, "Constructed empty Headers object"); + SpecialPowers.wrap(headers).guard = "request-no-cors"; + + headers.append("foo", "bar"); + checkNotHas(headers, "foo", "Should not be able to append arbitrary headers to request-no-cors headers."); + headers.set("foo", "bar"); + checkNotHas(headers, "foo", "Should not be able to set arbitrary headers on request-no-cors headers."); + + var simpleNames = [ + "Accept", + "Accept-Language", + "Content-Language" + ]; + + var simpleContentTypes = [ + "application/x-www-form-urlencoded", + "multipart/form-data", + "text/plain", + "application/x-www-form-urlencoded; charset=utf-8", + "multipart/form-data; charset=utf-8", + "text/plain; charset=utf-8" + ]; + + for (var i = 0, n = simpleNames.length; i < n; ++i) { + var name = simpleNames[i]; + headers.append(name, "hmm"); + checkHas(headers, name, "Should be able to append " + name + " to request-no-cors headers"); + headers.set(name, "hmm"); + checkHas(headers, name, "Should be able to set " + name + " on request-no-cors headers"); + } + + for (var i = 0, n = simpleContentTypes.length; i < n; ++i) { + var value = simpleContentTypes[i]; + headers.append("Content-Type", value); + checkHas(headers, "Content-Type", "Should be able to append " + value + " Content-Type to request-no-cors headers"); + headers.delete("Content-Type"); + headers.set("Content-Type", value); + checkHas(headers, "Content-Type", "Should be able to set " + value + " Content-Type on request-no-cors headers"); + } +} + +function TestResponseHeaders() { + is(typeof Headers, "function", "Headers global constructor exists."); + var headers = new Headers(); + ok(headers, "Constructed empty Headers object"); + SpecialPowers.wrap(headers).guard = "response"; + TestCoreBehavior(headers, "foo"); + var forbidden = [ + "Set-Cookie", + "Set-Cookie2" + ]; + + for (var i = 0, n = forbidden.length; i < n; ++i) { + var name = forbidden[i]; + headers.append(name, "hmm"); + checkNotHas(headers, name, "Should not be able to append " + name + " to response headers"); + headers.set(name, "hmm"); + checkNotHas(headers, name, "Should not be able to set " + name + " on response headers"); + } +} + +function TestImmutableHeaders() { + is(typeof Headers, "function", "Headers global constructor exists."); + var headers = new Headers(); + ok(headers, "Constructed empty Headers object"); + TestCoreBehavior(headers, "foo"); + headers.append("foo", "atleastone"); + + SpecialPowers.wrap(headers).guard = "immutable"; + + shouldThrow(function() { + headers.append("foo", "wat"); + }, TypeError, "Should not be able to append to immutable headers"); + + shouldThrow(function() { + headers.set("foo", "wat"); + }, TypeError, "Should not be able to set immutable headers"); + + shouldThrow(function() { + headers.delete("foo"); + }, TypeError, "Should not be able to delete immutable headers"); + + checkHas(headers, "foo", "Should be able to check immutable headers"); + ok(headers.get("foo"), "Should be able to get immutable headers"); + ok(headers.getAll("foo").length, "Should be able to get all immutable headers"); +} + +TestRequestHeaders(); +TestRequestNoCorsHeaders(); +TestResponseHeaders(); +TestImmutableHeaders(); +</script> +</body> +</html> +
deleted file mode 100644 --- a/dom/tests/mochitest/fetch/test_headers_mainthread.js +++ /dev/null @@ -1,141 +0,0 @@ -// Main thread specific tests because they need SpecialPowers. Expects -// test_headers_common.js to already be loaded. - -function TestRequestHeaders() { - is(typeof Headers, "function", "Headers global constructor exists."); - var headers = new Headers(); - ok(headers, "Constructed empty Headers object"); - SpecialPowers.wrap(headers).guard = "request"; - TestCoreBehavior(headers, "foo"); - var forbidden = [ - "Accept-Charset", - "Accept-Encoding", - "Access-Control-Request-Headers", - "Access-Control-Request-Method", - "Connection", - "Content-Length", - "Cookie", - "Cookie2", - "Date", - "DNT", - "Expect", - "Host", - "Keep-Alive", - "Origin", - "Referer", - "TE", - "Trailer", - "Transfer-Encoding", - "Upgrade", - "User-Agent", - "Via", - "Proxy-Authorization", - "Proxy-blarg", - "Proxy-", - "Sec-foo", - "Sec-" - ]; - - for (var i = 0, n = forbidden.length; i < n; ++i) { - var name = forbidden[i]; - headers.append(name, "hmm"); - checkNotHas(headers, name, "Should not be able to append " + name + " to request headers"); - headers.set(name, "hmm"); - checkNotHas(headers, name, "Should not be able to set " + name + " on request headers"); - } -} - -function TestRequestNoCorsHeaders() { - is(typeof Headers, "function", "Headers global constructor exists."); - var headers = new Headers(); - ok(headers, "Constructed empty Headers object"); - SpecialPowers.wrap(headers).guard = "request-no-cors"; - - headers.append("foo", "bar"); - checkNotHas(headers, "foo", "Should not be able to append arbitrary headers to request-no-cors headers."); - headers.set("foo", "bar"); - checkNotHas(headers, "foo", "Should not be able to set arbitrary headers on request-no-cors headers."); - - var simpleNames = [ - "Accept", - "Accept-Language", - "Content-Language" - ]; - - var simpleContentTypes = [ - "application/x-www-form-urlencoded", - "multipart/form-data", - "text/plain", - "application/x-www-form-urlencoded; charset=utf-8", - "multipart/form-data; charset=utf-8", - "text/plain; charset=utf-8" - ]; - - for (var i = 0, n = simpleNames.length; i < n; ++i) { - var name = simpleNames[i]; - headers.append(name, "hmm"); - checkHas(headers, name, "Should be able to append " + name + " to request-no-cors headers"); - headers.set(name, "hmm"); - checkHas(headers, name, "Should be able to set " + name + " on request-no-cors headers"); - } - - for (var i = 0, n = simpleContentTypes.length; i < n; ++i) { - var value = simpleContentTypes[i]; - headers.append("Content-Type", value); - checkHas(headers, "Content-Type", "Should be able to append " + value + " Content-Type to request-no-cors headers"); - headers.delete("Content-Type"); - headers.set("Content-Type", value); - checkHas(headers, "Content-Type", "Should be able to set " + value + " Content-Type on request-no-cors headers"); - } -} - -function TestResponseHeaders() { - is(typeof Headers, "function", "Headers global constructor exists."); - var headers = new Headers(); - ok(headers, "Constructed empty Headers object"); - SpecialPowers.wrap(headers).guard = "response"; - TestCoreBehavior(headers, "foo"); - var forbidden = [ - "Set-Cookie", - "Set-Cookie2" - ]; - - for (var i = 0, n = forbidden.length; i < n; ++i) { - var name = forbidden[i]; - headers.append(name, "hmm"); - checkNotHas(headers, name, "Should not be able to append " + name + " to response headers"); - headers.set(name, "hmm"); - checkNotHas(headers, name, "Should not be able to set " + name + " on response headers"); - } -} - -function TestImmutableHeaders() { - is(typeof Headers, "function", "Headers global constructor exists."); - var headers = new Headers(); - ok(headers, "Constructed empty Headers object"); - TestCoreBehavior(headers, "foo"); - headers.append("foo", "atleastone"); - - SpecialPowers.wrap(headers).guard = "immutable"; - - shouldThrow(function() { - headers.append("foo", "wat"); - }, TypeError, "Should not be able to append to immutable headers"); - - shouldThrow(function() { - headers.set("foo", "wat"); - }, TypeError, "Should not be able to set immutable headers"); - - shouldThrow(function() { - headers.delete("foo"); - }, TypeError, "Should not be able to delete immutable headers"); - - checkHas(headers, "foo", "Should be able to check immutable headers"); - ok(headers.get("foo"), "Should be able to get immutable headers"); - ok(headers.getAll("foo").length, "Should be able to get all immutable headers"); -} - -TestRequestHeaders(); -TestRequestNoCorsHeaders(); -TestResponseHeaders(); -TestImmutableHeaders();
rename from dom/workers/test/fetch/test_request.html rename to dom/tests/mochitest/fetch/test_request.html --- a/dom/workers/test/fetch/test_request.html +++ b/dom/tests/mochitest/fetch/test_request.html @@ -8,37 +8,15 @@ <title>Bug XXXXXX - Test Request object in worker</title> <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> </head> <body> <p id="display"></p> <div id="content" style="display: none"></div> <pre id="test"></pre> +<script type="text/javascript" src="fetch_test_framework.js"> </script> <script class="testbody" type="text/javascript"> - - function checkEnabled() { - var worker = new Worker("worker_test_request.js"); - worker.onmessage = function(event) { - - if (event.data.type == 'finish') { - SimpleTest.finish(); - } else if (event.data.type == 'status') { - ok(event.data.status, event.data.msg); - } - } - - worker.onerror = function(event) { - ok(false, "Worker had an error: " + event.message + " at " + event.lineno); - SimpleTest.finish(); - }; - - worker.postMessage(true); - } - - SimpleTest.waitForExplicitFinish(); - - checkEnabled(); +testScript("test_request.js"); </script> -</pre> </body> </html>
rename from dom/workers/test/fetch/worker_test_request.js rename to dom/tests/mochitest/fetch/test_request.js --- a/dom/workers/test/fetch/worker_test_request.js +++ b/dom/tests/mochitest/fetch/test_request.js @@ -1,16 +1,8 @@ -function ok(a, msg) { - postMessage({type: 'status', status: !!a, msg: a + ": " + msg }); -} - -function is(a, b, msg) { - postMessage({type: 'status', status: a === b, msg: a + " === " + b + ": " + msg }); -} - function testDefaultCtor() { var req = new Request(""); is(req.method, "GET", "Default Request method is GET"); ok(req.headers instanceof Headers, "Request should have non-null Headers object"); is(req.url, self.location.href, "URL should be resolved with entry settings object's API base URL"); is(req.context, "fetch", "Default context is fetch."); is(req.referrer, "about:client", "Default referrer is `client` which serializes to about:client."); is(req.mode, "cors", "Request mode for string input is cors"); @@ -266,18 +258,19 @@ function testBodyExtraction() { var text = "κόσμε"; var newReq = function() { return new Request("", { method: 'post', body: text }); } return newReq().text().then(function(v) { ok(typeof v === "string", "Should resolve to string"); is(text, v, "Extracted string should match"); }).then(function() { return newReq().blob().then(function(v) { ok(v instanceof Blob, "Should resolve to Blob"); - var fs = new FileReaderSync(); - is(fs.readAsText(v), text, "Decoded Blob should match original"); + return readAsText(v).then(function(result) { + is(result, text, "Decoded Blob should match original"); + }); }); }).then(function() { return newReq().json().then(function(v) { ok(false, "Invalid json should reject"); }, function(e) { ok(true, "Invalid json should reject"); }) }).then(function() { @@ -306,31 +299,24 @@ function testModeCorsPreflightEnumValue( invalidExc = e; } var expectedMessage = invalidExc.message.replace(invalidMode, 'cors-with-forced-preflight'); is(e.message, expectedMessage, "mode cors-with-forced-preflight should throw same error as invalid RequestMode strings."); } } -onmessage = function() { - var done = function() { postMessage({ type: 'finish' }) } - +function runTest() { testDefaultCtor(); testSimpleUrlParse(); testUrlFragment(); testMethod(); testBug1109574(); testModeCorsPreflightEnumValue(); - Promise.resolve() + return Promise.resolve() .then(testBodyCreation) .then(testBodyUsed) .then(testBodyExtraction) .then(testUsedRequest) .then(testClone()) // Put more promise based tests here. - .then(done) - .catch(function(e) { - ok(false, "Some Request tests failed " + e); - done(); - }) }
rename from dom/workers/test/fetch/test_response.html rename to dom/tests/mochitest/fetch/test_response.html --- a/dom/workers/test/fetch/test_response.html +++ b/dom/tests/mochitest/fetch/test_response.html @@ -8,37 +8,15 @@ <title>Bug 1039846 - Test Response object in worker</title> <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> </head> <body> <p id="display"></p> <div id="content" style="display: none"></div> <pre id="test"></pre> +<script type="text/javascript" src="fetch_test_framework.js"> </script> <script class="testbody" type="text/javascript"> - - function runTest() { - var worker = new Worker("worker_test_response.js"); - worker.onmessage = function(event) { - - if (event.data.type == 'finish') { - SimpleTest.finish(); - } else if (event.data.type == 'status') { - ok(event.data.status, event.data.msg); - } - } - - worker.onerror = function(event) { - ok(false, "Worker had an error: " + event.message + " at " + event.lineno); - SimpleTest.finish(); - }; - - worker.postMessage(true); - } - - SimpleTest.waitForExplicitFinish(); - - runTest(); +testScript("test_response.js"); </script> -</pre> </body> </html>
rename from dom/workers/test/fetch/worker_test_response.js rename to dom/tests/mochitest/fetch/test_response.js --- a/dom/workers/test/fetch/worker_test_response.js +++ b/dom/tests/mochitest/fetch/test_response.js @@ -1,18 +1,8 @@ -function ok(a, msg) { - dump("OK: " + !!a + " => " + a + " " + msg + "\n"); - postMessage({type: 'status', status: !!a, msg: a + ": " + msg }); -} - -function is(a, b, msg) { - dump("IS: " + (a===b) + " => " + a + " | " + b + " " + msg + "\n"); - postMessage({type: 'status', status: a === b, msg: a + " === " + b + ": " + msg }); -} - function testDefaultCtor() { var res = new Response(); is(res.type, "default", "Default Response type is default"); ok(res.headers instanceof Headers, "Response should have non-null Headers object"); is(res.url, "", "URL should be empty string"); is(res.status, 200, "Default status is 200"); is(res.statusText, "OK", "Default statusText is OK"); } @@ -207,47 +197,41 @@ function testBodyExtraction() { var text = "κόσμε"; var newRes = function() { return new Response(text); } return newRes().text().then(function(v) { ok(typeof v === "string", "Should resolve to string"); is(text, v, "Extracted string should match"); }).then(function() { return newRes().blob().then(function(v) { ok(v instanceof Blob, "Should resolve to Blob"); - var fs = new FileReaderSync(); - is(fs.readAsText(v), text, "Decoded Blob should match original"); + return readAsText(v).then(function(result) { + is(result, text, "Decoded Blob should match original"); + }); }); }).then(function() { return newRes().json().then(function(v) { ok(false, "Invalid json should reject"); }, function(e) { ok(true, "Invalid json should reject"); }) }).then(function() { return newRes().arrayBuffer().then(function(v) { ok(v instanceof ArrayBuffer, "Should resolve to ArrayBuffer"); var dec = new TextDecoder(); is(dec.decode(new Uint8Array(v)), text, "UTF-8 decoded ArrayBuffer should match original"); }); }) } -onmessage = function() { - var done = function() { postMessage({ type: 'finish' }) } - +function runTest() { testDefaultCtor(); testError(); testRedirect(); testOk(); testFinalURL(); - Promise.resolve() + return Promise.resolve() .then(testBodyCreation) .then(testBodyUsed) .then(testBodyExtraction) .then(testClone) // Put more promise based tests here. - .then(done) - .catch(function(e) { - ok(false, "Some Response tests failed " + e); - done(); - }) }
--- a/dom/tests/mochitest/fetch/worker_wrapper.js +++ b/dom/tests/mochitest/fetch/worker_wrapper.js @@ -1,24 +1,29 @@ function ok(a, msg) { - dump("OK: " + !!a + " => " + a + " " + msg + "\n"); postMessage({type: 'status', status: !!a, msg: a + ": " + msg }); } function is(a, b, msg) { - dump("IS: " + (a===b) + " => " + a + " | " + b + " " + msg + "\n"); postMessage({type: 'status', status: a === b, msg: a + " === " + b + ": " + msg }); } addEventListener('message', function workerWrapperOnMessage(e) { removeEventListener('message', workerWrapperOnMessage); var data = e.data; + + var done = function() { + postMessage({ type: 'finish' }); + } + try { importScripts(data.script); + // runTest() is provided by the test. + runTest().then(done, done); } catch(e) { postMessage({ type: 'status', status: false, msg: 'worker failed to import ' + data.script + "; error: " + e.message }); + done(); } - postMessage({ type: 'finish' }); });
--- a/dom/webidl/Client.webidl +++ b/dom/webidl/Client.webidl @@ -5,16 +5,17 @@ * * The origin of this IDL file is * http://slightlyoff.github.io/ServiceWorker/spec/service_worker/index.html * */ [Exposed=ServiceWorker] interface Client { + readonly attribute DOMString id; readonly attribute USVString url; [Throws] void postMessage(any message, optional sequence<Transferable> transfer); }; [Exposed=ServiceWorker] interface WindowClient : Client {
--- a/dom/webidl/MessageEvent.webidl +++ b/dom/webidl/MessageEvent.webidl @@ -26,19 +26,22 @@ interface MessageEvent : Event { /** * The last event ID string of the event source, for server-sent DOM events; this * value is the empty string for cross-origin messaging. */ readonly attribute DOMString lastEventId; /** - * The window or the port which originated this event. + * The window, port or client which originated this event. + * FIXME(catalinb): Update this when the spec changes are implemented. + * https://www.w3.org/Bugs/Public/show_bug.cgi?id=28199 + * https://bugzilla.mozilla.org/show_bug.cgi?id=1143717 */ - readonly attribute (WindowProxy or MessagePort)? source; + readonly attribute (WindowProxy or MessagePort or Client)? source; /** * Initializes this event with the given data, in a manner analogous to * the similarly-named method on the nsIDOMEvent interface, also setting the * data, origin, source, and lastEventId attributes of this appropriately. */ readonly attribute MessagePortList? ports; };
--- a/dom/webidl/ServiceWorkerContainer.webidl +++ b/dom/webidl/ServiceWorkerContainer.webidl @@ -16,17 +16,17 @@ interface ServiceWorkerContainer : Event // and discussion at https://etherpad.mozilla.org/serviceworker07apr [Unforgeable] readonly attribute ServiceWorker? controller; [Throws] readonly attribute Promise<ServiceWorkerRegistration> ready; [Throws] Promise<ServiceWorkerRegistration> register(USVString scriptURL, - optional RegistrationOptionList options); + optional RegistrationOptions options); [Throws] Promise<ServiceWorkerRegistration> getRegistration(optional USVString documentURL = ""); [Throws] Promise<sequence<ServiceWorkerRegistration>> getRegistrations(); attribute EventHandler oncontrollerchange; @@ -36,11 +36,11 @@ interface ServiceWorkerContainer : Event }; // Testing only. partial interface ServiceWorkerContainer { [Throws,Pref="dom.serviceWorkers.testing.enabled"] DOMString getScopeForUrl(DOMString url); }; -dictionary RegistrationOptionList { - USVString scope = "/"; +dictionary RegistrationOptions { + USVString scope; };
--- a/dom/workers/ServiceWorker.cpp +++ b/dom/workers/ServiceWorker.cpp @@ -1,16 +1,17 @@ /* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */ /* 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 "ServiceWorker.h" #include "nsPIDOMWindow.h" +#include "ServiceWorkerClient.h" #include "ServiceWorkerManager.h" #include "SharedWorker.h" #include "WorkerPrivate.h" #include "mozilla/Preferences.h" #include "mozilla/dom/Promise.h" #include "mozilla/dom/ServiceWorkerGlobalScopeBinding.h" @@ -90,17 +91,22 @@ ServiceWorker::PostMessage(JSContext* aC WorkerPrivate* workerPrivate = GetWorkerPrivate(); MOZ_ASSERT(workerPrivate); if (State() == ServiceWorkerState::Redundant) { aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); return; } - workerPrivate->PostMessage(aCx, aMessage, aTransferable, aRv); + nsCOMPtr<nsPIDOMWindow> window = do_QueryInterface(GetParentObject()); + nsCOMPtr<nsIDocument> doc = window->GetExtantDoc(); + nsAutoPtr<ServiceWorkerClientInfo> clientInfo(new ServiceWorkerClientInfo(doc)); + + workerPrivate->PostMessageToServiceWorker(aCx, aMessage, aTransferable, + clientInfo, aRv); } WorkerPrivate* ServiceWorker::GetWorkerPrivate() const { // At some point in the future, this may be optimized to terminate a worker // that hasn't been used in a certain amount of time or when there is memory // pressure or similar.
--- a/dom/workers/ServiceWorkerClient.cpp +++ b/dom/workers/ServiceWorkerClient.cpp @@ -26,18 +26,23 @@ NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION( NS_INTERFACE_MAP_ENTRY(nsISupports) NS_INTERFACE_MAP_END ServiceWorkerClientInfo::ServiceWorkerClientInfo(nsIDocument* aDoc) { MOZ_ASSERT(aDoc); MOZ_ASSERT(aDoc->GetWindow()); + nsresult rv = aDoc->GetId(mClientId); + if (NS_FAILED(rv)) { + NS_WARNING("Failed to get the UUID of the document."); + } + nsRefPtr<nsGlobalWindow> outerWindow = static_cast<nsGlobalWindow*>(aDoc->GetWindow()); - mClientId = outerWindow->WindowID(); + mWindowId = outerWindow->WindowID(); aDoc->GetURL(mUrl); mVisibilityState = aDoc->VisibilityState(); ErrorResult result; mFocused = aDoc->HasFocus(result); if (result.Failed()) { NS_WARNING("Failed to get focus information."); } @@ -56,35 +61,35 @@ ServiceWorkerClient::WrapObject(JSContex { return ClientBinding::Wrap(aCx, this); } namespace { class ServiceWorkerClientPostMessageRunnable MOZ_FINAL : public nsRunnable { - uint64_t mId; + uint64_t mWindowId; JSAutoStructuredCloneBuffer mBuffer; nsTArray<nsCOMPtr<nsISupports>> mClonedObjects; public: - ServiceWorkerClientPostMessageRunnable(uint64_t aId, + ServiceWorkerClientPostMessageRunnable(uint64_t aWindowId, JSAutoStructuredCloneBuffer&& aData, nsTArray<nsCOMPtr<nsISupports>>& aClonedObjects) - : mId(aId), + : mWindowId(aWindowId), mBuffer(Move(aData)) { mClonedObjects.SwapElements(aClonedObjects); } NS_IMETHOD Run() { AssertIsOnMainThread(); - nsGlobalWindow* window = nsGlobalWindow::GetOuterWindowWithId(mId); + nsGlobalWindow* window = nsGlobalWindow::GetOuterWindowWithId(mWindowId); if (!window) { return NS_ERROR_FAILURE; } ErrorResult result; dom::Navigator* navigator = window->GetNavigator(result); if (NS_WARN_IF(result.Failed())) { return result.ErrorCode(); @@ -177,15 +182,15 @@ ServiceWorkerClient::PostMessage(JSConte JSAutoStructuredCloneBuffer buffer; if (!buffer.write(aCx, aMessage, transferable, callbacks, &clonedObjects)) { aRv.Throw(NS_ERROR_DOM_DATA_CLONE_ERR); return; } nsRefPtr<ServiceWorkerClientPostMessageRunnable> runnable = - new ServiceWorkerClientPostMessageRunnable(mId, Move(buffer), clonedObjects); + new ServiceWorkerClientPostMessageRunnable(mWindowId, Move(buffer), clonedObjects); nsresult rv = NS_DispatchToMainThread(runnable); if (NS_FAILED(rv)) { aRv.Throw(NS_ERROR_FAILURE); } }
--- a/dom/workers/ServiceWorkerClient.h +++ b/dom/workers/ServiceWorkerClient.h @@ -26,17 +26,18 @@ class ServiceWorkerClientInfo MOZ_FINAL { friend class ServiceWorkerClient; friend class ServiceWorkerWindowClient; public: explicit ServiceWorkerClientInfo(nsIDocument* aDoc); private: - uint64_t mClientId; + nsString mClientId; + uint64_t mWindowId; nsString mUrl; // Window Clients VisibilityState mVisibilityState; bool mFocused; FrameType mFrameType; }; @@ -46,27 +47,33 @@ class ServiceWorkerClient : public nsISu public: NS_DECL_CYCLE_COLLECTING_ISUPPORTS NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(ServiceWorkerClient) ServiceWorkerClient(nsISupports* aOwner, const ServiceWorkerClientInfo& aClientInfo) : mOwner(aOwner), mId(aClientInfo.mClientId), + mWindowId(aClientInfo.mWindowId), mUrl(aClientInfo.mUrl) { MOZ_ASSERT(aOwner); } nsISupports* GetParentObject() const { return mOwner; } + void GetId(nsString& aRetval) const + { + aRetval = mId; + } + void GetUrl(nsAString& aUrl) const { aUrl.Assign(mUrl); } void PostMessage(JSContext* aCx, JS::Handle<JS::Value> aMessage, @@ -76,17 +83,18 @@ public: JSObject* WrapObject(JSContext* aCx) MOZ_OVERRIDE; protected: virtual ~ServiceWorkerClient() { } private: nsCOMPtr<nsISupports> mOwner; - uint64_t mId; + nsString mId; + uint64_t mWindowId; nsString mUrl; }; } // namespace workers } // namespace dom } // namespace mozilla #endif // mozilla_dom_workers_serviceworkerclient_h
--- a/dom/workers/ServiceWorkerContainer.cpp +++ b/dom/workers/ServiceWorkerContainer.cpp @@ -68,28 +68,59 @@ ServiceWorkerContainer::RemoveReadyPromi JSObject* ServiceWorkerContainer::WrapObject(JSContext* aCx) { return ServiceWorkerContainerBinding::Wrap(aCx, this); } already_AddRefed<Promise> ServiceWorkerContainer::Register(const nsAString& aScriptURL, - const RegistrationOptionList& aOptions, + const RegistrationOptions& aOptions, ErrorResult& aRv) { nsCOMPtr<nsISupports> promise; nsCOMPtr<nsIServiceWorkerManager> swm = mozilla::services::GetServiceWorkerManager(); if (!swm) { aRv.Throw(NS_ERROR_FAILURE); return nullptr; } - aRv = swm->Register(GetOwner(), aOptions.mScope, aScriptURL, getter_AddRefs(promise)); + nsCOMPtr<nsPIDOMWindow> window = GetOwner(); + MOZ_ASSERT(window); + + nsresult rv; + nsCOMPtr<nsIURI> scriptURI; + rv = NS_NewURI(getter_AddRefs(scriptURI), aScriptURL, nullptr, + window->GetDocBaseURI()); + if (NS_WARN_IF(NS_FAILED(rv))) { + aRv.ThrowTypeError(MSG_INVALID_URL, &aScriptURL); + return nullptr; + } + + // In ServiceWorkerContainer.register() the scope argument is parsed against + // different base URLs depending on whether it was passed or not. + nsCOMPtr<nsIURI> scopeURI; + + // Step 4. If none passed, parse against script's URL + if (!aOptions.mScope.WasPassed()) { + nsresult rv = NS_NewURI(getter_AddRefs(scopeURI), NS_LITERAL_CSTRING("./"), + nullptr, scriptURI); + MOZ_ALWAYS_TRUE(NS_SUCCEEDED(rv)); + } else { + // Step 5. Parse against entry settings object's base URL. + nsresult rv = NS_NewURI(getter_AddRefs(scopeURI), aOptions.mScope.Value(), + nullptr, window->GetDocBaseURI()); + if (NS_WARN_IF(NS_FAILED(rv))) { + aRv.ThrowTypeError(MSG_INVALID_URL, &aOptions.mScope.Value()); + return nullptr; + } + } + + aRv = swm->Register(window, scopeURI, scriptURI, getter_AddRefs(promise)); if (aRv.Failed()) { return nullptr; } nsRefPtr<Promise> ret = static_cast<Promise*>(promise.get()); MOZ_ASSERT(ret); return ret.forget(); }
--- a/dom/workers/ServiceWorkerContainer.h +++ b/dom/workers/ServiceWorkerContainer.h @@ -10,17 +10,17 @@ #include "mozilla/DOMEventTargetHelper.h" class nsPIDOMWindow; namespace mozilla { namespace dom { class Promise; -struct RegistrationOptionList; +struct RegistrationOptions; namespace workers { class ServiceWorker; } // Lightweight serviceWorker APIs collection. class ServiceWorkerContainer MOZ_FINAL : public DOMEventTargetHelper { @@ -35,17 +35,17 @@ public: explicit ServiceWorkerContainer(nsPIDOMWindow* aWindow); virtual JSObject* WrapObject(JSContext* aCx) MOZ_OVERRIDE; already_AddRefed<Promise> Register(const nsAString& aScriptURL, - const RegistrationOptionList& aOptions, + const RegistrationOptions& aOptions, ErrorResult& aRv); already_AddRefed<workers::ServiceWorker> GetController(); already_AddRefed<Promise> GetRegistration(const nsAString& aDocumentURL, ErrorResult& aRv);
--- a/dom/workers/ServiceWorkerManager.cpp +++ b/dom/workers/ServiceWorkerManager.cpp @@ -437,16 +437,48 @@ public: NS_WARNING("Failed to dispatch ContinueUpdateRunnable to main thread."); } } return true; } }; +namespace { +nsresult +GetRequiredScopeStringPrefix(const nsACString& aScriptSpec, nsACString& aPrefix) +{ + nsCOMPtr<nsIURI> scriptURI; + nsresult rv = NS_NewURI(getter_AddRefs(scriptURI), aScriptSpec, + nullptr, nullptr); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + rv = scriptURI->GetPrePath(aPrefix); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + nsCOMPtr<nsIURL> scriptURL(do_QueryInterface(scriptURI)); + if (NS_WARN_IF(!scriptURL)) { + return rv; + } + + nsAutoCString dir; + rv = scriptURL->GetDirectory(dir); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + aPrefix.Append(dir); + return NS_OK; +} +} // anonymous namespace + class ServiceWorkerRegisterJob MOZ_FINAL : public ServiceWorkerJob, public nsIStreamLoaderObserver { friend class ContinueInstallTask; nsCString mScope; nsCString mScriptSpec; nsRefPtr<ServiceWorkerRegistrationInfo> mRegistration; @@ -558,16 +590,30 @@ public: } // FIXME(nsm): "Extract mime type..." // FIXME(nsm): Byte match to aString. NS_WARNING("Byte wise check is disabled, just using new one"); nsRefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance(); + // FIXME: Bug 1130101 - Read max scope from Service-Worker-Allowed header. + nsAutoCString allowedPrefix; + rv = GetRequiredScopeStringPrefix(mRegistration->mScriptSpec, allowedPrefix); + if (NS_WARN_IF(NS_FAILED(rv))) { + Fail(NS_ERROR_DOM_SECURITY_ERR); + return rv; + } + + if (!StringBeginsWith(mRegistration->mScope, allowedPrefix)) { + NS_WARNING("By default a service worker's scope is restricted to at or below it's script's location."); + Fail(NS_ERROR_DOM_SECURITY_ERR); + return NS_ERROR_DOM_SECURITY_ERR; + } + // We have to create a ServiceWorker here simply to ensure there are no // errors. Ideally we should just pass this worker on to ContinueInstall. MOZ_ASSERT(!swm->mSetOfScopesBeingUpdated.Contains(mRegistration->mScope)); swm->mSetOfScopesBeingUpdated.Put(mRegistration->mScope, true); nsRefPtr<ServiceWorkerInfo> dummyInfo = new ServiceWorkerInfo(mRegistration, mRegistration->mScriptSpec); nsRefPtr<ServiceWorker> serviceWorker; rv = swm->CreateServiceWorker(mRegistration->mPrincipal, @@ -587,17 +633,17 @@ public: nsRefPtr<CheckWorkerEvaluationAndContinueUpdateWorkerRunnable> r = new CheckWorkerEvaluationAndContinueUpdateWorkerRunnable(serviceWorker->GetWorkerPrivate(), handle); AutoJSAPI jsapi; jsapi.Init(); bool ok = r->Dispatch(jsapi.cx()); if (NS_WARN_IF(!ok)) { swm->mSetOfScopesBeingUpdated.Remove(mRegistration->mScope); Fail(NS_ERROR_DOM_ABORT_ERR); - return rv; + return NS_ERROR_FAILURE; } return NS_OK; } // Public so our error handling code can use it. void Fail(const ErrorEventInit& aError) @@ -808,18 +854,18 @@ ContinueInstallTask::ContinueAfterWorker // queue, which captures the "atomic" behaviour we want. mJob->ContinueAfterInstallEvent(aSuccess, aActivateImmediately); } // If we return an error code here, the ServiceWorkerContainer will // automatically reject the Promise. NS_IMETHODIMP ServiceWorkerManager::Register(nsIDOMWindow* aWindow, - const nsAString& aScope, - const nsAString& aScriptURL, + nsIURI* aScopeURI, + nsIURI* aScriptURI, nsISupports** aPromise) { AssertIsOnMainThread(); // XXXnsm Don't allow chrome callers for now, we don't support chrome // ServiceWorkers. MOZ_ASSERT(!nsContentUtils::IsCallerChrome()); @@ -879,51 +925,39 @@ ServiceWorkerManager::Register(nsIDOMWin rv = documentURI->SchemeIs("https", &isHttps); if (NS_WARN_IF(NS_FAILED(rv)) || !isHttps) { NS_WARNING("ServiceWorker registration from insecure websites is not allowed."); return NS_ERROR_DOM_SECURITY_ERR; } } } - nsCOMPtr<nsIURI> scriptURI; - rv = NS_NewURI(getter_AddRefs(scriptURI), aScriptURL, nullptr, documentURI); - if (NS_WARN_IF(NS_FAILED(rv))) { - return rv; - } - // Data URLs are not allowed. nsCOMPtr<nsIPrincipal> documentPrincipal = doc->NodePrincipal(); - rv = documentPrincipal->CheckMayLoad(scriptURI, true /* report */, + rv = documentPrincipal->CheckMayLoad(aScriptURI, true /* report */, false /* allowIfInheritsPrincipal */); if (NS_WARN_IF(NS_FAILED(rv))) { return NS_ERROR_DOM_SECURITY_ERR; } - nsCOMPtr<nsIURI> scopeURI; - rv = NS_NewURI(getter_AddRefs(scopeURI), aScope, nullptr, documentURI); - if (NS_WARN_IF(NS_FAILED(rv))) { - return NS_ERROR_DOM_SECURITY_ERR; - } - - rv = documentPrincipal->CheckMayLoad(scopeURI, true /* report */, + rv = documentPrincipal->CheckMayLoad(aScopeURI, true /* report */, false /* allowIfInheritsPrinciple */); if (NS_WARN_IF(NS_FAILED(rv))) { return NS_ERROR_DOM_SECURITY_ERR; } nsCString cleanedScope; - rv = scopeURI->GetSpecIgnoringRef(cleanedScope); + rv = aScopeURI->GetSpecIgnoringRef(cleanedScope); if (NS_WARN_IF(NS_FAILED(rv))) { return NS_ERROR_FAILURE; } nsAutoCString spec; - rv = scriptURI->GetSpec(spec); + rv = aScriptURI->GetSpec(spec); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } nsCOMPtr<nsIGlobalObject> sgo = do_QueryInterface(window); ErrorResult result; nsRefPtr<Promise> promise = Promise::Create(sgo, result); if (result.Failed()) {
--- a/dom/workers/WorkerPrivate.cpp +++ b/dom/workers/WorkerPrivate.cpp @@ -980,30 +980,39 @@ private: class MessageEventRunnable MOZ_FINAL : public WorkerRunnable { JSAutoStructuredCloneBuffer mBuffer; nsTArray<nsCOMPtr<nsISupports> > mClonedObjects; uint64_t mMessagePortSerial; bool mToMessagePort; + // This is only used for messages dispatched to a service worker. + nsAutoPtr<ServiceWorkerClientInfo> mEventSource; + public: MessageEventRunnable(WorkerPrivate* aWorkerPrivate, TargetAndBusyBehavior aBehavior, JSAutoStructuredCloneBuffer&& aData, nsTArray<nsCOMPtr<nsISupports> >& aClonedObjects, bool aToMessagePort, uint64_t aMessagePortSerial) : WorkerRunnable(aWorkerPrivate, aBehavior) , mBuffer(Move(aData)) , mMessagePortSerial(aMessagePortSerial) , mToMessagePort(aToMessagePort) { mClonedObjects.SwapElements(aClonedObjects); } + void + SetMessageSource(ServiceWorkerClientInfo* aSource) + { + mEventSource = aSource; + } + bool DispatchDOMEvent(JSContext* aCx, WorkerPrivate* aWorkerPrivate, DOMEventTargetHelper* aTarget, bool aIsMainThread) { // Release reference to objects that were AddRef'd for // cloning into worker when array goes out of scope. nsTArray<nsCOMPtr<nsISupports>> clonedObjects; clonedObjects.SwapElements(mClonedObjects); @@ -1019,16 +1028,22 @@ public: nsresult rv = event->InitMessageEvent(NS_LITERAL_STRING("message"), false /* non-bubbling */, true /* cancelable */, messageData, EmptyString(), EmptyString(), nullptr); + if (mEventSource) { + nsRefPtr<ServiceWorkerClient> client = + new ServiceWorkerWindowClient(aTarget, *mEventSource); + event->SetSource(client); + } + if (NS_FAILED(rv)) { xpc::Throw(aCx, rv); return false; } event->SetTrusted(true); nsCOMPtr<nsIDOMEvent> domEvent = do_QueryObject(event); @@ -3084,19 +3099,20 @@ WorkerPrivateParent<Derived>::ForgetMain mMainThreadObjectsForgotten = true; } template <class Derived> void WorkerPrivateParent<Derived>::PostMessageInternal( JSContext* aCx, JS::Handle<JS::Value> aMessage, - const Optional<Sequence<JS::Value> >& aTransferable, + const Optional<Sequence<JS::Value>>& aTransferable, bool aToMessagePort, uint64_t aMessagePortSerial, + ServiceWorkerClientInfo* aClientInfo, ErrorResult& aRv) { AssertIsOnParentThread(); { MutexAutoLock lock(mMutex); if (mParentStatus > Running) { return; @@ -3150,34 +3166,49 @@ WorkerPrivateParent<Derived>::PostMessag return; } nsRefPtr<MessageEventRunnable> runnable = new MessageEventRunnable(ParentAsWorkerPrivate(), WorkerRunnable::WorkerThreadModifyBusyCount, Move(buffer), clonedObjects, aToMessagePort, aMessagePortSerial); + runnable->SetMessageSource(aClientInfo); + if (!runnable->Dispatch(aCx)) { aRv.Throw(NS_ERROR_FAILURE); } } template <class Derived> void +WorkerPrivateParent<Derived>::PostMessageToServiceWorker( + JSContext* aCx, JS::Handle<JS::Value> aMessage, + const Optional<Sequence<JS::Value>>& aTransferable, + nsAutoPtr<ServiceWorkerClientInfo>& aClientInfo, + ErrorResult& aRv) +{ + AssertIsOnMainThread(); + PostMessageInternal(aCx, aMessage, aTransferable, false, 0, + aClientInfo.forget(), aRv); +} + +template <class Derived> +void WorkerPrivateParent<Derived>::PostMessageToMessagePort( JSContext* aCx, uint64_t aMessagePortSerial, JS::Handle<JS::Value> aMessage, const Optional<Sequence<JS::Value>>& aTransferable, ErrorResult& aRv) { AssertIsOnMainThread(); PostMessageInternal(aCx, aMessage, aTransferable, true, aMessagePortSerial, - aRv); + nullptr, aRv); } template <class Derived> bool WorkerPrivateParent<Derived>::DispatchMessageEventToMessagePort( JSContext* aCx, uint64_t aMessagePortSerial, JSAutoStructuredCloneBuffer&& aBuffer, nsTArray<nsCOMPtr<nsISupports>>& aClonedObjects)
--- a/dom/workers/WorkerPrivate.h +++ b/dom/workers/WorkerPrivate.h @@ -56,16 +56,17 @@ class PrincipalInfo; struct PRThread; BEGIN_WORKERS_NAMESPACE class AutoSyncLoopHolder; class MessagePort; class SharedWorker; +class ServiceWorkerClientInfo; class WorkerControlRunnable; class WorkerDebugger; class WorkerDebuggerGlobalScope; class WorkerGlobalScope; class WorkerPrivate; class WorkerRunnable; class WorkerThread; @@ -216,18 +217,19 @@ private: bool TerminatePrivate(JSContext* aCx) { return NotifyPrivate(aCx, Terminating); } void PostMessageInternal(JSContext* aCx, JS::Handle<JS::Value> aMessage, - const Optional<Sequence<JS::Value> >& aTransferable, + const Optional<Sequence<JS::Value>>& aTransferable, bool aToMessagePort, uint64_t aMessagePortSerial, + ServiceWorkerClientInfo* aClientInfo, ErrorResult& aRv); nsresult DispatchPrivate(WorkerRunnable* aRunnable, nsIEventTarget* aSyncLoopTarget); public: virtual JSObject* WrapObject(JSContext* aCx) MOZ_OVERRIDE; @@ -322,20 +324,26 @@ public: void ForgetMainThreadObjects(nsTArray<nsCOMPtr<nsISupports> >& aDoomed); void PostMessage(JSContext* aCx, JS::Handle<JS::Value> aMessage, const Optional<Sequence<JS::Value> >& aTransferable, ErrorResult& aRv) { - PostMessageInternal(aCx, aMessage, aTransferable, false, 0, aRv); + PostMessageInternal(aCx, aMessage, aTransferable, false, 0, nullptr, aRv); } void + PostMessageToServiceWorker(JSContext* aCx, JS::Handle<JS::Value> aMessage, + const Optional<Sequence<JS::Value>>& aTransferable, + nsAutoPtr<ServiceWorkerClientInfo>& aClientInfo, + ErrorResult& aRv); + + void PostMessageToMessagePort(JSContext* aCx, uint64_t aMessagePortSerial, JS::Handle<JS::Value> aMessage, const Optional<Sequence<JS::Value> >& aTransferable, ErrorResult& aRv); bool DispatchMessageEventToMessagePort(
--- a/dom/workers/moz.build +++ b/dom/workers/moz.build @@ -103,17 +103,16 @@ include('/ipc/chromium/chromium-config.m FINAL_LIBRARY = 'xul' TEST_DIRS += [ 'test/extensions/bootstrap', 'test/extensions/traditional', ] MOCHITEST_MANIFESTS += [ - 'test/fetch/mochitest.ini', 'test/mochitest.ini', 'test/serviceworkers/mochitest.ini', ] MOCHITEST_CHROME_MANIFESTS += ['test/chrome.ini'] XPCSHELL_TESTS_MANIFESTS += ['test/xpcshell/xpcshell.ini']
deleted file mode 100644 --- a/dom/workers/test/fetch/mochitest.ini +++ /dev/null @@ -1,9 +0,0 @@ -[DEFAULT] -support-files = - worker_interfaces.js - worker_test_request.js - worker_test_response.js - -[test_interfaces.html] -[test_request.html] -[test_response.html]
deleted file mode 100644 --- a/dom/workers/test/fetch/test_interfaces.html +++ /dev/null @@ -1,44 +0,0 @@ -<!-- - Any copyright is dedicated to the Public Domain. - http://creativecommons.org/publicdomain/zero/1.0/ ---> -<!DOCTYPE HTML> -<html> -<head> - <title>Bug 1017613 - Test fetch API interfaces</title> - <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> - <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> -</head> -<body> -<p id="display"></p> -<div id="content" style="display: none"></div> -<pre id="test"></pre> -<script class="testbody" type="text/javascript"> - - function checkEnabled() { - var worker = new Worker("worker_interfaces.js"); - worker.onmessage = function(event) { - - if (event.data.type == 'finish') { - SimpleTest.finish(); - } else if (event.data.type == 'status') { - ok(event.data.status, event.data.msg); - } - } - - worker.onerror = function(event) { - ok(false, "Worker had an error: " + event.data); - SimpleTest.finish(); - }; - - worker.postMessage(true); - } - - SimpleTest.waitForExplicitFinish(); - - checkEnabled(); -</script> -</pre> -</body> -</html> -
deleted file mode 100644 --- a/dom/workers/test/fetch/worker_interfaces.js +++ /dev/null @@ -1,12 +0,0 @@ -function ok(a, msg) { - dump("OK: " + !!a + " => " + a + " " + msg + "\n"); - postMessage({type: 'status', status: !!a, msg: a + ": " + msg }); -} - -onmessage = function() { - ok(typeof Headers === "function", "Headers should be defined"); - ok(typeof Request === "function", "Request should be defined"); - ok(typeof Response === "function", "Response should be defined"); - ok(typeof fetch === "function", "fetch() should be defined"); - postMessage({ type: 'finish' }); -}
new file mode 100644 --- /dev/null +++ b/dom/workers/test/serviceworkers/match_all_client/match_all_client_id.html @@ -0,0 +1,31 @@ +<!-- + Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ +--> +<!DOCTYPE HTML> +<html> +<head> + <title>Bug 1139425 - controlled page</title> +<script class="testbody" type="text/javascript"> + var testWindow = parent; + if (opener) { + testWindow = opener; + } + + window.onload = function() { + navigator.serviceWorker.ready.then(function(swr) { + swr.active.postMessage("Start"); + }); + } + + navigator.serviceWorker.onmessage = function(msg) { + // worker message; + testWindow.postMessage(msg.data, "*"); + window.close(); + }; +</script> + +</head> +<body> +</body> +</html>
new file mode 100644 --- /dev/null +++ b/dom/workers/test/serviceworkers/match_all_client_id_worker.js @@ -0,0 +1,28 @@ +onmessage = function(e) { + dump("MatchAllClientIdWorker:" + e.data + "\n"); + var id = []; + var iterations = 5; + var counter = 0; + + for (var i = 0; i < iterations; i++) { + self.clients.matchAll().then(function(res) { + if (!res.length) { + dump("ERROR: no clients are currently controlled.\n"); + } + + client = res[0]; + id[counter] = client.id; + counter++; + if (counter >= iterations) { + var response = true; + for (var index = 1; index < iterations; index++) { + if (id[0] != id[index]) { + response = false; + break; + } + } + client.postMessage(response); + } + }); + } +}
--- a/dom/workers/test/serviceworkers/match_all_clients/match_all_controlled.html +++ b/dom/workers/test/serviceworkers/match_all_clients/match_all_controlled.html @@ -2,16 +2,17 @@ Any copyright is dedicated to the Public Domain. http://creativecommons.org/publicdomain/zero/1.0/ --> <!DOCTYPE HTML> <html> <head> <title>Bug 1058311 - controlled page</title> <script class="testbody" type="text/javascript"> + var re = /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/; var frameType = "none"; var testWindow = parent; if (parent != window) { frameType = "nested"; } else if (opener) { frameType = "auxiliary"; testWindow = opener; @@ -33,16 +34,19 @@ message: msg }; testWindow.postMessage(response, "*"); } navigator.serviceWorker.onmessage = function(msg) { // worker message; + result = re.test(msg.data.id); + postResult(result, "Client id test"); + result = msg.data.url == window.location; postResult(result, "Client url test"); result = msg.data.visibilityState === document.visibilityState; postResult(result, "Client visibility test. expected=" +document.visibilityState); result = msg.data.focused === document.hasFocus(); postResult(result, "Client focus test. expected=" + document.hasFocus());
--- a/dom/workers/test/serviceworkers/match_all_properties_worker.js +++ b/dom/workers/test/serviceworkers/match_all_properties_worker.js @@ -3,16 +3,17 @@ onmessage = function(e) { self.clients.matchAll().then(function(res) { if (!res.length) { dump("ERROR: no clients are currently controlled.\n"); } for (i = 0; i < res.length; i++) { client = res[i]; response = { + id: client.id, url: client.url, visibilityState: client.visibilityState, focused: client.focused, frameType: client.frameType }; client.postMessage(response); } });
--- a/dom/workers/test/serviceworkers/mochitest.ini +++ b/dom/workers/test/serviceworkers/mochitest.ini @@ -34,27 +34,33 @@ support-files = fetch/https/clonedresponse/https_test.js match_all_properties_worker.js match_all_clients/match_all_controlled.html test_serviceworker_interfaces.js serviceworker_wrapper.js message_receiver.html close_test.js serviceworker_not_sharedworker.js + match_all_client/match_all_client_id.html + match_all_client_id_worker.js + source_message_posting_worker.js + scope/scope_worker.js [test_unregister.html] [test_installation_simple.html] [test_fetch_event.html] [test_https_fetch.html] [test_https_fetch_cloned_response.html] [test_match_all.html] [test_install_event.html] [test_navigator.html] [test_scopes.html] [test_controller.html] [test_workerUpdate.html] [test_workerUnregister.html] [test_post_message.html] [test_post_message_advanced.html] +[test_post_message_source.html] [test_match_all_client_properties.html] [test_close.html] [test_serviceworker_interfaces.html] [test_serviceworker_not_sharedworker.html] +[test_match_all_client_id.html]
new file mode 100644 --- /dev/null +++ b/dom/workers/test/serviceworkers/scope/scope_worker.js @@ -0,0 +1,2 @@ +// This worker is used to test if calling register() without a scope argument +// leads to scope being relative to service worker script.
new file mode 100644 --- /dev/null +++ b/dom/workers/test/serviceworkers/source_message_posting_worker.js @@ -0,0 +1,12 @@ +onmessage = function(e) { + if (!e.source) { + dump("ERROR: message doesn't have a source."); + } + + // The client should be a window client + if (e.source instanceof WindowClient) { + e.source.postMessage(e.data); + } else { + e.source.postMessage("ERROR. source is not a window client."); + } +};
new file mode 100644 --- /dev/null +++ b/dom/workers/test/serviceworkers/test_match_all_client_id.html @@ -0,0 +1,87 @@ +<!-- + Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ +--> +<!DOCTYPE HTML> +<html> +<head> + <title>Bug 1058311 - Test matchAll client id </title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<p id="display"></p> +<div id="content" style="display: none"></div> +<pre id="test"></pre> +<script class="testbody" type="text/javascript"> + var registration; + var clientURL = "match_all_client/match_all_client_id.html"; + function start() { + return navigator.serviceWorker.register("match_all_client_id_worker.js", + { scope: "./match_all_client/" }) + .then((swr) => registration = swr); + } + + function unregister() { + return registration.unregister().then(function(result) { + ok(result, "Unregister should return true."); + }); + } + + function getMessageListener() { + return new Promise(function(res, rej) { + window.onmessage = function(e) { + ok(e.data, "Same client id for multiple calls."); + + if (!e.data) { + rej(); + return; + } + + info("DONE from: " + e.source); + res(); + } + }); + } + + function testNestedWindow() { + var p = getMessageListener(); + + var content = document.getElementById("content"); + ok(content, "Parent exists."); + + iframe = document.createElement("iframe"); + + content.appendChild(iframe); + iframe.setAttribute('src', clientURL); + + return p.then(() => content.removeChild(iframe)); + } + + function testAuxiliaryWindow() { + var p = getMessageListener(); + var w = window.open(clientURL); + + return p.then(() => w.close()); + } + + function runTest() { + info(window.opener == undefined); + start() + .then(testAuxiliaryWindow) + .then(testNestedWindow) + .catch(function(e) { + ok(false, "Some test failed with error " + e); + }).then(SimpleTest.finish); + } + + SimpleTest.waitForExplicitFinish(); + SpecialPowers.pushPrefEnv({"set": [ + ["dom.serviceWorkers.exemptFromPerDomainMax", true], + ["dom.serviceWorkers.enabled", true], + ["dom.serviceWorkers.testing.enabled", true] + ]}, runTest); +</script> +</pre> +</body> +</html>
--- a/dom/workers/test/serviceworkers/test_match_all_client_properties.html +++ b/dom/workers/test/serviceworkers/test_match_all_client_properties.html @@ -79,15 +79,16 @@ .then(testNestedWindow) .catch(function(e) { ok(false, "Some test failed with error " + e); }).then(SimpleTest.finish); } SimpleTest.waitForExplicitFinish(); SpecialPowers.pushPrefEnv({"set": [ + ["dom.serviceWorkers.exemptFromPerDomainMax", true], ["dom.serviceWorkers.enabled", true], ["dom.serviceWorkers.testing.enabled", true] ]}, runTest); </script> </pre> </body> </html>
new file mode 100644 --- /dev/null +++ b/dom/workers/test/serviceworkers/test_post_message_source.html @@ -0,0 +1,65 @@ +<!-- + Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ +--> +<!DOCTYPE HTML> +<html> +<head> + <title>Bug 1142015 - Test service worker post message source </title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<p id="display"></p> +<div id="content" style="display: none"></div> +<pre id="test"></pre> +<script class="testbody" type="text/javascript"> + var magic_value = "MAGIC_VALUE_RANDOM"; + var registration; + function start() { + return navigator.serviceWorker.register("source_message_posting_worker.js", + { scope: "./nonexistent_scope/" }) + .then((swr) => registration = swr); + } + + function unregister() { + return registration.unregister().then(function(result) { + ok(result, "Unregister should return true."); + }); + } + + + function testPostMessage(swr) { + var p = new Promise(function(res, rej) { + navigator.serviceWorker.onmessage = function(e) { + ok(e.data === magic_value, "Worker posted the correct value."); + res(); + } + }); + + ok(swr.installing, "Installing worker exists."); + swr.installing.postMessage(magic_value); + return p; + } + + + function runTest() { + start() + .then(testPostMessage) + .then(unregister) + .catch(function(e) { + ok(false, "Some test failed with error " + e); + }).then(SimpleTest.finish); + } + + SimpleTest.waitForExplicitFinish(); + SpecialPowers.pushPrefEnv({"set": [ + ["dom.serviceWorkers.exemptFromPerDomainMax", true], + ["dom.serviceWorkers.enabled", true], + ["dom.serviceWorkers.testing.enabled", true] + ]}, runTest); +</script> +</pre> +</body> +</html> +
--- a/dom/workers/test/serviceworkers/test_scopes.html +++ b/dom/workers/test/serviceworkers/test_scopes.html @@ -24,27 +24,48 @@ [ "worker.js", "./star*" ], // '*' has no special meaning ]; function registerWorkers() { var registerArray = []; scriptsAndScopes.forEach(function(item) { registerArray.push(navigator.serviceWorker.register(item[0], { scope: item[1] })); }); + + // Check register()'s step 4 which uses script's url with "./" as the scope if no scope is passed. + // The other tests already check step 5. + registerArray.push(navigator.serviceWorker.register("scope/scope_worker.js")); + + // Check that SW cannot be registered for a scope "above" the script's location. + registerArray.push(new Promise(function(resolve, reject) { + navigator.serviceWorker.register("scope/scope_worker.js", { scope: "./" }) + .then(function() { + ok(false, "registration scope has to be inside service worker script scope."); + reject(); + }, function() { + ok(true, "registration scope has to be inside service worker script scope."); + resolve(); + }); + })); return Promise.all(registerArray); } function unregisterWorkers() { var unregisterArray = []; scriptsAndScopes.forEach(function(item) { var p = navigator.serviceWorker.getRegistration(item[1]); unregisterArray.push(p.then(function(reg) { return reg.unregister(); })); }); + + unregisterArray.push(navigator.serviceWorker.getRegistration("scope/").then(function (reg) { + return reg.unregister(); + })); + return Promise.all(unregisterArray); } function testScopes() { return new Promise(function(resolve, reject) { var getScope = navigator.serviceWorker.getScopeForUrl.bind(navigator.serviceWorker); var base = new URL(".", document.baseURI); @@ -62,16 +83,17 @@ } ok(getScope(p("sub.html")) === p("sub"), "Scope should match"); ok(getScope(p("sub/dir.html")) === p("sub/dir.html"), "Scope should match"); ok(getScope(p("sub/dir")) === p("sub/dir"), "Scope should match"); ok(getScope(p("sub/dir/foo")) === p("sub/dir/"), "Scope should match"); ok(getScope(p("sub/dir/afoo")) === p("sub/dir/a"), "Scope should match"); ok(getScope(p("star*wars")) === p("star*"), "Scope should match"); + ok(getScope(p("scope/some_file.html")) === p("scope/"), "Scope should match"); fail("index.html"); fail("sua.html"); fail("star/a.html"); resolve(); }); } function runTest() {
--- a/gfx/2d/BaseSize.h +++ b/gfx/2d/BaseSize.h @@ -64,16 +64,19 @@ struct BaseSize { } Sub operator*(T aScale) const { return Sub(width * aScale, height * aScale); } Sub operator/(T aScale) const { return Sub(width / aScale, height / aScale); } + friend Sub operator*(T aScale, const Sub& aSize) { + return Sub(aScale * aSize.width, aScale * aSize.height); + } void Scale(T aXScale, T aYScale) { width *= aXScale; height *= aYScale; } Sub operator*(const Sub& aSize) const { return Sub(width * aSize.width, height * aSize.height); }
--- a/gfx/gl/GLContext.cpp +++ b/gfx/gl/GLContext.cpp @@ -1684,18 +1684,19 @@ GLContext::InitExtensions() MarkExtensionUnsupported(ANGLE_texture_compression_dxt5); } #ifdef XP_MACOSX // Bug 1009642: On OSX Mavericks (10.9), the driver for Intel HD // 3000 appears to be buggy WRT updating sub-images of S3TC // textures with glCompressedTexSubImage2D. Works on Intel HD 4000 // and Intel HD 5000/Iris that I tested. + // Bug 1124996: Appears to be the same on OSX Yosemite (10.10) if (nsCocoaFeatures::OSXVersionMajor() == 10 && - nsCocoaFeatures::OSXVersionMinor() == 9 && + nsCocoaFeatures::OSXVersionMinor() >= 9 && Renderer() == GLRenderer::IntelHD3000) { MarkExtensionUnsupported(EXT_texture_compression_s3tc); } #endif } if (shouldDumpExts) {
--- a/gfx/gl/GLScreenBuffer.cpp +++ b/gfx/gl/GLScreenBuffer.cpp @@ -398,40 +398,55 @@ GLScreenBuffer::Attach(SharedSurface* su surf->mAttachType == SharedSurf()->mAttachType && size == Size()) { // Same size, same type, ready for reuse! mRead->Attach(surf); } else { // Else something changed, so resize: UniquePtr<DrawBuffer> draw; - bool drawOk = CreateDraw(size, &draw); // Can be null. + bool drawOk = true; + + /* Don't change out the draw buffer unless we resize. In the + * preserveDrawingBuffer:true case, prior contents of the buffer must + * be retained. If we're using a draw buffer, it's an MSAA buffer, so + * even if we copy the previous frame into the (single-sampled) read + * buffer, if we need to re-resolve from draw to read (as triggered by + * drawing), we'll need the old MSAA content to still be in the draw + * buffer. + */ + if (!mDraw || size != Size()) + drawOk = CreateDraw(size, &draw); // Can be null. UniquePtr<ReadBuffer> read = CreateRead(surf); bool readOk = !!read; if (!drawOk || !readOk) { surf->UnlockProd(); return false; } - mDraw = Move(draw); + if (draw) + mDraw = Move(draw); + mRead = Move(read); } // Check that we're all set up. MOZ_ASSERT(SharedSurf() == surf); // Update the ReadBuffer mode. if (mGL->IsSupported(gl::GLFeature::read_buffer)) { BindFB(0); mRead->SetReadBuffer(mUserReadBufferMode); } + RequireBlit(); + return true; } bool GLScreenBuffer::Swap(const gfx::IntSize& size) { RefPtr<ShSurfHandle> newBack = mFactory->NewShSurfHandle(size); if (!newBack) @@ -445,17 +460,18 @@ GLScreenBuffer::Swap(const gfx::IntSize& mBack = newBack; if (mBack) { mBack->Surf()->ProducerAcquire(); } if (ShouldPreserveBuffer() && mFront && - mBack) + mBack && + !mDraw) { auto src = mFront->Surf(); auto dest = mBack->Surf(); //uint32_t srcPixel = ReadPixel(src); //uint32_t destPixel = ReadPixel(dest); //printf_stderr("Before: src: 0x%08x, dest: 0x%08x\n", srcPixel, destPixel);
--- a/gfx/layers/apz/util/APZCCallbackHelper.cpp +++ b/gfx/layers/apz/util/APZCCallbackHelper.cpp @@ -203,17 +203,17 @@ APZCCallbackHelper::UpdateRootFrame(nsID nsIContent* content = nsLayoutUtils::FindContentFor(aMetrics.GetScrollId()); ScrollFrame(content, aMetrics); // The pres shell resolution is updated by the the async zoom since the // last paint. float presShellResolution = aMetrics.GetPresShellResolution() * aMetrics.GetAsyncZoom().scale; - aUtils->SetResolutionAndScaleTo(presShellResolution, presShellResolution); + aUtils->SetResolutionAndScaleTo(presShellResolution); SetDisplayPortMargins(aUtils, content, aMetrics); } void APZCCallbackHelper::UpdateSubFrame(nsIContent* aContent, FrameMetrics& aMetrics) { @@ -410,33 +410,20 @@ APZCCallbackHelper::ApplyCallbackTransfo aEvent.touches[i]->mRefPoint = ApplyCallbackTransform( aEvent.touches[i]->mRefPoint, aGuid, aScale, aPresShellResolution); } } nsEventStatus APZCCallbackHelper::DispatchWidgetEvent(WidgetGUIEvent& aEvent) { - if (!aEvent.widget) - return nsEventStatus_eConsumeNoDefault; - - // A nested process may be capturing events. - if (TabParent* capturer = TabParent::GetEventCapturer()) { - if (capturer->TryCapture(aEvent)) { - // Only touch events should be captured, and touch events from a parent - // process should not make it here. Capture for those is done elsewhere - // (for gonk, in nsWindow::DispatchTouchInputViaAPZ). - MOZ_ASSERT(!XRE_IsParentProcess()); - - return nsEventStatus_eConsumeNoDefault; - } + nsEventStatus status = nsEventStatus_eConsumeNoDefault; + if (aEvent.widget) { + aEvent.widget->DispatchEvent(&aEvent, status); } - nsEventStatus status; - NS_ENSURE_SUCCESS(aEvent.widget->DispatchEvent(&aEvent, status), - nsEventStatus_eConsumeNoDefault); return status; } nsEventStatus APZCCallbackHelper::DispatchSynthesizedMouseEvent(uint32_t aMsg, uint64_t aTime, const LayoutDevicePoint& aRefPoint, Modifiers aModifiers,
--- a/gfx/layers/apz/util/APZEventState.cpp +++ b/gfx/layers/apz/util/APZEventState.cpp @@ -80,16 +80,17 @@ APZEventState::APZEventState(nsIWidget* const nsRefPtr<ContentReceivedInputBlockCallback>& aCallback) : mWidget(nullptr) // initialized in constructor body , mActiveElementManager(new ActiveElementManager()) , mContentReceivedInputBlockCallback(aCallback) , mPendingTouchPreventedResponse(false) , mPendingTouchPreventedBlockId(0) , mEndTouchIsClick(false) , mTouchEndCancelled(false) + , mActiveAPZTransforms(0) { nsresult rv; mWidget = do_GetWeakReference(aWidget, &rv); MOZ_ASSERT(NS_SUCCEEDED(rv), "APZEventState constructed with a widget that" " does not support weak references. APZ will NOT work!"); if (!sActiveDurationMsSet) { Preferences::AddIntVarCache(&sActiveDurationMs, @@ -315,37 +316,39 @@ APZEventState::ProcessAPZStateChange(con if (sf) { sf->SetTransformingByAPZ(true); } nsIScrollbarMediator* scrollbarMediator = do_QueryFrame(sf); if (scrollbarMediator) { scrollbarMediator->ScrollbarActivityStarted(); } - if (aDocument) { + if (aDocument && mActiveAPZTransforms == 0) { nsCOMPtr<nsIDocShell> docshell(aDocument->GetDocShell()); if (docshell && sf) { nsDocShell* nsdocshell = static_cast<nsDocShell*>(docshell.get()); nsdocshell->NotifyAsyncPanZoomStarted(sf->GetScrollPositionCSSPixels()); } } + mActiveAPZTransforms++; break; } case APZStateChange::TransformEnd: { + mActiveAPZTransforms--; nsIScrollableFrame* sf = nsLayoutUtils::FindScrollableFrameFor(aViewId); if (sf) { sf->SetTransformingByAPZ(false); } nsIScrollbarMediator* scrollbarMediator = do_QueryFrame(sf); if (scrollbarMediator) { scrollbarMediator->ScrollbarActivityStopped(); } - if (aDocument) { + if (aDocument && mActiveAPZTransforms == 0) { nsCOMPtr<nsIDocShell> docshell(aDocument->GetDocShell()); if (docshell && sf) { nsDocShell* nsdocshell = static_cast<nsDocShell*>(docshell.get()); nsdocshell->NotifyAsyncPanZoomStopped(sf->GetScrollPositionCSSPixels()); } } break; }
--- a/gfx/layers/apz/util/APZEventState.h +++ b/gfx/layers/apz/util/APZEventState.h @@ -82,14 +82,15 @@ private: nsWeakPtr mWidget; nsRefPtr<ActiveElementManager> mActiveElementManager; nsRefPtr<ContentReceivedInputBlockCallback> mContentReceivedInputBlockCallback; bool mPendingTouchPreventedResponse; ScrollableLayerGuid mPendingTouchPreventedGuid; uint64_t mPendingTouchPreventedBlockId; bool mEndTouchIsClick; bool mTouchEndCancelled; + int mActiveAPZTransforms; }; } } #endif /* mozilla_layers_APZEventState_h */
--- a/gfx/layers/basic/BasicCompositor.cpp +++ b/gfx/layers/basic/BasicCompositor.cpp @@ -12,18 +12,23 @@ #include "gfx2DGlue.h" #include "mozilla/gfx/2D.h" #include "mozilla/gfx/Helpers.h" #include "gfxUtils.h" #include "YCbCrUtils.h" #include <algorithm> #include "ImageContainer.h" #include "gfxPrefs.h" +#ifdef MOZ_ENABLE_SKIA #include "skia/SkCanvas.h" // for SkCanvas #include "skia/SkBitmapDevice.h" // for SkBitmapDevice +#else +#define PIXMAN_DONT_DEFINE_STDINT +#include "pixman.h" // for pixman_f_transform, etc +#endif namespace mozilla { using namespace mozilla::gfx; namespace layers { class DataTextureSourceBasic : public DataTextureSource , public TextureSourceBasic @@ -170,16 +175,17 @@ DrawSurfaceWithTextureCoords(DrawTarget // Only use REPEAT if aTextureCoords is outside (0, 0, 1, 1). gfx::Rect unitRect(0, 0, 1, 1); ExtendMode mode = unitRect.Contains(aTextureCoords) ? ExtendMode::CLAMP : ExtendMode::REPEAT; FillRectWithMask(aDest, aDestRect, aSource, aFilter, DrawOptions(aOpacity), mode, aMask, aMaskTransform, &matrix); } +#ifdef MOZ_ENABLE_SKIA static SkMatrix Matrix3DToSkia(const gfx3DMatrix& aMatrix) { SkMatrix transform; transform.setAll(aMatrix._11, aMatrix._21, aMatrix._41, aMatrix._12, @@ -188,20 +194,20 @@ Matrix3DToSkia(const gfx3DMatrix& aMatri aMatrix._14, aMatrix._24, aMatrix._44); return transform; } static void -SkiaTransform(DataSourceSurface* aDest, - DataSourceSurface* aSource, - const gfx3DMatrix& aTransform, - const Point& aDestOffset) +Transform(DataSourceSurface* aDest, + DataSourceSurface* aSource, + const gfx3DMatrix& aTransform, + const Point& aDestOffset) { if (aTransform.IsSingular()) { return; } IntSize destSize = aDest->GetSize(); SkImageInfo destInfo = SkImageInfo::Make(destSize.width, destSize.height, @@ -227,16 +233,88 @@ SkiaTransform(DataSourceSurface* aDest, SkPaint paint; paint.setXfermodeMode(SkXfermode::kSrc_Mode); paint.setAntiAlias(true); paint.setFilterLevel(SkPaint::kLow_FilterLevel); SkRect destRect = SkRect::MakeXYWH(0, 0, srcSize.width, srcSize.height); destCanvas.drawBitmapRectToRect(src, nullptr, destRect, &paint); } +#else +static pixman_transform +Matrix3DToPixman(const gfx3DMatrix& aMatrix) +{ + pixman_f_transform transform; + + transform.m[0][0] = aMatrix._11; + transform.m[0][1] = aMatrix._21; + transform.m[0][2] = aMatrix._41; + transform.m[1][0] = aMatrix._12; + transform.m[1][1] = aMatrix._22; + transform.m[1][2] = aMatrix._42; + transform.m[2][0] = aMatrix._14; + transform.m[2][1] = aMatrix._24; + transform.m[2][2] = aMatrix._44; + + pixman_transform result; + pixman_transform_from_pixman_f_transform(&result, &transform); + + return result; +} + +static void +Transform(DataSourceSurface* aDest, + DataSourceSurface* aSource, + const gfx3DMatrix& aTransform, + const Point& aDestOffset) +{ + IntSize destSize = aDest->GetSize(); + pixman_image_t* dest = pixman_image_create_bits(PIXMAN_a8r8g8b8, + destSize.width, + destSize.height, + (uint32_t*)aDest->GetData(), + aDest->Stride()); + + IntSize srcSize = aSource->GetSize(); + pixman_image_t* src = pixman_image_create_bits(PIXMAN_a8r8g8b8, + srcSize.width, + srcSize.height, + (uint32_t*)aSource->GetData(), + aSource->Stride()); + + NS_ABORT_IF_FALSE(src && dest, "Failed to create pixman images?"); + + pixman_transform pixTransform = Matrix3DToPixman(aTransform); + pixman_transform pixTransformInverted; + + // If the transform is singular then nothing would be drawn anyway, return here + if (!pixman_transform_invert(&pixTransformInverted, &pixTransform)) { + pixman_image_unref(dest); + pixman_image_unref(src); + return; + } + pixman_image_set_transform(src, &pixTransformInverted); + + pixman_image_composite32(PIXMAN_OP_SRC, + src, + nullptr, + dest, + aDestOffset.x, + aDestOffset.y, + 0, + 0, + 0, + 0, + destSize.width, + destSize.height); + + pixman_image_unref(dest); + pixman_image_unref(src); +} +#endif static inline IntRect RoundOut(Rect r) { r.RoundOut(); return IntRect(r.x, r.y, r.width, r.height); } @@ -366,22 +444,26 @@ BasicCompositor::DrawQuad(const gfx::Rec } if (!aTransform.Is2D()) { dest->Flush(); RefPtr<SourceSurface> snapshot = dest->Snapshot(); RefPtr<DataSourceSurface> source = snapshot->GetDataSurface(); RefPtr<DataSourceSurface> temp = - Factory::CreateDataSourceSurface(RoundOut(transformBounds).Size(), SurfaceFormat::B8G8R8A8, true); + Factory::CreateDataSourceSurface(RoundOut(transformBounds).Size(), SurfaceFormat::B8G8R8A8 +#ifdef MOZ_ENABLE_SKIA + , true +#endif + ); if (NS_WARN_IF(!temp)) { return; } - SkiaTransform(temp, source, new3DTransform, transformBounds.TopLeft()); + Transform(temp, source, new3DTransform, transformBounds.TopLeft()); transformBounds.MoveTo(0, 0); buffer->DrawSurface(temp, transformBounds, transformBounds); } buffer->PopClip(); }
--- a/gfx/layers/basic/BasicLayerManager.cpp +++ b/gfx/layers/basic/BasicLayerManager.cpp @@ -41,18 +41,23 @@ #include "nsAutoPtr.h" // for nsRefPtr #include "nsCOMPtr.h" // for already_AddRefed #include "nsDebug.h" // for NS_ASSERTION, etc #include "nsISupportsImpl.h" // for gfxContext::Release, etc #include "nsPoint.h" // for nsIntPoint #include "nsRect.h" // for nsIntRect #include "nsRegion.h" // for nsIntRegion, etc #include "nsTArray.h" // for nsAutoTArray +#ifdef MOZ_ENABLE_SKIA #include "skia/SkCanvas.h" // for SkCanvas #include "skia/SkBitmapDevice.h" // for SkBitmapDevice +#else +#define PIXMAN_DONT_DEFINE_STDINT +#include "pixman.h" // for pixman_f_transform, etc +#endif class nsIWidget; namespace mozilla { namespace layers { using namespace mozilla::gfx; /** @@ -601,16 +606,17 @@ void BasicLayerManager::SetRoot(Layer* aLayer) { NS_ASSERTION(aLayer, "Root can't be null"); NS_ASSERTION(aLayer->Manager() == this, "Wrong manager"); NS_ASSERTION(InConstruction(), "Only allowed in construction phase"); mRoot = aLayer; } +#ifdef MOZ_ENABLE_SKIA static SkMatrix BasicLayerManager_Matrix3DToSkia(const gfx3DMatrix& aMatrix) { SkMatrix transform; transform.setAll(aMatrix._11, aMatrix._21, aMatrix._41, aMatrix._12, @@ -619,20 +625,20 @@ BasicLayerManager_Matrix3DToSkia(const g aMatrix._14, aMatrix._24, aMatrix._44); return transform; } static void -SkiaTransform(const gfxImageSurface* aDest, - RefPtr<DataSourceSurface> aSrc, - const gfx3DMatrix& aTransform, - gfxPoint aDestOffset) +Transform(const gfxImageSurface* aDest, + RefPtr<DataSourceSurface> aSrc, + const gfx3DMatrix& aTransform, + gfxPoint aDestOffset) { if (aTransform.IsSingular()) { return; } IntSize destSize = ToIntSize(aDest->GetSize()); SkImageInfo destInfo = SkImageInfo::Make(destSize.width, destSize.height, @@ -658,16 +664,88 @@ SkiaTransform(const gfxImageSurface* aDe SkPaint paint; paint.setXfermodeMode(SkXfermode::kSrc_Mode); paint.setAntiAlias(true); paint.setFilterLevel(SkPaint::kLow_FilterLevel); SkRect destRect = SkRect::MakeXYWH(0, 0, srcSize.width, srcSize.height); destCanvas.drawBitmapRectToRect(src, nullptr, destRect, &paint); } +#else +static pixman_transform +BasicLayerManager_Matrix3DToPixman(const gfx3DMatrix& aMatrix) +{ + pixman_f_transform transform; + + transform.m[0][0] = aMatrix._11; + transform.m[0][1] = aMatrix._21; + transform.m[0][2] = aMatrix._41; + transform.m[1][0] = aMatrix._12; + transform.m[1][1] = aMatrix._22; + transform.m[1][2] = aMatrix._42; + transform.m[2][0] = aMatrix._14; + transform.m[2][1] = aMatrix._24; + transform.m[2][2] = aMatrix._44; + + pixman_transform result; + pixman_transform_from_pixman_f_transform(&result, &transform); + + return result; +} + +static void +Transform(const gfxImageSurface* aDest, + RefPtr<DataSourceSurface> aSrc, + const gfx3DMatrix& aTransform, + gfxPoint aDestOffset) +{ + IntSize destSize = ToIntSize(aDest->GetSize()); + pixman_image_t* dest = pixman_image_create_bits(aDest->Format() == gfxImageFormat::ARGB32 ? PIXMAN_a8r8g8b8 : PIXMAN_x8r8g8b8, + destSize.width, + destSize.height, + (uint32_t*)aDest->Data(), + aDest->Stride()); + + IntSize srcSize = aSrc->GetSize(); + pixman_image_t* src = pixman_image_create_bits(aSrc->GetFormat() == SurfaceFormat::B8G8R8A8 ? PIXMAN_a8r8g8b8 : PIXMAN_x8r8g8b8, + srcSize.width, + srcSize.height, + (uint32_t*)aSrc->GetData(), + aSrc->Stride()); + + NS_ABORT_IF_FALSE(src && dest, "Failed to create pixman images?"); + + pixman_transform pixTransform = BasicLayerManager_Matrix3DToPixman(aTransform); + pixman_transform pixTransformInverted; + + // If the transform is singular then nothing would be drawn anyway, return here + if (!pixman_transform_invert(&pixTransformInverted, &pixTransform)) { + pixman_image_unref(dest); + pixman_image_unref(src); + return; + } + pixman_image_set_transform(src, &pixTransformInverted); + + pixman_image_composite32(PIXMAN_OP_SRC, + src, + nullptr, + dest, + aDestOffset.x, + aDestOffset.y, + 0, + 0, + 0, + 0, + destSize.width, + destSize.height); + + pixman_image_unref(dest); + pixman_image_unref(src); +} +#endif /** * Transform a surface using a gfx3DMatrix and blit to the destination if * it is efficient to do so. * * @param aSource Source surface. * @param aDest Desintation context. * @param aBounds Area represented by aSource. @@ -699,17 +777,17 @@ Transform3D(RefPtr<SourceSurface> aSourc aDestRect.height), gfxImageFormat::ARGB32); gfxPoint offset = aDestRect.TopLeft(); // Include a translation to the correct origin. gfx3DMatrix translation = gfx3DMatrix::Translation(aBounds.x, aBounds.y, 0); // Transform the content and offset it such that the content begins at the origin. - SkiaTransform(destImage, aSource->GetDataSurface(), translation * aTransform, offset); + Transform(destImage, aSource->GetDataSurface(), translation * aTransform, offset); // If we haven't actually drawn to aDest then return our temporary image so // that the caller can do this. return destImage.forget(); } void BasicLayerManager::PaintSelfOrChildren(PaintLayerContext& aPaintContext,
--- a/gfx/thebes/VsyncSource.cpp +++ b/gfx/thebes/VsyncSource.cpp @@ -119,16 +119,20 @@ VsyncSource::Display::UpdateVsyncStatus( enableVsync = !mCompositorVsyncDispatchers.IsEmpty() || mRefreshTimerNeedsVsync; } if (enableVsync) { EnableVsync(); } else { DisableVsync(); } + + if (IsVsyncEnabled() != enableVsync) { + NS_WARNING("Vsync status did not change."); + } } nsRefPtr<RefreshTimerVsyncDispatcher> VsyncSource::Display::GetRefreshTimerVsyncDispatcher() { return mRefreshTimerVsyncDispatcher; }
--- a/gfx/thebes/gfxPlatformMac.cpp +++ b/gfx/thebes/gfxPlatformMac.cpp @@ -464,28 +464,41 @@ public: return; } // Create a display link capable of being used with all active displays // TODO: See if we need to create an active DisplayLink for each monitor in multi-monitor // situations. According to the docs, it is compatible with all displays running on the computer // But if we have different monitors at different display rates, we may hit issues. if (CVDisplayLinkCreateWithActiveCGDisplays(&mDisplayLink) != kCVReturnSuccess) { - NS_WARNING("Could not create a display link, returning"); - return; + NS_WARNING("Could not create a display link with all active displays. Falling back to main display\n"); + CVDisplayLinkRelease(mDisplayLink); + + // bug 1142708 - When coming back from sleep, there may be no active displays ready yet, + // even if listening for the kIOMessageSystemHasPoweredOn event from OS X sleep notifications. + // Active displays are those that are drawable. + // In these cases, default back to the main display to try to get a vsync event. + // The alternative would be to keep polling the CGActiveDisplayList for the displays to be ready. + if (CVDisplayLinkCreateWithCGDisplay(CGMainDisplayID(), &mDisplayLink) != kCVReturnSuccess) { + MOZ_CRASH("Could not create a CVDisplayLink with either active displays or the main display"); + } + NS_WARNING("Using the CVDisplayLink from the main display\n"); } if (CVDisplayLinkSetOutputCallback(mDisplayLink, &VsyncCallback, this) != kCVReturnSuccess) { NS_WARNING("Could not set displaylink output callback"); + CVDisplayLinkRelease(mDisplayLink); + mDisplayLink = nullptr; return; } mPreviousTimestamp = TimeStamp::Now(); if (CVDisplayLinkStart(mDisplayLink) != kCVReturnSuccess) { NS_WARNING("Could not activate the display link"); + CVDisplayLinkRelease(mDisplayLink); mDisplayLink = nullptr; } } virtual void DisableVsync() MOZ_OVERRIDE { MOZ_ASSERT(NS_IsMainThread()); if (!IsVsyncEnabled()) {
--- a/image/test/crashtests/crashtests.list +++ b/image/test/crashtests/crashtests.list @@ -41,9 +41,9 @@ load truncated-second-frame.png # bug 86 # This icon's size is such that it leads to multiple writes to the PNG decoder # after we've gotten our size. load multiple-png-hassize.ico # Bug 856615 # Asserts in the debug build load 856616.gif -load 944353.jpg +skip-if(AddressSanitizer) load 944353.jpg
--- a/js/public/Utility.h +++ b/js/public/Utility.h @@ -90,19 +90,32 @@ static MOZ_NEVER_INLINE void js_failedAl do \ { \ if (++OOM_counter > OOM_maxAllocations) { \ JS_OOM_CALL_BP_FUNC();\ return false; \ } \ } while (0) +namespace js { +namespace oom { +static inline bool ShouldFailWithOOM() +{ + if (++OOM_counter > OOM_maxAllocations) { + JS_OOM_CALL_BP_FUNC(); + return true; + } + return false; +} +} +} # else # define JS_OOM_POSSIBLY_FAIL() do {} while(0) # define JS_OOM_POSSIBLY_FAIL_BOOL() do {} while(0) +namespace js { namespace oom { static inline bool ShouldFailWithOOM() { return false; } } } # endif /* DEBUG || JS_OOM_BREAKPOINT */ static inline void* js_malloc(size_t bytes) { JS_OOM_POSSIBLY_FAIL(); return malloc(bytes); }
--- a/js/src/asmjs/AsmJSValidate.cpp +++ b/js/src/asmjs/AsmJSValidate.cpp @@ -5929,20 +5929,20 @@ CheckSimdOperationCall(FunctionCompiler case AsmJSSimdOperation_abs: return CheckSimdUnary(f, call, opType, MSimdUnaryArith::abs, def, type); case AsmJSSimdOperation_neg: return CheckSimdUnary(f, call, opType, MSimdUnaryArith::neg, def, type); case AsmJSSimdOperation_not: return CheckSimdUnary(f, call, opType, MSimdUnaryArith::not_, def, type); case AsmJSSimdOperation_sqrt: return CheckSimdUnary(f, call, opType, MSimdUnaryArith::sqrt, def, type); - case AsmJSSimdOperation_reciprocal: - return CheckSimdUnary(f, call, opType, MSimdUnaryArith::reciprocal, def, type); - case AsmJSSimdOperation_reciprocalSqrt: - return CheckSimdUnary(f, call, opType, MSimdUnaryArith::reciprocalSqrt, def, type); + case AsmJSSimdOperation_reciprocalApproximation: + return CheckSimdUnary(f, call, opType, MSimdUnaryArith::reciprocalApproximation, def, type); + case AsmJSSimdOperation_reciprocalSqrtApproximation: + return CheckSimdUnary(f, call, opType, MSimdUnaryArith::reciprocalSqrtApproximation, def, type); case AsmJSSimdOperation_swizzle: return CheckSimdSwizzle(f, call, opType, def, type); case AsmJSSimdOperation_shuffle: return CheckSimdShuffle(f, call, opType, def, type); case AsmJSSimdOperation_load: return CheckSimdLoad(f, call, opType, 4, def, type);
--- a/js/src/builtin/SIMD.cpp +++ b/js/src/builtin/SIMD.cpp @@ -527,21 +527,21 @@ template<typename T> struct Neg { static inline T apply(T x) { return -1 * x; } }; template<typename T> struct Not { static inline T apply(T x) { return ~x; } }; template<typename T> -struct Rec { +struct RecApprox { static inline T apply(T x) { return 1 / x; } }; template<typename T> -struct RecSqrt { +struct RecSqrtApprox { static inline T apply(T x) { return sqrt(1 / x); } }; template<typename T> struct Sqrt { static inline T apply(T x) { return sqrt(x); } }; // Binary SIMD operators @@ -743,19 +743,19 @@ Swizzle(JSContext *cx, unsigned argc, Va typedef typename V::Elem Elem; CallArgs args = CallArgsFromVp(argc, vp); if (args.length() != (V::lanes + 1) || !IsVectorObject<V>(args[0])) return ErrorBadArgs(cx); uint32_t lanes[V::lanes]; for (unsigned i = 0; i < V::lanes; i++) { - int32_t lane = -1; - if (!ToInt32(cx, args[i + 1], &lane)) - return false; + if (!args[i + 1].isInt32()) + return ErrorBadArgs(cx); + int32_t lane = args[i + 1].toInt32(); if (lane < 0 || uint32_t(lane) >= V::lanes) return ErrorBadArgs(cx); lanes[i] = uint32_t(lane); } Elem *val = TypedObjectMemory<Elem *>(args[0]); Elem result[V::lanes]; @@ -772,19 +772,19 @@ Shuffle(JSContext *cx, unsigned argc, Va typedef typename V::Elem Elem; CallArgs args = CallArgsFromVp(argc, vp); if (args.length() != (V::lanes + 2) || !IsVectorObject<V>(args[0]) || !IsVectorObject<V>(args[1])) return ErrorBadArgs(cx); uint32_t lanes[V::lanes]; for (unsigned i = 0; i < V::lanes; i++) { - int32_t lane = -1; - if (!ToInt32(cx, args[i + 2], &lane)) - return false; + if (!args[i + 2].isInt32()) + return ErrorBadArgs(cx); + int32_t lane = args[i + 2].toInt32(); if (lane < 0 || uint32_t(lane) >= (2 * V::lanes)) return ErrorBadArgs(cx); lanes[i] = uint32_t(lane); } Elem *lhs = TypedObjectMemory<Elem *>(args[0]); Elem *rhs = TypedObjectMemory<Elem *>(args[1]);
--- a/js/src/builtin/SIMD.h +++ b/js/src/builtin/SIMD.h @@ -24,18 +24,18 @@ V(abs, (UnaryFunc<Float32x4, Abs, Float32x4>), 1, 0) \ V(check, (UnaryFunc<Float32x4, Identity, Float32x4>), 1, 0) \ V(fromFloat64x2, (FuncConvert<Float64x2, Float32x4> ), 1, 0) \ V(fromFloat64x2Bits, (FuncConvertBits<Float64x2, Float32x4>), 1, 0) \ V(fromInt32x4, (FuncConvert<Int32x4, Float32x4> ), 1, 0) \ V(fromInt32x4Bits, (FuncConvertBits<Int32x4, Float32x4>), 1, 0) \ V(neg, (UnaryFunc<Float32x4, Neg, Float32x4>), 1, 0) \ V(not, (CoercedUnaryFunc<Float32x4, Int32x4, Not, Float32x4>), 1, 0) \ - V(reciprocal, (UnaryFunc<Float32x4, Rec, Float32x4>), 1, 0) \ - V(reciprocalSqrt, (UnaryFunc<Float32x4, RecSqrt, Float32x4>), 1, 0) \ + V(reciprocalApproximation, (UnaryFunc<Float32x4, RecApprox, Float32x4>), 1, 0) \ + V(reciprocalSqrtApproximation, (UnaryFunc<Float32x4, RecSqrtApprox, Float32x4>), 1, 0) \ V(splat, (FuncSplat<Float32x4>), 1, 0) \ V(sqrt, (UnaryFunc<Float32x4, Sqrt, Float32x4>), 1, 0) #define FLOAT32X4_BINARY_FUNCTION_LIST(V) \ V(add, (BinaryFunc<Float32x4, Add, Float32x4>), 2, 0) \ V(and, (CoercedBinaryFunc<Float32x4, Int32x4, And, Float32x4>), 2, 0) \ V(div, (BinaryFunc<Float32x4, Div, Float32x4>), 2, 0) \ V(equal, (CompareFunc<Float32x4, Equal>), 2, 0) \ @@ -83,18 +83,18 @@ #define FLOAT64X2_UNARY_FUNCTION_LIST(V) \ V(abs, (UnaryFunc<Float64x2, Abs, Float64x2>), 1, 0) \ V(check, (UnaryFunc<Float64x2, Identity, Float64x2>), 1, 0) \ V(fromFloat32x4, (FuncConvert<Float32x4, Float64x2> ), 1, 0) \ V(fromFloat32x4Bits, (FuncConvertBits<Float32x4, Float64x2>), 1, 0) \ V(fromInt32x4, (FuncConvert<Int32x4, Float64x2> ), 1, 0) \ V(fromInt32x4Bits, (FuncConvertBits<Int32x4, Float64x2>), 1, 0) \ V(neg, (UnaryFunc<Float64x2, Neg, Float64x2>), 1, 0) \ - V(reciprocal, (UnaryFunc<Float64x2, Rec, Float64x2>), 1, 0) \ - V(reciprocalSqrt, (UnaryFunc<Float64x2, RecSqrt, Float64x2>), 1, 0) \ + V(reciprocalApproximation, (UnaryFunc<Float64x2, RecApprox, Float64x2>), 1, 0) \ + V(reciprocalSqrtApproximation, (UnaryFunc<Float64x2, RecSqrtApprox, Float64x2>), 1, 0) \ V(splat, (FuncSplat<Float64x2>), 1, 0) \ V(sqrt, (UnaryFunc<Float64x2, Sqrt, Float64x2>), 1, 0) #define FLOAT64X2_BINARY_FUNCTION_LIST(V) \ V(add, (BinaryFunc<Float64x2, Add, Float64x2>), 2, 0) \ V(div, (BinaryFunc<Float64x2, Div, Float64x2>), 2, 0) \ V(equal, (CompareFunc<Float64x2, Equal>), 2, 0) \ V(greaterThan, (CompareFunc<Float64x2, GreaterThan>), 2, 0) \ @@ -193,18 +193,18 @@ #define FOREACH_INT32X4_SIMD_OP(_) \ CONVERSION_INT32X4_SIMD_OP(_) \ _(shiftLeftByScalar) \ _(shiftRightArithmeticByScalar) \ _(shiftRightLogicalByScalar) #define UNARY_ARITH_FLOAT32X4_SIMD_OP(_) \ _(abs) \ _(sqrt) \ - _(reciprocal) \ - _(reciprocalSqrt) + _(reciprocalApproximation) \ + _(reciprocalSqrtApproximation) #define BINARY_ARITH_FLOAT32X4_SIMD_OP(_) \ _(div) \ _(max) \ _(min) \ _(maxNum) \ _(minNum) #define FOREACH_FLOAT32X4_SIMD_OP(_) \ UNARY_ARITH_FLOAT32X4_SIMD_OP(_) \
--- a/js/src/ctypes/CTypes.cpp +++ b/js/src/ctypes/CTypes.cpp @@ -5336,16 +5336,21 @@ StructType::AddressOfField(JSContext* cx return false; } if (args.length() != 1) { JS_ReportError(cx, "addressOfField takes one argument"); return false; } + if (!args[0].isString()) { + JS_ReportError(cx, "argument must be a string"); + return false; + } + JSFlatString *str = JS_FlattenString(cx, args[0].toString()); if (!str) return false; const FieldInfo* field = LookupField(cx, typeObj, str); if (!field) return false;
--- a/js/src/doc/Debugger/Conventions.md +++ b/js/src/doc/Debugger/Conventions.md @@ -131,16 +131,22 @@ resumption value has one of the followin If a function that would normally return a resumption value to indicate how the debuggee should continue instead throws an exception, we never propagate such an exception to the debuggee; instead, we call the associated `Debugger` instance's `uncaughtExceptionHook` property, as described below. +## Timestamps + +Timestamps are expressed in units of microseconds since the epoch (midnight, +January 1st, 1970). + + ## The `Debugger.DebuggeeWouldRun` Exception Some debugger operations that appear to simply inspect the debuggee's state may actually cause debuggee code to run. For example, reading a variable might run a getter function on the global or on a `with` expression's operand; and getting an object's property descriptor will run a handler trap if the object is a proxy. To protect the debugger's integrity, only methods whose stated purpose is to run debuggee code can do so. These
--- a/js/src/doc/Debugger/Debugger.Memory.md +++ b/js/src/doc/Debugger/Debugger.Memory.md @@ -80,22 +80,114 @@ following accessor properties from its p [`Debugger.Object.prototype.allocationSite`][allocation-site] accessor property. <code id='max-alloc-log'>maxAllocationsLogLength</code> : The maximum number of allocation sites to accumulate in the allocations log at a time. This accessor can be both fetched and stored to. Its default value is `5000`. -<code id='allocationsLogOverflowed'>allocationsLogOverflowed</a> +<code id='allocationsLogOverflowed'>allocationsLogOverflowed</code> : Returns `true` if there have been more than [`maxAllocationsLogLength`][#max-alloc-log] allocations since the last time [`drainAllocationsLog`][#drain-alloc-log] was called and some data has been lost. Returns `false` otherwise. +Debugger.Memory Handler Functions +--------------------------------- + +Similar to [`Debugger`'s handler functions][debugger], `Debugger.Memory` +inherits accessor properties that store handler functions for SpiderMonkey to +call when given events occur in debuggee code. + +Unlike `Debugger`'s hooks, `Debugger.Memory`'s handlers' return values are not +significant, and are ignored. The handler functions receive the +`Debugger.Memory`'s owning `Debugger` instance as their `this` value. The owning +`Debugger`'s `uncaughtExceptionHandler` is still fired for errors thrown in +`Debugger.Memory` hooks. + +On a new `Debugger.Memory` instance, each of these properties is initially +`undefined`. Any value assigned to a debugging handler must be either a function +or `undefined`; otherwise a `TypeError` is thrown. + +Handler functions run in the same thread in which the event occurred. +They run in the compartment to which they belong, not in a debuggee +compartment. + +<code>onGarbageCollection(<i>statistics</i>)</code> +: A garbage collection cycle spanning one or more debuggees has just been + completed. + + The *statistics* parameter is an object containing information about the GC + cycle. It has the following properties: + + `collections` + : The `collections` property's value is an array. Because SpiderMonkey's + collector is incremental, a full collection cycle may consist of + multiple discrete collection slices with the JS mutator running + interleaved. For each collection slice that occurred, there is an entry + in the `collections` array with the following form: + + <pre class='language-js'><code> + { + "startTimestamp": <i>timestamp</i>, + "endTimestamp": <i>timestamp</i>, + } + </code></pre> + + Here the *timestamp* values are [timestamps][] of the GC slice's start + and end events. + + `reason` + : A very short string describing th reason why the collection was + triggered. Known values include the following: + + * "API" + * "EAGER_ALLOC_TRIGGER" + * "DESTROY_RUNTIME" + * "DESTROY_CONTEXT" + * "LAST_DITCH" + * "TOO_MUCH_MALLOC" + * "ALLOC_TRIGGER" + * "DEBUG_GC" + * "COMPARTMENT_REVIVED" + * "RESET" + * "OUT_OF_NURSERY" + * "EVICT_NURSERY" + * "FULL_STORE_BUFFER" + * "SHARED_MEMORY_LIMIT" + * "PERIODIC_FULL_GC" + * "INCREMENTAL_TOO_SLOW" + * "DOM_WINDOW_UTILS" + * "COMPONENT_UTILS" + * "MEM_PRESSURE" + * "CC_WAITING" + * "CC_FORCED" + * "LOAD_END" + * "POST_COMPARTMENT" + * "PAGE_HIDE" + * "NSJSCONTEXT_DESTROY" + * "SET_NEW_DOCUMENT" + * "SET_DOC_SHELL" + * "DOM_UTILS" + * "DOM_IPC" + * "DOM_WORKER" + * "INTER_SLICE_GC" + * "REFRESH_FRAME" + * "FULL_GC_TIMER" + * "SHUTDOWN_CC" + * "FINISH_LARGE_EVALUATE" + * "USER_INACTIVE" + + `nonincrementalReason` + : If SpiderMonkey's collector determined it could not incrementally + collect garbage, and had to do a full GC all at once, this is a short + string describing the reason it determined the full GC was necessary. + Otherwise, `null` is returned. + Function Properties of the `Debugger.Memory.prototype` Object ------------------------------------------------------------- <code id='drain-alloc-log'>drainAllocationsLog()</code> : When `trackingAllocationSites` is `true`, this method returns an array of recent `Object` allocations within the set of debuggees. *Recent* is defined as the `maxAllocationsLogLength` most recent `Object` allocations since the last call to `drainAllocationsLog`. Therefore, calling this @@ -105,21 +197,20 @@ Function Properties of the `Debugger.Mem <pre class='language-js'><code> { "timestamp": <i>timestamp</i>, "frame": <i>allocationSite</i> } </code></pre> - Here <i>timestamp</i> is the timestamp of the event in units of - microseconds since the epoch and <i>allocationSite</i> is an - allocation site (as a [captured stack][saved-frame]). - <i>allocationSite</i> is `null` for objects allocated with no - JavaScript frames on the stack. + Here <i>timestamp</i> is the [timestamp][timestamps] of the allocation event and + <i>allocationSite</i> is an allocation site (as a + [captured stack][saved-frame]). <i>allocationSite</i> is `null` for objects + allocated with no JavaScript frames on the stack. When `trackingAllocationSites` is `false`, `drainAllocationsLog()` throws an `Error`. <code id='take-census'>takeCensus()</code> : Carry out a census of the debuggee compartments' contents. A *census* is a complete traversal of the graph of all reachable memory items belonging to a particular `Debugger`'s debuggees. The census produces a count of those
--- a/js/src/doc/Debugger/config.sh +++ b/js/src/doc/Debugger/config.sh @@ -6,16 +6,17 @@ base-url https://developer.mozilla.org/e markdown Debugger-API.md Debugger-API label 'debugger' "The Debugger API" markdown Conventions.md Debugger-API/Conventions label 'conventions' "Debugger API: General Conventions" label 'dbg code' '#debuggee-code' "Debugger API: General Conventions: Debuggee Code" label 'cv' '#completion-values' "Debugger API: General Conventions: Completion Values" label 'rv' '#resumption-values' "Debugger API: General Conventions: Resumption Values" + label 'timestamps' '#timestamps' "Debugger API: General Conventions: Timestamps" label 'wouldrun' '#the-debugger.debuggeewouldrun-exception' "Debugger API: DebuggeeWouldRun" markdown Debugger.md Debugger-API/Debugger label 'debugger-object' "The Debugger object" label 'add' '#addDebuggee' "The Debugger object: addDebuggee" markdown Debugger.Environment.md Debugger-API/Debugger.Environment label 'environment' "Debugger.Environment"
--- a/js/src/frontend/Parser.cpp +++ b/js/src/frontend/Parser.cpp @@ -2551,17 +2551,17 @@ Parser<ParseHandler>::functionArgsAndBod fun->setIsExprClosure(); #endif } Node body = functionBody(kind, bodyType); if (!body) return false; - if (kind != Method && kind != Lazy && + if (kind != Method && kind != Lazy && fun->name() && !checkStrictBinding(fun->name(), pn)) { return false; } if (bodyType == StatementListBody) { bool matched; if (!tokenStream.matchToken(&matched, TOK_RC)) @@ -6410,16 +6410,25 @@ Parser<ParseHandler>::assignExpr(Invoked case TOK_LSHASSIGN: kind = PNK_LSHASSIGN; op = JSOP_LSH; break; case TOK_RSHASSIGN: kind = PNK_RSHASSIGN; op = JSOP_RSH; break; case TOK_URSHASSIGN: kind = PNK_URSHASSIGN; op = JSOP_URSH; break; case TOK_MULASSIGN: kind = PNK_MULASSIGN; op = JSOP_MUL; break; case TOK_DIVASSIGN: kind = PNK_DIVASSIGN; op = JSOP_DIV; break; case TOK_MODASSIGN: kind = PNK_MODASSIGN; op = JSOP_MOD; break; case TOK_ARROW: { + // A line terminator between ArrowParameters and the => should trigger a SyntaxError. + tokenStream.ungetToken(); + TokenKind next; + if (!tokenStream.peekTokenSameLine(&next) || next != TOK_ARROW) { + report(ParseError, false, null(), JSMSG_UNEXPECTED_TOKEN, + "expression", TokenKindToDesc(TOK_ARROW)); + return null(); + } + tokenStream.seek(start); if (!abortIfSyntaxParser()) return null(); TokenKind ignored; if (!tokenStream.peekToken(&ignored)) return null(); @@ -8446,17 +8455,17 @@ Parser<ParseHandler>::primaryExpr(TokenK if (!tokenStream.getToken(&next)) return null(); if (next != TOK_RP) { report(ParseError, false, null(), JSMSG_UNEXPECTED_TOKEN, "closing parenthesis", TokenKindToDesc(next)); return null(); } - if (!tokenStream.peekToken(&next)) + if (!tokenStream.peekTokenSameLine(&next)) return null(); if (next != TOK_ARROW) { report(ParseError, false, null(), JSMSG_UNEXPECTED_TOKEN, "'=>' after argument list", TokenKindToDesc(next)); return null(); } tokenStream.ungetToken(); // put back right paren
--- a/js/src/gc/Allocator.cpp +++ b/js/src/gc/Allocator.cpp @@ -3,242 +3,231 @@ * 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 "gc/Allocator.h" #include "jscntxt.h" +#include "gc/GCInternals.h" #include "gc/GCTrace.h" #include "gc/Nursery.h" #include "jit/JitCompartment.h" #include "vm/Runtime.h" #include "vm/String.h" #include "jsobjinlines.h" using namespace js; using namespace gc; -static inline bool -ShouldNurseryAllocateObject(const Nursery &nursery, InitialHeap heap) -{ - return nursery.isEnabled() && heap != TenuredHeap; -} - -/* - * Attempt to allocate a new GC thing out of the nursery. If there is not enough - * room in the nursery or there is an OOM, this method will return nullptr. - */ -template <AllowGC allowGC> -inline JSObject * -TryNewNurseryObject(JSContext *cx, size_t thingSize, size_t nDynamicSlots, const Class *clasp) +bool +GCRuntime::gcIfNeededPerAllocation(JSContext *cx) { - MOZ_ASSERT(!IsAtomsCompartment(cx->compartment())); - JSRuntime *rt = cx->runtime(); - Nursery &nursery = rt->gc.nursery; - JSObject *obj = nursery.allocateObject(cx, thingSize, nDynamicSlots, clasp); - if (obj) - return obj; - - if (allowGC && !rt->mainThread.suppressGC) { - cx->minorGC(JS::gcreason::OUT_OF_NURSERY); - - /* Exceeding gcMaxBytes while tenuring can disable the Nursery. */ - if (nursery.isEnabled()) { - JSObject *obj = nursery.allocateObject(cx, thingSize, nDynamicSlots, clasp); - MOZ_ASSERT(obj); - return obj; - } - } - return nullptr; -} - -static inline bool -PossiblyFail() -{ - JS_OOM_POSSIBLY_FAIL_BOOL(); - return true; -} - -static inline bool -GCIfNeeded(ExclusiveContext *cx) -{ - if (cx->isJSContext()) { - JSContext *ncx = cx->asJSContext(); - JSRuntime *rt = ncx->runtime(); - #ifdef JS_GC_ZEAL - if (rt->gc.needZealousGC()) - rt->gc.runDebugGC(); + if (needZealousGC()) + runDebugGC(); #endif - // Invoking the interrupt callback can fail and we can't usefully - // handle that here. Just check in case we need to collect instead. - if (rt->hasPendingInterrupt()) - rt->gc.gcIfRequested(ncx); + // Invoking the interrupt callback can fail and we can't usefully + // handle that here. Just check in case we need to collect instead. + if (rt->hasPendingInterrupt()) + gcIfRequested(cx); - // If we have grown past our GC heap threshold while in the middle of - // an incremental GC, we're growing faster than we're GCing, so stop - // the world and do a full, non-incremental GC right now, if possible. - if (rt->gc.isIncrementalGCInProgress() && - cx->zone()->usage.gcBytes() > cx->zone()->threshold.gcTriggerBytes()) - { - PrepareZoneForGC(cx->zone()); - AutoKeepAtoms keepAtoms(cx->perThreadData); - rt->gc.gc(GC_NORMAL, JS::gcreason::INCREMENTAL_TOO_SLOW); - } + // If we have grown past our GC heap threshold while in the middle of + // an incremental GC, we're growing faster than we're GCing, so stop + // the world and do a full, non-incremental GC right now, if possible. + if (isIncrementalGCInProgress() && + cx->zone()->usage.gcBytes() > cx->zone()->threshold.gcTriggerBytes()) + { + PrepareZoneForGC(cx->zone()); + AutoKeepAtoms keepAtoms(cx->perThreadData); + gc(GC_NORMAL, JS::gcreason::INCREMENTAL_TOO_SLOW); } return true; } template <AllowGC allowGC> -static inline bool -CheckAllocatorState(ExclusiveContext *cx, AllocKind kind) +bool +GCRuntime::checkAllocatorState(JSContext *cx, AllocKind kind) { if (allowGC) { - if (!GCIfNeeded(cx)) + if (!gcIfNeededPerAllocation(cx)) return false; } - if (!cx->isJSContext()) - return true; - - JSContext *ncx = cx->asJSContext(); - JSRuntime *rt = ncx->runtime(); #if defined(JS_GC_ZEAL) || defined(DEBUG) - MOZ_ASSERT_IF(rt->isAtomsCompartment(ncx->compartment()), + MOZ_ASSERT_IF(rt->isAtomsCompartment(cx->compartment()), kind == AllocKind::STRING || kind == AllocKind::FAT_INLINE_STRING || kind == AllocKind::SYMBOL || kind == AllocKind::JITCODE); MOZ_ASSERT(!rt->isHeapBusy()); - MOZ_ASSERT(rt->gc.isAllocAllowed()); + MOZ_ASSERT(isAllocAllowed()); #endif // Crash if we perform a GC action when it is not safe. if (allowGC && !rt->mainThread.suppressGC) JS::AutoAssertOnGC::VerifyIsSafeToGC(rt); // For testing out of memory conditions - if (!PossiblyFail()) { - ReportOutOfMemory(ncx); + if (js::oom::ShouldFailWithOOM()) { + ReportOutOfMemory(cx); return false; } return true; } template <typename T> -static inline void -CheckIncrementalZoneState(ExclusiveContext *cx, T *t) +/* static */ void +GCRuntime::checkIncrementalZoneState(ExclusiveContext *cx, T *t) { #ifdef DEBUG if (!cx->isJSContext()) return; Zone *zone = cx->asJSContext()->zone(); MOZ_ASSERT_IF(t && zone->wasGCStarted() && (zone->isGCMarking() || zone->isGCSweeping()), t->asTenured().arenaHeader()->allocatedDuringIncremental); #endif } -/* - * Allocate a new GC thing. After a successful allocation the caller must - * fully initialize the thing before calling any function that can potentially - * trigger GC. This will ensure that GC tracing never sees junk values stored - * in the partially initialized thing. - */ - +// Allocate a new GC thing. After a successful allocation the caller must +// fully initialize the thing before calling any function that can potentially +// trigger GC. This will ensure that GC tracing never sees junk values stored +// in the partially initialized thing. template <typename T, AllowGC allowGC /* = CanGC */> JSObject * js::Allocate(ExclusiveContext *cx, AllocKind kind, size_t nDynamicSlots, InitialHeap heap, const Class *clasp) { static_assert(mozilla::IsConvertible<T *, JSObject *>::value, "must be JSObject derived"); MOZ_ASSERT(kind <= AllocKind::OBJECT_LAST); size_t thingSize = Arena::thingSize(kind); MOZ_ASSERT(thingSize == Arena::thingSize(kind)); MOZ_ASSERT(thingSize >= sizeof(JSObject_Slots0)); static_assert(sizeof(JSObject_Slots0) >= CellSize, "All allocations must be at least the allocator-imposed minimum size."); - if (!CheckAllocatorState<allowGC>(cx, kind)) + // Off-main-thread alloc cannot trigger GC or make runtime assertions. + if (!cx->isJSContext()) + return GCRuntime::tryNewTenuredObject<NoGC>(cx, kind, thingSize, nDynamicSlots); + + JSContext *ncx = cx->asJSContext(); + JSRuntime *rt = ncx->runtime(); + if (!rt->gc.checkAllocatorState<allowGC>(ncx, kind)) return nullptr; - if (cx->isJSContext() && - ShouldNurseryAllocateObject(cx->asJSContext()->nursery(), heap)) - { - JSObject *obj = TryNewNurseryObject<allowGC>(cx->asJSContext(), thingSize, nDynamicSlots, - clasp); + if (ncx->nursery().isEnabled() && heap != TenuredHeap) { + JSObject *obj = rt->gc.tryNewNurseryObject<allowGC>(ncx, thingSize, nDynamicSlots, clasp); if (obj) return obj; // Our most common non-jit allocation path is NoGC; thus, if we fail the // alloc and cannot GC, we *must* return nullptr here so that the caller // will do a CanGC allocation to clear the nursery. Failing to do so will // cause all allocations on this path to land in Tenured, and we will not // get the benefit of the nursery. if (!allowGC) return nullptr; } + return GCRuntime::tryNewTenuredObject<allowGC>(cx, kind, thingSize, nDynamicSlots); +} +template JSObject *js::Allocate<JSObject, NoGC>(ExclusiveContext *cx, gc::AllocKind kind, + size_t nDynamicSlots, gc::InitialHeap heap, + const Class *clasp); +template JSObject *js::Allocate<JSObject, CanGC>(ExclusiveContext *cx, gc::AllocKind kind, + size_t nDynamicSlots, gc::InitialHeap heap, + const Class *clasp); + +// Attempt to allocate a new GC thing out of the nursery. If there is not enough +// room in the nursery or there is an OOM, this method will return nullptr. +template <AllowGC allowGC> +JSObject * +GCRuntime::tryNewNurseryObject(JSContext *cx, size_t thingSize, size_t nDynamicSlots, const Class *clasp) +{ + MOZ_ASSERT(!IsAtomsCompartment(cx->compartment())); + JSObject *obj = nursery.allocateObject(cx, thingSize, nDynamicSlots, clasp); + if (obj) + return obj; + + if (allowGC && !rt->mainThread.suppressGC) { + minorGC(cx, JS::gcreason::OUT_OF_NURSERY); + + // Exceeding gcMaxBytes while tenuring can disable the Nursery. + if (nursery.isEnabled()) { + JSObject *obj = nursery.allocateObject(cx, thingSize, nDynamicSlots, clasp); + MOZ_ASSERT(obj); + return obj; + } + } + return nullptr; +} + +typedef mozilla::UniquePtr<HeapSlot, JS::FreePolicy> UniqueSlots; + +static inline UniqueSlots +MakeSlotArray(ExclusiveContext *cx, size_t count) +{ + HeapSlot *slots = nullptr; + if (count) { + slots = cx->zone()->pod_malloc<HeapSlot>(count); + if (slots) + Debug_SetSlotRangeToCrashOnTouch(slots, count); + } + return UniqueSlots(slots); +} + +template <AllowGC allowGC> +JSObject * +GCRuntime::tryNewTenuredObject(ExclusiveContext *cx, AllocKind kind, size_t thingSize, + size_t nDynamicSlots) +{ HeapSlot *slots = nullptr; if (nDynamicSlots) { slots = cx->zone()->pod_malloc<HeapSlot>(nDynamicSlots); if (MOZ_UNLIKELY(!slots)) return nullptr; Debug_SetSlotRangeToCrashOnTouch(slots, nDynamicSlots); } - JSObject *obj = reinterpret_cast<JSObject *>(cx->arenas()->allocateFromFreeList(kind, thingSize)); - if (!obj) - obj = reinterpret_cast<JSObject *>(GCRuntime::refillFreeListFromAnyThread<allowGC>(cx, kind)); + JSObject *obj = tryNewTenuredThing<JSObject, allowGC>(cx, kind, thingSize); if (obj) obj->setInitialSlotsMaybeNonNative(slots); else js_free(slots); - CheckIncrementalZoneState(cx, obj); - TraceTenuredAlloc(obj, kind); return obj; } -template JSObject *js::Allocate<JSObject, NoGC>(ExclusiveContext *cx, gc::AllocKind kind, - size_t nDynamicSlots, gc::InitialHeap heap, - const Class *clasp); -template JSObject *js::Allocate<JSObject, CanGC>(ExclusiveContext *cx, gc::AllocKind kind, - size_t nDynamicSlots, gc::InitialHeap heap, - const Class *clasp); template <typename T, AllowGC allowGC /* = CanGC */> T * js::Allocate(ExclusiveContext *cx) { static_assert(!mozilla::IsConvertible<T*, JSObject*>::value, "must not be JSObject derived"); static_assert(sizeof(T) >= CellSize, "All allocations must be at least the allocator-imposed minimum size."); AllocKind kind = MapTypeToFinalizeKind<T>::kind; size_t thingSize = sizeof(T); - MOZ_ASSERT(thingSize == Arena::thingSize(kind)); - if (!CheckAllocatorState<allowGC>(cx, kind)) - return nullptr; - T *t = static_cast<T *>(cx->arenas()->allocateFromFreeList(kind, thingSize)); - if (!t) - t = static_cast<T *>(GCRuntime::refillFreeListFromAnyThread<allowGC>(cx, kind)); + if (cx->isJSContext()) { + JSContext *ncx = cx->asJSContext(); + if (!ncx->runtime()->gc.checkAllocatorState<allowGC>(ncx, kind)) + return nullptr; + } - CheckIncrementalZoneState(cx, t); - gc::TraceTenuredAlloc(t, kind); - return t; + return GCRuntime::tryNewTenuredThing<T, allowGC>(cx, kind, thingSize); } #define FOR_ALL_NON_OBJECT_GC_LAYOUTS(macro) \ macro(JS::Symbol) \ macro(JSExternalString) \ macro(JSFatInlineString) \ macro(JSScript) \ macro(JSString) \ @@ -249,8 +238,200 @@ js::Allocate(ExclusiveContext *cx) macro(js::Shape) \ macro(js::jit::JitCode) #define DECL_ALLOCATOR_INSTANCES(type) \ template type *js::Allocate<type, NoGC>(ExclusiveContext *cx);\ template type *js::Allocate<type, CanGC>(ExclusiveContext *cx); FOR_ALL_NON_OBJECT_GC_LAYOUTS(DECL_ALLOCATOR_INSTANCES) #undef DECL_ALLOCATOR_INSTANCES + +template <typename T, AllowGC allowGC> +/* static */ T * +GCRuntime::tryNewTenuredThing(ExclusiveContext *cx, AllocKind kind, size_t thingSize) +{ + T *t = reinterpret_cast<T *>(cx->arenas()->allocateFromFreeList(kind, thingSize)); + if (!t) + t = reinterpret_cast<T *>(refillFreeListFromAnyThread<allowGC>(cx, kind)); + + checkIncrementalZoneState(cx, t); + TraceTenuredAlloc(t, kind); + return t; +} + +template <AllowGC allowGC> +/* static */ void * +GCRuntime::refillFreeListFromAnyThread(ExclusiveContext *cx, AllocKind thingKind) +{ + MOZ_ASSERT(cx->arenas()->freeLists[thingKind].isEmpty()); + + if (cx->isJSContext()) + return refillFreeListFromMainThread<allowGC>(cx->asJSContext(), thingKind); + + return refillFreeListOffMainThread(cx, thingKind); +} + +template <AllowGC allowGC> +/* static */ void * +GCRuntime::refillFreeListFromMainThread(JSContext *cx, AllocKind thingKind) +{ + JSRuntime *rt = cx->runtime(); + MOZ_ASSERT(!rt->isHeapBusy(), "allocating while under GC"); + MOZ_ASSERT_IF(allowGC, !rt->currentThreadHasExclusiveAccess()); + + // Try to allocate; synchronize with background GC threads if necessary. + void *thing = tryRefillFreeListFromMainThread(cx, thingKind); + if (MOZ_LIKELY(thing)) + return thing; + + // Perform a last-ditch GC to hopefully free up some memory. + { + // If we are doing a fallible allocation, percolate up the OOM + // instead of reporting it. + if (!allowGC) + return nullptr; + + JS::PrepareForFullGC(rt); + AutoKeepAtoms keepAtoms(cx->perThreadData); + rt->gc.gc(GC_SHRINK, JS::gcreason::LAST_DITCH); + } + + // Retry the allocation after the last-ditch GC. + thing = tryRefillFreeListFromMainThread(cx, thingKind); + if (thing) + return thing; + + // We are really just totally out of memory. + MOZ_ASSERT(allowGC, "A fallible allocation must not report OOM on failure."); + ReportOutOfMemory(cx); + return nullptr; +} + +/* static */ void * +GCRuntime::tryRefillFreeListFromMainThread(JSContext *cx, AllocKind thingKind) +{ + ArenaLists *arenas = cx->arenas(); + Zone *zone = cx->zone(); + + AutoMaybeStartBackgroundAllocation maybeStartBGAlloc; + + void *thing = arenas->allocateFromArena(zone, thingKind, maybeStartBGAlloc); + if (MOZ_LIKELY(thing)) + return thing; + + // Even if allocateFromArena failed due to OOM, a background + // finalization or allocation task may be running freeing more memory + // or adding more available memory to our free pool; wait for them to + // finish, then try to allocate again in case they made more memory + // available. + cx->runtime()->gc.waitBackgroundSweepOrAllocEnd(); + + thing = arenas->allocateFromArena(zone, thingKind, maybeStartBGAlloc); + if (thing) + return thing; + + return nullptr; +} + +/* static */ void * +GCRuntime::refillFreeListOffMainThread(ExclusiveContext *cx, AllocKind thingKind) +{ + ArenaLists *arenas = cx->arenas(); + Zone *zone = cx->zone(); + JSRuntime *rt = zone->runtimeFromAnyThread(); + + AutoMaybeStartBackgroundAllocation maybeStartBGAlloc; + + // If we're off the main thread, we try to allocate once and return + // whatever value we get. We need to first ensure the main thread is not in + // a GC session. + AutoLockHelperThreadState lock; + while (rt->isHeapBusy()) + HelperThreadState().wait(GlobalHelperThreadState::PRODUCER); + + void *thing = arenas->allocateFromArena(zone, thingKind, maybeStartBGAlloc); + if (thing) + return thing; + + ReportOutOfMemory(cx); + return nullptr; +} + +TenuredCell * +ArenaLists::allocateFromArena(JS::Zone *zone, AllocKind thingKind, + AutoMaybeStartBackgroundAllocation &maybeStartBGAlloc) +{ + JSRuntime *rt = zone->runtimeFromAnyThread(); + Maybe<AutoLockGC> maybeLock; + + // See if we can proceed without taking the GC lock. + if (backgroundFinalizeState[thingKind] != BFS_DONE) + maybeLock.emplace(rt); + + ArenaList &al = arenaLists[thingKind]; + ArenaHeader *aheader = al.takeNextArena(); + if (aheader) { + // Empty arenas should be immediately freed. + MOZ_ASSERT(!aheader->isEmpty()); + + return allocateFromArenaInner<HasFreeThings>(zone, aheader, thingKind); + } + + // Parallel threads have their own ArenaLists, but chunks are shared; + // if we haven't already, take the GC lock now to avoid racing. + if (maybeLock.isNothing()) + maybeLock.emplace(rt); + + Chunk *chunk = rt->gc.pickChunk(maybeLock.ref(), maybeStartBGAlloc); + if (!chunk)