Merge inbound to m-c a=merge

This commit is contained in:
Wes Kocher 2015-03-02 12:12:47 -08:00
commit b17feb3f40
326 changed files with 5482 additions and 3359 deletions

View File

@ -32,10 +32,6 @@ static const uint32_t kGenericAccType = 0;
*
* When no Role enum mapping exists for an ARIA role, the role will be exposed
* via the object attribute "xml-roles".
*
* There are no Role enums for the following landmark roles:
* banner, contentinfo, main, navigation, note, search, secondary,
* seealso, breadcrumbs.
*/
static nsRoleMapEntry sWAIRoleMaps[] =
@ -67,7 +63,7 @@ static nsRoleMapEntry sWAIRoleMaps[] =
eNoValue,
eNoAction,
eNoLiveAttr,
kGenericAccType,
eLandmark,
kNoReqStates
},
{ // article
@ -81,6 +77,16 @@ static nsRoleMapEntry sWAIRoleMaps[] =
kNoReqStates,
eReadonlyUntilEditable
},
{ // banner
&nsGkAtoms::banner,
roles::NOTHING,
kUseNativeRole,
eNoValue,
eNoAction,
eNoLiveAttr,
eLandmark,
kNoReqStates
},
{ // button
&nsGkAtoms::button,
roles::PUSHBUTTON,
@ -129,6 +135,26 @@ static nsRoleMapEntry sWAIRoleMaps[] =
eARIAReadonly,
eARIAOrientation
},
{ // complementary
&nsGkAtoms::complementary,
roles::NOTHING,
kUseNativeRole,
eNoValue,
eNoAction,
eNoLiveAttr,
eLandmark,
kNoReqStates
},
{ // contentinfo
&nsGkAtoms::contentinfo,
roles::NOTHING,
kUseNativeRole,
eNoValue,
eNoAction,
eNoLiveAttr,
eLandmark,
kNoReqStates
},
{ // dialog
&nsGkAtoms::dialog,
roles::DIALOG,
@ -167,7 +193,7 @@ static nsRoleMapEntry sWAIRoleMaps[] =
eNoValue,
eNoAction,
eNoLiveAttr,
kGenericAccType,
eLandmark,
kNoReqStates
},
{ // grid
@ -290,6 +316,16 @@ static nsRoleMapEntry sWAIRoleMaps[] =
kGenericAccType,
kNoReqStates
},
{ // main
&nsGkAtoms::main,
roles::NOTHING,
kUseNativeRole,
eNoValue,
eNoAction,
eNoLiveAttr,
eLandmark,
kNoReqStates
},
{ // marquee
&nsGkAtoms::marquee,
roles::ANIMATION,
@ -366,6 +402,16 @@ static nsRoleMapEntry sWAIRoleMaps[] =
kNoReqStates,
eARIACheckableBool
},
{ // navigation
&nsGkAtoms::navigation,
roles::NOTHING,
kUseNativeRole,
eNoValue,
eNoAction,
eNoLiveAttr,
eLandmark,
kNoReqStates
},
{ // none
&nsGkAtoms::none,
roles::NOTHING,
@ -496,6 +542,29 @@ static nsRoleMapEntry sWAIRoleMaps[] =
eARIAOrientation,
eARIAReadonly
},
{ // search
&nsGkAtoms::search,
roles::NOTHING,
kUseNativeRole,
eNoValue,
eNoAction,
eNoLiveAttr,
eLandmark,
kNoReqStates
},
{ // searchbox
&nsGkAtoms::searchbox,
roles::ENTRY,
kUseMapRole,
eNoValue,
eActivateAction,
eNoLiveAttr,
kGenericAccType,
kNoReqStates,
eARIAAutoComplete,
eARIAMultiline,
eARIAReadonlyOrEditable
},
{ // separator
&nsGkAtoms::separator_,
roles::SEPARATOR,

View File

@ -74,13 +74,14 @@ enum AccGenericType {
eCombobox = 1 << 3,
eDocument = 1 << 4,
eHyperText = 1 << 5,
eList = 1 << 6,
eListControl = 1 << 7,
eMenuButton = 1 << 8,
eSelect = 1 << 9,
eTable = 1 << 10,
eTableCell = 1 << 11,
eTableRow = 1 << 12,
eLandmark = 1 << 6,
eList = 1 << 7,
eListControl = 1 << 8,
eMenuButton = 1 << 9,
eSelect = 1 << 10,
eTable = 1 << 11,
eTableCell = 1 << 12,
eTableRow = 1 << 13,
eLastAccGenericType = eTableRow
};

View File

@ -37,11 +37,18 @@ nsAccUtils::SetAccAttr(nsIPersistentProperties *aAttributes,
nsIAtom *aAttrName, const nsAString& aAttrValue)
{
nsAutoString oldValue;
nsAutoCString attrName;
aAttributes->SetStringProperty(nsAtomCString(aAttrName), aAttrValue, oldValue);
}
void
nsAccUtils::SetAccAttr(nsIPersistentProperties *aAttributes,
nsIAtom* aAttrName, nsIAtom* aAttrValue)
{
nsAutoString oldValue;
aAttributes->SetStringProperty(nsAtomCString(aAttrName),
nsAtomString(aAttrValue), oldValue);
}
void
nsAccUtils::SetAccGroupAttrs(nsIPersistentProperties *aAttributes,
int32_t aLevel, int32_t aSetSize,

View File

@ -50,6 +50,10 @@ public:
nsIAtom *aAttrName,
const nsAString& aAttrValue);
static void SetAccAttr(nsIPersistentProperties *aAttributes,
nsIAtom* aAttrName,
nsIAtom* aAttrValue);
/**
* Set group attributes ('level', 'setsize', 'posinset').
*/

View File

@ -44,6 +44,15 @@ Accessible::ARIARole()
return ARIATransformRole(mRoleMapEntry->role);
}
inline bool
Accessible::IsSearchbox() const
{
return (mRoleMapEntry && mRoleMapEntry->Is(nsGkAtoms::searchbox)) ||
(mContent->IsHTML(nsGkAtoms::input) &&
mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::type,
nsGkAtoms::textInputType, eCaseMatters));
}
inline bool
Accessible::HasGenericType(AccGenericType aType) const
{

View File

@ -860,14 +860,20 @@ Accessible::Attributes()
if (!HasOwnContent() || !mContent->IsElement())
return attributes.forget();
// 'xml-roles' attribute coming from ARIA.
nsAutoString xmlRoles, unused;
if (mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::role, xmlRoles)) {
attributes->SetStringProperty(NS_LITERAL_CSTRING("xml-roles"),
xmlRoles, unused);
// 'xml-roles' attribute for landmark.
nsIAtom* landmark = LandmarkRole();
if (landmark) {
nsAccUtils::SetAccAttr(attributes, nsGkAtoms::xmlroles, landmark);
} else {
// 'xml-roles' attribute coming from ARIA.
nsAutoString xmlRoles;
if (mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::role, xmlRoles))
nsAccUtils::SetAccAttr(attributes, nsGkAtoms::xmlroles, xmlRoles);
}
// Expose object attributes from ARIA attributes.
nsAutoString unused;
aria::AttrIterator attribIter(mContent);
nsAutoString name, value;
while(attribIter.Next(name, value))
@ -881,6 +887,11 @@ Accessible::Attributes()
// If there is no aria-live attribute then expose default value of 'live'
// object attribute used for ARIA role of this accessible.
if (mRoleMapEntry) {
if (mRoleMapEntry->Is(nsGkAtoms::searchbox)) {
nsAccUtils::SetAccAttr(attributes, nsGkAtoms::textInputType,
NS_LITERAL_STRING("search"));
}
nsAutoString live;
nsAccUtils::GetAccAttr(attributes, nsGkAtoms::live, live);
if (live.IsEmpty()) {
@ -1385,6 +1396,13 @@ Accessible::ARIATransformRole(role aRole)
return aRole;
}
nsIAtom*
Accessible::LandmarkRole() const
{
return mRoleMapEntry && mRoleMapEntry->IsOfType(eLandmark) ?
*(mRoleMapEntry->roleAtom) : nullptr;
}
role
Accessible::NativeRole()
{

View File

@ -230,6 +230,11 @@ public:
*/
mozilla::a11y::role ARIARole();
/**
* Return a landmark role if applied.
*/
virtual nsIAtom* LandmarkRole() const;
/**
* Returns enumerated accessible role from native markup (see constants in
* Role.h). Doesn't take into account ARIA roles.
@ -621,6 +626,8 @@ public:
bool IsRoot() const { return mType == eRootType; }
a11y::RootAccessible* AsRoot();
bool IsSearchbox() const;
bool IsSelect() const { return HasGenericType(eSelect); }
bool IsTable() const { return HasGenericType(eTable); }
@ -1098,7 +1105,7 @@ protected:
static const uint8_t kStateFlagsBits = 9;
static const uint8_t kContextFlagsBits = 2;
static const uint8_t kTypeBits = 6;
static const uint8_t kGenericTypesBits = 13;
static const uint8_t kGenericTypesBits = 14;
/**
* Keep in sync with ChildrenFlags, StateFlags, ContextFlags, and AccTypes.

View File

@ -944,45 +944,13 @@ HyperTextAccessible::NativeAttributes()
if (!HasOwnContent())
return attributes.forget();
// For the html landmark elements we expose them like we do aria landmarks to
// make AT navigation schemes "just work".
nsIAtom* tag = mContent->Tag();
if (tag == nsGkAtoms::nav) {
nsAccUtils::SetAccAttr(attributes, nsGkAtoms::xmlroles,
NS_LITERAL_STRING("navigation"));
} else if (tag == nsGkAtoms::section) {
if (tag == nsGkAtoms::section) {
nsAccUtils::SetAccAttr(attributes, nsGkAtoms::xmlroles,
NS_LITERAL_STRING("region"));
} else if (tag == nsGkAtoms::header || tag == nsGkAtoms::footer) {
// Only map header and footer if they are not descendants
// of an article or section tag.
nsIContent* parent = mContent->GetParent();
while (parent) {
if (parent->Tag() == nsGkAtoms::article ||
parent->Tag() == nsGkAtoms::section)
break;
parent = parent->GetParent();
}
// No article or section elements found.
if (!parent) {
if (tag == nsGkAtoms::header) {
nsAccUtils::SetAccAttr(attributes, nsGkAtoms::xmlroles,
NS_LITERAL_STRING("banner"));
} else if (tag == nsGkAtoms::footer) {
nsAccUtils::SetAccAttr(attributes, nsGkAtoms::xmlroles,
NS_LITERAL_STRING("contentinfo"));
}
}
} else if (tag == nsGkAtoms::aside) {
nsAccUtils::SetAccAttr(attributes, nsGkAtoms::xmlroles,
NS_LITERAL_STRING("complementary"));
} else if (tag == nsGkAtoms::article) {
nsAccUtils::SetAccAttr(attributes, nsGkAtoms::xmlroles,
NS_LITERAL_STRING("article"));
} else if (tag == nsGkAtoms::main) {
nsAccUtils::SetAccAttr(attributes, nsGkAtoms::xmlroles,
NS_LITERAL_STRING("main"));
} else if (tag == nsGkAtoms::time) {
nsAccUtils::SetAccAttr(attributes, nsGkAtoms::xmlroles,
NS_LITERAL_STRING("time"));
@ -997,6 +965,47 @@ HyperTextAccessible::NativeAttributes()
return attributes.forget();
}
nsIAtom*
HyperTextAccessible::LandmarkRole() const
{
// For the html landmark elements we expose them like we do ARIA landmarks to
// make AT navigation schemes "just work".
nsIAtom* tag = mContent->Tag();
if (tag == nsGkAtoms::nav)
return nsGkAtoms::navigation;
if (tag == nsGkAtoms::header || tag == nsGkAtoms::footer) {
// Only map header and footer if they are not descendants of an article
// or section tag.
nsIContent* parent = mContent->GetParent();
while (parent) {
if (parent->Tag() == nsGkAtoms::article ||
parent->Tag() == nsGkAtoms::section)
break;
parent = parent->GetParent();
}
// No article or section elements found.
if (!parent) {
if (tag == nsGkAtoms::header)
return nsGkAtoms::banner;
if (tag == nsGkAtoms::footer) {
return nsGkAtoms::contentinfo;
}
}
return nullptr;
}
if (tag == nsGkAtoms::aside)
return nsGkAtoms::complementary;
if (tag == nsGkAtoms::main)
return nsGkAtoms::main;
return nullptr;
}
int32_t
HyperTextAccessible::OffsetAtPoint(int32_t aX, int32_t aY, uint32_t aCoordType)
{

View File

@ -54,6 +54,7 @@ public:
NS_DECL_ISUPPORTS_INHERITED
// Accessible
virtual nsIAtom* LandmarkRole() const MOZ_OVERRIDE;
virtual int32_t GetLevelInternal() MOZ_OVERRIDE;
virtual already_AddRefed<nsIPersistentProperties> NativeAttributes() MOZ_OVERRIDE;
virtual mozilla::a11y::role NativeRole() MOZ_OVERRIDE;

View File

@ -306,8 +306,13 @@ HTMLTextFieldAccessible::NativeAttributes()
// Expose type for text input elements as it gives some useful context,
// especially for mobile.
nsAutoString type;
if (mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::type, type))
if (mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::type, type)) {
nsAccUtils::SetAccAttr(attributes, nsGkAtoms::textInputType, type);
if (!mRoleMapEntry && type.EqualsLiteral("search")) {
nsAccUtils::SetAccAttr(attributes, nsGkAtoms::xmlroles,
NS_LITERAL_STRING("searchbox"));
}
}
return attributes.forget();
}

View File

@ -430,42 +430,37 @@ GetClosestInterestingAccessible(id anObject)
if (!mGeckoAccessible)
return nil;
// XXX maybe we should cache the subrole.
nsAutoString xmlRoles;
// XXX we don't need all the attributes (see bug 771113)
nsCOMPtr<nsIPersistentProperties> attributes = mGeckoAccessible->Attributes();
if (attributes)
nsAccUtils::GetAccAttr(attributes, nsGkAtoms::xmlroles, xmlRoles);
nsWhitespaceTokenizer tokenizer(xmlRoles);
while (tokenizer.hasMoreTokens()) {
const nsDependentSubstring token(tokenizer.nextToken());
if (token.EqualsLiteral("banner"))
nsIAtom* landmark = mGeckoAccessible->LandmarkRole();
if (landmark) {
if (landmark == nsGkAtoms::application)
return @"AXLandmarkApplication";
if (landmark == nsGkAtoms::banner)
return @"AXLandmarkBanner";
if (token.EqualsLiteral("complementary"))
if (landmark == nsGkAtoms::complementary)
return @"AXLandmarkComplementary";
if (token.EqualsLiteral("contentinfo"))
if (landmark == nsGkAtoms::contentinfo)
return @"AXLandmarkContentInfo";
if (token.EqualsLiteral("main"))
if (landmark == nsGkAtoms::form)
return @"AXLandmarkForm";
if (landmark == nsGkAtoms::main)
return @"AXLandmarkMain";
if (token.EqualsLiteral("navigation"))
if (landmark == nsGkAtoms::navigation)
return @"AXLandmarkNavigation";
if (token.EqualsLiteral("search"))
if (landmark == nsGkAtoms::search)
return @"AXLandmarkSearch";
if (landmark == nsGkAtoms::searchbox)
return @"AXSearchField";
}
switch (mRole) {
case roles::LIST:
return @"AXContentList"; // 10.6+ NSAccessibilityContentListSubrole;
case roles::ENTRY:
if (mGeckoAccessible->IsSearchbox())
return @"AXSearchField";
break;
case roles::DEFINITION_LIST:
return @"AXDefinitionList"; // 10.6+ NSAccessibilityDefinitionListSubrole;
@ -485,6 +480,33 @@ GetClosestInterestingAccessible(id anObject)
return nil;
}
struct RoleDescrMap
{
NSString* role;
const nsString& description;
};
static const RoleDescrMap sRoleDescrMap[] = {
{ @"AXDefinition", NS_LITERAL_STRING("definition") },
{ @"AXLandmarkBanner", NS_LITERAL_STRING("banner") },
{ @"AXLandmarkComplementary", NS_LITERAL_STRING("complementary") },
{ @"AXLandmarkContentInfo", NS_LITERAL_STRING("content") },
{ @"AXLandmarkMain", NS_LITERAL_STRING("main") },
{ @"AXLandmarkNavigation", NS_LITERAL_STRING("navigation") },
{ @"AXLandmarkSearch", NS_LITERAL_STRING("search") },
{ @"AXSearchField", NS_LITERAL_STRING("searchTextField") },
{ @"AXTerm", NS_LITERAL_STRING("term") }
};
struct RoleDescrComparator
{
const NSString* mRole;
explicit RoleDescrComparator(const NSString* aRole) : mRole(aRole) {}
int operator()(const RoleDescrMap& aEntry) const {
return [mRole compare:aEntry.role];
}
};
- (NSString*)roleDescription
{
if (mRole == roles::DOCUMENT)
@ -492,30 +514,13 @@ GetClosestInterestingAccessible(id anObject)
NSString* subrole = [self subrole];
if ((mRole == roles::LISTITEM) && [subrole isEqualToString:@"AXTerm"])
return utils::LocalizedString(NS_LITERAL_STRING("term"));
if ((mRole == roles::PARAGRAPH) && [subrole isEqualToString:@"AXDefinition"])
return utils::LocalizedString(NS_LITERAL_STRING("definition"));
NSString* role = [self role];
// the WAI-ARIA Landmarks
if ([role isEqualToString:NSAccessibilityGroupRole]) {
if ([subrole isEqualToString:@"AXLandmarkBanner"])
return utils::LocalizedString(NS_LITERAL_STRING("banner"));
if ([subrole isEqualToString:@"AXLandmarkComplementary"])
return utils::LocalizedString(NS_LITERAL_STRING("complementary"));
if ([subrole isEqualToString:@"AXLandmarkContentInfo"])
return utils::LocalizedString(NS_LITERAL_STRING("content"));
if ([subrole isEqualToString:@"AXLandmarkMain"])
return utils::LocalizedString(NS_LITERAL_STRING("main"));
if ([subrole isEqualToString:@"AXLandmarkNavigation"])
return utils::LocalizedString(NS_LITERAL_STRING("navigation"));
if ([subrole isEqualToString:@"AXLandmarkSearch"])
return utils::LocalizedString(NS_LITERAL_STRING("search"));
size_t idx = 0;
if (BinarySearchIf(sRoleDescrMap, 0, ArrayLength(sRoleDescrMap),
RoleDescrComparator(subrole), &idx)) {
return utils::LocalizedString(sRoleDescrMap[idx].description);
}
return NSAccessibilityRoleDescription(role, subrole);
return NSAccessibilityRoleDescription([self role], subrole);
}
- (NSString*)title

View File

@ -108,6 +108,9 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=558036
testAttrs("tel", {"text-input-type" : "tel"}, true);
testAttrs("url", {"text-input-type" : "url"}, true);
// ARIA
testAttrs("searchbox", {"text-input-type" : "search"}, true);
// html
testAttrs("radio", {"checkable" : "true"}, true);
testAttrs("checkbox", {"checkable" : "true"}, true);
@ -183,6 +186,11 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=558036
title="aria-hidden false value shouldn't be exposed via object attributes">
Mozilla Bug 838407
</a>
<a target="_blank"
href="https://bugzilla.mozilla.org/show_bug.cgi?id=1121518"
title="ARIA 1.1: Support role 'searchbox'">
Mozilla Bug 1121518
</a>
<p id="display"></p>
<div id="content" style="display: none"></div>
<pre id="test">
@ -242,6 +250,7 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=558036
<input id="search" type="search"/>
<input id="tel" type="tel"/>
<input id="url" type="url"/>
<div id="searchbox" role="searchbox"></div>
<!-- html -->
<input id="radio" type="radio"/>

View File

@ -34,6 +34,8 @@
testAttrs("article", {"xml-roles" : "article"}, true);
testAttrs("main_element", {"xml-roles" : "main"}, true);
testAttrs("search", {"xml-roles" : "searchbox"}, true);
SimpleTest.finish();
}
@ -78,6 +80,11 @@
title="modify HTML5 header and footer accessibility API mapping">
Bug 849624
</a>
<a target="_blank"
href="https://bugzilla.mozilla.org/show_bug.cgi?id=1121518"
title="ARIA 1.1: Support role 'searchbox'">
Bug 1121518
</a>
<p id="display"></p>
<div id="content" style="display: none"></div>
<pre id="test">
@ -101,5 +108,6 @@
<article id="article">article</article>
<main id="main_element">another main area</main>
<input id="search" type="search"/>
</body>
</html>

View File

@ -165,22 +165,22 @@
</a><br>
<a target="_blank"
href="https://bugzilla.mozilla.org/show_bug.cgi?id=648133"
title="fire state change event for aria-busy"
title="fire state change event for aria-busy">
Mozilla Bug 648133
</a><br>
<a target="_blank"
href="https://bugzilla.mozilla.org/show_bug.cgi?id=467143"
title="mixed state change event is fired for focused accessible only"
title="mixed state change event is fired for focused accessible only">
Mozilla Bug 467143
</a>
<a target="_blank"
href="https://bugzilla.mozilla.org/show_bug.cgi?id=989958"
title="Pressed state is not exposed on a button element with aria-pressed attribute"
title="Pressed state is not exposed on a button element with aria-pressed attribute">
Mozilla Bug 989958
</a>
<a target="_blank"
href="https://bugzilla.mozilla.org/show_bug.cgi?id=1136563"
title="Support ARIA 1.1 switch role"
title="Support ARIA 1.1 switch role">
Mozilla Bug 1136563
</a>

View File

@ -56,6 +56,7 @@
testRole("aria_row", ROLE_ROW);
testRole("aria_rowheader", ROLE_ROWHEADER);
testRole("aria_scrollbar", ROLE_SCROLLBAR);
testRole("aria_searchbox", ROLE_ENTRY);
testRole("aria_separator", ROLE_SEPARATOR);
testRole("aria_slider", ROLE_SLIDER);
testRole("aria_spinbutton", ROLE_SPINBUTTON);
@ -181,9 +182,14 @@
</a>
<a target="_blank"
href="https://bugzilla.mozilla.org/show_bug.cgi?id=1136563"
title="Support ARIA 1.1 switch role"
title="Support ARIA 1.1 switch role">
Bug 1136563
</a>
<a target="_blank"
href="https://bugzilla.mozilla.org/show_bug.cgi?id=1121518"
title="Support ARIA 1.1 searchbox role">
Bug 1121518
</a>
<p id="display"></p>
<div id="content" style="display: none"></div>
<pre id="test">
@ -227,6 +233,7 @@
<span id="aria_row" role="row"/>
<span id="aria_rowheader" role="rowheader"/>
<span id="aria_scrollbar" role="scrollbar"/>
<span id="aria_searchbox" role="textbox"/>
<span id="aria_separator" role="separator"/>
<span id="aria_slider" role="slider"/>
<span id="aria_spinbutton" role="spinbutton"/>

View File

@ -349,12 +349,12 @@
</a>
<a target="_blank"
href="https://bugzilla.mozilla.org/show_bug.cgi?id=989958"
title="Pressed state is not exposed on a button element with aria-pressed attribute"
title="Pressed state is not exposed on a button element with aria-pressed attribute">
Mozilla Bug 989958
</a>
<a target="_blank"
href="https://bugzilla.mozilla.org/show_bug.cgi?id=1136563"
title="Support ARIA 1.1 switch role"
title="Support ARIA 1.1 switch role">
Mozilla Bug 1136563
</a>

View File

@ -1760,12 +1760,19 @@ pref("plain_text.wrap_long_lines", true);
pref("dom.debug.propagate_gesture_events_through_content", false);
// The request URL of the GeoLocation backend.
#ifdef RELEASE_BUILD
pref("geo.wifi.uri", "https://www.googleapis.com/geolocation/v1/geolocate?key=%GOOGLE_API_KEY%");
#else
pref("geo.wifi.uri", "https://location.services.mozilla.com/v1/geolocate?key=%MOZILLA_API_KEY%");
#endif
// On Mac, the default geo provider is corelocation.
#ifdef XP_MACOSX
#ifdef RELEASE_BUILD
pref("geo.provider.use_corelocation", false);
#else
pref("geo.provider.use_corelocation", true);
#endif
#endif
// Necko IPC security checks only needed for app isolation for cookies/cache/etc:
// currently irrelevant for desktop e10s

View File

@ -1,4 +1,4 @@
.. _healthreport_dataformat:
.. _sslerrorreport_dataformat:
==============
Payload Format

View File

@ -1,4 +1,4 @@
.. _sslerrorreport
.. _sslerrorreport:
===================
SSL Error Reporting

View File

@ -981,7 +981,7 @@
// Strip out any null bytes in the content title, since the
// underlying widget implementations of nsWindow::SetTitle pass
// null-terminated strings to system APIs.
var docTitle = aBrowser.contentTitle.replace("\0", "", "g");
var docTitle = aBrowser.contentTitle.replace(/\0/g, "");
if (!docTitle)
docTitle = docElement.getAttribute("titledefault");
@ -3229,8 +3229,7 @@
addonInfo: aMessage.data.addonInfo };
let popup = browser.ownerDocument.getElementById("contentAreaContextMenu");
let event = gContextMenuContentData.event;
let pos = browser.mapScreenCoordinatesFromContent(event.screenX, event.screenY);
popup.openPopupAtScreen(pos.x, pos.y, true);
popup.openPopupAtScreen(event.screenX, event.screenY, true);
break;
}
case "DOMWebNotificationClicked": {

View File

@ -15,7 +15,7 @@ var gTests = [
{ desc: "Searchbar replaces newlines with spaces",
element: document.getElementById('searchbar'),
expected: kTestString.replace('\n',' ','g')
expected: kTestString.replace(/\n/g,' ')
},
];

View File

@ -323,7 +323,7 @@ DistributionCustomizer.prototype = {
for (let key in enumerate(this._ini.getKeys("LocalizablePreferences"))) {
try {
let value = eval(this._ini.getString("LocalizablePreferences", key));
value = value.replace("%LOCALE%", this._locale, "g");
value = value.replace(/%LOCALE%/g, this._locale);
localizedStr.data = "data:text/plain," + key + "=" + value;
defaults.setComplexValue(key, Ci.nsIPrefLocalizedString, localizedStr);
} catch (e) { /* ignore bad prefs and move on */ }

View File

@ -3257,7 +3257,7 @@ let SessionStoreInternal = {
// By creating a regex we reduce overhead and there is only one loop pass
// through either array (cookieHosts and aWinState.cookies).
let hosts = Object.keys(cookieHosts).join("|").replace("\\.", "\\.", "g");
let hosts = Object.keys(cookieHosts).join("|").replace(/\./g, "\\.");
// If we don't actually have any hosts, then we don't want to do anything.
if (!hosts.length)
return;

View File

@ -100,7 +100,7 @@ TabMatcher.prototype = {
tabs = tabs.filter(function TabMatcher__filterAndSortForMatches_filter(tab) {
let name = TabUtils.nameOf(tab);
let url = TabUtils.URLOf(tab);
return name.match(self.term, "i") || url.match(self.term, "i");
return name.match(new RegExp(self.term, "i")) || url.match(new RegExp(self.term, "i"));
});
tabs.sort(function TabMatcher__filterAndSortForMatches_sort(x, y) {
@ -121,7 +121,7 @@ TabMatcher.prototype = {
return tabs.filter(function TabMatcher__filterForUnmatches_filter(tab) {
let name = tab.$tabTitle[0].textContent;
let url = TabUtils.URLOf(tab);
return !name.match(self.term, "i") && !url.match(self.term, "i");
return !name.match(new RegExp(self.term, "i")) && !url.match(new RegExp(self.term, "i"));
});
},

View File

@ -207,7 +207,7 @@ this.BingTranslator.prototype = {
if (root.isSimpleRoot) {
// Workaround for Bing's service problem in which "&" chars in
// plain-text TranslationItems are double-escaped.
result = result.replace("&amp;", "&", "g");
result = result.replace(/&amp;/g, "&");
}
root.parseResult(result);
@ -422,11 +422,11 @@ let BingTokenManager = {
*/
function escapeXML(aStr) {
return aStr.toString()
.replace("&", "&amp;", "g")
.replace('"', "&quot;", "g")
.replace("'", "&apos;", "g")
.replace("<", "&lt;", "g")
.replace(">", "&gt;", "g");
.replace(/&/g, "&amp;")
.replace(/\"/g, "&quot;")
.replace(/\'/g, "&apos;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;");
}
/**

View File

@ -6,7 +6,7 @@ UI Telemetry sends its data as a JSON blob. This document describes the differen
of the JSON blob.
``toolbars``
------------
============
This tracks the state of the user's UI customizations. It has the following properties:
@ -34,15 +34,16 @@ This tracks the state of the user's UI customizations. It has the following prop
- ``countableEvents`` - please refer to the next section.
- ``durations`` - an object mapping descriptions to duration records, which records the amount of
time a user spent doing something. Currently only has one property:
- ``customization`` - how long a user spent customizing the browser. This is an array of
objects, where each object has a ``duration`` property indicating the time in milliseconds,
and a ``bucket`` property indicating a bucket in which the duration info falls.
- ``customization`` - how long a user spent customizing the browser. This is an array of
objects, where each object has a ``duration`` property indicating the time in milliseconds,
and a ``bucket`` property indicating a bucket in which the duration info falls.
.. _UITelemetry_countableEvents:
``countableEvents``
-------------------
===================
Countable events are stored under the ``toolbars`` section. They count the number of times certain
events happen. No timing or other correlating information is stored - purely the number of times
@ -66,23 +67,27 @@ Each bucket is an object with the following properties:
or ``other``, depending on the kind of item clicked. Note that this is not tracked on OS X, where
we can't listen for these events because of the global menubar.
- ``click-bookmarks-menu-button`` is also similar, with the item IDs being replaced by:
- ``menu`` for clicks on the 'menu' part of the item;
- ``add`` for clicks that add a bookmark;
- ``edit`` for clicks that open the panel to edit an existing bookmark;
- ``in-panel`` for clicks when the button is in the menu panel, and clicking it does none of the
- ``menu`` for clicks on the 'menu' part of the item;
- ``add`` for clicks that add a bookmark;
- ``edit`` for clicks that open the panel to edit an existing bookmark;
- ``in-panel`` for clicks when the button is in the menu panel, and clicking it does none of the
above;
- ``customize`` tracks different types of customization events without the ``left``, ``middle`` and
``right`` distinctions. The different events are the following, with each storing a count of the
number of times they occurred:
- ``start`` counts the number of times the user starts customizing;
- ``add`` counts the number of times an item is added somewhere from the palette;
- ``move`` counts the number of times an item is moved somewhere else (but not to the palette);
- ``remove`` counts the number of times an item is removed to the palette;
- ``reset`` counts the number of times the 'restore defaults' button is used;
- ``start`` counts the number of times the user starts customizing;
- ``add`` counts the number of times an item is added somewhere from the palette;
- ``move`` counts the number of times an item is moved somewhere else (but not to the palette);
- ``remove`` counts the number of times an item is removed to the palette;
- ``reset`` counts the number of times the 'restore defaults' button is used;
- ``search`` is an object tracking searches of various types, keyed off the search
location, storing a number indicating how often the respective type of search
has happened.
- There are also two special keys that mean slightly different things.
- ``urlbar-keyword`` records searches that would have been an invalid-protocol
error, but are now keyword searches. They are also counted in the ``urlbar``
keyword (along with all the other urlbar searches).
@ -93,7 +98,8 @@ Each bucket is an object with the following properties:
``UITour``
----------
==========
The UITour API provides ways for pages on trusted domains to safely interact with the browser UI and request it to perform actions such as opening menus and showing highlights over the browser chrome - for the purposes of interactive tours. We track some usage of this API via the ``UITour`` object in the UI Telemetry output.
Each page is able to register itself with an identifier, a ``Page ID``. A list of Page IDs that have been seen over the last 8 weeks is available via ``seenPageIDs``.
@ -107,7 +113,8 @@ Page IDs are also used to identify buckets for :ref:`UITelemetry_countableEvents
``contextmenu``
---------------
===============
We track context menu interactions to figure out which ones are most often used and/or how
effective they are. In the ``contextmenu`` object, we first store things per-bucket. Next, we
divide the following different context menu situations:

View File

@ -761,7 +761,7 @@ var Browser = {
hasher.updateFromStream(stringStream, -1);
let hashASCII = hasher.finish(true);
// Replace '/' with a valid filesystem character
return ("FFTileID_" + hashASCII).replace('/', '_', 'g');
return ("FFTileID_" + hashASCII).replace(/\//g, '_');
},
unpinSite: function browser_unpinSite() {

View File

@ -193,7 +193,7 @@ BrowserCLH.prototype = {
if (searchParam) {
var ss = Components.classes["@mozilla.org/browser/search-service;1"]
.getService(nsIBrowserSearchService);
var submission = ss.defaultEngine.getSubmission(searchParam.replace("\"", "", "g"));
var submission = ss.defaultEngine.getSubmission(searchParam.replace(/\"/g, ""));
uris.push(submission.uri);
}

View File

@ -1,27 +0,0 @@
# 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/.
ICON_FILES := ../shared/icon.png
ICON_DEST = $(FINAL_TARGET)/extensions/{972ce4c6-7e08-4474-a285-3208198ce6fd}
INSTALL_TARGETS += ICON
# By default, the pre-processor used for jar.mn will use "%" as a marker for ".css" files and "#"
# otherwise. This falls apart when a file using one marker needs to include a file with the other
# marker since the pre-processor instructions in the included file will not be processed. The
# following SVG files need to include a file which uses "%" as the marker so we invoke the pre-
# processor ourselves here with the marker specified. The resulting SVG files will get packaged by
# the processing of the jar file in this directory.
tab-selected-svg: $(srcdir)/../shared/tab-selected.svg
$(call py_action,preprocessor, \
--marker % -D TAB_SIDE=start \
$(ACDEFINES) \
$(srcdir)/../shared/tab-selected.svg -o tab-selected-start.svg)
$(call py_action,preprocessor, \
--marker % -D TAB_SIDE=end \
$(ACDEFINES) \
$(srcdir)/../shared/tab-selected.svg -o tab-selected-end.svg)
.PHONY: tab-selected-svg
export:: tab-selected-svg

View File

@ -7,3 +7,5 @@
DIRS += ['communicator']
JAR_MANIFESTS += ['jar.mn']
include('../tab-svgs.mozbuild')

View File

@ -12,3 +12,5 @@ elif toolkit in ('gtk2', 'gtk3', 'qt'):
DIRS += ['linux']
else:
DIRS += ['windows']
FINAL_TARGET_FILES.extensions['{972ce4c6-7e08-4474-a285-3208198ce6fd}'] += ['shared/icon.png']

View File

@ -1,27 +0,0 @@
# 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/.
ICON_FILES := ../shared/icon.png
ICON_DEST = $(FINAL_TARGET)/extensions/{972ce4c6-7e08-4474-a285-3208198ce6fd}
INSTALL_TARGETS += ICON
# By default, the pre-processor used for jar.mn will use "%" as a marker for ".css" files and "#"
# otherwise. This falls apart when a file using one marker needs to include a file with the other
# marker since the pre-processor instructions in the included file will not be processed. The
# following SVG files need to include a file which uses "%" as the marker so we invoke the pre-
# processor ourselves here with the marker specified. The resulting SVG files will get packaged by
# the processing of the jar file in this directory.
tab-selected-svg: $(srcdir)/../shared/tab-selected.svg
$(call py_action,preprocessor, \
--marker % -D TAB_SIDE=start \
$(ACDEFINES) \
$(srcdir)/../shared/tab-selected.svg -o tab-selected-start.svg)
$(call py_action,preprocessor, \
--marker % -D TAB_SIDE=end \
$(ACDEFINES) \
$(srcdir)/../shared/tab-selected.svg -o tab-selected-end.svg)
.PHONY: tab-selected-svg
export:: tab-selected-svg

View File

@ -7,3 +7,5 @@
DIRS += ['communicator']
JAR_MANIFESTS += ['jar.mn']
include('../tab-svgs.mozbuild')

View File

@ -0,0 +1,41 @@
#!/usr/bin/env python
# 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/.
import buildconfig
from mozbuild.preprocessor import preprocess
# By default, the pre-processor used for jar.mn will use "%" as a marker
# for ".css" files and "#" otherwise. This falls apart when a file using
# one marker needs to include a file with the other marker since the
# pre-processor instructions in the included file will not be
# processed. The following SVG files need to include a file which uses
# "%" as the marker so we invoke the pre- processor ourselves here with
# the marker specified. The resulting SVG files will get packaged by the
# processing of the jar file in the appropriate directory.
def _do_preprocessing(output_svg, input_svg_file, additional_defines):
additional_defines.update(buildconfig.defines)
preprocess(output=output_svg,
includes=[input_svg_file],
marker='%',
defines=additional_defines)
def tab_side_start(output_svg, input_svg_file):
_do_preprocessing(output_svg, input_svg_file, {'TAB_SIDE': 'start'})
def tab_side_end(output_svg, input_svg_file):
_do_preprocessing(output_svg, input_svg_file, {'TAB_SIDE': 'end'})
def aero_tab_side_start(output_svg, input_svg_file):
_do_preprocessing(output_svg, input_svg_file,
{'TAB_SIDE': 'start',
'WINDOWS_AERO': 1})
def aero_tab_side_end(output_svg, input_svg_file):
_do_preprocessing(output_svg, input_svg_file,
{'TAB_SIDE': 'end',
'WINDOWS_AERO': 1})

View File

@ -0,0 +1,26 @@
# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
# vim: set filetype=python:
# 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/.
script = TOPSRCDIR + '/browser/themes/preprocess-tab-svgs.py'
input = [TOPSRCDIR + '/browser/themes/shared/tab-selected.svg']
# Context variables can't be used inside functions, so hack around that.
generated_files = GENERATED_FILES
def generate_svg(svg_name, script_function):
global generated_files
generated_files += [svg_name]
svg = generated_files[svg_name]
svg.script = script + ':' + script_function
svg.inputs = input
generate_svg('tab-selected-end.svg', 'tab_side_end')
generate_svg('tab-selected-start.svg', 'tab_side_start')
if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'windows':
# Same as above, but for aero.
generate_svg('tab-selected-end-aero.svg', 'aero_tab_side_end')
generate_svg('tab-selected-start-aero.svg', 'aero_tab_side_start')

View File

@ -1,36 +0,0 @@
# 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/.
ICON_FILES := ../shared/icon.png
ICON_DEST = $(FINAL_TARGET)/extensions/{972ce4c6-7e08-4474-a285-3208198ce6fd}
INSTALL_TARGETS += ICON
# By default, the pre-processor used for jar.mn will use "%" as a marker for ".css" files and "#"
# otherwise. This falls apart when a file using one marker needs to include a file with the other
# marker since the pre-processor instructions in the included file will not be processed. The
# following SVG files need to include a file which uses "%" as the marker so we invoke the pre-
# processor ourselves here with the marker specified. The resulting SVG files will get packaged by
# the processing of the jar file in this directory.
tab-selected-svg: $(srcdir)/../shared/tab-selected.svg
$(call py_action,preprocessor, \
--marker % -D TAB_SIDE=start \
$(ACDEFINES) \
$(srcdir)/../shared/tab-selected.svg -o tab-selected-start.svg)
$(call py_action,preprocessor, \
--marker % -D TAB_SIDE=end \
$(ACDEFINES) \
$(srcdir)/../shared/tab-selected.svg -o tab-selected-end.svg)
# Same as above for aero.
$(call py_action,preprocessor, \
--marker % -D TAB_SIDE=start -D WINDOWS_AERO \
$(ACDEFINES) \
$(srcdir)/../shared/tab-selected.svg -o tab-selected-start-aero.svg)
$(call py_action,preprocessor, \
--marker % -D TAB_SIDE=end -D WINDOWS_AERO \
$(ACDEFINES) \
$(srcdir)/../shared/tab-selected.svg -o tab-selected-end-aero.svg)
.PHONY: tab-selected-svg
export:: tab-selected-svg

View File

@ -7,3 +7,5 @@
DIRS += ['communicator']
JAR_MANIFESTS += ['jar.mn']
include('../tab-svgs.mozbuild')

View File

@ -21,6 +21,8 @@ are no conflicting variables in those source files.
``SOURCES`` and ``UNIFIED_SOURCES`` are lists which must be appended to, and
each append requires the given list to be alphanumerically ordered.
.. code-block:: python
UNIFIED_SOURCES += [
'FirstSource.cpp',
'SecondSource.cpp',
@ -41,6 +43,8 @@ Static Libraries
To build a static library, other than defining the source files (see above), one
just needs to define a library name with the ``Library`` template.
.. code-block:: python
Library('foo')
The library file name will be ``libfoo.a`` on UNIX systems and ``foo.lib`` on
@ -50,12 +54,16 @@ If the static library needs to aggregate other static libraries, a list of
``Library`` names can be added to the ``USE_LIBS`` variable. Like ``SOURCES``, it
requires the appended list to be alphanumerically ordered.
.. code-block:: python
USE_LIBS += ['bar', 'baz']
If there are multiple directories containing the same ``Library`` name, it is
possible to disambiguate by prefixing with the path to the wanted one (relative
or absolute):
.. code-block:: python
USE_LIBS += [
'/path/from/topsrcdir/to/bar',
'../relative/baz',
@ -82,6 +90,8 @@ required libraries to ``USE_LIBS`` for the bigger one, it is possible to tell
the build system that the library built in the current directory is meant to
be linked to that bigger library, with the ``FINAL_LIBRARY`` variable.
.. code-block:: python
FINAL_LIBRARY = 'xul'
The ``FINAL_LIBRARY`` value must match a unique ``Library`` name somewhere
@ -98,6 +108,8 @@ Sometimes, we want shared libraries, a.k.a. dynamic libraries. Such libraries
are defined similarly to static libraries, using the ``SharedLibrary`` template
instead of ``Library``.
.. code-block:: python
SharedLibrary('foo')
When this template is used, no static library is built. See further below to
@ -113,6 +125,8 @@ systems.
On OSX, one may want to create a special kind of dynamic library: frameworks.
This is done with the ``Framework`` template.
.. code-block:: python
Framework('foo')
With a ``Framework`` name of ``foo``, the framework file name will be ``foo``.
@ -126,6 +140,8 @@ Executables
Executables, a.k.a. programs, are, in the simplest form, defined with the
``Program`` template.
.. code-block:: python
Program('foobar')
On UNIX systems, the executable file name will be ``foobar``, while on Windows,
@ -138,6 +154,8 @@ names.
In some cases, we want to create an executable per source file in the current
directory, in which case we can use the ``SimplePrograms`` template
.. code-block:: python
SimplePrograms([
'FirstProgram',
'SecondProgram',
@ -148,6 +166,8 @@ Contrary to ``Program``, which requires corresponding ``SOURCES``, when using
corresponding ``sources`` have an extension different from ``.cpp``, it is
possible to specify the proper extension:
.. code-block:: python
SimplePrograms([
'ThirdProgram',
'FourthProgram',
@ -170,6 +190,8 @@ Programs and libraries usually need to link with system libraries, such as a
widget toolkit, etc. Those required dependencies can be given with the
``OS_LIBS`` variable.
.. code-block:: python
OS_LIBS += [
'foo',
'bar',
@ -182,6 +204,8 @@ For convenience with ``pkg-config``, ``OS_LIBS`` can also take linker flags
such as ``-L/some/path`` and ``-llib``, such that it is possible to directly
assign ``LIBS`` variables from ``CONFIG``, such as:
.. code-block:: python
OS_LIBS += CONFIG['MOZ_PANGO_LIBS']
(assuming ``CONFIG['MOZ_PANGO_LIBS']`` is a list, not a string)
@ -201,6 +225,8 @@ path (like when disambiguating identical ``Library`` names). The same naming
rules apply as other uses of ``USE_LIBS``, so only the library name without
prefix and suffix shall be given.
.. code-block:: python
USE_LIBS += [
'/path/from/topsrcdir/to/third-party/bar',
'../relative/third-party/baz',
@ -217,6 +243,8 @@ Building both static and shared libraries
When both types of libraries are required, one needs to set both
``FORCE_SHARED_LIB`` and ``FORCE_STATIC_LIB`` boolean variables.
.. code-block:: python
FORCE_SHARED_LIB = True
FORCE_STATIC_LIB = True
@ -227,6 +255,8 @@ than the name given to the ``Library`` template.
The ``STATIC_LIBRARY_NAME`` and ``SHARED_LIBRARY_NAME`` variables can be used
to change either the static or the shared library name.
.. code-block:: python
Library('foo')
STATIC_LIBRARY_NAME = 'foo_s'
@ -236,6 +266,8 @@ With the above, on Windows, ``foo_s.lib`` will be the static library,
In some cases, for convenience, it is possible to set both
``STATIC_LIBRARY_NAME`` and ``SHARED_LIBRARY_NAME``. For example:
.. code-block:: python
Library('mylib')
STATIC_LIBRARY_NAME = 'mylib_s'
SHARED_LIBRARY_NAME = CONFIG['SHARED_NAME']
@ -248,6 +280,8 @@ When refering to a ``Library`` name building both types of libraries in
it is wanted to link the static version, in which case the ``Library`` name
needs to be prefixed with ``static:`` in ``USE_LIBS``
::
a/moz.build:
Library('mylib')
FORCE_SHARED_LIB = True
@ -272,6 +306,8 @@ linking to a library with a ``SONAME``, the resulting library or program will
have a dependency on the library with the name corresponding to the ``SONAME``
instead of the ``Library`` name. This only impacts ELF systems.
::
a/moz.build:
Library('mylib')
b/moz.build:

View File

@ -1741,14 +1741,14 @@ ilen = dnaInput.length;
dnaInput = dnaInput.replace(/>.*\n|\n/g,"")
clen = dnaInput.length
var dnaOutputString;
var dnaOutputString = "";
for(i in seqs)
dnaOutputString += seqs[i].source + " " + (dnaInput.match(seqs[i]) || []).length + "\n";
// match returns null if no matches, so replace with empty
for(k in subs)
dnaInput = dnaInput.replace(k, subs[k], "g")
dnaInput = dnaInput.replace(k, subs[k]) // FIXME: Would like this to be a global substitution in a future version of SunSpider.
// search string, replacement string, flags

View File

@ -7217,6 +7217,9 @@ if test -z "$MOZ_MEMORY"; then
esac
else
AC_DEFINE(MOZ_MEMORY)
if test -n "$NIGHTLY_BUILD"; then
MOZ_JEMALLOC3=1
fi
if test -n "$MOZ_JEMALLOC3"; then
AC_DEFINE(MOZ_JEMALLOC3)
fi

View File

@ -621,14 +621,14 @@ function do_single_test_run() {
if (couldDoKeywordLookup) {
if (expectKeywordLookup) {
if (!affectedByWhitelist || (affectedByWhitelist && !inWhitelist)) {
let urlparamInput = encodeURIComponent(sanitize(testInput)).replace("%20", "+", "g");
let urlparamInput = encodeURIComponent(sanitize(testInput)).replace(/%20/g, "+");
// If the input starts with `?`, then info.preferredURI.spec will omit it
// In order to test this behaviour, remove `?` only if it is the first character
if (urlparamInput.startsWith("%3F")) {
urlparamInput = urlparamInput.replace("%3F", "");
}
let searchURL = kSearchEngineURL.replace("{searchTerms}", urlparamInput);
let spec = info.preferredURI.spec.replace("%27", "'", "g");
let spec = info.preferredURI.spec.replace(/%27/g, "'");
do_check_eq(spec, searchURL);
} else {
do_check_eq(info.preferredURI, null);

View File

@ -1,3 +1,4 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
@ -8,18 +9,11 @@
#include <stdint.h>
#include "nsAttrValue.h"
#include "mozilla/Attributes.h"
struct MiscContainer;
namespace mozilla {
template<>
struct HasDangerousPublicDestructor<MiscContainer>
{
static const bool value = true;
};
}
struct MiscContainer
struct MiscContainer MOZ_FINAL
{
typedef nsAttrValue::ValueType ValueType;
@ -70,6 +64,10 @@ struct MiscContainer
mValue.mCached = 0;
}
protected:
// Only nsAttrValue should be able to delete us.
friend class nsAttrValue;
~MiscContainer()
{
if (IsRefCounted()) {
@ -79,6 +77,7 @@ struct MiscContainer
MOZ_COUNT_DTOR(MiscContainer);
}
public:
bool GetString(nsAString& aString) const;
inline bool IsRefCounted() const

View File

@ -914,8 +914,7 @@ nsFrameLoader::ShowRemoteFrame(const nsIntSize& size,
// Don't show remote iframe if we are waiting for the completion of reflow.
if (!aFrame || !(aFrame->GetStateBits() & NS_FRAME_FIRST_REFLOW)) {
nsIntPoint chromeDisp = aFrame->GetChromeDisplacement();
mRemoteBrowser->UpdateDimensions(dimensions, size, chromeDisp);
mRemoteBrowser->UpdateDimensions(dimensions, size);
}
}
@ -1960,8 +1959,7 @@ nsFrameLoader::UpdatePositionAndSize(nsSubDocumentFrame *aIFrame)
nsIntSize size = aIFrame->GetSubdocumentSize();
nsIntRect dimensions;
NS_ENSURE_SUCCESS(GetWindowDimensions(dimensions), NS_ERROR_FAILURE);
nsIntPoint chromeDisp = aIFrame->GetChromeDisplacement();
mRemoteBrowser->UpdateDimensions(dimensions, size, chromeDisp);
mRemoteBrowser->UpdateDimensions(dimensions, size);
}
return NS_OK;
}

View File

@ -227,6 +227,9 @@ public:
void ActivateUpdateHitRegion();
void DeactivateUpdateHitRegion();
// Properly retrieves documentSize of any subdocument type.
nsresult GetWindowDimensions(nsIntRect& aRect);
private:
void SetOwnerContent(mozilla::dom::Element* aContent);
@ -282,9 +285,6 @@ private:
nsresult MaybeCreateDocShell();
nsresult EnsureMessageManager();
// Properly retrieves documentSize of any subdocument type.
nsresult GetWindowDimensions(nsIntRect& aRect);
// Updates the subdocument position and size. This gets called only
// when we have our own in-process DocShell.
void UpdateBaseWindowPositionAndSize(nsSubDocumentFrame *aIFrame);

View File

@ -514,7 +514,7 @@ nsFrameMessageManager::GetDelayedScripts(JSContext* aCx, JS::MutableHandle<JS::V
pair = JS_NewArrayObject(aCx, pairElts);
NS_ENSURE_TRUE(pair, NS_ERROR_OUT_OF_MEMORY);
NS_ENSURE_TRUE(JS_SetElement(aCx, array, i, pair),
NS_ENSURE_TRUE(JS_DefineElement(aCx, array, i, pair, JSPROP_ENUMERATE),
NS_ERROR_OUT_OF_MEMORY);
}
@ -696,7 +696,7 @@ nsFrameMessageManager::SendMessage(const nsAString& aMessageName,
retval[i].Length(), &ret)) {
return NS_ERROR_UNEXPECTED;
}
NS_ENSURE_TRUE(JS_SetElement(aCx, dataArray, i, ret),
NS_ENSURE_TRUE(JS_DefineElement(aCx, dataArray, i, ret, JSPROP_ENUMERATE),
NS_ERROR_OUT_OF_MEMORY);
}

View File

@ -2256,14 +2256,17 @@ GK_ATOM(aria_valuemax, "aria-valuemax")
GK_ATOM(aria_valuetext, "aria-valuetext")
GK_ATOM(AreaFrame, "AreaFrame")
GK_ATOM(auto_generated, "auto-generated")
GK_ATOM(banner, "banner")
GK_ATOM(checkable, "checkable")
GK_ATOM(choices, "choices")
GK_ATOM(columnheader, "columnheader")
GK_ATOM(complementary, "complementary")
GK_ATOM(containerAtomic, "container-atomic")
GK_ATOM(containerBusy, "container-busy")
GK_ATOM(containerLive, "container-live")
GK_ATOM(containerLiveRole, "container-live-role")
GK_ATOM(containerRelevant, "container-relevant")
GK_ATOM(contentinfo, "contentinfo")
GK_ATOM(cycles, "cycles")
GK_ATOM(datatable, "datatable")
GK_ATOM(directory, "directory")
@ -2284,6 +2287,7 @@ GK_ATOM(menuitemcheckbox, "menuitemcheckbox")
GK_ATOM(menuitemradio, "menuitemradio")
GK_ATOM(mixed, "mixed")
GK_ATOM(multiline, "multiline")
GK_ATOM(navigation, "navigation")
GK_ATOM(password, "password")
GK_ATOM(posinset, "posinset")
GK_ATOM(presentation, "presentation")
@ -2291,6 +2295,8 @@ GK_ATOM(progressbar, "progressbar")
GK_ATOM(region, "region")
GK_ATOM(rowgroup, "rowgroup")
GK_ATOM(rowheader, "rowheader")
GK_ATOM(search, "search")
GK_ATOM(searchbox, "searchbox")
GK_ATOM(select1, "select1")
GK_ATOM(setsize, "setsize")
GK_ATOM(spelling, "spelling")

View File

@ -9184,6 +9184,8 @@ nsGlobalWindow::ShowModalDialog(const nsAString& aUrl, nsIVariant* aArgument,
return nullptr;
}
Telemetry::Accumulate(Telemetry::DOM_WINDOW_SHOWMODALDIALOG_USED, true);
nsRefPtr<DialogValueHolder> argHolder =
new DialogValueHolder(nsContentUtils::SubjectPrincipal(), aArgument);

View File

@ -27,8 +27,8 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=902847
var range = document.createRange();
range.selectNodeContents(element);
encoder.setRange(range);
return encoder.encodeToString().replace('\n', '\\n', 'g')
.replace('\r', '\\r', 'g');
return encoder.encodeToString().replace(/\n/g, '\\n')
.replace(/\r/g, '\\r');
}
// Test cases.

View File

@ -1941,15 +1941,15 @@ struct FakeString {
return reinterpret_cast<const nsString*>(this);
}
nsAString* ToAStringPtr() {
return reinterpret_cast<nsString*>(this);
}
operator const nsAString& () const {
operator const nsAString& () const {
return *reinterpret_cast<const nsString*>(this);
}
private:
nsAString* ToAStringPtr() {
return reinterpret_cast<nsString*>(this);
}
nsString::char_type* mData;
nsString::size_type mLength;
uint32_t mFlags;
@ -1965,6 +1965,8 @@ private:
mData = const_cast<nsString::char_type*>(aData);
}
friend class NonNull<nsAString>;
// A class to use for our static asserts to ensure our object layout
// matches that of nsString.
class StringAsserter;

View File

@ -42,11 +42,19 @@ if (!('BrowserElementIsPreloaded' in this)) {
}
}
if (docShell.asyncPanZoomEnabled === false) {
Services.scriptloader.loadSubScript("chrome://global/content/BrowserElementPanningAPZDisabled.js");
ContentPanningAPZDisabled.init();
}
Services.scriptloader.loadSubScript("chrome://global/content/BrowserElementPanning.js");
ContentPanning.init();
Services.scriptloader.loadSubScript("chrome://global/content/BrowserElementChildPreload.js");
} else {
if (docShell.asyncPanZoomEnabled === false) {
ContentPanningAPZDisabled.init();
}
ContentPanning.init();
}

View File

@ -12,8 +12,6 @@ let { classes: Cc, interfaces: Ci, results: Cr, utils: Cu } = Components;
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/Geometry.jsm");
var global = this;
const kObservedEvents = [
"BEC:ShownModalPrompt",
"Activity:Success",
@ -21,21 +19,7 @@ const kObservedEvents = [
];
const ContentPanning = {
// Are we listening to touch or mouse events?
watchedEventsType: '',
// Are mouse events being delivered to this content along with touch
// events, in violation of spec?
hybridEvents: false,
init: function cp_init() {
// If APZ is enabled, we do active element handling in C++
// (see widget/xpwidgets/ActiveElementManager.h), and panning
// itself in APZ, so we don't need to handle any touch events here.
if (docShell.asyncPanZoomEnabled === false) {
this._setupListenersForPanning();
}
addEventListener("unload",
this._unloadHandler.bind(this),
/* useCapture = */ false,
@ -49,441 +33,16 @@ const ContentPanning = {
});
},
_setupListenersForPanning: function cp_setupListenersForPanning() {
let events;
if (content.TouchEvent) {
events = ['touchstart', 'touchend', 'touchmove'];
this.watchedEventsType = 'touch';
#ifdef MOZ_WIDGET_GONK
// The gonk widget backend does not deliver mouse events per
// spec. Third-party content isn't exposed to this behavior,
// but that behavior creates some extra work for us here.
let appInfo = Cc["@mozilla.org/xre/app-info;1"];
let isParentProcess =
!appInfo || appInfo.getService(Ci.nsIXULRuntime)
.processType == Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT;
this.hybridEvents = isParentProcess;
#endif
} else {
// Touch events aren't supported, so fall back on mouse.
events = ['mousedown', 'mouseup', 'mousemove'];
this.watchedEventsType = 'mouse';
}
let els = Cc["@mozilla.org/eventlistenerservice;1"]
.getService(Ci.nsIEventListenerService);
events.forEach(function(type) {
// Using the system group for mouse/touch events to avoid
// missing events if .stopPropagation() has been called.
els.addSystemEventListener(global, type,
this.handleEvent.bind(this),
/* useCapture = */ false);
}.bind(this));
},
handleEvent: function cp_handleEvent(evt) {
// Ignore events targeting an oop <iframe mozbrowser> since those will be
// handle by the BrowserElementPanning.js instance in the child process.
if (evt.target instanceof Ci.nsIMozBrowserFrame) {
return;
}
// For in-process <iframe mozbrowser> the events are not targetting
// directly the container iframe element, but some node of the document.
// So, the BrowserElementPanning instance of the system app will receive
// the sequence of touch events, as well as the BrowserElementPanning
// instance in the targetted app.
// As a result, multiple mozbrowser iframes will try to interpret the
// sequence of touch events, which may results into multiple clicks.
let targetWindow = evt.target.ownerDocument.defaultView;
let frameElement = targetWindow.frameElement;
while (frameElement) {
targetWindow = frameElement.ownerDocument.defaultView;
frameElement = targetWindow.frameElement;
}
if (content !== targetWindow) {
return;
}
if (evt.defaultPrevented || evt.multipleActionsPrevented) {
// clean up panning state even if touchend/mouseup has been preventDefault.
if(evt.type === 'touchend' || evt.type === 'mouseup') {
if (this.dragging &&
(this.watchedEventsType === 'mouse' ||
this.findPrimaryPointer(evt.changedTouches))) {
this._finishPanning();
}
}
return;
}
switch (evt.type) {
case 'mousedown':
case 'touchstart':
this.onTouchStart(evt);
break;
case 'mousemove':
case 'touchmove':
this.onTouchMove(evt);
break;
case 'mouseup':
case 'touchend':
this.onTouchEnd(evt);
break;
case 'click':
evt.stopPropagation();
evt.preventDefault();
let target = evt.target;
let view = target.ownerDocument ? target.ownerDocument.defaultView
: target;
view.removeEventListener('click', this, true, true);
break;
}
},
observe: function cp_observe(subject, topic, data) {
this._resetHover();
},
position: new Point(0 , 0),
findPrimaryPointer: function cp_findPrimaryPointer(touches) {
if (!('primaryPointerId' in this))
return null;
for (let i = 0; i < touches.length; i++) {
if (touches[i].identifier === this.primaryPointerId) {
return touches[i];
}
}
return null;
},
onTouchStart: function cp_onTouchStart(evt) {
let screenX, screenY;
if (this.watchedEventsType == 'touch') {
if ('primaryPointerId' in this || evt.touches.length >= 2) {
this._resetActive();
return;
}
let firstTouch = evt.changedTouches[0];
this.primaryPointerId = firstTouch.identifier;
this.pointerDownTarget = firstTouch.target;
screenX = firstTouch.screenX;
screenY = firstTouch.screenY;
} else {
this.pointerDownTarget = evt.target;
screenX = evt.screenX;
screenY = evt.screenY;
}
this.dragging = true;
this.panning = false;
let oldTarget = this.target;
[this.target, this.scrollCallback] = this.getPannable(this.pointerDownTarget);
// If we have a pointer down target, we may need to fill in for EventStateManager
// in setting the active state on the target element. Set a timer to
// ensure the pointer-down target is active. (If it's already
// active, the timer is a no-op.)
if (this.pointerDownTarget !== null) {
// If there's no possibility this is a drag/pan, activate now.
// Otherwise wait a little bit to see if the gesture isn't a
// tap.
if (this.target === null) {
this.notify(this._activationTimer);
} else {
this._activationTimer.initWithCallback(this,
this._activationDelayMs,
Ci.nsITimer.TYPE_ONE_SHOT);
}
}
// If there is a pan animation running (from a previous pan gesture) and
// the user touch back the screen, stop this animation immediatly and
// prevent the possible click action if the touch happens on the same
// target.
this.preventNextClick = false;
if (KineticPanning.active) {
KineticPanning.stop();
if (oldTarget && oldTarget == this.target)
this.preventNextClick = true;
}
this.position.set(screenX, screenY);
KineticPanning.reset();
KineticPanning.record(new Point(0, 0), evt.timeStamp);
// We prevent start events to avoid sending a focus event at the end of this
// touch series. See bug 889717.
if ((this.panning || this.preventNextClick)) {
evt.preventDefault();
}
},
onTouchEnd: function cp_onTouchEnd(evt) {
let touch = null;
if (!this.dragging ||
(this.watchedEventsType == 'touch' &&
!(touch = this.findPrimaryPointer(evt.changedTouches)))) {
return;
}
// !isPan() and evt.detail should always give the same answer here
// since they use the same heuristics, but use the native gecko
// computation when possible.
//
// NB: when we're using touch events, then !KineticPanning.isPan()
// => this.panning, so we'll never attempt to block the click
// event. That's OK however, because we won't fire a synthetic
// click when we're using touch events and this touch series
// wasn't a "tap" gesture.
let click = (this.watchedEventsType == 'mouse') ?
evt.detail : !KineticPanning.isPan();
// Additionally, if we're seeing non-compliant hybrid events, a
// "real" click will be generated if we started and ended on the
// same element.
if (this.hybridEvents) {
let target =
content.document.elementFromPoint(touch.clientX, touch.clientY);
click |= (target === this.pointerDownTarget);
}
if (this.target && click && (this.panning || this.preventNextClick)) {
if (this.hybridEvents) {
let target = this.target;
let view = target.ownerDocument ? target.ownerDocument.defaultView
: target;
view.addEventListener('click', this, true, true);
} else {
// We prevent end events to avoid sending a focus event. See bug 889717.
evt.preventDefault();
}
} else if (this.target && click && !this.panning) {
this.notify(this._activationTimer);
}
this._finishPanning();
// Now that we're done, avoid entraining the thing we just panned.
this.pointerDownTarget = null;
},
onTouchMove: function cp_onTouchMove(evt) {
if (!this.dragging)
return;
let screenX, screenY;
if (this.watchedEventsType == 'touch') {
let primaryTouch = this.findPrimaryPointer(evt.changedTouches);
if (evt.touches.length > 1 || !primaryTouch)
return;
screenX = primaryTouch.screenX;
screenY = primaryTouch.screenY;
} else {
screenX = evt.screenX;
screenY = evt.screenY;
}
let current = this.position;
let delta = new Point(screenX - current.x, screenY - current.y);
current.set(screenX, screenY);
KineticPanning.record(delta, evt.timeStamp);
let isPan = KineticPanning.isPan();
// If we've detected a pan gesture, cancel the active state of the
// current target.
if (!this.panning && isPan) {
this._resetActive();
}
// There's no possibility of us panning anything.
if (!this.scrollCallback) {
return;
}
// Scroll manually.
this.scrollCallback(delta.scale(-1));
if (!this.panning && isPan) {
this.panning = true;
this._activationTimer.cancel();
}
if (this.panning) {
// Only do this when we're actually executing a pan gesture.
// Otherwise synthetic mouse events will be canceled.
evt.stopPropagation();
evt.preventDefault();
}
},
// nsITimerCallback
notify: function cp_notify(timer) {
this._setActive(this.pointerDownTarget);
},
onKineticBegin: function cp_onKineticBegin(evt) {
},
onKineticPan: function cp_onKineticPan(delta) {
return !this.scrollCallback(delta);
},
onKineticEnd: function cp_onKineticEnd() {
if (!this.dragging)
this.scrollCallback = null;
},
getPannable: function cp_getPannable(node) {
let pannableNode = this._findPannable(node);
if (pannableNode) {
return [pannableNode, this._generateCallback(pannableNode)];
}
return [null, null];
},
_findPannable: function cp_findPannable(node) {
if (!(node instanceof Ci.nsIDOMHTMLElement) || node.tagName == 'HTML') {
return null;
}
let nodeContent = node.ownerDocument.defaultView;
while (!(node instanceof Ci.nsIDOMHTMLBodyElement)) {
let style = nodeContent.getComputedStyle(node, null);
let overflow = [style.getPropertyValue('overflow'),
style.getPropertyValue('overflow-x'),
style.getPropertyValue('overflow-y')];
let rect = node.getBoundingClientRect();
let isAuto = (overflow.indexOf('auto') != -1 &&
(rect.height < node.scrollHeight ||
rect.width < node.scrollWidth));
let isScroll = (overflow.indexOf('scroll') != -1);
let isScrollableTextarea = (node.tagName == 'TEXTAREA' &&
(node.scrollHeight > node.clientHeight ||
node.scrollWidth > node.clientWidth ||
('scrollLeftMax' in node && node.scrollLeftMax > 0) ||
('scrollTopMax' in node && node.scrollTopMax > 0)));
if (isScroll || isAuto || isScrollableTextarea) {
return node;
}
node = node.parentNode;
}
if (nodeContent.scrollMaxX || nodeContent.scrollMaxY) {
return nodeContent;
}
if (nodeContent.frameElement) {
return this._findPannable(nodeContent.frameElement);
}
return null;
},
_generateCallback: function cp_generateCallback(root) {
let firstScroll = true;
let target;
let current;
let win, doc, htmlNode, bodyNode;
function doScroll(node, delta) {
if (node instanceof Ci.nsIDOMHTMLElement) {
return node.scrollByNoFlush(delta.x, delta.y);
} else if (node instanceof Ci.nsIDOMWindow) {
win = node;
doc = win.document;
// "overflow:hidden" on either the <html> or the <body> node should
// prevent the user from scrolling the root viewport.
if (doc instanceof Ci.nsIDOMHTMLDocument) {
htmlNode = doc.documentElement;
bodyNode = doc.body;
if (win.getComputedStyle(htmlNode, null).overflowX == "hidden" ||
win.getComputedStyle(bodyNode, null).overflowX == "hidden") {
delta.x = 0;
}
if (win.getComputedStyle(htmlNode, null).overflowY == "hidden" ||
win.getComputedStyle(bodyNode, null).overflowY == "hidden") {
delta.y = 0;
}
}
let oldX = node.scrollX;
let oldY = node.scrollY;
node.scrollBy(delta.x, delta.y);
return (node.scrollX != oldX || node.scrollY != oldY);
}
// If we get here, |node| isn't an HTML element and it's not a window,
// but findPannable apparently thought it was scrollable... What is it?
return false;
}
function targetParent(node) {
return node.parentNode || node.frameElement || null;
}
function scroll(delta) {
current = root;
firstScroll = true;
while (current) {
if (doScroll(current, delta)) {
firstScroll = false;
return true;
}
// TODO The current code looks for possible scrolling regions only if
// this is the first scroll action but this should be more dynamic.
if (!firstScroll) {
return false;
}
current = ContentPanning._findPannable(targetParent(current));
}
// There is nothing scrollable here.
return false;
}
return scroll;
},
get _domUtils() {
delete this._domUtils;
return this._domUtils = Cc['@mozilla.org/inspector/dom-utils;1']
.getService(Ci.inIDOMUtils);
},
get _activationTimer() {
delete this._activationTimer;
return this._activationTimer = Cc["@mozilla.org/timer;1"]
.createInstance(Ci.nsITimer);
},
get _activationDelayMs() {
let delay = Services.prefs.getIntPref('ui.touch_activation.delay_ms');
delete this._activationDelayMs;
return this._activationDelayMs = delay;
},
_resetActive: function cp_resetActive() {
let elt = this.pointerDownTarget || this.target;
let root = elt.ownerDocument || elt.document;
this._setActive(root.documentElement);
},
_resetHover: function cp_resetHover() {
const kStateHover = 0x00000004;
try {
@ -492,11 +51,6 @@ const ContentPanning = {
} catch(e) {}
},
_setActive: function cp_setActive(elt) {
const kStateActive = 0x00000001;
this._domUtils.setContentState(elt, kStateActive);
},
_recvViewportChange: function(data) {
let metrics = data.json;
this._viewport = new Rect(metrics.x, metrics.y,
@ -610,17 +164,6 @@ const ContentPanning = {
return (showing > 0.9 && (ratioW > 0.9 || ratioH > 0.9));
},
_finishPanning: function() {
this.dragging = false;
delete this.primaryPointerId;
this._activationTimer.cancel();
// If there is a scroll action, let's do a manual kinetic panning action.
if (this.panning) {
KineticPanning.start(this);
}
},
_unloadHandler: function() {
kObservedEvents.forEach((topic) => {
Services.obs.removeObserver(this, topic);
@ -628,206 +171,6 @@ const ContentPanning = {
}
};
// Min/max velocity of kinetic panning. This is in pixels/millisecond.
const kMinVelocity = 0.2;
const kMaxVelocity = 6;
// Constants that affect the "friction" of the scroll pane.
const kExponentialC = 1000;
const kPolynomialC = 100 / 1000000;
// How often do we change the position of the scroll pane?
// Too often and panning may jerk near the end.
// Too little and panning will be choppy. In milliseconds.
const kUpdateInterval = 16;
// The numbers of momentums to use for calculating the velocity of the pan.
// Those are taken from the end of the action
const kSamples = 5;
const KineticPanning = {
_position: new Point(0, 0),
_velocity: new Point(0, 0),
_acceleration: new Point(0, 0),
get active() {
return this.target !== null;
},
target: null,
start: function kp_start(target) {
this.target = target;
// Calculate the initial velocity of the movement based on user input
let momentums = this.momentums;
let flick = momentums[momentums.length - 1].time - momentums[0].time < 300;
let distance = new Point(0, 0);
momentums.forEach(function(momentum) {
distance.add(momentum.dx, momentum.dy);
});
function clampFromZero(x, min, max) {
if (x >= 0)
return Math.max(min, Math.min(max, x));
return Math.min(-min, Math.max(-max, x));
}
let elapsed = momentums[momentums.length - 1].time - momentums[0].time;
let velocityX = clampFromZero(distance.x / elapsed, 0, kMaxVelocity);
let velocityY = clampFromZero(distance.y / elapsed, 0, kMaxVelocity);
let velocity = this._velocity;
if (flick) {
// Very fast pan action that does not generate a click are very often pan
// action. If this is a small gesture then it will not move the view a lot
// and so it will be above the minimun threshold and not generate any
// kinetic panning. This does not look on a device since this is often
// a real gesture, so let's lower the velocity threshold for such moves.
velocity.set(velocityX, velocityY);
} else {
velocity.set(Math.abs(velocityX) < kMinVelocity ? 0 : velocityX,
Math.abs(velocityY) < kMinVelocity ? 0 : velocityY);
}
this.momentums = [];
// Set acceleration vector to opposite signs of velocity
function sign(x) {
return x ? (x > 0 ? 1 : -1) : 0;
}
this._acceleration.set(velocity.clone().map(sign).scale(-kPolynomialC));
// Reset the position
this._position.set(0, 0);
this._startAnimation();
this.target.onKineticBegin();
},
stop: function kp_stop() {
this.reset();
if (!this.target)
return;
this.target.onKineticEnd();
this.target = null;
},
reset: function kp_reset() {
this.momentums = [];
this.distance.set(0, 0);
},
momentums: [],
record: function kp_record(delta, timestamp) {
this.momentums.push({ 'time': this._getTime(timestamp),
'dx' : delta.x, 'dy' : delta.y });
// We only need to keep kSamples in this.momentums.
if (this.momentums.length > kSamples) {
this.momentums.shift();
}
this.distance.add(delta.x, delta.y);
},
_getTime: function kp_getTime(time) {
// Touch events generated by the platform or hand-made are defined in
// microseconds instead of milliseconds. Bug 77992 will fix this at the
// platform level.
if (time > Date.now()) {
return Math.floor(time / 1000);
} else {
return time;
}
},
get threshold() {
let dpi = content.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindowUtils)
.displayDPI;
let threshold = Services.prefs.getIntPref('ui.dragThresholdX') / 240 * dpi;
delete this.threshold;
return this.threshold = threshold;
},
distance: new Point(0, 0),
isPan: function cp_isPan() {
return (Math.abs(this.distance.x) > this.threshold ||
Math.abs(this.distance.y) > this.threshold);
},
_startAnimation: function kp_startAnimation() {
let c = kExponentialC;
function getNextPosition(position, v, a, t) {
// Important traits for this function:
// p(t=0) is 0
// p'(t=0) is v0
//
// We use exponential to get a smoother stop, but by itself exponential
// is too smooth at the end. Adding a polynomial with the appropriate
// weight helps to balance
position.set(v.x * Math.exp(-t / c) * -c + a.x * t * t + v.x * c,
v.y * Math.exp(-t / c) * -c + a.y * t * t + v.y * c);
}
let startTime = content.mozAnimationStartTime;
let elapsedTime = 0, targetedTime = 0, averageTime = 0;
let velocity = this._velocity;
let acceleration = this._acceleration;
let position = this._position;
let nextPosition = new Point(0, 0);
let delta = new Point(0, 0);
let callback = (function(timestamp) {
if (!this.target)
return;
// To make animation end fast enough but to keep smoothness, average the
// ideal time frame (smooth animation) with the actual time lapse
// (end fast enough).
// Animation will never take longer than 2 times the ideal length of time.
elapsedTime = timestamp - startTime;
targetedTime += kUpdateInterval;
averageTime = (targetedTime + elapsedTime) / 2;
// Calculate new position.
getNextPosition(nextPosition, velocity, acceleration, averageTime);
delta.set(Math.round(nextPosition.x - position.x),
Math.round(nextPosition.y - position.y));
// Test to see if movement is finished for each component.
if (delta.x * acceleration.x > 0)
delta.x = position.x = velocity.x = acceleration.x = 0;
if (delta.y * acceleration.y > 0)
delta.y = position.y = velocity.y = acceleration.y = 0;
if (velocity.equals(0, 0) || delta.equals(0, 0)) {
this.stop();
return;
}
position.add(delta);
if (this.target.onKineticPan(delta.scale(-1))) {
this.stop();
return;
}
content.mozRequestAnimationFrame(callback);
}).bind(this);
content.mozRequestAnimationFrame(callback);
}
};
const ElementTouchHelper = {
anyElementFromPoint: function(aWindow, aX, aY) {
let cwu = aWindow.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils);

View File

@ -0,0 +1,675 @@
/* -*- tab-width: 2; indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set ts=2 sw=2 sts=2 et: */
/* 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/. */
"use strict";
dump("############################### browserElementPanningAPZDisabled.js loaded\n");
let { classes: Cc, interfaces: Ci, results: Cr, utils: Cu } = Components;
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/Geometry.jsm");
var global = this;
const ContentPanningAPZDisabled = {
// Are we listening to touch or mouse events?
watchedEventsType: '',
// Are mouse events being delivered to this content along with touch
// events, in violation of spec?
hybridEvents: false,
init: function cp_init() {
this._setupListenersForPanning();
},
_setupListenersForPanning: function cp_setupListenersForPanning() {
let events;
if (content.TouchEvent) {
events = ['touchstart', 'touchend', 'touchmove'];
this.watchedEventsType = 'touch';
#ifdef MOZ_WIDGET_GONK
// The gonk widget backend does not deliver mouse events per
// spec. Third-party content isn't exposed to this behavior,
// but that behavior creates some extra work for us here.
let appInfo = Cc["@mozilla.org/xre/app-info;1"];
let isParentProcess =
!appInfo || appInfo.getService(Ci.nsIXULRuntime)
.processType == Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT;
this.hybridEvents = isParentProcess;
#endif
} else {
// Touch events aren't supported, so fall back on mouse.
events = ['mousedown', 'mouseup', 'mousemove'];
this.watchedEventsType = 'mouse';
}
let els = Cc["@mozilla.org/eventlistenerservice;1"]
.getService(Ci.nsIEventListenerService);
events.forEach(function(type) {
// Using the system group for mouse/touch events to avoid
// missing events if .stopPropagation() has been called.
els.addSystemEventListener(global, type,
this.handleEvent.bind(this),
/* useCapture = */ false);
}.bind(this));
},
handleEvent: function cp_handleEvent(evt) {
// Ignore events targeting an oop <iframe mozbrowser> since those will be
// handle by the BrowserElementPanning.js instance in the child process.
if (evt.target instanceof Ci.nsIMozBrowserFrame) {
return;
}
// For in-process <iframe mozbrowser> the events are not targetting
// directly the container iframe element, but some node of the document.
// So, the BrowserElementPanning instance of the system app will receive
// the sequence of touch events, as well as the BrowserElementPanning
// instance in the targetted app.
// As a result, multiple mozbrowser iframes will try to interpret the
// sequence of touch events, which may results into multiple clicks.
let targetWindow = evt.target.ownerDocument.defaultView;
let frameElement = targetWindow.frameElement;
while (frameElement) {
targetWindow = frameElement.ownerDocument.defaultView;
frameElement = targetWindow.frameElement;
}
if (content !== targetWindow) {
return;
}
if (evt.defaultPrevented || evt.multipleActionsPrevented) {
// clean up panning state even if touchend/mouseup has been preventDefault.
if(evt.type === 'touchend' || evt.type === 'mouseup') {
if (this.dragging &&
(this.watchedEventsType === 'mouse' ||
this.findPrimaryPointer(evt.changedTouches))) {
this._finishPanning();
}
}
return;
}
switch (evt.type) {
case 'mousedown':
case 'touchstart':
this.onTouchStart(evt);
break;
case 'mousemove':
case 'touchmove':
this.onTouchMove(evt);
break;
case 'mouseup':
case 'touchend':
this.onTouchEnd(evt);
break;
case 'click':
evt.stopPropagation();
evt.preventDefault();
let target = evt.target;
let view = target.ownerDocument ? target.ownerDocument.defaultView
: target;
view.removeEventListener('click', this, true, true);
break;
}
},
position: new Point(0 , 0),
findPrimaryPointer: function cp_findPrimaryPointer(touches) {
if (!('primaryPointerId' in this))
return null;
for (let i = 0; i < touches.length; i++) {
if (touches[i].identifier === this.primaryPointerId) {
return touches[i];
}
}
return null;
},
onTouchStart: function cp_onTouchStart(evt) {
let screenX, screenY;
if (this.watchedEventsType == 'touch') {
if ('primaryPointerId' in this || evt.touches.length >= 2) {
this._resetActive();
return;
}
let firstTouch = evt.changedTouches[0];
this.primaryPointerId = firstTouch.identifier;
this.pointerDownTarget = firstTouch.target;
screenX = firstTouch.screenX;
screenY = firstTouch.screenY;
} else {
this.pointerDownTarget = evt.target;
screenX = evt.screenX;
screenY = evt.screenY;
}
this.dragging = true;
this.panning = false;
let oldTarget = this.target;
[this.target, this.scrollCallback] = this.getPannable(this.pointerDownTarget);
// If we have a pointer down target, we may need to fill in for EventStateManager
// in setting the active state on the target element. Set a timer to
// ensure the pointer-down target is active. (If it's already
// active, the timer is a no-op.)
if (this.pointerDownTarget !== null) {
// If there's no possibility this is a drag/pan, activate now.
// Otherwise wait a little bit to see if the gesture isn't a
// tap.
if (this.target === null) {
this.notify(this._activationTimer);
} else {
this._activationTimer.initWithCallback(this,
this._activationDelayMs,
Ci.nsITimer.TYPE_ONE_SHOT);
}
}
// If there is a pan animation running (from a previous pan gesture) and
// the user touch back the screen, stop this animation immediatly and
// prevent the possible click action if the touch happens on the same
// target.
this.preventNextClick = false;
if (KineticPanning.active) {
KineticPanning.stop();
if (oldTarget && oldTarget == this.target)
this.preventNextClick = true;
}
this.position.set(screenX, screenY);
KineticPanning.reset();
KineticPanning.record(new Point(0, 0), evt.timeStamp);
// We prevent start events to avoid sending a focus event at the end of this
// touch series. See bug 889717.
if ((this.panning || this.preventNextClick)) {
evt.preventDefault();
}
},
onTouchEnd: function cp_onTouchEnd(evt) {
let touch = null;
if (!this.dragging ||
(this.watchedEventsType == 'touch' &&
!(touch = this.findPrimaryPointer(evt.changedTouches)))) {
return;
}
// !isPan() and evt.detail should always give the same answer here
// since they use the same heuristics, but use the native gecko
// computation when possible.
//
// NB: when we're using touch events, then !KineticPanning.isPan()
// => this.panning, so we'll never attempt to block the click
// event. That's OK however, because we won't fire a synthetic
// click when we're using touch events and this touch series
// wasn't a "tap" gesture.
let click = (this.watchedEventsType == 'mouse') ?
evt.detail : !KineticPanning.isPan();
// Additionally, if we're seeing non-compliant hybrid events, a
// "real" click will be generated if we started and ended on the
// same element.
if (this.hybridEvents) {
let target =
content.document.elementFromPoint(touch.clientX, touch.clientY);
click |= (target === this.pointerDownTarget);
}
if (this.target && click && (this.panning || this.preventNextClick)) {
if (this.hybridEvents) {
let target = this.target;
let view = target.ownerDocument ? target.ownerDocument.defaultView
: target;
view.addEventListener('click', this, true, true);
} else {
// We prevent end events to avoid sending a focus event. See bug 889717.
evt.preventDefault();
}
} else if (this.target && click && !this.panning) {
this.notify(this._activationTimer);
}
this._finishPanning();
// Now that we're done, avoid entraining the thing we just panned.
this.pointerDownTarget = null;
},
onTouchMove: function cp_onTouchMove(evt) {
if (!this.dragging)
return;
let screenX, screenY;
if (this.watchedEventsType == 'touch') {
let primaryTouch = this.findPrimaryPointer(evt.changedTouches);
if (evt.touches.length > 1 || !primaryTouch)
return;
screenX = primaryTouch.screenX;
screenY = primaryTouch.screenY;
} else {
screenX = evt.screenX;
screenY = evt.screenY;
}
let current = this.position;
let delta = new Point(screenX - current.x, screenY - current.y);
current.set(screenX, screenY);
KineticPanning.record(delta, evt.timeStamp);
let isPan = KineticPanning.isPan();
// If we've detected a pan gesture, cancel the active state of the
// current target.
if (!this.panning && isPan) {
this._resetActive();
}
// There's no possibility of us panning anything.
if (!this.scrollCallback) {
return;
}
// Scroll manually.
this.scrollCallback(delta.scale(-1));
if (!this.panning && isPan) {
this.panning = true;
this._activationTimer.cancel();
}
if (this.panning) {
// Only do this when we're actually executing a pan gesture.
// Otherwise synthetic mouse events will be canceled.
evt.stopPropagation();
evt.preventDefault();
}
},
// nsITimerCallback
notify: function cp_notify(timer) {
this._setActive(this.pointerDownTarget);
},
onKineticBegin: function cp_onKineticBegin(evt) {
},
onKineticPan: function cp_onKineticPan(delta) {
return !this.scrollCallback(delta);
},
onKineticEnd: function cp_onKineticEnd() {
if (!this.dragging)
this.scrollCallback = null;
},
getPannable: function cp_getPannable(node) {
let pannableNode = this._findPannable(node);
if (pannableNode) {
return [pannableNode, this._generateCallback(pannableNode)];
}
return [null, null];
},
_findPannable: function cp_findPannable(node) {
if (!(node instanceof Ci.nsIDOMHTMLElement) || node.tagName == 'HTML') {
return null;
}
let nodeContent = node.ownerDocument.defaultView;
while (!(node instanceof Ci.nsIDOMHTMLBodyElement)) {
let style = nodeContent.getComputedStyle(node, null);
let overflow = [style.getPropertyValue('overflow'),
style.getPropertyValue('overflow-x'),
style.getPropertyValue('overflow-y')];
let rect = node.getBoundingClientRect();
let isAuto = (overflow.indexOf('auto') != -1 &&
(rect.height < node.scrollHeight ||
rect.width < node.scrollWidth));
let isScroll = (overflow.indexOf('scroll') != -1);
let isScrollableTextarea = (node.tagName == 'TEXTAREA' &&
(node.scrollHeight > node.clientHeight ||
node.scrollWidth > node.clientWidth ||
('scrollLeftMax' in node && node.scrollLeftMax > 0) ||
('scrollTopMax' in node && node.scrollTopMax > 0)));
if (isScroll || isAuto || isScrollableTextarea) {
return node;
}
node = node.parentNode;
}
if (nodeContent.scrollMaxX || nodeContent.scrollMaxY) {
return nodeContent;
}
if (nodeContent.frameElement) {
return this._findPannable(nodeContent.frameElement);
}
return null;
},
_generateCallback: function cp_generateCallback(root) {
let firstScroll = true;
let target;
let current;
let win, doc, htmlNode, bodyNode;
function doScroll(node, delta) {
if (node instanceof Ci.nsIDOMHTMLElement) {
return node.scrollByNoFlush(delta.x, delta.y);
} else if (node instanceof Ci.nsIDOMWindow) {
win = node;
doc = win.document;
// "overflow:hidden" on either the <html> or the <body> node should
// prevent the user from scrolling the root viewport.
if (doc instanceof Ci.nsIDOMHTMLDocument) {
htmlNode = doc.documentElement;
bodyNode = doc.body;
if (win.getComputedStyle(htmlNode, null).overflowX == "hidden" ||
win.getComputedStyle(bodyNode, null).overflowX == "hidden") {
delta.x = 0;
}
if (win.getComputedStyle(htmlNode, null).overflowY == "hidden" ||
win.getComputedStyle(bodyNode, null).overflowY == "hidden") {
delta.y = 0;
}
}
let oldX = node.scrollX;
let oldY = node.scrollY;
node.scrollBy(delta.x, delta.y);
return (node.scrollX != oldX || node.scrollY != oldY);
}
// If we get here, |node| isn't an HTML element and it's not a window,
// but findPannable apparently thought it was scrollable... What is it?
return false;
}
function targetParent(node) {
return node.parentNode || node.frameElement || null;
}
function scroll(delta) {
current = root;
firstScroll = true;
while (current) {
if (doScroll(current, delta)) {
firstScroll = false;
return true;
}
// TODO The current code looks for possible scrolling regions only if
// this is the first scroll action but this should be more dynamic.
if (!firstScroll) {
return false;
}
current = ContentPanningAPZDisabled._findPannable(targetParent(current));
}
// There is nothing scrollable here.
return false;
}
return scroll;
},
get _activationTimer() {
delete this._activationTimer;
return this._activationTimer = Cc["@mozilla.org/timer;1"]
.createInstance(Ci.nsITimer);
},
get _activationDelayMs() {
let delay = Services.prefs.getIntPref('ui.touch_activation.delay_ms');
delete this._activationDelayMs;
return this._activationDelayMs = delay;
},
get _domUtils() {
delete this._domUtils;
return this._domUtils = Cc['@mozilla.org/inspector/dom-utils;1']
.getService(Ci.inIDOMUtils);
},
_resetActive: function cp_resetActive() {
let elt = this.pointerDownTarget || this.target;
let root = elt.ownerDocument || elt.document;
this._setActive(root.documentElement);
},
_setActive: function cp_setActive(elt) {
const kStateActive = 0x00000001;
this._domUtils.setContentState(elt, kStateActive);
},
_finishPanning: function() {
this.dragging = false;
delete this.primaryPointerId;
this._activationTimer.cancel();
// If there is a scroll action, let's do a manual kinetic panning action.
if (this.panning) {
KineticPanning.start(this);
}
},
};
// Min/max velocity of kinetic panning. This is in pixels/millisecond.
const kMinVelocity = 0.2;
const kMaxVelocity = 6;
// Constants that affect the "friction" of the scroll pane.
const kExponentialC = 1000;
const kPolynomialC = 100 / 1000000;
// How often do we change the position of the scroll pane?
// Too often and panning may jerk near the end.
// Too little and panning will be choppy. In milliseconds.
const kUpdateInterval = 16;
// The numbers of momentums to use for calculating the velocity of the pan.
// Those are taken from the end of the action
const kSamples = 5;
const KineticPanning = {
_position: new Point(0, 0),
_velocity: new Point(0, 0),
_acceleration: new Point(0, 0),
get active() {
return this.target !== null;
},
target: null,
start: function kp_start(target) {
this.target = target;
// Calculate the initial velocity of the movement based on user input
let momentums = this.momentums;
let flick = momentums[momentums.length - 1].time - momentums[0].time < 300;
let distance = new Point(0, 0);
momentums.forEach(function(momentum) {
distance.add(momentum.dx, momentum.dy);
});
function clampFromZero(x, min, max) {
if (x >= 0)
return Math.max(min, Math.min(max, x));
return Math.min(-min, Math.max(-max, x));
}
let elapsed = momentums[momentums.length - 1].time - momentums[0].time;
let velocityX = clampFromZero(distance.x / elapsed, 0, kMaxVelocity);
let velocityY = clampFromZero(distance.y / elapsed, 0, kMaxVelocity);
let velocity = this._velocity;
if (flick) {
// Very fast pan action that does not generate a click are very often pan
// action. If this is a small gesture then it will not move the view a lot
// and so it will be above the minimun threshold and not generate any
// kinetic panning. This does not look on a device since this is often
// a real gesture, so let's lower the velocity threshold for such moves.
velocity.set(velocityX, velocityY);
} else {
velocity.set(Math.abs(velocityX) < kMinVelocity ? 0 : velocityX,
Math.abs(velocityY) < kMinVelocity ? 0 : velocityY);
}
this.momentums = [];
// Set acceleration vector to opposite signs of velocity
function sign(x) {
return x ? (x > 0 ? 1 : -1) : 0;
}
this._acceleration.set(velocity.clone().map(sign).scale(-kPolynomialC));
// Reset the position
this._position.set(0, 0);
this._startAnimation();
this.target.onKineticBegin();
},
stop: function kp_stop() {
this.reset();
if (!this.target)
return;
this.target.onKineticEnd();
this.target = null;
},
reset: function kp_reset() {
this.momentums = [];
this.distance.set(0, 0);
},
momentums: [],
record: function kp_record(delta, timestamp) {
this.momentums.push({ 'time': this._getTime(timestamp),
'dx' : delta.x, 'dy' : delta.y });
// We only need to keep kSamples in this.momentums.
if (this.momentums.length > kSamples) {
this.momentums.shift();
}
this.distance.add(delta.x, delta.y);
},
_getTime: function kp_getTime(time) {
// Touch events generated by the platform or hand-made are defined in
// microseconds instead of milliseconds. Bug 77992 will fix this at the
// platform level.
if (time > Date.now()) {
return Math.floor(time / 1000);
} else {
return time;
}
},
get threshold() {
let dpi = content.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindowUtils)
.displayDPI;
let threshold = Services.prefs.getIntPref('ui.dragThresholdX') / 240 * dpi;
delete this.threshold;
return this.threshold = threshold;
},
distance: new Point(0, 0),
isPan: function cp_isPan() {
return (Math.abs(this.distance.x) > this.threshold ||
Math.abs(this.distance.y) > this.threshold);
},
_startAnimation: function kp_startAnimation() {
let c = kExponentialC;
function getNextPosition(position, v, a, t) {
// Important traits for this function:
// p(t=0) is 0
// p'(t=0) is v0
//
// We use exponential to get a smoother stop, but by itself exponential
// is too smooth at the end. Adding a polynomial with the appropriate
// weight helps to balance
position.set(v.x * Math.exp(-t / c) * -c + a.x * t * t + v.x * c,
v.y * Math.exp(-t / c) * -c + a.y * t * t + v.y * c);
}
let startTime = content.mozAnimationStartTime;
let elapsedTime = 0, targetedTime = 0, averageTime = 0;
let velocity = this._velocity;
let acceleration = this._acceleration;
let position = this._position;
let nextPosition = new Point(0, 0);
let delta = new Point(0, 0);
let callback = (function(timestamp) {
if (!this.target)
return;
// To make animation end fast enough but to keep smoothness, average the
// ideal time frame (smooth animation) with the actual time lapse
// (end fast enough).
// Animation will never take longer than 2 times the ideal length of time.
elapsedTime = timestamp - startTime;
targetedTime += kUpdateInterval;
averageTime = (targetedTime + elapsedTime) / 2;
// Calculate new position.
getNextPosition(nextPosition, velocity, acceleration, averageTime);
delta.set(Math.round(nextPosition.x - position.x),
Math.round(nextPosition.y - position.y));
// Test to see if movement is finished for each component.
if (delta.x * acceleration.x > 0)
delta.x = position.x = velocity.x = acceleration.x = 0;
if (delta.y * acceleration.y > 0)
delta.y = position.y = velocity.y = acceleration.y = 0;
if (velocity.equals(0, 0) || delta.equals(0, 0)) {
this.stop();
return;
}
position.add(delta);
if (this.target.onKineticPan(delta.scale(-1))) {
this.stop();
return;
}
content.mozRequestAnimationFrame(callback);
}).bind(this);
content.mozRequestAnimationFrame(callback);
}
};

View File

@ -111,8 +111,7 @@ const browserElementTestHelpers = {
// Returns a promise which is resolved when a subprocess is created. The
// argument to resolve() is the childID of the subprocess.
function expectProcessCreated(/* optional */ initialPriority,
/* optional */ initialCPUPriority) {
function expectProcessCreated(/* optional */ initialPriority) {
return new Promise(function(resolve, reject) {
var observed = false;
browserElementTestHelpers.addProcessPriorityObserver(
@ -128,7 +127,7 @@ function expectProcessCreated(/* optional */ initialPriority,
var childID = parseInt(data);
ok(true, 'Got new process, id=' + childID);
if (initialPriority) {
expectPriorityChange(childID, initialPriority, initialCPUPriority).then(function() {
expectPriorityChange(childID, initialPriority).then(function() {
resolve(childID);
});
} else {
@ -141,9 +140,8 @@ function expectProcessCreated(/* optional */ initialPriority,
// Just like expectProcessCreated(), except we'll call ok(false) if a second
// process is created.
function expectOnlyOneProcessCreated(/* optional */ initialPriority,
/* optional */ initialCPUPriority) {
var p = expectProcessCreated(initialPriority, initialCPUPriority);
function expectOnlyOneProcessCreated(/* optional */ initialPriority) {
var p = expectProcessCreated(initialPriority);
p.then(function() {
expectProcessCreated().then(function(childID) {
ok(false, 'Got unexpected process creation, childID=' + childID);
@ -153,15 +151,10 @@ function expectOnlyOneProcessCreated(/* optional */ initialPriority,
}
// Returns a promise which is resolved or rejected the next time the process
// childID changes its priority. We resolve if the (priority, CPU priority)
// tuple matches (expectedPriority, expectedCPUPriority) and we reject
// otherwise.
//
// expectedCPUPriority is an optional argument; if it's not specified, we
// resolve if priority matches expectedPriority.
// childID changes its priority. We resolve if the priority matches
// expectedPriority, and we reject otherwise.
function expectPriorityChange(childID, expectedPriority,
/* optional */ expectedCPUPriority) {
function expectPriorityChange(childID, expectedPriority) {
return new Promise(function(resolve, reject) {
var observed = false;
browserElementTestHelpers.addProcessPriorityObserver(
@ -171,7 +164,7 @@ function expectPriorityChange(childID, expectedPriority,
return;
}
var [id, priority, cpuPriority] = data.split(":");
var [id, priority] = data.split(":");
if (id != childID) {
return;
}
@ -184,14 +177,7 @@ function expectPriorityChange(childID, expectedPriority,
'Expected priority of childID ' + childID +
' to change to ' + expectedPriority);
if (expectedCPUPriority) {
is(cpuPriority, expectedCPUPriority,
'Expected CPU priority of childID ' + childID +
' to change to ' + expectedCPUPriority);
}
if (priority == expectedPriority &&
(!expectedCPUPriority || expectedCPUPriority == cpuPriority)) {
if (priority == expectedPriority) {
resolve();
} else {
reject();
@ -212,13 +198,14 @@ function expectPriorityWithBackgroundLRUSet(childID, expectedBackgroundLRU) {
'process-priority-with-background-LRU-set',
function(subject, topic, data) {
var [id, priority, cpuPriority, backgroundLRU] = data.split(":");
var [id, priority, backgroundLRU] = data.split(":");
if (id != childID) {
return;
}
is(backgroundLRU, expectedBackgroundLRU,
'Expected backgroundLRU ' + backgroundLRU + ' of childID ' + childID +
'Expected backgroundLRU ' + backgroundLRU +
' of childID ' + childID +
' to change to ' + expectedBackgroundLRU);
if (backgroundLRU == expectedBackgroundLRU) {

View File

@ -1,15 +0,0 @@
A word to the wise:
You must ensure that if your test finishes successfully, no processes have
priority FOREGROUND_HIGH.
If you don't do this, expect to see tests randomly fail with mysterious
FOREGROUND --> FOREGROUND priority transitions.
What's happening in this case is that your FOREGROUND_HIGH process lives until
the beginning of the next test. This causes the process started by the next
test to have low CPU priority. Then the FOREGROUND_HIGH process dies, because
its iframe gets GC'ed, and we transition the new test's process from low CPU
priority to regular CPU priority.
Ouch.

View File

@ -10,8 +10,6 @@ skip-if = toolkit != "gtk2" || ((buildapp =='mulet' || buildapp == 'b2g') && (to
[test_Visibility.html]
[test_HighPriority.html]
support-files = file_HighPriority.html
[test_HighPriorityDowngrade.html]
[test_HighPriorityDowngrade2.html]
[test_Background.html]
[test_BackgroundLRU.html]
[test_Audio.html]

View File

@ -1,81 +0,0 @@
<!DOCTYPE HTML>
<html>
<!--
Test that high-priority processes downgrade the CPU priority of regular
processes.
-->
<head>
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<script type="application/javascript" src="../browserElementTestHelpers.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
</head>
<body>
<script type="application/javascript;version=1.7">
"use strict";
SimpleTest.waitForExplicitFinish();
browserElementTestHelpers.setEnabledPref(true);
browserElementTestHelpers.addPermission();
browserElementTestHelpers.enableProcessPriorityManager();
SpecialPowers.addPermission("embed-apps", true, document);
var iframe = null;
var childID = null;
function runTest() {
var iframe = document.createElement('iframe');
iframe.setAttribute('mozbrowser', true);
iframe.src = browserElementTestHelpers.emptyPage1;
var highPriorityIframe = null;
var childID = null;
var lock = null;
var p = null;
expectProcessCreated('FOREGROUND', 'CPU_NORMAL').then(function(chid) {
childID = chid;
}).then(function() {
// Create a new, high-priority iframe.
highPriorityIframe = document.createElement('iframe');
highPriorityIframe.setAttribute('mozbrowser', true);
highPriorityIframe.setAttribute('expecting-system-message', true);
highPriorityIframe.setAttribute('mozapptype', 'critical');
highPriorityIframe.setAttribute('mozapp', 'http://example.org/manifest.webapp');
highPriorityIframe.src = browserElementTestHelpers.emptyPage2;
p = expectPriorityChange(childID, 'FOREGROUND', 'CPU_LOW');
document.body.appendChild(highPriorityIframe);
return p;
}).then(function() {
return expectPriorityChange(childID, 'FOREGROUND', 'CPU_NORMAL');
}).then(function() {
p = expectPriorityChange(childID, 'FOREGROUND', 'CPU_LOW');
lock = navigator.requestWakeLock('high-priority');
return p;
}).then(function() {
p = expectPriorityChange(childID, 'FOREGROUND', 'CPU_NORMAL');
lock.unlock();
return p;
}).then(SimpleTest.finish);
document.body.appendChild(iframe);
}
addEventListener('testready', function() {
SpecialPowers.pushPrefEnv(
{set: [
/* Cause the CPU wake lock taken on behalf of the high-priority process
* to time out after 1s. */
["dom.ipc.systemMessageCPULockTimeoutSec", 1],
["dom.wakelock.enabled", true]
]},
runTest);
});
</script>
</body>
</html>

View File

@ -1,76 +0,0 @@
<!DOCTYPE HTML>
<html>
<!--
Test that high-priority processes downgrade the CPU priority of regular
processes.
This is just like test_HighPriorityDowngrade, except instead of waiting for the
high-priority process's wake lock to expire, we kill the process by removing
its iframe from the DOM.
-->
<head>
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<script type="application/javascript" src="../browserElementTestHelpers.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
</head>
<body>
<script type="application/javascript;version=1.7">
"use strict";
SimpleTest.waitForExplicitFinish();
browserElementTestHelpers.setEnabledPref(true);
browserElementTestHelpers.addPermission();
browserElementTestHelpers.enableProcessPriorityManager();
SpecialPowers.addPermission("embed-apps", true, document);
function runTest() {
var iframe = document.createElement('iframe');
iframe.setAttribute('mozbrowser', true);
iframe.src = browserElementTestHelpers.emptyPage1;
var highPriorityIframe = null;
var childID = null;
expectProcessCreated('FOREGROUND', 'CPU_NORMAL').then(function(chid) {
childID = chid;
}).then(function() {
// Create a new, high-priority iframe.
highPriorityIframe = document.createElement('iframe');
highPriorityIframe.setAttribute('mozbrowser', true);
highPriorityIframe.setAttribute('expecting-system-message', true);
highPriorityIframe.setAttribute('mozapptype', 'critical');
highPriorityIframe.setAttribute('mozapp', 'http://example.org/manifest.webapp');
highPriorityIframe.src = browserElementTestHelpers.emptyPage2;
var p = Promise.all(
[expectPriorityChange(childID, 'FOREGROUND', 'CPU_LOW'),
expectMozbrowserEvent(highPriorityIframe, 'loadend')]
);
document.body.appendChild(highPriorityIframe);
return p;
}).then(function() {
// Killing the high-priority iframe should cause our CPU priority to go back
// up to regular.
var p = expectPriorityChange(childID, 'FOREGROUND', 'CPU_NORMAL');
document.body.removeChild(highPriorityIframe);
return p;
}).then(SimpleTest.finish);
document.body.appendChild(iframe);
}
addEventListener('testready', function() {
// Cause the CPU wake lock taken on behalf of the high-priority process never
// to time out during this test.
SpecialPowers.pushPrefEnv(
{set: [["dom.ipc.systemMessageCPULockTimeoutSec", 1000]]},
runTest);
});
</script>
</body>
</html>

View File

@ -53,7 +53,7 @@ function runTest()
// Ensure that the preallocated process initially gets BACKGROUND priority.
// That's it.
expectProcessCreated('PREALLOC', 'CPU_LOW').then(function() {
expectProcessCreated('PREALLOC').then(function() {
// We need to set the pref asynchoronously or the preallocated process won't
// be shut down.
SimpleTest.executeSoon(function(){

View File

@ -499,6 +499,7 @@ support-files = webgl-conformance/../webgl-mochitest/driver-info.js
[webgl-conformance/_wrappers/test_conformance__buffers__index-validation-with-resized-buffer.html]
[webgl-conformance/_wrappers/test_conformance__buffers__index-validation.html]
[webgl-conformance/_wrappers/test_conformance__canvas__buffer-offscreen-test.html]
skip-if = os == 'android'
[webgl-conformance/_wrappers/test_conformance__canvas__buffer-preserve-test.html]
[webgl-conformance/_wrappers/test_conformance__canvas__canvas-test.html]
[webgl-conformance/_wrappers/test_conformance__canvas__canvas-zero-size.html]
@ -775,6 +776,7 @@ skip-if = (os == 'android') || (os == 'b2g') || (os == 'linux')
[webgl-conformance/_wrappers/test_conformance__textures__texture-npot-video.html]
skip-if = os == 'win'
[webgl-conformance/_wrappers/test_conformance__textures__texture-npot.html]
skip-if = os == 'android'
[webgl-conformance/_wrappers/test_conformance__textures__texture-size.html]
skip-if = os == 'android'
[webgl-conformance/_wrappers/test_conformance__textures__texture-size-cube-maps.html]

View File

@ -159,7 +159,7 @@ function do_canvas(row, col, type, options)
var color = document.getElementById('c' + row).textContent;
color = color.substr(5, color.length - 6); // strip off the 'argb()'
var colors = color.replace(' ', '', 'g').split(',');
var colors = color.replace(/ /g, '').split(',');
var r = colors[0]*colors[3],
g = colors[1]*colors[3],
b = colors[2]*colors[3];

View File

@ -122,7 +122,6 @@ function GetExpectedTestFailSet() {
failSet['conformance/textures/tex-image-with-format-and-type.html'] = true;
failSet['conformance/textures/tex-sub-image-2d.html'] = true;
failSet['conformance/textures/texture-mips.html'] = true;
failSet['conformance/textures/texture-npot.html'] = true;
failSet['conformance/textures/texture-size-cube-maps.html'] = true;
} else {
// Android 2.3 slaves.

View File

@ -53,6 +53,10 @@ skip-if = (os == 'android') || (os == 'b2g') || (os == 'linux')
# Crashes on 'Android 2.3'
# Asserts on 'B2G ICS Emulator Debug'.
skip-if = (os == 'android') || (os == 'b2g') || (os == 'linux')
[_wrappers/test_conformance__canvas__buffer-offscreen-test.html]
# Causes frequent *blues*: "DMError: Remote Device Error: unable to
# connect to 127.0.0.1 after 5 attempts" on 'Android 2.3 Opt'.
skip-if = os == 'android'
########################################################################
# Android
@ -68,6 +72,9 @@ skip-if = os == 'android'
[_wrappers/test_conformance__textures__texture-size-cube-maps.html]
# Crashes on Android 4.0.
skip-if = os == 'android'
[_wrappers/test_conformance__textures__texture-npot.html]
# Intermittent fail on Android 4.0.
skip-if = os == 'android'
########################################################################
# B2G

View File

@ -33,13 +33,13 @@ function readTemplate(path) {
cis.init(fis, "UTF-8", 0, 0);
var data = "";
let (str = {}) {
let read = 0;
do {
read = cis.readString(0xffffffff, str); // read as much as we can and put it in str.value
data += str.value;
} while (read != 0);
}
let str = {};
let read = 0;
do {
read = cis.readString(0xffffffff, str); // read as much as we can and put it in str.value
data += str.value;
} while (read != 0);
cis.close();
return data;
}
@ -52,4 +52,3 @@ function getQuery(request) {
});
return query;
}

View File

@ -0,0 +1,82 @@
/* -*- 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 "MLSFallback.h"
#include "nsGeoPosition.h"
#include "nsIGeolocationProvider.h"
#include "nsServiceManagerUtils.h"
NS_IMPL_ISUPPORTS(MLSFallback, nsITimerCallback)
MLSFallback::MLSFallback(uint32_t delay)
: mDelayMs(delay)
{
}
MLSFallback::~MLSFallback()
{
}
nsresult
MLSFallback::Startup(nsIGeolocationUpdate* aWatcher)
{
if (mHandoffTimer || mMLSFallbackProvider) {
return NS_OK;
}
mUpdateWatcher = aWatcher;
nsresult rv;
mHandoffTimer = do_CreateInstance("@mozilla.org/timer;1", &rv);
NS_ENSURE_SUCCESS(rv, rv);
rv = mHandoffTimer->InitWithCallback(this, mDelayMs, nsITimer::TYPE_ONE_SHOT);
return rv;
}
nsresult
MLSFallback::Shutdown()
{
mUpdateWatcher = nullptr;
if (mHandoffTimer) {
mHandoffTimer->Cancel();
mHandoffTimer = nullptr;
}
nsresult rv = NS_OK;
if (mMLSFallbackProvider) {
rv = mMLSFallbackProvider->Shutdown();
mMLSFallbackProvider = nullptr;
}
return rv;
}
NS_IMETHODIMP
MLSFallback::Notify(nsITimer* aTimer)
{
return CreateMLSFallbackProvider();
}
nsresult
MLSFallback::CreateMLSFallbackProvider()
{
if (mMLSFallbackProvider || !mUpdateWatcher) {
return NS_OK;
}
nsresult rv;
mMLSFallbackProvider = do_CreateInstance("@mozilla.org/geolocation/mls-provider;1", &rv);
NS_ENSURE_SUCCESS(rv, rv);
if (mMLSFallbackProvider) {
rv = mMLSFallbackProvider->Startup();
if (NS_SUCCEEDED(rv)) {
mMLSFallbackProvider->Watch(mUpdateWatcher);
}
}
mUpdateWatcher = nullptr;
return rv;
}

View File

@ -0,0 +1,47 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "nsCOMPtr.h"
#include "nsITimer.h"
class nsIGeolocationUpdate;
class nsIGeolocationProvider;
/*
This class wraps the NetworkGeolocationProvider in a delayed startup.
It is for providing a fallback to MLS when:
1) using another provider as the primary provider, and
2) that primary provider may fail to return a result (i.e. the error returned
is indeterminate, or no error callback occurs)
The intent is that the primary provider is started, then MLSFallback
is started with sufficient delay that the primary provider will respond first
if successful (in the majority of cases).
MLS has an average response of 3s, so with the 2s default delay, a response can
be expected in 5s.
Telemetry is recommended to monitor that the primary provider is responding
first when expected to do so.
*/
class MLSFallback : public nsITimerCallback
{
public:
NS_DECL_ISUPPORTS
NS_DECL_NSITIMERCALLBACK
explicit MLSFallback(uint32_t delayMs = 2000);
nsresult Startup(nsIGeolocationUpdate* aWatcher);
nsresult Shutdown();
private:
nsresult CreateMLSFallbackProvider();
virtual ~MLSFallback();
nsCOMPtr<nsITimer> mHandoffTimer;
nsCOMPtr<nsIGeolocationProvider> mMLSFallbackProvider;
nsCOMPtr<nsIGeolocationUpdate> mUpdateWatcher;
const uint32_t mDelayMs;
};

View File

@ -15,6 +15,7 @@ SOURCES += [
]
UNIFIED_SOURCES += [
'MLSFallback.cpp',
'nsGeoGridFuzzer.cpp',
'nsGeolocationSettings.cpp',
'nsGeoPosition.cpp',

View File

@ -44,7 +44,7 @@ function sendKeyEventToSubmitForm() {
}
function urlify(aStr) {
return aStr.replace(':', '%3A', 'g');
return aStr.replace(/:/g, '%3A');
}
function runTestsForNextInputType()

View File

@ -42,7 +42,7 @@ function testNumSame() {
is(document.all.id3.length, 3, "three length");
}
testNumSame();
p.innerHTML = p.innerHTML.replace("id=", "name=", "g");
p.innerHTML = p.innerHTML.replace(/id=/g, "name=");
testNumSame();

View File

@ -580,10 +580,10 @@ function checkMPSubmission(sub, expected, test) {
return l;
}
function mpquote(s) {
return s.replace("\r\n", " ", "g")
.replace("\r", " ", "g")
.replace("\n", " ", "g")
.replace("\"", "\\\"", "g");
return s.replace(/\r\n/g, " ")
.replace(/\r/g, " ")
.replace(/\n/g, " ")
.replace(/\"/g, "\\\"");
}
is(sub.length, expected.length,
@ -628,9 +628,9 @@ function utf8encode(s) {
function checkURLSubmission(sub, expected) {
function urlEscape(s) {
return escape(utf8encode(s)).replace("%20", "+", "g")
.replace("/", "%2F", "g")
.replace("@", "%40", "g");
return escape(utf8encode(s)).replace(/%20/g, "+")
.replace(/\//g, "%2F")
.replace(/@/g, "%40");
}
subItems = sub.split("&");

View File

@ -452,7 +452,8 @@ private:
return rv;
}
if (NS_WARN_IF(!JS_SetElement(aCx, array, index, value))) {
if (NS_WARN_IF(!JS_DefineElement(aCx, array, index, value,
JSPROP_ENUMERATE))) {
IDB_REPORT_INTERNAL_ERR();
return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
}
@ -505,7 +506,8 @@ private:
return rv;
}
if (NS_WARN_IF(!JS_SetElement(aCx, array, index, value))) {
if (NS_WARN_IF(!JS_DefineElement(aCx, array, index, value,
JSPROP_ENUMERATE))) {
IDB_REPORT_INTERNAL_ERR();
return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
}
@ -1094,7 +1096,7 @@ BackgroundFactoryRequestChild::Recv__delete__(
IDBOpenDBRequest* request = GetOpenDBRequest();
MOZ_ASSERT(request);
request->NoteComplete();
if (NS_WARN_IF(!result)) {

View File

@ -218,7 +218,7 @@ Key::DecodeJSValInternal(const unsigned char*& aPos, const unsigned char* aEnd,
aTypeOffset = 0;
if (!JS_SetElement(aCx, array, index++, val)) {
if (!JS_DefineElement(aCx, array, index++, val, JSPROP_ENUMERATE)) {
NS_WARNING("Failed to set array element!");
IDB_REPORT_INTERNAL_ERR();
return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;

View File

@ -359,7 +359,7 @@ KeyPath::ExtractKeyAsJSVal(JSContext* aCx, const JS::Value& aValue,
return rv;
}
if (!JS_SetElement(aCx, arrayObj, i, value)) {
if (!JS_DefineElement(aCx, arrayObj, i, value, JSPROP_ENUMERATE)) {
IDB_REPORT_INTERNAL_ERR();
return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
}
@ -471,7 +471,7 @@ KeyPath::ToJSVal(JSContext* aCx, JS::MutableHandle<JS::Value> aValue) const
return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
}
if (!JS_SetElement(aCx, array, i, val)) {
if (!JS_DefineElement(aCx, array, i, val, JSPROP_ENUMERATE)) {
IDB_REPORT_INTERNAL_ERR();
return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
}

View File

@ -138,13 +138,6 @@ public:
void FireTestOnlyObserverNotification(const char* aTopic,
const nsACString& aData = EmptyCString());
/**
* Does some process, other than the one handled by aParticularManager, have
* priority FOREGROUND_HIGH?
*/
bool OtherProcessHasHighPriority(
ParticularProcessPriorityManager* aParticularManager);
/**
* Does one of the child processes have priority FOREGROUND_HIGH?
*/
@ -182,7 +175,6 @@ private:
void ObserveContentParentCreated(nsISupports* aContentParent);
void ObserveContentParentDestroyed(nsISupports* aSubject);
void ResetAllCPUPriorities();
nsDataHashtable<nsUint64HashKey, nsRefPtr<ParticularProcessPriorityManager> >
mParticularManagers;
@ -267,23 +259,11 @@ public:
ProcessPriority CurrentPriority();
ProcessPriority ComputePriority();
ProcessCPUPriority ComputeCPUPriority(ProcessPriority aPriority);
void ScheduleResetPriority(const char* aTimeoutPref);
void ResetPriority();
void ResetPriorityNow();
void ResetCPUPriorityNow();
/**
* This overload is equivalent to SetPriorityNow(aPriority,
* ComputeCPUPriority()).
*/
void SetPriorityNow(ProcessPriority aPriority,
uint32_t aBackgroundLRU = 0);
void SetPriorityNow(ProcessPriority aPriority,
ProcessCPUPriority aCPUPriority,
uint32_t aBackgroundLRU = 0);
void SetPriorityNow(ProcessPriority aPriority, uint32_t aBackgroundLRU = 0);
void ShutDown();
@ -299,7 +279,6 @@ private:
ContentParent* mContentParent;
uint64_t mChildID;
ProcessPriority mPriority;
ProcessCPUPriority mCPUPriority;
bool mHoldsCPUWakeLock;
bool mHoldsHighPriorityWakeLock;
@ -439,8 +418,7 @@ ProcessPriorityManagerImpl::Init()
// The master process's priority never changes; set it here and then forget
// about it. We'll manage only subprocesses' priorities using the process
// priority manager.
hal::SetProcessPriority(getpid(), PROCESS_PRIORITY_MASTER,
PROCESS_CPU_PRIORITY_NORMAL);
hal::SetProcessPriority(getpid(), PROCESS_PRIORITY_MASTER);
nsCOMPtr<nsIObserverService> os = services::GetObserverService();
if (os) {
@ -517,18 +495,6 @@ ProcessPriorityManagerImpl::ObserveContentParentCreated(
GetParticularProcessPriorityManager(cp->AsContentParent());
}
static PLDHashOperator
EnumerateParticularProcessPriorityManagers(
const uint64_t& aKey,
nsRefPtr<ParticularProcessPriorityManager> aValue,
void* aUserData)
{
nsTArray<nsRefPtr<ParticularProcessPriorityManager> >* aArray =
static_cast<nsTArray<nsRefPtr<ParticularProcessPriorityManager> >*>(aUserData);
aArray->AppendElement(aValue);
return PL_DHASH_NEXT;
}
void
ProcessPriorityManagerImpl::ObserveContentParentDestroyed(nsISupports* aSubject)
{
@ -548,38 +514,10 @@ ProcessPriorityManagerImpl::ObserveContentParentDestroyed(nsISupports* aSubject)
if (mHighPriorityChildIDs.Contains(childID)) {
mHighPriorityChildIDs.RemoveEntry(childID);
// We just lost a high-priority process; reset everyone's CPU priorities.
ResetAllCPUPriorities();
}
}
}
void
ProcessPriorityManagerImpl::ResetAllCPUPriorities( void )
{
nsTArray<nsRefPtr<ParticularProcessPriorityManager> > pppms;
mParticularManagers.EnumerateRead(
&EnumerateParticularProcessPriorityManagers,
&pppms);
for (uint32_t i = 0; i < pppms.Length(); i++) {
pppms[i]->ResetCPUPriorityNow();
}
}
bool
ProcessPriorityManagerImpl::OtherProcessHasHighPriority(
ParticularProcessPriorityManager* aParticularManager)
{
if (mHighPriority) {
return true;
} else if (mHighPriorityChildIDs.Contains(aParticularManager->ChildID())) {
return mHighPriorityChildIDs.Count() > 1;
}
return mHighPriorityChildIDs.Count() > 0;
}
bool
ProcessPriorityManagerImpl::ChildProcessHasHighPriority( void )
{
@ -591,8 +529,9 @@ ProcessPriorityManagerImpl::NotifyProcessPriorityChanged(
ParticularProcessPriorityManager* aParticularManager,
ProcessPriority aOldPriority)
{
// This priority change can only affect other processes' priorities if we're
// changing to/from FOREGROUND_HIGH.
/* We're interested only in changes to/from FOREGROUND_HIGH as we use we
* need to track high priority processes so that we can react to their
* presence. */
if (aOldPriority < PROCESS_PRIORITY_FOREGROUND_HIGH &&
aParticularManager->CurrentPriority() <
@ -607,17 +546,6 @@ ProcessPriorityManagerImpl::NotifyProcessPriorityChanged(
} else {
mHighPriorityChildIDs.RemoveEntry(aParticularManager->ChildID());
}
nsTArray<nsRefPtr<ParticularProcessPriorityManager> > pppms;
mParticularManagers.EnumerateRead(
&EnumerateParticularProcessPriorityManagers,
&pppms);
for (uint32_t i = 0; i < pppms.Length(); i++) {
if (pppms[i] != aParticularManager) {
pppms[i]->ResetCPUPriorityNow();
}
}
}
/* virtual */ void
@ -633,10 +561,6 @@ ProcessPriorityManagerImpl::Notify(const WakeLockInformation& aInfo)
mHighPriority = false;
}
/* The main process got a high-priority wakelock change; reset everyone's
* CPU priorities. */
ResetAllCPUPriorities();
LOG("Got wake lock changed event. "
"Now mHighPriorityParent = %d\n", mHighPriority);
}
@ -653,7 +577,6 @@ ParticularProcessPriorityManager::ParticularProcessPriorityManager(
: mContentParent(aContentParent)
, mChildID(aContentParent->ChildID())
, mPriority(PROCESS_PRIORITY_UNKNOWN)
, mCPUPriority(PROCESS_CPU_PRIORITY_NORMAL)
, mHoldsCPUWakeLock(false)
, mHoldsHighPriorityWakeLock(false)
{
@ -1015,40 +938,9 @@ ParticularProcessPriorityManager::ComputePriority()
PROCESS_PRIORITY_BACKGROUND;
}
ProcessCPUPriority
ParticularProcessPriorityManager::ComputeCPUPriority(ProcessPriority aPriority)
{
if (aPriority == PROCESS_PRIORITY_PREALLOC) {
return PROCESS_CPU_PRIORITY_LOW;
}
if (aPriority >= PROCESS_PRIORITY_FOREGROUND_HIGH) {
return PROCESS_CPU_PRIORITY_NORMAL;
}
return ProcessPriorityManagerImpl::GetSingleton()->
OtherProcessHasHighPriority(this) ?
PROCESS_CPU_PRIORITY_LOW :
PROCESS_CPU_PRIORITY_NORMAL;
}
void
ParticularProcessPriorityManager::ResetCPUPriorityNow()
{
SetPriorityNow(mPriority);
}
void
ParticularProcessPriorityManager::SetPriorityNow(ProcessPriority aPriority,
uint32_t aBackgroundLRU)
{
SetPriorityNow(aPriority, ComputeCPUPriority(aPriority), aBackgroundLRU);
}
void
ParticularProcessPriorityManager::SetPriorityNow(ProcessPriority aPriority,
ProcessCPUPriority aCPUPriority,
uint32_t aBackgroundLRU)
{
if (aPriority == PROCESS_PRIORITY_UNKNOWN) {
MOZ_ASSERT(false);
@ -1058,11 +950,10 @@ ParticularProcessPriorityManager::SetPriorityNow(ProcessPriority aPriority,
if (aBackgroundLRU > 0 &&
aPriority == PROCESS_PRIORITY_BACKGROUND &&
mPriority == PROCESS_PRIORITY_BACKGROUND) {
hal::SetProcessPriority(Pid(), mPriority, mCPUPriority, aBackgroundLRU);
hal::SetProcessPriority(Pid(), mPriority, aBackgroundLRU);
nsPrintfCString ProcessPriorityWithBackgroundLRU("%s:%d",
ProcessPriorityToString(mPriority, mCPUPriority),
aBackgroundLRU);
ProcessPriorityToString(mPriority), aBackgroundLRU);
FireTestOnlyObserverNotification("process-priority-with-background-LRU-set",
ProcessPriorityWithBackgroundLRU.get());
@ -1070,7 +961,7 @@ ParticularProcessPriorityManager::SetPriorityNow(ProcessPriority aPriority,
if (!mContentParent ||
!ProcessPriorityManagerImpl::PrefsEnabled() ||
(mPriority == aPriority && mCPUPriority == aCPUPriority)) {
(mPriority == aPriority)) {
return;
}
@ -1095,16 +986,18 @@ ParticularProcessPriorityManager::SetPriorityNow(ProcessPriority aPriority,
}
LOGP("Changing priority from %s to %s.",
ProcessPriorityToString(mPriority, mCPUPriority),
ProcessPriorityToString(aPriority, aCPUPriority));
ProcessPriorityToString(mPriority),
ProcessPriorityToString(aPriority));
ProcessPriority oldPriority = mPriority;
mPriority = aPriority;
mCPUPriority = aCPUPriority;
hal::SetProcessPriority(Pid(), mPriority, mCPUPriority);
hal::SetProcessPriority(Pid(), mPriority);
if (oldPriority != mPriority) {
ProcessPriorityManagerImpl::GetSingleton()->
NotifyProcessPriorityChanged(this, oldPriority);
unused << mContentParent->SendNotifyProcessPriorityChanged(mPriority);
}
@ -1113,12 +1006,7 @@ ParticularProcessPriorityManager::SetPriorityNow(ProcessPriority aPriority,
}
FireTestOnlyObserverNotification("process-priority-set",
ProcessPriorityToString(mPriority, mCPUPriority));
if (oldPriority != mPriority) {
ProcessPriorityManagerImpl::GetSingleton()->
NotifyProcessPriorityChanged(this, oldPriority);
}
ProcessPriorityToString(mPriority));
}
void

View File

@ -2011,7 +2011,7 @@ TabChild::RecvUpdateDimensions(const nsIntRect& rect, const nsIntSize& size,
ScreenIntSize oldScreenSize = mInnerSize;
mInnerSize = ScreenIntSize::FromUnknownSize(
gfx::IntSize(size.width, size.height));
mWidget->Resize(0, 0, size.width, size.height,
mWidget->Resize(rect.x + chromeDisp.x, rect.y + chromeDisp.y, size.width, size.height,
true);
nsCOMPtr<nsIBaseWindow> baseWin = do_QueryInterface(WebNavigation());
@ -2227,7 +2227,7 @@ TabChild::UpdateTapState(const WidgetTouchEvent& aEvent, nsEventStatus aStatus)
return;
}
if (aStatus == nsEventStatus_eConsumeNoDefault ||
nsIPresShell::gPreventMouseEvents ||
TouchManager::gPreventMouseEvents ||
aEvent.mFlags.mMultipleActionsPrevented) {
return;
}
@ -2268,7 +2268,7 @@ TabChild::UpdateTapState(const WidgetTouchEvent& aEvent, nsEventStatus aStatus)
return;
case NS_TOUCH_END:
if (!nsIPresShell::gPreventMouseEvents) {
if (!TouchManager::gPreventMouseEvents) {
APZCCallbackHelper::DispatchSynthesizedMouseEvent(NS_MOUSE_MOVE, time, currentPoint, mWidget);
APZCCallbackHelper::DispatchSynthesizedMouseEvent(NS_MOUSE_BUTTON_DOWN, time, currentPoint, mWidget);
APZCCallbackHelper::DispatchSynthesizedMouseEvent(NS_MOUSE_BUTTON_UP, time, currentPoint, mWidget);

View File

@ -80,6 +80,7 @@
#include "nsICancelable.h"
#include "gfxPrefs.h"
#include "nsILoginManagerPrompter.h"
#include "nsPIWindowRoot.h"
#include <algorithm>
using namespace mozilla::dom;
@ -321,7 +322,27 @@ TabParent::RemoveTabParentFromTable(uint64_t aLayersId)
void
TabParent::SetOwnerElement(Element* aElement)
{
// If we held previous content then unregister for its events.
if (mFrameElement && mFrameElement->OwnerDoc()->GetWindow()) {
nsCOMPtr<nsPIDOMWindow> window = mFrameElement->OwnerDoc()->GetWindow();
nsCOMPtr<EventTarget> eventTarget = window->GetTopWindowRoot();
if (eventTarget) {
eventTarget->RemoveEventListener(NS_LITERAL_STRING("MozUpdateWindowPos"),
this, false);
}
}
// Update to the new content, and register to listen for events from it.
mFrameElement = aElement;
if (mFrameElement && mFrameElement->OwnerDoc()->GetWindow()) {
nsCOMPtr<nsPIDOMWindow> window = mFrameElement->OwnerDoc()->GetWindow();
nsCOMPtr<EventTarget> eventTarget = window->GetTopWindowRoot();
if (eventTarget) {
eventTarget->AddEventListener(NS_LITERAL_STRING("MozUpdateWindowPos"),
this, false, false);
}
}
TryCacheDPIAndScale();
}
@ -357,6 +378,8 @@ TabParent::Destroy()
return;
}
SetOwnerElement(nullptr);
// If this fails, it's most likely due to a content-process crash,
// and auto-cleanup will kick in. Otherwise, the child side will
// destroy itself and send back __delete__().
@ -881,9 +904,40 @@ TabParent::RecvSetDimensions(const uint32_t& aFlags,
return false;
}
static nsIntPoint
GetChromeDisplacement(nsFrameLoader *aFrameLoader)
{
if (!aFrameLoader) {
return nsIntPoint();
}
// Calculate the displacement from the primary frame of the tab
// content to the top-level frame of the widget we are in.
nsIFrame* contentFrame = aFrameLoader->GetPrimaryFrameOfOwningContent();
if (!contentFrame) {
return nsIntPoint();
}
nsIFrame* nextFrame = nsLayoutUtils::GetCrossDocParentFrame(contentFrame);
if (!nextFrame) {
NS_WARNING("Couldn't find window chrome to calculate displacement to.");
return nsIntPoint();
}
nsIFrame* rootFrame = nextFrame;
while (nextFrame) {
rootFrame = nextFrame;
nextFrame = nsLayoutUtils::GetCrossDocParentFrame(rootFrame);
}
nsPoint offset = contentFrame->GetOffsetToCrossDoc(rootFrame);
int32_t appUnitsPerDevPixel = rootFrame->PresContext()->AppUnitsPerDevPixel();
return nsIntPoint((int)(offset.x/appUnitsPerDevPixel),
(int)(offset.y/appUnitsPerDevPixel));
}
void
TabParent::UpdateDimensions(const nsIntRect& rect, const nsIntSize& size,
const nsIntPoint& aChromeDisp)
TabParent::UpdateDimensions(const nsIntRect& rect, const nsIntSize& size)
{
if (mIsDestroyed) {
return;
@ -894,12 +948,22 @@ TabParent::UpdateDimensions(const nsIntRect& rect, const nsIntSize& size,
if (!mUpdatedDimensions || mOrientation != orientation ||
mDimensions != size || !mRect.IsEqualEdges(rect)) {
nsCOMPtr<nsIWidget> widget = GetWidget();
nsIntRect contentRect = rect;
if (widget) {
contentRect.x += widget->GetClientOffset().x;
contentRect.y += widget->GetClientOffset().y;
}
mUpdatedDimensions = true;
mRect = rect;
mRect = contentRect;
mDimensions = size;
mOrientation = orientation;
unused << SendUpdateDimensions(mRect, mDimensions, mOrientation, aChromeDisp);
nsRefPtr<nsFrameLoader> frameLoader = GetFrameLoader();
nsIntPoint chromeOffset = GetChromeDisplacement(frameLoader);
unused << SendUpdateDimensions(mRect, mDimensions, mOrientation, chromeOffset);
}
}
@ -2654,6 +2718,27 @@ TabParent::DeallocPPluginWidgetParent(mozilla::plugins::PPluginWidgetParent* aAc
return true;
}
nsresult
TabParent::HandleEvent(nsIDOMEvent* aEvent)
{
nsAutoString eventType;
aEvent->GetType(eventType);
if (eventType.EqualsLiteral("MozUpdateWindowPos")) {
// This event is sent when the widget moved. Therefore we only update
// the position.
nsRefPtr<nsFrameLoader> frameLoader = GetFrameLoader();
if (!frameLoader) {
return NS_OK;
}
nsIntRect windowDims;
NS_ENSURE_SUCCESS(frameLoader->GetWindowDimensions(windowDims), NS_ERROR_FAILURE);
UpdateDimensions(windowDims, mDimensions);
return NS_OK;
}
return NS_OK;
}
class FakeChannel MOZ_FINAL : public nsIChannel,
public nsIAuthPromptCallback,
public nsIInterfaceRequestor,

View File

@ -22,6 +22,7 @@
#include "Units.h"
#include "WritingModes.h"
#include "js/TypeDecls.h"
#include "nsIDOMEventListener.h"
class nsFrameLoader;
class nsIFrameLoader;
@ -58,7 +59,8 @@ class nsIContentParent;
class Element;
struct StructuredCloneData;
class TabParent : public PBrowserParent
class TabParent : public PBrowserParent
, public nsIDOMEventListener
, public nsITabParent
, public nsIAuthPromptProvider
, public nsISecureBrowserUI
@ -98,6 +100,9 @@ public:
mBrowserDOMWindow = aBrowserDOMWindow;
}
// nsIDOMEventListener interfaces
NS_DECL_NSIDOMEVENTLISTENER
already_AddRefed<nsILoadContext> GetLoadContext();
nsIXULBrowserWindow* GetXULBrowserWindow();
@ -241,8 +246,7 @@ public:
// message-sending functions under a layer of indirection and
// eating the return values
void Show(const nsIntSize& size, bool aParentIsActive);
void UpdateDimensions(const nsIntRect& rect, const nsIntSize& size,
const nsIntPoint& chromeDisp);
void UpdateDimensions(const nsIntRect& rect, const nsIntSize& size);
void UpdateFrame(const layers::FrameMetrics& aFrameMetrics);
void UIResolutionChanged();
void AcknowledgeScrollUpdate(const ViewID& aScrollId, const uint32_t& aScrollGeneration);

View File

@ -8,5 +8,6 @@ toolkit.jar:
content/global/BrowserElementChild.js (../browser-element/BrowserElementChild.js)
content/global/BrowserElementChildPreload.js (../browser-element/BrowserElementChildPreload.js)
* content/global/BrowserElementPanning.js (../browser-element/BrowserElementPanning.js)
* content/global/BrowserElementPanningAPZDisabled.js (../browser-element/BrowserElementPanningAPZDisabled.js)
content/global/preload.js (preload.js)
content/global/post-fork-preload.js (post-fork-preload.js)

View File

@ -91,6 +91,13 @@ const BrowserElementIsPreloaded = true;
} catch (e) {
}
try {
if (Services.prefs.getBoolPref("layers.async-pan-zoom.enabled") === false) {
Services.scriptloader.loadSubScript("chrome://global/content/BrowserElementPanningAPZDisabled.js", global);
}
} catch (e) {
}
Services.scriptloader.loadSubScript("chrome://global/content/BrowserElementPanning.js", global);
Services.scriptloader.loadSubScript("chrome://global/content/BrowserElementChildPreload.js", global);

View File

@ -25,6 +25,8 @@ tab = tab
# The Role Description for definition list dl, dt and dd
term = term
definition = definition
# The Role Description for an input type="search" text field
searchTextField = search text field
# The Role Description for WAI-ARIA Landmarks
search = search
banner = banner

View File

@ -85,6 +85,15 @@ AudioSink::GetPosition()
return mLastGoodPosition;
}
bool
AudioSink::HasUnplayedFrames()
{
AssertCurrentThreadInMonitor();
// Experimentation suggests that GetPositionInFrames() is zero-indexed,
// so we need to add 1 here before comparing it to mWritten.
return mAudioStream && mAudioStream->GetPositionInFrames() + 1 < mWritten;
}
void
AudioSink::PrepareToShutdown()
{

View File

@ -27,6 +27,10 @@ public:
int64_t GetPosition();
// Check whether we've pushed more frames to the audio hardware than it has
// played.
bool HasUnplayedFrames();
// Tell the AudioSink to stop processing and initiate shutdown. Must be
// called with the decoder monitor held.
void PrepareToShutdown();

View File

@ -655,6 +655,9 @@ MediaDecoderReader* DecoderTraits::CreateReader(const nsACString& aType, Abstrac
{
MediaDecoderReader* decoderReader = nullptr;
if (!aDecoder) {
return decoderReader;
}
#ifdef MOZ_FMP4
if (IsMP4SupportedType(aType)) {
decoderReader = new MP4Reader(aDecoder);

View File

@ -1173,8 +1173,10 @@ MediaCache::Update()
actions.AppendElement(NONE);
MediaCacheStream* stream = mStreams[i];
if (stream->mClosed)
if (stream->mClosed) {
CACHE_LOG(PR_LOG_DEBUG, ("Stream %p closed", stream));
continue;
}
// Figure out where we should be reading from. It's the first
// uncached byte after the current mStreamOffset.
@ -1280,7 +1282,7 @@ MediaCache::Update()
for (uint32_t j = 0; j < i; ++j) {
MediaCacheStream* other = mStreams[j];
if (other->mResourceID == stream->mResourceID &&
!other->mClient->IsSuspended() &&
!other->mClosed && !other->mClient->IsSuspended() &&
other->mChannelOffset/BLOCK_SIZE == desiredOffset/BLOCK_SIZE) {
// This block is already going to be read by the other stream.
// So don't try to read it from this stream as well.

View File

@ -1438,7 +1438,6 @@ bool
MediaDecoder::IsTransportSeekable()
{
ReentrantMonitorAutoEnter mon(GetReentrantMonitor());
MOZ_ASSERT(OnDecodeThread() || NS_IsMainThread());
return GetResource()->IsTransportSeekable();
}
@ -1446,7 +1445,6 @@ bool MediaDecoder::IsMediaSeekable()
{
NS_ENSURE_TRUE(GetStateMachine(), false);
ReentrantMonitorAutoEnter mon(GetReentrantMonitor());
MOZ_ASSERT(OnDecodeThread() || NS_IsMainThread());
return mMediaSeekable;
}

View File

@ -310,8 +310,7 @@ bool MediaDecoderStateMachine::HaveNextFrameData() {
}
int64_t MediaDecoderStateMachine::GetDecodedAudioDuration() {
NS_ASSERTION(OnDecodeThread() || OnStateMachineThread(),
"Should be on decode thread or state machine thread");
MOZ_ASSERT(OnStateMachineThread());
AssertCurrentThreadInMonitor();
int64_t audioDecoded = AudioQueue().Duration();
if (mAudioEndTime != -1) {
@ -324,8 +323,7 @@ void MediaDecoderStateMachine::SendStreamAudio(AudioData* aAudio,
DecodedStreamData* aStream,
AudioSegment* aOutput)
{
NS_ASSERTION(OnDecodeThread() || OnStateMachineThread(),
"Should be on decode thread or state machine thread");
MOZ_ASSERT(OnStateMachineThread());
AssertCurrentThreadInMonitor();
// This logic has to mimic AudioSink closely to make sure we write
@ -702,6 +700,7 @@ MediaDecoderStateMachine::IsVideoSeekComplete()
void
MediaDecoderStateMachine::OnAudioDecoded(AudioData* aAudioSample)
{
MOZ_ASSERT(OnStateMachineThread());
ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
nsRefPtr<AudioData> audio(aAudioSample);
MOZ_ASSERT(audio);
@ -822,7 +821,7 @@ void
MediaDecoderStateMachine::OnNotDecoded(MediaData::Type aType,
MediaDecoderReader::NotDecodedReason aReason)
{
MOZ_ASSERT(OnDecodeThread());
MOZ_ASSERT(OnStateMachineThread());
ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
SAMPLE_LOG("OnNotDecoded (aType=%u, aReason=%u)", aType, aReason);
bool isAudio = aType == MediaData::AUDIO_DATA;
@ -845,10 +844,11 @@ MediaDecoderStateMachine::OnNotDecoded(MediaData::Type aType,
if (aReason == MediaDecoderReader::WAITING_FOR_DATA) {
MOZ_ASSERT(mReader->IsWaitForDataSupported(),
"Readers that send WAITING_FOR_DATA need to implement WaitForData");
WaitRequestRef(aType).Begin(mReader->WaitForData(aType)
->RefableThen(DecodeTaskQueue(), __func__, this,
&MediaDecoderStateMachine::OnWaitForDataResolved,
&MediaDecoderStateMachine::OnWaitForDataRejected));
WaitRequestRef(aType).Begin(ProxyMediaCall(DecodeTaskQueue(), mReader.get(), __func__,
&MediaDecoderReader::WaitForData, aType)
->RefableThen(mScheduler.get(), __func__, this,
&MediaDecoderStateMachine::OnWaitForDataResolved,
&MediaDecoderStateMachine::OnWaitForDataRejected));
return;
}
@ -926,6 +926,7 @@ MediaDecoderStateMachine::AcquireMonitorAndInvokeDecodeError()
void
MediaDecoderStateMachine::MaybeFinishDecodeFirstFrame()
{
MOZ_ASSERT(OnStateMachineThread());
AssertCurrentThreadInMonitor();
if ((IsAudioDecoding() && AudioQueue().GetSize() == 0) ||
(IsVideoDecoding() && VideoQueue().GetSize() == 0)) {
@ -939,7 +940,7 @@ MediaDecoderStateMachine::MaybeFinishDecodeFirstFrame()
void
MediaDecoderStateMachine::OnVideoDecoded(VideoData* aVideoSample)
{
MOZ_ASSERT(OnDecodeThread());
MOZ_ASSERT(OnStateMachineThread());
ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
nsRefPtr<VideoData> video(aVideoSample);
mVideoDataRequest.Complete();
@ -1046,6 +1047,7 @@ MediaDecoderStateMachine::OnVideoDecoded(VideoData* aVideoSample)
void
MediaDecoderStateMachine::CheckIfSeekComplete()
{
MOZ_ASSERT(OnStateMachineThread());
AssertCurrentThreadInMonitor();
const bool videoSeekComplete = IsVideoSeekComplete();
@ -1073,7 +1075,7 @@ MediaDecoderStateMachine::CheckIfSeekComplete()
mDecodeToSeekTarget = false;
RefPtr<nsIRunnable> task(
NS_NewRunnableMethod(this, &MediaDecoderStateMachine::SeekCompleted));
nsresult rv = DecodeTaskQueue()->Dispatch(task);
nsresult rv = GetStateMachineThread()->Dispatch(task, NS_DISPATCH_NORMAL);
if (NS_FAILED(rv)) {
DecodeError();
}
@ -1146,6 +1148,10 @@ nsresult MediaDecoderStateMachine::Init(MediaDecoderStateMachine* aCloneDonor)
void MediaDecoderStateMachine::StopPlayback()
{
// XXXbholley - Needed because DecodeSeek runs on the decode thread.
// Once bug 1135170 lands, this becomes state-machine only, and we can invoke
// DispatchDecodeTasksIfNeeded directly.
MOZ_ASSERT(OnStateMachineThread() || OnDecodeThread());
DECODER_LOG("StopPlayback()");
AssertCurrentThreadInMonitor();
@ -1162,7 +1168,9 @@ void MediaDecoderStateMachine::StopPlayback()
NS_ASSERTION(!IsPlaying(), "Should report not playing at end of StopPlayback()");
mDecoder->UpdateStreamBlockingForStateMachinePlaying();
DispatchDecodeTasksIfNeeded();
nsCOMPtr<nsIRunnable> event =
NS_NewRunnableMethod(this, &MediaDecoderStateMachine::AcquireMonitorAndInvokeDispatchDecodeTasksIfNeeded);
GetStateMachineThread()->Dispatch(event, NS_DISPATCH_NORMAL);
}
void MediaDecoderStateMachine::SetSyncPointForMediaStream()
@ -1242,9 +1250,10 @@ void MediaDecoderStateMachine::MaybeStartPlayback()
void MediaDecoderStateMachine::UpdatePlaybackPositionInternal(int64_t aTime)
{
// XXXbholley - Needed because DecodeSeek runs on the decode thread.
// Once bug 1135170 lands, this becomes state-machine only.
MOZ_ASSERT(OnStateMachineThread() || OnDecodeThread());
SAMPLE_LOG("UpdatePlaybackPositionInternal(%lld) (mStartTime=%lld)", aTime, mStartTime);
NS_ASSERTION(OnStateMachineThread() || OnDecodeThread(),
"Should be on state machine thread.");
AssertCurrentThreadInMonitor();
NS_ASSERTION(mStartTime >= 0, "Should have positive mStartTime");
@ -1509,7 +1518,6 @@ void MediaDecoderStateMachine::SetDormant(bool aDormant)
mCurrentSeekTarget.Reset();
ScheduleStateMachine();
SetState(DECODER_STATE_DORMANT);
StopPlayback();
mDecoder->GetReentrantMonitor().NotifyAll();
} else if ((aDormant != true) && (mState == DECODER_STATE_DORMANT)) {
mDecodingFrozenAtStateDecoding = true;
@ -1540,8 +1548,7 @@ void MediaDecoderStateMachine::Shutdown()
void MediaDecoderStateMachine::StartDecoding()
{
NS_ASSERTION(OnStateMachineThread() || OnDecodeThread(),
"Should be on state machine or decode thread.");
MOZ_ASSERT(OnStateMachineThread());
ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
if (mState == DECODER_STATE_DECODING) {
return;
@ -1642,6 +1649,10 @@ void MediaDecoderStateMachine::PlayInternal()
void MediaDecoderStateMachine::ResetPlayback()
{
// XXXbholley - Needed because DecodeSeek runs on the decode thread.
// Once bug 1135170 lands, this becomes state-machine only.
MOZ_ASSERT(OnStateMachineThread() || OnDecodeThread());
// We should be reseting because we're seeking, shutting down, or
// entering dormant state. We could also be in the process of going dormant,
// and have just switched to exiting dormant before we finished entering
@ -1784,8 +1795,9 @@ MediaDecoderStateMachine::StartSeek(const SeekTarget& aTarget)
void MediaDecoderStateMachine::StopAudioThread()
{
NS_ASSERTION(OnDecodeThread() || OnStateMachineThread(),
"Should be on decode thread or state machine thread");
// XXXbholley - Needed because DecodeSeek runs on the decode thread.
// Once bug 1135170 lands, this becomes state-machine only.
MOZ_ASSERT(OnStateMachineThread() || OnDecodeThread());
AssertCurrentThreadInMonitor();
if (mStopAudioThread) {
@ -1833,7 +1845,7 @@ MediaDecoderStateMachine::EnqueueDecodeFirstFrameTask()
RefPtr<nsIRunnable> task(
NS_NewRunnableMethod(this, &MediaDecoderStateMachine::CallDecodeFirstFrame));
nsresult rv = DecodeTaskQueue()->Dispatch(task);
nsresult rv = GetStateMachineThread()->Dispatch(task, NS_DISPATCH_NORMAL);
NS_ENSURE_SUCCESS(rv, rv);
return NS_OK;
}
@ -1841,18 +1853,19 @@ MediaDecoderStateMachine::EnqueueDecodeFirstFrameTask()
void
MediaDecoderStateMachine::SetReaderIdle()
{
#ifdef PR_LOGGING
{
ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
DECODER_LOG("SetReaderIdle() audioQueue=%lld videoQueue=%lld",
GetDecodedAudioDuration(),
VideoQueue().Duration());
}
#endif
MOZ_ASSERT(OnDecodeThread());
DECODER_LOG("Invoking SetReaderIdle()");
mReader->SetIdle();
}
void
MediaDecoderStateMachine::AcquireMonitorAndInvokeDispatchDecodeTasksIfNeeded()
{
MOZ_ASSERT(OnStateMachineThread());
ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
DispatchDecodeTasksIfNeeded();
}
void
MediaDecoderStateMachine::DispatchDecodeTasksIfNeeded()
{
@ -1909,6 +1922,9 @@ MediaDecoderStateMachine::DispatchDecodeTasksIfNeeded()
}
if (needIdle) {
DECODER_LOG("Dispatching SetReaderIdle() audioQueue=%lld videoQueue=%lld",
GetDecodedAudioDuration(),
VideoQueue().Duration());
RefPtr<nsIRunnable> event = NS_NewRunnableMethod(
this, &MediaDecoderStateMachine::SetReaderIdle);
nsresult rv = DecodeTaskQueue()->Dispatch(event.forget());
@ -1921,8 +1937,7 @@ MediaDecoderStateMachine::DispatchDecodeTasksIfNeeded()
nsresult
MediaDecoderStateMachine::EnqueueDecodeSeekTask()
{
NS_ASSERTION(OnStateMachineThread() || OnDecodeThread(),
"Should be on state machine or decode thread.");
MOZ_ASSERT(OnStateMachineThread());
AssertCurrentThreadInMonitor();
RefPtr<nsIRunnable> task(
@ -1939,9 +1954,8 @@ MediaDecoderStateMachine::EnqueueDecodeSeekTask()
nsresult
MediaDecoderStateMachine::DispatchAudioDecodeTaskIfNeeded()
{
MOZ_ASSERT(OnStateMachineThread());
ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
NS_ASSERTION(OnStateMachineThread() || OnDecodeThread(),
"Should be on state machine or decode thread.");
if (NeedToDecodeAudio()) {
return EnsureAudioDecodeTaskQueued();
@ -1953,9 +1967,8 @@ MediaDecoderStateMachine::DispatchAudioDecodeTaskIfNeeded()
nsresult
MediaDecoderStateMachine::EnsureAudioDecodeTaskQueued()
{
MOZ_ASSERT(OnStateMachineThread());
AssertCurrentThreadInMonitor();
NS_ASSERTION(OnStateMachineThread() || OnDecodeThread(),
"Should be on state machine or decode thread.");
SAMPLE_LOG("EnsureAudioDecodeTaskQueued isDecoding=%d status=%s",
IsAudioDecoding(), AudioRequestStatus());
@ -1977,7 +1990,7 @@ MediaDecoderStateMachine::EnsureAudioDecodeTaskQueued()
mAudioDataRequest.Begin(ProxyMediaCall(DecodeTaskQueue(), mReader.get(),
__func__, &MediaDecoderReader::RequestAudioData)
->RefableThen(DecodeTaskQueue(), __func__, this,
->RefableThen(mScheduler.get(), __func__, this,
&MediaDecoderStateMachine::OnAudioDecoded,
&MediaDecoderStateMachine::OnAudioNotDecoded));
@ -1987,9 +2000,8 @@ MediaDecoderStateMachine::EnsureAudioDecodeTaskQueued()
nsresult
MediaDecoderStateMachine::DispatchVideoDecodeTaskIfNeeded()
{
MOZ_ASSERT(OnStateMachineThread());
ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
NS_ASSERTION(OnStateMachineThread() || OnDecodeThread(),
"Should be on state machine or decode thread.");
if (NeedToDecodeVideo()) {
return EnsureVideoDecodeTaskQueued();
@ -2001,14 +2013,12 @@ MediaDecoderStateMachine::DispatchVideoDecodeTaskIfNeeded()
nsresult
MediaDecoderStateMachine::EnsureVideoDecodeTaskQueued()
{
MOZ_ASSERT(OnStateMachineThread());
AssertCurrentThreadInMonitor();
SAMPLE_LOG("EnsureVideoDecodeTaskQueued isDecoding=%d status=%s",
IsVideoDecoding(), VideoRequestStatus());
NS_ASSERTION(OnStateMachineThread() || OnDecodeThread(),
"Should be on state machine or decode thread.");
if (mState != DECODER_STATE_DECODING &&
mState != DECODER_STATE_DECODING_FIRSTFRAME &&
mState != DECODER_STATE_BUFFERING &&
@ -2036,7 +2046,7 @@ MediaDecoderStateMachine::EnsureVideoDecodeTaskQueued()
mVideoDataRequest.Begin(ProxyMediaCall(DecodeTaskQueue(), mReader.get(), __func__,
&MediaDecoderReader::RequestVideoData,
skipToNextKeyFrame, currentTime)
->RefableThen(DecodeTaskQueue(), __func__, this,
->RefableThen(mScheduler.get(), __func__, this,
&MediaDecoderStateMachine::OnVideoDecoded,
&MediaDecoderStateMachine::OnVideoNotDecoded));
return NS_OK;
@ -2045,8 +2055,7 @@ MediaDecoderStateMachine::EnsureVideoDecodeTaskQueued()
nsresult
MediaDecoderStateMachine::StartAudioThread()
{
NS_ASSERTION(OnStateMachineThread() || OnDecodeThread(),
"Should be on state machine or decode thread.");
MOZ_ASSERT(OnStateMachineThread());
AssertCurrentThreadInMonitor();
if (mAudioCaptured) {
NS_ASSERTION(mStopAudioThread, "mStopAudioThread must always be true if audio is captured");
@ -2103,6 +2112,13 @@ bool MediaDecoderStateMachine::HasLowDecodedData(int64_t aAudioUsecs)
static_cast<uint32_t>(VideoQueue().GetSize()) < LOW_VIDEO_FRAMES));
}
bool MediaDecoderStateMachine::OutOfDecodedAudio()
{
return IsAudioDecoding() && !AudioQueue().IsFinished() &&
AudioQueue().GetSize() == 0 &&
(!mAudioSink || !mAudioSink->HasUnplayedFrames());
}
bool MediaDecoderStateMachine::HasLowUndecodedData()
{
return HasLowUndecodedData(mLowDataThresholdUsecs);
@ -2147,13 +2163,24 @@ void
MediaDecoderStateMachine::DecodeError()
{
AssertCurrentThreadInMonitor();
NS_ASSERTION(OnDecodeThread(), "Should be on decode thread.");
if (mState == DECODER_STATE_SHUTDOWN) {
// Already shutdown.
return;
}
// DecodeError should probably be redesigned so that it doesn't need to run
// on the Decode Task Queue, but this does the trick for now.
if (!OnDecodeThread()) {
RefPtr<nsIRunnable> task(
NS_NewRunnableMethod(this, &MediaDecoderStateMachine::AcquireMonitorAndInvokeDecodeError));
nsresult rv = DecodeTaskQueue()->Dispatch(task);
if (NS_FAILED(rv)) {
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.
@ -2287,6 +2314,7 @@ MediaDecoderStateMachine::EnqueueFirstFrameLoadedEvent()
void
MediaDecoderStateMachine::CallDecodeFirstFrame()
{
MOZ_ASSERT(OnStateMachineThread());
ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
if (mState != DECODER_STATE_DECODING_FIRSTFRAME) {
return;
@ -2300,20 +2328,20 @@ MediaDecoderStateMachine::CallDecodeFirstFrame()
nsresult
MediaDecoderStateMachine::DecodeFirstFrame()
{
MOZ_ASSERT(OnStateMachineThread());
AssertCurrentThreadInMonitor();
NS_ASSERTION(OnDecodeThread(), "Should be on decode thread.");
MOZ_ASSERT(mState == DECODER_STATE_DECODING_FIRSTFRAME);
DECODER_LOG("DecodeFirstFrame started");
if (HasAudio()) {
RefPtr<nsIRunnable> decodeTask(
NS_NewRunnableMethod(this, &MediaDecoderStateMachine::DispatchAudioDecodeTaskIfNeeded));
AudioQueue().AddPopListener(decodeTask, DecodeTaskQueue());
AudioQueue().AddPopListener(decodeTask, GetStateMachineThread());
}
if (HasVideo()) {
RefPtr<nsIRunnable> decodeTask(
NS_NewRunnableMethod(this, &MediaDecoderStateMachine::DispatchVideoDecodeTaskIfNeeded));
VideoQueue().AddPopListener(decodeTask, DecodeTaskQueue());
VideoQueue().AddPopListener(decodeTask, GetStateMachineThread());
}
if (IsRealTime()) {
@ -2328,12 +2356,10 @@ MediaDecoderStateMachine::DecodeFirstFrame()
nsresult res = FinishDecodeFirstFrame();
NS_ENSURE_SUCCESS(res, res);
} else {
// NB: We're already on the decode thread, but we proxy these anyway so that
// we don't need to worry about dropping locks.
if (HasAudio()) {
mAudioDataRequest.Begin(ProxyMediaCall(DecodeTaskQueue(), mReader.get(),
__func__, &MediaDecoderReader::RequestAudioData)
->RefableThen(DecodeTaskQueue(), __func__, this,
->RefableThen(mScheduler.get(), __func__, this,
&MediaDecoderStateMachine::OnAudioDecoded,
&MediaDecoderStateMachine::OnAudioNotDecoded));
}
@ -2341,7 +2367,7 @@ MediaDecoderStateMachine::DecodeFirstFrame()
mVideoDecodeStartTime = TimeStamp::Now();
mVideoDataRequest.Begin(ProxyMediaCall(DecodeTaskQueue(), mReader.get(),
__func__, &MediaDecoderReader::RequestVideoData, false, int64_t(0))
->RefableThen(DecodeTaskQueue(), __func__, this,
->RefableThen(mScheduler.get(), __func__, this,
&MediaDecoderStateMachine::OnVideoDecoded,
&MediaDecoderStateMachine::OnVideoNotDecoded));
}
@ -2354,7 +2380,7 @@ nsresult
MediaDecoderStateMachine::FinishDecodeFirstFrame()
{
AssertCurrentThreadInMonitor();
NS_ASSERTION(OnDecodeThread(), "Should be on decode thread.");
MOZ_ASSERT(OnStateMachineThread());
DECODER_LOG("FinishDecodeFirstFrame");
if (mState == DECODER_STATE_SHUTDOWN) {
@ -2499,8 +2525,8 @@ void MediaDecoderStateMachine::DecodeSeek()
DECODER_LOG("Seek !currentTimeChanged...");
mDropAudioUntilNextDiscontinuity = false;
mDropVideoUntilNextDiscontinuity = false;
nsresult rv = DecodeTaskQueue()->Dispatch(
NS_NewRunnableMethod(this, &MediaDecoderStateMachine::SeekCompleted));
nsCOMPtr<nsIRunnable> task = NS_NewRunnableMethod(this, &MediaDecoderStateMachine::SeekCompleted);
nsresult rv = GetStateMachineThread()->Dispatch(task, NS_DISPATCH_NORMAL);
if (NS_FAILED(rv)) {
DecodeError();
}
@ -2542,7 +2568,11 @@ MediaDecoderStateMachine::OnSeekCompleted(int64_t aTime)
// We must decode the first samples of active streams, so we can determine
// the new stream time. So dispatch tasks to do that.
mDecodeToSeekTarget = true;
DispatchDecodeTasksIfNeeded();
// XXXbholley - This can become a direct call once bug 1135170 lands.
nsCOMPtr<nsIRunnable> event =
NS_NewRunnableMethod(this, &MediaDecoderStateMachine::AcquireMonitorAndInvokeDispatchDecodeTasksIfNeeded);
GetStateMachineThread()->Dispatch(event, NS_DISPATCH_NORMAL);
}
void
@ -2570,6 +2600,7 @@ MediaDecoderStateMachine::OnSeekFailed(nsresult aResult)
void
MediaDecoderStateMachine::SeekCompleted()
{
MOZ_ASSERT(OnStateMachineThread());
ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
// We must reset the seek target when exiting this function, but not
@ -2578,7 +2609,6 @@ MediaDecoderStateMachine::SeekCompleted()
// an inconsistent state.
AutoSetOnScopeExit<SeekTarget> reset(mCurrentSeekTarget, SeekTarget());
NS_ASSERTION(OnDecodeThread(), "Should be on decode thread.");
if (mState != DECODER_STATE_SEEKING) {
return;
}
@ -2665,8 +2695,6 @@ MediaDecoderStateMachine::SeekCompleted()
// Try to decode another frame to detect if we're at the end...
DECODER_LOG("Seek completed, mCurrentFrameTime=%lld", mCurrentFrameTime);
mCurrentSeekTarget = SeekTarget();
// Reset quick buffering status. This ensures that if we began the
// seek while quick-buffering, we won't bypass quick buffering mode
// if we need to buffer after the seek.
@ -2728,7 +2756,7 @@ void
MediaDecoderStateMachine::ShutdownReader()
{
MOZ_ASSERT(OnDecodeThread());
mReader->Shutdown()->Then(GetStateMachineThread(), __func__, this,
mReader->Shutdown()->Then(mScheduler.get(), __func__, this,
&MediaDecoderStateMachine::FinishShutdown,
&MediaDecoderStateMachine::FinishShutdown);
}
@ -2958,8 +2986,7 @@ nsresult MediaDecoderStateMachine::RunStateMachine()
void
MediaDecoderStateMachine::FlushDecoding()
{
NS_ASSERTION(OnStateMachineThread() || OnDecodeThread(),
"Should be on state machine or decode thread.");
MOZ_ASSERT(OnStateMachineThread());
AssertCurrentThreadInMonitor();
{
@ -3010,8 +3037,7 @@ MediaDecoderStateMachine::ResetDecode()
void MediaDecoderStateMachine::RenderVideoFrame(VideoData* aData,
TimeStamp aTarget)
{
NS_ASSERTION(OnStateMachineThread() || OnDecodeThread(),
"Should be on state machine or decode thread.");
MOZ_ASSERT(OnStateMachineThread());
mDecoder->GetReentrantMonitor().AssertNotCurrentThreadIn();
if (aData->mDuplicate) {
@ -3636,12 +3662,7 @@ void MediaDecoderStateMachine::OnAudioSinkError()
// Otherwise notify media decoder/element about this error for it makes
// no sense to play an audio-only file without sound output.
RefPtr<nsIRunnable> task(
NS_NewRunnableMethod(this, &MediaDecoderStateMachine::AcquireMonitorAndInvokeDecodeError));
nsresult rv = DecodeTaskQueue()->Dispatch(task);
if (NS_FAILED(rv)) {
DECODER_WARN("Failed to dispatch AcquireMonitorAndInvokeDecodeError");
}
DecodeError();
}
} // namespace mozilla

View File

@ -496,10 +496,7 @@ protected:
// May not be invoked when mReader->UseBufferingHeuristics() is false.
bool HasLowDecodedData(int64_t aAudioUsecs);
bool OutOfDecodedAudio()
{
return IsAudioDecoding() && !AudioQueue().IsFinished() && AudioQueue().GetSize() == 0;
}
bool OutOfDecodedAudio();
bool OutOfDecodedVideo()
{
@ -622,7 +619,7 @@ protected:
void EnqueueFirstFrameLoadedEvent();
// Dispatches a task to the decode task queue to begin decoding content.
// Dispatches a task to the state machine thread to begin decoding content.
// This is threadsafe and can be called on any thread.
// The decoder monitor must be held.
nsresult EnqueueDecodeFirstFrameTask();
@ -657,6 +654,7 @@ protected:
// to idle mode. This is threadsafe, and can be called from any thread.
// The decoder monitor must be held.
void DispatchDecodeTasksIfNeeded();
void AcquireMonitorAndInvokeDispatchDecodeTasksIfNeeded();
// Returns the "media time". This is the absolute time which the media
// playback has reached. i.e. this returns values in the range
@ -684,7 +682,7 @@ protected:
// Wraps the call to DecodeMetadata(), signals a DecodeError() on failure.
void CallDecodeMetadata();
// Initiate first content decoding. Called on the decode thread.
// Initiate first content decoding. Called on the state machine thread.
// The decoder monitor must be held with exactly one lock count.
nsresult DecodeFirstFrame();
@ -775,7 +773,7 @@ protected:
// Used to schedule state machine cycles. This should never outlive
// the life cycle of the state machine.
const nsAutoPtr<MediaDecoderStateMachineScheduler> mScheduler;
const nsRefPtr<MediaDecoderStateMachineScheduler> mScheduler;
// 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.

View File

@ -26,10 +26,10 @@ class MediaDecoderStateMachineScheduler {
SCHEDULER_STATE_SHUTDOWN
};
public:
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(MediaDecoderStateMachineScheduler)
MediaDecoderStateMachineScheduler(ReentrantMonitor& aMonitor,
nsresult (*aTimeoutCallback)(void*),
void* aClosure, bool aRealTime);
~MediaDecoderStateMachineScheduler();
nsresult Init();
nsresult Schedule(int64_t aUsecs = 0);
void ScheduleAndShutdown();
@ -54,6 +54,7 @@ public:
}
private:
~MediaDecoderStateMachineScheduler();
void ResetTimer();
// Callback function provided by MediaDecoderStateMachine to run

View File

@ -5,6 +5,8 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "MediaPromise.h"
#include "MediaDecoderStateMachineScheduler.h"
#include "MediaTaskQueue.h"
#include "nsThreadUtils.h"
@ -23,6 +25,12 @@ DispatchMediaPromiseRunnable(nsIEventTarget* aEventTarget, nsIRunnable* aRunnabl
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)
{
@ -36,5 +44,11 @@ void AssertOnThread(nsIEventTarget* aTarget)
MOZ_ASSERT(NS_GetCurrentThread() == targetThread);
}
void
AssertOnThread(MediaDecoderStateMachineScheduler* aScheduler)
{
MOZ_ASSERT(aScheduler->OnStateMachineThread());
}
}
} // namespace mozilla

View File

@ -32,14 +32,17 @@ extern PRLogModuleInfo* 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
@ -591,7 +594,19 @@ protected:
Type mMethod;
};
// NB: MethodCallWithOneArg definition should go here, if/when it is needed.
template<typename PromiseType, typename ThisType, typename Arg1Type>
class MethodCallWithOneArg : public MethodCallBase<PromiseType>
{
public:
typedef nsRefPtr<PromiseType>(ThisType::*Type)(Arg1Type);
MethodCallWithOneArg(ThisType* aThisVal, Type aMethod, Arg1Type aArg1)
: mThisVal(aThisVal), mMethod(aMethod), mArg1(aArg1) {}
nsRefPtr<PromiseType> Invoke() MOZ_OVERRIDE { return ((*mThisVal).*mMethod)(mArg1); }
protected:
nsRefPtr<ThisType> mThisVal;
Type mMethod;
Arg1Type mArg1;
};
template<typename PromiseType, typename ThisType, typename Arg1Type, typename Arg2Type>
class MethodCallWithTwoArgs : public MethodCallBase<PromiseType>
@ -652,7 +667,15 @@ ProxyMediaCall(TargetType* aTarget, ThisType* aThisVal, const char* aCallerName,
return detail::ProxyInternal(aTarget, methodCall, aCallerName);
}
// NB: One-arg overload should go here, if/when it is needed.
template<typename PromiseType, typename TargetType, typename ThisType, typename Arg1Type>
static nsRefPtr<PromiseType>
ProxyMediaCall(TargetType* aTarget, ThisType* aThisVal, const char* aCallerName,
nsRefPtr<PromiseType>(ThisType::*aMethod)(Arg1Type), Arg1Type aArg1)
{
typedef detail::MethodCallWithOneArg<PromiseType, ThisType, Arg1Type> MethodCallType;
MethodCallType* methodCall = new MethodCallType(aThisVal, aMethod, aArg1);
return detail::ProxyInternal(aTarget, methodCall, aCallerName);
}
template<typename PromiseType, typename TargetType, typename ThisType,
typename Arg1Type, typename Arg2Type>

View File

@ -157,18 +157,18 @@ template <class T> class MediaQueue : private nsDeque {
mPopListeners.Clear();
}
void AddPopListener(nsIRunnable* aRunnable, MediaTaskQueue* aTaskQueue) {
void AddPopListener(nsIRunnable* aRunnable, nsIEventTarget* aTarget) {
ReentrantMonitorAutoEnter mon(mReentrantMonitor);
mPopListeners.AppendElement(Listener(aRunnable, aTaskQueue));
mPopListeners.AppendElement(Listener(aRunnable, aTarget));
}
private:
mutable ReentrantMonitor mReentrantMonitor;
struct Listener {
Listener(nsIRunnable* aRunnable, MediaTaskQueue* aTaskQueue)
Listener(nsIRunnable* aRunnable, nsIEventTarget* aTarget)
: mRunnable(aRunnable)
, mTarget(aTaskQueue)
, mTarget(aTarget)
{
}
Listener(const Listener& aOther)
@ -177,7 +177,7 @@ private:
{
}
RefPtr<nsIRunnable> mRunnable;
RefPtr<MediaTaskQueue> mTarget;
RefPtr<nsIEventTarget> mTarget;
};
nsTArray<Listener> mPopListeners;
@ -185,7 +185,7 @@ private:
void NotifyPopListeners() {
for (uint32_t i = 0; i < mPopListeners.Length(); i++) {
Listener& l = mPopListeners[i];
l.mTarget->Dispatch(l.mRunnable);
l.mTarget->Dispatch(l.mRunnable, NS_DISPATCH_NORMAL);
}
}

View File

@ -195,12 +195,8 @@ MediaTaskQueue::IsEmpty()
bool
MediaTaskQueue::IsCurrentThreadIn()
{
#ifdef DEBUG
MonitorAutoLock mon(mQueueMonitor);
return NS_GetCurrentThread() == mRunningThread;
#else
return false;
#endif
}
nsresult

View File

@ -28,6 +28,7 @@
#include "nsServiceManagerUtils.h"
#include "mozIGeckoMediaPluginService.h"
#include "mozilla/dom/MediaKeySystemAccess.h"
#include "nsPrintfCString.h"
namespace mozilla {
@ -174,6 +175,8 @@ MediaKeys::StorePromise(Promise* aPromise)
MOZ_ASSERT(aPromise);
uint32_t id = sEMEPromiseCount++;
EME_LOG("MediaKeys::StorePromise() id=%d", id);
// Keep MediaKeys alive for the lifetime of its promises. Any still-pending
// promises are rejected in Shutdown().
AddRef();
@ -185,7 +188,10 @@ MediaKeys::StorePromise(Promise* aPromise)
already_AddRefed<Promise>
MediaKeys::RetrievePromise(PromiseId aId)
{
MOZ_ASSERT(mPromises.Contains(aId));
if (!mPromises.Contains(aId)) {
NS_WARNING(nsPrintfCString("Tried to retrieve a non-existent promise id=%d", aId).get());
return nullptr;
}
nsRefPtr<Promise> promise;
mPromises.Remove(aId, getter_AddRefs(promise));
Release();
@ -195,9 +201,10 @@ MediaKeys::RetrievePromise(PromiseId aId)
void
MediaKeys::RejectPromise(PromiseId aId, nsresult aExceptionCode)
{
EME_LOG("MediaKeys::RejectPromise(%d, 0x%x)", aId, aExceptionCode);
nsRefPtr<Promise> promise(RetrievePromise(aId));
if (!promise) {
NS_WARNING("MediaKeys tried to reject a non-existent promise");
return;
}
if (mPendingSessions.Contains(aId)) {
@ -242,9 +249,10 @@ MediaKeys::OnSessionIdReady(MediaKeySession* aSession)
void
MediaKeys::ResolvePromise(PromiseId aId)
{
EME_LOG("MediaKeys::ResolvePromise(%d)", aId);
nsRefPtr<Promise> promise(RetrievePromise(aId));
if (!promise) {
NS_WARNING("MediaKeys tried to resolve a non-existent promise");
return;
}
if (mPendingSessions.Contains(aId)) {
@ -357,11 +365,11 @@ MediaKeys::OnCDMCreated(PromiseId aId, const nsACString& aNodeId)
{
nsRefPtr<Promise> promise(RetrievePromise(aId));
if (!promise) {
NS_WARNING("MediaKeys tried to resolve a non-existent promise");
return;
}
mNodeId = aNodeId;
nsRefPtr<MediaKeys> keys(this);
EME_LOG("MediaKeys::OnCDMCreated() resolve promise id=%d", aId);
promise->MaybeResolve(keys);
if (mCreatePromiseId == aId) {
Release();
@ -399,9 +407,10 @@ MediaKeys::OnSessionLoaded(PromiseId aId, bool aSuccess)
{
nsRefPtr<Promise> promise(RetrievePromise(aId));
if (!promise) {
NS_WARNING("MediaKeys tried to resolve a non-existent promise");
return;
}
EME_LOG("MediaKeys::OnSessionLoaded() resolve promise id=%d", aId);
promise->MaybeResolve(aSuccess);
}

View File

@ -469,7 +469,9 @@ MP4Reader::ReadMetadata(MediaInfo* aInfo,
mInfo.mVideo.mDisplay =
nsIntSize(video.display_width, video.display_height);
mVideo.mCallback = new DecoderCallback(this, kVideo);
if (mSharedDecoderManager) {
if (!mIsEncrypted && mSharedDecoderManager) {
// Note: Don't use SharedDecoderManager in EME content, as it doesn't
// handle reiniting the decoder properly yet.
mVideo.mDecoder =
mSharedDecoderManager->CreateVideoDecoder(mPlatform,
video,
@ -1075,7 +1077,9 @@ void MP4Reader::NotifyResourcesStatusChanged()
void
MP4Reader::SetIdle()
{
if (mSharedDecoderManager && mVideo.mDecoder) {
if (!mIsEncrypted && mSharedDecoderManager && mVideo.mDecoder) {
// Note: Don't use SharedDecoderManager in EME content, as it doesn't
// handle reiniting the decoder properly yet.
mSharedDecoderManager->SetIdle(mVideo.mDecoder);
NotifyResourcesStatusChanged();
}

View File

@ -27,6 +27,9 @@ PRLogModuleInfo* GetAppleMediaLog() {
namespace mozilla {
// This defines the resolution height over which VDA will be prefered.
#define VDA_RESOLUTION_THRESHOLD 720
bool AppleDecoderModule::sInitialized = false;
bool AppleDecoderModule::sIsVTAvailable = false;
bool AppleDecoderModule::sIsVTHWAvailable = false;
@ -47,12 +50,12 @@ AppleDecoderModule::Init()
{
MOZ_ASSERT(NS_IsMainThread(), "Must be on main thread.");
sForceVDA = Preferences::GetBool("media.apple.forcevda", false);
if (sInitialized) {
return;
}
Preferences::AddBoolVarCache(&sForceVDA, "media.apple.forcevda", false);
// dlopen VideoDecodeAcceleration.framework if it's available.
sIsVDAAvailable = AppleVDALinker::Link();
@ -163,7 +166,9 @@ AppleDecoderModule::CreateVideoDecoder(const mp4_demuxer::VideoDecoderConfig& aC
{
nsRefPtr<MediaDataDecoder> decoder;
if (sIsVDAAvailable && (!sIsVTHWAvailable || sForceVDA)) {
if (sIsVDAAvailable &&
(!sIsVTHWAvailable || sForceVDA ||
aConfig.image_height >= VDA_RESOLUTION_THRESHOLD)) {
decoder =
AppleVDADecoder::CreateVDADecoder(aConfig,
aVideoTaskQueue,

View File

@ -9,7 +9,7 @@
namespace mozilla {
#if defined(DEBUG)
static bool IsOnGMPThread()
bool IsOnGMPThread()
{
nsCOMPtr<mozIGeckoMediaPluginService> mps = do_GetService("@mozilla.org/gecko-media-plugin-service;1");
MOZ_ASSERT(mps);

View File

@ -11,7 +11,7 @@
namespace mozilla {
#if defined(DEBUG)
static bool IsOnGMPThread();
extern bool IsOnGMPThread();
#endif
void

View File

@ -69,7 +69,7 @@ void
GMPVideoDecoderParent::Close()
{
LOGD(("%s: %p", __FUNCTION__, this));
MOZ_ASSERT(mPlugin->GMPThread() == NS_GetCurrentThread());
MOZ_ASSERT(!mPlugin || mPlugin->GMPThread() == NS_GetCurrentThread());
// Consumer is done with us; we can shut down. No more callbacks should
// be made to mCallback. Note: do this before Shutdown()!
mCallback = nullptr;

View File

@ -39,11 +39,13 @@
#include "gmp-video-frame-encoded.h"
#include "gmp-video-codec.h"
// This interface must be called on the main thread only.
class GMPVideoHost
{
public:
// Construct various video API objects. Host does not retain reference,
// caller is owner and responsible for deleting.
// MAIN THREAD ONLY
virtual GMPErr CreateFrame(GMPVideoFrameFormat aFormat, GMPVideoFrame** aFrame) = 0;
virtual GMPErr CreatePlane(GMPPlane** aPlane) = 0;
};

View File

@ -23,6 +23,8 @@
#endif
#ifdef PR_LOGGING
extern PRLogModuleInfo* GetMediaSourceLog();
#define MSE_DEBUG(arg, ...) PR_LOG(GetMediaSourceLog(), PR_LOG_DEBUG, ("MediaSourceReader(%p)::%s: " arg, this, __func__, ##__VA_ARGS__))
#define MSE_DEBUGV(arg, ...) PR_LOG(GetMediaSourceLog(), PR_LOG_DEBUG + 1, ("MediaSourceReader(%p)::%s: " arg, this, __func__, ##__VA_ARGS__))
#else
@ -37,6 +39,12 @@
// default value used in Blink, kDefaultBufferDurationInMs.
#define EOS_FUZZ_US 125000
// Audio and video source buffers often have a slight duration
// discrepency. We want to handle the case where a source buffer is smaller than
// another by more than EOS_FUZZ_US so we can properly detect EOS in IsNearEnd().
// This value was chosen at random, to cater for most streams seen in the wild.
#define DURATION_DIFFERENCE_FUZZ 300000
using mozilla::dom::TimeRanges;
namespace mozilla {
@ -108,6 +116,7 @@ MediaSourceReader::SizeOfAudioQueueInFrames()
nsRefPtr<MediaDecoderReader::AudioDataPromise>
MediaSourceReader::RequestAudioData()
{
MOZ_ASSERT(OnDecodeThread());
nsRefPtr<AudioDataPromise> p = mAudioPromise.Ensure(__func__);
MSE_DEBUGV("");
if (!mAudioTrack) {
@ -251,6 +260,7 @@ MediaSourceReader::OnAudioNotDecoded(NotDecodedReason aReason)
nsRefPtr<MediaDecoderReader::VideoDataPromise>
MediaSourceReader::RequestVideoData(bool aSkipToNextKeyframe, int64_t aTimeThreshold)
{
MOZ_ASSERT(OnDecodeThread());
nsRefPtr<VideoDataPromise> p = mVideoPromise.Ensure(__func__);
MSE_DEBUGV("RequestVideoData(%d, %lld)",
aSkipToNextKeyframe, aTimeThreshold);
@ -378,7 +388,7 @@ void
MediaSourceReader::CheckForWaitOrEndOfStream(MediaData::Type aType, int64_t aTime)
{
// If the entire MediaSource is done, generate an EndOfStream.
if (IsNearEnd(aTime)) {
if (IsNearEnd(aType, aTime)) {
if (aType == MediaData::AUDIO_DATA) {
mAudioPromise.Reject(END_OF_STREAM, __func__);
} else {
@ -600,7 +610,7 @@ CreateReaderForType(const nsACString& aType, AbstractMediaDecoder* aDecoder)
// directly here.
if ((aType.LowerCaseEqualsLiteral("video/mp4") ||
aType.LowerCaseEqualsLiteral("audio/mp4")) &&
MP4Decoder::IsEnabled()) {
MP4Decoder::IsEnabled() && aDecoder) {
return new MP4Reader(aDecoder);
}
#endif
@ -936,6 +946,7 @@ MediaSourceReader::GetBuffered(dom::TimeRanges* aBuffered)
nsRefPtr<MediaDecoderReader::WaitForDataPromise>
MediaSourceReader::WaitForData(MediaData::Type aType)
{
MOZ_ASSERT(OnDecodeThread());
ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
nsRefPtr<WaitForDataPromise> p = WaitPromise(aType).Ensure(__func__);
MaybeNotifyHaveData();
@ -1045,10 +1056,27 @@ MediaSourceReader::IsEnded()
}
bool
MediaSourceReader::IsNearEnd(int64_t aTime)
MediaSourceReader::IsNearEnd(MediaData::Type aType, int64_t aTime)
{
ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
return mEnded && aTime >= (mMediaSourceDuration * USECS_PER_S - EOS_FUZZ_US);
if (!mEnded) {
return false;
}
if (aTime >= (mMediaSourceDuration * USECS_PER_S - EOS_FUZZ_US)) {
return true;
}
// We may have discrepencies between the mediasource duration and the
// sourcebuffer end time (mMediaSourceDuration == max(audio.EndTime, video.EndTime)
// If the sourcebuffer duration is close enough to the mediasource duration,
// then use it instead to determine if we're near the end.
TrackBuffer* trackBuffer =
aType == MediaData::AUDIO_DATA ? mAudioTrack : mVideoTrack;
nsRefPtr<dom::TimeRanges> buffered = new dom::TimeRanges();
trackBuffer->Buffered(buffered);
if ((mMediaSourceDuration - buffered->GetEndTime()) * USECS_PER_S <= DURATION_DIFFERENCE_FUZZ) {
return aTime >= std::floor(buffered->GetEndTime() * USECS_PER_S);
}
return false;
}
void

View File

@ -132,7 +132,7 @@ public:
// Return true if the Ended method has been called
bool IsEnded();
bool IsNearEnd(int64_t aTime /* microseconds */);
bool IsNearEnd(MediaData::Type aType, int64_t aTime /* microseconds */);
// Set the duration of the attached mediasource element.
void SetMediaSourceDuration(double aDuration /* seconds */);

Some files were not shown because too many files have changed in this diff Show More