CLOSED TREE
This commit is contained in:
Ryan VanderMeulen 2015-04-08 12:59:08 -04:00
Родитель d8ade60200 75871f42cb
Коммит abc76acfae
105 изменённых файлов: 2175 добавлений и 881 удалений

Просмотреть файл

@ -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

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 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",

Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше