зеркало из https://github.com/mozilla/gecko-dev.git
Merge m-c to fx-team. a=merge
This commit is contained in:
Коммит
ffdc1661d7
|
@ -25,6 +25,7 @@
|
|||
#include "nsCoord.h"
|
||||
#include "nsObjCExceptions.h"
|
||||
#include "nsWhitespaceTokenizer.h"
|
||||
#include <prdtoa.h>
|
||||
|
||||
using namespace mozilla;
|
||||
using namespace mozilla::a11y;
|
||||
|
@ -38,11 +39,11 @@ using namespace mozilla::a11y;
|
|||
#define NSAccessibilityMathSuperscriptAttribute @"AXMathSuperscript"
|
||||
#define NSAccessibilityMathUnderAttribute @"AXMathUnder"
|
||||
#define NSAccessibilityMathOverAttribute @"AXMathOver"
|
||||
#define NSAccessibilityMathLineThicknessAttribute @"AXMathLineThickness"
|
||||
// XXX WebKit also defines the following attributes.
|
||||
// See bugs 1176970, 1176973 and 1176983.
|
||||
// See bugs 1176970 and 1176983.
|
||||
// - NSAccessibilityMathFencedOpenAttribute @"AXMathFencedOpen"
|
||||
// - NSAccessibilityMathFencedCloseAttribute @"AXMathFencedClose"
|
||||
// - NSAccessibilityMathLineThicknessAttribute @"AXMathLineThickness"
|
||||
// - NSAccessibilityMathPrescriptsAttribute @"AXMathPrescripts"
|
||||
// - NSAccessibilityMathPostscriptsAttribute @"AXMathPostscripts"
|
||||
|
||||
|
@ -172,8 +173,7 @@ ConvertToNSArray(nsTArray<Accessible*>& aArray)
|
|||
case roles::MATHML_FRACTION:
|
||||
[additional addObject:NSAccessibilityMathFractionNumeratorAttribute];
|
||||
[additional addObject:NSAccessibilityMathFractionDenominatorAttribute];
|
||||
// XXX bug 1176973
|
||||
// WebKit also defines NSAccessibilityMathLineThicknessAttribute
|
||||
[additional addObject:NSAccessibilityMathLineThicknessAttribute];
|
||||
break;
|
||||
case roles::MATHML_SUB:
|
||||
case roles::MATHML_SUP:
|
||||
|
@ -422,8 +422,24 @@ ConvertToNSArray(nsTArray<Accessible*>& aArray)
|
|||
return [self childAt:0];
|
||||
if ([attribute isEqualToString:NSAccessibilityMathFractionDenominatorAttribute])
|
||||
return [self childAt:1];
|
||||
// XXX bug 1176973
|
||||
// WebKit also defines NSAccessibilityMathLineThicknessAttribute
|
||||
if ([attribute isEqualToString:NSAccessibilityMathLineThicknessAttribute]) {
|
||||
// WebKit sets line thickness to some logical value parsed in the
|
||||
// renderer object of the <mfrac> element. It's not clear whether the
|
||||
// exact value is relevant to assistive technologies. From a semantic
|
||||
// point of view, the only important point is to distinguish between
|
||||
// <mfrac> elements that have a fraction bar and those that do not.
|
||||
// Per the MathML 3 spec, the latter happens iff the linethickness
|
||||
// attribute is of the form [zero-float][optional-unit]. In that case we
|
||||
// set line thickness to zero and in the other cases we set it to one.
|
||||
nsCOMPtr<nsIPersistentProperties> attributes = accWrap->Attributes();
|
||||
nsAutoString thickness;
|
||||
nsAccUtils::GetAccAttr(attributes, nsGkAtoms::linethickness_, thickness);
|
||||
double value = 1.0;
|
||||
if (!thickness.IsEmpty())
|
||||
value = PR_strtod(NS_LossyConvertUTF16toASCII(thickness).get(),
|
||||
nullptr);
|
||||
return [NSNumber numberWithInteger:(value ? 1 : 0)];
|
||||
}
|
||||
break;
|
||||
case roles::MATHML_SUB:
|
||||
if ([attribute isEqualToString:NSAccessibilityMathBaseAttribute])
|
||||
|
|
|
@ -31,6 +31,15 @@ XPCOMUtils.defineLazyGetter(this, 'MemoryFront', function() {
|
|||
|
||||
Cu.import('resource://gre/modules/Frames.jsm');
|
||||
|
||||
let _telemetryDebug = true;
|
||||
|
||||
function telemetryDebug(...args) {
|
||||
if (_telemetryDebug) {
|
||||
args.unshift('[AdvancedTelemetry]');
|
||||
console.log(...args);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The Developer HUD is an on-device developer tool that displays widgets,
|
||||
* showing visual debug information about apps. Each widget corresponds to a
|
||||
|
@ -43,6 +52,7 @@ let developerHUD = {
|
|||
_conn: null,
|
||||
_watchers: [],
|
||||
_logging: true,
|
||||
_telemetry: false,
|
||||
|
||||
/**
|
||||
* This method registers a metric watcher that will watch one or more metrics
|
||||
|
@ -90,6 +100,10 @@ let developerHUD = {
|
|||
SettingsListener.observe('hud.logging', this._logging, enabled => {
|
||||
this._logging = enabled;
|
||||
});
|
||||
|
||||
SettingsListener.observe('debug.performance_data.advanced_telemetry', this._telemetry, enabled => {
|
||||
this._telemetry = enabled;
|
||||
});
|
||||
},
|
||||
|
||||
uninit: function dwp_uninit() {
|
||||
|
@ -169,13 +183,24 @@ let developerHUD = {
|
|||
* metrics, and how to notify the front-end when metrics have changed.
|
||||
*/
|
||||
function Target(frame, actor) {
|
||||
this.frame = frame;
|
||||
this._frame = frame;
|
||||
this.actor = actor;
|
||||
this.metrics = new Map();
|
||||
}
|
||||
|
||||
Target.prototype = {
|
||||
|
||||
get frame() {
|
||||
let frame = this._frame;
|
||||
let systemapp = document.querySelector('#systemapp');
|
||||
|
||||
return (frame === systemapp ? getContentWindow() : frame);
|
||||
},
|
||||
|
||||
get manifest() {
|
||||
return this._frame.appManifestURL;
|
||||
},
|
||||
|
||||
/**
|
||||
* Register a metric that can later be updated. Does not update the front-end.
|
||||
*/
|
||||
|
@ -203,7 +228,7 @@ Target.prototype = {
|
|||
|
||||
let data = {
|
||||
metrics: [], // FIXME(Bug 982066) Remove this field.
|
||||
manifest: this.frame.appManifestURL,
|
||||
manifest: this.manifest,
|
||||
metric: metric,
|
||||
message: message
|
||||
};
|
||||
|
@ -218,6 +243,7 @@ Target.prototype = {
|
|||
if (message) {
|
||||
developerHUD.log('[' + data.manifest + '] ' + data.message);
|
||||
}
|
||||
|
||||
this._send(data);
|
||||
},
|
||||
|
||||
|
@ -251,14 +277,33 @@ Target.prototype = {
|
|||
_send: function target_send(data) {
|
||||
let frame = this.frame;
|
||||
|
||||
let systemapp = document.querySelector('#systemapp');
|
||||
if (this.frame === systemapp) {
|
||||
frame = getContentWindow();
|
||||
shell.sendEvent(frame, 'developer-hud-update', Cu.cloneInto(data, frame));
|
||||
this._sendTelemetryEvent(data.metric);
|
||||
},
|
||||
|
||||
_sendTelemetryEvent: function target_sendTelemetryEvent(metric) {
|
||||
if (!developerHUD._telemetry || !metric || metric.skipTelemetry) {
|
||||
return;
|
||||
}
|
||||
|
||||
shell.sendEvent(frame, 'developer-hud-update', Cu.cloneInto(data, frame));
|
||||
}
|
||||
if (!this.appName) {
|
||||
let manifest = this.manifest;
|
||||
if (!manifest) {
|
||||
return;
|
||||
}
|
||||
let start = manifest.indexOf('/') + 2;
|
||||
let end = manifest.indexOf('.', start);
|
||||
this.appName = manifest.substring(start, end).toLowerCase();
|
||||
}
|
||||
|
||||
metric.appName = this.appName;
|
||||
|
||||
let data = { metric: metric };
|
||||
let frame = this.frame;
|
||||
|
||||
telemetryDebug('sending advanced-telemetry-update with this data: ' + JSON.stringify(data));
|
||||
shell.sendEvent(frame, 'advanced-telemetry-update', Cu.cloneInto(data, frame));
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
@ -359,6 +404,18 @@ let consoleWatcher = {
|
|||
|
||||
if (this._security.indexOf(pageError.category) > -1) {
|
||||
metric.name = 'security';
|
||||
|
||||
// Telemetry sends the security error category not the
|
||||
// count of security errors.
|
||||
target._sendTelemetryEvent({
|
||||
name: 'security',
|
||||
value: pageError.category,
|
||||
});
|
||||
|
||||
// Indicate that the 'hud' security metric (the count of security
|
||||
// errors) should not be sent as a telemetry metric since the
|
||||
// security error category is being sent instead.
|
||||
metric.skipTelemetry = true;
|
||||
}
|
||||
|
||||
let {errorMessage, sourceName, category, lineNumber, columnNumber} = pageError;
|
||||
|
@ -379,6 +436,16 @@ let consoleWatcher = {
|
|||
output += 'Warning (console)';
|
||||
break;
|
||||
|
||||
case 'info':
|
||||
this.handleTelemetryMessage(target, packet);
|
||||
|
||||
// Currently, informational log entries are tracked only by
|
||||
// advanced telemetry. Nonetheless, for consistency, we
|
||||
// continue here and let the function return normally, when it
|
||||
// concludes 'info' entries are not being watched.
|
||||
metric.name = 'info';
|
||||
break;
|
||||
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
@ -394,6 +461,9 @@ let consoleWatcher = {
|
|||
if (sourceURL) {
|
||||
output += ' ' + this.formatSourceURL(packet);
|
||||
}
|
||||
|
||||
// Telemetry also records reflow duration.
|
||||
target._sendTelemetryEvent({name: 'reflow-duration', value: Math.round(duration)});
|
||||
break;
|
||||
|
||||
default:
|
||||
|
@ -417,6 +487,49 @@ let consoleWatcher = {
|
|||
', ' + source + ':' + sourceLine;
|
||||
|
||||
return source;
|
||||
},
|
||||
|
||||
handleTelemetryMessage:
|
||||
function cw_handleTelemetryMessage(target, packet) {
|
||||
|
||||
if (!developerHUD._telemetry) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If this is a 'telemetry' log entry, create a telemetry metric from
|
||||
// the log content.
|
||||
let separator = '|';
|
||||
let logContent = packet.message.arguments.toString();
|
||||
|
||||
if (logContent.indexOf('telemetry') < 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
let telemetryData = logContent.split(separator);
|
||||
|
||||
// Positions of the components of a telemetry log entry.
|
||||
let TELEMETRY_IDENTIFIER_IDX = 0;
|
||||
let NAME_IDX = 1;
|
||||
let VALUE_IDX = 2;
|
||||
let CONTEXT_IDX = 3;
|
||||
|
||||
if (telemetryData[TELEMETRY_IDENTIFIER_IDX] != 'telemetry' ||
|
||||
telemetryData.length < 3 || telemetryData.length > 4) {
|
||||
return;
|
||||
}
|
||||
|
||||
let metric = {
|
||||
name: telemetryData[NAME_IDX],
|
||||
value: telemetryData[VALUE_IDX]
|
||||
};
|
||||
|
||||
// The metric's app name, if a 'context' was provided, is the
|
||||
// specified context appended to the specified app name.
|
||||
if (telemetryData.length === 4) {
|
||||
metric.context = telemetryData[CONTEXT_IDX];
|
||||
}
|
||||
|
||||
target._sendTelemetryEvent(metric);
|
||||
}
|
||||
};
|
||||
developerHUD.registerWatcher(consoleWatcher);
|
||||
|
|
|
@ -149,7 +149,7 @@ this.AboutServiceWorkers = {
|
|||
!message.principal.origin ||
|
||||
!message.principal.originAttributes ||
|
||||
!message.principal.originAttributes.appId ||
|
||||
!message.principal.originAttributes.isInBrowser) {
|
||||
(message.principal.originAttributes.inBrowser == null)) {
|
||||
self.sendError(message.id, "MissingPrincipal");
|
||||
return;
|
||||
}
|
||||
|
@ -157,7 +157,7 @@ this.AboutServiceWorkers = {
|
|||
let principal = Services.scriptSecurityManager.getAppCodebasePrincipal(
|
||||
Services.io.newURI(message.principal.origin, null, null),
|
||||
message.principal.originAttributes.appId,
|
||||
message.principal.originAttributes.isInBrowser
|
||||
message.principal.originAttributes.inBrowser
|
||||
);
|
||||
|
||||
if (!message.scope) {
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
<project name="platform_build" path="build" remote="b2g" revision="e862ab9177af664f00b4522e2350f4cb13866d73">
|
||||
<copyfile dest="Makefile" src="core/root.mk"/>
|
||||
</project>
|
||||
<project name="gaia" path="gaia" remote="mozillaorg" revision="4b7ad0dde990bdc44e166818c0bb09a35cd1207f"/>
|
||||
<project name="gaia" path="gaia" remote="mozillaorg" revision="722028715a56a03f327e2e70f2c32dcb6d819d4c"/>
|
||||
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
|
||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="3477513bcd385571aa01c0d074849e35bd5e2376"/>
|
||||
<project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
<project name="platform_build" path="build" remote="b2g" revision="e862ab9177af664f00b4522e2350f4cb13866d73">
|
||||
<copyfile dest="Makefile" src="core/root.mk"/>
|
||||
</project>
|
||||
<project name="gaia" path="gaia" remote="mozillaorg" revision="4b7ad0dde990bdc44e166818c0bb09a35cd1207f"/>
|
||||
<project name="gaia" path="gaia" remote="mozillaorg" revision="722028715a56a03f327e2e70f2c32dcb6d819d4c"/>
|
||||
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
|
||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="3477513bcd385571aa01c0d074849e35bd5e2376"/>
|
||||
<project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
<copyfile dest="Makefile" src="core/root.mk"/>
|
||||
</project>
|
||||
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
|
||||
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="4b7ad0dde990bdc44e166818c0bb09a35cd1207f"/>
|
||||
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="722028715a56a03f327e2e70f2c32dcb6d819d4c"/>
|
||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="3477513bcd385571aa01c0d074849e35bd5e2376"/>
|
||||
<project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
|
||||
<project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="87a2d8ab9248540910e56921654367b78a587095"/>
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
</project>
|
||||
<project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
|
||||
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
|
||||
<project name="gaia" path="gaia" remote="mozillaorg" revision="4b7ad0dde990bdc44e166818c0bb09a35cd1207f"/>
|
||||
<project name="gaia" path="gaia" remote="mozillaorg" revision="722028715a56a03f327e2e70f2c32dcb6d819d4c"/>
|
||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="3477513bcd385571aa01c0d074849e35bd5e2376"/>
|
||||
<project name="moztt" path="external/moztt" remote="b2g" revision="46da1a05ac04157669685246d70ac59d48699c9e"/>
|
||||
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="49192a4e48d080e44a0d66f059e6897f07cf67f8"/>
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
<project name="platform_build" path="build" remote="b2g" revision="e862ab9177af664f00b4522e2350f4cb13866d73">
|
||||
<copyfile dest="Makefile" src="core/root.mk"/>
|
||||
</project>
|
||||
<project name="gaia" path="gaia" remote="mozillaorg" revision="4b7ad0dde990bdc44e166818c0bb09a35cd1207f"/>
|
||||
<project name="gaia" path="gaia" remote="mozillaorg" revision="722028715a56a03f327e2e70f2c32dcb6d819d4c"/>
|
||||
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
|
||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="3477513bcd385571aa01c0d074849e35bd5e2376"/>
|
||||
<project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
<project name="platform_build" path="build" remote="b2g" revision="61e82f99bb8bc78d52b5717e9a2481ec7267fa33">
|
||||
<copyfile dest="Makefile" src="core/root.mk"/>
|
||||
</project>
|
||||
<project name="gaia" path="gaia" remote="mozillaorg" revision="4b7ad0dde990bdc44e166818c0bb09a35cd1207f"/>
|
||||
<project name="gaia" path="gaia" remote="mozillaorg" revision="722028715a56a03f327e2e70f2c32dcb6d819d4c"/>
|
||||
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
|
||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="3477513bcd385571aa01c0d074849e35bd5e2376"/>
|
||||
<project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
<copyfile dest="Makefile" src="core/root.mk"/>
|
||||
</project>
|
||||
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
|
||||
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="4b7ad0dde990bdc44e166818c0bb09a35cd1207f"/>
|
||||
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="722028715a56a03f327e2e70f2c32dcb6d819d4c"/>
|
||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="3477513bcd385571aa01c0d074849e35bd5e2376"/>
|
||||
<project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
|
||||
<project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="87a2d8ab9248540910e56921654367b78a587095"/>
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
<project name="platform_build" path="build" remote="b2g" revision="e862ab9177af664f00b4522e2350f4cb13866d73">
|
||||
<copyfile dest="Makefile" src="core/root.mk"/>
|
||||
</project>
|
||||
<project name="gaia" path="gaia" remote="mozillaorg" revision="4b7ad0dde990bdc44e166818c0bb09a35cd1207f"/>
|
||||
<project name="gaia" path="gaia" remote="mozillaorg" revision="722028715a56a03f327e2e70f2c32dcb6d819d4c"/>
|
||||
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
|
||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="3477513bcd385571aa01c0d074849e35bd5e2376"/>
|
||||
<project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
{
|
||||
"git": {
|
||||
"git_revision": "4b7ad0dde990bdc44e166818c0bb09a35cd1207f",
|
||||
"git_revision": "722028715a56a03f327e2e70f2c32dcb6d819d4c",
|
||||
"remote": "https://git.mozilla.org/releases/gaia.git",
|
||||
"branch": ""
|
||||
},
|
||||
"revision": "4dd94426a71c70db03184cb1e2d9c4c2ad065461",
|
||||
"revision": "547f049d7e7510b628b5b1074f73b204ab6627df",
|
||||
"repo_path": "integration/gaia-central"
|
||||
}
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
</project>
|
||||
<project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
|
||||
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
|
||||
<project name="gaia" path="gaia" remote="mozillaorg" revision="4b7ad0dde990bdc44e166818c0bb09a35cd1207f"/>
|
||||
<project name="gaia" path="gaia" remote="mozillaorg" revision="722028715a56a03f327e2e70f2c32dcb6d819d4c"/>
|
||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="3477513bcd385571aa01c0d074849e35bd5e2376"/>
|
||||
<project name="moztt" path="external/moztt" remote="b2g" revision="46da1a05ac04157669685246d70ac59d48699c9e"/>
|
||||
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="49192a4e48d080e44a0d66f059e6897f07cf67f8"/>
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
<project name="platform_build" path="build" remote="b2g" revision="61e82f99bb8bc78d52b5717e9a2481ec7267fa33">
|
||||
<copyfile dest="Makefile" src="core/root.mk"/>
|
||||
</project>
|
||||
<project name="gaia" path="gaia" remote="mozillaorg" revision="4b7ad0dde990bdc44e166818c0bb09a35cd1207f"/>
|
||||
<project name="gaia" path="gaia" remote="mozillaorg" revision="722028715a56a03f327e2e70f2c32dcb6d819d4c"/>
|
||||
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
|
||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="3477513bcd385571aa01c0d074849e35bd5e2376"/>
|
||||
<project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
|
||||
|
|
|
@ -126,12 +126,14 @@ var FullScreen = {
|
|||
} else {
|
||||
let topWin = event.target.ownerDocument.defaultView.top;
|
||||
browser = gBrowser.getBrowserForContentWindow(topWin);
|
||||
if (!browser) {
|
||||
document.mozCancelFullScreen();
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!this.enterDomFullscreen(browser)) {
|
||||
if (!browser || !this.enterDomFullscreen(browser)) {
|
||||
if (document.mozFullScreen) {
|
||||
// MozDOMFullscreen:Entered is dispatched synchronously in
|
||||
// fullscreen change, hence we have to avoid calling this
|
||||
// method synchronously here.
|
||||
setTimeout(() => document.mozCancelFullScreen(), 0);
|
||||
}
|
||||
break;
|
||||
}
|
||||
// If it is a remote browser, send a message to ask the content
|
||||
|
@ -176,7 +178,6 @@ var FullScreen = {
|
|||
// active. If not, we exit fullscreen since the "full-screen document" isn't
|
||||
// actually visible now.
|
||||
if (gBrowser.selectedBrowser != aBrowser) {
|
||||
document.mozCancelFullScreen();
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -184,7 +185,6 @@ var FullScreen = {
|
|||
if (focusManager.activeWindow != window) {
|
||||
// The top-level window has lost focus since the request to enter
|
||||
// full-screen was made. Cancel full-screen.
|
||||
document.mozCancelFullScreen();
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
#include "nsPIDOMWindow.h"
|
||||
|
||||
class nsIConsoleAPIStorage;
|
||||
class nsIPrincipal;
|
||||
class nsIProfiler;
|
||||
class nsIXPConnectJSObjectHolder;
|
||||
|
||||
|
|
|
@ -5,8 +5,9 @@
|
|||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "PerformanceEntry.h"
|
||||
#include "nsIURI.h"
|
||||
#include "MainThreadUtils.h"
|
||||
#include "mozilla/dom/PerformanceEntryBinding.h"
|
||||
#include "nsIURI.h"
|
||||
|
||||
using namespace mozilla::dom;
|
||||
|
||||
|
|
|
@ -8,6 +8,8 @@
|
|||
#define mozilla_dom_PerformanceEntry_h___
|
||||
|
||||
#include "nsDOMNavigationTiming.h"
|
||||
#include "nsString.h"
|
||||
#include "nsWrapperCache.h"
|
||||
|
||||
class nsISupports;
|
||||
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "PerformanceMark.h"
|
||||
#include "MainThreadUtils.h"
|
||||
#include "mozilla/dom/PerformanceMarkBinding.h"
|
||||
|
||||
using namespace mozilla::dom;
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "PerformanceMeasure.h"
|
||||
#include "MainThreadUtils.h"
|
||||
#include "mozilla/dom/PerformanceMeasureBinding.h"
|
||||
|
||||
using namespace mozilla::dom;
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
#include "nsGlobalWindow.h"
|
||||
#include "nsIPresShell.h"
|
||||
#include "nsIPrincipal.h"
|
||||
#include "nsPresContext.h"
|
||||
|
||||
namespace mozilla {
|
||||
namespace dom {
|
||||
|
|
|
@ -11,9 +11,11 @@
|
|||
#include "nsCOMPtr.h"
|
||||
#include "nsRefPtr.h"
|
||||
#include "nsTArray.h"
|
||||
#include "nsThreadUtils.h"
|
||||
|
||||
class nsGlobalWindow;
|
||||
class nsIPrincipal;
|
||||
class nsPIDOMWindow;
|
||||
|
||||
namespace mozilla {
|
||||
namespace dom {
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
|
||||
#include "nsContentCID.h"
|
||||
#include "nsDOMClassInfoID.h"
|
||||
#include "mozilla/HoldDropJSObjects.h"
|
||||
|
||||
using namespace mozilla;
|
||||
using namespace mozilla::dom;
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "SameProcessMessageQueue.h"
|
||||
#include "nsThreadUtils.h"
|
||||
|
||||
using namespace mozilla;
|
||||
using namespace mozilla::dom;
|
||||
|
|
|
@ -33,6 +33,7 @@
|
|||
#include "xpcpublic.h"
|
||||
#include "nsObserverService.h"
|
||||
#include "nsFocusManager.h"
|
||||
#include "nsIInterfaceRequestorUtils.h"
|
||||
|
||||
using namespace mozilla;
|
||||
using namespace mozilla::dom;
|
||||
|
|
|
@ -16,11 +16,13 @@
|
|||
#include "nsContentPolicy.h"
|
||||
#include "nsIURI.h"
|
||||
#include "nsIDocShell.h"
|
||||
#include "nsIDOMElement.h"
|
||||
#include "nsIDOMNode.h"
|
||||
#include "nsIDOMWindow.h"
|
||||
#include "nsIContent.h"
|
||||
#include "nsILoadContext.h"
|
||||
#include "nsCOMArray.h"
|
||||
#include "nsContentUtils.h"
|
||||
|
||||
using mozilla::LogLevel;
|
||||
|
||||
|
|
|
@ -17,6 +17,8 @@
|
|||
#include "mozilla/dom/Animation.h"
|
||||
#include "mozilla/dom/KeyframeEffect.h"
|
||||
|
||||
using mozilla::dom::Animation;
|
||||
|
||||
nsAutoTArray<nsRefPtr<nsDOMMutationObserver>, 4>*
|
||||
nsDOMMutationObserver::sScheduledMutationObservers = nullptr;
|
||||
|
||||
|
@ -345,12 +347,12 @@ void
|
|||
nsAnimationReceiver::RecordAnimationMutation(Animation* aAnimation,
|
||||
AnimationMutation aMutationType)
|
||||
{
|
||||
KeyframeEffectReadOnly* effect = aAnimation->GetEffect();
|
||||
mozilla::dom::KeyframeEffectReadOnly* effect = aAnimation->GetEffect();
|
||||
if (!effect) {
|
||||
return;
|
||||
}
|
||||
|
||||
Element* animationTarget = effect->GetTarget();
|
||||
mozilla::dom::Element* animationTarget = effect->GetTarget();
|
||||
if (!animationTarget) {
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -3536,6 +3536,7 @@ nsDOMWindowUtils::RequestCompositorProperty(const nsAString& property,
|
|||
NS_IMETHODIMP
|
||||
nsDOMWindowUtils::GetOMTAStyle(nsIDOMElement* aElement,
|
||||
const nsAString& aProperty,
|
||||
const nsAString& aPseudoElement,
|
||||
nsAString& aResult)
|
||||
{
|
||||
MOZ_RELEASE_ASSERT(nsContentUtils::IsCallerChrome());
|
||||
|
@ -3547,6 +3548,15 @@ nsDOMWindowUtils::GetOMTAStyle(nsIDOMElement* aElement,
|
|||
|
||||
nsRefPtr<nsROCSSPrimitiveValue> cssValue = nullptr;
|
||||
nsIFrame* frame = element->GetPrimaryFrame();
|
||||
if (frame && !aPseudoElement.IsEmpty()) {
|
||||
if (aPseudoElement.EqualsLiteral("::before")) {
|
||||
frame = nsLayoutUtils::GetBeforeFrame(frame);
|
||||
} else if (aPseudoElement.EqualsLiteral("::after")) {
|
||||
frame = nsLayoutUtils::GetAfterFrame(frame);
|
||||
} else {
|
||||
return NS_ERROR_INVALID_ARG;
|
||||
}
|
||||
}
|
||||
if (frame && nsLayoutUtils::AreAsyncAnimationsEnabled()) {
|
||||
if (aProperty.EqualsLiteral("opacity")) {
|
||||
Layer* layer =
|
||||
|
|
|
@ -3904,6 +3904,9 @@ nsDocument::DeleteShell()
|
|||
if (IsEventHandlingEnabled()) {
|
||||
RevokeAnimationFrameNotifications();
|
||||
}
|
||||
if (nsPresContext* presContext = mPresShell->GetPresContext()) {
|
||||
presContext->RefreshDriver()->CancelPendingEvents(this);
|
||||
}
|
||||
|
||||
// When our shell goes away, request that all our images be immediately
|
||||
// discarded, so we don't carry around decoded image data for a document we
|
||||
|
@ -9213,15 +9216,32 @@ NotifyPageHide(nsIDocument* aDocument, void* aData)
|
|||
return true;
|
||||
}
|
||||
|
||||
static void
|
||||
DispatchCustomEventWithFlush(nsINode* aTarget, const nsAString& aEventType,
|
||||
bool aBubbles, bool aOnlyChromeDispatch)
|
||||
{
|
||||
nsCOMPtr<nsIDOMEvent> event;
|
||||
NS_NewDOMEvent(getter_AddRefs(event), aTarget, nullptr, nullptr);
|
||||
nsresult rv = event->InitEvent(aEventType, aBubbles, false);
|
||||
if (NS_FAILED(rv)) {
|
||||
return;
|
||||
}
|
||||
event->SetTrusted(true);
|
||||
if (aOnlyChromeDispatch) {
|
||||
event->GetInternalNSEvent()->mFlags.mOnlyChromeDispatch = true;
|
||||
}
|
||||
if (nsIPresShell* shell = aTarget->OwnerDoc()->GetShell()) {
|
||||
shell->GetPresContext()->
|
||||
RefreshDriver()->ScheduleEventDispatch(aTarget, event);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
DispatchFullScreenChange(nsIDocument* aTarget)
|
||||
{
|
||||
nsRefPtr<AsyncEventDispatcher> asyncDispatcher =
|
||||
new AsyncEventDispatcher(aTarget,
|
||||
NS_LITERAL_STRING("mozfullscreenchange"),
|
||||
true,
|
||||
false);
|
||||
asyncDispatcher->PostDOMEvent();
|
||||
DispatchCustomEventWithFlush(
|
||||
aTarget, NS_LITERAL_STRING("mozfullscreenchange"),
|
||||
/* Bubbles */ true, /* OnlyChrome */ false);
|
||||
}
|
||||
|
||||
void
|
||||
|
@ -11167,11 +11187,10 @@ ExitFullscreenInDocTree(nsIDocument* aMaybeNotARootDoc)
|
|||
// Dispatch MozDOMFullscreen:Exited to the last document in
|
||||
// the list since we want this event to follow the same path
|
||||
// MozDOMFullscreen:Entered dispatched.
|
||||
nsRefPtr<AsyncEventDispatcher> asyncDispatcher =
|
||||
new AsyncEventDispatcher(changed.LastElement(),
|
||||
NS_LITERAL_STRING("MozDOMFullscreen:Exited"),
|
||||
true, true);
|
||||
asyncDispatcher->PostDOMEvent();
|
||||
nsContentUtils::DispatchEventOnlyToChrome(
|
||||
changed.LastElement(), ToSupports(changed.LastElement()),
|
||||
NS_LITERAL_STRING("MozDOMFullscreen:Exited"),
|
||||
/* Bubbles */ true, /* Cancelable */ false, /* DefaultAction */ nullptr);
|
||||
// Move the top-level window out of fullscreen mode.
|
||||
FullscreenRoots::Remove(root);
|
||||
SetWindowFullScreen(root, false);
|
||||
|
@ -11250,9 +11269,10 @@ nsDocument::RestorePreviousFullScreenState()
|
|||
// If we are fully exiting fullscreen, don't touch anything here,
|
||||
// just wait for the window to get out from fullscreen first.
|
||||
if (XRE_GetProcessType() == GeckoProcessType_Content) {
|
||||
(new AsyncEventDispatcher(
|
||||
this, NS_LITERAL_STRING("MozDOMFullscreen:Exit"),
|
||||
/* Bubbles */ true, /* ChromeOnly */ true))->PostDOMEvent();
|
||||
nsContentUtils::DispatchEventOnlyToChrome(
|
||||
this, ToSupports(this), NS_LITERAL_STRING("MozDOMFullscreen:Exit"),
|
||||
/* Bubbles */ true, /* Cancelable */ false,
|
||||
/* DefaultAction */ nullptr);
|
||||
} else {
|
||||
SetWindowFullScreen(this, false);
|
||||
}
|
||||
|
@ -11295,11 +11315,9 @@ nsDocument::RestorePreviousFullScreenState()
|
|||
if (!nsContentUtils::HaveEqualPrincipals(fullScreenDoc, doc) ||
|
||||
(!nsContentUtils::IsSitePermAllow(doc->NodePrincipal(), "fullscreen") &&
|
||||
!static_cast<nsDocument*>(doc)->mIsApprovedForFullscreen)) {
|
||||
nsRefPtr<AsyncEventDispatcher> asyncDispatcher =
|
||||
new AsyncEventDispatcher(
|
||||
doc, NS_LITERAL_STRING("MozDOMFullscreen:NewOrigin"),
|
||||
/* Bubbles */ true, /* ChromeOnly */ true);
|
||||
asyncDispatcher->PostDOMEvent();
|
||||
DispatchCustomEventWithFlush(
|
||||
doc, NS_LITERAL_STRING("MozDOMFullscreen:NewOrigin"),
|
||||
/* Bubbles */ true, /* ChromeOnly */ true);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
@ -11691,9 +11709,9 @@ nsDocument::RequestFullScreen(UniquePtr<FullscreenRequest>&& aRequest)
|
|||
if (XRE_GetProcessType() == GeckoProcessType_Content) {
|
||||
// If we are not the top level process, dispatch an event to make
|
||||
// our parent process go fullscreen first.
|
||||
(new AsyncEventDispatcher(
|
||||
this, NS_LITERAL_STRING("MozDOMFullscreen:Request"),
|
||||
/* Bubbles */ true, /* ChromeOnly */ true))->PostDOMEvent();
|
||||
nsContentUtils::DispatchEventOnlyToChrome(
|
||||
this, ToSupports(this), NS_LITERAL_STRING("MozDOMFullscreen:Request"),
|
||||
/* Bubbles */ true, /* Cancelable */ false, /* DefaultAction */ nullptr);
|
||||
} else {
|
||||
// Make the window fullscreen.
|
||||
FullscreenRequest* lastRequest = sPendingFullscreenRequests.getLast();
|
||||
|
@ -11820,13 +11838,6 @@ nsDocument::ApplyFullscreen(const FullscreenRequest& aRequest)
|
|||
}
|
||||
}
|
||||
|
||||
// Dispatch "mozfullscreenchange" events. Note this loop is in reverse
|
||||
// order so that the events for the root document arrives before the leaf
|
||||
// document, as required by the spec.
|
||||
for (uint32_t i = 0; i < changed.Length(); ++i) {
|
||||
DispatchFullScreenChange(changed[changed.Length() - i - 1]);
|
||||
}
|
||||
|
||||
// If this document hasn't already been approved in this session,
|
||||
// check to see if the user has granted the fullscreen access
|
||||
// to the document's principal's host, if it has one. Note that documents
|
||||
|
@ -11839,17 +11850,17 @@ nsDocument::ApplyFullscreen(const FullscreenRequest& aRequest)
|
|||
nsContentUtils::IsSitePermAllow(NodePrincipal(), "fullscreen");
|
||||
}
|
||||
|
||||
FullscreenRoots::Add(this);
|
||||
|
||||
// If it is the first entry of the fullscreen, trigger an event so
|
||||
// that the UI can response to this change, e.g. hide chrome, or
|
||||
// notifying parent process to enter fullscreen. Note that chrome
|
||||
// code may also want to listen to MozDOMFullscreen:NewOrigin event
|
||||
// to pop up warning/approval UI.
|
||||
if (!previousFullscreenDoc) {
|
||||
nsRefPtr<AsyncEventDispatcher> asyncDispatcher =
|
||||
new AsyncEventDispatcher(
|
||||
elem, NS_LITERAL_STRING("MozDOMFullscreen:Entered"),
|
||||
/* Bubbles */ true, /* ChromeOnly */ true);
|
||||
asyncDispatcher->PostDOMEvent();
|
||||
nsContentUtils::DispatchEventOnlyToChrome(
|
||||
this, ToSupports(elem), NS_LITERAL_STRING("MozDOMFullscreen:Entered"),
|
||||
/* Bubbles */ true, /* Cancelable */ false, /* DefaultAction */ nullptr);
|
||||
}
|
||||
|
||||
// The origin which is fullscreen gets changed. Trigger an event so
|
||||
|
@ -11861,14 +11872,17 @@ nsDocument::ApplyFullscreen(const FullscreenRequest& aRequest)
|
|||
// shouldn't rely on this event itself.
|
||||
if (aRequest.mShouldNotifyNewOrigin &&
|
||||
!nsContentUtils::HaveEqualPrincipals(previousFullscreenDoc, this)) {
|
||||
nsRefPtr<AsyncEventDispatcher> asyncDispatcher =
|
||||
new AsyncEventDispatcher(
|
||||
this, NS_LITERAL_STRING("MozDOMFullscreen:NewOrigin"),
|
||||
/* Bubbles */ true, /* ChromeOnly */ true);
|
||||
asyncDispatcher->PostDOMEvent();
|
||||
DispatchCustomEventWithFlush(
|
||||
this, NS_LITERAL_STRING("MozDOMFullscreen:NewOrigin"),
|
||||
/* Bubbles */ true, /* ChromeOnly */ true);
|
||||
}
|
||||
|
||||
FullscreenRoots::Add(this);
|
||||
// Dispatch "mozfullscreenchange" events. Note this loop is in reverse
|
||||
// order so that the events for the root document arrives before the leaf
|
||||
// document, as required by the spec.
|
||||
for (uint32_t i = 0; i < changed.Length(); ++i) {
|
||||
DispatchFullScreenChange(changed[changed.Length() - i - 1]);
|
||||
}
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
|
|
|
@ -206,7 +206,7 @@ class BlobURLsReporter final : public nsIMemoryReporter
|
|||
{
|
||||
EnumArg* envp = static_cast<EnumArg*>(aUserArg);
|
||||
nsCOMPtr<nsIDOMBlob> tmp = do_QueryInterface(aInfo->mObject);
|
||||
nsRefPtr<Blob> blob = static_cast<Blob*>(tmp.get());
|
||||
nsRefPtr<mozilla::dom::Blob> blob = static_cast<mozilla::dom::Blob*>(tmp.get());
|
||||
|
||||
if (blob) {
|
||||
NS_NAMED_LITERAL_CSTRING
|
||||
|
|
|
@ -106,7 +106,7 @@ nsHostObjectURI::Serialize(mozilla::ipc::URIParams& aParams)
|
|||
|
||||
hostParams.principal() = info;
|
||||
} else {
|
||||
hostParams.principal() = void_t();
|
||||
hostParams.principal() = mozilla::void_t();
|
||||
}
|
||||
|
||||
aParams = hostParams;
|
||||
|
|
|
@ -6,6 +6,8 @@
|
|||
|
||||
#include "nsIGlobalObject.h"
|
||||
#include "nsContentUtils.h"
|
||||
#include "nsThreadUtils.h"
|
||||
#include "nsHostObjectProtocolHandler.h"
|
||||
|
||||
nsIGlobalObject::~nsIGlobalObject()
|
||||
{
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
#include "nsIIOService.h"
|
||||
#include "nsIExternalProtocolHandler.h"
|
||||
#include "nsNetUtil.h"
|
||||
#include "nsContentUtils.h"
|
||||
|
||||
NS_IMPL_ISUPPORTS(nsNoDataProtocolContentPolicy, nsIContentPolicy)
|
||||
|
||||
|
|
|
@ -77,12 +77,6 @@ extern bool gBluetoothDebugFlag;
|
|||
#define BT_WARNING(msg, ...) printf("%s: " msg, __FUNCTION__, ##__VA_ARGS__)
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Prints 'R'ELEASE build logs for WebBluetooth API v2.
|
||||
*/
|
||||
#define BT_API2_LOGR(msg, ...) \
|
||||
BT_LOGR("[WEBBT-API2] " msg, ##__VA_ARGS__)
|
||||
|
||||
/**
|
||||
* Wrap literal name and value into a BluetoothNamedValue
|
||||
* and append it to the array.
|
||||
|
@ -141,7 +135,7 @@ extern bool gBluetoothDebugFlag;
|
|||
#define BT_ENSURE_TRUE_RESOLVE(x, promise, ret) \
|
||||
do { \
|
||||
if (MOZ_UNLIKELY(!(x))) { \
|
||||
BT_API2_LOGR("BT_ENSURE_TRUE_RESOLVE(" #x ") failed"); \
|
||||
BT_LOGR("BT_ENSURE_TRUE_RESOLVE(" #x ") failed"); \
|
||||
(promise)->MaybeResolve(ret); \
|
||||
return (promise).forget(); \
|
||||
} \
|
||||
|
@ -153,7 +147,7 @@ extern bool gBluetoothDebugFlag;
|
|||
#define BT_ENSURE_TRUE_REJECT(x, promise, ret) \
|
||||
do { \
|
||||
if (MOZ_UNLIKELY(!(x))) { \
|
||||
BT_API2_LOGR("BT_ENSURE_TRUE_REJECT(" #x ") failed"); \
|
||||
BT_LOGR("BT_ENSURE_TRUE_REJECT(" #x ") failed"); \
|
||||
(promise)->MaybeReject(ret); \
|
||||
return (promise).forget(); \
|
||||
} \
|
||||
|
|
|
@ -680,7 +680,7 @@ public:
|
|||
virtual void DeregisterNotification() { }
|
||||
|
||||
virtual void ReadRemoteRssi() { }
|
||||
virtual void GetDeviceType() { }
|
||||
virtual void GetDeviceType(BluetoothTypeOfDevice type) { }
|
||||
virtual void SetAdvData() { }
|
||||
virtual void TestCommand() { }
|
||||
|
||||
|
|
|
@ -875,14 +875,37 @@ BluetoothDaemonGattModule::ClientReadRemoteRssiRsp(
|
|||
UnpackPDUInitOp(aPDU));
|
||||
}
|
||||
|
||||
// Init operator class for ClientGetDeviceTypeRsp
|
||||
class BluetoothDaemonGattModule::ClientGetDeviceTypeInitOp final
|
||||
: private PDUInitOp
|
||||
{
|
||||
public:
|
||||
ClientGetDeviceTypeInitOp(DaemonSocketPDU& aPDU)
|
||||
: PDUInitOp(aPDU)
|
||||
{ }
|
||||
|
||||
nsresult
|
||||
operator () (BluetoothTypeOfDevice& aArg1) const
|
||||
{
|
||||
/* Read device type */
|
||||
nsresult rv = UnpackPDU(
|
||||
GetPDU(), UnpackConversion<uint8_t, BluetoothTypeOfDevice>(aArg1));
|
||||
if (NS_FAILED(rv)) {
|
||||
return rv;
|
||||
}
|
||||
WarnAboutTrailingData();
|
||||
return NS_OK;
|
||||
}
|
||||
};
|
||||
|
||||
void
|
||||
BluetoothDaemonGattModule::ClientGetDeviceTypeRsp(
|
||||
const DaemonSocketPDUHeader& aHeader, DaemonSocketPDU& aPDU,
|
||||
BluetoothGattClientResultHandler* aRes)
|
||||
{
|
||||
ClientResultRunnable::Dispatch(
|
||||
ClientGetDeviceTypeResultRunnable::Dispatch(
|
||||
aRes, &BluetoothGattClientResultHandler::GetDeviceType,
|
||||
UnpackPDUInitOp(aPDU));
|
||||
ClientGetDeviceTypeInitOp(aPDU));
|
||||
}
|
||||
|
||||
void
|
||||
|
|
|
@ -227,6 +227,10 @@ protected:
|
|||
typedef BluetoothResultRunnable0<BluetoothGattClientResultHandler, void>
|
||||
ClientResultRunnable;
|
||||
|
||||
typedef BluetoothResultRunnable1<BluetoothGattClientResultHandler, void,
|
||||
BluetoothTypeOfDevice, BluetoothTypeOfDevice>
|
||||
ClientGetDeviceTypeResultRunnable;
|
||||
|
||||
typedef BluetoothResultRunnable0<BluetoothGattResultHandler, void>
|
||||
ResultRunnable;
|
||||
|
||||
|
@ -455,6 +459,7 @@ protected:
|
|||
class ClientScanResultInitOp;
|
||||
class ClientConnectDisconnectInitOp;
|
||||
class ClientReadRemoteRssiInitOp;
|
||||
class ClientGetDeviceTypeInitOp;
|
||||
|
||||
void ClientRegisterNtf(const DaemonSocketPDUHeader& aHeader,
|
||||
DaemonSocketPDU& aPDU);
|
||||
|
|
|
@ -402,6 +402,12 @@ Convert(uint8_t aIn, BluetoothHandsfreeWbsConfig& aOut)
|
|||
return NS_OK;
|
||||
}
|
||||
|
||||
nsresult
|
||||
Convert(uint8_t aIn, BluetoothTypeOfDevice& aOut)
|
||||
{
|
||||
return Convert((int32_t)aIn, aOut);
|
||||
}
|
||||
|
||||
nsresult
|
||||
Convert(uint8_t aIn, BluetoothPropertyType& aOut)
|
||||
{
|
||||
|
|
|
@ -13,6 +13,11 @@ typedef
|
|||
BluetoothHALInterfaceRunnable0<BluetoothGattClientResultHandler, void>
|
||||
BluetoothGattClientHALResultRunnable;
|
||||
|
||||
typedef
|
||||
BluetoothHALInterfaceRunnable1<BluetoothGattClientResultHandler, void,
|
||||
BluetoothTypeOfDevice, BluetoothTypeOfDevice>
|
||||
BluetoothGattClientGetDeviceTypeHALResultRunnable;
|
||||
|
||||
typedef
|
||||
BluetoothHALInterfaceRunnable1<BluetoothGattClientResultHandler, void,
|
||||
BluetoothStatus, BluetoothStatus>
|
||||
|
@ -50,6 +55,35 @@ DispatchBluetoothGattClientHALResult(
|
|||
return rv;
|
||||
}
|
||||
|
||||
template <typename ResultRunnable, typename Tin1, typename Arg1>
|
||||
static nsresult
|
||||
DispatchBluetoothGattClientHALResult(
|
||||
BluetoothGattClientResultHandler* aRes,
|
||||
void (BluetoothGattClientResultHandler::*aMethod)(Arg1),
|
||||
Tin1 aArg1,
|
||||
BluetoothStatus aStatus)
|
||||
{
|
||||
MOZ_ASSERT(aRes);
|
||||
|
||||
nsRunnable* runnable;
|
||||
Arg1 arg1;
|
||||
|
||||
if (aStatus != STATUS_SUCCESS) {
|
||||
runnable = new BluetoothGattClientHALErrorRunnable(aRes,
|
||||
&BluetoothGattClientResultHandler::OnError, aStatus);
|
||||
} else if (NS_FAILED(Convert(aArg1, arg1))) {
|
||||
runnable = new BluetoothGattClientHALErrorRunnable(aRes,
|
||||
&BluetoothGattClientResultHandler::OnError, STATUS_PARM_INVALID);
|
||||
} else {
|
||||
runnable = new ResultRunnable(aRes, aMethod, arg1);
|
||||
}
|
||||
nsresult rv = NS_DispatchToMainThread(runnable);
|
||||
if (NS_FAILED(rv)) {
|
||||
BT_WARNING("NS_DispatchToMainThread failed: %X", rv);
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
static nsresult
|
||||
DispatchBluetoothGattHALResult(
|
||||
BluetoothGattResultHandler* aRes,
|
||||
|
@ -1007,22 +1041,23 @@ void
|
|||
BluetoothGattClientHALInterface::GetDeviceType(
|
||||
const nsAString& aBdAddr, BluetoothGattClientResultHandler* aRes)
|
||||
{
|
||||
int status;
|
||||
int status = BT_STATUS_FAIL;
|
||||
bt_device_type_t type = BT_DEVICE_DEVTYPE_BLE;
|
||||
#if ANDROID_VERSION >= 19
|
||||
bt_bdaddr_t bdAddr;
|
||||
|
||||
if (NS_SUCCEEDED(Convert(aBdAddr, bdAddr))) {
|
||||
status = mInterface->get_device_type(&bdAddr);
|
||||
} else {
|
||||
status = BT_STATUS_PARM_INVALID;
|
||||
status = BT_STATUS_SUCCESS;
|
||||
type = static_cast<bt_device_type_t>(mInterface->get_device_type(&bdAddr));
|
||||
}
|
||||
#else
|
||||
status = BT_STATUS_UNSUPPORTED;
|
||||
#endif
|
||||
|
||||
if (aRes) {
|
||||
DispatchBluetoothGattClientHALResult(
|
||||
aRes, &BluetoothGattClientResultHandler::GetDeviceType,
|
||||
DispatchBluetoothGattClientHALResult<
|
||||
BluetoothGattClientGetDeviceTypeHALResultRunnable>(
|
||||
aRes, &BluetoothGattClientResultHandler::GetDeviceType, type,
|
||||
ConvertDefault(status, STATUS_FAIL));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1355,7 +1355,6 @@ BluetoothGattManager::RegisterClientNotification(BluetoothGattStatus aStatus,
|
|||
int aClientIf,
|
||||
const BluetoothUuid& aAppUuid)
|
||||
{
|
||||
BT_API2_LOGR("Client Registered, clientIf = %d", aClientIf);
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
|
||||
nsString uuid;
|
||||
|
@ -1370,9 +1369,8 @@ BluetoothGattManager::RegisterClientNotification(BluetoothGattStatus aStatus,
|
|||
NS_ENSURE_TRUE_VOID(bs);
|
||||
|
||||
if (aStatus != GATT_STATUS_SUCCESS) {
|
||||
BT_API2_LOGR(
|
||||
"RegisterClient failed, clientIf = %d, status = %d, appUuid = %s",
|
||||
aClientIf, aStatus, NS_ConvertUTF16toUTF8(uuid).get());
|
||||
BT_LOGD("RegisterClient failed: clientIf = %d, status = %d, appUuid = %s",
|
||||
aClientIf, aStatus, NS_ConvertUTF16toUTF8(uuid).get());
|
||||
|
||||
// Notify BluetoothGatt for client disconnected
|
||||
bs->DistributeSignal(
|
||||
|
@ -1418,6 +1416,53 @@ BluetoothGattManager::RegisterClientNotification(BluetoothGattStatus aStatus,
|
|||
}
|
||||
}
|
||||
|
||||
class BluetoothGattManager::ScanDeviceTypeResultHandler final
|
||||
: public BluetoothGattClientResultHandler
|
||||
{
|
||||
public:
|
||||
ScanDeviceTypeResultHandler(const nsAString& aBdAddr, int aRssi,
|
||||
const BluetoothGattAdvData& aAdvData)
|
||||
: mBdAddr(aBdAddr)
|
||||
, mRssi(static_cast<int32_t>(aRssi))
|
||||
{
|
||||
mAdvData.AppendElements(aAdvData.mAdvData, sizeof(aAdvData.mAdvData));
|
||||
}
|
||||
|
||||
void GetDeviceType(BluetoothTypeOfDevice type)
|
||||
{
|
||||
DistributeSignalDeviceFound(type);
|
||||
}
|
||||
|
||||
void OnError(BluetoothStatus aStatus) override
|
||||
{
|
||||
DistributeSignalDeviceFound(TYPE_OF_DEVICE_BLE);
|
||||
}
|
||||
|
||||
private:
|
||||
void DistributeSignalDeviceFound(BluetoothTypeOfDevice type)
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
|
||||
InfallibleTArray<BluetoothNamedValue> properties;
|
||||
|
||||
BT_APPEND_NAMED_VALUE(properties, "Address", mBdAddr);
|
||||
BT_APPEND_NAMED_VALUE(properties, "Rssi", mRssi);
|
||||
BT_APPEND_NAMED_VALUE(properties, "GattAdv", mAdvData);
|
||||
BT_APPEND_NAMED_VALUE(properties, "Type", static_cast<uint32_t>(type));
|
||||
|
||||
BluetoothService* bs = BluetoothService::Get();
|
||||
NS_ENSURE_TRUE_VOID(bs);
|
||||
|
||||
bs->DistributeSignal(NS_LITERAL_STRING("LeDeviceFound"),
|
||||
NS_LITERAL_STRING(KEY_ADAPTER),
|
||||
BluetoothValue(properties));
|
||||
}
|
||||
|
||||
nsString mBdAddr;
|
||||
int32_t mRssi;
|
||||
nsTArray<uint8_t> mAdvData;
|
||||
};
|
||||
|
||||
void
|
||||
BluetoothGattManager::ScanResultNotification(
|
||||
const nsAString& aBdAddr, int aRssi,
|
||||
|
@ -1425,23 +1470,13 @@ BluetoothGattManager::ScanResultNotification(
|
|||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
|
||||
InfallibleTArray<BluetoothNamedValue> properties;
|
||||
NS_ENSURE_TRUE_VOID(sBluetoothGattClientInterface);
|
||||
|
||||
nsTArray<uint8_t> advData;
|
||||
advData.AppendElements(aAdvData.mAdvData, sizeof(aAdvData.mAdvData));
|
||||
|
||||
BT_APPEND_NAMED_VALUE(properties, "Address", nsString(aBdAddr));
|
||||
BT_APPEND_NAMED_VALUE(properties, "Rssi", static_cast<int32_t>(aRssi));
|
||||
BT_APPEND_NAMED_VALUE(properties, "GattAdv", advData);
|
||||
BT_APPEND_NAMED_VALUE(properties, "Type",
|
||||
static_cast<uint32_t>(TYPE_OF_DEVICE_BLE));
|
||||
|
||||
BluetoothService* bs = BluetoothService::Get();
|
||||
NS_ENSURE_TRUE_VOID(bs);
|
||||
|
||||
bs->DistributeSignal(NS_LITERAL_STRING("LeDeviceFound"),
|
||||
NS_LITERAL_STRING(KEY_ADAPTER),
|
||||
BluetoothValue(properties));
|
||||
// Distribute "LeDeviceFound" signal after we know the corresponding
|
||||
// BluetoothTypeOfDevice of the device
|
||||
sBluetoothGattClientInterface->GetDeviceType(
|
||||
aBdAddr,
|
||||
new ScanDeviceTypeResultHandler(aBdAddr, aRssi, aAdvData));
|
||||
}
|
||||
|
||||
void
|
||||
|
@ -1450,7 +1485,6 @@ BluetoothGattManager::ConnectNotification(int aConnId,
|
|||
int aClientIf,
|
||||
const nsAString& aDeviceAddr)
|
||||
{
|
||||
BT_API2_LOGR();
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
|
||||
BluetoothService* bs = BluetoothService::Get();
|
||||
|
@ -1463,8 +1497,8 @@ BluetoothGattManager::ConnectNotification(int aConnId,
|
|||
nsRefPtr<BluetoothGattClient> client = sClients->ElementAt(index);
|
||||
|
||||
if (aStatus != GATT_STATUS_SUCCESS) {
|
||||
BT_API2_LOGR("Connect failed, clientIf = %d, connId = %d, status = %d",
|
||||
aClientIf, aConnId, aStatus);
|
||||
BT_LOGD("Connect failed: clientIf = %d, connId = %d, status = %d",
|
||||
aClientIf, aConnId, aStatus);
|
||||
|
||||
// Notify BluetoothGatt that the client remains disconnected
|
||||
bs->DistributeSignal(
|
||||
|
@ -1503,7 +1537,6 @@ BluetoothGattManager::DisconnectNotification(int aConnId,
|
|||
int aClientIf,
|
||||
const nsAString& aDeviceAddr)
|
||||
{
|
||||
BT_API2_LOGR();
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
|
||||
BluetoothService* bs = BluetoothService::Get();
|
||||
|
@ -2030,7 +2063,6 @@ BluetoothGattManager::ReadRemoteRssiNotification(int aClientIf,
|
|||
int aRssi,
|
||||
BluetoothGattStatus aStatus)
|
||||
{
|
||||
BT_API2_LOGR();
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
|
||||
BluetoothService* bs = BluetoothService::Get();
|
||||
|
@ -2043,9 +2075,9 @@ BluetoothGattManager::ReadRemoteRssiNotification(int aClientIf,
|
|||
nsRefPtr<BluetoothGattClient> client = sClients->ElementAt(index);
|
||||
|
||||
if (aStatus != GATT_STATUS_SUCCESS) { // operation failed
|
||||
BT_API2_LOGR("ReadRemoteRssi failed, clientIf = %d, bdAddr = %s, " \
|
||||
"rssi = %d, status = %d", aClientIf,
|
||||
NS_ConvertUTF16toUTF8(aBdAddr).get(), aRssi, (int)aStatus);
|
||||
BT_LOGD("ReadRemoteRssi failed: clientIf = %d, bdAddr = %s, rssi = %d, " \
|
||||
"status = %d", aClientIf, NS_ConvertUTF16toUTF8(aBdAddr).get(),
|
||||
aRssi, (int)aStatus);
|
||||
|
||||
// Reject the read remote rssi request
|
||||
if (client->mReadRemoteRssiRunnable) {
|
||||
|
|
|
@ -110,6 +110,7 @@ private:
|
|||
class WriteCharacteristicValueResultHandler;
|
||||
class ReadDescriptorValueResultHandler;
|
||||
class WriteDescriptorValueResultHandler;
|
||||
class ScanDeviceTypeResultHandler;
|
||||
|
||||
BluetoothGattManager();
|
||||
|
||||
|
|
|
@ -566,8 +566,6 @@ BluetoothAdapter::StartDiscovery(ErrorResult& aRv)
|
|||
BluetoothService* bs = BluetoothService::Get();
|
||||
BT_ENSURE_TRUE_REJECT(bs, promise, NS_ERROR_NOT_AVAILABLE);
|
||||
|
||||
BT_API2_LOGR();
|
||||
|
||||
// Clear unpaired devices before start discovery
|
||||
for (int32_t i = mDevices.Length() - 1; i >= 0; i--) {
|
||||
if (!mDevices[i]->Paired()) {
|
||||
|
@ -608,8 +606,6 @@ BluetoothAdapter::StopDiscovery(ErrorResult& aRv)
|
|||
BluetoothService* bs = BluetoothService::Get();
|
||||
BT_ENSURE_TRUE_REJECT(bs, promise, NS_ERROR_NOT_AVAILABLE);
|
||||
|
||||
BT_API2_LOGR();
|
||||
|
||||
nsRefPtr<BluetoothReplyRunnable> result =
|
||||
new BluetoothVoidReplyRunnable(nullptr /* DOMRequest */,
|
||||
promise,
|
||||
|
@ -1193,8 +1189,6 @@ void
|
|||
BluetoothAdapter::DispatchDeviceEvent(const nsAString& aType,
|
||||
const BluetoothDeviceEventInit& aInit)
|
||||
{
|
||||
BT_API2_LOGR("aType (%s)", NS_ConvertUTF16toUTF8(aType).get());
|
||||
|
||||
nsRefPtr<BluetoothDeviceEvent> event =
|
||||
BluetoothDeviceEvent::Constructor(this, aType, aInit);
|
||||
DispatchTrustedEvent(event);
|
||||
|
|
|
@ -248,7 +248,7 @@ BluetoothGatt::DiscoverServices(ErrorResult& aRv)
|
|||
void
|
||||
BluetoothGatt::UpdateConnectionState(BluetoothConnectionState aState)
|
||||
{
|
||||
BT_API2_LOGR("GATT connection state changes to: %d", int(aState));
|
||||
BT_LOGR("GATT connection state changes to: %d", int(aState));
|
||||
mConnectionState = aState;
|
||||
|
||||
// Dispatch connectionstatechanged event to application
|
||||
|
|
|
@ -81,7 +81,6 @@ class GetAdaptersTask : public BluetoothReplyRunnable
|
|||
|
||||
const InfallibleTArray<BluetoothNamedValue>& adaptersPropertiesArray =
|
||||
adaptersProperties.get_ArrayOfBluetoothNamedValue();
|
||||
BT_API2_LOGR("GetAdaptersTask: len[%d]", adaptersPropertiesArray.Length());
|
||||
|
||||
// Append a BluetoothAdapter into adapters array for each properties array
|
||||
uint32_t numAdapters = adaptersPropertiesArray.Length();
|
||||
|
@ -114,7 +113,6 @@ BluetoothManager::BluetoothManager(nsPIDOMWindow *aWindow)
|
|||
MOZ_ASSERT(aWindow);
|
||||
|
||||
RegisterBluetoothSignalHandler(NS_LITERAL_STRING(KEY_MANAGER), this);
|
||||
BT_API2_LOGR("aWindow %p", aWindow);
|
||||
|
||||
// Query adapters list from bluetooth backend
|
||||
BluetoothService* bs = BluetoothService::Get();
|
||||
|
@ -139,8 +137,6 @@ BluetoothManager::DisconnectFromOwner()
|
|||
BluetoothAdapter*
|
||||
BluetoothManager::GetDefaultAdapter()
|
||||
{
|
||||
BT_API2_LOGR("mDefaultAdapterIndex: %d", mDefaultAdapterIndex);
|
||||
|
||||
return DefaultAdapterExists() ? mAdapters[mDefaultAdapterIndex] : nullptr;
|
||||
}
|
||||
|
||||
|
@ -185,7 +181,6 @@ void
|
|||
BluetoothManager::HandleAdapterAdded(const BluetoothValue& aValue)
|
||||
{
|
||||
MOZ_ASSERT(aValue.type() == BluetoothValue::TArrayOfBluetoothNamedValue);
|
||||
BT_API2_LOGR();
|
||||
|
||||
AppendAdapter(aValue);
|
||||
|
||||
|
@ -230,8 +225,6 @@ BluetoothManager::ReselectDefaultAdapter()
|
|||
{
|
||||
// Select the first of existing/remaining adapters as default adapter
|
||||
mDefaultAdapterIndex = mAdapters.IsEmpty() ? -1 : 0;
|
||||
BT_API2_LOGR("mAdapters length: %d => NEW mDefaultAdapterIndex: %d",
|
||||
mAdapters.Length(), mDefaultAdapterIndex);
|
||||
|
||||
// Notify application of default adapter change
|
||||
DispatchAttributeEvent();
|
||||
|
@ -241,8 +234,6 @@ void
|
|||
BluetoothManager::DispatchAdapterEvent(const nsAString& aType,
|
||||
const BluetoothAdapterEventInit& aInit)
|
||||
{
|
||||
BT_API2_LOGR("aType (%s)", NS_ConvertUTF16toUTF8(aType).get());
|
||||
|
||||
nsRefPtr<BluetoothAdapterEvent> event =
|
||||
BluetoothAdapterEvent::Constructor(this, aType, aInit);
|
||||
DispatchTrustedEvent(event);
|
||||
|
@ -252,7 +243,6 @@ void
|
|||
BluetoothManager::DispatchAttributeEvent()
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
BT_API2_LOGR();
|
||||
|
||||
Sequence<nsString> types;
|
||||
BT_APPEND_ENUM_STRING_FALLIBLE(types,
|
||||
|
|
|
@ -23,11 +23,7 @@ BluetoothReplyRunnable::BluetoothReplyRunnable(nsIDOMDOMRequest* aReq,
|
|||
, mPromise(aPromise)
|
||||
, mErrorStatus(STATUS_FAIL)
|
||||
, mName(aName)
|
||||
{
|
||||
if (aPromise) {
|
||||
BT_API2_LOGR("<%s>", NS_ConvertUTF16toUTF8(mName).get());
|
||||
}
|
||||
}
|
||||
{}
|
||||
|
||||
void
|
||||
BluetoothReplyRunnable::SetReply(BluetoothReply* aReply)
|
||||
|
@ -61,7 +57,6 @@ BluetoothReplyRunnable::FireReplySuccess(JS::Handle<JS::Value> aVal)
|
|||
|
||||
// Promise
|
||||
if (mPromise) {
|
||||
BT_API2_LOGR("<%s>", NS_ConvertUTF16toUTF8(mName).get());
|
||||
mPromise->MaybeResolve(aVal);
|
||||
}
|
||||
|
||||
|
@ -82,8 +77,6 @@ BluetoothReplyRunnable::FireErrorString()
|
|||
|
||||
// Promise
|
||||
if (mPromise) {
|
||||
BT_API2_LOGR("<%s>", NS_ConvertUTF16toUTF8(mName).get());
|
||||
|
||||
nsresult rv =
|
||||
NS_ERROR_GENERATE_FAILURE(NS_ERROR_MODULE_DOM_BLUETOOTH, mErrorStatus);
|
||||
mPromise->MaybeReject(rv);
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Test app for bug 1172562</title>
|
||||
<script src='test.js'></script>
|
||||
<script type='application/javascript;version=1.7'>
|
||||
|
||||
function runTests() {
|
||||
return Promise.resolve()
|
||||
.then(() => { return navigator.serviceWorker.ready })
|
||||
.then((registration) => {
|
||||
return new Promise((resolve) => {
|
||||
var worker = registration.waiting || registration.active;
|
||||
worker.postMessage('read');
|
||||
navigator.serviceWorker.onmessage = (message) => {
|
||||
if (message.data.type == 'done') {
|
||||
ok(!message.data.cached, 'No cached data');
|
||||
resolve();
|
||||
}
|
||||
};
|
||||
});
|
||||
})
|
||||
.then(done);
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body onload='runTests()'>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,31 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Test app for bug 1172562</title>
|
||||
<script src='test.js'></script>
|
||||
<script type='application/javascript;version=1.7'>
|
||||
|
||||
function runTests() {
|
||||
return Promise.resolve()
|
||||
.then(() => { return navigator.serviceWorker.ready })
|
||||
.then((registration) => {
|
||||
return new Promise((resolve) => {
|
||||
var worker = registration.waiting || registration.active;
|
||||
worker.postMessage('write');
|
||||
navigator.serviceWorker.onmessage = (message) => {
|
||||
if (message.data.type == 'written') {
|
||||
worker.postMessage('read');
|
||||
} else if (message.data.type == 'done') {
|
||||
ok(message.data.cached, 'Write success');
|
||||
resolve();
|
||||
}
|
||||
};
|
||||
});
|
||||
})
|
||||
.then(done);
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body onload='runTests()'>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,41 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Test app for bug 1172562</title>
|
||||
<script type='application/javascript;version=1.7'>
|
||||
function ok(aCondition, aMessage) {
|
||||
if (aCondition) {
|
||||
alert('OK: ' + aMessage);
|
||||
} else {
|
||||
alert('KO: ' + aMessage);
|
||||
}
|
||||
}
|
||||
|
||||
function ready() {
|
||||
alert('READY');
|
||||
}
|
||||
|
||||
function registerServiceWorker() {
|
||||
return new Promise((resolve, reject) => {
|
||||
navigator.serviceWorker.ready.then(() => {
|
||||
ready();
|
||||
resolve();
|
||||
});
|
||||
navigator.serviceWorker.register('sw.js', {scope: '.'})
|
||||
.then(registration => {
|
||||
ok(true, 'service worker registered');
|
||||
})
|
||||
.catch(reject);
|
||||
});
|
||||
}
|
||||
|
||||
function runTests() {
|
||||
return Promise.resolve()
|
||||
.then(registerServiceWorker)
|
||||
.then(ready)
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body onload='runTests()'>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"name": "App",
|
||||
"launch_path": "/index.html",
|
||||
"description": "Test app for bug 1172562"
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
Content-Type: application/manifest+json
|
|
@ -0,0 +1,18 @@
|
|||
self.addEventListener('message', (message) => {
|
||||
caches.open('acache').then((cache) => {
|
||||
if(message.data == 'write') {
|
||||
cache.add('aurl').then(() => {
|
||||
message.source.postMessage({
|
||||
type: 'written'
|
||||
});
|
||||
});
|
||||
} else if (message.data == 'read') {
|
||||
cache.match('aurl').then((result) => {
|
||||
message.source.postMessage({
|
||||
type: 'done',
|
||||
cached: !!result
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
|
@ -0,0 +1,15 @@
|
|||
function ok(aCondition, aMessage) {
|
||||
if (aCondition) {
|
||||
alert('OK: ' + aMessage);
|
||||
} else {
|
||||
alert('KO: ' + aMessage);
|
||||
}
|
||||
}
|
||||
|
||||
function ready() {
|
||||
alert('READY');
|
||||
}
|
||||
|
||||
function done() {
|
||||
alert('DONE');
|
||||
}
|
|
@ -23,6 +23,7 @@ support-files =
|
|||
test_cache_https.js
|
||||
large_url_list.js
|
||||
empty.html
|
||||
app/*
|
||||
|
||||
[test_cache.html]
|
||||
[test_cache_add.html]
|
||||
|
@ -40,6 +41,8 @@ support-files =
|
|||
skip-if = buildapp == 'b2g' # bug 1162353
|
||||
[test_cache_restart.html]
|
||||
[test_cache_shrink.html]
|
||||
[test_cache_clear_on_app_uninstall.html]
|
||||
skip-if = e10s || buildapp == 'b2g' # bug 1178685
|
||||
[test_cache_orphaned_cache.html]
|
||||
[test_cache_orphaned_body.html]
|
||||
[test_cache_untrusted.html]
|
||||
|
|
|
@ -0,0 +1,163 @@
|
|||
<!--
|
||||
Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/
|
||||
-->
|
||||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<title>Bug 1172562 - Clear QuotaManager storage when uninstalling an app</title>
|
||||
<script type='text/javascript' src='/tests/SimpleTest/SimpleTest.js'></script>
|
||||
<link rel='stylesheet' type='text/css' href='/tests/SimpleTest/test.css' />
|
||||
</head>
|
||||
<body onload='runTests()'>
|
||||
<p id='display'></p>
|
||||
<div id='content' style='display: none'></div>
|
||||
<pre id='test'></pre>
|
||||
<script class='testbody' type='application/javascript;version=1.7'>
|
||||
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
|
||||
const gOrigin = 'http://mochi.test:8888/tests/dom/cache/test/mochitest/app';
|
||||
const appManifestURL = gOrigin + '/manifest.webapp';
|
||||
let gApp;
|
||||
|
||||
function setup() {
|
||||
return new Promise((resolve, reject) => {
|
||||
SpecialPowers.setAllAppsLaunchable(true);
|
||||
SpecialPowers.pushPrefEnv({'set': [
|
||||
['dom.mozBrowserFramesEnabled', true],
|
||||
['dom.serviceWorkers.exemptFromPerDomainMax', true],
|
||||
['dom.serviceWorkers.enabled', true],
|
||||
['dom.serviceWorkers.testing.enabled', true],
|
||||
['dom.caches.enabled', true],
|
||||
]}, () => {
|
||||
SpecialPowers.pushPermissions([
|
||||
{ 'type': 'webapps-manage', 'allow': 1, 'context': document },
|
||||
{ 'type': 'browser', 'allow': 1, 'context': document },
|
||||
{ 'type': 'embed-apps', 'allow': 1, 'context': document }
|
||||
], () => {
|
||||
SpecialPowers.autoConfirmAppInstall(() => {
|
||||
SpecialPowers.autoConfirmAppUninstall(resolve);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function installApp() {
|
||||
return new Promise((resolve, reject) => {
|
||||
let req = navigator.mozApps.install(appManifestURL);
|
||||
req.onsuccess = function() {
|
||||
gApp = req.result;
|
||||
is(req.result.manifestURL, appManifestURL, 'app installed');
|
||||
if (req.result.installState == 'installed') {
|
||||
is(req.result.installState, 'installed', 'app downloaded');
|
||||
resolve()
|
||||
} else {
|
||||
req.result.ondownloadapplied = function() {
|
||||
is(req.result.installState, 'installed', 'app downloaded');
|
||||
resolve();
|
||||
}
|
||||
}
|
||||
}
|
||||
req.onerror = reject;
|
||||
});
|
||||
}
|
||||
|
||||
function launchApp() {
|
||||
if (!gApp) {
|
||||
ok(false, 'No test application to launch');
|
||||
return Promise.reject();
|
||||
}
|
||||
return new Promise((resolve, reject) => {
|
||||
let iframe = document.createElement('iframe');
|
||||
iframe.setAttribute('mozbrowser', 'true');
|
||||
iframe.setAttribute('mozapp', gApp.manifestURL);
|
||||
iframe.addEventListener('mozbrowsershowmodalprompt', function listener(e) {
|
||||
let message = e.detail.message;
|
||||
if (/OK/.exec(message)) {
|
||||
ok(true, "Message from app: " + message);
|
||||
} else if (/KO/.exec(message)) {
|
||||
ok(false, "Message from app: " + message);
|
||||
} else if (/READY/.exec(message)) {
|
||||
ok(true, "Message from app: " + message);
|
||||
resolve();
|
||||
} else {
|
||||
ok(false, "Unexpected message received: " + message);
|
||||
}
|
||||
}, false);
|
||||
let domParent = document.getElementById('container');
|
||||
domParent.appendChild(iframe);
|
||||
SpecialPowers.wrap(iframe.contentWindow).location =
|
||||
gOrigin + gApp.manifest.launch_path;
|
||||
});
|
||||
}
|
||||
|
||||
function loadControlled(aUrl) {
|
||||
return new Promise((resolve, reject) => {
|
||||
let iframe = document.createElement('iframe');
|
||||
iframe.setAttribute('mozbrowser', 'true');
|
||||
iframe.setAttribute('mozapp', gApp.manifestURL);
|
||||
iframe.addEventListener('mozbrowsershowmodalprompt', function listener(e) {
|
||||
let message = e.detail.message;
|
||||
if (/OK/.exec(message)) {
|
||||
ok(true, "Message from app: " + message);
|
||||
} else if (/KO/.exec(message)) {
|
||||
ok(false, "Message from app: " + message);
|
||||
} else if (/DONE/.exec(message)) {
|
||||
ok(true, "Messaging from app complete");
|
||||
iframe.removeEventListener('mozbrowsershowmodalprompt', listener);
|
||||
let domParent = document.getElementById('container');
|
||||
domParent.removeChild(iframe);
|
||||
resolve();
|
||||
} else {
|
||||
ok(false, "Unexpected message received: " + message);
|
||||
}
|
||||
}, false);
|
||||
let domParent = document.getElementById('container');
|
||||
domParent.appendChild(iframe);
|
||||
SpecialPowers.wrap(iframe.contentWindow).location =
|
||||
gOrigin + aUrl;
|
||||
});
|
||||
}
|
||||
|
||||
function loadBeforeClear() {
|
||||
return loadControlled('/before_clear.html');
|
||||
}
|
||||
|
||||
function loadAfterClear() {
|
||||
return loadControlled('/after_clear.html');
|
||||
}
|
||||
|
||||
function uninstallApp() {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!gApp) {
|
||||
return reject();
|
||||
}
|
||||
let req = navigator.mozApps.mgmt.uninstall(gApp);
|
||||
req.onsuccess = resolve;
|
||||
req.onerror = reject;
|
||||
});
|
||||
}
|
||||
|
||||
function runTests() {
|
||||
setup()
|
||||
.then(installApp)
|
||||
.then(launchApp)
|
||||
.then(loadBeforeClear)
|
||||
.then(uninstallApp)
|
||||
.then(installApp)
|
||||
.then(launchApp)
|
||||
.then(loadAfterClear)
|
||||
.then(uninstallApp)
|
||||
.then(SimpleTest.finish)
|
||||
.catch((e) => {
|
||||
ok(false, 'Unexpected error ' + e.target.error.name);
|
||||
SimpleTest.finish();
|
||||
});
|
||||
}
|
||||
|
||||
</script>
|
||||
<div id='container'></div>
|
||||
</body>
|
||||
</html>
|
|
@ -49,7 +49,7 @@ interface nsIJSRAIIHelper;
|
|||
interface nsIContentPermissionRequest;
|
||||
interface nsIObserver;
|
||||
|
||||
[scriptable, uuid(e7b44320-8255-4ad1-bbe9-d78a8a1867c9)]
|
||||
[scriptable, uuid(336a8683-5626-4512-a3d5-ec280c13e5c2)]
|
||||
interface nsIDOMWindowUtils : nsISupports {
|
||||
|
||||
/**
|
||||
|
@ -1746,7 +1746,8 @@ interface nsIDOMWindowUtils : nsISupports {
|
|||
* If the property is NOT currently being animated on the compositor thread,
|
||||
* returns an empty string.
|
||||
*/
|
||||
AString getOMTAStyle(in nsIDOMElement aElement, in AString aProperty);
|
||||
AString getOMTAStyle(in nsIDOMElement aElement, in AString aProperty,
|
||||
[optional] in AString aPseudoElement);
|
||||
|
||||
/**
|
||||
* Special function that gets a property syncronously from the last composite
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
#include "nsIContentPolicy.h"
|
||||
#include "nsIContentSecurityPolicy.h"
|
||||
#include "nsIHttpChannel.h"
|
||||
#include "nsIHttpChannelInternal.h"
|
||||
#include "nsIInputStreamPump.h"
|
||||
#include "nsIIOService.h"
|
||||
#include "nsIProtocolHandler.h"
|
||||
|
@ -870,6 +871,16 @@ private:
|
|||
return rv;
|
||||
}
|
||||
|
||||
// If we are loading a script for a ServiceWorker then we must not
|
||||
// try to intercept it. If the interception matches the current
|
||||
// ServiceWorker's scope then we could deadlock the load.
|
||||
if (mWorkerPrivate->IsServiceWorker()) {
|
||||
nsCOMPtr<nsIHttpChannelInternal> internal = do_QueryInterface(channel);
|
||||
if (internal) {
|
||||
internal->ForceNoIntercept();
|
||||
}
|
||||
}
|
||||
|
||||
if (loadInfo.mCacheStatus != ScriptLoadInfo::ToBeCached) {
|
||||
rv = channel->AsyncOpen(loader, indexSupports);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
|
|
|
@ -51,6 +51,11 @@ ServiceWorker::ServiceWorker(nsPIDOMWindow* aWindow,
|
|||
MOZ_ASSERT(aInfo);
|
||||
MOZ_ASSERT(mSharedWorker);
|
||||
|
||||
if (aWindow) {
|
||||
mDocument = aWindow->GetExtantDoc();
|
||||
mWindow = aWindow->GetOuterWindow();
|
||||
}
|
||||
|
||||
// This will update our state too.
|
||||
mInfo->AppendWorker(this);
|
||||
}
|
||||
|
@ -68,7 +73,7 @@ NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(ServiceWorker)
|
|||
NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTION_INHERITED(ServiceWorker, DOMEventTargetHelper,
|
||||
mSharedWorker)
|
||||
mSharedWorker, mDocument, mWindow)
|
||||
|
||||
JSObject*
|
||||
ServiceWorker::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
|
||||
|
@ -97,9 +102,12 @@ ServiceWorker::PostMessage(JSContext* aCx, JS::Handle<JS::Value> aMessage,
|
|||
return;
|
||||
}
|
||||
|
||||
nsCOMPtr<nsPIDOMWindow> window = do_QueryInterface(GetParentObject());
|
||||
nsCOMPtr<nsIDocument> doc = window->GetExtantDoc();
|
||||
nsAutoPtr<ServiceWorkerClientInfo> clientInfo(new ServiceWorkerClientInfo(doc));
|
||||
MOZ_ASSERT(mDocument && mWindow,
|
||||
"Cannot call PostMessage on a ServiceWorker object that doesn't "
|
||||
"have a window");
|
||||
|
||||
nsAutoPtr<ServiceWorkerClientInfo> clientInfo(
|
||||
new ServiceWorkerClientInfo(mDocument, mWindow));
|
||||
|
||||
workerPrivate->PostMessageToServiceWorker(aCx, aMessage, aTransferable,
|
||||
clientInfo, aRv);
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
#include "mozilla/dom/BindingDeclarations.h"
|
||||
#include "mozilla/dom/ServiceWorkerBinding.h" // For ServiceWorkerState.
|
||||
|
||||
class nsIDocument;
|
||||
class nsPIDOMWindow;
|
||||
|
||||
namespace mozilla {
|
||||
|
@ -91,6 +92,10 @@ private:
|
|||
// can be released and recreated as required rather than re-implement some of
|
||||
// the SharedWorker logic.
|
||||
nsRefPtr<SharedWorker> mSharedWorker;
|
||||
// We need to keep the document and window alive for PostMessage to be able
|
||||
// to access them.
|
||||
nsCOMPtr<nsIDocument> mDocument;
|
||||
nsCOMPtr<nsPIDOMWindow> mWindow;
|
||||
};
|
||||
|
||||
} // namespace workers
|
||||
|
|
|
@ -28,7 +28,8 @@ NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ServiceWorkerClient)
|
|||
NS_INTERFACE_MAP_ENTRY(nsISupports)
|
||||
NS_INTERFACE_MAP_END
|
||||
|
||||
ServiceWorkerClientInfo::ServiceWorkerClientInfo(nsIDocument* aDoc)
|
||||
ServiceWorkerClientInfo::ServiceWorkerClientInfo(nsIDocument* aDoc,
|
||||
nsPIDOMWindow* aWindow)
|
||||
: mWindowId(0)
|
||||
{
|
||||
MOZ_ASSERT(aDoc);
|
||||
|
@ -53,7 +54,7 @@ ServiceWorkerClientInfo::ServiceWorkerClientInfo(nsIDocument* aDoc)
|
|||
NS_WARNING("Failed to get focus information.");
|
||||
}
|
||||
|
||||
nsRefPtr<nsGlobalWindow> outerWindow = static_cast<nsGlobalWindow*>(aDoc->GetWindow());
|
||||
nsRefPtr<nsGlobalWindow> outerWindow = static_cast<nsGlobalWindow*>(aWindow);
|
||||
MOZ_ASSERT(outerWindow);
|
||||
if (!outerWindow->IsTopLevelWindow()) {
|
||||
mFrameType = FrameType::Nested;
|
||||
|
|
|
@ -14,6 +14,9 @@
|
|||
#include "mozilla/dom/BindingDeclarations.h"
|
||||
#include "mozilla/dom/ClientBinding.h"
|
||||
|
||||
class nsIDocument;
|
||||
class nsPIDOMWindow;
|
||||
|
||||
namespace mozilla {
|
||||
namespace dom {
|
||||
namespace workers {
|
||||
|
@ -29,7 +32,7 @@ class ServiceWorkerClientInfo final
|
|||
friend class ServiceWorkerWindowClient;
|
||||
|
||||
public:
|
||||
explicit ServiceWorkerClientInfo(nsIDocument* aDoc);
|
||||
ServiceWorkerClientInfo(nsIDocument* aDoc, nsPIDOMWindow* aWindow);
|
||||
|
||||
private:
|
||||
nsString mClientId;
|
||||
|
|
|
@ -3551,7 +3551,7 @@ ServiceWorkerManager::DispatchFetchEvent(const OriginAttributes& aOriginAttribut
|
|||
if (!isNavigation) {
|
||||
MOZ_ASSERT(aDoc);
|
||||
aRv = GetDocumentController(aDoc->GetInnerWindow(), getter_AddRefs(serviceWorker));
|
||||
clientInfo = new ServiceWorkerClientInfo(aDoc);
|
||||
clientInfo = new ServiceWorkerClientInfo(aDoc, aDoc->GetWindow());
|
||||
} else {
|
||||
nsCOMPtr<nsIChannel> internalChannel;
|
||||
aRv = aChannel->GetChannel(getter_AddRefs(internalChannel));
|
||||
|
@ -3924,7 +3924,7 @@ EnumControlledDocuments(nsISupports* aKey,
|
|||
return PL_DHASH_NEXT;
|
||||
}
|
||||
|
||||
ServiceWorkerClientInfo clientInfo(document);
|
||||
ServiceWorkerClientInfo clientInfo(document, document->GetWindow());
|
||||
data->mDocuments.AppendElement(clientInfo);
|
||||
|
||||
return PL_DHASH_NEXT;
|
||||
|
|
|
@ -87,7 +87,8 @@ public:
|
|||
ErrorResult result;
|
||||
//FIXME(catalinb): Bug 1144660 - check if we are allowed to focus here.
|
||||
window->Focus(result);
|
||||
clientInfo.reset(new ServiceWorkerClientInfo(window->GetDocument()));
|
||||
clientInfo.reset(new ServiceWorkerClientInfo(window->GetDocument(),
|
||||
window->GetOuterWindow()));
|
||||
}
|
||||
|
||||
DispatchResult(Move(clientInfo));
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
#include "nsCOMPtr.h"
|
||||
#include "nscore.h"
|
||||
|
||||
class nsILoadInfo;
|
||||
class nsIPrincipal;
|
||||
|
||||
namespace IPC {
|
||||
|
|
|
@ -286,12 +286,7 @@ ActiveLayerTracker::IsStyleAnimated(nsDisplayListBuilder* aBuilder,
|
|||
if (aProperty == eCSSProperty_transform && aFrame->Preserves3D()) {
|
||||
return IsStyleAnimated(aBuilder, aFrame->GetParent(), aProperty);
|
||||
}
|
||||
nsIContent* content = aFrame->GetContent();
|
||||
if (content) {
|
||||
return nsLayoutUtils::HasCurrentAnimationsForProperties(content, &aProperty, 1);
|
||||
}
|
||||
|
||||
return false;
|
||||
return nsLayoutUtils::HasCurrentAnimationsForProperties(aFrame, &aProperty, 1);
|
||||
}
|
||||
|
||||
/* static */ bool
|
||||
|
|
|
@ -4773,11 +4773,10 @@ ChooseScaleAndSetTransform(FrameLayerBuilder* aLayerBuilder,
|
|||
if (canDraw2D) {
|
||||
// If the container's transform is animated off main thread, fix a suitable scale size
|
||||
// for animation
|
||||
if (aContainerFrame->GetContent() &&
|
||||
aContainerItem &&
|
||||
if (aContainerItem &&
|
||||
aContainerItem->GetType() == nsDisplayItem::TYPE_TRANSFORM &&
|
||||
nsLayoutUtils::HasAnimationsForCompositor(
|
||||
aContainerFrame->GetContent(), eCSSProperty_transform)) {
|
||||
aContainerFrame, eCSSProperty_transform)) {
|
||||
// Use the size of the nearest widget as the maximum size. This
|
||||
// is important since it might be a popup that is bigger than the
|
||||
// pres context's size.
|
||||
|
@ -4795,7 +4794,7 @@ ChooseScaleAndSetTransform(FrameLayerBuilder* aLayerBuilder,
|
|||
// compute scale using the animation on the container (ignoring
|
||||
// its ancestors)
|
||||
scale = nsLayoutUtils::ComputeSuitableScaleForAnimation(
|
||||
aContainerFrame->GetContent(), aVisibleRect.Size(),
|
||||
aContainerFrame, aVisibleRect.Size(),
|
||||
displaySize);
|
||||
// multiply by the scale inherited from ancestors
|
||||
scale.width *= aIncomingScale.mXScale;
|
||||
|
|
|
@ -515,14 +515,11 @@ nsDisplayListBuilder::AddAnimationsAndTransitionsToLayer(Layer* aLayer,
|
|||
RestyleManager::GetMaxAnimationGenerationForFrame(aFrame);
|
||||
aLayer->SetAnimationGeneration(animationGeneration);
|
||||
|
||||
nsIContent* content = aFrame->GetContent();
|
||||
if (!content) {
|
||||
return;
|
||||
}
|
||||
nsPresContext* presContext = aFrame->PresContext();
|
||||
AnimationCollection* transitions =
|
||||
nsTransitionManager::GetAnimationsForCompositor(content, aProperty);
|
||||
presContext->TransitionManager()->GetAnimationsForCompositor(aFrame, aProperty);
|
||||
AnimationCollection* animations =
|
||||
nsAnimationManager::GetAnimationsForCompositor(content, aProperty);
|
||||
presContext->AnimationManager()->GetAnimationsForCompositor(aFrame, aProperty);
|
||||
|
||||
if (!animations && !transitions) {
|
||||
return;
|
||||
|
@ -723,8 +720,7 @@ void nsDisplayListBuilder::MarkOutOfFlowFrameForDisplay(nsIFrame* aDirtyFrame,
|
|||
nsRect overflowRect = aFrame->GetVisualOverflowRect();
|
||||
|
||||
if (aFrame->IsTransformed() &&
|
||||
nsLayoutUtils::HasAnimationsForCompositor(aFrame->GetContent(),
|
||||
eCSSProperty_transform)) {
|
||||
nsLayoutUtils::HasAnimationsForCompositor(aFrame, eCSSProperty_transform)) {
|
||||
/**
|
||||
* Add a fuzz factor to the overflow rectangle so that elements only just
|
||||
* out of view are pulled into the display list, so they can be
|
||||
|
@ -3856,11 +3852,8 @@ nsDisplayOpacity::NeedsActiveLayer(nsDisplayListBuilder* aBuilder)
|
|||
if (ActiveLayerTracker::IsStyleAnimated(aBuilder, mFrame, eCSSProperty_opacity) &&
|
||||
!IsItemTooSmallForActiveLayer(this))
|
||||
return true;
|
||||
if (mFrame->GetContent()) {
|
||||
if (nsLayoutUtils::HasAnimationsForCompositor(mFrame->GetContent(),
|
||||
eCSSProperty_opacity)) {
|
||||
return true;
|
||||
}
|
||||
if (nsLayoutUtils::HasAnimationsForCompositor(mFrame, eCSSProperty_opacity)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
@ -5087,9 +5080,7 @@ nsDisplayTransform::ShouldPrerenderTransformedContent(nsDisplayListBuilder* aBui
|
|||
// might have only just had its transform animated in which case
|
||||
// the ActiveLayerManager may not have been notified yet.
|
||||
if (!ActiveLayerTracker::IsStyleMaybeAnimated(aFrame, eCSSProperty_transform) &&
|
||||
(!aFrame->GetContent() ||
|
||||
!nsLayoutUtils::HasAnimationsForCompositor(aFrame->GetContent(),
|
||||
eCSSProperty_transform))) {
|
||||
!nsLayoutUtils::HasAnimationsForCompositor(aFrame, eCSSProperty_transform)) {
|
||||
if (aLogAnimations) {
|
||||
nsCString message;
|
||||
message.AppendLiteral("Performance warning: Async animation disabled because frame was not marked active for transform animation");
|
||||
|
@ -5245,11 +5236,8 @@ nsDisplayTransform::GetLayerState(nsDisplayListBuilder* aBuilder,
|
|||
if (ActiveLayerTracker::IsStyleAnimated(aBuilder, mFrame, eCSSProperty_transform) &&
|
||||
!IsItemTooSmallForActiveLayer(this))
|
||||
return LAYER_ACTIVE;
|
||||
if (mFrame->GetContent()) {
|
||||
if (nsLayoutUtils::HasAnimationsForCompositor(mFrame->GetContent(),
|
||||
eCSSProperty_transform)) {
|
||||
return LAYER_ACTIVE;
|
||||
}
|
||||
if (nsLayoutUtils::HasAnimationsForCompositor(mFrame, eCSSProperty_transform)) {
|
||||
return LAYER_ACTIVE;
|
||||
}
|
||||
|
||||
const nsStyleDisplay* disp = mFrame->StyleDisplay();
|
||||
|
|
|
@ -372,81 +372,79 @@ TextAlignTrueEnabledPrefChangeCallback(const char* aPrefName, void* aClosure)
|
|||
}
|
||||
|
||||
bool
|
||||
nsLayoutUtils::HasAnimationsForCompositor(nsIContent* aContent,
|
||||
nsLayoutUtils::HasAnimationsForCompositor(const nsIFrame* aFrame,
|
||||
nsCSSProperty aProperty)
|
||||
{
|
||||
return nsAnimationManager::GetAnimationsForCompositor(aContent, aProperty) ||
|
||||
nsTransitionManager::GetAnimationsForCompositor(aContent, aProperty);
|
||||
}
|
||||
|
||||
static AnimationCollection*
|
||||
GetAnimationsOrTransitions(nsIContent* aContent,
|
||||
nsIAtom* aAnimationProperty,
|
||||
nsCSSProperty aProperty)
|
||||
{
|
||||
AnimationCollection* collection =
|
||||
static_cast<AnimationCollection*>(aContent->GetProperty(
|
||||
aAnimationProperty));
|
||||
if (collection) {
|
||||
bool propertyMatches = collection->HasAnimationOfProperty(aProperty);
|
||||
if (propertyMatches) {
|
||||
return collection;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
nsPresContext* presContext = aFrame->PresContext();
|
||||
return presContext->AnimationManager()->GetAnimationsForCompositor(aFrame, aProperty) ||
|
||||
presContext->TransitionManager()->GetAnimationsForCompositor(aFrame, aProperty);
|
||||
}
|
||||
|
||||
bool
|
||||
nsLayoutUtils::HasAnimations(nsIContent* aContent,
|
||||
nsLayoutUtils::HasAnimations(const nsIFrame* aFrame,
|
||||
nsCSSProperty aProperty)
|
||||
{
|
||||
if (!aContent->MayHaveAnimations())
|
||||
return false;
|
||||
return GetAnimationsOrTransitions(aContent, nsGkAtoms::animationsProperty,
|
||||
aProperty) ||
|
||||
GetAnimationsOrTransitions(aContent, nsGkAtoms::transitionsProperty,
|
||||
aProperty);
|
||||
}
|
||||
|
||||
bool
|
||||
nsLayoutUtils::HasCurrentAnimations(nsIContent* aContent,
|
||||
nsIAtom* aAnimationProperty)
|
||||
{
|
||||
if (!aContent->MayHaveAnimations())
|
||||
return false;
|
||||
|
||||
nsPresContext* presContext = aFrame->PresContext();
|
||||
AnimationCollection* collection =
|
||||
static_cast<AnimationCollection*>(
|
||||
aContent->GetProperty(aAnimationProperty));
|
||||
return (collection && collection->HasCurrentAnimations());
|
||||
presContext->AnimationManager()->GetAnimationCollection(aFrame);
|
||||
if (collection &&
|
||||
collection->HasAnimationOfProperty(aProperty)) {
|
||||
return true;
|
||||
}
|
||||
collection =
|
||||
presContext->TransitionManager()->GetAnimationCollection(aFrame);
|
||||
if (collection &&
|
||||
collection->HasAnimationOfProperty(aProperty)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool
|
||||
nsLayoutUtils::HasCurrentAnimationsForProperties(nsIContent* aContent,
|
||||
nsLayoutUtils::HasCurrentAnimations(const nsIFrame* aFrame)
|
||||
{
|
||||
nsPresContext* presContext = aFrame->PresContext();
|
||||
AnimationCollection* collection =
|
||||
presContext->AnimationManager()->GetAnimationCollection(aFrame);
|
||||
return collection &&
|
||||
collection->HasCurrentAnimations();
|
||||
}
|
||||
|
||||
bool
|
||||
nsLayoutUtils::HasCurrentTransitions(const nsIFrame* aFrame)
|
||||
{
|
||||
nsPresContext* presContext = aFrame->PresContext();
|
||||
AnimationCollection* collection =
|
||||
presContext->TransitionManager()->GetAnimationCollection(aFrame);
|
||||
return collection &&
|
||||
collection->HasCurrentAnimations();
|
||||
}
|
||||
|
||||
bool
|
||||
nsLayoutUtils::HasCurrentAnimationsForProperties(const nsIFrame* aFrame,
|
||||
const nsCSSProperty* aProperties,
|
||||
size_t aPropertyCount)
|
||||
{
|
||||
if (!aContent->MayHaveAnimations())
|
||||
return false;
|
||||
|
||||
static nsIAtom* const sAnimProps[] = { nsGkAtoms::transitionsProperty,
|
||||
nsGkAtoms::animationsProperty,
|
||||
nullptr };
|
||||
for (nsIAtom* const* animProp = sAnimProps; *animProp; animProp++) {
|
||||
AnimationCollection* collection =
|
||||
static_cast<AnimationCollection*>(aContent->GetProperty(*animProp));
|
||||
if (collection &&
|
||||
collection->HasCurrentAnimationsForProperties(aProperties,
|
||||
aPropertyCount)) {
|
||||
return true;
|
||||
}
|
||||
nsPresContext* presContext = aFrame->PresContext();
|
||||
AnimationCollection* collection =
|
||||
presContext->AnimationManager()->GetAnimationCollection(aFrame);
|
||||
if (collection &&
|
||||
collection->HasCurrentAnimationsForProperties(aProperties,
|
||||
aPropertyCount)) {
|
||||
return true;
|
||||
}
|
||||
collection =
|
||||
presContext->TransitionManager()->GetAnimationCollection(aFrame);
|
||||
if (collection &&
|
||||
collection->HasCurrentAnimationsForProperties(aProperties,
|
||||
aPropertyCount)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static gfxSize
|
||||
GetScaleForValue(const StyleAnimationValue& aValue, nsIFrame* aFrame)
|
||||
GetScaleForValue(const StyleAnimationValue& aValue, const nsIFrame* aFrame)
|
||||
{
|
||||
if (!aFrame) {
|
||||
NS_WARNING("No frame.");
|
||||
|
@ -491,7 +489,7 @@ GetSuitableScale(float aMaxScale, float aMinScale,
|
|||
}
|
||||
|
||||
static void
|
||||
GetMinAndMaxScaleForAnimationProperty(nsIContent* aContent,
|
||||
GetMinAndMaxScaleForAnimationProperty(const nsIFrame* aFrame,
|
||||
AnimationCollection* aAnimations,
|
||||
gfxSize& aMaxScale,
|
||||
gfxSize& aMinScale)
|
||||
|
@ -507,14 +505,12 @@ GetMinAndMaxScaleForAnimationProperty(nsIContent* aContent,
|
|||
if (prop.mProperty == eCSSProperty_transform) {
|
||||
for (uint32_t segIdx = prop.mSegments.Length(); segIdx-- != 0; ) {
|
||||
AnimationPropertySegment& segment = prop.mSegments[segIdx];
|
||||
gfxSize from = GetScaleForValue(segment.mFromValue,
|
||||
aContent->GetPrimaryFrame());
|
||||
gfxSize from = GetScaleForValue(segment.mFromValue, aFrame);
|
||||
aMaxScale.width = std::max<float>(aMaxScale.width, from.width);
|
||||
aMaxScale.height = std::max<float>(aMaxScale.height, from.height);
|
||||
aMinScale.width = std::min<float>(aMinScale.width, from.width);
|
||||
aMinScale.height = std::min<float>(aMinScale.height, from.height);
|
||||
gfxSize to = GetScaleForValue(segment.mToValue,
|
||||
aContent->GetPrimaryFrame());
|
||||
gfxSize to = GetScaleForValue(segment.mToValue, aFrame);
|
||||
aMaxScale.width = std::max<float>(aMaxScale.width, to.width);
|
||||
aMaxScale.height = std::max<float>(aMaxScale.height, to.height);
|
||||
aMinScale.width = std::min<float>(aMinScale.width, to.width);
|
||||
|
@ -526,7 +522,7 @@ GetMinAndMaxScaleForAnimationProperty(nsIContent* aContent,
|
|||
}
|
||||
|
||||
gfxSize
|
||||
nsLayoutUtils::ComputeSuitableScaleForAnimation(nsIContent* aContent,
|
||||
nsLayoutUtils::ComputeSuitableScaleForAnimation(const nsIFrame* aFrame,
|
||||
const nsSize& aVisibleSize,
|
||||
const nsSize& aDisplaySize)
|
||||
{
|
||||
|
@ -534,20 +530,21 @@ nsLayoutUtils::ComputeSuitableScaleForAnimation(nsIContent* aContent,
|
|||
std::numeric_limits<gfxFloat>::min());
|
||||
gfxSize minScale(std::numeric_limits<gfxFloat>::max(),
|
||||
std::numeric_limits<gfxFloat>::max());
|
||||
nsPresContext* presContext = aFrame->PresContext();
|
||||
|
||||
AnimationCollection* animations =
|
||||
nsAnimationManager::GetAnimationsForCompositor(aContent,
|
||||
eCSSProperty_transform);
|
||||
presContext->AnimationManager()->GetAnimationsForCompositor(
|
||||
aFrame, eCSSProperty_transform);
|
||||
if (animations) {
|
||||
GetMinAndMaxScaleForAnimationProperty(aContent, animations,
|
||||
GetMinAndMaxScaleForAnimationProperty(aFrame, animations,
|
||||
maxScale, minScale);
|
||||
}
|
||||
|
||||
animations =
|
||||
nsTransitionManager::GetAnimationsForCompositor(aContent,
|
||||
eCSSProperty_transform);
|
||||
presContext->TransitionManager()->GetAnimationsForCompositor(
|
||||
aFrame, eCSSProperty_transform);
|
||||
if (animations) {
|
||||
GetMinAndMaxScaleForAnimationProperty(aContent, animations,
|
||||
GetMinAndMaxScaleForAnimationProperty(aFrame, animations,
|
||||
maxScale, minScale);
|
||||
}
|
||||
|
||||
|
|
|
@ -2135,32 +2135,48 @@ public:
|
|||
bool clear);
|
||||
|
||||
/**
|
||||
* Returns true if the content node has animations or transitions that can be
|
||||
* Given a frame with possibly animated content, finds the content node
|
||||
* that contains its animations as well as the frame's pseudo-element type
|
||||
* relative to the resulting content node. Returns true if animated content
|
||||
* was found, otherwise it returns false and the output parameters are
|
||||
* undefined.
|
||||
*/
|
||||
static bool GetAnimationContent(const nsIFrame* aFrame,
|
||||
nsIContent* &aContentResult,
|
||||
nsCSSPseudoElements::Type &aPseudoTypeResult);
|
||||
|
||||
/**
|
||||
* Returns true if the frame has animations or transitions that can be
|
||||
* performed on the compositor.
|
||||
*/
|
||||
static bool HasAnimationsForCompositor(nsIContent* aContent,
|
||||
static bool HasAnimationsForCompositor(const nsIFrame* aFrame,
|
||||
nsCSSProperty aProperty);
|
||||
|
||||
/**
|
||||
* Returns true if the content node has animations or transitions for the
|
||||
* Returns true if the frame has animations or transitions for the
|
||||
* property.
|
||||
*/
|
||||
static bool HasAnimations(nsIContent* aContent, nsCSSProperty aProperty);
|
||||
static bool HasAnimations(const nsIFrame* aFrame, nsCSSProperty aProperty);
|
||||
|
||||
/**
|
||||
* Returns true if the content node has any current animations or transitions
|
||||
* (depending on the value of |aAnimationProperty|).
|
||||
* Returns true if the frame has any current animations.
|
||||
* A current animation is any animation that has not yet finished playing
|
||||
* including paused animations.
|
||||
*/
|
||||
static bool HasCurrentAnimations(nsIContent* aContent,
|
||||
nsIAtom* aAnimationProperty);
|
||||
static bool HasCurrentAnimations(const nsIFrame* aFrame);
|
||||
|
||||
/**
|
||||
* Returns true if the content node has any current animations or transitions
|
||||
* Returns true if the frame has any current transitions.
|
||||
* A current transition is any transition that has not yet finished playing
|
||||
* including paused transitions.
|
||||
*/
|
||||
static bool HasCurrentTransitions(const nsIFrame* aFrame);
|
||||
|
||||
/**
|
||||
* Returns true if the frame has any current animations or transitions
|
||||
* for any of the specified properties.
|
||||
*/
|
||||
static bool HasCurrentAnimationsForProperties(nsIContent* aContent,
|
||||
static bool HasCurrentAnimationsForProperties(const nsIFrame* aFrame,
|
||||
const nsCSSProperty* aProperties,
|
||||
size_t aPropertyCount);
|
||||
|
||||
|
@ -2175,7 +2191,7 @@ public:
|
|||
static bool IsAnimationLoggingEnabled();
|
||||
|
||||
/**
|
||||
* Find a suitable scale for an element (aContent) over the course of any
|
||||
* Find a suitable scale for a element (aFrame's content) over the course of any
|
||||
* animations and transitions of the CSS transform property on the
|
||||
* element that run on the compositor thread.
|
||||
* It will check the maximum and minimum scale during the animations and
|
||||
|
@ -2185,7 +2201,7 @@ public:
|
|||
* @param aVisibleSize is the size of the area we want to paint
|
||||
* @param aDisplaySize is the size of the display area of the pres context
|
||||
*/
|
||||
static gfxSize ComputeSuitableScaleForAnimation(nsIContent* aContent,
|
||||
static gfxSize ComputeSuitableScaleForAnimation(const nsIFrame* aFrame,
|
||||
const nsSize& aVisibleSize,
|
||||
const nsSize& aDisplaySize);
|
||||
|
||||
|
|
|
@ -27,6 +27,7 @@
|
|||
|
||||
#include "mozilla/ArrayUtils.h"
|
||||
#include "mozilla/AutoRestore.h"
|
||||
#include "mozilla/IntegerRange.h"
|
||||
#include "nsHostObjectProtocolHandler.h"
|
||||
#include "nsRefreshDriver.h"
|
||||
#include "nsITimer.h"
|
||||
|
@ -1341,6 +1342,7 @@ nsRefreshDriver::ObserverCount() const
|
|||
// style changes, etc.
|
||||
sum += mStyleFlushObservers.Length();
|
||||
sum += mLayoutFlushObservers.Length();
|
||||
sum += mPendingEvents.Length();
|
||||
sum += mFrameRequestCallbackDocs.Length();
|
||||
sum += mThrottledFrameRequestCallbackDocs.Length();
|
||||
sum += mViewManagerFlushIsPending;
|
||||
|
@ -1476,6 +1478,17 @@ TakeFrameRequestCallbacksFrom(nsIDocument* aDocument,
|
|||
aDocument->TakeFrameRequestCallbacks(aTarget.LastElement().mCallbacks);
|
||||
}
|
||||
|
||||
void
|
||||
nsRefreshDriver::DispatchPendingEvents()
|
||||
{
|
||||
// Swap out the current pending events
|
||||
nsTArray<PendingEvent> pendingEvents(Move(mPendingEvents));
|
||||
for (PendingEvent& event : pendingEvents) {
|
||||
bool dummy;
|
||||
event.mTarget->DispatchEvent(event.mEvent, &dummy);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
nsRefreshDriver::RunFrameRequestCallbacks(int64_t aNowEpoch, TimeStamp aNowTime)
|
||||
{
|
||||
|
@ -1657,6 +1670,7 @@ nsRefreshDriver::Tick(int64_t aNowEpoch, TimeStamp aNowTime)
|
|||
if (i == 0) {
|
||||
// This is the Flush_Style case.
|
||||
|
||||
DispatchPendingEvents();
|
||||
RunFrameRequestCallbacks(aNowEpoch, aNowTime);
|
||||
|
||||
if (mPresContext && mPresContext->GetPresShell()) {
|
||||
|
@ -2131,4 +2145,22 @@ nsRefreshDriver::RevokeFrameRequestCallbacks(nsIDocument* aDocument)
|
|||
// running; that will happen automatically when it fires.
|
||||
}
|
||||
|
||||
void
|
||||
nsRefreshDriver::ScheduleEventDispatch(nsINode* aTarget, nsIDOMEvent* aEvent)
|
||||
{
|
||||
mPendingEvents.AppendElement(PendingEvent{aTarget, aEvent});
|
||||
// make sure that the timer is running
|
||||
EnsureTimerStarted();
|
||||
}
|
||||
|
||||
void
|
||||
nsRefreshDriver::CancelPendingEvents(nsIDocument* aDocument)
|
||||
{
|
||||
for (auto i : Reversed(MakeRange(mPendingEvents.Length()))) {
|
||||
if (mPendingEvents[i].mTarget->OwnerDoc() == aDocument) {
|
||||
mPendingEvents.RemoveElementAt(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#undef LOG
|
||||
|
|
|
@ -29,6 +29,8 @@ class nsPresContext;
|
|||
class nsIPresShell;
|
||||
class nsIDocument;
|
||||
class imgIRequest;
|
||||
class nsIDOMEvent;
|
||||
class nsINode;
|
||||
|
||||
namespace mozilla {
|
||||
class RefreshDriverTimer;
|
||||
|
@ -222,6 +224,17 @@ public:
|
|||
*/
|
||||
void RevokeFrameRequestCallbacks(nsIDocument* aDocument);
|
||||
|
||||
/**
|
||||
* Queue a new event to dispatch in next tick before the style flush
|
||||
*/
|
||||
void ScheduleEventDispatch(nsINode* aTarget, nsIDOMEvent* aEvent);
|
||||
|
||||
/**
|
||||
* Cancel all pending events scheduled by ScheduleEventDispatch which
|
||||
* targets any node in aDocument.
|
||||
*/
|
||||
void CancelPendingEvents(nsIDocument* aDocument);
|
||||
|
||||
/**
|
||||
* Tell the refresh driver that it is done driving refreshes and
|
||||
* should stop its timer and forget about its pres context. This may
|
||||
|
@ -308,6 +321,7 @@ private:
|
|||
};
|
||||
typedef nsClassHashtable<nsUint32HashKey, ImageStartData> ImageStartTable;
|
||||
|
||||
void DispatchPendingEvents();
|
||||
void RunFrameRequestCallbacks(int64_t aNowEpoch, mozilla::TimeStamp aNowTime);
|
||||
|
||||
void Tick(int64_t aNowEpoch, mozilla::TimeStamp aNowTime);
|
||||
|
@ -403,6 +417,11 @@ private:
|
|||
RequestTable mRequests;
|
||||
ImageStartTable mStartTable;
|
||||
|
||||
struct PendingEvent {
|
||||
nsCOMPtr<nsINode> mTarget;
|
||||
nsCOMPtr<nsIDOMEvent> mEvent;
|
||||
};
|
||||
|
||||
nsAutoTArray<nsIPresShell*, 16> mStyleFlushObservers;
|
||||
nsAutoTArray<nsIPresShell*, 16> mLayoutFlushObservers;
|
||||
nsAutoTArray<nsIPresShell*, 16> mPresShellsToInvalidateIfHidden;
|
||||
|
@ -410,6 +429,7 @@ private:
|
|||
nsTArray<nsIDocument*> mFrameRequestCallbackDocs;
|
||||
nsTArray<nsIDocument*> mThrottledFrameRequestCallbackDocs;
|
||||
nsTObserverArray<nsAPostRefreshObserver*> mPostRefreshObservers;
|
||||
nsTArray<PendingEvent> mPendingEvents;
|
||||
|
||||
// Helper struct for processing image requests
|
||||
struct ImageRequestParameters {
|
||||
|
|
|
@ -1082,7 +1082,7 @@ nsIFrame::IsTransformed() const
|
|||
(StyleDisplay()->HasTransform(this) ||
|
||||
IsSVGTransformed() ||
|
||||
(mContent &&
|
||||
nsLayoutUtils::HasAnimationsForCompositor(mContent,
|
||||
nsLayoutUtils::HasAnimationsForCompositor(this,
|
||||
eCSSProperty_transform) &&
|
||||
IsFrameOfType(eSupportsCSSTransforms) &&
|
||||
mContent->GetPrimaryFrame() == this)));
|
||||
|
@ -1096,7 +1096,7 @@ nsIFrame::HasOpacityInternal(float aThreshold) const
|
|||
return StyleDisplay()->mOpacity < aThreshold ||
|
||||
(displayStyle->mWillChangeBitField & NS_STYLE_WILL_CHANGE_OPACITY) ||
|
||||
(mContent &&
|
||||
nsLayoutUtils::HasAnimationsForCompositor(mContent,
|
||||
nsLayoutUtils::HasAnimationsForCompositor(this,
|
||||
eCSSProperty_opacity) &&
|
||||
mContent->GetPrimaryFrame() == this);
|
||||
}
|
||||
|
@ -1946,7 +1946,7 @@ nsIFrame::BuildDisplayListForStackingContext(nsDisplayListBuilder* aBuilder,
|
|||
if (disp->mOpacity == 0.0 && aBuilder->IsForPainting() &&
|
||||
!aBuilder->WillComputePluginGeometry() &&
|
||||
!(disp->mWillChangeBitField & NS_STYLE_WILL_CHANGE_OPACITY) &&
|
||||
!nsLayoutUtils::HasAnimations(mContent, eCSSProperty_opacity) &&
|
||||
!nsLayoutUtils::HasAnimations(this, eCSSProperty_opacity) &&
|
||||
!needEventRegions) {
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -2591,7 +2591,7 @@ public:
|
|||
*
|
||||
* @return whether the frame correspods to generated content
|
||||
*/
|
||||
bool IsGeneratedContentFrame() {
|
||||
bool IsGeneratedContentFrame() const {
|
||||
return (mState & NS_FRAME_GENERATED_CONTENT) != 0;
|
||||
}
|
||||
|
||||
|
|
|
@ -134,15 +134,45 @@ CommonAnimationManager::NeedsRefresh() const
|
|||
}
|
||||
|
||||
AnimationCollection*
|
||||
CommonAnimationManager::GetAnimationsForCompositor(nsIContent* aContent,
|
||||
nsIAtom* aElementProperty,
|
||||
CommonAnimationManager::GetAnimationCollection(const nsIFrame* aFrame)
|
||||
{
|
||||
nsIContent* content = aFrame->GetContent();
|
||||
if (!content) {
|
||||
return nullptr;
|
||||
}
|
||||
nsIAtom* animProp;
|
||||
if (aFrame->IsGeneratedContentFrame()) {
|
||||
nsIFrame* parent = aFrame->GetParent();
|
||||
if (parent->IsGeneratedContentFrame()) {
|
||||
return nullptr;
|
||||
}
|
||||
nsIAtom* name = content->NodeInfo()->NameAtom();
|
||||
if (name == nsGkAtoms::mozgeneratedcontentbefore) {
|
||||
animProp = GetAnimationsBeforeAtom();
|
||||
} else if (name == nsGkAtoms::mozgeneratedcontentafter) {
|
||||
animProp = GetAnimationsAfterAtom();
|
||||
} else {
|
||||
return nullptr;
|
||||
}
|
||||
content = content->GetParent();
|
||||
if (!content) {
|
||||
return nullptr;
|
||||
}
|
||||
} else {
|
||||
if (!content->MayHaveAnimations()) {
|
||||
return nullptr;
|
||||
}
|
||||
animProp = GetAnimationsAtom();
|
||||
}
|
||||
|
||||
return static_cast<AnimationCollection*>(content->GetProperty(animProp));
|
||||
}
|
||||
|
||||
AnimationCollection*
|
||||
CommonAnimationManager::GetAnimationsForCompositor(const nsIFrame* aFrame,
|
||||
nsCSSProperty aProperty)
|
||||
{
|
||||
if (!aContent->MayHaveAnimations())
|
||||
return nullptr;
|
||||
|
||||
AnimationCollection* collection =
|
||||
static_cast<AnimationCollection*>(aContent->GetProperty(aElementProperty));
|
||||
AnimationCollection* collection = GetAnimationCollection(aFrame);
|
||||
if (!collection ||
|
||||
!collection->HasAnimationOfProperty(aProperty) ||
|
||||
!collection->CanPerformOnCompositorThread(
|
||||
|
@ -337,11 +367,12 @@ CommonAnimationManager::GetAnimations(dom::Element *aElement,
|
|||
&AnimationCollection::PropertyDtor, false);
|
||||
if (NS_FAILED(rv)) {
|
||||
NS_WARNING("SetProperty failed");
|
||||
delete collection;
|
||||
// The collection must be destroyed via PropertyDtor, otherwise
|
||||
// mCalledPropertyDtor assertion is triggered in destructor.
|
||||
AnimationCollection::PropertyDtor(aElement, propName, collection, nullptr);
|
||||
return nullptr;
|
||||
}
|
||||
if (propName == nsGkAtoms::animationsProperty ||
|
||||
propName == nsGkAtoms::transitionsProperty) {
|
||||
if (aPseudoType == nsCSSPseudoElements::ePseudo_NotPseudoElement) {
|
||||
aElement->SetMayHaveAnimations();
|
||||
}
|
||||
|
||||
|
@ -577,21 +608,12 @@ bool
|
|||
AnimationCollection::CanPerformOnCompositorThread(
|
||||
CanAnimateFlags aFlags) const
|
||||
{
|
||||
nsIFrame* frame = nsLayoutUtils::GetStyleFrame(mElement);
|
||||
if (!frame) {
|
||||
dom::Element* element = GetElementToRestyle();
|
||||
if (!element) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (mElementProperty != nsGkAtoms::transitionsProperty &&
|
||||
mElementProperty != nsGkAtoms::animationsProperty) {
|
||||
if (nsLayoutUtils::IsAnimationLoggingEnabled()) {
|
||||
nsCString message;
|
||||
message.AppendLiteral("Gecko bug: Async animation of pseudoelements"
|
||||
" not supported. See bug 771367 (");
|
||||
message.Append(nsAtomCString(mElementProperty));
|
||||
message.Append(")");
|
||||
LogAsyncAnimationFailure(message, mElement);
|
||||
}
|
||||
nsIFrame* frame = nsLayoutUtils::GetStyleFrame(element);
|
||||
if (!frame) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -628,7 +650,7 @@ AnimationCollection::CanPerformOnCompositorThread(
|
|||
for (size_t propIdx = 0, propEnd = effect->Properties().Length();
|
||||
propIdx != propEnd; ++propIdx) {
|
||||
const AnimationProperty& prop = effect->Properties()[propIdx];
|
||||
if (!CanAnimatePropertyOnCompositor(mElement,
|
||||
if (!CanAnimatePropertyOnCompositor(element,
|
||||
prop.mProperty,
|
||||
aFlags) ||
|
||||
IsCompositorAnimationDisabledForFrame(frame)) {
|
||||
|
@ -839,10 +861,15 @@ AnimationCollection::CanThrottleTransformChanges(TimeStamp aTime)
|
|||
return true;
|
||||
}
|
||||
|
||||
dom::Element* element = GetElementToRestyle();
|
||||
if (!element) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// If the nearest scrollable ancestor has overflow:hidden,
|
||||
// we don't care about overflow.
|
||||
nsIScrollableFrame* scrollable = nsLayoutUtils::GetNearestScrollableFrame(
|
||||
nsLayoutUtils::GetStyleFrame(mElement));
|
||||
nsLayoutUtils::GetStyleFrame(element));
|
||||
if (!scrollable) {
|
||||
return true;
|
||||
}
|
||||
|
@ -860,7 +887,11 @@ AnimationCollection::CanThrottleTransformChanges(TimeStamp aTime)
|
|||
bool
|
||||
AnimationCollection::CanThrottleAnimation(TimeStamp aTime)
|
||||
{
|
||||
nsIFrame* frame = nsLayoutUtils::GetStyleFrame(mElement);
|
||||
dom::Element* element = GetElementToRestyle();
|
||||
if (!element) {
|
||||
return false;
|
||||
}
|
||||
nsIFrame* frame = nsLayoutUtils::GetStyleFrame(element);
|
||||
if (!frame) {
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -161,19 +161,26 @@ protected:
|
|||
return false;
|
||||
}
|
||||
|
||||
|
||||
public:
|
||||
// Return an AnimationCollection* if we have an animation for
|
||||
// the element aContent and pseudo-element indicator aElementProperty
|
||||
// that can be performed on the compositor thread (as defined by
|
||||
// AnimationCollection::CanPerformOnCompositorThread).
|
||||
// the frame aFrame that can be performed on the compositor thread (as
|
||||
// defined by AnimationCollection::CanPerformOnCompositorThread).
|
||||
//
|
||||
// Note that this does not test whether the element's layer uses
|
||||
// Note that this does not test whether the frame's layer uses
|
||||
// off-main-thread compositing, although it does check whether
|
||||
// off-main-thread compositing is enabled as a whole.
|
||||
static AnimationCollection*
|
||||
GetAnimationsForCompositor(nsIContent* aContent,
|
||||
nsIAtom* aElementProperty,
|
||||
AnimationCollection*
|
||||
GetAnimationsForCompositor(const nsIFrame* aFrame,
|
||||
nsCSSProperty aProperty);
|
||||
|
||||
// Given the frame aFrame with possibly animated content, finds its
|
||||
// associated collection of animations. If it is a generated content
|
||||
// frame, it may examine the parent frame to search for such animations.
|
||||
AnimationCollection*
|
||||
GetAnimationCollection(const nsIFrame* aFrame);
|
||||
|
||||
protected:
|
||||
PRCList mElementCollections;
|
||||
nsPresContext *mPresContext; // weak (non-null from ctor to Disconnect)
|
||||
bool mIsObservingRefreshDriver;
|
||||
|
|
|
@ -163,13 +163,6 @@ public:
|
|||
{
|
||||
}
|
||||
|
||||
static mozilla::AnimationCollection*
|
||||
GetAnimationsForCompositor(nsIContent* aContent, nsCSSProperty aProperty)
|
||||
{
|
||||
return mozilla::css::CommonAnimationManager::GetAnimationsForCompositor(
|
||||
aContent, nsGkAtoms::animationsProperty, aProperty);
|
||||
}
|
||||
|
||||
void UpdateStyleAndEvents(mozilla::AnimationCollection* aEA,
|
||||
mozilla::TimeStamp aRefreshTime,
|
||||
mozilla::EnsureStyleRuleFlags aFlags);
|
||||
|
|
|
@ -117,13 +117,6 @@ public:
|
|||
|
||||
typedef mozilla::AnimationCollection AnimationCollection;
|
||||
|
||||
static AnimationCollection*
|
||||
GetAnimationsForCompositor(nsIContent* aContent, nsCSSProperty aProperty)
|
||||
{
|
||||
return mozilla::css::CommonAnimationManager::GetAnimationsForCompositor(
|
||||
aContent, nsGkAtoms::transitionsProperty, aProperty);
|
||||
}
|
||||
|
||||
/**
|
||||
* StyleContextChanged
|
||||
*
|
||||
|
|
|
@ -404,19 +404,23 @@ const ExpectComparisonTo = {
|
|||
};
|
||||
|
||||
(function() {
|
||||
window.omta_todo_is = function(elem, property, expected, runningOn, desc) {
|
||||
window.omta_todo_is = function(elem, property, expected, runningOn, desc,
|
||||
pseudo) {
|
||||
return omta_is_approx(elem, property, expected, 0, runningOn, desc,
|
||||
ExpectComparisonTo.Fail);
|
||||
ExpectComparisonTo.Fail, pseudo);
|
||||
};
|
||||
|
||||
window.omta_is = function(elem, property, expected, runningOn, desc) {
|
||||
return omta_is_approx(elem, property, expected, 0, runningOn, desc);
|
||||
window.omta_is = function(elem, property, expected, runningOn, desc,
|
||||
pseudo) {
|
||||
return omta_is_approx(elem, property, expected, 0, runningOn, desc,
|
||||
ExpectComparisonTo.Pass, pseudo);
|
||||
};
|
||||
|
||||
// Many callers of this method will pass 'undefined' for
|
||||
// expectedComparisonResult.
|
||||
window.omta_is_approx = function(elem, property, expected, tolerance,
|
||||
runningOn, desc, expectedComparisonResult) {
|
||||
runningOn, desc, expectedComparisonResult,
|
||||
pseudo) {
|
||||
// Check input
|
||||
const omtaProperties = [ "transform", "opacity" ];
|
||||
if (omtaProperties.indexOf(property) === -1) {
|
||||
|
@ -434,8 +438,8 @@ const ExpectComparisonTo = {
|
|||
|
||||
// Get actual values
|
||||
var compositorStr =
|
||||
SpecialPowers.DOMWindowUtils.getOMTAStyle(elem, property);
|
||||
var computedStr = window.getComputedStyle(elem)[property];
|
||||
SpecialPowers.DOMWindowUtils.getOMTAStyle(elem, property, pseudo);
|
||||
var computedStr = window.getComputedStyle(elem, pseudo)[property];
|
||||
|
||||
// Prepare expected value
|
||||
var expectedValue = normalize(expected);
|
||||
|
|
|
@ -86,6 +86,12 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=964646
|
|||
from, to { transform: translate(50px) }
|
||||
}
|
||||
|
||||
#withbefore::before, #withafter::after {
|
||||
content: "test";
|
||||
animation: anim4 1s linear alternate 3;
|
||||
display:block;
|
||||
}
|
||||
|
||||
@keyframes multiprop {
|
||||
0% {
|
||||
transform: translate(10px); opacity: 0.3;
|
||||
|
@ -1436,8 +1442,72 @@ addAsyncAnimTest(function *() {
|
|||
* http://dev.w3.org/csswg/css3-animations/#the-animation-shorthand-property-
|
||||
*/
|
||||
|
||||
// (Unlike test_animations.html, pseudo-elements are not tested here since they
|
||||
// are currently not run on the compositor thread.)
|
||||
/**
|
||||
* Basic tests of animations on pseudo-elements
|
||||
*/
|
||||
addAsyncAnimTest(function *() {
|
||||
new_div("");
|
||||
listen();
|
||||
gDiv.id = "withbefore";
|
||||
yield waitForPaintsFlushed();
|
||||
omta_is("transform", { ty: 0 }, RunningOn.Compositor,
|
||||
":before test at 0ms", "::before");
|
||||
advance_clock(400);
|
||||
omta_is("transform", { ty: 40 }, RunningOn.Compositor,
|
||||
":before test at 400ms", "::before");
|
||||
advance_clock(800);
|
||||
omta_is("transform", { ty: 80 }, RunningOn.Compositor,
|
||||
":before test at 1200ms", "::before");
|
||||
omta_is("transform", { ty: 0 }, RunningOn.MainThread,
|
||||
":before animation should not affect element");
|
||||
advance_clock(800);
|
||||
omta_is("transform", { ty: 0 }, RunningOn.Compositor,
|
||||
":before test at 2000ms", "::before");
|
||||
advance_clock(300);
|
||||
omta_is("transform", { ty: 30 }, RunningOn.Compositor,
|
||||
":before test at 2300ms", "::before");
|
||||
advance_clock(700);
|
||||
check_events([ { type: "animationstart", animationName: "anim4",
|
||||
elapsedTime: 0, pseudoElement: "::before" },
|
||||
{ type: "animationiteration", animationName: "anim4",
|
||||
elapsedTime: 1, pseudoElement: "::before" },
|
||||
{ type: "animationiteration", animationName: "anim4",
|
||||
elapsedTime: 2, pseudoElement: "::before" },
|
||||
{ type: "animationend", animationName: "anim4",
|
||||
elapsedTime: 3, pseudoElement: "::before" }]);
|
||||
done_div();
|
||||
|
||||
new_div("");
|
||||
listen();
|
||||
gDiv.id = "withafter";
|
||||
yield waitForPaintsFlushed();
|
||||
omta_is("transform", { ty: 0 }, RunningOn.Compositor,
|
||||
":after test at 0ms", "::after");
|
||||
advance_clock(400);
|
||||
omta_is("transform", { ty: 40 }, RunningOn.Compositor,
|
||||
":after test at 400ms", "::after");
|
||||
advance_clock(800);
|
||||
omta_is("transform", { ty: 80 }, RunningOn.Compositor,
|
||||
":after test at 1200ms", "::after");
|
||||
omta_is("transform", { ty: 0 }, RunningOn.MainThread,
|
||||
":before animation should not affect element");
|
||||
advance_clock(800);
|
||||
omta_is("transform", { ty: 0 }, RunningOn.Compositor,
|
||||
":after test at 2000ms", "::after");
|
||||
advance_clock(300);
|
||||
omta_is("transform", { ty: 30 }, RunningOn.Compositor,
|
||||
":after test at 2300ms", "::after");
|
||||
advance_clock(700);
|
||||
check_events([ { type: "animationstart", animationName: "anim4",
|
||||
elapsedTime: 0, pseudoElement: "::after" },
|
||||
{ type: "animationiteration", animationName: "anim4",
|
||||
elapsedTime: 1, pseudoElement: "::after" },
|
||||
{ type: "animationiteration", animationName: "anim4",
|
||||
elapsedTime: 2, pseudoElement: "::after" },
|
||||
{ type: "animationend", animationName: "anim4",
|
||||
elapsedTime: 3, pseudoElement: "::after" }]);
|
||||
done_div();
|
||||
});
|
||||
|
||||
/**
|
||||
* Test handling of properties that are present in only some of the
|
||||
|
|
|
@ -521,8 +521,7 @@ nsMenuPopupFrame::LayoutPopup(nsBoxLayoutState& aState, nsIFrame* aParentMenu,
|
|||
// for it to finish before firing the popupshown event.
|
||||
if (mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::animate,
|
||||
nsGkAtoms::open, eCaseMatters) &&
|
||||
nsLayoutUtils::HasCurrentAnimations(mContent,
|
||||
nsGkAtoms::transitionsProperty)) {
|
||||
nsLayoutUtils::HasCurrentTransitions(this)) {
|
||||
mPopupShownDispatcher = new nsXULPopupShownEvent(mContent, pc);
|
||||
mContent->AddSystemEventListener(NS_LITERAL_STRING("transitionend"),
|
||||
mPopupShownDispatcher, false, false);
|
||||
|
|
|
@ -1473,8 +1473,7 @@ nsXULPopupManager::FirePopupHidingEvent(nsIContent* aPopup,
|
|||
if (!popupFrame)
|
||||
return;
|
||||
|
||||
if (nsLayoutUtils::HasCurrentAnimations(aPopup,
|
||||
nsGkAtoms::transitionsProperty)) {
|
||||
if (nsLayoutUtils::HasCurrentTransitions(popupFrame)) {
|
||||
nsRefPtr<TransitionEnder> ender = new TransitionEnder(aPopup, aDeselectMenu);
|
||||
aPopup->AddSystemEventListener(NS_LITERAL_STRING("transitionend"),
|
||||
ender, false, false);
|
||||
|
|
|
@ -119,7 +119,7 @@ GetInterfaces(std::vector<NetworkInterface>* aInterfaces)
|
|||
|
||||
int
|
||||
nr_stun_get_addrs(nr_local_addr aAddrs[], int aMaxAddrs,
|
||||
int aDropLoopback, int* aCount)
|
||||
int aDropLoopback, int aDropLinkLocal, int* aCount)
|
||||
{
|
||||
nsresult rv;
|
||||
int r;
|
||||
|
@ -141,7 +141,6 @@ nr_stun_get_addrs(nr_local_addr aAddrs[], int aMaxAddrs,
|
|||
for (size_t i = 0; i < num_interface; ++i) {
|
||||
NetworkInterface &interface = interfaces[i];
|
||||
if (nr_sockaddr_to_transport_addr((sockaddr*)&(interface.addr),
|
||||
sizeof(struct sockaddr_in),
|
||||
IPPROTO_UDP, 0, &(aAddrs[n].addr))) {
|
||||
r_log(NR_LOG_STUN, LOG_WARNING, "Problem transforming address");
|
||||
return R_FAILED;
|
||||
|
@ -154,7 +153,7 @@ nr_stun_get_addrs(nr_local_addr aAddrs[], int aMaxAddrs,
|
|||
}
|
||||
|
||||
*aCount = n;
|
||||
r = nr_stun_remove_duplicate_addrs(aAddrs, aDropLoopback, aCount);
|
||||
r = nr_stun_remove_duplicate_addrs(aAddrs, aDropLoopback, aDropLinkLocal, aCount);
|
||||
if (r != 0) {
|
||||
return r;
|
||||
}
|
||||
|
|
|
@ -337,20 +337,11 @@ static int nr_transport_addr_to_praddr(nr_transport_addr *addr,
|
|||
naddr->inet.ip = addr->u.addr4.sin_addr.s_addr;
|
||||
break;
|
||||
case NR_IPV6:
|
||||
#if 0
|
||||
naddr->ipv6.family = PR_AF_INET6;
|
||||
naddr->ipv6.port = addr->u.addr6.sin6_port;
|
||||
#ifdef LINUX
|
||||
memcpy(naddr->ipv6.ip._S6_un._S6_u8,
|
||||
&addr->u.addr6.sin6_addr.__in6_u.__u6_addr8, 16);
|
||||
#else
|
||||
memcpy(naddr->ipv6.ip._S6_un._S6_u8,
|
||||
&addr->u.addr6.sin6_addr.__u6_addr.__u6_addr8, 16);
|
||||
#endif
|
||||
#else
|
||||
// TODO: make IPv6 work
|
||||
ABORT(R_INTERNAL);
|
||||
#endif
|
||||
naddr->ipv6.flowinfo = addr->u.addr6.sin6_flowinfo;
|
||||
memcpy(&naddr->ipv6.ip, &addr->u.addr6.sin6_addr, sizeof(in6_addr));
|
||||
naddr->ipv6.scope_id = addr->u.addr6.sin6_scope_id;
|
||||
break;
|
||||
default:
|
||||
ABORT(R_BAD_ARGS);
|
||||
|
@ -440,6 +431,7 @@ int nr_praddr_to_transport_addr(const PRNetAddr *praddr,
|
|||
int _status;
|
||||
int r;
|
||||
struct sockaddr_in ip4;
|
||||
struct sockaddr_in6 ip6;
|
||||
|
||||
switch(praddr->raw.family) {
|
||||
case PR_AF_INET:
|
||||
|
@ -447,18 +439,19 @@ int nr_praddr_to_transport_addr(const PRNetAddr *praddr,
|
|||
ip4.sin_addr.s_addr = praddr->inet.ip;
|
||||
ip4.sin_port = praddr->inet.port;
|
||||
if ((r = nr_sockaddr_to_transport_addr((sockaddr *)&ip4,
|
||||
sizeof(ip4),
|
||||
protocol, keep,
|
||||
addr)))
|
||||
ABORT(r);
|
||||
break;
|
||||
case PR_AF_INET6:
|
||||
#if 0
|
||||
r = nr_sockaddr_to_transport_addr((sockaddr *)&praddr->raw,
|
||||
sizeof(struct sockaddr_in6),IPPROTO_UDP,keep,addr);
|
||||
ip6.sin6_family = PF_INET6;
|
||||
ip6.sin6_port = praddr->ipv6.port;
|
||||
ip6.sin6_flowinfo = praddr->ipv6.flowinfo;
|
||||
memcpy(&ip6.sin6_addr, &praddr->ipv6.ip, sizeof(in6_addr));
|
||||
ip6.sin6_scope_id = praddr->ipv6.scope_id;
|
||||
if ((r = nr_sockaddr_to_transport_addr((sockaddr *)&ip6,protocol,keep,addr)))
|
||||
ABORT(r);
|
||||
break;
|
||||
#endif
|
||||
ABORT(R_BAD_ARGS);
|
||||
default:
|
||||
MOZ_ASSERT(false);
|
||||
ABORT(R_BAD_ARGS);
|
||||
|
@ -516,14 +509,16 @@ int NrSocket::create(nr_transport_addr *addr) {
|
|||
|
||||
switch (addr->protocol) {
|
||||
case IPPROTO_UDP:
|
||||
if (!(fd_ = PR_NewUDPSocket())) {
|
||||
r_log(LOG_GENERIC,LOG_CRIT,"Couldn't create socket");
|
||||
if (!(fd_ = PR_OpenUDPSocket(naddr.raw.family))) {
|
||||
r_log(LOG_GENERIC,LOG_CRIT,"Couldn't create UDP socket, "
|
||||
"family=%d, err=%d", naddr.raw.family, PR_GetError());
|
||||
ABORT(R_INTERNAL);
|
||||
}
|
||||
break;
|
||||
case IPPROTO_TCP:
|
||||
if (!(fd_ = PR_NewTCPSocket())) {
|
||||
r_log(LOG_GENERIC,LOG_CRIT,"Couldn't create socket");
|
||||
if (!(fd_ = PR_OpenTCPSocket(naddr.raw.family))) {
|
||||
r_log(LOG_GENERIC,LOG_CRIT,"Couldn't create TCP socket, "
|
||||
"family=%d, err=%d", naddr.raw.family, PR_GetError());
|
||||
ABORT(R_INTERNAL);
|
||||
}
|
||||
// Set ReuseAddr for TCP sockets to enable having several
|
||||
|
@ -686,7 +681,8 @@ int NrSocket::sendto(const void *msg, size_t len,
|
|||
if (PR_GetError() == PR_WOULD_BLOCK_ERROR)
|
||||
ABORT(R_WOULDBLOCK);
|
||||
|
||||
r_log(LOG_GENERIC, LOG_INFO, "Error in sendto: %s", to->as_string);
|
||||
r_log(LOG_GENERIC, LOG_INFO, "Error in sendto %s: %d",
|
||||
to->as_string, PR_GetError());
|
||||
ABORT(R_IO_ERROR);
|
||||
}
|
||||
|
||||
|
|
|
@ -380,7 +380,8 @@ RefPtr<NrIceCtx> NrIceCtx::Create(const std::string& name,
|
|||
bool offerer,
|
||||
bool set_interface_priorities,
|
||||
bool allow_loopback,
|
||||
bool tcp_enabled) {
|
||||
bool tcp_enabled,
|
||||
bool allow_link_local) {
|
||||
|
||||
RefPtr<NrIceCtx> ctx = new NrIceCtx(name, offerer);
|
||||
|
||||
|
@ -472,6 +473,10 @@ RefPtr<NrIceCtx> NrIceCtx::Create(const std::string& name,
|
|||
if (allow_loopback) {
|
||||
NR_reg_set_char((char *)NR_STUN_REG_PREF_ALLOW_LOOPBACK_ADDRS, 1);
|
||||
}
|
||||
|
||||
if (allow_link_local) {
|
||||
NR_reg_set_char((char *)NR_STUN_REG_PREF_ALLOW_LINK_LOCAL_ADDRS, 1);
|
||||
}
|
||||
}
|
||||
|
||||
// Create the ICE context
|
||||
|
|
|
@ -212,7 +212,8 @@ class NrIceCtx {
|
|||
bool offerer,
|
||||
bool set_interface_priorities = true,
|
||||
bool allow_loopback = false,
|
||||
bool tcp_enabled = true);
|
||||
bool tcp_enabled = true,
|
||||
bool allow_link_local = false);
|
||||
|
||||
// Deinitialize all ICE global state. Used only for testing.
|
||||
static void internal_DeinitializeGlobal();
|
||||
|
|
|
@ -150,6 +150,7 @@ int NrIceResolver::resolve(nr_resolver_resource *resource,
|
|||
MOZ_ASSERT(allocated_resolvers_ > 0);
|
||||
ASSERT_ON_THREAD(sts_thread_);
|
||||
nsRefPtr<PendingResolution> pr;
|
||||
uint32_t resolve_flags = 0;
|
||||
|
||||
if (resource->transport_protocol != IPPROTO_UDP &&
|
||||
resource->transport_protocol != IPPROTO_TCP) {
|
||||
|
@ -162,8 +163,20 @@ int NrIceResolver::resolve(nr_resolver_resource *resource,
|
|||
resource->transport_protocol :
|
||||
IPPROTO_UDP,
|
||||
cb, cb_arg);
|
||||
|
||||
switch(resource->address_family) {
|
||||
case AF_INET:
|
||||
resolve_flags |= nsIDNSService::RESOLVE_DISABLE_IPV6;
|
||||
break;
|
||||
case AF_INET6:
|
||||
resolve_flags |= nsIDNSService::RESOLVE_DISABLE_IPV4;
|
||||
break;
|
||||
default:
|
||||
ABORT(R_BAD_ARGS);
|
||||
}
|
||||
|
||||
if (NS_FAILED(dns_->AsyncResolve(nsAutoCString(resource->domain_name),
|
||||
nsIDNSService::RESOLVE_DISABLE_IPV6, pr,
|
||||
resolve_flags, pr,
|
||||
sts_thread_, getter_AddRefs(pr->request_)))) {
|
||||
MOZ_MTLOG(ML_ERROR, "AsyncResolve failed.");
|
||||
ABORT(R_NOT_FOUND);
|
||||
|
|
|
@ -122,6 +122,7 @@ int NrIceResolverFake::resolve(void *obj,
|
|||
resource->transport_protocol ?
|
||||
resource->transport_protocol :
|
||||
IPPROTO_UDP,
|
||||
resource->address_family,
|
||||
cb, cb_arg);
|
||||
|
||||
if ((r=NR_ASYNC_TIMER_SET(fake->delay_ms_,NrIceResolverFake::resolve_cb,
|
||||
|
@ -140,7 +141,8 @@ void NrIceResolverFake::resolve_cb(NR_SOCKET s, int how, void *cb_arg) {
|
|||
MOZ_ASSERT(cb_arg);
|
||||
PendingResolution *pending = static_cast<PendingResolution *>(cb_arg);
|
||||
|
||||
const PRNetAddr *addr=pending->resolver_->Resolve(pending->hostname_);
|
||||
const PRNetAddr *addr=pending->resolver_->Resolve(pending->hostname_,
|
||||
pending->address_family_);
|
||||
|
||||
if (addr) {
|
||||
nr_transport_addr transport_addr;
|
||||
|
|
|
@ -63,7 +63,16 @@ class NrIceResolverFake {
|
|||
~NrIceResolverFake();
|
||||
|
||||
void SetAddr(const std::string& hostname, const PRNetAddr& addr) {
|
||||
addrs_[hostname] = addr;
|
||||
switch (addr.raw.family) {
|
||||
case AF_INET:
|
||||
addrs_[hostname] = addr;
|
||||
break;
|
||||
case AF_INET6:
|
||||
addrs6_[hostname] = addr;
|
||||
break;
|
||||
default:
|
||||
MOZ_CRASH();
|
||||
}
|
||||
}
|
||||
|
||||
nr_resolver *AllocateResolver();
|
||||
|
@ -83,11 +92,21 @@ private:
|
|||
static int cancel(void *obj, void *handle);
|
||||
|
||||
// Get an address.
|
||||
const PRNetAddr *Resolve(const std::string& hostname) {
|
||||
if (!addrs_.count(hostname))
|
||||
return nullptr;
|
||||
const PRNetAddr *Resolve(const std::string& hostname, int address_family) {
|
||||
switch (address_family) {
|
||||
case AF_INET:
|
||||
if (!addrs_.count(hostname))
|
||||
return nullptr;
|
||||
|
||||
return &addrs_[hostname];
|
||||
return &addrs_[hostname];
|
||||
case AF_INET6:
|
||||
if (!addrs6_.count(hostname))
|
||||
return nullptr;
|
||||
|
||||
return &addrs6_[hostname];
|
||||
default:
|
||||
MOZ_CRASH();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -96,18 +115,21 @@ private:
|
|||
const std::string& hostname,
|
||||
uint16_t port,
|
||||
int transport,
|
||||
int address_family,
|
||||
int (*cb)(void *cb_arg, nr_transport_addr *addr),
|
||||
void *cb_arg) :
|
||||
resolver_(resolver),
|
||||
hostname_(hostname),
|
||||
port_(port),
|
||||
transport_(transport),
|
||||
address_family_(address_family),
|
||||
cb_(cb), cb_arg_(cb_arg) {}
|
||||
|
||||
NrIceResolverFake *resolver_;
|
||||
std::string hostname_;
|
||||
uint16_t port_;
|
||||
int transport_;
|
||||
int address_family_;
|
||||
int (*cb_)(void *cb_arg, nr_transport_addr *addr);
|
||||
void *cb_arg_;
|
||||
void *timer_handle_;
|
||||
|
@ -115,6 +137,7 @@ private:
|
|||
|
||||
nr_resolver_vtbl* vtbl_;
|
||||
std::map<std::string, PRNetAddr> addrs_;
|
||||
std::map<std::string, PRNetAddr> addrs6_;
|
||||
uint32_t delay_ms_;
|
||||
int allocated_resolvers_;
|
||||
};
|
||||
|
|
|
@ -18,7 +18,8 @@ public:
|
|||
: key_(),
|
||||
is_vpn_(-1),
|
||||
estimated_speed_(-1),
|
||||
type_preference_(-1) {}
|
||||
type_preference_(-1),
|
||||
ip_version_(-1) {}
|
||||
|
||||
bool Init(const nr_local_addr& local_addr) {
|
||||
char buf[MAXIFNAME + 41];
|
||||
|
@ -31,6 +32,7 @@ public:
|
|||
is_vpn_ = (local_addr.interface.type & NR_INTERFACE_TYPE_VPN) != 0 ? 1 : 0;
|
||||
estimated_speed_ = local_addr.interface.estimated_speed;
|
||||
type_preference_ = GetNetworkTypePreference(local_addr.interface.type);
|
||||
ip_version_ = local_addr.addr.ip_version;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -54,6 +56,11 @@ public:
|
|||
return estimated_speed_ > rhs.estimated_speed_;
|
||||
}
|
||||
|
||||
// Prefer IPV6 over IPV4
|
||||
if (ip_version_ != rhs.ip_version_) {
|
||||
return ip_version_ > rhs.ip_version_;
|
||||
}
|
||||
|
||||
// All things above are the same, we can at least sort with key.
|
||||
return key_ < rhs.key_;
|
||||
}
|
||||
|
@ -82,6 +89,7 @@ private:
|
|||
int is_vpn_;
|
||||
int estimated_speed_;
|
||||
int type_preference_;
|
||||
int ip_version_;
|
||||
};
|
||||
|
||||
class InterfacePrioritizer {
|
||||
|
|
|
@ -21,42 +21,51 @@ extern "C" {
|
|||
|
||||
namespace {
|
||||
|
||||
class NetAddressAdapter {
|
||||
class NetAddrCompare {
|
||||
public:
|
||||
MOZ_IMPLICIT NetAddressAdapter(const mozilla::net::NetAddr& netaddr)
|
||||
: addr_(ntohl(netaddr.inet.ip)),
|
||||
port_(ntohs(netaddr.inet.port)) {
|
||||
MOZ_ASSERT(netaddr.raw.family == AF_INET);
|
||||
}
|
||||
bool operator()(const mozilla::net::NetAddr& lhs,
|
||||
const mozilla::net::NetAddr& rhs) const {
|
||||
if (lhs.raw.family != rhs.raw.family) {
|
||||
return lhs.raw.family < rhs.raw.family;
|
||||
}
|
||||
|
||||
bool operator<(const NetAddressAdapter& rhs) const {
|
||||
return addr_ != rhs.addr_ ? (addr_ < rhs.addr_) : (port_ < rhs.port_);
|
||||
}
|
||||
|
||||
bool operator!=(const NetAddressAdapter& rhs) const {
|
||||
return (*this < rhs) || (rhs < *this);
|
||||
}
|
||||
|
||||
private:
|
||||
const uint32_t addr_;
|
||||
const uint16_t port_;
|
||||
switch (lhs.raw.family) {
|
||||
case AF_INET:
|
||||
if (lhs.inet.port != rhs.inet.port) {
|
||||
return lhs.inet.port < rhs.inet.port;
|
||||
}
|
||||
return lhs.inet.ip < rhs.inet.ip;
|
||||
case AF_INET6:
|
||||
if (lhs.inet6.port != rhs.inet6.port) {
|
||||
return lhs.inet6.port < rhs.inet6.port;
|
||||
}
|
||||
return memcmp(&lhs.inet6.ip, &rhs.inet6.ip, sizeof(lhs.inet6.ip)) < 0;
|
||||
default:
|
||||
MOZ_ASSERT(false);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
class PendingSTUNRequest {
|
||||
public:
|
||||
PendingSTUNRequest(const NetAddressAdapter& netaddr, const UINT12 &id)
|
||||
PendingSTUNRequest(const mozilla::net::NetAddr& netaddr, const UINT12 &id)
|
||||
: id_(id),
|
||||
net_addr_(netaddr),
|
||||
is_id_set_(true) {}
|
||||
|
||||
MOZ_IMPLICIT PendingSTUNRequest(const NetAddressAdapter& netaddr)
|
||||
MOZ_IMPLICIT PendingSTUNRequest(const mozilla::net::NetAddr& netaddr)
|
||||
: id_(),
|
||||
net_addr_(netaddr),
|
||||
is_id_set_(false) {}
|
||||
|
||||
bool operator<(const PendingSTUNRequest& rhs) const {
|
||||
if (net_addr_ != rhs.net_addr_) {
|
||||
return net_addr_ < rhs.net_addr_;
|
||||
if (NetAddrCompare()(net_addr_, rhs.net_addr_)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (NetAddrCompare()(rhs.net_addr_, net_addr_)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!is_id_set_ && !rhs.is_id_set_) {
|
||||
|
@ -76,7 +85,7 @@ class PendingSTUNRequest {
|
|||
|
||||
private:
|
||||
const UINT12 id_;
|
||||
const NetAddressAdapter net_addr_;
|
||||
const mozilla::net::NetAddr net_addr_;
|
||||
const bool is_id_set_;
|
||||
};
|
||||
|
||||
|
@ -101,7 +110,7 @@ class STUNUDPSocketFilter : public nsIUDPSocketFilter {
|
|||
const uint8_t *data,
|
||||
uint32_t len);
|
||||
|
||||
std::set<NetAddressAdapter> white_list_;
|
||||
std::set<mozilla::net::NetAddr, NetAddrCompare> white_list_;
|
||||
std::set<PendingSTUNRequest> pending_requests_;
|
||||
std::set<PendingSTUNRequest> response_allowed_;
|
||||
};
|
||||
|
@ -114,12 +123,6 @@ STUNUDPSocketFilter::FilterPacket(const mozilla::net::NetAddr *remote_addr,
|
|||
uint32_t len,
|
||||
int32_t direction,
|
||||
bool *result) {
|
||||
// Allowing IPv4 address only.
|
||||
if (remote_addr->raw.family != AF_INET) {
|
||||
*result = false;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
switch (direction) {
|
||||
case nsIUDPSocketFilter::SF_INCOMING:
|
||||
*result = filter_incoming_packet(remote_addr, data, len);
|
||||
|
|
|
@ -64,7 +64,7 @@ class BufferedStunSocketTest : public ::testing::Test {
|
|||
ASSERT_EQ(0, r);
|
||||
dummy_ = dummy.forget(); // Now owned by test_socket_.
|
||||
|
||||
r = nr_ip4_str_port_to_transport_addr(
|
||||
r = nr_str_port_to_transport_addr(
|
||||
(char *)"192.0.2.133", 3333, IPPROTO_TCP, &remote_addr_);
|
||||
ASSERT_EQ(0, r);
|
||||
|
||||
|
@ -184,7 +184,7 @@ TEST_F(BufferedStunSocketTest, TestSendToReject) {
|
|||
TEST_F(BufferedStunSocketTest, TestSendToWrongAddr) {
|
||||
nr_transport_addr addr;
|
||||
|
||||
int r = nr_ip4_str_port_to_transport_addr(
|
||||
int r = nr_str_port_to_transport_addr(
|
||||
(char *)"192.0.2.134", 3333, IPPROTO_TCP, &addr);
|
||||
ASSERT_EQ(0, r);
|
||||
|
||||
|
|
|
@ -354,8 +354,8 @@ class IceTestPeer : public sigslot::has_slots<> {
|
|||
}
|
||||
|
||||
void UseTestStunServer() {
|
||||
SetStunServer(TestStunServer::GetInstance()->addr(),
|
||||
TestStunServer::GetInstance()->port());
|
||||
SetStunServer(TestStunServer::GetInstance(AF_INET)->addr(),
|
||||
TestStunServer::GetInstance(AF_INET)->port());
|
||||
}
|
||||
|
||||
void SetTurnServer(const std::string addr, uint16_t port,
|
||||
|
@ -382,15 +382,15 @@ class IceTestPeer : public sigslot::has_slots<> {
|
|||
ASSERT_TRUE(NS_SUCCEEDED(ice_ctx_->SetTurnServers(servers)));
|
||||
}
|
||||
|
||||
void SetFakeResolver() {
|
||||
void SetFakeResolver(const std::string& ip = g_stun_server_address,
|
||||
const std::string& fqdn = g_stun_server_hostname) {
|
||||
ASSERT_TRUE(NS_SUCCEEDED(dns_resolver_->Init()));
|
||||
if (!g_stun_server_address.empty() && !g_stun_server_hostname.empty()) {
|
||||
if (!ip.empty() && !fqdn.empty()) {
|
||||
PRNetAddr addr;
|
||||
PRStatus status = PR_StringToNetAddr(g_stun_server_address.c_str(),
|
||||
&addr);
|
||||
PRStatus status = PR_StringToNetAddr(ip.c_str(), &addr);
|
||||
addr.inet.port = kDefaultStunServerPort;
|
||||
ASSERT_EQ(PR_SUCCESS, status);
|
||||
fake_resolver_.SetAddr(g_stun_server_hostname, addr);
|
||||
fake_resolver_.SetAddr(fqdn, addr);
|
||||
}
|
||||
ASSERT_TRUE(NS_SUCCEEDED(ice_ctx_->SetResolver(
|
||||
fake_resolver_.AllocateResolver())));
|
||||
|
@ -1098,9 +1098,16 @@ void SchedulableTrickleCandidate::Trickle() {
|
|||
class IceGatherTest : public ::testing::Test {
|
||||
public:
|
||||
void SetUp() {
|
||||
test_utils->sts_target()->Dispatch(WrapRunnable(TestStunServer::GetInstance(),
|
||||
&TestStunServer::Reset),
|
||||
NS_DISPATCH_SYNC);
|
||||
test_utils->sts_target()->Dispatch(
|
||||
WrapRunnable(TestStunServer::GetInstance(AF_INET),
|
||||
&TestStunServer::Reset),
|
||||
NS_DISPATCH_SYNC);
|
||||
if (TestStunServer::GetInstance(AF_INET6)) {
|
||||
test_utils->sts_target()->Dispatch(
|
||||
WrapRunnable(TestStunServer::GetInstance(AF_INET6),
|
||||
&TestStunServer::Reset),
|
||||
NS_DISPATCH_SYNC);
|
||||
}
|
||||
}
|
||||
|
||||
void TearDown() {
|
||||
|
@ -1135,54 +1142,90 @@ class IceGatherTest : public ::testing::Test {
|
|||
ASSERT_TRUE_WAIT(peer_->gathering_complete(), waitTime);
|
||||
}
|
||||
|
||||
void UseFakeStunUdpServerWithResponse(const std::string& fake_addr,
|
||||
uint16_t fake_port) {
|
||||
EnsurePeer();
|
||||
TestStunServer::GetInstance()->SetResponseAddr(fake_addr, fake_port);
|
||||
// Sets an additional stun server
|
||||
peer_->SetStunServer(TestStunServer::GetInstance()->addr(),
|
||||
TestStunServer::GetInstance()->port(),
|
||||
kNrIceTransportUdp);
|
||||
void AddStunServerWithResponse(
|
||||
const std::string& fake_addr,
|
||||
uint16_t fake_port,
|
||||
const std::string& fqdn,
|
||||
const std::string& proto,
|
||||
std::vector<NrIceStunServer>* stun_servers) {
|
||||
int family;
|
||||
if (fake_addr.find(':') != std::string::npos) {
|
||||
family = AF_INET6;
|
||||
} else {
|
||||
family = AF_INET;
|
||||
}
|
||||
|
||||
std::string stun_addr;
|
||||
uint16_t stun_port;
|
||||
if (proto == kNrIceTransportUdp) {
|
||||
TestStunServer::GetInstance(family)->SetResponseAddr(fake_addr,
|
||||
fake_port);
|
||||
stun_addr = TestStunServer::GetInstance(family)->addr();
|
||||
stun_port = TestStunServer::GetInstance(family)->port();
|
||||
} else if (proto == kNrIceTransportTcp) {
|
||||
TestStunTcpServer::GetInstance(family)->SetResponseAddr(fake_addr,
|
||||
fake_port);
|
||||
stun_addr = TestStunTcpServer::GetInstance(family)->addr();
|
||||
stun_port = TestStunTcpServer::GetInstance(family)->port();
|
||||
} else {
|
||||
MOZ_CRASH();
|
||||
}
|
||||
|
||||
if (!fqdn.empty()) {
|
||||
peer_->SetFakeResolver(stun_addr, fqdn);
|
||||
stun_addr = fqdn;
|
||||
}
|
||||
|
||||
stun_servers->push_back(*NrIceStunServer::Create(stun_addr,
|
||||
stun_port,
|
||||
proto.c_str()));
|
||||
}
|
||||
|
||||
void UseFakeStunTcpServerWithResponse(const std::string& fake_addr,
|
||||
uint16_t fake_port) {
|
||||
EnsurePeer();
|
||||
TestStunTcpServer::GetInstance()->SetResponseAddr(fake_addr, fake_port);
|
||||
// Sets an additional stun server
|
||||
peer_->SetStunServer(TestStunTcpServer::GetInstance()->addr(),
|
||||
TestStunTcpServer::GetInstance()->port(),
|
||||
kNrIceTransportTcp);
|
||||
}
|
||||
|
||||
void UseFakeStunUdpTcpServersWithResponse(const std::string& fake_udp_addr,
|
||||
uint16_t fake_udp_port,
|
||||
const std::string& fake_tcp_addr,
|
||||
uint16_t fake_tcp_port) {
|
||||
void UseFakeStunUdpServerWithResponse(
|
||||
const std::string& fake_addr,
|
||||
uint16_t fake_port,
|
||||
const std::string& fqdn = std::string()) {
|
||||
EnsurePeer();
|
||||
std::vector<NrIceStunServer> stun_servers;
|
||||
AddStunServerWithResponse(fake_addr, fake_port, fqdn, "udp", &stun_servers);
|
||||
peer_->SetStunServers(stun_servers);
|
||||
}
|
||||
|
||||
stun_servers.push_back(*NrIceStunServer::Create(
|
||||
TestStunServer::GetInstance()->addr(),
|
||||
TestStunServer::GetInstance()->port(),
|
||||
kNrIceTransportUdp));
|
||||
stun_servers.push_back(*NrIceStunServer::Create(
|
||||
TestStunTcpServer::GetInstance()->addr(),
|
||||
TestStunTcpServer::GetInstance()->port(),
|
||||
kNrIceTransportTcp));
|
||||
void UseFakeStunTcpServerWithResponse(
|
||||
const std::string& fake_addr,
|
||||
uint16_t fake_port,
|
||||
const std::string& fqdn = std::string()) {
|
||||
EnsurePeer();
|
||||
std::vector<NrIceStunServer> stun_servers;
|
||||
AddStunServerWithResponse(fake_addr, fake_port, fqdn, "tcp", &stun_servers);
|
||||
peer_->SetStunServers(stun_servers);
|
||||
}
|
||||
|
||||
void UseFakeStunUdpTcpServersWithResponse(
|
||||
const std::string& fake_udp_addr,
|
||||
uint16_t fake_udp_port,
|
||||
const std::string& fake_tcp_addr,
|
||||
uint16_t fake_tcp_port) {
|
||||
EnsurePeer();
|
||||
std::vector<NrIceStunServer> stun_servers;
|
||||
AddStunServerWithResponse(fake_udp_addr,
|
||||
fake_udp_port,
|
||||
"", // no fqdn
|
||||
"udp",
|
||||
&stun_servers);
|
||||
AddStunServerWithResponse(fake_tcp_addr,
|
||||
fake_tcp_port,
|
||||
"", // no fqdn
|
||||
"tcp",
|
||||
&stun_servers);
|
||||
|
||||
TestStunServer::GetInstance()->SetResponseAddr(fake_udp_addr,
|
||||
fake_udp_port);
|
||||
TestStunTcpServer::GetInstance()->SetResponseAddr(fake_tcp_addr,
|
||||
fake_tcp_port);
|
||||
// Sets an additional stun server
|
||||
peer_->SetStunServers(stun_servers);
|
||||
}
|
||||
|
||||
void UseTestStunServer() {
|
||||
TestStunServer::GetInstance()->Reset();
|
||||
peer_->SetStunServer(TestStunServer::GetInstance()->addr(),
|
||||
TestStunServer::GetInstance()->port());
|
||||
TestStunServer::GetInstance(AF_INET)->Reset();
|
||||
peer_->SetStunServer(TestStunServer::GetInstance(AF_INET)->addr(),
|
||||
TestStunServer::GetInstance(AF_INET)->port());
|
||||
}
|
||||
|
||||
// NB: Only does substring matching, watch out for stuff like "1.2.3.4"
|
||||
|
@ -1314,7 +1357,7 @@ class IceConnectTest : public ::testing::Test {
|
|||
}
|
||||
|
||||
void UseTestStunServer() {
|
||||
TestStunServer::GetInstance()->Reset();
|
||||
TestStunServer::GetInstance(AF_INET)->Reset();
|
||||
p1_->UseTestStunServer();
|
||||
p2_->UseTestStunServer();
|
||||
}
|
||||
|
@ -1482,8 +1525,8 @@ class PrioritizerTest : public ::testing::Test {
|
|||
local_addr.interface.type = type;
|
||||
local_addr.interface.estimated_speed = estimated_speed;
|
||||
|
||||
int r = nr_ip4_str_port_to_transport_addr(str_addr.c_str(), 0,
|
||||
IPPROTO_UDP, &(local_addr.addr));
|
||||
int r = nr_str_port_to_transport_addr(str_addr.c_str(), 0,
|
||||
IPPROTO_UDP, &(local_addr.addr));
|
||||
ASSERT_EQ(0, r);
|
||||
strncpy(local_addr.addr.ifname, ifname.c_str(), MAXIFNAME);
|
||||
|
||||
|
@ -1791,12 +1834,48 @@ TEST_F(IceGatherTest, VerifyTestStunTcpServer) {
|
|||
" tcptype "));
|
||||
}
|
||||
|
||||
TEST_F(IceGatherTest, VerifyTestStunServerV6) {
|
||||
if (!TestStunServer::GetInstance(AF_INET6)) {
|
||||
// No V6 addresses
|
||||
return;
|
||||
}
|
||||
UseFakeStunUdpServerWithResponse("beef::", 3333);
|
||||
Gather();
|
||||
ASSERT_TRUE(StreamHasMatchingCandidate(0, " beef:: 3333 "));
|
||||
}
|
||||
|
||||
TEST_F(IceGatherTest, VerifyTestStunServerFQDN) {
|
||||
UseFakeStunUdpServerWithResponse("192.0.2.133", 3333, "stun.example.com");
|
||||
Gather();
|
||||
ASSERT_TRUE(StreamHasMatchingCandidate(0, " 192.0.2.133 3333 "));
|
||||
}
|
||||
|
||||
TEST_F(IceGatherTest, VerifyTestStunServerV6FQDN) {
|
||||
if (!TestStunServer::GetInstance(AF_INET6)) {
|
||||
// No V6 addresses
|
||||
return;
|
||||
}
|
||||
UseFakeStunUdpServerWithResponse("beef::", 3333, "stun.example.com");
|
||||
Gather();
|
||||
ASSERT_TRUE(StreamHasMatchingCandidate(0, " beef:: 3333 "));
|
||||
}
|
||||
|
||||
TEST_F(IceGatherTest, TestStunServerReturnsWildcardAddr) {
|
||||
UseFakeStunUdpServerWithResponse("0.0.0.0", 3333);
|
||||
Gather(kDefaultTimeout * 3);
|
||||
ASSERT_FALSE(StreamHasMatchingCandidate(0, " 0.0.0.0 "));
|
||||
}
|
||||
|
||||
TEST_F(IceGatherTest, TestStunServerReturnsWildcardAddrV6) {
|
||||
if (!TestStunServer::GetInstance(AF_INET6)) {
|
||||
// No V6 addresses
|
||||
return;
|
||||
}
|
||||
UseFakeStunUdpServerWithResponse("::", 3333);
|
||||
Gather(kDefaultTimeout * 3);
|
||||
ASSERT_FALSE(StreamHasMatchingCandidate(0, " :: "));
|
||||
}
|
||||
|
||||
TEST_F(IceGatherTest, TestStunServerReturnsPort0) {
|
||||
UseFakeStunUdpServerWithResponse("192.0.2.133", 0);
|
||||
Gather(kDefaultTimeout * 3);
|
||||
|
@ -1809,35 +1888,45 @@ TEST_F(IceGatherTest, TestStunServerReturnsLoopbackAddr) {
|
|||
ASSERT_FALSE(StreamHasMatchingCandidate(0, " 127.0.0.133 "));
|
||||
}
|
||||
|
||||
TEST_F(IceGatherTest, TestStunServerReturnsLoopbackAddrV6) {
|
||||
if (!TestStunServer::GetInstance(AF_INET6)) {
|
||||
// No V6 addresses
|
||||
return;
|
||||
}
|
||||
UseFakeStunUdpServerWithResponse("::1", 3333);
|
||||
Gather(kDefaultTimeout * 3);
|
||||
ASSERT_FALSE(StreamHasMatchingCandidate(0, " ::1 "));
|
||||
}
|
||||
|
||||
TEST_F(IceGatherTest, TestStunServerTrickle) {
|
||||
UseFakeStunUdpServerWithResponse("192.0.2.1", 3333);
|
||||
TestStunServer::GetInstance()->SetActive(false);
|
||||
TestStunServer::GetInstance(AF_INET)->SetActive(false);
|
||||
Gather(0);
|
||||
ASSERT_FALSE(StreamHasMatchingCandidate(0, "192.0.2.1"));
|
||||
TestStunServer::GetInstance()->SetActive(true);
|
||||
TestStunServer::GetInstance(AF_INET)->SetActive(true);
|
||||
WaitForGather();
|
||||
ASSERT_TRUE(StreamHasMatchingCandidate(0, "192.0.2.1"));
|
||||
}
|
||||
|
||||
TEST_F(IceGatherTest, TestStunTcpServerTrickle) {
|
||||
UseFakeStunTcpServerWithResponse("192.0.3.1", 3333);
|
||||
TestStunTcpServer::GetInstance()->SetActive(false);
|
||||
TestStunTcpServer::GetInstance(AF_INET)->SetActive(false);
|
||||
Gather(0);
|
||||
ASSERT_FALSE(StreamHasMatchingCandidate(0, " 192.0.3.1 ", " tcptype "));
|
||||
TestStunTcpServer::GetInstance()->SetActive(true);
|
||||
TestStunTcpServer::GetInstance(AF_INET)->SetActive(true);
|
||||
WaitForGather();
|
||||
ASSERT_TRUE(StreamHasMatchingCandidate(0, " 192.0.3.1 ", " tcptype "));
|
||||
}
|
||||
|
||||
TEST_F(IceGatherTest, TestStunTcpAndUdpServerTrickle) {
|
||||
UseFakeStunUdpTcpServersWithResponse("192.0.2.1", 3333, "192.0.3.1", 3333);
|
||||
TestStunServer::GetInstance()->SetActive(false);
|
||||
TestStunTcpServer::GetInstance()->SetActive(false);
|
||||
TestStunServer::GetInstance(AF_INET)->SetActive(false);
|
||||
TestStunTcpServer::GetInstance(AF_INET)->SetActive(false);
|
||||
Gather(0);
|
||||
ASSERT_FALSE(StreamHasMatchingCandidate(0, "192.0.2.1", "UDP"));
|
||||
ASSERT_FALSE(StreamHasMatchingCandidate(0, " 192.0.3.1 ", " tcptype "));
|
||||
TestStunServer::GetInstance()->SetActive(true);
|
||||
TestStunTcpServer::GetInstance()->SetActive(true);
|
||||
TestStunServer::GetInstance(AF_INET)->SetActive(true);
|
||||
TestStunTcpServer::GetInstance(AF_INET)->SetActive(true);
|
||||
WaitForGather();
|
||||
ASSERT_TRUE(StreamHasMatchingCandidate(0, "192.0.2.1", "UDP"));
|
||||
ASSERT_TRUE(StreamHasMatchingCandidate(0, " 192.0.3.1 ", " tcptype "));
|
||||
|
@ -2863,10 +2952,18 @@ int main(int argc, char **argv)
|
|||
|
||||
listeners.Append(new test::RingbufferDumper(test_utils));
|
||||
test_utils->sts_target()->Dispatch(
|
||||
WrapRunnableNM(&TestStunServer::GetInstance), NS_DISPATCH_SYNC);
|
||||
WrapRunnableNM(&TestStunServer::GetInstance, AF_INET),
|
||||
NS_DISPATCH_SYNC);
|
||||
test_utils->sts_target()->Dispatch(
|
||||
WrapRunnableNM(&TestStunServer::GetInstance, AF_INET6),
|
||||
NS_DISPATCH_SYNC);
|
||||
|
||||
test_utils->sts_target()->Dispatch(
|
||||
WrapRunnableNM(&TestStunTcpServer::GetInstance), NS_DISPATCH_SYNC);
|
||||
WrapRunnableNM(&TestStunTcpServer::GetInstance, AF_INET),
|
||||
NS_DISPATCH_SYNC);
|
||||
test_utils->sts_target()->Dispatch(
|
||||
WrapRunnableNM(&TestStunTcpServer::GetInstance, AF_INET6),
|
||||
NS_DISPATCH_SYNC);
|
||||
|
||||
int rv = RUN_ALL_TESTS();
|
||||
|
||||
|
|
|
@ -112,7 +112,7 @@ class MultiTcpSocketTest : public ::testing::Test {
|
|||
|
||||
r = 1;
|
||||
for (int tries=10; tries && r; --tries) {
|
||||
r = nr_ip4_str_port_to_transport_addr(
|
||||
r = nr_str_port_to_transport_addr(
|
||||
(char *)"127.0.0.1", EnsureEphemeral(port_s++), IPPROTO_TCP, &local);
|
||||
ASSERT_EQ(0, r);
|
||||
|
||||
|
|
|
@ -84,7 +84,7 @@ class DummyResolver {
|
|||
void **handle) {
|
||||
nr_transport_addr addr;
|
||||
|
||||
nr_ip4_str_port_to_transport_addr(
|
||||
nr_str_port_to_transport_addr(
|
||||
(char *)kProxyAddr.c_str(), kProxyPort, IPPROTO_TCP, &addr);
|
||||
|
||||
cb(cb_arg, &addr);
|
||||
|
@ -120,11 +120,11 @@ class ProxyTunnelSocketTest : public ::testing::Test {
|
|||
|
||||
nr_resolver_ = resolver_impl_.get_nr_resolver();
|
||||
|
||||
int r = nr_ip4_str_port_to_transport_addr(
|
||||
int r = nr_str_port_to_transport_addr(
|
||||
(char *)kRemoteAddr.c_str(), kRemotePort, IPPROTO_TCP, &remote_addr_);
|
||||
ASSERT_EQ(0, r);
|
||||
|
||||
r = nr_ip4_str_port_to_transport_addr(
|
||||
r = nr_str_port_to_transport_addr(
|
||||
(char *)kProxyAddr.c_str(), kProxyPort, IPPROTO_TCP, &proxy_addr_);
|
||||
ASSERT_EQ(0, r);
|
||||
|
||||
|
|
|
@ -188,6 +188,8 @@ int nr_socket_wrapped_create(nr_socket *inner, nr_socket **outp) {
|
|||
// we have no reason to expect this will be initted to a nullptr yet.
|
||||
TestStunServer* TestStunServer::instance;
|
||||
TestStunTcpServer* TestStunTcpServer::instance;
|
||||
TestStunServer* TestStunServer::instance6;
|
||||
TestStunTcpServer* TestStunTcpServer::instance6;
|
||||
uint16_t TestStunServer::instance_port = 3478;
|
||||
uint16_t TestStunTcpServer::instance_port = 3478;
|
||||
|
||||
|
@ -243,12 +245,14 @@ int TestStunServer::TryOpenListenSocket(nr_local_addr* addr, uint16_t port) {
|
|||
return 0;
|
||||
}
|
||||
|
||||
int TestStunServer::Initialize() {
|
||||
nr_local_addr addrs[100];
|
||||
int TestStunServer::Initialize(int address_family) {
|
||||
static const size_t max_addrs = 100;
|
||||
nr_local_addr addrs[max_addrs];
|
||||
int addr_ct;
|
||||
int r;
|
||||
int i;
|
||||
|
||||
r = nr_stun_find_local_addresses(addrs, 100, &addr_ct);
|
||||
r = nr_stun_find_local_addresses(addrs, max_addrs, &addr_ct);
|
||||
if (r) {
|
||||
MOZ_MTLOG(ML_ERROR, "Couldn't retrieve addresses");
|
||||
return R_INTERNAL;
|
||||
|
@ -259,10 +263,21 @@ int TestStunServer::Initialize() {
|
|||
return R_INTERNAL;
|
||||
}
|
||||
|
||||
for (i = 0; i < addr_ct; ++i) {
|
||||
if (addrs[i].addr.addr->sa_family == address_family) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (i == addr_ct) {
|
||||
MOZ_MTLOG(ML_ERROR, "No local addresses of the configured IP version");
|
||||
return R_INTERNAL;
|
||||
}
|
||||
|
||||
int tries = 100;
|
||||
while (tries--) {
|
||||
// Bind to the first address (arbitrarily) on configured port (default 3478)
|
||||
r = TryOpenListenSocket(&addrs[0], instance_port);
|
||||
// Bind on configured port (default 3478)
|
||||
r = TryOpenListenSocket(&addrs[i], instance_port);
|
||||
// We interpret R_ALREADY to mean the addr is probably in use. Try another.
|
||||
// Otherwise, it either worked or it didn't, and we check below.
|
||||
if (r != R_ALREADY) {
|
||||
|
@ -291,7 +306,7 @@ int TestStunServer::Initialize() {
|
|||
|
||||
// Cache the address and port.
|
||||
char addr_string[INET6_ADDRSTRLEN];
|
||||
r = nr_transport_addr_get_addrstring(&addrs[0].addr, addr_string,
|
||||
r = nr_transport_addr_get_addrstring(&addrs[i].addr, addr_string,
|
||||
sizeof(addr_string));
|
||||
if (r) {
|
||||
MOZ_MTLOG(ML_ERROR, "Failed to convert listen addr to a string representation");
|
||||
|
@ -304,12 +319,12 @@ int TestStunServer::Initialize() {
|
|||
return 0;
|
||||
}
|
||||
|
||||
TestStunServer* TestStunServer::Create() {
|
||||
TestStunServer* TestStunServer::Create(int address_family) {
|
||||
NR_reg_init(NR_REG_MODE_LOCAL);
|
||||
|
||||
ScopedDeletePtr<TestStunServer> server(new TestStunServer());
|
||||
|
||||
if (server->Initialize())
|
||||
if (server->Initialize(address_family))
|
||||
return nullptr;
|
||||
|
||||
NR_SOCKET fd;
|
||||
|
@ -328,18 +343,29 @@ void TestStunServer::ConfigurePort(uint16_t port) {
|
|||
instance_port = port;
|
||||
}
|
||||
|
||||
TestStunServer* TestStunServer::GetInstance() {
|
||||
if (!instance)
|
||||
instance = Create();
|
||||
TestStunServer* TestStunServer::GetInstance(int address_family) {
|
||||
switch (address_family) {
|
||||
case AF_INET:
|
||||
if (!instance)
|
||||
instance = Create(address_family);
|
||||
|
||||
MOZ_ASSERT(instance);
|
||||
return instance;
|
||||
MOZ_ASSERT(instance);
|
||||
return instance;
|
||||
case AF_INET6:
|
||||
if (!instance6)
|
||||
instance6 = Create(address_family);
|
||||
|
||||
return instance6;
|
||||
default:
|
||||
MOZ_CRASH();
|
||||
}
|
||||
}
|
||||
|
||||
void TestStunServer::ShutdownInstance() {
|
||||
delete instance;
|
||||
|
||||
instance = nullptr;
|
||||
delete instance6;
|
||||
instance6 = nullptr;
|
||||
}
|
||||
|
||||
|
||||
|
@ -453,9 +479,9 @@ nsresult TestStunServer::SetResponseAddr(const std::string& addr,
|
|||
uint16_t port) {
|
||||
nr_transport_addr addr2;
|
||||
|
||||
int r = nr_ip4_str_port_to_transport_addr(addr.c_str(),
|
||||
port, IPPROTO_UDP,
|
||||
&addr2);
|
||||
int r = nr_str_port_to_transport_addr(addr.c_str(),
|
||||
port, IPPROTO_UDP,
|
||||
&addr2);
|
||||
if (r)
|
||||
return NS_ERROR_FAILURE;
|
||||
|
||||
|
@ -479,12 +505,22 @@ void TestStunTcpServer::ConfigurePort(uint16_t port) {
|
|||
instance_port = port;
|
||||
}
|
||||
|
||||
TestStunTcpServer* TestStunTcpServer::GetInstance() {
|
||||
if (!instance)
|
||||
instance = Create();
|
||||
TestStunTcpServer* TestStunTcpServer::GetInstance(int address_family) {
|
||||
switch (address_family) {
|
||||
case AF_INET:
|
||||
if (!instance)
|
||||
instance = Create(address_family);
|
||||
|
||||
MOZ_ASSERT(instance);
|
||||
return instance;
|
||||
MOZ_ASSERT(instance);
|
||||
return instance;
|
||||
case AF_INET6:
|
||||
if (!instance6)
|
||||
instance6 = Create(address_family);
|
||||
|
||||
return instance6;
|
||||
default:
|
||||
MOZ_CRASH();
|
||||
}
|
||||
}
|
||||
|
||||
void TestStunTcpServer::ShutdownInstance() {
|
||||
|
@ -523,12 +559,14 @@ int TestStunTcpServer::TryOpenListenSocket(nr_local_addr* addr, uint16_t port) {
|
|||
return 0;
|
||||
}
|
||||
|
||||
TestStunTcpServer* TestStunTcpServer::Create() {
|
||||
TestStunTcpServer* TestStunTcpServer::Create(int address_family) {
|
||||
NR_reg_init(NR_REG_MODE_LOCAL);
|
||||
|
||||
ScopedDeletePtr<TestStunTcpServer> server(new TestStunTcpServer());
|
||||
|
||||
server->Initialize();
|
||||
if (server->Initialize(address_family)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
nr_socket_multi_tcp_set_readable_cb(server->listen_sock_,
|
||||
&TestStunServer::readable_cb, server.get());
|
||||
|
|
|
@ -24,12 +24,13 @@ class TestStunServer {
|
|||
public:
|
||||
// Generally, you should only call API in this class from the same thread that
|
||||
// the initial |GetInstance| call was made from.
|
||||
static TestStunServer *GetInstance();
|
||||
static TestStunServer *GetInstance(int address_family = AF_INET);
|
||||
static void ShutdownInstance();
|
||||
// |ConfigurePort| will only have an effect if called before the first call
|
||||
// to |GetInstance| (possibly following a |ShutdownInstance| call)
|
||||
static void ConfigurePort(uint16_t port);
|
||||
static TestStunServer *Create();
|
||||
// AF_INET, AF_INET6
|
||||
static TestStunServer *Create(int address_family);
|
||||
|
||||
virtual ~TestStunServer();
|
||||
|
||||
|
@ -59,7 +60,7 @@ class TestStunServer {
|
|||
timer_handle_(nullptr) {}
|
||||
|
||||
int SetInternalPort(nr_local_addr* addr, uint16_t port);
|
||||
int Initialize();
|
||||
int Initialize(int address_family);
|
||||
static void readable_cb(NR_SOCKET sock, int how, void *cb_arg);
|
||||
|
||||
private:
|
||||
|
@ -82,12 +83,13 @@ class TestStunServer {
|
|||
std::map<std::string, uint32_t> received_ct_;
|
||||
|
||||
static TestStunServer* instance;
|
||||
static TestStunServer* instance6;
|
||||
static uint16_t instance_port;
|
||||
};
|
||||
|
||||
class TestStunTcpServer: public TestStunServer {
|
||||
public:
|
||||
static TestStunTcpServer *GetInstance();
|
||||
static TestStunTcpServer *GetInstance(int address_family);
|
||||
static void ShutdownInstance();
|
||||
static void ConfigurePort(uint16_t port);
|
||||
virtual ~TestStunTcpServer();
|
||||
|
@ -98,9 +100,10 @@ class TestStunTcpServer: public TestStunServer {
|
|||
nsRefPtr<NrIceCtx> ice_ctx_;
|
||||
private:
|
||||
virtual int TryOpenListenSocket(nr_local_addr* addr, uint16_t port);
|
||||
static TestStunTcpServer *Create();
|
||||
static TestStunTcpServer *Create(int address_family);
|
||||
|
||||
static TestStunTcpServer* instance;
|
||||
static TestStunTcpServer* instance6;
|
||||
static uint16_t instance_port;
|
||||
};
|
||||
} // End of namespace mozilla
|
||||
|
|
|
@ -67,7 +67,7 @@ class TestNrSocketTest : public ::testing::Test {
|
|||
// is how we simulate a non-natted socket.
|
||||
nsRefPtr<TestNrSocket> sock(new TestNrSocket(nat ? nat : new TestNat));
|
||||
nr_transport_addr address;
|
||||
nr_ip4_str_port_to_transport_addr(ip_str, 0, IPPROTO_UDP, &address);
|
||||
nr_str_port_to_transport_addr(ip_str, 0, IPPROTO_UDP, &address);
|
||||
int r = sock->create(&address);
|
||||
if (r) {
|
||||
return nullptr;
|
||||
|
|
|
@ -132,7 +132,7 @@ class TurnClient : public ::testing::Test {
|
|||
net_socket_ = real_socket_;
|
||||
}
|
||||
|
||||
r = nr_ip4_str_port_to_transport_addr(turn_server_.c_str(), 3478,
|
||||
r = nr_str_port_to_transport_addr(turn_server_.c_str(), 3478,
|
||||
protocol_, &addr);
|
||||
ASSERT_EQ(0, r);
|
||||
|
||||
|
@ -237,10 +237,10 @@ class TurnClient : public ::testing::Test {
|
|||
std::string host = target.substr(4, offset - 4);
|
||||
std::string port = target.substr(offset + 1);
|
||||
|
||||
r = nr_ip4_str_port_to_transport_addr(host.c_str(),
|
||||
atoi(port.c_str()),
|
||||
IPPROTO_UDP,
|
||||
&addr);
|
||||
r = nr_str_port_to_transport_addr(host.c_str(),
|
||||
atoi(port.c_str()),
|
||||
IPPROTO_UDP,
|
||||
&addr);
|
||||
ASSERT_EQ(0, r);
|
||||
|
||||
r = nr_turn_client_ensure_perm(turn_ctx_, &addr);
|
||||
|
@ -324,10 +324,10 @@ class TurnClient : public ::testing::Test {
|
|||
std::string host = target.substr(4, offset - 4);
|
||||
std::string port = target.substr(offset + 1);
|
||||
|
||||
r = nr_ip4_str_port_to_transport_addr(host.c_str(),
|
||||
atoi(port.c_str()),
|
||||
IPPROTO_UDP,
|
||||
&addr);
|
||||
r = nr_str_port_to_transport_addr(host.c_str(),
|
||||
atoi(port.c_str()),
|
||||
IPPROTO_UDP,
|
||||
&addr);
|
||||
ASSERT_EQ(0, r);
|
||||
|
||||
unsigned char test[100];
|
||||
|
@ -488,8 +488,8 @@ int main(int argc, char **argv)
|
|||
}
|
||||
{
|
||||
nr_transport_addr addr;
|
||||
if (nr_ip4_str_port_to_transport_addr(g_turn_server.c_str(), 3478,
|
||||
IPPROTO_UDP, &addr)) {
|
||||
if (nr_str_port_to_transport_addr(g_turn_server.c_str(), 3478,
|
||||
IPPROTO_UDP, &addr)) {
|
||||
printf("Invalid TURN_SERVER_ADDRESS \"%s\". Only IP numbers supported.\n",
|
||||
g_turn_server.c_str());
|
||||
return 0;
|
||||
|
@ -504,7 +504,7 @@ int main(int argc, char **argv)
|
|||
std::string dummy("dummy");
|
||||
RUN_ON_THREAD(test_utils->sts_target(),
|
||||
WrapRunnableNM(&NrIceCtx::Create,
|
||||
dummy, false, false, false, false),
|
||||
dummy, false, false, false, false, false),
|
||||
NS_DISPATCH_SYNC);
|
||||
|
||||
// Start the tests
|
||||
|
|
|
@ -37,6 +37,8 @@
|
|||
# STUN
|
||||
./src/stun/addrs.c
|
||||
./src/stun/addrs.h
|
||||
./src/stun/ifaddrs-android.c
|
||||
./src/stun/ifaddrs-android.h
|
||||
./src/stun/nr_socket_turn.c
|
||||
./src/stun/nr_socket_turn.h
|
||||
./src/stun/stun.h
|
||||
|
|
|
@ -88,6 +88,8 @@
|
|||
# STUN
|
||||
"./src/stun/addrs.c",
|
||||
"./src/stun/addrs.h",
|
||||
"./src/stun/ifaddrs-android.c",
|
||||
"./src/stun/ifaddrs-android.h",
|
||||
"./src/stun/nr_socket_turn.c",
|
||||
"./src/stun/nr_socket_turn.h",
|
||||
"./src/stun/nr_socket_buffered_stun.c",
|
||||
|
|
|
@ -64,7 +64,7 @@ static char *RCSSTRING __UNUSED__="$Id: ice_candidate.c,v 1.2 2008/04/28 17:59:0
|
|||
#include "nr_socket.h"
|
||||
#include "nr_socket_multi_tcp.h"
|
||||
|
||||
static int next_automatic_preference = 224;
|
||||
static int next_automatic_preference = 127;
|
||||
|
||||
static int nr_ice_candidate_initialize2(nr_ice_candidate *cand);
|
||||
static int nr_ice_get_foundation(nr_ice_ctx *ctx,nr_ice_candidate *cand);
|
||||
|
@ -470,8 +470,12 @@ int nr_ice_candidate_compute_priority(nr_ice_candidate *cand)
|
|||
if (r=NR_reg_set2_uchar(NR_ICE_REG_PREF_INTERFACE_PRFX,cand->base.ifname,next_automatic_preference)){
|
||||
ABORT(r);
|
||||
}
|
||||
interface_preference=next_automatic_preference;
|
||||
interface_preference=next_automatic_preference << 1;
|
||||
next_automatic_preference--;
|
||||
if (cand->base.ip_version == NR_IPV6) {
|
||||
/* Prefer IPV6 over IPV4 on the same interface. */
|
||||
interface_preference += 1;
|
||||
}
|
||||
}
|
||||
else {
|
||||
ABORT(r);
|
||||
|
@ -550,6 +554,11 @@ int nr_ice_candidate_initialize(nr_ice_candidate *cand, NR_async_cb ready_cb, vo
|
|||
cand->state=NR_ICE_CAND_STATE_INITIALIZING;
|
||||
|
||||
if(cand->stun_server->type == NR_ICE_STUN_SERVER_TYPE_ADDR) {
|
||||
if(cand->base.ip_version != cand->stun_server->u.addr.ip_version) {
|
||||
r_log(LOG_ICE, LOG_INFO, "ICE-CANDIDATE(%s): Skipping srflx/relayed candidate with different IP version (%d) than STUN/TURN server (%d).", cand->label,cand->base.ip_version,cand->stun_server->u.addr.ip_version);
|
||||
ABORT(R_NOT_FOUND); /* Same error code when DNS lookup fails */
|
||||
}
|
||||
|
||||
/* Just copy the address */
|
||||
if (r=nr_transport_addr_copy(&cand->stun_server_addr,
|
||||
&cand->stun_server->u.addr)) {
|
||||
|
@ -567,6 +576,17 @@ int nr_ice_candidate_initialize(nr_ice_candidate *cand, NR_async_cb ready_cb, vo
|
|||
resource.stun_turn=protocol;
|
||||
resource.transport_protocol=cand->stun_server->transport;
|
||||
|
||||
switch (cand->base.ip_version) {
|
||||
case NR_IPV4:
|
||||
resource.address_family=AF_INET;
|
||||
break;
|
||||
case NR_IPV6:
|
||||
resource.address_family=AF_INET6;
|
||||
break;
|
||||
default:
|
||||
ABORT(R_BAD_ARGS);
|
||||
}
|
||||
|
||||
/* Try to resolve */
|
||||
if(!cand->ctx->resolver) {
|
||||
r_log(LOG_ICE, LOG_ERR, "ICE-CANDIDATE(%s): Can't use DNS names without a resolver", cand->label);
|
||||
|
|
Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше
Загрузка…
Ссылка в новой задаче