зеркало из https://github.com/mozilla/gecko-dev.git
Коммит
abc76acfae
|
@ -15,7 +15,7 @@
|
|||
<project name="platform_build" path="build" remote="b2g" revision="ef937d1aca7c4cf89ecb5cc43ae8c21c2000a9db">
|
||||
<copyfile dest="Makefile" src="core/root.mk"/>
|
||||
</project>
|
||||
<project name="gaia" path="gaia" remote="mozillaorg" revision="84cbd4391fb7175d5380fa72c04d68873ce77e6d"/>
|
||||
<project name="gaia" path="gaia" remote="mozillaorg" revision="a290b11627ec2b7c25980f5687a98da86641cfe4"/>
|
||||
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
|
||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="2aa4a75c63cd6e93870a8bddbba45f863cbfd9a3"/>
|
||||
<project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
|
||||
|
@ -128,7 +128,7 @@
|
|||
<!-- Stock Android things -->
|
||||
<project name="platform/external/icu4c" path="external/icu4c" revision="2bb01561780583cc37bc667f0ea79f48a122d8a2"/>
|
||||
<!-- dolphin specific things -->
|
||||
<project name="device/sprd" path="device/sprd" revision="1eb575040af22c58a16f90ecd04b6e1ebdee5c80"/>
|
||||
<project name="device/sprd" path="device/sprd" revision="a26ba0ab998133ad590102be1e5950818b86ce82"/>
|
||||
<project name="platform/external/wpa_supplicant_8" path="external/wpa_supplicant_8" revision="2ea7f18a7bc45e16cb97a835dc86ee64d2d6b0b3"/>
|
||||
<project name="platform/frameworks/av" path="frameworks/av" revision="fd359e3a74a658d9eaab1c683440ba5412535777"/>
|
||||
<project name="platform/hardware/akm" path="hardware/akm" revision="6d3be412647b0eab0adff8a2768736cf4eb68039"/>
|
||||
|
|
|
@ -19,11 +19,11 @@
|
|||
<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="84cbd4391fb7175d5380fa72c04d68873ce77e6d"/>
|
||||
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="a290b11627ec2b7c25980f5687a98da86641cfe4"/>
|
||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="2aa4a75c63cd6e93870a8bddbba45f863cbfd9a3"/>
|
||||
<project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
|
||||
<project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="93f9ba577f68d772093987c2f1c0a4ae293e1802"/>
|
||||
<project name="platform_external_qemu" path="external/qemu" remote="b2g" revision="3b3407510d6e2c60242e8b9b5f2bc1783ca0a0e4"/>
|
||||
<project name="platform_external_qemu" path="external/qemu" remote="b2g" revision="a89cebcccc1e067ebdb71a93194f4ee79d71bd69"/>
|
||||
<project name="moztt" path="external/moztt" remote="b2g" revision="ed2cf97a6c37a4bbd0bbbbffe06ec7136d8c79ff"/>
|
||||
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="34ea6163f9f0e0122fb0bb03607eccdca31ced7a"/>
|
||||
<!-- Stock Android things -->
|
||||
|
|
|
@ -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="84cbd4391fb7175d5380fa72c04d68873ce77e6d"/>
|
||||
<project name="gaia" path="gaia" remote="mozillaorg" revision="a290b11627ec2b7c25980f5687a98da86641cfe4"/>
|
||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="2aa4a75c63cd6e93870a8bddbba45f863cbfd9a3"/>
|
||||
<project name="moztt" path="external/moztt" remote="b2g" revision="ed2cf97a6c37a4bbd0bbbbffe06ec7136d8c79ff"/>
|
||||
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="1b1d86462d3150dceacff927536ded9fcc168419"/>
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
<project name="platform_build" path="build" remote="b2g" revision="ef937d1aca7c4cf89ecb5cc43ae8c21c2000a9db">
|
||||
<copyfile dest="Makefile" src="core/root.mk"/>
|
||||
</project>
|
||||
<project name="gaia" path="gaia" remote="mozillaorg" revision="84cbd4391fb7175d5380fa72c04d68873ce77e6d"/>
|
||||
<project name="gaia" path="gaia" remote="mozillaorg" revision="a290b11627ec2b7c25980f5687a98da86641cfe4"/>
|
||||
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
|
||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="2aa4a75c63cd6e93870a8bddbba45f863cbfd9a3"/>
|
||||
<project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
|
||||
|
@ -131,7 +131,7 @@
|
|||
<project name="device_generic_goldfish" path="device/generic/goldfish" remote="b2g" revision="f5f7fa2fc26b96d2cbd0af4569c0036fe034bb43"/>
|
||||
<project name="platform_external_qemu" path="external/qemu" remote="b2g" revision="fbd2becab3825c49e756db5149552f85049c66e2"/>
|
||||
<project name="platform/external/wpa_supplicant_8" path="external/wpa_supplicant_8" revision="694cecf256122d0cb3b6a1a4efb4b5c7401db223"/>
|
||||
<project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="f810abaa47932079396ea3815df65fbdd83b1f94"/>
|
||||
<project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="c0bf1eec60644abbc5e72c251c15092b4c71b6a9"/>
|
||||
<project name="platform/development" path="development" revision="5968ff4e13e0d696ad8d972281fc27ae5a12829b"/>
|
||||
<project name="android-sdk" path="sdk" remote="b2g" revision="0951179277915335251c5e11d242e4e1a8c2236f"/>
|
||||
<project name="darwinstreamingserver" path="system/darwinstreamingserver" remote="b2g" revision="cf85968c7f85e0ec36e72c87ceb4837a943b8af6"/>
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
<project name="platform_build" path="build" remote="b2g" revision="52775e03a2d8532429dff579cb2cd56718e488c3">
|
||||
<copyfile dest="Makefile" src="core/root.mk"/>
|
||||
</project>
|
||||
<project name="gaia" path="gaia" remote="mozillaorg" revision="84cbd4391fb7175d5380fa72c04d68873ce77e6d"/>
|
||||
<project name="gaia" path="gaia" remote="mozillaorg" revision="a290b11627ec2b7c25980f5687a98da86641cfe4"/>
|
||||
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
|
||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="2aa4a75c63cd6e93870a8bddbba45f863cbfd9a3"/>
|
||||
<project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
|
||||
|
|
|
@ -19,11 +19,11 @@
|
|||
<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="84cbd4391fb7175d5380fa72c04d68873ce77e6d"/>
|
||||
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="a290b11627ec2b7c25980f5687a98da86641cfe4"/>
|
||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="2aa4a75c63cd6e93870a8bddbba45f863cbfd9a3"/>
|
||||
<project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
|
||||
<project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="93f9ba577f68d772093987c2f1c0a4ae293e1802"/>
|
||||
<project name="platform_external_qemu" path="external/qemu" remote="b2g" revision="3b3407510d6e2c60242e8b9b5f2bc1783ca0a0e4"/>
|
||||
<project name="platform_external_qemu" path="external/qemu" remote="b2g" revision="a89cebcccc1e067ebdb71a93194f4ee79d71bd69"/>
|
||||
<project name="moztt" path="external/moztt" remote="b2g" revision="ed2cf97a6c37a4bbd0bbbbffe06ec7136d8c79ff"/>
|
||||
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="34ea6163f9f0e0122fb0bb03607eccdca31ced7a"/>
|
||||
<!-- Stock Android things -->
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
<project name="platform_build" path="build" remote="b2g" revision="ef937d1aca7c4cf89ecb5cc43ae8c21c2000a9db">
|
||||
<copyfile dest="Makefile" src="core/root.mk"/>
|
||||
</project>
|
||||
<project name="gaia" path="gaia" remote="mozillaorg" revision="84cbd4391fb7175d5380fa72c04d68873ce77e6d"/>
|
||||
<project name="gaia" path="gaia" remote="mozillaorg" revision="a290b11627ec2b7c25980f5687a98da86641cfe4"/>
|
||||
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
|
||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="2aa4a75c63cd6e93870a8bddbba45f863cbfd9a3"/>
|
||||
<project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
</project>
|
||||
<project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
|
||||
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
|
||||
<project name="gaia" path="gaia" remote="mozillaorg" revision="84cbd4391fb7175d5380fa72c04d68873ce77e6d"/>
|
||||
<project name="gaia" path="gaia" remote="mozillaorg" revision="a290b11627ec2b7c25980f5687a98da86641cfe4"/>
|
||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="2aa4a75c63cd6e93870a8bddbba45f863cbfd9a3"/>
|
||||
<project name="moztt" path="external/moztt" remote="b2g" revision="ed2cf97a6c37a4bbd0bbbbffe06ec7136d8c79ff"/>
|
||||
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="1b1d86462d3150dceacff927536ded9fcc168419"/>
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
{
|
||||
"git": {
|
||||
"git_revision": "84cbd4391fb7175d5380fa72c04d68873ce77e6d",
|
||||
"git_revision": "a290b11627ec2b7c25980f5687a98da86641cfe4",
|
||||
"remote": "https://git.mozilla.org/releases/gaia.git",
|
||||
"branch": ""
|
||||
},
|
||||
"revision": "a7d22a159f8c412c7d8ebb371025b17563265c06",
|
||||
"revision": "dbc540265f837510772bbcfdc7e9181b280b2551",
|
||||
"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="84cbd4391fb7175d5380fa72c04d68873ce77e6d"/>
|
||||
<project name="gaia" path="gaia" remote="mozillaorg" revision="a290b11627ec2b7c25980f5687a98da86641cfe4"/>
|
||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="2aa4a75c63cd6e93870a8bddbba45f863cbfd9a3"/>
|
||||
<project name="moztt" path="external/moztt" remote="b2g" revision="ed2cf97a6c37a4bbd0bbbbffe06ec7136d8c79ff"/>
|
||||
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="1b1d86462d3150dceacff927536ded9fcc168419"/>
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
<project name="platform_build" path="build" remote="b2g" revision="52775e03a2d8532429dff579cb2cd56718e488c3">
|
||||
<copyfile dest="Makefile" src="core/root.mk"/>
|
||||
</project>
|
||||
<project name="gaia" path="gaia" remote="mozillaorg" revision="84cbd4391fb7175d5380fa72c04d68873ce77e6d"/>
|
||||
<project name="gaia" path="gaia" remote="mozillaorg" revision="a290b11627ec2b7c25980f5687a98da86641cfe4"/>
|
||||
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
|
||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="2aa4a75c63cd6e93870a8bddbba45f863cbfd9a3"/>
|
||||
<project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
|
||||
|
|
|
@ -102,10 +102,10 @@ pref("app.update.log", false);
|
|||
pref("app.update.backgroundMaxErrors", 10);
|
||||
|
||||
// The aus update xml certificate checks for application update are disabled on
|
||||
// Windows since the mar signature check which is currently only implemented on
|
||||
// Windows is sufficient for preventing us from applying a mar that is not
|
||||
// Windows and Mac OS X since the mar signature check are implemented on these
|
||||
// platforms and is sufficient to prevent us from applying a mar that is not
|
||||
// valid.
|
||||
#ifdef XP_WIN
|
||||
#if defined(XP_WIN) || defined(XP_MACOSX)
|
||||
pref("app.update.cert.requireBuiltIn", false);
|
||||
pref("app.update.cert.checkAttributes", false);
|
||||
#else
|
||||
|
|
|
@ -568,11 +568,16 @@ let AboutReaderListener = {
|
|||
}
|
||||
},
|
||||
updateReaderButton: function() {
|
||||
if (!ReaderMode.isEnabledForParseOnLoad || this.isAboutReader) {
|
||||
if (!ReaderMode.isEnabledForParseOnLoad || this.isAboutReader ||
|
||||
!(content.document instanceof content.HTMLDocument) ||
|
||||
content.document.mozSyntheticDocument) {
|
||||
return;
|
||||
}
|
||||
let isArticle = ReaderMode.isProbablyReaderable(content.document);
|
||||
sendAsyncMessage("Reader:UpdateReaderButton", { isArticle: isArticle });
|
||||
// Only send updates when there are articles; there's no point updating with
|
||||
// |false| all the time.
|
||||
if (ReaderMode.isProbablyReaderable(content.document)) {
|
||||
sendAsyncMessage("Reader:UpdateReaderButton", { isArticle: true });
|
||||
}
|
||||
},
|
||||
};
|
||||
AboutReaderListener.init();
|
||||
|
|
|
@ -54,7 +54,7 @@ If you install eslint and the react plugin globally:
|
|||
|
||||
You can also run it by hand in the browser/components/loop directory:
|
||||
|
||||
eslint -ext .js -ext .jsx .
|
||||
eslint -ext .js -ext .jsx --ext .jsm .
|
||||
|
||||
Front-End Unit Tests
|
||||
====================
|
||||
|
|
|
@ -11,6 +11,7 @@ loop.conversationViews = (function(mozL10n) {
|
|||
|
||||
var CALL_STATES = loop.store.CALL_STATES;
|
||||
var CALL_TYPES = loop.shared.utils.CALL_TYPES;
|
||||
var FAILURE_DETAILS = loop.shared.utils.FAILURE_DETAILS;
|
||||
var REST_ERRNOS = loop.shared.utils.REST_ERRNOS;
|
||||
var WEBSOCKET_REASONS = loop.shared.utils.WEBSOCKET_REASONS;
|
||||
var sharedActions = loop.shared.actions;
|
||||
|
@ -322,7 +323,8 @@ loop.conversationViews = (function(mozL10n) {
|
|||
],
|
||||
|
||||
propTypes: {
|
||||
cancelCall: React.PropTypes.func.isRequired
|
||||
cancelCall: React.PropTypes.func.isRequired,
|
||||
failureReason: React.PropTypes.string
|
||||
},
|
||||
|
||||
componentDidMount: function() {
|
||||
|
@ -332,9 +334,18 @@ loop.conversationViews = (function(mozL10n) {
|
|||
render: function() {
|
||||
this.setTitle(mozL10n.get("generic_failure_title"));
|
||||
|
||||
var errorString;
|
||||
switch (this.props.failureReason) {
|
||||
case FAILURE_DETAILS.UNABLE_TO_PUBLISH_MEDIA:
|
||||
errorString = mozL10n.get("no_media_failure_message");
|
||||
break;
|
||||
default:
|
||||
errorString = mozL10n.get("generic_failure_title");
|
||||
}
|
||||
|
||||
return (
|
||||
React.createElement("div", {className: "call-window"},
|
||||
React.createElement("h2", null, mozL10n.get("generic_failure_title")),
|
||||
React.createElement("h2", null, errorString),
|
||||
|
||||
React.createElement("div", {className: "btn-group call-action-group"},
|
||||
React.createElement("button", {className: "btn btn-cancel",
|
||||
|
@ -467,21 +478,22 @@ loop.conversationViews = (function(mozL10n) {
|
|||
},
|
||||
|
||||
_getTitleMessage: function() {
|
||||
var callStateReason =
|
||||
this.getStoreState().callStateReason;
|
||||
switch (this.getStoreState().callStateReason) {
|
||||
case WEBSOCKET_REASONS.REJECT:
|
||||
case WEBSOCKET_REASONS.BUSY:
|
||||
case REST_ERRNOS.USER_UNAVAILABLE:
|
||||
var contactDisplayName = _getContactDisplayName(this.props.contact);
|
||||
if (contactDisplayName.length) {
|
||||
return mozL10n.get(
|
||||
"contact_unavailable_title",
|
||||
{"contactName": contactDisplayName});
|
||||
}
|
||||
|
||||
if (callStateReason === WEBSOCKET_REASONS.REJECT || callStateReason === WEBSOCKET_REASONS.BUSY ||
|
||||
callStateReason === REST_ERRNOS.USER_UNAVAILABLE) {
|
||||
var contactDisplayName = _getContactDisplayName(this.props.contact);
|
||||
if (contactDisplayName.length) {
|
||||
return mozL10n.get(
|
||||
"contact_unavailable_title",
|
||||
{"contactName": contactDisplayName});
|
||||
}
|
||||
|
||||
return mozL10n.get("generic_contact_unavailable_title");
|
||||
} else {
|
||||
return mozL10n.get("generic_failure_title");
|
||||
return mozL10n.get("generic_contact_unavailable_title");
|
||||
case FAILURE_DETAILS.UNABLE_TO_PUBLISH_MEDIA:
|
||||
return mozL10n.get("no_media_failure_message");
|
||||
default:
|
||||
return mozL10n.get("generic_failure_title");
|
||||
}
|
||||
},
|
||||
|
||||
|
|
|
@ -11,6 +11,7 @@ loop.conversationViews = (function(mozL10n) {
|
|||
|
||||
var CALL_STATES = loop.store.CALL_STATES;
|
||||
var CALL_TYPES = loop.shared.utils.CALL_TYPES;
|
||||
var FAILURE_DETAILS = loop.shared.utils.FAILURE_DETAILS;
|
||||
var REST_ERRNOS = loop.shared.utils.REST_ERRNOS;
|
||||
var WEBSOCKET_REASONS = loop.shared.utils.WEBSOCKET_REASONS;
|
||||
var sharedActions = loop.shared.actions;
|
||||
|
@ -322,7 +323,8 @@ loop.conversationViews = (function(mozL10n) {
|
|||
],
|
||||
|
||||
propTypes: {
|
||||
cancelCall: React.PropTypes.func.isRequired
|
||||
cancelCall: React.PropTypes.func.isRequired,
|
||||
failureReason: React.PropTypes.string
|
||||
},
|
||||
|
||||
componentDidMount: function() {
|
||||
|
@ -332,9 +334,18 @@ loop.conversationViews = (function(mozL10n) {
|
|||
render: function() {
|
||||
this.setTitle(mozL10n.get("generic_failure_title"));
|
||||
|
||||
var errorString;
|
||||
switch (this.props.failureReason) {
|
||||
case FAILURE_DETAILS.UNABLE_TO_PUBLISH_MEDIA:
|
||||
errorString = mozL10n.get("no_media_failure_message");
|
||||
break;
|
||||
default:
|
||||
errorString = mozL10n.get("generic_failure_title");
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="call-window">
|
||||
<h2>{mozL10n.get("generic_failure_title")}</h2>
|
||||
<h2>{errorString}</h2>
|
||||
|
||||
<div className="btn-group call-action-group">
|
||||
<button className="btn btn-cancel"
|
||||
|
@ -467,21 +478,22 @@ loop.conversationViews = (function(mozL10n) {
|
|||
},
|
||||
|
||||
_getTitleMessage: function() {
|
||||
var callStateReason =
|
||||
this.getStoreState().callStateReason;
|
||||
switch (this.getStoreState().callStateReason) {
|
||||
case WEBSOCKET_REASONS.REJECT:
|
||||
case WEBSOCKET_REASONS.BUSY:
|
||||
case REST_ERRNOS.USER_UNAVAILABLE:
|
||||
var contactDisplayName = _getContactDisplayName(this.props.contact);
|
||||
if (contactDisplayName.length) {
|
||||
return mozL10n.get(
|
||||
"contact_unavailable_title",
|
||||
{"contactName": contactDisplayName});
|
||||
}
|
||||
|
||||
if (callStateReason === WEBSOCKET_REASONS.REJECT || callStateReason === WEBSOCKET_REASONS.BUSY ||
|
||||
callStateReason === REST_ERRNOS.USER_UNAVAILABLE) {
|
||||
var contactDisplayName = _getContactDisplayName(this.props.contact);
|
||||
if (contactDisplayName.length) {
|
||||
return mozL10n.get(
|
||||
"contact_unavailable_title",
|
||||
{"contactName": contactDisplayName});
|
||||
}
|
||||
|
||||
return mozL10n.get("generic_contact_unavailable_title");
|
||||
} else {
|
||||
return mozL10n.get("generic_failure_title");
|
||||
return mozL10n.get("generic_contact_unavailable_title");
|
||||
case FAILURE_DETAILS.UNABLE_TO_PUBLISH_MEDIA:
|
||||
return mozL10n.get("no_media_failure_message");
|
||||
default:
|
||||
return mozL10n.get("generic_failure_title");
|
||||
}
|
||||
},
|
||||
|
||||
|
|
|
@ -251,7 +251,8 @@ loop.roomViews = (function(mozL10n) {
|
|||
// FULL case should never happen on desktop.
|
||||
return (
|
||||
React.createElement(loop.conversationViews.GenericFailureView, {
|
||||
cancelCall: this.closeWindow})
|
||||
cancelCall: this.closeWindow,
|
||||
failureReason: this.state.failureReason})
|
||||
);
|
||||
}
|
||||
case ROOM_STATES.ENDED: {
|
||||
|
|
|
@ -251,7 +251,8 @@ loop.roomViews = (function(mozL10n) {
|
|||
// FULL case should never happen on desktop.
|
||||
return (
|
||||
<loop.conversationViews.GenericFailureView
|
||||
cancelCall={this.closeWindow} />
|
||||
cancelCall={this.closeWindow}
|
||||
failureReason={this.state.failureReason} />
|
||||
);
|
||||
}
|
||||
case ROOM_STATES.ENDED: {
|
||||
|
|
|
@ -13,6 +13,7 @@ describe("loop.conversationViews", function () {
|
|||
|
||||
var CALL_STATES = loop.store.CALL_STATES;
|
||||
var CALL_TYPES = loop.shared.utils.CALL_TYPES;
|
||||
var FAILURE_DETAILS = loop.shared.utils.FAILURE_DETAILS;
|
||||
var REST_ERRNOS = loop.shared.utils.REST_ERRNOS;
|
||||
var WEBSOCKET_REASONS = loop.shared.utils.WEBSOCKET_REASONS;
|
||||
|
||||
|
@ -450,6 +451,15 @@ describe("loop.conversationViews", function () {
|
|||
{contactName: loop.conversationViews._getContactDisplayName(contact)});
|
||||
});
|
||||
|
||||
it("should show 'no media' when the reason is FAILURE_DETAILS.UNABLE_TO_PUBLISH_MEDIA",
|
||||
function () {
|
||||
store.setStoreState({callStateReason: FAILURE_DETAILS.UNABLE_TO_PUBLISH_MEDIA});
|
||||
|
||||
view = mountTestComponent({contact: contact});
|
||||
|
||||
sinon.assert.calledWithExactly(document.mozL10n.get, "no_media_failure_message");
|
||||
});
|
||||
|
||||
it("should display a generic contact unavailable msg when the reason is" +
|
||||
" WEBSOCKET_REASONS.BUSY and no display name is available", function() {
|
||||
store.setStoreState({callStateReason: WEBSOCKET_REASONS.BUSY});
|
||||
|
@ -887,6 +897,11 @@ describe("loop.conversationViews", function () {
|
|||
describe("GenericFailureView", function() {
|
||||
var view, fakeAudio;
|
||||
|
||||
function mountTestComponent(props) {
|
||||
return TestUtils.renderIntoDocument(
|
||||
React.createElement(loop.conversationViews.GenericFailureView, props));
|
||||
}
|
||||
|
||||
beforeEach(function() {
|
||||
fakeAudio = {
|
||||
play: sinon.spy(),
|
||||
|
@ -895,14 +910,11 @@ describe("loop.conversationViews", function () {
|
|||
};
|
||||
navigator.mozLoop.doNotDisturb = false;
|
||||
sandbox.stub(window, "Audio").returns(fakeAudio);
|
||||
|
||||
view = TestUtils.renderIntoDocument(
|
||||
React.createElement(loop.conversationViews.GenericFailureView, {
|
||||
cancelCall: function() {}
|
||||
}));
|
||||
});
|
||||
|
||||
it("should play a failure sound, once", function() {
|
||||
view = mountTestComponent({cancelCall: function() {}});
|
||||
|
||||
sinon.assert.calledOnce(navigator.mozLoop.getAudioBlob);
|
||||
sinon.assert.calledWithExactly(navigator.mozLoop.getAudioBlob,
|
||||
"failure", sinon.match.func);
|
||||
|
@ -911,7 +923,24 @@ describe("loop.conversationViews", function () {
|
|||
});
|
||||
|
||||
it("should set the title to generic_failure_title", function() {
|
||||
view = mountTestComponent({cancelCall: function() {}});
|
||||
|
||||
expect(fakeWindow.document.title).eql("generic_failure_title");
|
||||
});
|
||||
|
||||
it("should show 'no media' for FAILURE_DETAILS.UNABLE_TO_PUBLISH_MEDIA reason", function() {
|
||||
view = mountTestComponent({
|
||||
cancelCall: function() {},
|
||||
failureReason: FAILURE_DETAILS.UNABLE_TO_PUBLISH_MEDIA
|
||||
});
|
||||
|
||||
expect(view.getDOMNode().querySelector("h2").textContent).eql("no_media_failure_message");
|
||||
});
|
||||
|
||||
it("should show 'generic_failure_title' when no reason is specified", function() {
|
||||
view = mountTestComponent({cancelCall: function() {}});
|
||||
|
||||
expect(view.getDOMNode().querySelector("h2").textContent).eql("generic_failure_title");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -346,7 +346,7 @@ ReadingListImpl.prototype = {
|
|||
let item = this._itemFromRecord(record);
|
||||
this._callListeners("onItemAdded", item);
|
||||
let mm = Cc["@mozilla.org/globalmessagemanager;1"].getService(Ci.nsIMessageListenerManager);
|
||||
mm.broadcastAsyncMessage("Reader:Added", item);
|
||||
mm.broadcastAsyncMessage("Reader:Added", item.toJSON());
|
||||
return item;
|
||||
}),
|
||||
|
||||
|
@ -427,7 +427,7 @@ ReadingListImpl.prototype = {
|
|||
}
|
||||
this._invalidateIterators();
|
||||
let mm = Cc["@mozilla.org/globalmessagemanager;1"].getService(Ci.nsIMessageListenerManager);
|
||||
mm.broadcastAsyncMessage("Reader:Removed", item);
|
||||
mm.broadcastAsyncMessage("Reader:Removed", item.toJSON());
|
||||
this._callListeners("onItemDeleted", item);
|
||||
}),
|
||||
|
||||
|
|
|
@ -21,6 +21,10 @@ XPCOMUtils.defineLazyModuleGetter(this, "ReadingList",
|
|||
XPCOMUtils.defineLazyModuleGetter(this, "ServerClient",
|
||||
"resource:///modules/readinglist/ServerClient.jsm");
|
||||
|
||||
// The maximum number of sub-requests per POST /batch supported by the server.
|
||||
// See http://readinglist.readthedocs.org/en/latest/api/batch.html.
|
||||
const BATCH_REQUEST_LIMIT = 25;
|
||||
|
||||
// The Last-Modified header of server responses is stored here.
|
||||
const SERVER_LAST_MODIFIED_HEADER_PREF = "readinglist.sync.serverLastModified";
|
||||
|
||||
|
@ -180,8 +184,6 @@ SyncImpl.prototype = {
|
|||
|
||||
// Send the request.
|
||||
let request = {
|
||||
method: "POST",
|
||||
path: "/batch",
|
||||
body: {
|
||||
defaults: {
|
||||
method: "PATCH",
|
||||
|
@ -189,7 +191,7 @@ SyncImpl.prototype = {
|
|||
requests: requests,
|
||||
},
|
||||
};
|
||||
let batchResponse = yield this._sendRequest(request);
|
||||
let batchResponse = yield this._postBatch(request);
|
||||
if (batchResponse.status != 200) {
|
||||
this._handleUnexpectedResponse("uploading changes", batchResponse);
|
||||
return;
|
||||
|
@ -244,8 +246,6 @@ SyncImpl.prototype = {
|
|||
|
||||
// Send the request.
|
||||
let request = {
|
||||
method: "POST",
|
||||
path: "/batch",
|
||||
body: {
|
||||
defaults: {
|
||||
method: "POST",
|
||||
|
@ -254,7 +254,7 @@ SyncImpl.prototype = {
|
|||
requests: requests,
|
||||
},
|
||||
};
|
||||
let batchResponse = yield this._sendRequest(request);
|
||||
let batchResponse = yield this._postBatch(request);
|
||||
if (batchResponse.status != 200) {
|
||||
this._handleUnexpectedResponse("uploading new items", batchResponse);
|
||||
return;
|
||||
|
@ -308,8 +308,6 @@ SyncImpl.prototype = {
|
|||
|
||||
// Send the request.
|
||||
let request = {
|
||||
method: "POST",
|
||||
path: "/batch",
|
||||
body: {
|
||||
defaults: {
|
||||
method: "DELETE",
|
||||
|
@ -317,7 +315,7 @@ SyncImpl.prototype = {
|
|||
requests: requests,
|
||||
},
|
||||
};
|
||||
let batchResponse = yield this._sendRequest(request);
|
||||
let batchResponse = yield this._postBatch(request);
|
||||
if (batchResponse.status != 200) {
|
||||
this._handleUnexpectedResponse("uploading deleted items", batchResponse);
|
||||
return;
|
||||
|
@ -504,6 +502,50 @@ SyncImpl.prototype = {
|
|||
return response;
|
||||
}),
|
||||
|
||||
/**
|
||||
* The server limits the number of sub-requests in POST /batch'es to
|
||||
* BATCH_REQUEST_LIMIT. This method takes an arbitrarily big batch request
|
||||
* and breaks it apart into many individual batch requests in order to stay
|
||||
* within the limit.
|
||||
*
|
||||
* @param bigRequest The same type of request object that _sendRequest takes.
|
||||
* Since it's a POST /batch request, its `body` should have a
|
||||
* `requests` property whose value is an array of sub-requests.
|
||||
* `method` and `path` are automatically filled.
|
||||
* @return Promise<response> Resolved when all requests complete with 200s, or
|
||||
* when the first response that is not a 200 is received. In the
|
||||
* first case, the resolved response is a combination of all the
|
||||
* server responses, and response.body.responses contains the sub-
|
||||
* responses for all the sub-requests in bigRequest. In the second
|
||||
* case, the resolved response is the non-200 response straight from
|
||||
* the server.
|
||||
*/
|
||||
_postBatch: Task.async(function* (bigRequest) {
|
||||
log.debug("Sending batch requests");
|
||||
let allSubResponses = [];
|
||||
let remainingSubRequests = bigRequest.body.requests;
|
||||
while (remainingSubRequests.length) {
|
||||
let request = Object.assign({}, bigRequest);
|
||||
request.method = "POST";
|
||||
request.path = "/batch";
|
||||
request.body.requests =
|
||||
remainingSubRequests.splice(0, BATCH_REQUEST_LIMIT);
|
||||
let response = yield this._sendRequest(request);
|
||||
if (response.status != 200) {
|
||||
return response;
|
||||
}
|
||||
allSubResponses = allSubResponses.concat(response.body.responses);
|
||||
}
|
||||
let bigResponse = {
|
||||
status: 200,
|
||||
body: {
|
||||
responses: allSubResponses,
|
||||
},
|
||||
};
|
||||
log.debug("All batch requests successfully sent");
|
||||
return bigResponse;
|
||||
}),
|
||||
|
||||
_handleUnexpectedResponse(contextMsgFragment, response) {
|
||||
log.error(`Unexpected response ${contextMsgFragment}`, response);
|
||||
},
|
||||
|
|
|
@ -40,6 +40,8 @@ let AnimationsPanel = {
|
|||
|
||||
this.startListeners();
|
||||
|
||||
yield this.createPlayerWidgets();
|
||||
|
||||
this.initialized.resolve();
|
||||
|
||||
this.emit(this.PANEL_INITIALIZED);
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
tags = devtools
|
||||
subsuite = devtools
|
||||
support-files =
|
||||
doc_body_animation.html
|
||||
doc_frame_script.js
|
||||
doc_simple_animation.html
|
||||
head.js
|
||||
|
@ -12,6 +13,7 @@ support-files =
|
|||
[browser_animation_participate_in_inspector_update.js]
|
||||
[browser_animation_play_pause_button.js]
|
||||
[browser_animation_playerFronts_are_refreshed.js]
|
||||
[browser_animation_playerWidgets_appear_on_panel_init.js]
|
||||
[browser_animation_playerWidgets_destroy.js]
|
||||
[browser_animation_playerWidgets_disables_on_finished.js]
|
||||
[browser_animation_playerWidgets_dont_show_time_after_duration.js]
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
/* vim: set ts=2 et sw=2 tw=80: */
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
// Test that player widgets are displayed right when the animation panel is
|
||||
// initialized, if the selected node (<body> by default) is animated.
|
||||
|
||||
add_task(function*() {
|
||||
yield addTab(TEST_URL_ROOT + "doc_body_animation.html");
|
||||
let {panel} = yield openAnimationInspector();
|
||||
|
||||
is(panel.playerWidgets.length, 1, "One animation player is displayed after init");
|
||||
});
|
|
@ -0,0 +1,23 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<style>
|
||||
body {
|
||||
background-color: white;
|
||||
color: black;
|
||||
animation: change-background-color 3s infinite alternate;
|
||||
}
|
||||
|
||||
@keyframes change-background-color {
|
||||
to {
|
||||
background-color: black;
|
||||
color: white;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Animated body element</h1>
|
||||
</body>
|
||||
</html>
|
|
@ -119,12 +119,12 @@ ToolSidebar.prototype = {
|
|||
|
||||
let tabs = this._tabbox.tabs;
|
||||
|
||||
// Create a toolbar and insert it first in the tabbox
|
||||
let allTabsToolbar = this._panelDoc.createElementNS(XULNS, "toolbar");
|
||||
this._tabbox.insertBefore(allTabsToolbar, tabs);
|
||||
// Create a container and insert it first in the tabbox
|
||||
let allTabsContainer = this._panelDoc.createElementNS(XULNS, "box");
|
||||
this._tabbox.insertBefore(allTabsContainer, tabs);
|
||||
|
||||
// Move the tabs inside and make them flex
|
||||
allTabsToolbar.appendChild(tabs);
|
||||
allTabsContainer.appendChild(tabs);
|
||||
tabs.setAttribute("flex", "1");
|
||||
|
||||
// Create the dropdown menu next to the tabs
|
||||
|
@ -134,7 +134,7 @@ ToolSidebar.prototype = {
|
|||
this._allTabsBtn.setAttribute("label", l10n("sidebar.showAllTabs.label"));
|
||||
this._allTabsBtn.setAttribute("tooltiptext", l10n("sidebar.showAllTabs.tooltip"));
|
||||
this._allTabsBtn.setAttribute("hidden", "true");
|
||||
allTabsToolbar.appendChild(this._allTabsBtn);
|
||||
allTabsContainer.appendChild(this._allTabsBtn);
|
||||
|
||||
let menuPopup = this._panelDoc.createElementNS(XULNS, "menupopup");
|
||||
this._allTabsBtn.appendChild(menuPopup);
|
||||
|
@ -162,7 +162,7 @@ ToolSidebar.prototype = {
|
|||
|
||||
// Moving back the tabs as a first child of the tabbox
|
||||
this._tabbox.insertBefore(tabs, this._tabbox.tabpanels);
|
||||
this._tabbox.querySelector("toolbar").remove();
|
||||
this._tabbox.querySelector("box").remove();
|
||||
|
||||
this._allTabsBtn = null;
|
||||
},
|
||||
|
|
|
@ -31,6 +31,7 @@ EXTRA_JS_MODULES.devtools += [
|
|||
]
|
||||
|
||||
EXTRA_JS_MODULES.devtools.shared.profiler += [
|
||||
'profiler/frame-utils.js',
|
||||
'profiler/global.js',
|
||||
'profiler/jit.js',
|
||||
'profiler/tree-model.js',
|
||||
|
@ -46,7 +47,6 @@ EXTRA_JS_MODULES.devtools.shared.timeline += [
|
|||
|
||||
EXTRA_JS_MODULES.devtools.shared += [
|
||||
'autocomplete-popup.js',
|
||||
'd3.js',
|
||||
'devices.js',
|
||||
'doorhanger.js',
|
||||
'frame-script-utils.js',
|
||||
|
|
|
@ -0,0 +1,131 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
"use strict";
|
||||
|
||||
const { Ci } = require("chrome");
|
||||
const { extend } = require("sdk/util/object");
|
||||
loader.lazyRequireGetter(this, "Services");
|
||||
loader.lazyRequireGetter(this, "CATEGORY_OTHER",
|
||||
"devtools/shared/profiler/global", true);
|
||||
|
||||
// The cache used in the `nsIURL` function.
|
||||
const gNSURLStore = new Map();
|
||||
const CHROME_SCHEMES = ["chrome://", "resource://", "jar:file://"];
|
||||
const CONTENT_SCHEMES = ["http://", "https://", "file://", "app://"];
|
||||
|
||||
/**
|
||||
* Parses the raw location of this function call to retrieve the actual
|
||||
* function name, source url, host name, line and column.
|
||||
*/
|
||||
exports.parseLocation = function parseLocation (frame) {
|
||||
// Parse the `location` for the function name, source url, line, column etc.
|
||||
let lineAndColumn = frame.location.match(/((:\d+)*)\)?$/)[1];
|
||||
let [, line, column] = lineAndColumn.split(":");
|
||||
line = line || frame.line;
|
||||
column = column || frame.column;
|
||||
|
||||
let firstParenIndex = frame.location.indexOf("(");
|
||||
let lineAndColumnIndex = frame.location.indexOf(lineAndColumn);
|
||||
let resource = frame.location.substring(firstParenIndex + 1, lineAndColumnIndex);
|
||||
|
||||
let url = resource.split(" -> ").pop();
|
||||
let uri = nsIURL(url);
|
||||
let functionName, fileName, hostName;
|
||||
|
||||
// If the URI digged out from the `location` is valid, this is a JS frame.
|
||||
if (uri) {
|
||||
functionName = frame.location.substring(0, firstParenIndex - 1);
|
||||
fileName = (uri.fileName + (uri.ref ? "#" + uri.ref : "")) || "/";
|
||||
hostName = url.indexOf("jar:") == 0 ? "" : uri.host;
|
||||
} else {
|
||||
functionName = frame.location;
|
||||
url = null;
|
||||
}
|
||||
|
||||
return {
|
||||
functionName: functionName,
|
||||
fileName: fileName,
|
||||
hostName: hostName,
|
||||
url: url,
|
||||
line: line,
|
||||
column: column
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* Checks if the specified function represents a chrome or content frame.
|
||||
*
|
||||
* @param object frame
|
||||
* The { category, location } properties of the frame.
|
||||
* @return boolean
|
||||
* True if a content frame, false if a chrome frame.
|
||||
*/
|
||||
exports.isContent = function isContent ({ category, location }) {
|
||||
// Only C++ stack frames have associated category information.
|
||||
return !!(!category &&
|
||||
!CHROME_SCHEMES.find(e => location.contains(e)) &&
|
||||
CONTENT_SCHEMES.find(e => location.contains(e)));
|
||||
}
|
||||
|
||||
/**
|
||||
* This filters out platform data frames in a sample. With latest performance
|
||||
* tool in Fx40, when displaying only content, we still filter out all platform data,
|
||||
* except we generalize platform data that are leaves. We do this because of two
|
||||
* observations:
|
||||
*
|
||||
* 1. The leaf is where time is _actually_ being spent, so we _need_ to show it
|
||||
* to developers in some way to give them accurate profiling data. We decide to
|
||||
* split the platform into various category buckets and just show time spent in
|
||||
* each bucket.
|
||||
*
|
||||
* 2. The calls leading to the leaf _aren't_ where we are spending time, but
|
||||
* _do_ give the developer context for how they got to the leaf where they _are_
|
||||
* spending time. For non-platform hackers, the non-leaf platform frames don't
|
||||
* give any meaningful context, and so we can safely filter them out.
|
||||
*
|
||||
* Example transformations:
|
||||
* Before: PlatformA -> PlatformB -> ContentA -> ContentB
|
||||
* After: ContentA -> ContentB
|
||||
*
|
||||
* Before: PlatformA -> ContentA -> PlatformB -> PlatformC
|
||||
* After: ContentA -> Category(PlatformC)
|
||||
*/
|
||||
exports.filterPlatformData = function filterPlatformData (frames) {
|
||||
let result = [];
|
||||
let last = frames.length - 1;
|
||||
let frame;
|
||||
|
||||
for (let i = 0; i < frames.length; i++) {
|
||||
frame = frames[i];
|
||||
if (exports.isContent(frame)) {
|
||||
result.push(frame);
|
||||
} else if (last === i) {
|
||||
// Extend here so we're not destructively editing
|
||||
// the original profiler data. Set isMetaCategory `true`,
|
||||
// and ensure we have a category set by default, because that's how
|
||||
// the generalized frame nodes are organized.
|
||||
result.push(extend({ isMetaCategory: true, category: CATEGORY_OTHER }, frame));
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper for getting an nsIURL instance out of a string.
|
||||
*/
|
||||
function nsIURL(url) {
|
||||
let cached = gNSURLStore.get(url);
|
||||
if (cached) {
|
||||
return cached;
|
||||
}
|
||||
let uri = null;
|
||||
try {
|
||||
uri = Services.io.newURI(url, null, null).QueryInterface(Ci.nsIURL);
|
||||
} catch(e) {
|
||||
// The passed url string is invalid.
|
||||
}
|
||||
gNSURLStore.set(url, uri);
|
||||
return uri;
|
||||
}
|
|
@ -4,9 +4,7 @@
|
|||
"use strict";
|
||||
|
||||
const {Cc, Ci, Cu, Cr} = require("chrome");
|
||||
const {extend} = require("sdk/util/object");
|
||||
|
||||
loader.lazyRequireGetter(this, "Services");
|
||||
loader.lazyRequireGetter(this, "L10N",
|
||||
"devtools/shared/profiler/global", true);
|
||||
loader.lazyRequireGetter(this, "CATEGORY_MAPPINGS",
|
||||
|
@ -17,15 +15,12 @@ loader.lazyRequireGetter(this, "CATEGORY_JIT",
|
|||
"devtools/shared/profiler/global", true);
|
||||
loader.lazyRequireGetter(this, "JITOptimizations",
|
||||
"devtools/shared/profiler/jit", true);
|
||||
loader.lazyRequireGetter(this, "CATEGORY_OTHER",
|
||||
"devtools/shared/profiler/global", true);
|
||||
|
||||
const CHROME_SCHEMES = ["chrome://", "resource://", "jar:file://"];
|
||||
const CONTENT_SCHEMES = ["http://", "https://", "file://", "app://"];
|
||||
loader.lazyRequireGetter(this, "FrameUtils",
|
||||
"devtools/shared/profiler/frame-utils");
|
||||
|
||||
exports.ThreadNode = ThreadNode;
|
||||
exports.FrameNode = FrameNode;
|
||||
exports.FrameNode.isContent = isContent;
|
||||
exports.FrameNode.isContent = FrameUtils.isContent;
|
||||
|
||||
/**
|
||||
* A call tree for a thread. This is essentially a linkage between all frames
|
||||
|
@ -102,7 +97,7 @@ ThreadNode.prototype = {
|
|||
// should be taken into consideration.
|
||||
if (options.contentOnly) {
|
||||
// The (root) node is not considered a content function, it'll be removed.
|
||||
sampleFrames = filterPlatformData(sampleFrames);
|
||||
sampleFrames = FrameUtils.filterPlatformData(sampleFrames);
|
||||
} else {
|
||||
// Remove the (root) node manually.
|
||||
sampleFrames = sampleFrames.slice(1);
|
||||
|
@ -253,42 +248,13 @@ FrameNode.prototype = {
|
|||
// default to an "unknown" category otherwise.
|
||||
let categoryData = CATEGORY_MAPPINGS[this.category] || {};
|
||||
|
||||
// Parse the `location` for the function name, source url, line, column etc.
|
||||
let lineAndColumn = this.location.match(/((:\d+)*)\)?$/)[1];
|
||||
let [, line, column] = lineAndColumn.split(":");
|
||||
line = line || this.line;
|
||||
column = column || this.column;
|
||||
let parsedData = FrameUtils.parseLocation(this);
|
||||
parsedData.nodeType = "Frame";
|
||||
parsedData.categoryData = categoryData;
|
||||
parsedData.isContent = FrameUtils.isContent(this);
|
||||
parsedData.isMetaCategory = this.isMetaCategory;
|
||||
|
||||
let firstParenIndex = this.location.indexOf("(");
|
||||
let lineAndColumnIndex = this.location.indexOf(lineAndColumn);
|
||||
let resource = this.location.substring(firstParenIndex + 1, lineAndColumnIndex);
|
||||
|
||||
let url = resource.split(" -> ").pop();
|
||||
let uri = nsIURL(url);
|
||||
let functionName, fileName, hostName;
|
||||
|
||||
// If the URI digged out from the `location` is valid, this is a JS frame.
|
||||
if (uri) {
|
||||
functionName = this.location.substring(0, firstParenIndex - 1);
|
||||
fileName = (uri.fileName + (uri.ref ? "#" + uri.ref : "")) || "/";
|
||||
hostName = url.indexOf("jar:") == 0 ? "" : uri.host;
|
||||
} else {
|
||||
functionName = this.location;
|
||||
url = null;
|
||||
}
|
||||
|
||||
return this._data = {
|
||||
nodeType: "Frame",
|
||||
functionName: functionName,
|
||||
fileName: fileName,
|
||||
hostName: hostName,
|
||||
url: url,
|
||||
line: line,
|
||||
column: column,
|
||||
categoryData: categoryData,
|
||||
isContent: !!isContent(this),
|
||||
isMetaCategory: this.isMetaCategory
|
||||
};
|
||||
return this._data = parsedData;
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -310,83 +276,3 @@ FrameNode.prototype = {
|
|||
return this._optimizations;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Checks if the specified function represents a chrome or content frame.
|
||||
*
|
||||
* @param object frame
|
||||
* The { category, location } properties of the frame.
|
||||
* @return boolean
|
||||
* True if a content frame, false if a chrome frame.
|
||||
*/
|
||||
function isContent({ category, location }) {
|
||||
// Only C++ stack frames have associated category information.
|
||||
return !category &&
|
||||
!CHROME_SCHEMES.find(e => location.contains(e)) &&
|
||||
CONTENT_SCHEMES.find(e => location.contains(e));
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper for getting an nsIURL instance out of a string.
|
||||
*/
|
||||
function nsIURL(url) {
|
||||
let cached = gNSURLStore.get(url);
|
||||
if (cached) {
|
||||
return cached;
|
||||
}
|
||||
let uri = null;
|
||||
try {
|
||||
uri = Services.io.newURI(url, null, null).QueryInterface(Ci.nsIURL);
|
||||
} catch(e) {
|
||||
// The passed url string is invalid.
|
||||
}
|
||||
gNSURLStore.set(url, uri);
|
||||
return uri;
|
||||
}
|
||||
|
||||
// The cache used in the `nsIURL` function.
|
||||
let gNSURLStore = new Map();
|
||||
|
||||
/**
|
||||
* This filters out platform data frames in a sample. With latest performance
|
||||
* tool in Fx40, when displaying only content, we still filter out all platform data,
|
||||
* except we generalize platform data that are leaves. We do this because of two
|
||||
* observations:
|
||||
*
|
||||
* 1. The leaf is where time is _actually_ being spent, so we _need_ to show it
|
||||
* to developers in some way to give them accurate profiling data. We decide to
|
||||
* split the platform into various category buckets and just show time spent in
|
||||
* each bucket.
|
||||
*
|
||||
* 2. The calls leading to the leaf _aren't_ where we are spending time, but
|
||||
* _do_ give the developer context for how they got to the leaf where they _are_
|
||||
* spending time. For non-platform hackers, the non-leaf platform frames don't
|
||||
* give any meaningful context, and so we can safely filter them out.
|
||||
*
|
||||
* Example transformations:
|
||||
* Before: PlatformA -> PlatformB -> ContentA -> ContentB
|
||||
* After: ContentA -> ContentB
|
||||
*
|
||||
* Before: PlatformA -> ContentA -> PlatformB -> PlatformC
|
||||
* After: ContentA -> Category(PlatformC)
|
||||
*/
|
||||
function filterPlatformData (frames) {
|
||||
let result = [];
|
||||
let last = frames.length - 1;
|
||||
let frame;
|
||||
|
||||
for (let i = 0; i < frames.length; i++) {
|
||||
frame = frames[i];
|
||||
if (isContent(frame)) {
|
||||
result.push(frame);
|
||||
} else if (last === i) {
|
||||
// Extend here so we're not destructively editing
|
||||
// the original profiler data. Set isMetaCategory `true`,
|
||||
// and ensure we have a category set by default, because that's how
|
||||
// the generalized frame nodes are organized.
|
||||
result.push(extend({ isMetaCategory: true, category: CATEGORY_OTHER }, frame));
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
|
|
@ -31,6 +31,7 @@ support-files =
|
|||
[browser_flame-graph-utils-03.js]
|
||||
[browser_flame-graph-utils-04.js]
|
||||
[browser_flame-graph-utils-05.js]
|
||||
[browser_flame-graph-utils-06.js]
|
||||
[browser_flame-graph-utils-hash.js]
|
||||
[browser_graphs-01.js]
|
||||
[browser_graphs-02.js]
|
||||
|
|
|
@ -0,0 +1,86 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
// Tests that the text displayed is the function name, file name and line number
|
||||
// if applicable.
|
||||
|
||||
let {FlameGraphUtils, FLAME_GRAPH_BLOCK_HEIGHT} = devtools.require("devtools/shared/widgets/FlameGraph");
|
||||
|
||||
add_task(function*() {
|
||||
yield promiseTab("about:blank");
|
||||
yield performTest();
|
||||
gBrowser.removeCurrentTab();
|
||||
});
|
||||
|
||||
function* performTest() {
|
||||
let out = FlameGraphUtils.createFlameGraphDataFromSamples(TEST_DATA, {
|
||||
flattenRecursion: true
|
||||
});
|
||||
|
||||
ok(out, "Some data was outputted properly");
|
||||
is(out.length, 10, "The outputted length is correct.");
|
||||
|
||||
info("Got flame graph data:\n" + out.toSource() + "\n");
|
||||
|
||||
for (let i = 0; i < out.length; i++) {
|
||||
let found = out[i];
|
||||
let expected = EXPECTED_OUTPUT[i];
|
||||
|
||||
is(found.blocks.length, expected.blocks.length,
|
||||
"The correct number of blocks were found in this bucket.");
|
||||
|
||||
for (let j = 0; j < found.blocks.length; j++) {
|
||||
is(found.blocks[j].x, expected.blocks[j].x,
|
||||
"The expected block X position is correct for this frame.");
|
||||
is(found.blocks[j].y, expected.blocks[j].y,
|
||||
"The expected block Y position is correct for this frame.");
|
||||
is(found.blocks[j].width, expected.blocks[j].width,
|
||||
"The expected block width is correct for this frame.");
|
||||
is(found.blocks[j].height, expected.blocks[j].height,
|
||||
"The expected block height is correct for this frame.");
|
||||
is(found.blocks[j].text, expected.blocks[j].text,
|
||||
"The expected block text is correct for this frame.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let TEST_DATA = [{
|
||||
frames: [{
|
||||
location: "A (http://path/to/file.js:10:5"
|
||||
}, {
|
||||
location: "B (http://path/to/file.js:100:5"
|
||||
}],
|
||||
time: 50,
|
||||
}];
|
||||
|
||||
let EXPECTED_OUTPUT = [{
|
||||
blocks: []
|
||||
}, {
|
||||
blocks: []
|
||||
}, {
|
||||
blocks: []
|
||||
}, {
|
||||
blocks: []
|
||||
}, {
|
||||
blocks: [{
|
||||
srcData: {
|
||||
startTime: 0,
|
||||
rawLocation: "A (http://path/to/file.js:10:5)"
|
||||
},
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: 50,
|
||||
height: FLAME_GRAPH_BLOCK_HEIGHT,
|
||||
text: "A (file.js:10)"
|
||||
}]
|
||||
}, {
|
||||
blocks: []
|
||||
}, {
|
||||
blocks: []
|
||||
}, {
|
||||
blocks: []
|
||||
}, {
|
||||
blocks: []
|
||||
}, {
|
||||
blocks: []
|
||||
}];
|
|
@ -9,6 +9,7 @@ const { Promise } = require("resource://gre/modules/Promise.jsm");
|
|||
const { Task } = require("resource://gre/modules/Task.jsm");
|
||||
const { getColor } = require("devtools/shared/theme");
|
||||
const EventEmitter = require("devtools/toolkit/event-emitter");
|
||||
const FrameUtils = require("devtools/shared/profiler/frame-utils");
|
||||
|
||||
const HTML_NS = "http://www.w3.org/1999/xhtml";
|
||||
const GRAPH_SRC = "chrome://browser/content/devtools/graphs-frame.xhtml";
|
||||
|
@ -1021,10 +1022,11 @@ let FlameGraphUtils = {
|
|||
|
||||
// If no frames are available, add a pseudo "idle" block in between.
|
||||
if (options.showIdleBlocks && frames.length == 0) {
|
||||
frames = [{ location: options.showIdleBlocks || "" }];
|
||||
frames = [{ location: options.showIdleBlocks || "", idle: true }];
|
||||
}
|
||||
|
||||
for (let { location } of frames) {
|
||||
for (let frame of frames) {
|
||||
let { location } = frame;
|
||||
let prevFrame = prevFrames[frameIndex];
|
||||
|
||||
// Frames at the same location and the same depth will be reused.
|
||||
|
@ -1045,7 +1047,7 @@ let FlameGraphUtils = {
|
|||
y: frameIndex * FLAME_GRAPH_BLOCK_HEIGHT,
|
||||
width: time - prevTime,
|
||||
height: FLAME_GRAPH_BLOCK_HEIGHT,
|
||||
text: location
|
||||
text: this._formatLabel(frame)
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -1115,6 +1117,30 @@ let FlameGraphUtils = {
|
|||
}
|
||||
|
||||
return hash;
|
||||
},
|
||||
|
||||
/**
|
||||
* Takes a FrameNode and returns a string that should be displayed
|
||||
* in its flame block.
|
||||
*
|
||||
* @param FrameNode frame
|
||||
* @return string
|
||||
*/
|
||||
_formatLabel: function (frame) {
|
||||
// If an idle block, just return the location which will just be "(idle)" text
|
||||
// anyway.
|
||||
if (frame.idle) {
|
||||
return frame.location;
|
||||
}
|
||||
|
||||
let { functionName, fileName, line } = FrameUtils.parseLocation(frame);
|
||||
let label = functionName;
|
||||
|
||||
if (fileName) {
|
||||
label += ` (${fileName}${line != null ? (":" + line) : ""})`;
|
||||
}
|
||||
|
||||
return label;
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -65,6 +65,7 @@ skip-if = e10s # Bug 1055333 - style editor tests disabled with e10s
|
|||
[browser_styleeditor_loading.js]
|
||||
[browser_styleeditor_media_sidebar.js]
|
||||
[browser_styleeditor_media_sidebar_sourcemaps.js]
|
||||
[browser_styleeditor_navigate.js]
|
||||
[browser_styleeditor_new.js]
|
||||
[browser_styleeditor_nostyle.js]
|
||||
[browser_styleeditor_pretty.js]
|
||||
|
|
|
@ -6,11 +6,7 @@
|
|||
const TEST_URI = "http://example.com/browser/browser/devtools/styleeditor/" +
|
||||
"test/browser_styleeditor_cmd_edit.html";
|
||||
|
||||
function test() {
|
||||
return Task.spawn(spawnTest).then(finish, helpers.handleError);
|
||||
}
|
||||
|
||||
function spawnTest() {
|
||||
add_task(function* () {
|
||||
let options = yield helpers.openTab(TEST_URI);
|
||||
yield helpers.openToolbar(options);
|
||||
|
||||
|
@ -203,4 +199,4 @@ function spawnTest() {
|
|||
|
||||
yield helpers.closeToolbar(options);
|
||||
yield helpers.closeTab(options);
|
||||
}
|
||||
});
|
||||
|
|
|
@ -9,8 +9,6 @@
|
|||
const TEST_URL = TEST_BASE_HTTP + "doc_uncached.html";
|
||||
|
||||
add_task(function() {
|
||||
waitForExplicitFinish();
|
||||
|
||||
info("Opening netmonitor");
|
||||
let tab = yield addTab("about:blank");
|
||||
let target = TargetFactory.forTab(tab);
|
||||
|
|
|
@ -2,24 +2,15 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
///////////////////
|
||||
//
|
||||
// Whitelisting this test.
|
||||
// As part of bug 1077403, the leaking uncaught rejection should be fixed.
|
||||
//
|
||||
thisTestLeaksUncaughtRejectionsAndShouldBeFixed("Error: Unknown sheet source");
|
||||
|
||||
// Test that hovering over a simple selector in the style-editor requests the
|
||||
// highlighting of the corresponding nodes
|
||||
|
||||
waitForExplicitFinish();
|
||||
|
||||
const TEST_URL = "data:text/html;charset=utf8," +
|
||||
"<style>div{color:red}</style><div>highlighter test</div>";
|
||||
|
||||
add_task(function*() {
|
||||
let {UI} = yield addTabAndOpenStyleEditors(1, null, TEST_URL);
|
||||
let editor = UI.editors[0];
|
||||
let { ui } = yield openStyleEditorForURL(TEST_URL);
|
||||
let editor = ui.editors[0];
|
||||
|
||||
// Mock the highlighter so we can locally assert that things happened
|
||||
// correctly instead of accessing the highlighter elements
|
||||
|
|
|
@ -1,37 +1,36 @@
|
|||
/* vim: set ts=2 et sw=2 tw=80: */
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
"use strict";
|
||||
|
||||
// Test that style editor loads correctly.
|
||||
|
||||
const TESTCASE_URI = TEST_BASE_HTTP + "longload.html";
|
||||
|
||||
|
||||
function test()
|
||||
{
|
||||
waitForExplicitFinish();
|
||||
|
||||
add_task(function* () {
|
||||
// launch Style Editor right when the tab is created (before load)
|
||||
// this checks that the Style Editor still launches correctly when it is opened
|
||||
// *while* the page is still loading. The Style Editor should not signal that
|
||||
// it is loaded until the accompanying content page is loaded.
|
||||
let tabAdded = addTab(TESTCASE_URI);
|
||||
let target = TargetFactory.forTab(gBrowser.selectedTab);
|
||||
let styleEditorLoaded = gDevTools.showToolbox(target, "styleeditor");
|
||||
|
||||
addTabAndCheckOnStyleEditorAdded(function(panel) {
|
||||
content.location = TESTCASE_URI;
|
||||
}, testEditorAdded);
|
||||
}
|
||||
yield Promise.all([tabAdded, styleEditorLoaded]);
|
||||
|
||||
function testEditorAdded(event, editor)
|
||||
{
|
||||
let root = gPanelWindow.document.querySelector(".splitview-root");
|
||||
let toolbox = gDevTools.getToolbox(target);
|
||||
let panel = toolbox.getPanel("styleeditor");
|
||||
let { panelWindow } = panel;
|
||||
|
||||
let root = panelWindow.document.querySelector(".splitview-root");
|
||||
ok(!root.classList.contains("loading"),
|
||||
"style editor root element does not have 'loading' class name anymore");
|
||||
|
||||
let button = gPanelWindow.document.querySelector(".style-editor-newButton");
|
||||
let button = panelWindow.document.querySelector(".style-editor-newButton");
|
||||
ok(!button.hasAttribute("disabled"),
|
||||
"new style sheet button is enabled");
|
||||
|
||||
button = gPanelWindow.document.querySelector(".style-editor-importButton");
|
||||
button = panelWindow.document.querySelector(".style-editor-importButton");
|
||||
ok(!button.hasAttribute("disabled"),
|
||||
"import button is enabled");
|
||||
|
||||
finish();
|
||||
}
|
||||
});
|
||||
|
|
|
@ -14,31 +14,31 @@ const NEW_RULE = "\n@media (max-width: 600px) { div { color: blue; } }";
|
|||
waitForExplicitFinish();
|
||||
|
||||
add_task(function*() {
|
||||
let {UI} = yield addTabAndOpenStyleEditors(2, null, TESTCASE_URI);
|
||||
let { ui } = yield openStyleEditorForURL(TESTCASE_URI);
|
||||
|
||||
is(UI.editors.length, 2, "correct number of editors");
|
||||
is(ui.editors.length, 2, "correct number of editors");
|
||||
|
||||
// Test first plain css editor
|
||||
let plainEditor = UI.editors[0];
|
||||
let plainEditor = ui.editors[0];
|
||||
yield openEditor(plainEditor);
|
||||
testPlainEditor(plainEditor);
|
||||
|
||||
// Test editor with @media rules
|
||||
let mediaEditor = UI.editors[1];
|
||||
let mediaEditor = ui.editors[1];
|
||||
yield openEditor(mediaEditor);
|
||||
testMediaEditor(mediaEditor);
|
||||
|
||||
// Test that sidebar hides when flipping pref
|
||||
yield testShowHide(UI, mediaEditor);
|
||||
yield testShowHide(ui, mediaEditor);
|
||||
|
||||
// Test adding a rule updates the list
|
||||
yield testMediaRuleAdded(UI, mediaEditor);
|
||||
yield testMediaRuleAdded(ui, mediaEditor);
|
||||
|
||||
// Test resizing and seeing @media matching state change
|
||||
let originalWidth = window.outerWidth;
|
||||
let originalHeight = window.outerHeight;
|
||||
|
||||
let onMatchesChange = listenForMediaChange(UI);
|
||||
let onMatchesChange = listenForMediaChange(ui);
|
||||
window.resizeTo(RESIZE, RESIZE);
|
||||
yield onMatchesChange;
|
||||
|
||||
|
|
|
@ -0,0 +1,37 @@
|
|||
/* vim: set ts=2 et sw=2 tw=80: */
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
"use strict";
|
||||
|
||||
// Test that selected sheet and cursor position is reset during navigation.
|
||||
|
||||
const TESTCASE_URI = TEST_BASE_HTTPS + "simple.html";
|
||||
const NEW_URI = TEST_BASE_HTTPS + "media.html";
|
||||
|
||||
const LINE_NO = 5;
|
||||
const COL_NO = 3;
|
||||
|
||||
add_task(function* () {
|
||||
let { ui } = yield openStyleEditorForURL(TESTCASE_URI);
|
||||
loadCommonFrameScript();
|
||||
|
||||
is(ui.editors.length, 2, "Two sheets present after load.");
|
||||
|
||||
info("Selecting the second editor");
|
||||
yield ui.selectStyleSheet(ui.editors[1].styleSheet, LINE_NO, COL_NO);
|
||||
|
||||
info("Navigating to another page.");
|
||||
executeInContent("devtools:test:navigate", { location: NEW_URI }, {}, false);
|
||||
|
||||
info("Waiting for sheets to be loaded after navigation.");
|
||||
yield ui.once("stylesheets-reset");
|
||||
|
||||
info("Waiting for source editor to be ready.");
|
||||
yield ui.editors[0].getSourceEditor();
|
||||
|
||||
is(ui.selectedEditor, ui.editors[0], "first editor is selected");
|
||||
|
||||
let {line, ch} = ui.selectedEditor.sourceEditor.getCursor();
|
||||
is(line, 0, "first line is selected");
|
||||
is(ch, 0, "first column is selected");
|
||||
});
|
|
@ -1,55 +1,44 @@
|
|||
/* vim: set ts=2 et sw=2 tw=80: */
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
"use strict";
|
||||
|
||||
///////////////////
|
||||
//
|
||||
// Whitelisting this test.
|
||||
// As part of bug 1077403, the leaking uncaught rejection should be fixed.
|
||||
//
|
||||
thisTestLeaksUncaughtRejectionsAndShouldBeFixed("Error: Unknown sheet source");
|
||||
// Test that new sheets can be added and edited.
|
||||
|
||||
const TESTCASE_URI = TEST_BASE_HTTP + "simple.html";
|
||||
|
||||
let TESTCASE_CSS_SOURCE = "body{background-color:red;";
|
||||
|
||||
let gOriginalHref;
|
||||
let gUI;
|
||||
|
||||
waitForExplicitFinish();
|
||||
const TESTCASE_CSS_SOURCE = "body{background-color:red;";
|
||||
|
||||
add_task(function*() {
|
||||
let panel = yield addTabAndOpenStyleEditors(2, null, TESTCASE_URI);
|
||||
gUI = panel.UI;
|
||||
let { panel, ui } = yield openStyleEditorForURL(TESTCASE_URI);
|
||||
|
||||
let editor = yield createNew();
|
||||
let editor = yield createNew(ui, panel.panelWindow);
|
||||
testInitialState(editor);
|
||||
|
||||
let originalHref = editor.styleSheet.href;
|
||||
let waitForPropertyChange = onPropertyChange(editor);
|
||||
|
||||
yield typeInEditor(editor);
|
||||
yield typeInEditor(editor, panel.panelWindow);
|
||||
|
||||
yield waitForPropertyChange;
|
||||
|
||||
testUpdated(editor);
|
||||
|
||||
gUI = null;
|
||||
testUpdated(editor, originalHref);
|
||||
});
|
||||
|
||||
function createNew() {
|
||||
function createNew(ui, panelWindow) {
|
||||
info("Creating a new stylesheet now");
|
||||
let deferred = promise.defer();
|
||||
|
||||
gUI.once("editor-added", (ev, editor) => {
|
||||
ui.once("editor-added", (ev, editor) => {
|
||||
editor.getSourceEditor().then(deferred.resolve);
|
||||
});
|
||||
|
||||
waitForFocus(function () {// create a new style sheet
|
||||
let newButton = gPanelWindow.document.querySelector(".style-editor-newButton");
|
||||
let newButton = panelWindow.document.querySelector(".style-editor-newButton");
|
||||
ok(newButton, "'new' button exists");
|
||||
|
||||
EventUtils.synthesizeMouseAtCenter(newButton, {}, gPanelWindow);
|
||||
}, gPanelWindow);
|
||||
EventUtils.synthesizeMouseAtCenter(newButton, {}, panelWindow);
|
||||
}, panelWindow);
|
||||
|
||||
return deferred.promise;
|
||||
}
|
||||
|
@ -71,7 +60,6 @@ function onPropertyChange(aEditor) {
|
|||
|
||||
function testInitialState(aEditor) {
|
||||
info("Testing the initial state of the new editor");
|
||||
gOriginalHref = aEditor.styleSheet.href;
|
||||
|
||||
let summary = aEditor.summary;
|
||||
|
||||
|
@ -90,22 +78,22 @@ function testInitialState(aEditor) {
|
|||
"content's background color is initially white");
|
||||
}
|
||||
|
||||
function typeInEditor(aEditor) {
|
||||
function typeInEditor(aEditor, panelWindow) {
|
||||
let deferred = promise.defer();
|
||||
|
||||
waitForFocus(function () {
|
||||
for each (let c in TESTCASE_CSS_SOURCE) {
|
||||
EventUtils.synthesizeKey(c, {}, gPanelWindow);
|
||||
for (let c of TESTCASE_CSS_SOURCE) {
|
||||
EventUtils.synthesizeKey(c, {}, panelWindow);
|
||||
}
|
||||
ok(aEditor.unsaved, "new editor has unsaved flag");
|
||||
|
||||
deferred.resolve();
|
||||
}, gPanelWindow);
|
||||
}, panelWindow);
|
||||
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
function testUpdated(aEditor) {
|
||||
function testUpdated(aEditor, originalHref) {
|
||||
info("Testing the state of the new editor after editing it");
|
||||
|
||||
is(aEditor.sourceEditor.getText(), TESTCASE_CSS_SOURCE + "}",
|
||||
|
@ -115,6 +103,6 @@ function testUpdated(aEditor) {
|
|||
is(parseInt(ruleCount), 1,
|
||||
"new editor shows 1 rule after modification");
|
||||
|
||||
is(aEditor.styleSheet.href, gOriginalHref,
|
||||
is(aEditor.styleSheet.href, originalHref,
|
||||
"style sheet href did not change");
|
||||
}
|
||||
|
|
|
@ -1,41 +1,28 @@
|
|||
/* vim: set ts=2 et sw=2 tw=80: */
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
"use strict";
|
||||
|
||||
// Test that 'no styles' indicator is shown if a page doesn't contain any style
|
||||
// sheets.
|
||||
|
||||
const TESTCASE_URI = TEST_BASE_HTTP + "nostyle.html";
|
||||
|
||||
add_task(function* () {
|
||||
let { panel } = yield openStyleEditorForURL(TESTCASE_URI);
|
||||
let { panelWindow } = panel;
|
||||
|
||||
function test()
|
||||
{
|
||||
waitForExplicitFinish();
|
||||
|
||||
// launch Style Editor right when the tab is created (before load)
|
||||
// this checks that the Style Editor still launches correctly when it is opened
|
||||
// *while* the page is still loading. The Style Editor should not signal that
|
||||
// it is loaded until the accompanying content page is loaded.
|
||||
|
||||
addTabAndCheckOnStyleEditorAdded(function(panel) {
|
||||
panel.UI.once("stylesheets-reset", testDocumentLoad);
|
||||
|
||||
content.location = TESTCASE_URI;
|
||||
}, () => {});
|
||||
}
|
||||
|
||||
function testDocumentLoad(event)
|
||||
{
|
||||
let root = gPanelWindow.document.querySelector(".splitview-root");
|
||||
let root = panelWindow.document.querySelector(".splitview-root");
|
||||
ok(!root.classList.contains("loading"),
|
||||
"style editor root element does not have 'loading' class name anymore");
|
||||
|
||||
ok(root.querySelector(".empty.placeholder"), "showing 'no style' indicator");
|
||||
|
||||
let button = gPanelWindow.document.querySelector(".style-editor-newButton");
|
||||
let button = panelWindow.document.querySelector(".style-editor-newButton");
|
||||
ok(!button.hasAttribute("disabled"),
|
||||
"new style sheet button is enabled");
|
||||
|
||||
button = gPanelWindow.document.querySelector(".style-editor-importButton");
|
||||
button = panelWindow.document.querySelector(".style-editor-importButton");
|
||||
ok(!button.hasAttribute("disabled"),
|
||||
"import button is enabled");
|
||||
|
||||
finish();
|
||||
}
|
||||
});
|
||||
|
|
|
@ -1,56 +1,51 @@
|
|||
/* vim: set ts=2 et sw=2 tw=80: */
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
"use strict";
|
||||
|
||||
///////////////////
|
||||
//
|
||||
// Whitelisting this test.
|
||||
// As part of bug 1077403, the leaking uncaught rejection should be fixed.
|
||||
//
|
||||
thisTestLeaksUncaughtRejectionsAndShouldBeFixed("Error: Unknown sheet source");
|
||||
// Test that minified sheets are automatically prettified but other are left
|
||||
// untouched.
|
||||
|
||||
const TESTCASE_URI = TEST_BASE_HTTP + "minified.html";
|
||||
|
||||
let gUI;
|
||||
const PRETTIFIED_SOURCE = "" +
|
||||
"body\{\r?\n" + // body{
|
||||
"\tbackground\:white;\r?\n" + // background:white;
|
||||
"\}\r?\n" + // }
|
||||
"\r?\n" + //
|
||||
"div\{\r?\n" + // div{
|
||||
"\tfont\-size\:4em;\r?\n" + // font-size:4em;
|
||||
"\tcolor\:red\r?\n" + // color:red
|
||||
"\}\r?\n" + // }
|
||||
"\r?\n" + //
|
||||
"span\{\r?\n" + // span{
|
||||
"\tcolor\:green;\r?\n" // color:green;
|
||||
"\}\r?\n"; // }
|
||||
|
||||
function test()
|
||||
{
|
||||
waitForExplicitFinish();
|
||||
const ORIGINAL_SOURCE = "" +
|
||||
"body \{ background\: red; \}\r?\n" + // body { background: red; }
|
||||
"div \{\r?\n" + // div {
|
||||
"font\-size\: 5em;\r?\n" + // font-size: 5em;
|
||||
"color\: red\r?\n" + // color: red
|
||||
"\}"; // }
|
||||
|
||||
addTabAndCheckOnStyleEditorAdded(panel => gUI = panel.UI, editor => {
|
||||
editor.getSourceEditor().then(function() {
|
||||
testEditor(editor);
|
||||
});
|
||||
});
|
||||
add_task(function* () {
|
||||
let { ui } = yield openStyleEditorForURL(TESTCASE_URI);
|
||||
is(ui.editors.length, 2, "Two sheets present.");
|
||||
|
||||
content.location = TESTCASE_URI;
|
||||
}
|
||||
info("Testing minified style sheet.");
|
||||
let editor = yield ui.editors[0].getSourceEditor();
|
||||
|
||||
let editorTestedCount = 0;
|
||||
function testEditor(aEditor)
|
||||
{
|
||||
if (aEditor.styleSheet.styleSheetIndex == 0) {
|
||||
let prettifiedSource = "body\{\r?\n\tbackground\:white;\r?\n\}\r?\n\r?\ndiv\{\r?\n\tfont\-size\:4em;\r?\n\tcolor\:red\r?\n\}\r?\n\r?\nspan\{\r?\n\tcolor\:green;\r?\n\}\r?\n";
|
||||
let prettifiedSourceRE = new RegExp(prettifiedSource);
|
||||
let prettifiedSourceRE = new RegExp(PRETTIFIED_SOURCE);
|
||||
ok(prettifiedSourceRE.test(editor.sourceEditor.getText()),
|
||||
"minified source has been prettified automatically");
|
||||
|
||||
ok(prettifiedSourceRE.test(aEditor.sourceEditor.getText()),
|
||||
"minified source has been prettified automatically");
|
||||
editorTestedCount++;
|
||||
let summary = gUI.editors[1].summary;
|
||||
EventUtils.synthesizeMouseAtCenter(summary, {}, gPanelWindow);
|
||||
}
|
||||
info("Selecting second, non-minified style sheet.");
|
||||
yield ui.selectStyleSheet(ui.editors[1].styleSheet);
|
||||
|
||||
if (aEditor.styleSheet.styleSheetIndex == 1) {
|
||||
let originalSource = "body \{ background\: red; \}\r?\ndiv \{\r?\nfont\-size\: 5em;\r?\ncolor\: red\r?\n\}";
|
||||
let originalSourceRE = new RegExp(originalSource);
|
||||
editor = ui.editors[1];
|
||||
|
||||
ok(originalSourceRE.test(aEditor.sourceEditor.getText()),
|
||||
"non-minified source has been left untouched");
|
||||
editorTestedCount++;
|
||||
}
|
||||
|
||||
if (editorTestedCount == 2) {
|
||||
gUI = null;
|
||||
finish();
|
||||
}
|
||||
}
|
||||
let originalSourceRE = new RegExp(ORIGINAL_SOURCE);
|
||||
ok(originalSourceRE.test(editor.sourceEditor.getText()),
|
||||
"non-minified source has been left untouched");
|
||||
});
|
||||
|
|
|
@ -1,105 +1,38 @@
|
|||
/* vim: set ts=2 et sw=2 tw=80: */
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
"use strict";
|
||||
|
||||
///////////////////
|
||||
//
|
||||
// Whitelisting this test.
|
||||
// As part of bug 1077403, the leaking uncaught rejection should be fixed.
|
||||
//
|
||||
thisTestLeaksUncaughtRejectionsAndShouldBeFixed("Error: Unknown sheet source");
|
||||
// Test that selected sheet and cursor position persists during reload.
|
||||
|
||||
const TESTCASE_URI = TEST_BASE_HTTPS + "simple.html";
|
||||
const NEW_URI = TEST_BASE_HTTPS + "media.html";
|
||||
|
||||
const LINE_NO = 5;
|
||||
const COL_NO = 3;
|
||||
|
||||
let gContentWin;
|
||||
let gUI;
|
||||
add_task(function* () {
|
||||
let { ui } = yield openStyleEditorForURL(TESTCASE_URI);
|
||||
loadCommonFrameScript();
|
||||
|
||||
function test()
|
||||
{
|
||||
waitForExplicitFinish();
|
||||
is(ui.editors.length, 2, "Two sheets present after load.");
|
||||
|
||||
addTabAndOpenStyleEditors(2, function(panel) {
|
||||
gContentWin = gBrowser.selectedBrowser.contentWindow.wrappedJSObject;
|
||||
gUI = panel.UI;
|
||||
gUI.editors[0].getSourceEditor().then(runTests);
|
||||
});
|
||||
info("Selecting the second editor");
|
||||
yield ui.selectStyleSheet(ui.editors[1].styleSheet, LINE_NO, COL_NO);
|
||||
|
||||
content.location = TESTCASE_URI;
|
||||
}
|
||||
info("Reloading page.");
|
||||
executeInContent("devtools:test:reload", {}, {}, false /* no response */);
|
||||
|
||||
function runTests()
|
||||
{
|
||||
let count = 0;
|
||||
gUI.on("editor-selected", function editorSelected(event, editor) {
|
||||
if (editor.styleSheet != gUI.editors[1].styleSheet) {
|
||||
return;
|
||||
}
|
||||
gUI.off("editor-selected", editorSelected);
|
||||
editor.getSourceEditor().then(() => {
|
||||
info("selected second editor, about to reload page");
|
||||
reloadPage();
|
||||
info("Waiting for sheets to be loaded after reload.");
|
||||
yield ui.once("stylesheets-reset");
|
||||
|
||||
gUI.on("editor-added", function editorAdded(event, editor) {
|
||||
if (++count == 2) {
|
||||
info("all editors added after reload");
|
||||
gUI.off("editor-added", editorAdded);
|
||||
gUI.editors[1].getSourceEditor().then(testRemembered);
|
||||
}
|
||||
})
|
||||
});
|
||||
});
|
||||
gUI.selectStyleSheet(gUI.editors[1].styleSheet, LINE_NO, COL_NO);
|
||||
}
|
||||
is(ui.editors.length, 2, "Two sheets present after reload.");
|
||||
|
||||
function testRemembered()
|
||||
{
|
||||
is(gUI.selectedEditor, gUI.editors[1], "second editor is selected");
|
||||
info("Waiting for source editor to be ready.");
|
||||
yield ui.editors[1].getSourceEditor();
|
||||
|
||||
let {line, ch} = gUI.selectedEditor.sourceEditor.getCursor();
|
||||
is(ui.selectedEditor, ui.editors[1], "second editor is selected after reload");
|
||||
|
||||
let {line, ch} = ui.selectedEditor.sourceEditor.getCursor();
|
||||
is(line, LINE_NO, "correct line selected");
|
||||
is(ch, COL_NO, "correct column selected");
|
||||
|
||||
testNewPage();
|
||||
}
|
||||
|
||||
function testNewPage()
|
||||
{
|
||||
let count = 0;
|
||||
gUI.on("editor-added", function editorAdded(event, editor) {
|
||||
info("editor added here")
|
||||
if (++count == 2) {
|
||||
info("all editors added after navigating page");
|
||||
gUI.off("editor-added", editorAdded);
|
||||
gUI.editors[0].getSourceEditor().then(testNotRemembered);
|
||||
}
|
||||
})
|
||||
|
||||
info("navigating to a different page");
|
||||
navigatePage();
|
||||
}
|
||||
|
||||
function testNotRemembered()
|
||||
{
|
||||
is(gUI.selectedEditor, gUI.editors[0], "first editor is selected");
|
||||
|
||||
let {line, ch} = gUI.selectedEditor.sourceEditor.getCursor();
|
||||
is(line, 0, "first line is selected");
|
||||
is(ch, 0, "first column is selected");
|
||||
|
||||
gUI = null;
|
||||
finish();
|
||||
}
|
||||
|
||||
function reloadPage()
|
||||
{
|
||||
gContentWin.location.reload();
|
||||
}
|
||||
|
||||
function navigatePage()
|
||||
{
|
||||
gContentWin.location = NEW_URI;
|
||||
}
|
||||
});
|
||||
|
|
|
@ -1,58 +1,26 @@
|
|||
/* vim: set ts=2 et sw=2 tw=80: */
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
"use strict";
|
||||
|
||||
///////////////////
|
||||
//
|
||||
// Whitelisting this test.
|
||||
// As part of bug 1077403, the leaking uncaught rejection should be fixed.
|
||||
//
|
||||
thisTestLeaksUncaughtRejectionsAndShouldBeFixed("Error: Unknown sheet source");
|
||||
// Test that StyleEditorUI.selectStyleSheet selects the correct sheet, line and
|
||||
// column.
|
||||
|
||||
const TESTCASE_URI = TEST_BASE_HTTPS + "simple.html";
|
||||
const NEW_URI = TEST_BASE_HTTPS + "media.html";
|
||||
|
||||
const LINE_NO = 5;
|
||||
const COL_NO = 0;
|
||||
|
||||
let gContentWin;
|
||||
let gUI;
|
||||
add_task(function* () {
|
||||
let { ui } = yield openStyleEditorForURL(TESTCASE_URI);
|
||||
let editor = ui.editors[1];
|
||||
|
||||
function test()
|
||||
{
|
||||
waitForExplicitFinish();
|
||||
info("Selecting style sheet #1.");
|
||||
yield ui.selectStyleSheet(editor.styleSheet.href, LINE_NO);
|
||||
|
||||
addTabAndOpenStyleEditors(2, function(panel) {
|
||||
gContentWin = gBrowser.selectedBrowser.contentWindow.wrappedJSObject;
|
||||
gUI = panel.UI;
|
||||
gUI.editors[0].getSourceEditor().then(runTests);
|
||||
});
|
||||
is(ui.selectedEditor, ui.editors[1], "Second editor is selected.");
|
||||
let {line, ch} = ui.selectedEditor.sourceEditor.getCursor();
|
||||
|
||||
content.location = TESTCASE_URI;
|
||||
}
|
||||
|
||||
function runTests()
|
||||
{
|
||||
let count = 0;
|
||||
|
||||
// Make sure Editor doesn't go into an infinite loop when
|
||||
// column isn't passed. See bug 941018.
|
||||
gUI.on("editor-selected", function editorSelected(event, editor) {
|
||||
if (editor.styleSheet != gUI.editors[1].styleSheet) {
|
||||
return;
|
||||
}
|
||||
gUI.off("editor-selected", editorSelected);
|
||||
|
||||
editor.getSourceEditor().then(() => {
|
||||
is(gUI.selectedEditor, gUI.editors[1], "second editor is selected");
|
||||
let {line, ch} = gUI.selectedEditor.sourceEditor.getCursor();
|
||||
|
||||
is(line, LINE_NO, "correct line selected");
|
||||
is(ch, COL_NO, "correct column selected");
|
||||
|
||||
gUI = null;
|
||||
finish();
|
||||
});
|
||||
});
|
||||
gUI.selectStyleSheet(gUI.editors[1].styleSheet.href, LINE_NO);
|
||||
}
|
||||
is(line, LINE_NO, "correct line selected");
|
||||
is(ch, COL_NO, "correct column selected");
|
||||
});
|
||||
|
|
|
@ -1,82 +1,67 @@
|
|||
/* vim: set ts=2 et sw=2 tw=80: */
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
"use strict";
|
||||
|
||||
///////////////////
|
||||
//
|
||||
// Whitelisting this test.
|
||||
// As part of bug 1077403, the leaking uncaught rejection should be fixed.
|
||||
//
|
||||
thisTestLeaksUncaughtRejectionsAndShouldBeFixed("Error: Unknown sheet source");
|
||||
// Test that the style sheet list can be navigated with keyboard.
|
||||
|
||||
const TESTCASE_URI = TEST_BASE_HTTP + "four.html";
|
||||
|
||||
let gUI;
|
||||
add_task(function* () {
|
||||
let { panel, ui } = yield openStyleEditorForURL(TESTCASE_URI);
|
||||
|
||||
function test()
|
||||
{
|
||||
waitForExplicitFinish();
|
||||
info("Waiting for source editor to load.");
|
||||
yield ui.editors[0].getSourceEditor();
|
||||
|
||||
addTabAndOpenStyleEditors(4, runTests);
|
||||
let selected = ui.once("editor-selected");
|
||||
|
||||
content.location = TESTCASE_URI;
|
||||
}
|
||||
info("Testing keyboard navigation on the sheet list.");
|
||||
testKeyboardNavigation(ui.editors[0], panel);
|
||||
|
||||
function runTests(panel)
|
||||
{
|
||||
gUI = panel.UI;
|
||||
gUI.editors[0].getSourceEditor().then(onEditor0Attach);
|
||||
gUI.editors[2].getSourceEditor().then(onEditor2Attach);
|
||||
}
|
||||
info("Waiting for editor #2 to be selected due to keyboard navigation.");
|
||||
yield selected;
|
||||
|
||||
ok(ui.editors[2].sourceEditor.hasFocus(), "Editor #2 has focus.");
|
||||
});
|
||||
|
||||
function getStylesheetNameLinkFor(aEditor)
|
||||
{
|
||||
return aEditor.summary.querySelector(".stylesheet-name");
|
||||
}
|
||||
|
||||
function onEditor0Attach(aEditor)
|
||||
function testKeyboardNavigation(aEditor, panel)
|
||||
{
|
||||
let panelWindow = panel.panelWindow;
|
||||
let ui = panel.UI;
|
||||
waitForFocus(function () {
|
||||
let summary = aEditor.summary;
|
||||
EventUtils.synthesizeMouseAtCenter(summary, {}, gPanelWindow);
|
||||
EventUtils.synthesizeMouseAtCenter(summary, {}, panelWindow);
|
||||
|
||||
let item = getStylesheetNameLinkFor(gUI.editors[0]);
|
||||
is(gPanelWindow.document.activeElement, item,
|
||||
let item = getStylesheetNameLinkFor(ui.editors[0]);
|
||||
is(panelWindow.document.activeElement, item,
|
||||
"editor 0 item is the active element");
|
||||
|
||||
EventUtils.synthesizeKey("VK_DOWN", {}, gPanelWindow);
|
||||
item = getStylesheetNameLinkFor(gUI.editors[1]);
|
||||
is(gPanelWindow.document.activeElement, item,
|
||||
EventUtils.synthesizeKey("VK_DOWN", {}, panelWindow);
|
||||
item = getStylesheetNameLinkFor(ui.editors[1]);
|
||||
is(panelWindow.document.activeElement, item,
|
||||
"editor 1 item is the active element");
|
||||
|
||||
EventUtils.synthesizeKey("VK_HOME", {}, gPanelWindow);
|
||||
item = getStylesheetNameLinkFor(gUI.editors[0]);
|
||||
is(gPanelWindow.document.activeElement, item,
|
||||
EventUtils.synthesizeKey("VK_HOME", {}, panelWindow);
|
||||
item = getStylesheetNameLinkFor(ui.editors[0]);
|
||||
is(panelWindow.document.activeElement, item,
|
||||
"fist editor item is the active element");
|
||||
|
||||
EventUtils.synthesizeKey("VK_END", {}, gPanelWindow);
|
||||
item = getStylesheetNameLinkFor(gUI.editors[3]);
|
||||
is(gPanelWindow.document.activeElement, item,
|
||||
EventUtils.synthesizeKey("VK_END", {}, panelWindow);
|
||||
item = getStylesheetNameLinkFor(ui.editors[3]);
|
||||
is(panelWindow.document.activeElement, item,
|
||||
"last editor item is the active element");
|
||||
|
||||
EventUtils.synthesizeKey("VK_UP", {}, gPanelWindow);
|
||||
item = getStylesheetNameLinkFor(gUI.editors[2]);
|
||||
is(gPanelWindow.document.activeElement, item,
|
||||
EventUtils.synthesizeKey("VK_UP", {}, panelWindow);
|
||||
item = getStylesheetNameLinkFor(ui.editors[2]);
|
||||
is(panelWindow.document.activeElement, item,
|
||||
"editor 2 item is the active element");
|
||||
|
||||
EventUtils.synthesizeKey("VK_RETURN", {}, gPanelWindow);
|
||||
EventUtils.synthesizeKey("VK_RETURN", {}, panelWindow);
|
||||
// this will attach and give focus editor 2
|
||||
}, gPanelWindow);
|
||||
}
|
||||
|
||||
function onEditor2Attach(aEditor)
|
||||
{
|
||||
// Wait for the focus to be set.
|
||||
executeSoon(function () {
|
||||
ok(aEditor.sourceEditor.hasFocus(),
|
||||
"editor 2 has focus");
|
||||
|
||||
gUI = null;
|
||||
finish();
|
||||
});
|
||||
}, panelWindow);
|
||||
}
|
||||
|
|
|
@ -4,16 +4,14 @@
|
|||
|
||||
const TESTCASE_URI = TEST_BASE_HTTPS + "simple.html";
|
||||
|
||||
waitForExplicitFinish();
|
||||
|
||||
const NEW_RULE = "body { background-color: purple; }";
|
||||
|
||||
add_task(function*() {
|
||||
let {UI} = yield addTabAndOpenStyleEditors(2, null, TESTCASE_URI);
|
||||
let { ui } = yield openStyleEditorForURL(TESTCASE_URI);
|
||||
|
||||
is(UI.editors.length, 2, "correct number of editors");
|
||||
is(ui.editors.length, 2, "correct number of editors");
|
||||
|
||||
let editor = UI.editors[0];
|
||||
let editor = ui.editors[0];
|
||||
yield openEditor(editor);
|
||||
|
||||
// Set text twice in a row
|
||||
|
|
|
@ -179,13 +179,22 @@ let UI = {
|
|||
UI.updateCommands();
|
||||
UI.updateProjectButton();
|
||||
UI.openProject();
|
||||
UI.autoStartProject();
|
||||
yield UI.autoStartProject();
|
||||
UI.autoOpenToolbox();
|
||||
UI.saveLastSelectedProject();
|
||||
projectList.update();
|
||||
});
|
||||
return;
|
||||
case "project-stopped":
|
||||
case "project-started":
|
||||
this.updateCommands();
|
||||
projectList.update();
|
||||
UI.autoOpenToolbox();
|
||||
break;
|
||||
case "project-stopped":
|
||||
UI.destroyToolbox();
|
||||
this.updateCommands();
|
||||
projectList.update();
|
||||
break;
|
||||
case "runtime-global-actors":
|
||||
this.updateCommands();
|
||||
projectList.update();
|
||||
|
@ -657,7 +666,7 @@ let UI = {
|
|||
}, console.error);
|
||||
},
|
||||
|
||||
autoStartProject: function() {
|
||||
autoStartProject: Task.async(function*() {
|
||||
let project = AppManager.selectedProject;
|
||||
|
||||
if (!project) {
|
||||
|
@ -669,15 +678,27 @@ let UI = {
|
|||
return; // For something that is not an editable app, we're done.
|
||||
}
|
||||
|
||||
Task.spawn(function() {
|
||||
// Do not force opening apps that are already running, as they may have
|
||||
// some activity being opened and don't want to dismiss them.
|
||||
if (project.type == "runtimeApp" && !AppManager.isProjectRunning()) {
|
||||
yield UI.busyUntil(AppManager.launchRuntimeApp(), "running app");
|
||||
}
|
||||
yield UI.createToolbox();
|
||||
});
|
||||
},
|
||||
// Do not force opening apps that are already running, as they may have
|
||||
// some activity being opened and don't want to dismiss them.
|
||||
if (project.type == "runtimeApp" && !AppManager.isProjectRunning()) {
|
||||
yield UI.busyUntil(AppManager.launchRuntimeApp(), "running app");
|
||||
}
|
||||
}),
|
||||
|
||||
autoOpenToolbox: Task.async(function*() {
|
||||
let project = AppManager.selectedProject;
|
||||
|
||||
if (!project) {
|
||||
return;
|
||||
}
|
||||
if (!(project.type == "runtimeApp" ||
|
||||
project.type == "mainProcess" ||
|
||||
project.type == "tab")) {
|
||||
return; // For something that is not an editable app, we're done.
|
||||
}
|
||||
|
||||
yield UI.createToolbox();
|
||||
}),
|
||||
|
||||
importAndSelectApp: Task.async(function* (source) {
|
||||
let isPackaged = !!source.path;
|
||||
|
@ -1015,6 +1036,7 @@ let UI = {
|
|||
let panel = document.querySelector("#deck").selectedPanel;
|
||||
let nbox = document.querySelector("#notificationbox");
|
||||
if (panel && panel.id == "deck-panel-details" &&
|
||||
AppManager.selectedProject &&
|
||||
AppManager.selectedProject.type != "packaged" &&
|
||||
this.toolboxIframe) {
|
||||
nbox.setAttribute("toolboxfullscreen", "true");
|
||||
|
|
|
@ -249,6 +249,7 @@ cancel_button=Cancel
|
|||
cannot_start_call_session_not_ready=Can't start call, session is not ready.
|
||||
network_disconnected=The network connection terminated abruptly.
|
||||
connection_error_see_console_notification=Call failed; see console for details.
|
||||
no_media_failure_message=No camera or microphone found.
|
||||
|
||||
## LOCALIZATION NOTE (legal_text_and_links3): In this item, don't translate the
|
||||
## parts between {{..}} because these will be replaced with links with the labels
|
||||
|
|
|
@ -201,7 +201,7 @@ let AboutHome = {
|
|||
|
||||
// Trigger a search through nsISearchEngine.getSubmission()
|
||||
let submission = engine.getSubmission(data.searchTerms, null, "homepage");
|
||||
let where = data.useNewTab ? "tab" : "current";
|
||||
let where = data.useNewTab ? "tabshifted" : "current";
|
||||
window.openUILinkIn(submission.uri.spec, where, false,
|
||||
submission.postData);
|
||||
|
||||
|
|
|
@ -226,9 +226,6 @@ this.ContentSearch = {
|
|||
// method on it will throw.
|
||||
return Promise.resolve();
|
||||
}
|
||||
if (data.useNewTab) {
|
||||
browser.getTabBrowser().selectedTab = newTab;
|
||||
}
|
||||
let win = browser.ownerDocument.defaultView;
|
||||
win.BrowserSearch.recordSearchInHealthReport(engine, data.whence,
|
||||
data.selection || null);
|
||||
|
|
Двоичные данные
browser/themes/shared/icon.png
Двоичные данные
browser/themes/shared/icon.png
Двоичный файл не отображается.
До Ширина: | Высота: | Размер: 1.8 KiB После Ширина: | Высота: | Размер: 2.0 KiB |
|
@ -4,6 +4,7 @@
|
|||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "BluetoothReplyRunnable.h"
|
||||
#include "BluetoothService.h"
|
||||
#include "BluetoothUtils.h"
|
||||
#include "mozilla/dom/BluetoothGattCharacteristicBinding.h"
|
||||
|
@ -12,6 +13,7 @@
|
|||
#include "mozilla/dom/bluetooth/BluetoothGattDescriptor.h"
|
||||
#include "mozilla/dom/bluetooth/BluetoothGattService.h"
|
||||
#include "mozilla/dom/bluetooth/BluetoothTypes.h"
|
||||
#include "mozilla/dom/Promise.h"
|
||||
|
||||
using namespace mozilla;
|
||||
using namespace mozilla::dom;
|
||||
|
@ -58,6 +60,64 @@ BluetoothGattCharacteristic::~BluetoothGattCharacteristic()
|
|||
bs->UnregisterBluetoothSignalHandler(path, this);
|
||||
}
|
||||
|
||||
already_AddRefed<Promise>
|
||||
BluetoothGattCharacteristic::StartNotifications(ErrorResult& aRv)
|
||||
{
|
||||
nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(GetParentObject());
|
||||
if (!global) {
|
||||
aRv.Throw(NS_ERROR_FAILURE);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
nsRefPtr<Promise> promise = Promise::Create(global, aRv);
|
||||
NS_ENSURE_TRUE(!aRv.Failed(), nullptr);
|
||||
|
||||
BluetoothService* bs = BluetoothService::Get();
|
||||
BT_ENSURE_TRUE_REJECT(bs, NS_ERROR_NOT_AVAILABLE);
|
||||
BT_ENSURE_TRUE_REJECT(mService, NS_ERROR_NOT_AVAILABLE);
|
||||
|
||||
nsRefPtr<BluetoothReplyRunnable> result =
|
||||
new BluetoothVoidReplyRunnable(
|
||||
nullptr /* DOMRequest */,
|
||||
promise,
|
||||
NS_LITERAL_STRING("GattClientStartNotifications"));
|
||||
bs->GattClientStartNotificationsInternal(mService->GetAppUuid(),
|
||||
mService->GetServiceId(),
|
||||
mCharId,
|
||||
result);
|
||||
|
||||
return promise.forget();
|
||||
}
|
||||
|
||||
already_AddRefed<Promise>
|
||||
BluetoothGattCharacteristic::StopNotifications(ErrorResult& aRv)
|
||||
{
|
||||
nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(GetParentObject());
|
||||
if (!global) {
|
||||
aRv.Throw(NS_ERROR_FAILURE);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
nsRefPtr<Promise> promise = Promise::Create(global, aRv);
|
||||
NS_ENSURE_TRUE(!aRv.Failed(), nullptr);
|
||||
|
||||
BluetoothService* bs = BluetoothService::Get();
|
||||
BT_ENSURE_TRUE_REJECT(bs, NS_ERROR_NOT_AVAILABLE);
|
||||
BT_ENSURE_TRUE_REJECT(mService, NS_ERROR_NOT_AVAILABLE);
|
||||
|
||||
nsRefPtr<BluetoothReplyRunnable> result =
|
||||
new BluetoothVoidReplyRunnable(
|
||||
nullptr /* DOMRequest */,
|
||||
promise,
|
||||
NS_LITERAL_STRING("GattClientStopNotifications"));
|
||||
bs->GattClientStopNotificationsInternal(mService->GetAppUuid(),
|
||||
mService->GetServiceId(),
|
||||
mCharId,
|
||||
result);
|
||||
|
||||
return promise.forget();
|
||||
}
|
||||
|
||||
void
|
||||
BluetoothGattCharacteristic::HandleDescriptorsDiscovered(
|
||||
const BluetoothValue& aValue)
|
||||
|
|
|
@ -15,6 +15,12 @@
|
|||
#include "nsWrapperCache.h"
|
||||
#include "nsPIDOMWindow.h"
|
||||
|
||||
namespace mozilla {
|
||||
namespace dom {
|
||||
class Promise;
|
||||
}
|
||||
}
|
||||
|
||||
BEGIN_BLUETOOTH_NAMESPACE
|
||||
|
||||
class BluetoothGattService;
|
||||
|
@ -53,6 +59,12 @@ public:
|
|||
return mCharId.mInstanceId;
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* Methods (Web API Implementation)
|
||||
***************************************************************************/
|
||||
already_AddRefed<Promise> StartNotifications(ErrorResult& aRv);
|
||||
already_AddRefed<Promise> StopNotifications(ErrorResult& aRv);
|
||||
|
||||
/****************************************************************************
|
||||
* Others
|
||||
***************************************************************************/
|
||||
|
|
|
@ -953,7 +953,7 @@ public:
|
|||
const nsAString& aPinCode,
|
||||
BluetoothResultHandler* aRes) = 0;
|
||||
|
||||
virtual void SspReply(const nsAString& aBdAddr, const nsAString& aVariant,
|
||||
virtual void SspReply(const nsAString& aBdAddr, BluetoothSspVariant aVariant,
|
||||
bool aAccept, uint32_t aPasskey,
|
||||
BluetoothResultHandler* aRes) = 0;
|
||||
|
||||
|
|
|
@ -75,7 +75,7 @@ BluetoothPairingHandle::SetPinCode(const nsAString& aPinCode, ErrorResult& aRv)
|
|||
nsRefPtr<Promise> promise = Promise::Create(global, aRv);
|
||||
NS_ENSURE_TRUE(!aRv.Failed(), nullptr);
|
||||
|
||||
BT_ENSURE_TRUE_REJECT(mType.EqualsLiteral("enterpincodereq"),
|
||||
BT_ENSURE_TRUE_REJECT(mType.EqualsLiteral(PAIRING_REQ_TYPE_ENTERPINCODE),
|
||||
NS_ERROR_DOM_INVALID_STATE_ERR);
|
||||
|
||||
BluetoothService* bs = BluetoothService::Get();
|
||||
|
@ -85,13 +85,13 @@ BluetoothPairingHandle::SetPinCode(const nsAString& aPinCode, ErrorResult& aRv)
|
|||
new BluetoothVoidReplyRunnable(nullptr /* DOMRequest */,
|
||||
promise,
|
||||
NS_LITERAL_STRING("SetPinCode"));
|
||||
bs->SetPinCodeInternal(mDeviceAddress, aPinCode, result);
|
||||
bs->PinReplyInternal(mDeviceAddress, true /* accept */, aPinCode, result);
|
||||
|
||||
return promise.forget();
|
||||
}
|
||||
|
||||
already_AddRefed<Promise>
|
||||
BluetoothPairingHandle::SetPairingConfirmation(bool aConfirm, ErrorResult& aRv)
|
||||
BluetoothPairingHandle::Accept(ErrorResult& aRv)
|
||||
{
|
||||
nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(GetParentObject());
|
||||
if (!global) {
|
||||
|
@ -102,24 +102,79 @@ BluetoothPairingHandle::SetPairingConfirmation(bool aConfirm, ErrorResult& aRv)
|
|||
nsRefPtr<Promise> promise = Promise::Create(global, aRv);
|
||||
NS_ENSURE_TRUE(!aRv.Failed(), nullptr);
|
||||
|
||||
BT_ENSURE_TRUE_REJECT(mType.EqualsLiteral("pairingconfirmationreq"),
|
||||
BT_ENSURE_TRUE_REJECT(mType.EqualsLiteral(PAIRING_REQ_TYPE_CONFIRMATION) ||
|
||||
mType.EqualsLiteral(PAIRING_REQ_TYPE_CONSENT),
|
||||
NS_ERROR_DOM_INVALID_STATE_ERR);
|
||||
|
||||
BluetoothService* bs = BluetoothService::Get();
|
||||
BT_ENSURE_TRUE_REJECT(bs, NS_ERROR_NOT_AVAILABLE);
|
||||
|
||||
BluetoothSspVariant variant;
|
||||
BT_ENSURE_TRUE_REJECT(GetSspVariant(variant), NS_ERROR_DOM_OPERATION_ERR);
|
||||
|
||||
nsRefPtr<BluetoothReplyRunnable> result =
|
||||
new BluetoothVoidReplyRunnable(nullptr /* DOMRequest */,
|
||||
promise,
|
||||
NS_LITERAL_STRING("Accept"));
|
||||
bs->SspReplyInternal(
|
||||
mDeviceAddress, variant, true /* aAccept */, result);
|
||||
|
||||
return promise.forget();
|
||||
}
|
||||
|
||||
already_AddRefed<Promise>
|
||||
BluetoothPairingHandle::Reject(ErrorResult& aRv)
|
||||
{
|
||||
nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(GetParentObject());
|
||||
if (!global) {
|
||||
aRv.Throw(NS_ERROR_FAILURE);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
nsRefPtr<Promise> promise = Promise::Create(global, aRv);
|
||||
NS_ENSURE_TRUE(!aRv.Failed(), nullptr);
|
||||
|
||||
BluetoothService* bs = BluetoothService::Get();
|
||||
BT_ENSURE_TRUE_REJECT(bs, NS_ERROR_NOT_AVAILABLE);
|
||||
|
||||
nsRefPtr<BluetoothReplyRunnable> result =
|
||||
new BluetoothVoidReplyRunnable(nullptr /* DOMRequest */,
|
||||
promise,
|
||||
NS_LITERAL_STRING(
|
||||
"SetPairingConfirmation"));
|
||||
NS_LITERAL_STRING("Reject"));
|
||||
|
||||
if (mType.EqualsLiteral(PAIRING_REQ_TYPE_ENTERPINCODE)) { // Pin request
|
||||
bs->PinReplyInternal(
|
||||
mDeviceAddress, false /* aAccept */, EmptyString(), result);
|
||||
} else { // Ssp request
|
||||
BluetoothSspVariant variant;
|
||||
BT_ENSURE_TRUE_REJECT(GetSspVariant(variant), NS_ERROR_DOM_OPERATION_ERR);
|
||||
|
||||
bs->SspReplyInternal(
|
||||
mDeviceAddress, variant, false /* aAccept */, result);
|
||||
}
|
||||
|
||||
bs->SetPairingConfirmationInternal(mDeviceAddress,
|
||||
aConfirm,
|
||||
result);
|
||||
return promise.forget();
|
||||
}
|
||||
|
||||
bool
|
||||
BluetoothPairingHandle::GetSspVariant(BluetoothSspVariant& aVariant)
|
||||
{
|
||||
if (mType.EqualsLiteral(PAIRING_REQ_TYPE_DISPLAYPASSKEY)) {
|
||||
aVariant = BluetoothSspVariant::SSP_VARIANT_PASSKEY_NOTIFICATION;
|
||||
} else if (mType.EqualsLiteral(PAIRING_REQ_TYPE_CONFIRMATION)) {
|
||||
aVariant = BluetoothSspVariant::SSP_VARIANT_PASSKEY_CONFIRMATION;
|
||||
} else if (mType.EqualsLiteral(PAIRING_REQ_TYPE_CONSENT)) {
|
||||
aVariant = BluetoothSspVariant::SSP_VARIANT_CONSENT;
|
||||
} else {
|
||||
BT_LOGR("Invalid SSP variant name: %s",
|
||||
NS_ConvertUTF16toUTF8(mType).get());
|
||||
aVariant = SSP_VARIANT_PASSKEY_CONFIRMATION; // silences compiler warning
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
JSObject*
|
||||
BluetoothPairingHandle::WrapObject(JSContext* aCx,
|
||||
JS::Handle<JSObject*> aGivenProto)
|
||||
|
|
|
@ -47,11 +47,15 @@ public:
|
|||
aPasskey = mPasskey;
|
||||
}
|
||||
|
||||
// Reply to the enterpincodereq pairing request
|
||||
already_AddRefed<Promise>
|
||||
SetPinCode(const nsAString& aPinCode, ErrorResult& aRv);
|
||||
|
||||
already_AddRefed<Promise>
|
||||
SetPairingConfirmation(bool aConfirm, ErrorResult& aRv);
|
||||
// Accept the pairingconfirmationreq or pairingconsentreq pairing request
|
||||
already_AddRefed<Promise> Accept(ErrorResult& aRv);
|
||||
|
||||
// Reject the pairing request
|
||||
already_AddRefed<Promise> Reject(ErrorResult& aRv);
|
||||
|
||||
private:
|
||||
BluetoothPairingHandle(nsPIDOMWindow* aOwner,
|
||||
|
@ -60,6 +64,15 @@ private:
|
|||
const nsAString& aPasskey);
|
||||
~BluetoothPairingHandle();
|
||||
|
||||
/**
|
||||
* Map mType into a BluetoothSspVariant enum value.
|
||||
*
|
||||
* @param aVariant [out] BluetoothSspVariant value mapped from mType.
|
||||
* @return a boolean value to indicate whether mType can map into a
|
||||
* BluetoothSspVariant value.
|
||||
*/
|
||||
bool GetSspVariant(BluetoothSspVariant& aVariant);
|
||||
|
||||
nsCOMPtr<nsPIDOMWindow> mOwner;
|
||||
nsString mDeviceAddress;
|
||||
nsString mType;
|
||||
|
|
|
@ -236,13 +236,35 @@ public:
|
|||
BluetoothProfileManagerBase* aManager) = 0;
|
||||
|
||||
virtual void
|
||||
SetPinCodeInternal(const nsAString& aDeviceAddress, const nsAString& aPinCode,
|
||||
PinReplyInternal(const nsAString& aDeviceAddress,
|
||||
bool aAccept,
|
||||
const nsAString& aPinCode,
|
||||
BluetoothReplyRunnable* aRunnable) = 0;
|
||||
|
||||
virtual void
|
||||
SspReplyInternal(const nsAString& aDeviceAddress,
|
||||
BluetoothSspVariant aVariant,
|
||||
bool aAccept,
|
||||
BluetoothReplyRunnable* aRunnable) = 0;
|
||||
|
||||
/**
|
||||
* Legacy method used by bluez only to reply pincode request.
|
||||
*/
|
||||
virtual void
|
||||
SetPinCodeInternal(const nsAString& aDeviceAddress,
|
||||
const nsAString& aPinCode,
|
||||
BluetoothReplyRunnable* aRunnable) = 0;
|
||||
|
||||
/**
|
||||
* Legacy method used by bluez only to reply passkey entry request.
|
||||
*/
|
||||
virtual void
|
||||
SetPasskeyInternal(const nsAString& aDeviceAddress, uint32_t aPasskey,
|
||||
BluetoothReplyRunnable* aRunnable) = 0;
|
||||
|
||||
/**
|
||||
* Legacy method used by bluez only to reply pairing confirmation request.
|
||||
*/
|
||||
virtual void
|
||||
SetPairingConfirmationInternal(const nsAString& aDeviceAddress, bool aConfirm,
|
||||
BluetoothReplyRunnable* aRunnable) = 0;
|
||||
|
@ -350,6 +372,26 @@ public:
|
|||
DiscoverGattServicesInternal(const nsAString& aAppUuid,
|
||||
BluetoothReplyRunnable* aRunnable) = 0;
|
||||
|
||||
/**
|
||||
* Enable notifications of a given GATT characteristic.
|
||||
* (platform specific implementation)
|
||||
*/
|
||||
virtual void
|
||||
GattClientStartNotificationsInternal(const nsAString& aAppUuid,
|
||||
const BluetoothGattServiceId& aServId,
|
||||
const BluetoothGattId& aCharId,
|
||||
BluetoothReplyRunnable* aRunnable) = 0;
|
||||
|
||||
/**
|
||||
* Disable notifications of a given GATT characteristic.
|
||||
* (platform specific implementation)
|
||||
*/
|
||||
virtual void
|
||||
GattClientStopNotificationsInternal(const nsAString& aAppUuid,
|
||||
const BluetoothGattServiceId& aServId,
|
||||
const BluetoothGattId& aCharId,
|
||||
BluetoothReplyRunnable* aRunnable) = 0;
|
||||
|
||||
/**
|
||||
* Unregister a GATT client. (platform specific implementation)
|
||||
*/
|
||||
|
|
|
@ -583,25 +583,6 @@ Convert(const nsAString& aIn, BluetoothServiceName& aOut)
|
|||
return NS_OK;
|
||||
}
|
||||
|
||||
nsresult
|
||||
Convert(const nsAString& aIn, BluetoothSspVariant& aOut)
|
||||
{
|
||||
if (aIn.EqualsLiteral("PasskeyConfirmation")) {
|
||||
aOut = SSP_VARIANT_PASSKEY_CONFIRMATION;
|
||||
} else if (aIn.EqualsLiteral("PasskeyEntry")) {
|
||||
aOut = SSP_VARIANT_PASSKEY_ENTRY;
|
||||
} else if (aIn.EqualsLiteral("Consent")) {
|
||||
aOut = SSP_VARIANT_CONSENT;
|
||||
} else if (aIn.EqualsLiteral("PasskeyNotification")) {
|
||||
aOut = SSP_VARIANT_PASSKEY_NOTIFICATION;
|
||||
} else {
|
||||
BT_LOGR("Invalid SSP variant name: %s", NS_ConvertUTF16toUTF8(aIn).get());
|
||||
aOut = SSP_VARIANT_PASSKEY_CONFIRMATION; // silences compiler warning
|
||||
return NS_ERROR_ILLEGAL_VALUE;
|
||||
}
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsresult
|
||||
Convert(BluetoothAclState aIn, bool& aOut)
|
||||
{
|
||||
|
|
|
@ -221,9 +221,6 @@ Convert(const nsAString& aIn, BluetoothPropertyType& aOut);
|
|||
nsresult
|
||||
Convert(const nsAString& aIn, BluetoothServiceName& aOut);
|
||||
|
||||
nsresult
|
||||
Convert(const nsAString& aIn, BluetoothSspVariant& aOut);
|
||||
|
||||
nsresult
|
||||
Convert(BluetoothAclState aIn, bool& aOut);
|
||||
|
||||
|
|
|
@ -569,7 +569,7 @@ public:
|
|||
return rv;
|
||||
}
|
||||
|
||||
nsresult SspReplyCmd(const nsAString& aBdAddr, const nsAString& aVariant,
|
||||
nsresult SspReplyCmd(const nsAString& aBdAddr, BluetoothSspVariant aVariant,
|
||||
bool aAccept, uint32_t aPasskey,
|
||||
BluetoothResultHandler* aRes)
|
||||
{
|
||||
|
@ -580,8 +580,7 @@ public:
|
|||
|
||||
nsresult rv = PackPDU(
|
||||
PackConversion<nsAString, BluetoothAddress>(aBdAddr),
|
||||
PackConversion<nsAString, BluetoothSspVariant>(aVariant),
|
||||
aAccept, aPasskey, *pdu);
|
||||
aVariant, aAccept, aPasskey, *pdu);
|
||||
if (NS_FAILED(rv)) {
|
||||
return rv;
|
||||
}
|
||||
|
@ -2420,7 +2419,7 @@ BluetoothDaemonInterface::PinReply(const nsAString& aBdAddr, bool aAccept,
|
|||
|
||||
void
|
||||
BluetoothDaemonInterface::SspReply(const nsAString& aBdAddr,
|
||||
const nsAString& aVariant,
|
||||
BluetoothSspVariant aVariant,
|
||||
bool aAccept, uint32_t aPasskey,
|
||||
BluetoothResultHandler* aRes)
|
||||
{
|
||||
|
|
|
@ -83,7 +83,7 @@ public:
|
|||
const nsAString& aPinCode,
|
||||
BluetoothResultHandler* aRes);
|
||||
|
||||
void SspReply(const nsAString& aBdAddr, const nsAString& aVariant,
|
||||
void SspReply(const nsAString& aBdAddr, BluetoothSspVariant aVariant,
|
||||
bool aAccept, uint32_t aPasskey,
|
||||
BluetoothResultHandler* aRes);
|
||||
|
||||
|
|
|
@ -58,6 +58,8 @@ public:
|
|||
mDiscoverRunnable = nullptr;
|
||||
mUnregisterClientRunnable = nullptr;
|
||||
mReadRemoteRssiRunnable = nullptr;
|
||||
mRegisterNotificationsRunnable = nullptr;
|
||||
mDeregisterNotificationsRunnable = nullptr;
|
||||
}
|
||||
|
||||
void NotifyDiscoverCompleted(bool aSuccess)
|
||||
|
@ -98,6 +100,8 @@ public:
|
|||
nsRefPtr<BluetoothReplyRunnable> mDisconnectRunnable;
|
||||
nsRefPtr<BluetoothReplyRunnable> mDiscoverRunnable;
|
||||
nsRefPtr<BluetoothReplyRunnable> mReadRemoteRssiRunnable;
|
||||
nsRefPtr<BluetoothReplyRunnable> mRegisterNotificationsRunnable;
|
||||
nsRefPtr<BluetoothReplyRunnable> mDeregisterNotificationsRunnable;
|
||||
nsRefPtr<BluetoothReplyRunnable> mUnregisterClientRunnable;
|
||||
|
||||
/**
|
||||
|
@ -636,6 +640,151 @@ BluetoothGattManager::ReadRemoteRssi(int aClientIf,
|
|||
new ReadRemoteRssiResultHandler(client));
|
||||
}
|
||||
|
||||
class BluetoothGattManager::RegisterNotificationsResultHandler final
|
||||
: public BluetoothGattClientResultHandler
|
||||
{
|
||||
public:
|
||||
RegisterNotificationsResultHandler(BluetoothGattClient* aClient)
|
||||
: mClient(aClient)
|
||||
{
|
||||
MOZ_ASSERT(mClient);
|
||||
}
|
||||
|
||||
void RegisterNotification() override
|
||||
{
|
||||
MOZ_ASSERT(mClient->mRegisterNotificationsRunnable);
|
||||
|
||||
/**
|
||||
* Resolve the promise directly if we successfully issued this request to
|
||||
* stack.
|
||||
*
|
||||
* We resolve the promise here since bluedroid stack always returns
|
||||
* incorrect connId in |RegisterNotificationNotification| and we cannot map
|
||||
* back to the target client because of it.
|
||||
* Please see Bug 1149043 for more information.
|
||||
*/
|
||||
DispatchReplySuccess(mClient->mRegisterNotificationsRunnable);
|
||||
mClient->mRegisterNotificationsRunnable = nullptr;
|
||||
}
|
||||
|
||||
void OnError(BluetoothStatus aStatus) override
|
||||
{
|
||||
BT_WARNING(
|
||||
"BluetoothGattClientInterface::RegisterNotifications failed: %d",
|
||||
(int)aStatus);
|
||||
MOZ_ASSERT(mClient->mRegisterNotificationsRunnable);
|
||||
|
||||
DispatchReplyError(mClient->mRegisterNotificationsRunnable,
|
||||
NS_LITERAL_STRING("RegisterNotifications failed"));
|
||||
mClient->mRegisterNotificationsRunnable = nullptr;
|
||||
}
|
||||
|
||||
private:
|
||||
nsRefPtr<BluetoothGattClient> mClient;
|
||||
};
|
||||
|
||||
void
|
||||
BluetoothGattManager::RegisterNotifications(
|
||||
const nsAString& aAppUuid, const BluetoothGattServiceId& aServId,
|
||||
const BluetoothGattId& aCharId, BluetoothReplyRunnable* aRunnable)
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
MOZ_ASSERT(aRunnable);
|
||||
|
||||
ENSURE_GATT_CLIENT_INTF_IS_READY_VOID(aRunnable);
|
||||
|
||||
size_t index = sClients->IndexOf(aAppUuid, 0 /* Start */, UuidComparator());
|
||||
MOZ_ASSERT(index != sClients->NoIndex);
|
||||
|
||||
nsRefPtr<BluetoothGattClient> client = sClients->ElementAt(index);
|
||||
|
||||
// Reject the request if there is an ongoing request or client is already
|
||||
// disconnected
|
||||
if (client->mRegisterNotificationsRunnable || client->mConnId <= 0) {
|
||||
DispatchReplyError(aRunnable,
|
||||
NS_LITERAL_STRING("RegisterNotifications failed"));
|
||||
return;
|
||||
}
|
||||
|
||||
client->mRegisterNotificationsRunnable = aRunnable;
|
||||
|
||||
sBluetoothGattClientInterface->RegisterNotification(
|
||||
client->mClientIf, client->mDeviceAddr, aServId, aCharId,
|
||||
new RegisterNotificationsResultHandler(client));
|
||||
}
|
||||
|
||||
class BluetoothGattManager::DeregisterNotificationsResultHandler final
|
||||
: public BluetoothGattClientResultHandler
|
||||
{
|
||||
public:
|
||||
DeregisterNotificationsResultHandler(BluetoothGattClient* aClient)
|
||||
: mClient(aClient)
|
||||
{
|
||||
MOZ_ASSERT(mClient);
|
||||
}
|
||||
|
||||
void DeregisterNotification() override
|
||||
{
|
||||
MOZ_ASSERT(mClient->mDeregisterNotificationsRunnable);
|
||||
|
||||
/**
|
||||
* Resolve the promise directly if we successfully issued this request to
|
||||
* stack.
|
||||
*
|
||||
* We resolve the promise here since bluedroid stack always returns
|
||||
* incorrect connId in |RegisterNotificationNotification| and we cannot map
|
||||
* back to the target client because of it.
|
||||
* Please see Bug 1149043 for more information.
|
||||
*/
|
||||
DispatchReplySuccess(mClient->mDeregisterNotificationsRunnable);
|
||||
mClient->mDeregisterNotificationsRunnable = nullptr;
|
||||
}
|
||||
|
||||
void OnError(BluetoothStatus aStatus) override
|
||||
{
|
||||
BT_WARNING(
|
||||
"BluetoothGattClientInterface::DeregisterNotifications failed: %d",
|
||||
(int)aStatus);
|
||||
MOZ_ASSERT(mClient->mDeregisterNotificationsRunnable);
|
||||
|
||||
DispatchReplyError(mClient->mDeregisterNotificationsRunnable,
|
||||
NS_LITERAL_STRING("DeregisterNotifications failed"));
|
||||
mClient->mDeregisterNotificationsRunnable = nullptr;
|
||||
}
|
||||
|
||||
private:
|
||||
nsRefPtr<BluetoothGattClient> mClient;
|
||||
};
|
||||
|
||||
void
|
||||
BluetoothGattManager::DeregisterNotifications(
|
||||
const nsAString& aAppUuid, const BluetoothGattServiceId& aServId,
|
||||
const BluetoothGattId& aCharId, BluetoothReplyRunnable* aRunnable)
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
MOZ_ASSERT(aRunnable);
|
||||
|
||||
ENSURE_GATT_CLIENT_INTF_IS_READY_VOID(aRunnable);
|
||||
|
||||
size_t index = sClients->IndexOf(aAppUuid, 0 /* Start */, UuidComparator());
|
||||
MOZ_ASSERT(index != sClients->NoIndex);
|
||||
|
||||
nsRefPtr<BluetoothGattClient> client = sClients->ElementAt(index);
|
||||
|
||||
// Reject the request if there is an ongoing request
|
||||
if (client->mDeregisterNotificationsRunnable) {
|
||||
DispatchReplyError(aRunnable,
|
||||
NS_LITERAL_STRING("DeregisterNotifications failed"));
|
||||
return;
|
||||
}
|
||||
|
||||
client->mDeregisterNotificationsRunnable = aRunnable;
|
||||
|
||||
sBluetoothGattClientInterface->DeregisterNotification(
|
||||
client->mClientIf, client->mDeviceAddr, aServId, aCharId,
|
||||
new DeregisterNotificationsResultHandler(client));
|
||||
}
|
||||
|
||||
//
|
||||
// Notification Handlers
|
||||
//
|
||||
|
@ -999,7 +1148,25 @@ BluetoothGattManager::RegisterNotificationNotification(
|
|||
int aConnId, int aIsRegister, BluetoothGattStatus aStatus,
|
||||
const BluetoothGattServiceId& aServiceId,
|
||||
const BluetoothGattId& aCharId)
|
||||
{ }
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
BT_LOGD("aStatus = %d, aConnId = %d, aIsRegister = %d",
|
||||
aStatus, aConnId, aIsRegister);
|
||||
|
||||
/**
|
||||
* FIXME: Bug 1149043
|
||||
*
|
||||
* aConnId reported by bluedroid stack is wrong, with these limited
|
||||
* information we have, we currently cannot map back to the client from this
|
||||
* callback. Therefore, we resolve/reject the Promise for registering or
|
||||
* deregistering notifications in their result handlers instead of this
|
||||
* callback.
|
||||
* We should resolve/reject the Promise for registering or deregistering
|
||||
* notifications here if this bluedroid stack bug is fixed.
|
||||
*
|
||||
* Please see Bug 1149043 for more information.
|
||||
*/
|
||||
}
|
||||
|
||||
void
|
||||
BluetoothGattManager::NotifyNotification(
|
||||
|
|
|
@ -46,6 +46,16 @@ public:
|
|||
const nsAString& aDeviceAddr,
|
||||
BluetoothReplyRunnable* aRunnable);
|
||||
|
||||
void RegisterNotifications(const nsAString& aAppUuid,
|
||||
const BluetoothGattServiceId& aServId,
|
||||
const BluetoothGattId& aCharId,
|
||||
BluetoothReplyRunnable* aRunnable);
|
||||
|
||||
void DeregisterNotifications(const nsAString& aAppUuid,
|
||||
const BluetoothGattServiceId& aServId,
|
||||
const BluetoothGattId& aCharId,
|
||||
BluetoothReplyRunnable* aRunnable);
|
||||
|
||||
private:
|
||||
class CleanupResultHandler;
|
||||
class CleanupResultHandlerRunnable;
|
||||
|
@ -56,6 +66,8 @@ private:
|
|||
class DisconnectResultHandler;
|
||||
class DiscoverResultHandler;
|
||||
class ReadRemoteRssiResultHandler;
|
||||
class RegisterNotificationsResultHandler;
|
||||
class DeregisterNotificationsResultHandler;
|
||||
|
||||
BluetoothGattManager();
|
||||
|
||||
|
|
|
@ -80,25 +80,6 @@ Convert(const nsAString& aIn, bt_bdaddr_t& aOut)
|
|||
return NS_OK;
|
||||
}
|
||||
|
||||
nsresult
|
||||
Convert(const nsAString& aIn, bt_ssp_variant_t& aOut)
|
||||
{
|
||||
if (aIn.EqualsLiteral("PasskeyConfirmation")) {
|
||||
aOut = BT_SSP_VARIANT_PASSKEY_CONFIRMATION;
|
||||
} else if (aIn.EqualsLiteral("PasskeyEntry")) {
|
||||
aOut = BT_SSP_VARIANT_PASSKEY_ENTRY;
|
||||
} else if (aIn.EqualsLiteral("Consent")) {
|
||||
aOut = BT_SSP_VARIANT_CONSENT;
|
||||
} else if (aIn.EqualsLiteral("PasskeyNotification")) {
|
||||
aOut = BT_SSP_VARIANT_PASSKEY_NOTIFICATION;
|
||||
} else {
|
||||
BT_LOGR("Invalid SSP variant name: %s", NS_ConvertUTF16toUTF8(aIn).get());
|
||||
aOut = BT_SSP_VARIANT_PASSKEY_CONFIRMATION; // silences compiler warning
|
||||
return NS_ERROR_ILLEGAL_VALUE;
|
||||
}
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsresult
|
||||
Convert(const uint8_t aIn[16], bt_uuid_t& aOut)
|
||||
{
|
||||
|
|
|
@ -110,9 +110,6 @@ Convert(ConvertNamedValue& aIn, bt_property_t& aOut);
|
|||
nsresult
|
||||
Convert(const nsAString& aIn, bt_bdaddr_t& aOut);
|
||||
|
||||
nsresult
|
||||
Convert(const nsAString& aIn, bt_ssp_variant_t& aOut);
|
||||
|
||||
inline nsresult
|
||||
Convert(const bt_ssp_variant_t& aIn, BluetoothSspVariant& aOut)
|
||||
{
|
||||
|
@ -131,6 +128,24 @@ Convert(const bt_ssp_variant_t& aIn, BluetoothSspVariant& aOut)
|
|||
return NS_OK;
|
||||
}
|
||||
|
||||
inline nsresult
|
||||
Convert(const BluetoothSspVariant& aIn, bt_ssp_variant_t& aOut)
|
||||
{
|
||||
static const bt_ssp_variant_t sSspVariant[] = {
|
||||
CONVERT(SSP_VARIANT_PASSKEY_CONFIRMATION,
|
||||
BT_SSP_VARIANT_PASSKEY_CONFIRMATION),
|
||||
CONVERT(SSP_VARIANT_PASSKEY_ENTRY, BT_SSP_VARIANT_PASSKEY_ENTRY),
|
||||
CONVERT(SSP_VARIANT_CONSENT, BT_SSP_VARIANT_CONSENT),
|
||||
CONVERT(SSP_VARIANT_PASSKEY_NOTIFICATION,
|
||||
BT_SSP_VARIANT_PASSKEY_NOTIFICATION)
|
||||
};
|
||||
if (aIn >= MOZ_ARRAY_LENGTH(sSspVariant)) {
|
||||
return NS_ERROR_ILLEGAL_VALUE;
|
||||
}
|
||||
aOut = sSspVariant[aIn];
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
inline nsresult
|
||||
Convert(const bool& aIn, uint8_t& aOut)
|
||||
{
|
||||
|
|
|
@ -769,7 +769,7 @@ BluetoothHALInterface::PinReply(const nsAString& aBdAddr, bool aAccept,
|
|||
|
||||
void
|
||||
BluetoothHALInterface::SspReply(const nsAString& aBdAddr,
|
||||
const nsAString& aVariant,
|
||||
BluetoothSspVariant aVariant,
|
||||
bool aAccept, uint32_t aPasskey,
|
||||
BluetoothResultHandler* aRes)
|
||||
{
|
||||
|
|
|
@ -69,7 +69,7 @@ public:
|
|||
const nsAString& aPinCode,
|
||||
BluetoothResultHandler* aRes);
|
||||
|
||||
void SspReply(const nsAString& aBdAddr, const nsAString& aVariant,
|
||||
void SspReply(const nsAString& aBdAddr, BluetoothSspVariant aVariant,
|
||||
bool aAccept, uint32_t aPasskey,
|
||||
BluetoothResultHandler* aRes);
|
||||
|
||||
|
|
|
@ -768,25 +768,18 @@ private:
|
|||
};
|
||||
|
||||
void
|
||||
BluetoothServiceBluedroid::SetPinCodeInternal(
|
||||
const nsAString& aDeviceAddress, const nsAString& aPinCode,
|
||||
BluetoothReplyRunnable* aRunnable)
|
||||
BluetoothServiceBluedroid::PinReplyInternal(
|
||||
const nsAString& aDeviceAddress, bool aAccept,
|
||||
const nsAString& aPinCode, BluetoothReplyRunnable* aRunnable)
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
|
||||
ENSURE_BLUETOOTH_IS_READY_VOID(aRunnable);
|
||||
|
||||
sBtInterface->PinReply(aDeviceAddress, true, aPinCode,
|
||||
new PinReplyResultHandler(aRunnable));
|
||||
sBtInterface->PinReply(aDeviceAddress, aAccept, aPinCode,
|
||||
new PinReplyResultHandler(aRunnable));
|
||||
}
|
||||
|
||||
void
|
||||
BluetoothServiceBluedroid::SetPasskeyInternal(
|
||||
const nsAString& aDeviceAddress, uint32_t aPasskey,
|
||||
BluetoothReplyRunnable* aRunnable)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
class BluetoothServiceBluedroid::SspReplyResultHandler final
|
||||
: public BluetoothResultHandler
|
||||
|
@ -811,19 +804,43 @@ private:
|
|||
};
|
||||
|
||||
void
|
||||
BluetoothServiceBluedroid::SetPairingConfirmationInternal(
|
||||
const nsAString& aDeviceAddress, bool aConfirm,
|
||||
BluetoothReplyRunnable* aRunnable)
|
||||
BluetoothServiceBluedroid::SspReplyInternal(
|
||||
const nsAString& aDeviceAddress, BluetoothSspVariant aVariant,
|
||||
bool aAccept, BluetoothReplyRunnable* aRunnable)
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
|
||||
ENSURE_BLUETOOTH_IS_READY_VOID(aRunnable);
|
||||
|
||||
sBtInterface->SspReply(aDeviceAddress,
|
||||
NS_ConvertUTF8toUTF16("PasskeyConfirmation"),
|
||||
aConfirm, 0, new SspReplyResultHandler(aRunnable));
|
||||
sBtInterface->SspReply(aDeviceAddress, aVariant, aAccept, 0 /* passkey */,
|
||||
new SspReplyResultHandler(aRunnable));
|
||||
}
|
||||
|
||||
void
|
||||
BluetoothServiceBluedroid::SetPinCodeInternal(
|
||||
const nsAString& aDeviceAddress, const nsAString& aPinCode,
|
||||
BluetoothReplyRunnable* aRunnable)
|
||||
{
|
||||
// Legacy method used by bluez only.
|
||||
}
|
||||
|
||||
void
|
||||
BluetoothServiceBluedroid::SetPasskeyInternal(
|
||||
const nsAString& aDeviceAddress, uint32_t aPasskey,
|
||||
BluetoothReplyRunnable* aRunnable)
|
||||
{
|
||||
// Legacy method used by bluez only.
|
||||
}
|
||||
|
||||
void
|
||||
BluetoothServiceBluedroid::SetPairingConfirmationInternal(
|
||||
const nsAString& aDeviceAddress, bool aConfirm,
|
||||
BluetoothReplyRunnable* aRunnable)
|
||||
{
|
||||
// Legacy method used by bluez only.
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
BluetoothServiceBluedroid::NextBluetoothProfileController()
|
||||
{
|
||||
|
@ -1131,6 +1148,36 @@ BluetoothServiceBluedroid::DiscoverGattServicesInternal(
|
|||
gatt->Discover(aAppUuid, aRunnable);
|
||||
}
|
||||
|
||||
void
|
||||
BluetoothServiceBluedroid::GattClientStartNotificationsInternal(
|
||||
const nsAString& aAppUuid, const BluetoothGattServiceId& aServId,
|
||||
const BluetoothGattId& aCharId, BluetoothReplyRunnable* aRunnable)
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
|
||||
ENSURE_BLUETOOTH_IS_READY_VOID(aRunnable);
|
||||
|
||||
BluetoothGattManager* gatt = BluetoothGattManager::Get();
|
||||
ENSURE_GATT_MGR_IS_READY_VOID(gatt, aRunnable);
|
||||
|
||||
gatt->RegisterNotifications(aAppUuid, aServId, aCharId, aRunnable);
|
||||
}
|
||||
|
||||
void
|
||||
BluetoothServiceBluedroid::GattClientStopNotificationsInternal(
|
||||
const nsAString& aAppUuid, const BluetoothGattServiceId& aServId,
|
||||
const BluetoothGattId& aCharId, BluetoothReplyRunnable* aRunnable)
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
|
||||
ENSURE_BLUETOOTH_IS_READY_VOID(aRunnable);
|
||||
|
||||
BluetoothGattManager* gatt = BluetoothGattManager::Get();
|
||||
ENSURE_GATT_MGR_IS_READY_VOID(gatt, aRunnable);
|
||||
|
||||
gatt->DeregisterNotifications(aAppUuid, aServId, aCharId, aRunnable);
|
||||
}
|
||||
|
||||
void
|
||||
BluetoothServiceBluedroid::UnregisterGattClientInternal(
|
||||
int aClientIf, BluetoothReplyRunnable* aRunnable)
|
||||
|
|
|
@ -81,15 +81,30 @@ public:
|
|||
BluetoothReplyRunnable* aRunnable);
|
||||
|
||||
virtual void
|
||||
SetPinCodeInternal(const nsAString& aDeviceAddress, const nsAString& aPinCode,
|
||||
PinReplyInternal(const nsAString& aDeviceAddress,
|
||||
bool aAccept,
|
||||
const nsAString& aPinCode,
|
||||
BluetoothReplyRunnable* aRunnable);
|
||||
|
||||
virtual void
|
||||
SspReplyInternal(const nsAString& aDeviceAddress,
|
||||
BluetoothSspVariant aVariant,
|
||||
bool aAccept,
|
||||
BluetoothReplyRunnable* aRunnable);
|
||||
|
||||
virtual void
|
||||
SetPinCodeInternal(const nsAString& aDeviceAddress,
|
||||
const nsAString& aPinCode,
|
||||
BluetoothReplyRunnable* aRunnable);
|
||||
|
||||
virtual void
|
||||
SetPasskeyInternal(const nsAString& aDeviceAddress, uint32_t aPasskey,
|
||||
SetPasskeyInternal(const nsAString& aDeviceAddress,
|
||||
uint32_t aPasskey,
|
||||
BluetoothReplyRunnable* aRunnable);
|
||||
|
||||
virtual void
|
||||
SetPairingConfirmationInternal(const nsAString& aDeviceAddress, bool aConfirm,
|
||||
SetPairingConfirmationInternal(const nsAString& aDeviceAddress,
|
||||
bool aConfirm,
|
||||
BluetoothReplyRunnable* aRunnable);
|
||||
|
||||
virtual void
|
||||
|
@ -188,6 +203,20 @@ public:
|
|||
DiscoverGattServicesInternal(const nsAString& aAppUuid,
|
||||
BluetoothReplyRunnable* aRunnable) override;
|
||||
|
||||
virtual void
|
||||
GattClientStartNotificationsInternal(
|
||||
const nsAString& aAppUuid,
|
||||
const BluetoothGattServiceId& aServId,
|
||||
const BluetoothGattId& aCharId,
|
||||
BluetoothReplyRunnable* aRunnable) override;
|
||||
|
||||
virtual void
|
||||
GattClientStopNotificationsInternal(
|
||||
const nsAString& aAppUuid,
|
||||
const BluetoothGattServiceId& aServId,
|
||||
const BluetoothGattId& aCharId,
|
||||
BluetoothReplyRunnable* aRunnable) override;
|
||||
|
||||
virtual void
|
||||
UnregisterGattClientInternal(int aClientIf,
|
||||
BluetoothReplyRunnable* aRunnable) override;
|
||||
|
|
|
@ -4277,6 +4277,10 @@ BluetoothDBusService::UpdateNotification(ControlEventId aEventId,
|
|||
DispatchToDBusThread(task);
|
||||
}
|
||||
|
||||
//
|
||||
// Methods for BT APIv2 implementation which currently only supports bluedroid
|
||||
//
|
||||
|
||||
void
|
||||
BluetoothDBusService::ConnectGattClientInternal(
|
||||
const nsAString& aAppUuid, const nsAString& aDeviceAddress,
|
||||
|
@ -4297,6 +4301,20 @@ BluetoothDBusService::DiscoverGattServicesInternal(
|
|||
{
|
||||
}
|
||||
|
||||
void
|
||||
BluetoothDBusService::GattClientStartNotificationsInternal(
|
||||
const nsAString& aAppUuid, const BluetoothGattServiceId& aServId,
|
||||
const BluetoothGattId& aCharId, BluetoothReplyRunnable* aRunnable)
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
BluetoothDBusService::GattClientStopNotificationsInternal(
|
||||
const nsAString& aAppUuid, const BluetoothGattServiceId& aServId,
|
||||
const BluetoothGattId& aCharId, BluetoothReplyRunnable* aRunnable)
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
BluetoothDBusService::UnregisterGattClientInternal(
|
||||
int aClientIf, BluetoothReplyRunnable* aRunnable)
|
||||
|
@ -4309,3 +4327,17 @@ BluetoothDBusService::GattClientReadRemoteRssiInternal(
|
|||
BluetoothReplyRunnable* aRunnable)
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
BluetoothDBusService::PinReplyInternal(
|
||||
const nsAString& aDeviceAddress, bool aAccept,
|
||||
const nsAString& aPinCode, BluetoothReplyRunnable* aRunnable)
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
BluetoothDBusService::SspReplyInternal(
|
||||
const nsAString& aDeviceAddress, BluetoothSspVariant aVariant,
|
||||
bool aAccept, BluetoothReplyRunnable* aRunnable)
|
||||
{
|
||||
}
|
||||
|
|
|
@ -94,7 +94,20 @@ public:
|
|||
BluetoothReplyRunnable* aRunnable) override;
|
||||
|
||||
virtual void
|
||||
SetPinCodeInternal(const nsAString& aDeviceAddress, const nsAString& aPinCode,
|
||||
PinReplyInternal(const nsAString& aDeviceAddress,
|
||||
bool aAccept,
|
||||
const nsAString& aPinCode,
|
||||
BluetoothReplyRunnable* aRunnable)
|
||||
|
||||
virtual void
|
||||
SspReplyInternal(const nsAString& aDeviceAddress,
|
||||
BluetoothSspVariant aVariant,
|
||||
bool aAccept,
|
||||
BluetoothReplyRunnable* aRunnable)
|
||||
|
||||
virtual void
|
||||
SetPinCodeInternal(const nsAString& aDeviceAddress,
|
||||
const nsAString& aPinCode,
|
||||
BluetoothReplyRunnable* aRunnable) override;
|
||||
|
||||
virtual void
|
||||
|
@ -102,7 +115,8 @@ public:
|
|||
BluetoothReplyRunnable* aRunnable) override;
|
||||
|
||||
virtual void
|
||||
SetPairingConfirmationInternal(const nsAString& aDeviceAddress, bool aConfirm,
|
||||
SetPairingConfirmationInternal(const nsAString& aDeviceAddress,
|
||||
bool aConfirm,
|
||||
BluetoothReplyRunnable* aRunnable) override;
|
||||
|
||||
virtual void
|
||||
|
@ -199,6 +213,20 @@ public:
|
|||
DiscoverGattServicesInternal(const nsAString& aAppUuid,
|
||||
BluetoothReplyRunnable* aRunnable) override;
|
||||
|
||||
virtual void
|
||||
GattClientStartNotificationsInternal(
|
||||
const nsAString& aAppUuid,
|
||||
const BluetoothGattServiceId& aServId,
|
||||
const BluetoothGattId& aCharId,
|
||||
BluetoothReplyRunnable* aRunnable) override;
|
||||
|
||||
virtual void
|
||||
GattClientStopNotificationsInternal(
|
||||
const nsAString& aAppUuid,
|
||||
const BluetoothGattServiceId& aServId,
|
||||
const BluetoothGattId& aCharId,
|
||||
BluetoothReplyRunnable* aRunnable) override;
|
||||
|
||||
virtual void
|
||||
UnregisterGattClientInternal(int aClientIf,
|
||||
BluetoothReplyRunnable* aRunnable) override;
|
||||
|
|
|
@ -20,6 +20,14 @@ struct ParamTraits<mozilla::dom::bluetooth::BluetoothObjectType>
|
|||
mozilla::dom::bluetooth::TYPE_INVALID>
|
||||
{ };
|
||||
|
||||
template <>
|
||||
struct ParamTraits<mozilla::dom::bluetooth::BluetoothSspVariant>
|
||||
: public ContiguousEnumSerializer<
|
||||
mozilla::dom::bluetooth::BluetoothSspVariant,
|
||||
mozilla::dom::bluetooth::SSP_VARIANT_PASSKEY_CONFIRMATION,
|
||||
mozilla::dom::bluetooth::SSP_VARIANT_PASSKEY_NOTIFICATION>
|
||||
{ };
|
||||
|
||||
template <>
|
||||
struct ParamTraits<mozilla::dom::bluetooth::BluetoothStatus>
|
||||
: public ContiguousEnumSerializer<
|
||||
|
|
|
@ -212,6 +212,10 @@ BluetoothParent::RecvPBluetoothRequestConstructor(
|
|||
return actor->DoRequest(aRequest.get_ConnectedDevicePropertiesRequest());
|
||||
case Request::TFetchUuidsRequest:
|
||||
return actor->DoRequest(aRequest.get_FetchUuidsRequest());
|
||||
case Request::TPinReplyRequest:
|
||||
return actor->DoRequest(aRequest.get_PinReplyRequest());
|
||||
case Request::TSspReplyRequest:
|
||||
return actor->DoRequest(aRequest.get_SspReplyRequest());
|
||||
case Request::TSetPinCodeRequest:
|
||||
return actor->DoRequest(aRequest.get_SetPinCodeRequest());
|
||||
case Request::TSetPasskeyRequest:
|
||||
|
@ -256,6 +260,12 @@ BluetoothParent::RecvPBluetoothRequestConstructor(
|
|||
return actor->DoRequest(aRequest.get_DisconnectGattClientRequest());
|
||||
case Request::TDiscoverGattServicesRequest:
|
||||
return actor->DoRequest(aRequest.get_DiscoverGattServicesRequest());
|
||||
case Request::TGattClientStartNotificationsRequest:
|
||||
return actor->DoRequest(
|
||||
aRequest.get_GattClientStartNotificationsRequest());
|
||||
case Request::TGattClientStopNotificationsRequest:
|
||||
return actor->DoRequest(
|
||||
aRequest.get_GattClientStopNotificationsRequest());
|
||||
case Request::TUnregisterGattClientRequest:
|
||||
return actor->DoRequest(aRequest.get_UnregisterGattClientRequest());
|
||||
case Request::TGattClientReadRemoteRssiRequest:
|
||||
|
@ -470,6 +480,34 @@ BluetoothRequestParent::DoRequest(const FetchUuidsRequest& aRequest)
|
|||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
BluetoothRequestParent::DoRequest(const PinReplyRequest& aRequest)
|
||||
{
|
||||
MOZ_ASSERT(mService);
|
||||
MOZ_ASSERT(mRequestType == Request::TPinReplyRequest);
|
||||
|
||||
mService->PinReplyInternal(aRequest.address(),
|
||||
aRequest.accept(),
|
||||
aRequest.pinCode(),
|
||||
mReplyRunnable.get());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
BluetoothRequestParent::DoRequest(const SspReplyRequest& aRequest)
|
||||
{
|
||||
MOZ_ASSERT(mService);
|
||||
MOZ_ASSERT(mRequestType == Request::TSspReplyRequest);
|
||||
|
||||
mService->SspReplyInternal(aRequest.address(),
|
||||
aRequest.variant(),
|
||||
aRequest.accept(),
|
||||
mReplyRunnable.get());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
BluetoothRequestParent::DoRequest(const SetPinCodeRequest& aRequest)
|
||||
{
|
||||
|
@ -733,6 +771,36 @@ BluetoothRequestParent::DoRequest(const DiscoverGattServicesRequest& aRequest)
|
|||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
BluetoothRequestParent::DoRequest(
|
||||
const GattClientStartNotificationsRequest& aRequest)
|
||||
{
|
||||
MOZ_ASSERT(mService);
|
||||
MOZ_ASSERT(mRequestType == Request::TGattClientStartNotificationsRequest);
|
||||
|
||||
mService->GattClientStartNotificationsInternal(aRequest.appUuid(),
|
||||
aRequest.servId(),
|
||||
aRequest.charId(),
|
||||
mReplyRunnable.get());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
BluetoothRequestParent::DoRequest(
|
||||
const GattClientStopNotificationsRequest& aRequest)
|
||||
{
|
||||
MOZ_ASSERT(mService);
|
||||
MOZ_ASSERT(mRequestType == Request::TGattClientStopNotificationsRequest);
|
||||
|
||||
mService->GattClientStopNotificationsInternal(aRequest.appUuid(),
|
||||
aRequest.servId(),
|
||||
aRequest.charId(),
|
||||
mReplyRunnable.get());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
BluetoothRequestParent::DoRequest(const UnregisterGattClientRequest& aRequest)
|
||||
{
|
||||
|
|
|
@ -173,6 +173,12 @@ protected:
|
|||
bool
|
||||
DoRequest(const DenyPairingConfirmationRequest& aRequest);
|
||||
|
||||
bool
|
||||
DoRequest(const PinReplyRequest& aRequest);
|
||||
|
||||
bool
|
||||
DoRequest(const SspReplyRequest& aRequest);
|
||||
|
||||
bool
|
||||
DoRequest(const ConnectRequest& aRequest);
|
||||
|
||||
|
@ -226,6 +232,12 @@ protected:
|
|||
bool
|
||||
DoRequest(const DiscoverGattServicesRequest& aRequest);
|
||||
|
||||
bool
|
||||
DoRequest(const GattClientStartNotificationsRequest& aRequest);
|
||||
|
||||
bool
|
||||
DoRequest(const GattClientStopNotificationsRequest& aRequest);
|
||||
|
||||
bool
|
||||
DoRequest(const UnregisterGattClientRequest& aRequest);
|
||||
|
||||
|
|
|
@ -208,6 +208,28 @@ BluetoothServiceChildProcess::UpdateSdpRecords(const nsAString& aDeviceAddress,
|
|||
MOZ_CRASH("This should never be called!");
|
||||
}
|
||||
|
||||
void
|
||||
BluetoothServiceChildProcess::PinReplyInternal(
|
||||
const nsAString& aDeviceAddress, bool aAccept,
|
||||
const nsAString& aPinCode, BluetoothReplyRunnable* aRunnable)
|
||||
{
|
||||
SendRequest(aRunnable,
|
||||
PinReplyRequest(nsString(aDeviceAddress),
|
||||
aAccept,
|
||||
nsString(aPinCode)));
|
||||
}
|
||||
|
||||
void
|
||||
BluetoothServiceChildProcess::SspReplyInternal(
|
||||
const nsAString& aDeviceAddress, BluetoothSspVariant aVariant,
|
||||
bool aAccept, BluetoothReplyRunnable* aRunnable)
|
||||
{
|
||||
SendRequest(aRunnable,
|
||||
SspReplyRequest(nsString(aDeviceAddress),
|
||||
aVariant,
|
||||
aAccept));
|
||||
}
|
||||
|
||||
void
|
||||
BluetoothServiceChildProcess::SetPinCodeInternal(
|
||||
const nsAString& aDeviceAddress,
|
||||
|
@ -405,6 +427,24 @@ BluetoothServiceChildProcess::DiscoverGattServicesInternal(
|
|||
DiscoverGattServicesRequest(nsString(aAppUuid)));
|
||||
}
|
||||
|
||||
void
|
||||
BluetoothServiceChildProcess::GattClientStartNotificationsInternal(
|
||||
const nsAString& aAppUuid, const BluetoothGattServiceId& aServId,
|
||||
const BluetoothGattId& aCharId, BluetoothReplyRunnable* aRunnable)
|
||||
{
|
||||
SendRequest(aRunnable,
|
||||
GattClientStartNotificationsRequest(nsString(aAppUuid), aServId, aCharId));
|
||||
}
|
||||
|
||||
void
|
||||
BluetoothServiceChildProcess::GattClientStopNotificationsInternal(
|
||||
const nsAString& aAppUuid, const BluetoothGattServiceId& aServId,
|
||||
const BluetoothGattId& aCharId, BluetoothReplyRunnable* aRunnable)
|
||||
{
|
||||
SendRequest(aRunnable,
|
||||
GattClientStopNotificationsRequest(nsString(aAppUuid), aServId, aCharId));
|
||||
}
|
||||
|
||||
void
|
||||
BluetoothServiceChildProcess::UnregisterGattClientInternal(
|
||||
int aClientIf, BluetoothReplyRunnable* aRunnable)
|
||||
|
|
|
@ -110,6 +110,18 @@ public:
|
|||
BluetoothReplyRunnable* aRunnable)
|
||||
override;
|
||||
|
||||
virtual void
|
||||
PinReplyInternal(const nsAString& aDeviceAddress,
|
||||
bool aAccept,
|
||||
const nsAString& aPinCode,
|
||||
BluetoothReplyRunnable* aRunnable) override;
|
||||
|
||||
virtual void
|
||||
SspReplyInternal(const nsAString& aDeviceAddress,
|
||||
BluetoothSspVariant aVariant,
|
||||
bool aAccept,
|
||||
BluetoothReplyRunnable* aRunnable) override;
|
||||
|
||||
virtual void
|
||||
Connect(const nsAString& aDeviceAddress,
|
||||
uint32_t aCod,
|
||||
|
@ -206,6 +218,20 @@ public:
|
|||
DiscoverGattServicesInternal(const nsAString& aAppUuid,
|
||||
BluetoothReplyRunnable* aRunnable) override;
|
||||
|
||||
virtual void
|
||||
GattClientStartNotificationsInternal(
|
||||
const nsAString& aAppUuid,
|
||||
const BluetoothGattServiceId& aServId,
|
||||
const BluetoothGattId& aCharId,
|
||||
BluetoothReplyRunnable* aRunnable) override;
|
||||
|
||||
virtual void
|
||||
GattClientStopNotificationsInternal(
|
||||
const nsAString& aAppUuid,
|
||||
const BluetoothGattServiceId& aServId,
|
||||
const BluetoothGattId& aCharId,
|
||||
BluetoothReplyRunnable* aRunnable) override;
|
||||
|
||||
virtual void
|
||||
UnregisterGattClientInternal(int aClientIf,
|
||||
BluetoothReplyRunnable* aRunnable) override;
|
||||
|
|
|
@ -8,6 +8,8 @@ using mozilla::dom::bluetooth::BluetoothGattId
|
|||
from "mozilla/dom/bluetooth/BluetoothCommon.h";
|
||||
using mozilla::dom::bluetooth::BluetoothGattServiceId
|
||||
from "mozilla/dom/bluetooth/BluetoothCommon.h";
|
||||
using mozilla::dom::bluetooth::BluetoothSspVariant
|
||||
from "mozilla/dom/bluetooth/BluetoothCommon.h";
|
||||
using mozilla::dom::bluetooth::BluetoothStatus
|
||||
from "mozilla/dom/bluetooth/BluetoothCommon.h";
|
||||
|
||||
|
@ -28,7 +30,9 @@ union BluetoothValue
|
|||
nsString[];
|
||||
uint8_t[];
|
||||
BluetoothNamedValue[];
|
||||
BluetoothGattId;
|
||||
BluetoothGattId[];
|
||||
BluetoothGattServiceId;
|
||||
BluetoothGattServiceId[];
|
||||
};
|
||||
|
||||
|
|
|
@ -64,6 +64,20 @@ struct UnpairRequest
|
|||
nsString address;
|
||||
};
|
||||
|
||||
struct PinReplyRequest
|
||||
{
|
||||
nsString address;
|
||||
bool accept;
|
||||
nsString pinCode;
|
||||
};
|
||||
|
||||
struct SspReplyRequest
|
||||
{
|
||||
nsString address;
|
||||
BluetoothSspVariant variant;
|
||||
bool accept;
|
||||
};
|
||||
|
||||
struct SetPinCodeRequest
|
||||
{
|
||||
nsString path;
|
||||
|
@ -193,6 +207,20 @@ struct DiscoverGattServicesRequest
|
|||
nsString appUuid;
|
||||
};
|
||||
|
||||
struct GattClientStartNotificationsRequest
|
||||
{
|
||||
nsString appUuid;
|
||||
BluetoothGattServiceId servId;
|
||||
BluetoothGattId charId;
|
||||
};
|
||||
|
||||
struct GattClientStopNotificationsRequest
|
||||
{
|
||||
nsString appUuid;
|
||||
BluetoothGattServiceId servId;
|
||||
BluetoothGattId charId;
|
||||
};
|
||||
|
||||
struct UnregisterGattClientRequest
|
||||
{
|
||||
int clientIf;
|
||||
|
@ -215,6 +243,8 @@ union Request
|
|||
StopDiscoveryRequest;
|
||||
PairRequest;
|
||||
UnpairRequest;
|
||||
PinReplyRequest;
|
||||
SspReplyRequest;
|
||||
SetPinCodeRequest;
|
||||
SetPasskeyRequest;
|
||||
ConfirmPairingConfirmationRequest;
|
||||
|
@ -239,6 +269,8 @@ union Request
|
|||
ConnectGattClientRequest;
|
||||
DisconnectGattClientRequest;
|
||||
DiscoverGattServicesRequest;
|
||||
GattClientStartNotificationsRequest;
|
||||
GattClientStopNotificationsRequest;
|
||||
UnregisterGattClientRequest;
|
||||
GattClientReadRemoteRssiRequest;
|
||||
};
|
||||
|
|
|
@ -753,15 +753,13 @@ function addEventHandlerForPairingRequest(aAdapter, aSpecifiedBdAddress) {
|
|||
|
||||
let device = evt.device;
|
||||
if (!aSpecifiedBdAddress || device.address == aSpecifiedBdAddress) {
|
||||
let confirm = true;
|
||||
|
||||
evt.handle.setPairingConfirmation(confirm).then(
|
||||
evt.handle.accept().then(
|
||||
function onResolve() {
|
||||
log(" - 'setPairingConfirmation' resolve.");
|
||||
log(" - 'accept' resolve.");
|
||||
cleanupPairingListener(aAdapter.pairingReqs);
|
||||
},
|
||||
function onReject() {
|
||||
log(" - 'setPairingConfirmation' reject.");
|
||||
log(" - 'accept' reject.");
|
||||
cleanupPairingListener(aAdapter.pairingReqs);
|
||||
});
|
||||
}
|
||||
|
@ -772,7 +770,15 @@ function addEventHandlerForPairingRequest(aAdapter, aSpecifiedBdAddress) {
|
|||
|
||||
let device = evt.device;
|
||||
if (!aSpecifiedBdAddress || device.address == aSpecifiedBdAddress) {
|
||||
cleanupPairingListener(aAdapter.pairingReqs);
|
||||
evt.handle.accept().then(
|
||||
function onResolve() {
|
||||
log(" - 'accept' resolve.");
|
||||
cleanupPairingListener(aAdapter.pairingReqs);
|
||||
},
|
||||
function onReject() {
|
||||
log(" - 'accept' reject.");
|
||||
cleanupPairingListener(aAdapter.pairingReqs);
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
@ -43,7 +43,7 @@
|
|||
// - BluetoothPairingEvent.handle
|
||||
//
|
||||
// - BluetoothPairingHandle.setPinCode()
|
||||
// - BluetoothPairingHandle.setPairingConfirmation()
|
||||
// - BluetoothPairingHandle.accept()
|
||||
//
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
|
|
|
@ -118,6 +118,44 @@ let emulator = (function() {
|
|||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait for one named system message.
|
||||
*
|
||||
* Resolve if that named message is received. Never reject.
|
||||
*
|
||||
* Fulfill params: the message passed.
|
||||
*
|
||||
* @param aEventName
|
||||
* A string message name.
|
||||
* @param aMatchFun [optional]
|
||||
* A matching function returns true or false to filter the message. If no
|
||||
* matching function passed the promise is resolved after receiving the
|
||||
* first message.
|
||||
*
|
||||
* @return Promise<Message>
|
||||
*/
|
||||
function waitForSystemMessage(aMessageName, aMatchFun = null) {
|
||||
// Current page may not register to receiving the message. We should
|
||||
// register it first.
|
||||
let systemMessenger = SpecialPowers.Cc["@mozilla.org/system-message-internal;1"]
|
||||
.getService(SpecialPowers.Ci.nsISystemMessagesInternal);
|
||||
|
||||
// TODO: Find a better way to get current pageURI and manifestURI.
|
||||
systemMessenger.registerPage(aMessageName,
|
||||
SpecialPowers.Services.io.newURI("app://system.gaiamobile.org/index.html", null, null),
|
||||
SpecialPowers.Services.io.newURI("app://system.gaiamobile.org/manifest.webapp", null, null));
|
||||
|
||||
return new Promise(function(aResolve, aReject) {
|
||||
window.navigator.mozSetMessageHandler(aMessageName, function(aMessage) {
|
||||
if (!aMatchFun || aMatchFun(aMessage)) {
|
||||
log("System message '" + aMessageName + "' got.");
|
||||
window.navigator.mozSetMessageHandler(aMessageName, null);
|
||||
aResolve(aMessage);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait for one named event.
|
||||
*
|
||||
|
@ -1098,6 +1136,7 @@ let emulator = (function() {
|
|||
*/
|
||||
|
||||
this.gDelay = delay;
|
||||
this.gWaitForSystemMessage = waitForSystemMessage;
|
||||
this.gWaitForEvent = waitForEvent;
|
||||
this.gWaitForCallsChangedEvent = waitForCallsChangedEvent;
|
||||
this.gWaitForNamedStateEvent = waitForNamedStateEvent;
|
||||
|
|
|
@ -51,4 +51,5 @@ qemu = true
|
|||
[test_ready.js]
|
||||
[test_redundant_operations.js]
|
||||
[test_swap_held_and_active.js]
|
||||
[test_system_message_telephony_call_ended.js]
|
||||
[test_temporary_clir.js]
|
||||
|
|
|
@ -0,0 +1,104 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
MARIONETTE_TIMEOUT = 90000;
|
||||
MARIONETTE_HEAD_JS = 'head.js';
|
||||
|
||||
const inNumber = "5555552222";
|
||||
const inInfo = gInCallStrPool(inNumber);
|
||||
let inCall;
|
||||
|
||||
/**
|
||||
* telephony-call-ended message contains the following fields:
|
||||
* - serviceId
|
||||
* - number
|
||||
* - emergency
|
||||
* - duration
|
||||
* - direction
|
||||
* - hangUpLocal
|
||||
*/
|
||||
|
||||
function testIncomingReject() {
|
||||
log("= testIncomingReject =");
|
||||
|
||||
let p1 = gWaitForSystemMessage("telephony-call-ended")
|
||||
.then(message => {
|
||||
is(message.number, inNumber);
|
||||
is(message.duration, 0);
|
||||
is(message.direction, "incoming");
|
||||
is(message.hangUpLocal, true);
|
||||
});
|
||||
|
||||
let p2 = gRemoteDial(inNumber)
|
||||
.then(call => inCall = call)
|
||||
.then(() => gHangUp(inCall));
|
||||
|
||||
return Promise.all([p1, p2]);
|
||||
}
|
||||
|
||||
function testIncomingCancel() {
|
||||
log("= testIncomingCancel =");
|
||||
|
||||
let p1 = gWaitForSystemMessage("telephony-call-ended")
|
||||
.then(message => {
|
||||
is(message.number, inNumber);
|
||||
is(message.duration, 0);
|
||||
is(message.direction, "incoming");
|
||||
is(message.hangUpLocal, false);
|
||||
});
|
||||
|
||||
let p2 = gRemoteDial(inNumber)
|
||||
.then(call => inCall = call)
|
||||
.then(() => gRemoteHangUp(inCall));
|
||||
|
||||
return Promise.all([p1, p2]);
|
||||
}
|
||||
|
||||
function testIncomingAnswerHangUp() {
|
||||
log("= testIncomingAnswerHangUp =");
|
||||
|
||||
p1 = gWaitForSystemMessage("telephony-call-ended")
|
||||
.then(message => {
|
||||
is(message.number, inNumber);
|
||||
ok(message.duration > 0);
|
||||
is(message.direction, "incoming");
|
||||
is(message.hangUpLocal, true);
|
||||
});
|
||||
|
||||
p2 = gRemoteDial(inNumber)
|
||||
.then(call => inCall = call)
|
||||
.then(() => gAnswer(inCall))
|
||||
.then(() => gHangUp(inCall));
|
||||
|
||||
return Promise.all([p1, p2]);
|
||||
}
|
||||
|
||||
function testIncomingAnswerRemoteHangUp() {
|
||||
log("= testIncomingAnswerRemoteHangUp =");
|
||||
|
||||
p1 = gWaitForSystemMessage("telephony-call-ended")
|
||||
.then(message => {
|
||||
is(message.number, inNumber);
|
||||
ok(message.duration > 0);
|
||||
is(message.direction, "incoming");
|
||||
is(message.hangUpLocal, false);
|
||||
});
|
||||
|
||||
p2 = gRemoteDial(inNumber)
|
||||
.then(call => inCall = call)
|
||||
.then(() => gAnswer(inCall))
|
||||
.then(() => gRemoteHangUp(inCall));
|
||||
|
||||
return Promise.all([p1, p2]);
|
||||
}
|
||||
|
||||
startTest(function() {
|
||||
Promise.resolve()
|
||||
.then(() => testIncomingReject())
|
||||
.then(() => testIncomingCancel())
|
||||
.then(() => testIncomingAnswerHangUp())
|
||||
.then(() => testIncomingAnswerRemoteHangUp())
|
||||
|
||||
.catch(error => ok(false, "Promise reject: " + error))
|
||||
.then(finish);
|
||||
});
|
|
@ -13,4 +13,13 @@ interface BluetoothGattCharacteristic
|
|||
|
||||
readonly attribute DOMString uuid;
|
||||
readonly attribute unsigned short instanceId;
|
||||
|
||||
/**
|
||||
* Start or stop subscribing notifications of this characteristic from the
|
||||
* remote GATT server.
|
||||
*/
|
||||
[NewObject]
|
||||
Promise<void> startNotifications();
|
||||
[NewObject]
|
||||
Promise<void> stopNotifications();
|
||||
};
|
||||
|
|
|
@ -13,8 +13,22 @@ interface BluetoothPairingHandle
|
|||
*/
|
||||
readonly attribute DOMString passkey;
|
||||
|
||||
/**
|
||||
* Reply pin code for enterpincodereq. The promise will be rejected if the
|
||||
* pairing request type is not enterpincodereq or operation fails.
|
||||
*/
|
||||
[NewObject]
|
||||
Promise<void> setPinCode(DOMString aPinCode);
|
||||
|
||||
/**
|
||||
* Accept pairing requests. The promise will be rejected if the pairing
|
||||
* request type is not pairingconfirmationreq or pairingconsentreq or
|
||||
* operation fails.
|
||||
*/
|
||||
[NewObject]
|
||||
Promise<void> setPairingConfirmation(boolean aConfirm);
|
||||
Promise<void> accept();
|
||||
|
||||
// Reject pairing requests. The promise will be rejected if operation fails.
|
||||
[NewObject]
|
||||
Promise<void> reject();
|
||||
};
|
||||
|
|
|
@ -6,13 +6,10 @@
|
|||
package org.mozilla.gecko;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
import org.mozilla.gecko.AppConstants.Versions;
|
||||
import org.mozilla.gecko.prompts.PromptInput;
|
||||
import org.mozilla.gecko.util.GeckoEventListener;
|
||||
import org.mozilla.gecko.util.ThreadUtils;
|
||||
import org.mozilla.gecko.widget.AnchoredPopup;
|
||||
|
@ -21,7 +18,6 @@ import org.mozilla.gecko.widget.DoorHanger;
|
|||
import android.content.Context;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.widget.CheckBox;
|
||||
import org.mozilla.gecko.widget.DoorhangerConfig;
|
||||
|
||||
public class DoorHangerPopup extends AnchoredPopup
|
||||
|
@ -109,15 +105,16 @@ public class DoorHangerPopup extends AnchoredPopup
|
|||
private DoorhangerConfig makeConfigFromJSON(JSONObject json) throws JSONException {
|
||||
final int tabId = json.getInt("tabID");
|
||||
final String id = json.getString("value");
|
||||
final DoorhangerConfig config = new DoorhangerConfig(tabId, id);
|
||||
|
||||
final String typeString = json.optString("category");
|
||||
final boolean isLogin = DoorHanger.Type.LOGIN.toString().equals(typeString);
|
||||
final DoorHanger.Type doorhangerType = isLogin ? DoorHanger.Type.LOGIN : DoorHanger.Type.DEFAULT;
|
||||
|
||||
final DoorhangerConfig config = new DoorhangerConfig(tabId, id, doorhangerType, this);
|
||||
|
||||
config.setMessage(json.getString("message"));
|
||||
config.setButtons(json.getJSONArray("buttons"));
|
||||
config.appendButtonsFromJSON(json.getJSONArray("buttons"));
|
||||
config.setOptions(json.getJSONObject("options"));
|
||||
final String typeString = json.optString("category");
|
||||
if (DoorHanger.Type.LOGIN.toString().equals(typeString)) {
|
||||
config.setType(DoorHanger.Type.LOGIN);
|
||||
}
|
||||
|
||||
return config;
|
||||
}
|
||||
|
@ -181,18 +178,6 @@ public class DoorHangerPopup extends AnchoredPopup
|
|||
|
||||
final DoorHanger newDoorHanger = DoorHanger.Get(mContext, config);
|
||||
|
||||
final JSONArray buttons = config.getButtons();
|
||||
for (int i = 0; i < buttons.length(); i++) {
|
||||
try {
|
||||
JSONObject buttonObject = buttons.getJSONObject(i);
|
||||
String label = buttonObject.getString("label");
|
||||
String tag = String.valueOf(buttonObject.getInt("callback"));
|
||||
newDoorHanger.addButton(label, tag, this);
|
||||
} catch (JSONException e) {
|
||||
Log.e(LOGTAG, "Error creating doorhanger button", e);
|
||||
}
|
||||
}
|
||||
|
||||
mDoorHangers.add(newDoorHanger);
|
||||
mContent.addView(newDoorHanger);
|
||||
|
||||
|
@ -206,32 +191,10 @@ public class DoorHangerPopup extends AnchoredPopup
|
|||
* DoorHanger.OnButtonClickListener implementation
|
||||
*/
|
||||
@Override
|
||||
public void onButtonClick(DoorHanger dh, String tag) {
|
||||
JSONObject response = new JSONObject();
|
||||
try {
|
||||
response.put("callback", tag);
|
||||
|
||||
CheckBox checkBox = dh.getCheckBox();
|
||||
// If the checkbox is being used, pass its value
|
||||
if (checkBox != null) {
|
||||
response.put("checked", checkBox.isChecked());
|
||||
}
|
||||
|
||||
List<PromptInput> doorHangerInputs = dh.getInputs();
|
||||
if (doorHangerInputs != null) {
|
||||
JSONObject inputs = new JSONObject();
|
||||
for (PromptInput input : doorHangerInputs) {
|
||||
inputs.put(input.getId(), input.getValue());
|
||||
}
|
||||
response.put("inputs", inputs);
|
||||
}
|
||||
} catch (JSONException e) {
|
||||
Log.e(LOGTAG, "Error creating onClick response", e);
|
||||
}
|
||||
|
||||
public void onButtonClick(JSONObject response, DoorHanger doorhanger) {
|
||||
GeckoEvent e = GeckoEvent.createBroadcastEvent("Doorhanger:Reply", response.toString());
|
||||
GeckoAppShell.sendEventToGecko(e);
|
||||
removeDoorHanger(dh);
|
||||
removeDoorHanger(doorhanger);
|
||||
updatePopup();
|
||||
}
|
||||
|
||||
|
|
|
@ -381,6 +381,7 @@ size. -->
|
|||
<!ENTITY doorhanger_login_edit_username_hint "Username">
|
||||
<!ENTITY doorhanger_login_edit_password_hint "Password">
|
||||
<!ENTITY doorhanger_login_edit_toggle "Show password">
|
||||
<!ENTITY doorhanger_login_edit_toast_error "Failed to save login">
|
||||
|
||||
<!ENTITY pref_titlebar_mode "Title bar">
|
||||
<!ENTITY pref_titlebar_mode_title "Show page title">
|
||||
|
|
|
@ -39,6 +39,7 @@
|
|||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textAppearance="@style/TextAppearance.DoorHanger.Medium"
|
||||
android:textColor="@color/link_blue"
|
||||
android:paddingBottom="@dimen/doorhanger_section_padding_large"
|
||||
android:visibility="gone"/>
|
||||
|
||||
|
|
|
@ -0,0 +1,30 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- This Source Code Form is subject to the terms of the Mozilla Public
|
||||
- License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
|
||||
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:padding="@dimen/doorhanger_padding"
|
||||
android:orientation="vertical">
|
||||
|
||||
<EditText android:id="@+id/username_edit"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:inputType="textNoSuggestions"
|
||||
android:hint="@string/doorhanger_login_edit_username_hint"/>
|
||||
|
||||
<EditText android:id="@+id/password_edit"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:password="true"
|
||||
android:hint="@string/doorhanger_login_edit_password_hint"/>
|
||||
|
||||
<CheckBox android:id="@+id/checkbox_toggle_password"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/doorhanger_login_edit_toggle"
|
||||
android:paddingTop="@dimen/doorhanger_padding"/>
|
||||
|
||||
</LinearLayout>
|
|
@ -345,6 +345,7 @@
|
|||
<string name="doorhanger_login_edit_username_hint">&doorhanger_login_edit_username_hint;</string>
|
||||
<string name="doorhanger_login_edit_password_hint">&doorhanger_login_edit_password_hint;</string>
|
||||
<string name="doorhanger_login_edit_toggle">&doorhanger_login_edit_toggle;</string>
|
||||
<string name="doorhanger_login_edit_toast_error">&doorhanger_login_edit_toast_error;</string>
|
||||
|
||||
<string name="pref_titlebar_mode">&pref_titlebar_mode;</string>
|
||||
<string name="pref_titlebar_mode_title">&pref_titlebar_mode_title;</string>
|
||||
|
|
|
@ -17,7 +17,6 @@ import org.mozilla.gecko.Tabs;
|
|||
import org.mozilla.gecko.widget.AnchoredPopup;
|
||||
import org.mozilla.gecko.widget.DoorHanger;
|
||||
import org.mozilla.gecko.widget.DoorHanger.OnButtonClickListener;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import android.content.Context;
|
||||
|
@ -34,6 +33,8 @@ import org.mozilla.gecko.widget.DoorhangerConfig;
|
|||
* an arrow panel popup hanging from the lock icon in the browser toolbar.
|
||||
*/
|
||||
public class SiteIdentityPopup extends AnchoredPopup {
|
||||
public static enum ButtonType { DISABLE, ENABLE, KEEP_BLOCKING };
|
||||
|
||||
private static final String LOGTAG = "GeckoSiteIdentityPopup";
|
||||
|
||||
private static final String MIXED_CONTENT_SUPPORT_URL =
|
||||
|
@ -142,7 +143,7 @@ public class SiteIdentityPopup extends AnchoredPopup {
|
|||
// Remove any existing mixed content notification.
|
||||
removeMixedContentNotification();
|
||||
|
||||
final DoorhangerConfig config = new DoorhangerConfig();
|
||||
final DoorhangerConfig config = new DoorhangerConfig(DoorHanger.Type.MIXED_CONTENT, mButtonClickListener);
|
||||
int icon;
|
||||
if (blocked) {
|
||||
icon = R.drawable.shield_enabled_doorhanger;
|
||||
|
@ -154,11 +155,11 @@ public class SiteIdentityPopup extends AnchoredPopup {
|
|||
}
|
||||
|
||||
config.setLink(mContext.getString(R.string.learn_more), MIXED_CONTENT_SUPPORT_URL, "\n\n");
|
||||
config.setType(DoorHanger.Type.SITE);
|
||||
addNotificationButtons(config, blocked);
|
||||
|
||||
mMixedContentNotification = DoorHanger.Get(mContext, config);
|
||||
mMixedContentNotification.setIcon(icon);
|
||||
|
||||
addNotificationButtons(mMixedContentNotification, blocked);
|
||||
|
||||
mContent.addView(mMixedContentNotification);
|
||||
mDivider.setVisibility(View.VISIBLE);
|
||||
|
@ -175,7 +176,7 @@ public class SiteIdentityPopup extends AnchoredPopup {
|
|||
// Remove any existing tracking content notification.
|
||||
removeTrackingContentNotification();
|
||||
|
||||
final DoorhangerConfig config = new DoorhangerConfig();
|
||||
final DoorhangerConfig config = new DoorhangerConfig(DoorHanger.Type.TRACKING, mButtonClickListener);
|
||||
|
||||
int icon;
|
||||
if (blocked) {
|
||||
|
@ -189,12 +190,12 @@ public class SiteIdentityPopup extends AnchoredPopup {
|
|||
}
|
||||
|
||||
config.setLink(mContext.getString(R.string.learn_more), TRACKING_CONTENT_SUPPORT_URL, "\n\n");
|
||||
config.setType(DoorHanger.Type.SITE);
|
||||
addNotificationButtons(config, blocked);
|
||||
|
||||
mTrackingContentNotification = DoorHanger.Get(mContext, config);
|
||||
|
||||
mTrackingContentNotification.setIcon(icon);
|
||||
|
||||
addNotificationButtons(mTrackingContentNotification, blocked);
|
||||
|
||||
mContent.addView(mTrackingContentNotification);
|
||||
mDivider.setVisibility(View.VISIBLE);
|
||||
|
@ -207,13 +208,12 @@ public class SiteIdentityPopup extends AnchoredPopup {
|
|||
}
|
||||
}
|
||||
|
||||
private void addNotificationButtons(DoorHanger dh, boolean blocked) {
|
||||
// TODO: Add support for buttons in DoorHangerConfig.
|
||||
private void addNotificationButtons(DoorhangerConfig config, boolean blocked) {
|
||||
if (blocked) {
|
||||
dh.addButton(mContext.getString(R.string.disable_protection), "disable", mButtonClickListener);
|
||||
dh.addButton(mContext.getString(R.string.keep_blocking), "keepBlocking", mButtonClickListener);
|
||||
config.appendButton(mContext.getString(R.string.disable_protection), ButtonType.DISABLE.ordinal());
|
||||
config.appendButton(mContext.getString(R.string.keep_blocking), ButtonType.KEEP_BLOCKING.ordinal());
|
||||
} else {
|
||||
dh.addButton(mContext.getString(R.string.enable_protection), "enable", mButtonClickListener);
|
||||
config.appendButton(mContext.getString(R.string.enable_protection), ButtonType.ENABLE.ordinal());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -290,18 +290,9 @@ public class SiteIdentityPopup extends AnchoredPopup {
|
|||
|
||||
private class PopupButtonListener implements OnButtonClickListener {
|
||||
@Override
|
||||
public void onButtonClick(DoorHanger dh, String tag) {
|
||||
try {
|
||||
JSONObject data = new JSONObject();
|
||||
data.put("allowContent", tag.equals("disable"));
|
||||
data.put("contentType", (dh == mMixedContentNotification ? "mixed" : "tracking"));
|
||||
|
||||
GeckoEvent e = GeckoEvent.createBroadcastEvent("Session:Reload", data.toString());
|
||||
GeckoAppShell.sendEventToGecko(e);
|
||||
} catch (JSONException e) {
|
||||
Log.e(LOGTAG, "Exception creating message to enable/disable content blocking", e);
|
||||
}
|
||||
|
||||
public void onButtonClick(JSONObject response, DoorHanger doorhanger) {
|
||||
GeckoEvent e = GeckoEvent.createBroadcastEvent("Session:Reload", response.toString());
|
||||
GeckoAppShell.sendEventToGecko(e);
|
||||
dismiss();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,6 +5,9 @@
|
|||
|
||||
package org.mozilla.gecko.widget;
|
||||
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.widget.Button;
|
||||
import org.mozilla.gecko.R;
|
||||
import org.mozilla.gecko.prompts.PromptInput;
|
||||
|
||||
|
@ -13,11 +16,11 @@ import org.json.JSONException;
|
|||
import org.json.JSONObject;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
import android.text.TextUtils;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.CheckBox;
|
||||
import org.mozilla.gecko.toolbar.SiteIdentityPopup;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
@ -25,23 +28,16 @@ import java.util.List;
|
|||
public class DefaultDoorHanger extends DoorHanger {
|
||||
private static final String LOGTAG = "GeckoDefaultDoorHanger";
|
||||
|
||||
private final Resources mResources;
|
||||
private static int sSpinnerTextColor = -1;
|
||||
|
||||
private List<PromptInput> mInputs;
|
||||
private CheckBox mCheckBox;
|
||||
|
||||
public DefaultDoorHanger(Context context, DoorhangerConfig config) {
|
||||
this(context, config, Type.DEFAULT);
|
||||
}
|
||||
|
||||
public DefaultDoorHanger(Context context, DoorhangerConfig config, Type type) {
|
||||
super(context, config, type);
|
||||
|
||||
mResources = getResources();
|
||||
|
||||
if (sSpinnerTextColor == -1) {
|
||||
sSpinnerTextColor = getResources().getColor(R.color.text_color_primary_disable_only);
|
||||
sSpinnerTextColor = mResources.getColor(R.color.text_color_primary_disable_only);
|
||||
}
|
||||
loadConfig(config);
|
||||
}
|
||||
|
@ -62,15 +58,15 @@ public class DefaultDoorHanger extends DoorHanger {
|
|||
if (link != null) {
|
||||
addLink(link.label, link.url, link.delimiter);
|
||||
}
|
||||
|
||||
setButtons(config);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<PromptInput> getInputs() {
|
||||
private List<PromptInput> getInputs() {
|
||||
return mInputs;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CheckBox getCheckBox() {
|
||||
private CheckBox getCheckBox() {
|
||||
return mCheckBox;
|
||||
}
|
||||
|
||||
|
@ -115,6 +111,54 @@ public class DefaultDoorHanger extends DoorHanger {
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Button createButtonInstance(final String text, final int id) {
|
||||
final Button button = (Button) LayoutInflater.from(getContext()).inflate(R.layout.doorhanger_button, null);
|
||||
button.setText(text);
|
||||
|
||||
button.setOnClickListener(new Button.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
final JSONObject response = new JSONObject();
|
||||
try {
|
||||
// TODO: Bug 1149359 - Split this into each Doorhanger Type class.
|
||||
switch (mType) {
|
||||
case MIXED_CONTENT:
|
||||
response.put("allowContent", (id == SiteIdentityPopup.ButtonType.DISABLE.ordinal()));
|
||||
response.put("contentType", ("mixed"));
|
||||
break;
|
||||
case TRACKING:
|
||||
response.put("allowContent", (id == SiteIdentityPopup.ButtonType.DISABLE.ordinal()));
|
||||
response.put("contentType", ("tracking"));
|
||||
break;
|
||||
default:
|
||||
response.put("callback", id);
|
||||
|
||||
CheckBox checkBox = getCheckBox();
|
||||
// If the checkbox is being used, pass its value
|
||||
if (checkBox != null) {
|
||||
response.put("checked", checkBox.isChecked());
|
||||
}
|
||||
|
||||
List<PromptInput> doorHangerInputs = getInputs();
|
||||
if (doorHangerInputs != null) {
|
||||
JSONObject inputs = new JSONObject();
|
||||
for (PromptInput input : doorHangerInputs) {
|
||||
inputs.put(input.getId(), input.getValue());
|
||||
}
|
||||
response.put("inputs", inputs);
|
||||
}
|
||||
}
|
||||
mOnButtonClickListener.onButtonClick(response, DefaultDoorHanger.this);
|
||||
} catch (JSONException e) {
|
||||
Log.e(LOGTAG, "Error creating onClick response", e);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return button;
|
||||
}
|
||||
|
||||
private void styleInput(PromptInput input, View view) {
|
||||
if (input instanceof PromptInput.MenulistInput) {
|
||||
styleDropdownInputs(input, view);
|
||||
|
|
|
@ -6,49 +6,47 @@
|
|||
package org.mozilla.gecko.widget;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
import android.text.Html;
|
||||
import android.text.SpannableString;
|
||||
import android.text.Spanned;
|
||||
import android.text.method.LinkMovementMethod;
|
||||
import android.text.style.ForegroundColorSpan;
|
||||
import android.text.style.URLSpan;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.widget.Button;
|
||||
import android.widget.CheckBox;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
import org.mozilla.gecko.R;
|
||||
import org.mozilla.gecko.Tabs;
|
||||
import org.mozilla.gecko.prompts.PromptInput;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public abstract class DoorHanger extends LinearLayout {
|
||||
|
||||
public static DoorHanger Get(Context context, DoorhangerConfig config) {
|
||||
final Type type = config.getType();
|
||||
if (type != null) {
|
||||
switch (type) {
|
||||
case LOGIN:
|
||||
return new LoginDoorHanger(context, config);
|
||||
case SITE:
|
||||
return new DefaultDoorHanger(context, config, type);
|
||||
}
|
||||
switch (type) {
|
||||
case LOGIN:
|
||||
return new LoginDoorHanger(context, config);
|
||||
case TRACKING:
|
||||
case MIXED_CONTENT:
|
||||
return new DefaultDoorHanger(context, config, type);
|
||||
}
|
||||
|
||||
return new DefaultDoorHanger(context, config);
|
||||
return new DefaultDoorHanger(context, config, type);
|
||||
}
|
||||
|
||||
public static enum Type { DEFAULT, LOGIN, SITE }
|
||||
public static enum Type { DEFAULT, LOGIN, TRACKING, MIXED_CONTENT}
|
||||
|
||||
public interface OnButtonClickListener {
|
||||
public void onButtonClick(DoorHanger dh, String tag);
|
||||
public void onButtonClick(JSONObject response, DoorHanger doorhanger);
|
||||
}
|
||||
|
||||
private static final LayoutParams sButtonParams;
|
||||
protected static final LayoutParams sButtonParams;
|
||||
static {
|
||||
sButtonParams = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT, 1.0f);
|
||||
}
|
||||
|
@ -58,7 +56,8 @@ public abstract class DoorHanger extends LinearLayout {
|
|||
// Divider between doorhangers.
|
||||
private final View mDivider;
|
||||
|
||||
private final LinearLayout mButtonsContainer;
|
||||
protected final LinearLayout mButtonsContainer;
|
||||
protected final OnButtonClickListener mOnButtonClickListener;
|
||||
|
||||
// The tab this doorhanger is associated with.
|
||||
private final int mTabId;
|
||||
|
@ -66,10 +65,13 @@ public abstract class DoorHanger extends LinearLayout {
|
|||
// DoorHanger identifier.
|
||||
private final String mIdentifier;
|
||||
|
||||
protected final Type mType;
|
||||
|
||||
private final ImageView mIcon;
|
||||
private final TextView mMessage;
|
||||
|
||||
protected Context mContext;
|
||||
protected final Context mContext;
|
||||
protected final Resources mResources;
|
||||
|
||||
protected int mDividerColor;
|
||||
|
||||
|
@ -80,6 +82,7 @@ public abstract class DoorHanger extends LinearLayout {
|
|||
protected DoorHanger(Context context, DoorhangerConfig config, Type type) {
|
||||
super(context);
|
||||
mContext = context;
|
||||
mResources = context.getResources();
|
||||
mTabId = config.getTabId();
|
||||
mIdentifier = config.getId();
|
||||
|
||||
|
@ -96,16 +99,22 @@ public abstract class DoorHanger extends LinearLayout {
|
|||
mDivider = findViewById(R.id.divider_doorhanger);
|
||||
mIcon = (ImageView) findViewById(R.id.doorhanger_icon);
|
||||
mMessage = (TextView) findViewById(R.id.doorhanger_message);
|
||||
if (type == Type.SITE) {
|
||||
|
||||
// TODO: Bug 1149359 - split this into DoorHanger subclasses.
|
||||
if (type == Type.TRACKING || type == Type.MIXED_CONTENT) {
|
||||
mMessage.setTextAppearance(getContext(), R.style.TextAppearance_DoorHanger_Small);
|
||||
}
|
||||
mButtonsContainer = (LinearLayout) findViewById(R.id.doorhanger_buttons);
|
||||
|
||||
mDividerColor = getResources().getColor(R.color.divider_light);
|
||||
mType = type;
|
||||
|
||||
mButtonsContainer = (LinearLayout) findViewById(R.id.doorhanger_buttons);
|
||||
mOnButtonClickListener = config.getButtonClickListener();
|
||||
|
||||
mDividerColor = mResources.getColor(R.color.divider_light);
|
||||
setOrientation(VERTICAL);
|
||||
}
|
||||
|
||||
abstract protected void loadConfig(DoorhangerConfig config);
|
||||
protected abstract void loadConfig(DoorhangerConfig config);
|
||||
|
||||
protected void setOptions(final JSONObject options) {
|
||||
final int persistence = options.optInt("persistence");
|
||||
|
@ -119,6 +128,21 @@ public abstract class DoorHanger extends LinearLayout {
|
|||
if (timeout > 0) {
|
||||
mTimeout = timeout;
|
||||
}
|
||||
}
|
||||
|
||||
protected void setButtons(DoorhangerConfig config) {
|
||||
final JSONArray buttons = config.getButtons();
|
||||
final OnButtonClickListener listener = config.getButtonClickListener();
|
||||
for (int i = 0; i < buttons.length(); i++) {
|
||||
try {
|
||||
final JSONObject buttonObject = buttons.getJSONObject(i);
|
||||
final String label = buttonObject.getString("label");
|
||||
final int callbackId = buttonObject.getInt("callback");
|
||||
addButtonToLayout(label, callbackId);
|
||||
} catch (JSONException e) {
|
||||
Log.e(LOGTAG, "Error creating doorhanger button", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public int getTabId() {
|
||||
|
@ -166,18 +190,13 @@ public abstract class DoorHanger extends LinearLayout {
|
|||
mMessage.setMovementMethod(LinkMovementMethod.getInstance());
|
||||
}
|
||||
|
||||
public void addButton(final String text, final String tag, final OnButtonClickListener listener) {
|
||||
final Button button = (Button) LayoutInflater.from(getContext()).inflate(R.layout.doorhanger_button, null);
|
||||
button.setText(text);
|
||||
button.setTag(tag);
|
||||
|
||||
button.setOnClickListener(new Button.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
listener.onButtonClick(DoorHanger.this, tag);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Creates and adds a button into the DoorHanger.
|
||||
* @param text Button text
|
||||
* @param id Identifier associated with the button
|
||||
*/
|
||||
private void addButtonToLayout(String text, int id) {
|
||||
final Button button = createButtonInstance(text, id);
|
||||
if (mButtonsContainer.getChildCount() == 0) {
|
||||
// If this is the first button we're adding, make the choices layout visible.
|
||||
mButtonsContainer.setVisibility(View.VISIBLE);
|
||||
|
@ -195,6 +214,8 @@ public abstract class DoorHanger extends LinearLayout {
|
|||
mButtonsContainer.addView(button, sButtonParams);
|
||||
}
|
||||
|
||||
protected abstract Button createButtonInstance(String text, int id);
|
||||
|
||||
/*
|
||||
* Checks with persistence and timeout options to see if it's okay to remove a doorhanger.
|
||||
*
|
||||
|
@ -222,14 +243,4 @@ public abstract class DoorHanger extends LinearLayout {
|
|||
|
||||
return true;
|
||||
}
|
||||
|
||||
// TODO: remove and expose through instance Button Handler.
|
||||
public List<PromptInput> getInputs() {
|
||||
return null;
|
||||
}
|
||||
|
||||
public CheckBox getCheckBox() {
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -5,7 +5,9 @@
|
|||
|
||||
package org.mozilla.gecko.widget;
|
||||
|
||||
import android.util.Log;
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import org.mozilla.gecko.widget.DoorHanger.Type;
|
||||
|
@ -24,23 +26,29 @@ public class DoorhangerConfig {
|
|||
}
|
||||
}
|
||||
|
||||
private static final String LOGTAG = "DoorhangerConfig";
|
||||
|
||||
private final int tabId;
|
||||
private final String id;
|
||||
private DoorHanger.Type type;
|
||||
private final DoorHanger.OnButtonClickListener buttonClickListener;
|
||||
private final DoorHanger.Type type;
|
||||
private String message;
|
||||
private JSONObject options;
|
||||
private Link link;
|
||||
private JSONArray buttons;
|
||||
private JSONArray buttons = new JSONArray();
|
||||
|
||||
public DoorhangerConfig() {
|
||||
public DoorhangerConfig(Type type, DoorHanger.OnButtonClickListener listener) {
|
||||
// XXX: This should only be used by SiteIdentityPopup doorhangers which
|
||||
// don't need tab or id references, until bug 1141904 unifies doorhangers.
|
||||
this(-1, null);
|
||||
|
||||
this(-1, null, type, listener);
|
||||
}
|
||||
|
||||
public DoorhangerConfig(int tabId, String id) {
|
||||
public DoorhangerConfig(int tabId, String id, DoorHanger.Type type, DoorHanger.OnButtonClickListener buttonClickListener) {
|
||||
this.tabId = tabId;
|
||||
this.id = id;
|
||||
this.type = type;
|
||||
this.buttonClickListener = buttonClickListener;
|
||||
}
|
||||
|
||||
public int getTabId() {
|
||||
|
@ -51,10 +59,6 @@ public class DoorhangerConfig {
|
|||
return id;
|
||||
}
|
||||
|
||||
public void setType(Type type) {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public Type getType() {
|
||||
return type;
|
||||
}
|
||||
|
@ -75,8 +79,33 @@ public class DoorhangerConfig {
|
|||
return options;
|
||||
}
|
||||
|
||||
public void setButtons(JSONArray buttons) {
|
||||
this.buttons = buttons;
|
||||
/**
|
||||
* Add buttons from JSON to the Config object.
|
||||
* @param buttons JSONArray of JSONObjects of the form { label: <label>, callback: <callback_id> }
|
||||
*/
|
||||
public void appendButtonsFromJSON(JSONArray buttons) {
|
||||
try {
|
||||
for (int i = 0; i < buttons.length(); i++) {
|
||||
this.buttons.put(buttons.get(i));
|
||||
}
|
||||
} catch (JSONException e) {
|
||||
Log.e(LOGTAG, "Error parsing buttons from JSON", e);
|
||||
}
|
||||
}
|
||||
|
||||
public void appendButton(String label, int callbackId) {
|
||||
final JSONObject button = new JSONObject();
|
||||
try {
|
||||
button.put("label", label);
|
||||
button.put("callback", callbackId);
|
||||
this.buttons.put(button);
|
||||
} catch (JSONException e) {
|
||||
Log.e(LOGTAG, "Error creating button", e);
|
||||
}
|
||||
}
|
||||
|
||||
public DoorHanger.OnButtonClickListener getButtonClickListener() {
|
||||
return this.buttonClickListener;
|
||||
}
|
||||
|
||||
public JSONArray getButtons() {
|
||||
|
|
|
@ -5,12 +5,22 @@
|
|||
|
||||
package org.mozilla.gecko.widget;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.app.Dialog;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.drawable.BitmapDrawable;
|
||||
import android.text.method.PasswordTransformationMethod;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.widget.Button;
|
||||
import android.widget.CheckBox;
|
||||
import android.widget.CompoundButton;
|
||||
import android.widget.EditText;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
import ch.boye.httpclientandroidlib.util.TextUtils;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
@ -20,9 +30,11 @@ import org.mozilla.gecko.favicons.OnFaviconLoadedListener;
|
|||
|
||||
public class LoginDoorHanger extends DoorHanger {
|
||||
private static final String LOGTAG = "LoginDoorHanger";
|
||||
private enum ActionType { EDIT };
|
||||
|
||||
final TextView mTitle;
|
||||
final TextView mLogin;
|
||||
private final TextView mTitle;
|
||||
private final TextView mLogin;
|
||||
private int mCallbackID;
|
||||
|
||||
public LoginDoorHanger(Context context, DoorhangerConfig config) {
|
||||
super(context, config, Type.LOGIN);
|
||||
|
@ -37,7 +49,7 @@ public class LoginDoorHanger extends DoorHanger {
|
|||
protected void loadConfig(DoorhangerConfig config) {
|
||||
setOptions(config.getOptions());
|
||||
setMessage(config.getMessage());
|
||||
|
||||
setButtons(config);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -51,7 +63,7 @@ public class LoginDoorHanger extends DoorHanger {
|
|||
final String text = titleObj.getString("text");
|
||||
mTitle.setText(text);
|
||||
} catch (JSONException e) {
|
||||
Log.e(LOGTAG, "Error loading title from options JSON");
|
||||
Log.e(LOGTAG, "Error loading title from options JSON", e);
|
||||
}
|
||||
|
||||
final String resource = titleObj.optString("resource");
|
||||
|
@ -60,20 +72,132 @@ public class LoginDoorHanger extends DoorHanger {
|
|||
@Override
|
||||
public void onFaviconLoaded(String url, String faviconURL, Bitmap favicon) {
|
||||
if (favicon != null) {
|
||||
mTitle.setCompoundDrawablesWithIntrinsicBounds(new BitmapDrawable(mContext.getResources(), favicon), null, null, null);
|
||||
mTitle.setCompoundDrawablePadding((int) mContext.getResources().getDimension(R.dimen.doorhanger_drawable_padding));
|
||||
mTitle.setCompoundDrawablesWithIntrinsicBounds(new BitmapDrawable(mResources, favicon), null, null, null);
|
||||
mTitle.setCompoundDrawablePadding((int) mResources.getDimension(R.dimen.doorhanger_drawable_padding));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
final String subtext = options.optString("subtext");
|
||||
if (!TextUtils.isEmpty(subtext)) {
|
||||
mLogin.setText(subtext);
|
||||
mLogin.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
final JSONObject actionText = options.optJSONObject("actionText");
|
||||
addActionText(actionText);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Button createButtonInstance(final String text, final int id) {
|
||||
// HACK: Confirm button will the the rightmost/last button added. Bug 1147064 should add differentiation of the two.
|
||||
mCallbackID = id;
|
||||
|
||||
final Button button = (Button) LayoutInflater.from(getContext()).inflate(R.layout.doorhanger_button, null);
|
||||
button.setText(text);
|
||||
|
||||
button.setOnClickListener(new Button.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
final JSONObject response = new JSONObject();
|
||||
try {
|
||||
response.put("callback", id);
|
||||
} catch (JSONException e) {
|
||||
Log.e(LOGTAG, "Error making doorhanger response message", e);
|
||||
}
|
||||
mOnButtonClickListener.onButtonClick(response, LoginDoorHanger.this);
|
||||
}
|
||||
});
|
||||
|
||||
return button;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add sub-text to the doorhanger and add the click action.
|
||||
*
|
||||
* If the parsing the action from the JSON throws, the text is left visible, but there is no
|
||||
* click action.
|
||||
* @param actionTextObj JSONObject containing blob for making an action.
|
||||
*/
|
||||
private void addActionText(JSONObject actionTextObj) {
|
||||
if (actionTextObj == null) {
|
||||
mLogin.setVisibility(View.GONE);
|
||||
return;
|
||||
}
|
||||
|
||||
boolean hasUsername = true;
|
||||
String text = actionTextObj.optString("text");
|
||||
if (TextUtils.isEmpty(text)) {
|
||||
hasUsername = false;
|
||||
text = mResources.getString(R.string.doorhanger_login_no_username);
|
||||
}
|
||||
mLogin.setText(text);
|
||||
mLogin.setVisibility(View.VISIBLE);
|
||||
|
||||
// Make action.
|
||||
try {
|
||||
final JSONObject bundle = actionTextObj.getJSONObject("bundle");
|
||||
final ActionType type = ActionType.valueOf(actionTextObj.getString("type"));
|
||||
final AlertDialog.Builder builder = new AlertDialog.Builder(mContext);
|
||||
|
||||
switch (type) {
|
||||
case EDIT:
|
||||
builder.setTitle(mResources.getString(R.string.doorhanger_login_edit_title));
|
||||
|
||||
final View view = LayoutInflater.from(mContext).inflate(R.layout.login_edit_dialog, null);
|
||||
final EditText username = (EditText) view.findViewById(R.id.username_edit);
|
||||
username.setText(bundle.getString("username"));
|
||||
final EditText password = (EditText) view.findViewById(R.id.password_edit);
|
||||
password.setText(bundle.getString("password"));
|
||||
final CheckBox passwordCheckbox = (CheckBox) view.findViewById(R.id.checkbox_toggle_password);
|
||||
passwordCheckbox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
|
||||
@Override
|
||||
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
|
||||
if (isChecked) {
|
||||
password.setTransformationMethod(null);
|
||||
} else {
|
||||
password.setTransformationMethod(PasswordTransformationMethod.getInstance());
|
||||
}
|
||||
}
|
||||
});
|
||||
builder.setView(view);
|
||||
|
||||
builder.setPositiveButton(R.string.button_remember, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
JSONObject response = new JSONObject();
|
||||
try {
|
||||
response.put("callback", mCallbackID);
|
||||
final JSONObject inputs = new JSONObject();
|
||||
inputs.put("username", username.getText());
|
||||
inputs.put("password", password.getText());
|
||||
response.put("inputs", inputs);
|
||||
} catch (JSONException e) {
|
||||
Log.e(LOGTAG, "Error creating doorhanger reply message");
|
||||
response = null;
|
||||
Toast.makeText(mContext, mResources.getString(R.string.doorhanger_login_edit_toast_error), Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
mOnButtonClickListener.onButtonClick(response, LoginDoorHanger.this);
|
||||
}
|
||||
});
|
||||
builder.setNegativeButton(R.string.button_cancel, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
dialog.dismiss();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
final Dialog dialog = builder.create();
|
||||
mLogin.setOnClickListener(new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
dialog.show();
|
||||
}
|
||||
});
|
||||
|
||||
} catch (JSONException e) {
|
||||
// Log an error, but leave the text visible if there was a username.
|
||||
Log.e(LOGTAG, "Error fetching actionText from JSON", e);
|
||||
if (!hasUsername) {
|
||||
mLogin.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2232,7 +2232,11 @@ var NativeWindow = {
|
|||
* { text: <title>,
|
||||
* resource: <resource_url> }
|
||||
*
|
||||
* subtext: A string to appear below the doorhanger message.
|
||||
* actionText: An object that specifies a clickable string, a type of action,
|
||||
* and a bundle blob for the consumer to create a click action.
|
||||
* { text: <text>,
|
||||
* type: <type>,
|
||||
* bundle: <blob-object> }
|
||||
*
|
||||
* @param aCategory
|
||||
* Doorhanger type to display (e.g., LOGIN)
|
||||
|
|
|
@ -148,11 +148,11 @@ LoginManagerPrompter.prototype = {
|
|||
* String message to be displayed in the doorhanger
|
||||
* @param aButtons
|
||||
* Buttons to display with the doorhanger
|
||||
* @param aSubtext
|
||||
* String to be displayed below the aBody message
|
||||
* @param aActionText
|
||||
* Object with text to be displayed as clickable, along with a bundle to create an action
|
||||
*
|
||||
*/
|
||||
_showLoginNotification : function (aName, aTitle, aBody, aButtons, aSubtext) {
|
||||
_showLoginNotification : function (aName, aTitle, aBody, aButtons, aActionText) {
|
||||
this.log("Adding new " + aName + " notification bar");
|
||||
let notifyWin = this._window.top;
|
||||
let chromeWin = this._getChromeWindow(notifyWin).wrappedJSObject;
|
||||
|
@ -171,7 +171,7 @@ LoginManagerPrompter.prototype = {
|
|||
persistWhileVisible: true,
|
||||
timeout: Date.now() + 10000,
|
||||
title: aTitle,
|
||||
subtext: aSubtext
|
||||
actionText: aActionText
|
||||
}
|
||||
|
||||
var nativeWindow = this._getNativeWindow();
|
||||
|
@ -194,11 +194,16 @@ LoginManagerPrompter.prototype = {
|
|||
|
||||
let displayHost = this._getShortDisplayHost(aLogin.hostname);
|
||||
let title = { text: displayHost, resource: aLogin.hostname };
|
||||
let subtext = null;
|
||||
|
||||
if (aLogin.username) {
|
||||
subtext = this._sanitizeUsername(aLogin.username);
|
||||
}
|
||||
let username = aLogin.username ? this._sanitizeUsername(aLogin.username) : "";
|
||||
|
||||
let actionText = {
|
||||
text: username,
|
||||
type: "EDIT",
|
||||
bundle: { username: username,
|
||||
password: aLogin.password }
|
||||
};
|
||||
|
||||
// The callbacks in |buttons| have a closure to access the variables
|
||||
// in scope here; set one to |this._pwmgr| so we can get back to pwmgr
|
||||
// without a getService() call.
|
||||
|
@ -215,14 +220,19 @@ LoginManagerPrompter.prototype = {
|
|||
},
|
||||
{
|
||||
label: this._getLocalizedString("rememberButton"),
|
||||
callback: function() {
|
||||
callback: function(checked, response) {
|
||||
|
||||
if (response) {
|
||||
aLogin.username = response["username"] || aLogin.username;
|
||||
aLogin.password = response["password"] || aLogin.password;
|
||||
}
|
||||
pwmgr.addLogin(aLogin);
|
||||
promptHistogram.add(PROMPT_ADD);
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
this._showLoginNotification("password-save", title, notificationText, buttons, subtext);
|
||||
this._showLoginNotification("password-save", title, notificationText, buttons, actionText);
|
||||
},
|
||||
|
||||
/*
|
||||
|
|
|
@ -74,7 +74,7 @@ body {
|
|||
display: none;
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
font-size: 0.9rem;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
.header {
|
||||
|
@ -101,7 +101,7 @@ body {
|
|||
}
|
||||
|
||||
.header > h1 {
|
||||
font-size: 1.33rem;
|
||||
font-size: 1.33em;
|
||||
font-weight: 700;
|
||||
line-height: 1.1em;
|
||||
width: 100%;
|
||||
|
@ -146,16 +146,16 @@ body {
|
|||
/* This covers caption, domain, and credits
|
||||
texts in the reader UI */
|
||||
|
||||
.content .wp-caption-text,
|
||||
.content figcaption,
|
||||
#moz-reader-content .wp-caption-text,
|
||||
#moz-reader-content figcaption,
|
||||
.header > .domain,
|
||||
.header > .credits {
|
||||
font-size: 0.9rem;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
.content {
|
||||
#moz-reader-content {
|
||||
display: none;
|
||||
font-size: 1rem;
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
.content a {
|
||||
|
@ -175,7 +175,7 @@ body {
|
|||
height: auto !important;
|
||||
}
|
||||
|
||||
.content p {
|
||||
#moz-reader-content p {
|
||||
line-height: 1.4em !important;
|
||||
margin: 0px !important;
|
||||
margin-bottom: 20px !important;
|
||||
|
@ -258,8 +258,8 @@ body {
|
|||
border-left-color: #777777 !important;
|
||||
}
|
||||
|
||||
.content ul,
|
||||
.content ol {
|
||||
#moz-reader-content ul,
|
||||
#moz-reader-content ol {
|
||||
margin: 0px !important;
|
||||
margin-bottom: 20px !important;
|
||||
padding: 0px !important;
|
||||
|
|
|
@ -48,7 +48,7 @@ let AboutReader = function(mm, win, articlePromise) {
|
|||
this._domainElementRef = Cu.getWeakReference(doc.getElementById("reader-domain"));
|
||||
this._titleElementRef = Cu.getWeakReference(doc.getElementById("reader-title"));
|
||||
this._creditsElementRef = Cu.getWeakReference(doc.getElementById("reader-credits"));
|
||||
this._contentElementRef = Cu.getWeakReference(doc.getElementById("reader-content"));
|
||||
this._contentElementRef = Cu.getWeakReference(doc.getElementById("moz-reader-content"));
|
||||
this._toolbarElementRef = Cu.getWeakReference(doc.getElementById("reader-toolbar"));
|
||||
this._messageElementRef = Cu.getWeakReference(doc.getElementById("reader-message"));
|
||||
|
||||
|
@ -354,13 +354,13 @@ AboutReader.prototype = {
|
|||
},
|
||||
|
||||
_setFontSize: function Reader_setFontSize(newFontSize) {
|
||||
let htmlClasses = this._doc.documentElement.classList;
|
||||
let containerClasses = this._doc.getElementById("container").classList;
|
||||
|
||||
if (this._fontSize > 0)
|
||||
htmlClasses.remove("font-size" + this._fontSize);
|
||||
containerClasses.remove("font-size" + this._fontSize);
|
||||
|
||||
this._fontSize = newFontSize;
|
||||
htmlClasses.add("font-size" + this._fontSize);
|
||||
containerClasses.add("font-size" + this._fontSize);
|
||||
|
||||
this._mm.sendAsyncMessage("Reader:SetIntPref", {
|
||||
name: "reader.font_size",
|
||||
|
|
Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше
Загрузка…
Ссылка в новой задаче