Merge m-c to inbound, a=merge

This commit is contained in:
Wes Kocher 2015-10-16 12:30:35 -07:00
commit 9d6d71deba
89 changed files with 2385 additions and 988 deletions

View File

@ -15,7 +15,7 @@
<project name="platform_build" path="build" remote="b2g" revision="8d83715f08b7849f16a0dfc88f78d5c3a89c0a54">
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="gaia" path="gaia" remote="mozillaorg" revision="8999f0ba6326d815c8366e3c1155b7e4e9763b40"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="f75a7e01912cee313fed92ff2089586f507b2ba5"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="fake-qemu-kernel" path="prebuilts/qemu-kernel" remote="b2g" revision="939b377d55a2f081d94029a30a75d05e5a20daf3"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="7938df689aa87769fad3f2cf9097fb4ecb106a43"/>

View File

@ -15,7 +15,7 @@
<project name="platform_build" path="build" remote="b2g" revision="8d83715f08b7849f16a0dfc88f78d5c3a89c0a54">
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="gaia" path="gaia" remote="mozillaorg" revision="8999f0ba6326d815c8366e3c1155b7e4e9763b40"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="f75a7e01912cee313fed92ff2089586f507b2ba5"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="fake-qemu-kernel" path="prebuilts/qemu-kernel" remote="b2g" revision="939b377d55a2f081d94029a30a75d05e5a20daf3"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="7938df689aa87769fad3f2cf9097fb4ecb106a43"/>
@ -135,7 +135,7 @@
<project groups="invensense" name="platform/hardware/invensense" path="hardware/invensense" revision="e6d9ab28b4f4e7684f6c07874ee819c9ea0002a2"/>
<project name="platform/hardware/ril" path="hardware/ril" revision="865ce3b4a2ba0b3a31421ca671f4d6c5595f8690"/>
<project name="kernel/common" path="kernel" revision="b698c5aa4e9393dfd70d0ea7a0bf93b333b54750"/>
<project name="platform/system/core" path="system/core" revision="4776448ebcd3f07d58b91503c478da9b54cb58a0"/>
<project name="platform/system/core" path="system/core" revision="1b888e1ae54c358246f13e4fb915ce43cde00691"/>
<project name="u-boot" path="u-boot" revision="f1502910977ac88f43da7bf9277c3523ad4b0b2f"/>
<project name="vendor/sprd/gps" path="vendor/sprd/gps" revision="7d6e1269be7186b2073fa568958b357826692c4b"/>
<project name="vendor/sprd/open-source" path="vendor/sprd/open-source" revision="295ff253b74353751a99aafd687196a28c84a58e"/>

View File

@ -19,7 +19,7 @@
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="8999f0ba6326d815c8366e3c1155b7e4e9763b40"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="f75a7e01912cee313fed92ff2089586f507b2ba5"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="7938df689aa87769fad3f2cf9097fb4ecb106a43"/>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
<project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="4ace9aaee0e048dfda11bb787646c59982a3dc80"/>

View File

@ -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="8999f0ba6326d815c8366e3c1155b7e4e9763b40"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="f75a7e01912cee313fed92ff2089586f507b2ba5"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="7938df689aa87769fad3f2cf9097fb4ecb106a43"/>
<project name="moztt" path="external/moztt" remote="b2g" revision="31a7849fe9a8b743d6f5e5facc212f0ef9d57499"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="0c28789b9957913be975eb002a22323f93585d4c"/>

View File

@ -15,7 +15,7 @@
<project name="platform_build" path="build" remote="b2g" revision="8d83715f08b7849f16a0dfc88f78d5c3a89c0a54">
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="gaia" path="gaia" remote="mozillaorg" revision="8999f0ba6326d815c8366e3c1155b7e4e9763b40"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="f75a7e01912cee313fed92ff2089586f507b2ba5"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="7938df689aa87769fad3f2cf9097fb4ecb106a43"/>
<project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>

View File

@ -15,7 +15,7 @@
<project name="platform_build" path="build" remote="b2g" revision="c9d4fe680662ee44a4bdea42ae00366f5df399cf">
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="gaia" path="gaia" remote="mozillaorg" revision="8999f0ba6326d815c8366e3c1155b7e4e9763b40"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="f75a7e01912cee313fed92ff2089586f507b2ba5"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="7938df689aa87769fad3f2cf9097fb4ecb106a43"/>
<project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>

View File

@ -19,7 +19,7 @@
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="8999f0ba6326d815c8366e3c1155b7e4e9763b40"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="f75a7e01912cee313fed92ff2089586f507b2ba5"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="7938df689aa87769fad3f2cf9097fb4ecb106a43"/>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
<project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="4ace9aaee0e048dfda11bb787646c59982a3dc80"/>

View File

@ -15,7 +15,7 @@
<project name="platform_build" path="build" remote="b2g" revision="8d83715f08b7849f16a0dfc88f78d5c3a89c0a54">
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="gaia" path="gaia" remote="mozillaorg" revision="8999f0ba6326d815c8366e3c1155b7e4e9763b40"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="f75a7e01912cee313fed92ff2089586f507b2ba5"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="fake-qemu-kernel" path="prebuilts/qemu-kernel" remote="b2g" revision="939b377d55a2f081d94029a30a75d05e5a20daf3"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="7938df689aa87769fad3f2cf9097fb4ecb106a43"/>

View File

@ -1,9 +1,9 @@
{
"git": {
"git_revision": "8999f0ba6326d815c8366e3c1155b7e4e9763b40",
"git_revision": "f75a7e01912cee313fed92ff2089586f507b2ba5",
"remote": "https://git.mozilla.org/releases/gaia.git",
"branch": ""
},
"revision": "983b7ecb17c67b9fb511f400bb5b28b9069eea00",
"revision": "f51f8770b9665f44e57e9e09faee2b967a80abf1",
"repo_path": "integration/gaia-central"
}

View File

@ -15,7 +15,7 @@
<project name="platform_build" path="build" remote="b2g" revision="8d83715f08b7849f16a0dfc88f78d5c3a89c0a54">
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="gaia" path="gaia" remote="mozillaorg" revision="8999f0ba6326d815c8366e3c1155b7e4e9763b40"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="f75a7e01912cee313fed92ff2089586f507b2ba5"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="fake-qemu-kernel" path="prebuilts/qemu-kernel" remote="b2g" revision="939b377d55a2f081d94029a30a75d05e5a20daf3"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="7938df689aa87769fad3f2cf9097fb4ecb106a43"/>

View File

@ -18,7 +18,7 @@
<project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="fake-qemu-kernel" path="prebuilts/qemu-kernel" remote="b2g" revision="939b377d55a2f081d94029a30a75d05e5a20daf3"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="8999f0ba6326d815c8366e3c1155b7e4e9763b40"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="f75a7e01912cee313fed92ff2089586f507b2ba5"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="7938df689aa87769fad3f2cf9097fb4ecb106a43"/>
<project name="moztt" path="external/moztt" remote="b2g" revision="31a7849fe9a8b743d6f5e5facc212f0ef9d57499"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="0c28789b9957913be975eb002a22323f93585d4c"/>

View File

@ -15,7 +15,7 @@
<project name="platform_build" path="build" remote="b2g" revision="c9d4fe680662ee44a4bdea42ae00366f5df399cf">
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="gaia" path="gaia" remote="mozillaorg" revision="8999f0ba6326d815c8366e3c1155b7e4e9763b40"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="f75a7e01912cee313fed92ff2089586f507b2ba5"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="fake-qemu-kernel" path="prebuilts/qemu-kernel" remote="b2g" revision="939b377d55a2f081d94029a30a75d05e5a20daf3"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="7938df689aa87769fad3f2cf9097fb4ecb106a43"/>

View File

@ -44,6 +44,7 @@
"dot-location": [2, "property"],
"eol-last": 2,
"eqeqeq": [2, "smart"],
"jsx-quotes": [2, "prefer-double"],
"key-spacing": [2, {"beforeColon": false, "afterColon": true }],
"linebreak-style": [2, "unix"],
"new-cap": 0, // TODO: set to 2
@ -106,7 +107,6 @@
"yoda": [2, "never"],
// eslint-plugin-react rules. These are documented at
// <https://github.com/yannickcr/eslint-plugin-react#list-of-supported-rules>
"react/jsx-quotes": [2, "double", "avoid-escape"],
"react/jsx-no-undef": 2,
"react/jsx-sort-props": 2,
"react/jsx-sort-prop-types": 2,

View File

@ -469,51 +469,6 @@ html, .fx-embedded, #main,
height: 100%;
}
.room-conversation-wrapper header {
background: #000;
height: 50px;
text-align: left;
margin: 0 10px;
}
html[dir="rtl"] .room-conversation-wrapper header {
text-align: right;
}
.room-conversation-wrapper header h1 {
font-size: 1.5em;
color: #fff;
line-height: 50px;
text-indent: 40px;
background-image: url("../img/firefox-logo.png");
background-size: 30px;
background-position: 0 center;
background-repeat: no-repeat;
display: inline-block;
margin: 0 10px;
}
html[dir="rtl"] .room-conversation-wrapper header h1 {
background-position: 100% center;
}
.room-conversation-wrapper header a {
float: right;
}
html[dir="rtl"] .room-conversation-wrapper header a {
float: left;
}
.room-conversation-wrapper header .icon-help {
display: inline-block;
background-size: contain;
margin-top: 15px;
width: 20px;
height: 20px;
background: transparent url("../img/svg/glyph-help-16x16.svg") no-repeat;
}
.room-invitation-overlay {
position: absolute;
background: rgba(255, 255, 255, 0.85);
@ -676,12 +631,6 @@ body[platform="win"] .share-service-dropdown.overflow > .dropdown-menu-item {
height: 100%;
}
.standalone-room-wrapper > .media-layout {
/* 50px is the header, 10px is the bottom margin. */
height: calc(100% - 50px - 10px);
margin: 0 10px;
}
.media-layout > .media-wrapper {
display: flex;
flex-flow: column wrap;
@ -797,11 +746,6 @@ body[platform="win"] .share-service-dropdown.overflow > .dropdown-menu-item {
}
@media screen and (max-width:640px) {
.standalone-room-wrapper > .media-layout {
/* 50px is height of header, 10px is bottom margin. */
height: calc(100% - 50px - 10px);
}
.media-layout > .media-wrapper {
flex-direction: row;
margin: 0;
@ -832,8 +776,8 @@ body[platform="win"] .share-service-dropdown.overflow > .dropdown-menu-item {
}
.media-wrapper > .focus-stream > .local ~ .conversation-toolbar {
/* 22px are the margins, 120px is for the local video area. */
max-width: calc(100% - 22px - 120px);
/* 120px is for the local video area. */
max-width: calc(100% - 120px);
}
.media-wrapper > .focus-stream > .local {
@ -938,7 +882,7 @@ body[platform="win"] .share-service-dropdown.overflow > .dropdown-menu-item {
}
.media-wrapper > .focus-stream > .local ~ .conversation-toolbar {
max-width: calc(75% - 22px);
max-width: 75%;
}
.media-wrapper > .focus-stream > .local {
@ -981,7 +925,7 @@ body[platform="win"] .share-service-dropdown.overflow > .dropdown-menu-item {
}
.media-wrapper:not(.showing-remote-streams) > .focus-stream > .local ~ .conversation-toolbar {
max-width: calc(100% - 22px);
max-width: 100%;
}
.media-wrapper > .focus-stream {
@ -1247,8 +1191,7 @@ html[dir="rtl"] .text-chat-entry.received .text-chat-arrow {
left: 0;
}
.standalone .room-conversation-wrapper .video-layout-wrapper {
/* 50px: header's height; 10px: bottom margin */
height: calc(100% - 50px - 10px);
height: 100%;
}
.standalone .room-conversation .video_wrapper.remote_wrapper {
width: 100%;

View File

@ -1123,14 +1123,15 @@ loop.shared.views = (function(_, mozL10n) {
srcMediaElement: this.props.remoteSrcMediaElement}),
this.state.localMediaAboslutelyPositioned ?
this.renderLocalVideo() : null,
this.props.children
this.props.displayScreenShare ? null : this.props.children
),
React.createElement("div", {className: screenShareStreamClasses},
React.createElement(MediaView, {displayAvatar: false,
isLoading: this.props.isScreenShareLoading,
mediaType: "screen-share",
posterUrl: this.props.screenSharePosterUrl,
srcMediaElement: this.props.screenShareMediaElement})
srcMediaElement: this.props.screenShareMediaElement}),
this.props.displayScreenShare ? this.props.children : null
),
React.createElement(loop.shared.views.chat.TextChatView, {
dispatcher: this.props.dispatcher,

View File

@ -1123,7 +1123,7 @@ loop.shared.views = (function(_, mozL10n) {
srcMediaElement={this.props.remoteSrcMediaElement} />
{ this.state.localMediaAboslutelyPositioned ?
this.renderLocalVideo() : null }
{ this.props.children }
{ this.props.displayScreenShare ? null : this.props.children }
</div>
<div className={screenShareStreamClasses}>
<MediaView displayAvatar={false}
@ -1131,6 +1131,7 @@ loop.shared.views = (function(_, mozL10n) {
mediaType="screen-share"
posterUrl={this.props.screenSharePosterUrl}
srcMediaElement={this.props.screenShareMediaElement} />
{ this.props.displayScreenShare ? this.props.children : null }
</div>
<loop.shared.views.chat.TextChatView
dispatcher={this.props.dispatcher}

View File

@ -46,47 +46,82 @@ body,
height: 30px;
}
.room-conversation-wrapper > .beta-logo {
position: fixed;
top: 0;
/* Standalone Overlay wrapper */
.standalone-overlay-wrapper {
position: absolute;
left: 0;
width: 50px;
height: 50px;
background: transparent url(../shared/img/beta-ribbon.svg) no-repeat;
background-size: 50px;
z-index: 1000;
top: 0;
bottom: 0;
right: 0;
margin: 1.2rem;
pointer-events: none;
}
/* Mozilla Logo */
.standalone-overlay-wrapper > .hello-logo {
width: 128px;
height: 21px;
background-image: url("../shared/img/hello_logo.svg");
background-size: contain;
position: absolute;
left: 0;
top: 0;
}
.focus-stream > .standalone-moz-logo {
html[dir="rtl"] .standalone-overlay-wrapper > .hello-logo {
right: 0;
left: auto;
}
.standalone-overlay-wrapper > .general-support-url {
position: absolute;
top: 0;
right: 0;
pointer-events: auto;
}
html[dir="rtl"] .standalone-overlay-wrapper > .general-support-url {
left: 0;
right: auto;
}
.standalone-overlay-wrapper .icon-help {
background-size: contain;
width: 16px;
height: 16px;
background: transparent url("../shared/img/svg/glyph-help-16x16.svg") no-repeat;
}
.standalone-overlay-wrapper > .standalone-moz-logo {
width: 50px;
height: 13px;
background-size: contain;
background-image: url("../img/mozilla-logo.svg#logo-white");
background-repeat: no-repeat;
position: absolute;
bottom: 1.2rem;
right: 1.2rem;
left: auto;
bottom: 0;
right: 0;
}
html[dir="rtl"] .focus-stream > .standalone-moz-logo {
left: 1.2rem;
html[dir="rtl"] .standalone-overlay-wrapper > .standalone-moz-logo {
left: 0;
right: auto;
}
@media screen and (max-width:640px) {
.focus-stream > .standalone-moz-logo {
display: none;
}
}
.btn-large {
font-size: 1rem;
padding: .3em .5rem;
}
@media screen and (max-width:640px) {
.standalone-overlay-wrapper > .standalone-moz-logo {
display: none;
}
.standalone-overlay-wrapper > .hello-logo {
display: none;
}
}
/**
* Unsupported/expired views
*/

View File

@ -390,32 +390,6 @@ loop.standaloneRoomViews = (function(mozL10n) {
}
});
var StandaloneRoomHeader = React.createClass({displayName: "StandaloneRoomHeader",
propTypes: {
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired
},
recordClick: function() {
this.props.dispatcher.dispatch(new sharedActions.RecordClick({
linkInfo: "Support link click"
}));
},
render: function() {
return (
React.createElement("header", null,
React.createElement("h1", null, mozL10n.get("clientShortname2")),
React.createElement("a", {href: loop.config.generalSupportUrl,
onClick: this.recordClick,
rel: "noreferrer",
target: "_blank"},
React.createElement("i", {className: "icon icon-help"})
)
)
);
}
});
var StandaloneRoomView = React.createClass({displayName: "StandaloneRoomView",
mixins: [
Backbone.Events,
@ -628,8 +602,6 @@ loop.standaloneRoomViews = (function(mozL10n) {
return (
React.createElement("div", {className: "room-conversation-wrapper standalone-room-wrapper"},
React.createElement("div", {className: "beta-logo"}),
React.createElement(StandaloneRoomHeader, {dispatcher: this.props.dispatcher}),
React.createElement(sharedViews.MediaLayoutView, {
dispatcher: this.props.dispatcher,
displayScreenShare: displayScreenShare,
@ -647,6 +619,7 @@ loop.standaloneRoomViews = (function(mozL10n) {
screenSharePosterUrl: this.props.screenSharePosterUrl,
showContextRoomName: true,
useDesktopPaths: false},
React.createElement(StandaloneOverlayWrapper, {dispatcher: this.props.dispatcher}),
React.createElement(StandaloneRoomInfoArea, {activeRoomStore: this.props.activeRoomStore,
dispatcher: this.props.dispatcher,
failureReason: this.state.failureReason,
@ -662,22 +635,49 @@ loop.standaloneRoomViews = (function(mozL10n) {
publishStream: this.publishStream,
show: true,
video: {enabled: !this.state.videoMuted,
visible: this._roomIsActive()}}),
React.createElement(StandaloneMozLogo, {dispatcher: this.props.dispatcher})
visible: this._roomIsActive()}})
)
)
);
}
});
var StandaloneMozLogo = React.createClass({displayName: "StandaloneMozLogo",
var StandaloneOverlayWrapper = React.createClass({displayName: "StandaloneOverlayWrapper",
propTypes: {
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired
},
render: function() {
return (
React.createElement("div", {className: "standalone-moz-logo"})
React.createElement("div", {className: "standalone-overlay-wrapper"},
React.createElement("div", {className: "hello-logo"}),
React.createElement(GeneralSupportURL, {dispatcher: this.props.dispatcher}),
React.createElement("div", {className: "standalone-moz-logo"})
)
);
}
});
var GeneralSupportURL = React.createClass({displayName: "GeneralSupportURL",
propTypes: {
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired
},
generalSupportUrlClick: function() {
this.props.dispatcher.dispatch(new sharedActions.RecordClick({
linkInfo: "Support link click"
}));
},
render: function() {
return (
React.createElement("a", {className: "general-support-url",
href: loop.config.generalSupportUrl,
onClick: this.generalSupportUrlClick,
rel: "noreferrer",
target: "_blank"},
React.createElement("div", {className: "icon icon-help"})
)
);
}
});
@ -722,8 +722,8 @@ loop.standaloneRoomViews = (function(mozL10n) {
StandaloneHandleUserAgentView: StandaloneHandleUserAgentView,
StandaloneRoomControllerView: StandaloneRoomControllerView,
StandaloneRoomFailureView: StandaloneRoomFailureView,
StandaloneRoomHeader: StandaloneRoomHeader,
StandaloneRoomInfoArea: StandaloneRoomInfoArea,
StandaloneOverlayWrapper: StandaloneOverlayWrapper,
StandaloneRoomView: StandaloneRoomView,
ToSView: ToSView
};

View File

@ -390,32 +390,6 @@ loop.standaloneRoomViews = (function(mozL10n) {
}
});
var StandaloneRoomHeader = React.createClass({
propTypes: {
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired
},
recordClick: function() {
this.props.dispatcher.dispatch(new sharedActions.RecordClick({
linkInfo: "Support link click"
}));
},
render: function() {
return (
<header>
<h1>{mozL10n.get("clientShortname2")}</h1>
<a href={loop.config.generalSupportUrl}
onClick={this.recordClick}
rel="noreferrer"
target="_blank">
<i className="icon icon-help"></i>
</a>
</header>
);
}
});
var StandaloneRoomView = React.createClass({
mixins: [
Backbone.Events,
@ -628,8 +602,6 @@ loop.standaloneRoomViews = (function(mozL10n) {
return (
<div className="room-conversation-wrapper standalone-room-wrapper">
<div className="beta-logo" />
<StandaloneRoomHeader dispatcher={this.props.dispatcher} />
<sharedViews.MediaLayoutView
dispatcher={this.props.dispatcher}
displayScreenShare={displayScreenShare}
@ -647,6 +619,7 @@ loop.standaloneRoomViews = (function(mozL10n) {
screenSharePosterUrl={this.props.screenSharePosterUrl}
showContextRoomName={true}
useDesktopPaths={false}>
<StandaloneOverlayWrapper dispatcher={this.props.dispatcher} />
<StandaloneRoomInfoArea activeRoomStore={this.props.activeRoomStore}
dispatcher={this.props.dispatcher}
failureReason={this.state.failureReason}
@ -663,21 +636,48 @@ loop.standaloneRoomViews = (function(mozL10n) {
show={true}
video={{enabled: !this.state.videoMuted,
visible: this._roomIsActive()}} />
<StandaloneMozLogo dispatcher={this.props.dispatcher}/>
</sharedViews.MediaLayoutView>
</div>
);
}
});
var StandaloneMozLogo = React.createClass({
var StandaloneOverlayWrapper = React.createClass({
propTypes: {
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired
},
render: function() {
return (
<div className="standalone-moz-logo" />
<div className="standalone-overlay-wrapper">
<div className="hello-logo"></div>
<GeneralSupportURL dispatcher={this.props.dispatcher} />
<div className="standalone-moz-logo" />
</div>
);
}
});
var GeneralSupportURL = React.createClass({
propTypes: {
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired
},
generalSupportUrlClick: function() {
this.props.dispatcher.dispatch(new sharedActions.RecordClick({
linkInfo: "Support link click"
}));
},
render: function() {
return (
<a className="general-support-url"
href={loop.config.generalSupportUrl}
onClick={this.generalSupportUrlClick}
rel="noreferrer"
target="_blank">
<div className="icon icon-help"></div>
</a>
);
}
});
@ -722,8 +722,8 @@ loop.standaloneRoomViews = (function(mozL10n) {
StandaloneHandleUserAgentView: StandaloneHandleUserAgentView,
StandaloneRoomControllerView: StandaloneRoomControllerView,
StandaloneRoomFailureView: StandaloneRoomFailureView,
StandaloneRoomHeader: StandaloneRoomHeader,
StandaloneRoomInfoArea: StandaloneRoomInfoArea,
StandaloneOverlayWrapper: StandaloneOverlayWrapper,
StandaloneRoomView: StandaloneRoomView,
ToSView: ToSView
};

View File

@ -13,9 +13,9 @@
"dependencies": {},
"devDependencies": {
"compression": "1.5.x",
"eslint": "1.2.x",
"eslint": "1.6.x",
"eslint-plugin-mozilla": "../../../../testing/eslint-plugin-mozilla",
"eslint-plugin-react": "3.2.x",
"eslint-plugin-react": "3.5.x",
"exports-loader": "0.6.x",
"express": "4.x",
"imports-loader": "0.6.x",

View File

@ -40,6 +40,7 @@ describe("loop.standaloneRoomViews", function() {
close: sandbox.stub(),
addEventListener: function() {},
document: { addEventListener: function(){} },
removeEventListener: function() {},
setTimeout: function(callback) { callback(); }
};
loop.shared.mixins.setRootObject(fakeWindow);
@ -211,7 +212,7 @@ describe("loop.standaloneRoomViews", function() {
function mountTestComponent() {
return TestUtils.renderIntoDocument(
React.createElement(
loop.standaloneRoomViews.StandaloneRoomHeader, {
loop.standaloneRoomViews.StandaloneOverlayWrapper, {
dispatcher: dispatcher
}));
}

View File

@ -52,7 +52,6 @@
#nav-bar[brighttext] {
--toolbarbutton-hover-background: rgba(255,255,255,.25);
--toolbarbutton-hover-bordercolor: rgba(255,255,255,.5);
--toolbarbutton-hover-boxshadow: none;
--toolbarbutton-active-background: rgba(255,255,255,.4);
--toolbarbutton-active-bordercolor: rgba(255,255,255,.7);
@ -61,6 +60,17 @@
--toolbarbutton-checkedhover-backgroundcolor: rgba(255,255,255,.3);
}
#nav-bar:-moz-lwtheme {
--toolbarbutton-hover-background: rgba(255,255,255,.25);
--toolbarbutton-hover-bordercolor: rgba(0,0,0,.2);
--toolbarbutton-active-background: rgba(70%,70%,70%,.25);
--toolbarbutton-active-bordercolor: rgba(0,0,0,.3);
--toolbarbutton-active-boxshadow: 0 0 2px rgba(0,0,0,.6) inset;
--toolbarbutton-checkedhover-backgroundcolor: rgba(85%,85%,85%,.25);
}
#menubar-items {
-moz-box-orient: vertical; /* for flex hack */
}
@ -1136,10 +1146,6 @@ toolbar .toolbarbutton-1:-moz-any(@primaryToolbarButtons@) > :-moz-any(.toolbarb
}
#nav-bar:-moz-lwtheme {
--urlbar-border-color: rgba(0,0,0,.32);
}
#nav-bar:-moz-lwtheme-brighttext {
--urlbar-border-color: var(--toolbarbutton-hover-bordercolor);
}
@ -1171,11 +1177,6 @@ toolbar .toolbarbutton-1:-moz-any(@primaryToolbarButtons@) > :-moz-any(.toolbarb
border-color: var(--urlbar-border-color);
}
#urlbar:-moz-lwtheme-brighttext,
.searchbar-textbox:-moz-lwtheme-brighttext {
background-clip: border-box;
}
#urlbar:hover,
.searchbar-textbox:hover {
border-color: var(--urlbar-border-color-hover);

View File

@ -384,13 +384,6 @@ exports.dumpv = function(msg) {
// loader, so define it on dumpn instead.
exports.dumpv.wantVerbose = false;
exports.dbg_assert = function dbg_assert(cond, e) {
if (!cond) {
return e;
}
};
/**
* Utility function for updating an object with the properties of
* other objects.
@ -447,6 +440,50 @@ exports.defineLazyGetter = function defineLazyGetter(aObject, aName, aLambda) {
});
};
// DEPRECATED: use DevToolsUtils.assert(condition, message) instead!
exports.dbg_assert = function dbg_assert(cond, e) {
if (!cond) {
return e;
}
};
/**
* DevToolsUtils.assert(condition, message)
*
* @param Boolean condition
* @param String message
*
* Assertions are enabled when any of the following are true:
* - This is a DEBUG_JS_MODULES build
* - This is a DEBUG build
* - DevToolsUtils.testing is set to true
*
* If assertions are enabled, then `condition` is checked and if false-y, the
* assertion failure is logged and then an error is thrown.
*
* If assertions are not enabled, then this function is a no-op.
*
* This is an improvement over `dbg_assert`, which doesn't actually cause any
* fatal behavior, and is therefore much easier to accidentally ignore.
*/
exports.defineLazyGetter(exports, "assert", () => {
function noop(condition, msg) { }
function assert(condition, message) {
if (!condition) {
const err = new Error("Assertion failure: " + message);
exports.reportException("DevToolsUtils.assert", err);
throw err;
}
}
const scope = {};
Cu.import("resource://gre/modules/AppConstants.jsm", scope);
const { DEBUG, DEBUG_JS_MODULES } = scope.AppConstants;
return (DEBUG || DEBUG_JS_MODULES || exports.testing) ? assert : noop;
});
/**
* Defines a getter on a specified object for a module. The module will not
* be imported until first use.

View File

@ -0,0 +1,324 @@
/* 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/. */
/*** Visitor ****************************************************************/
/**
* A Visitor visits each node and edge of a census report tree as the census
* report is being traversed by `walk`.
*/
function Visitor() { };
exports.Visitor = Visitor;
/**
* The `enter` method is called when a new sub-report is entered in traversal.
*
* @param {Object} breakdown
* The breakdown for the sub-report that is being entered by traversal.
*
* @param {any} edge
* The edge leading to this sub-report. The edge is null if (but not iff!
* eg, null allocation stack edges) we are entering the root report.
*/
Visitor.prototype.enter = function (breakdown, edge) { };
/**
* The `exit` method is called when traversal of a sub-report has finished.
*
* @param {Object} breakdown
* The breakdown for the sub-report whose traversal has finished.
*/
Visitor.prototype.exit = function (breakdown) { };
/**
* The `count` method is called when leaf nodes (reports whose breakdown is
* by: "count") in the report tree are encountered.
*
* @param {Object} breakdown
* The count breakdown for this report.
*
* @param {Object} report
* The report generated by a breakdown by "count".
*
* @param {any|null} edge
* The edge leading to this count report. The edge is null if we are
* entering the root report.
*/
Visitor.prototype.count = function (breakdown, report, edge) { }
/*** getReportEdges *********************************************************/
const EDGES = Object.create(null);
EDGES.count = function (breakdown, report) {
return [];
};
EDGES.internalType = function (breakdown, report) {
return Object.keys(report).map(key => ({
edge: key,
referent: report[key],
breakdown: breakdown.then
}));
};
EDGES.objectClass = function (breakdown, report) {
return Object.keys(report).map(key => ({
edge: key,
referent: report[key],
breakdown: key === "other" ? breakdown.other : breakdown.then
}));
};
EDGES.coarseType = function (breakdown, report) {
return [
{ edge: "objects", referent: report.objects, breakdown: breakdown.objects },
{ edge: "scripts", referent: report.scripts, breakdown: breakdown.scripts },
{ edge: "strings", referent: report.strings, breakdown: breakdown.strings },
{ edge: "other", referent: report.other, breakdown: breakdown.other },
];
};
EDGES.allocationStack = function (breakdown, report) {
const edges = [];
report.forEach((value, key) => {
edges.push({
edge: key,
referent: value,
breakdown: key === "noStack" ? breakdown.noStack : breakdown.then
})
});
return edges;
};
/**
* Get the set of outgoing edges from `report` as specified by the given
* breakdown.
*
* @param {Object} breakdown
* The census breakdown.
*
* @param {Object} report
* The census report.
*/
function getReportEdges(breakdown, report) {
return EDGES[breakdown.by](breakdown, report);
}
exports.getReportEdges = getReportEdges;
/*** walk *******************************************************************/
function recursiveWalk(breakdown, edge, report, visitor) {
if (breakdown.by === "count") {
visitor.enter(breakdown, edge);
visitor.count(breakdown, report, edge);
visitor.exit(breakdown, edge);
} else {
visitor.enter(breakdown, edge);
for (let { edge, referent, breakdown } of getReportEdges(breakdown, report)) {
recursiveWalk(breakdown, edge, referent, visitor);
}
visitor.exit(breakdown, edge);
}
};
/**
* Walk the given `report` that was generated by taking a census with the
* specified `breakdown`.
*
* @param {Object} breakdown
* The census breakdown.
*
* @param {Object} report
* The census report.
*
* @param {Visitor} visitor
* The Visitor instance to call into while traversing.
*/
function walk(breakdown, report, visitor) {
recursiveWalk(breakdown, null, report, visitor);
};
exports.walk = walk;
/*** diff *******************************************************************/
/**
* Return true if the object is a Map, false otherwise. Works with Map objects
* from other globals, unlike `instanceof`.
*
* @returns {Boolean}
*/
function isMap(obj) {
return Object.prototype.toString.call(obj) === "[object Map]";
}
/**
* A Visitor for computing the difference between the census report being
* traversed and the given other census.
*
* @param {Object} otherCensus
* The other census report.
*/
function DiffVisitor(otherCensus) {
// The other census we are comparing against.
this._otherCensus = otherCensus;
// Stack maintaining the current corresponding sub-report for the other
// census we are comparing against.
this._otherCensusStack = [];
// Stack maintaining the set of edges visited at each sub-report.
this._edgesVisited = [new Set()];
// The final delta census. Valid only after traversal.
this._results = null;
// Stack maintaining the results corresponding to each sub-report we are
// currently traversing.
this._resultsStack = [];
}
DiffVisitor.prototype = Object.create(Visitor.prototype);
/**
* Given a report and an outgoing edge, get the edge's referent.
*/
DiffVisitor.prototype._get = function (report, edge) {
if (!report) {
return undefined;
}
return isMap(report) ? report.get(edge) : report[edge];
};
/**
* Given a report, an outgoing edge, and a value, set the edge's referent to
* the given value.
*/
DiffVisitor.prototype._set = function (report, edge, val) {
if (isMap(report)) {
report.set(edge, val);
} else {
report[edge] = val;
}
};
/**
* @overrides Visitor.prototype.enter
*/
DiffVisitor.prototype.enter = function (breakdown, edge) {
const isFirstTimeEntering = this._results === null;
const newResults = breakdown.by === "allocationStack" ? new Map() : {};
let newOther;
if (!this._results) {
// This is the first time we have entered a sub-report.
this._results = newResults;
newOther = this._otherCensus;
} else {
const topResults = this._resultsStack[this._resultsStack.length - 1];
this._set(topResults, edge, newResults);
const topOther = this._otherCensusStack[this._otherCensusStack.length - 1];
newOther = this._get(topOther, edge);
}
this._resultsStack.push(newResults);
this._otherCensusStack.push(newOther);
const visited = this._edgesVisited[this._edgesVisited.length - 1];
visited.add(edge);
this._edgesVisited.push(new Set());
};
/**
* @overrides Visitor.prototype.exit
*/
DiffVisitor.prototype.exit = function (breakdown) {
// Find all the edges in the other census report that were not traversed and
// add them to the results directly.
const other = this._otherCensusStack[this._otherCensusStack.length - 1];
if (other) {
const visited = this._edgesVisited[this._edgesVisited.length - 1];
const unvisited = getReportEdges(breakdown, other)
.map(e => e.edge)
.filter(e => !visited.has(e));
const results = this._resultsStack[this._resultsStack.length - 1];
for (let edge of unvisited) {
this._set(results, edge, this._get(other, edge));
}
}
this._otherCensusStack.pop();
this._resultsStack.pop();
this._edgesVisited.pop();
};
/**
* @overrides Visitor.prototype.count
*/
DiffVisitor.prototype.count = function (breakdown, report, edge) {
const other = this._otherCensusStack[this._otherCensusStack.length - 1];
const results = this._resultsStack[this._resultsStack.length - 1];
if (other) {
if (breakdown.count) {
results.count = other.count - report.count;
}
if (breakdown.bytes) {
results.bytes = other.bytes - report.bytes;
}
} else {
if (breakdown.count) {
results.count = -report.count;
}
if (breakdown.bytes) {
results.bytes = -report.bytes;
}
}
};
/**
* Get the resulting report of the difference between the traversed census
* report and the other census report.
*
* @returns {Object}
* The delta census report.
*/
DiffVisitor.prototype.results = function () {
if (!this._results) {
throw new Error("Attempt to get results before computing diff!");
}
if (this._resultsStack.length) {
throw new Error("Attempt to get results while still computing diff!");
}
return this._results;
};
/**
* Take the difference between two censuses. The resulting delta report
* contains the number/size of things that are in the `endCensus` that are not
* in the `startCensus`.
*
* @param {Object} breakdown
* The breakdown used to generate both census reports.
*
* @param {Object} startCensus
* The first census report.
*
* @param {Object} endCensus
* The second census report.
*
* @returns {Object}
* A delta report mirroring the structure of the two census reports
* (as specified by the given breakdown).
*/
function diff(breakdown, startCensus, endCensus) {
const visitor = new DiffVisitor(endCensus);
walk(breakdown, startCensus, visitor);
return visitor.results();
};
exports.diff = diff

View File

@ -76,7 +76,7 @@ HeapAnalysesClient.prototype.readHeapSnapshot = function (snapshotFilePath) {
* if `asTreeNode` is true.
*/
HeapAnalysesClient.prototype.takeCensus = function (snapshotFilePath,
censusOptions={},
censusOptions,
requestOptions={}) {
return this._worker.performTask("takeCensus", {
snapshotFilePath,
@ -84,3 +84,42 @@ HeapAnalysesClient.prototype.takeCensus = function (snapshotFilePath,
requestOptions,
});
};
/**
* Request that the worker take a census on the heap snapshots with the given
* paths and then return the difference between them. Both heap snapshots must
* have already been read into memory by the worker (see `readHeapSnapshot`).
*
* @param {String} firstSnapshotFilePath
* The first snapshot file path.
*
* @param {String} secondSnapshotFilePath
* The second snapshot file path.
*
* @param {Object} censusOptions
* A structured-cloneable object specifying the requested census's
* breakdown. See the "takeCensus" section of
* `js/src/doc/Debugger/Debugger.Memory.md` for detailed documentation.
*
* @param {Object} requestOptions
* An object specifying options for this request.
* - {Boolean} asTreeNode
* Whether the resulting delta report should be converted to a census
* tree node before returned. Defaults to false.
*
* @returns Promise<delta report|CensusTreeNode>
* The delta report generated by diffing the two census reports, or a
* CensusTreeNode generated from the delta report if
* `requestOptions.asTreeNode` was true.
*/
HeapAnalysesClient.prototype.takeCensusDiff = function (firstSnapshotFilePath,
secondSnapshotFilePath,
censusOptions,
requestOptions = {}) {
return this._worker.performTask("takeCensusDiff", {
firstSnapshotFilePath,
secondSnapshotFilePath,
censusOptions,
requestOptions
});
}

View File

@ -13,6 +13,7 @@
importScripts("resource://gre/modules/workers/require.js");
importScripts("resource://gre/modules/devtools/shared/worker/helper.js");
const { CensusTreeNode } = require("resource://gre/modules/devtools/shared/heapsnapshot/census-tree-node.js");
const CensusUtils = require("resource://gre/modules/devtools/shared/heapsnapshot/CensusUtils.js");
// The set of HeapSnapshot instances this worker has read into memory. Keyed by
// snapshot file path.
@ -36,5 +37,35 @@ workerHelper.createTask(self, "takeCensus", ({ snapshotFilePath, censusOptions,
}
let report = snapshots[snapshotFilePath].takeCensus(censusOptions);
return requestOptions.asTreeNode ? new CensusTreeNode(censusOptions.breakdown, report) : report;
return requestOptions.asTreeNode
? new CensusTreeNode(censusOptions.breakdown, report)
: report;
});
/**
* @see HeapAnalysesClient.prototype.takeCensusDiff
*/
workerHelper.createTask(self, "takeCensusDiff", request => {
const {
firstSnapshotFilePath,
secondSnapshotFilePath,
censusOptions,
requestOptions
} = request;
if (!snapshots[firstSnapshotFilePath]) {
throw new Error(`No known heap snapshot for '${firstSnapshotFilePath}'`);
}
if (!snapshots[secondSnapshotFilePath]) {
throw new Error(`No known heap snapshot for '${secondSnapshotFilePath}'`);
}
const first = snapshots[firstSnapshotFilePath].takeCensus(censusOptions);
const second = snapshots[secondSnapshotFilePath].takeCensus(censusOptions);
const delta = CensusUtils.diff(censusOptions.breakdown, first, second);
return requestOptions.asTreeNode
? new CensusTreeNode(censusOptions.breakdown, delta)
: delta;
});

View File

@ -202,6 +202,12 @@ HeapSnapshot::saveNode(const protobuf::Node& node)
return false;
NodeId id = node.id();
// NodeIds are derived from pointers (at most 48 bits) and we rely on them
// fitting into JS numbers (IEEE 754 doubles, can precisely store 53 bit
// integers) despite storing them on disk as 64 bit integers.
if (NS_WARN_IF(!JS::Value::isNumberRepresentable(id)))
return false;
// Should only deserialize each node once.
if (NS_WARN_IF(nodes.has(id)))
return false;

View File

@ -45,6 +45,7 @@ FINAL_LIBRARY = 'xul'
DevToolsModules(
'census-tree-node.js',
'CensusUtils.js',
'HeapAnalysesClient.js',
'HeapAnalysesWorker.js',
'HeapSnapshotFileUtils.js',

View File

@ -21,6 +21,7 @@ const HeapAnalysesClient =
require("devtools/shared/heapsnapshot/HeapAnalysesClient");
const Services = require("Services");
const { CensusTreeNode } = require("devtools/shared/heapsnapshot/census-tree-node");
const CensusUtils = require("devtools/shared/heapsnapshot/CensusUtils");
// Always log packets when running tests. runxpcshelltests.py will throw
// the output away anyway, unless you give it the --verbose flag.
@ -149,7 +150,96 @@ function saveHeapSnapshotAndTakeCensus(dbg=null, censusOptions=undefined) {
return snapshot.takeCensus(censusOptions);
}
/**
* Assert that creating a CensusTreeNode from the given `report` with the
* specified `breakdown` creates the given `expected` CensusTreeNode.
*
* @param {Object} breakdown
* The census breakdown.
*
* @param {Object} report
* The census report.
*
* @param {Object} expected
* The expected CensusTreeNode result.
*
* @param {String} assertion
* The assertion message.
*/
function compareCensusViewData (breakdown, report, expected, assertion) {
let data = new CensusTreeNode(breakdown, report);
equal(JSON.stringify(data), JSON.stringify(expected), assertion);
}
// Deep structural equivalence that can handle Map objects in addition to plain
// objects.
function assertStructurallyEquivalent(actual, expected, path="root") {
equal(typeof actual, typeof expected, `${path}: typeof should be the same`);
if (actual && typeof actual === "object") {
const actualProtoString = Object.prototype.toString.call(actual);
const expectedProtoString = Object.prototype.toString.call(expected);
equal(actualProtoString, expectedProtoString,
`${path}: Object.prototype.toString.call() should be the same`);
if (actualProtoString === "[object Map]") {
const expectedKeys = new Set([...expected.keys()]);
for (let key of actual.keys()) {
ok(expectedKeys.has(key),
`${path}: every key in actual should exist in expected: ${String(key).slice(0, 10)}`);
expectedKeys.delete(key);
assertStructurallyEquivalent(actual.get(key), expected.get(key),
path + ".get(" + String(key).slice(0, 20) + ")");
}
equal(expectedKeys.size, 0,
`${path}: every key in expected should also exist in actual`);
} else {
const expectedKeys = new Set(Object.keys(expected));
for (let key of Object.keys(actual)) {
ok(expectedKeys.has(key),
`${path}: every key in actual should exist in expected: ${key}`);
expectedKeys.delete(key);
assertStructurallyEquivalent(actual[key], expected[key], path + "." + key);
}
equal(expectedKeys.size, 0,
`${path}: every key in expected should also exist in actual`);
}
} else {
equal(actual, expected, `${path}: primitives should be equal`);
}
}
/**
* Assert that creating a diff of the `first` and `second` census reports
* creates the `expected` delta-report.
*
* @param {Object} breakdown
* The census breakdown.
*
* @param {Object} first
* The first census report.
*
* @param {Object} second
* The second census report.
*
* @param {Object} expected
* The expected delta-report.
*/
function assertDiff(breakdown, first, second, expected) {
dumpn("Diffing census reports:");
dumpn("Breakdown: " + JSON.stringify(breakdown, null, 4));
dumpn("First census report: " + JSON.stringify(first, null, 4));
dumpn("Second census report: " + JSON.stringify(second, null, 4));
dumpn("Expected delta-report: " + JSON.stringify(expected, null, 4));
const actual = CensusUtils.diff(breakdown, first, second);
dumpn("Actual delta-report: " + JSON.stringify(actual, null, 4));
assertStructurallyEquivalent(actual, expected);
}

View File

@ -0,0 +1,49 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
// Test that the HeapAnalyses{Client,Worker} can take diffs between censuses.
function run_test() {
run_next_test();
}
const BREAKDOWN = {
by: "objectClass",
then: { by: "count", count: true, bytes: false },
other: { by: "count", count: true, bytes: false },
};
add_task(function* () {
const client = new HeapAnalysesClient();
const markers = [allocationMarker()];
const firstSnapshotFilePath = saveNewHeapSnapshot();
// Allocate and hold an additional AllocationMarker object so we can see it in
// the next heap snapshot.
markers.push(allocationMarker());
const secondSnapshotFilePath = saveNewHeapSnapshot();
yield client.readHeapSnapshot(firstSnapshotFilePath);
yield client.readHeapSnapshot(secondSnapshotFilePath);
ok(true, "Should have read both heap snapshot files");
const delta = yield client.takeCensusDiff(firstSnapshotFilePath,
secondSnapshotFilePath,
{ breakdown: BREAKDOWN });
equal(delta.AllocationMarker.count, 1,
"There exists one new AllocationMarker in the second heap snapshot");
const deltaTreeNode = yield client.takeCensusDiff(firstSnapshotFilePath,
secondSnapshotFilePath,
{ breakdown: BREAKDOWN },
{ asTreeNode: true });
compareCensusViewData(BREAKDOWN, delta, deltaTreeNode,
"Returning delta-census as a tree node represents same data as the report");
client.destroy();
});

View File

@ -0,0 +1,74 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
// Test diffing census reports of breakdown by "internalType".
const BREAKDOWN = {
by: "internalType",
then: { by: "count", count: true, bytes: true }
};
const REPORT1 = {
"JSObject": {
"count": 10,
"bytes": 100,
},
"js::Shape": {
"count": 50,
"bytes": 500,
},
"JSString": {
"count": 0,
"bytes": 0,
},
"js::LazyScript": {
"count": 1,
"bytes": 10,
},
};
const REPORT2 = {
"JSObject": {
"count": 11,
"bytes": 110,
},
"js::Shape": {
"count": 51,
"bytes": 510,
},
"JSString": {
"count": 1,
"bytes": 1,
},
"js::BaseShape": {
"count": 1,
"bytes": 42,
},
};
const EXPECTED = {
"JSObject": {
"count": 1,
"bytes": 10,
},
"js::Shape": {
"count": 1,
"bytes": 10,
},
"JSString": {
"count": 1,
"bytes": 1,
},
"js::LazyScript": {
"count": -1,
"bytes": -10,
},
"js::BaseShape": {
"count": 1,
"bytes": 42,
},
};
function run_test() {
assertDiff(BREAKDOWN, REPORT1, REPORT2, EXPECTED);
}

View File

@ -0,0 +1,25 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
// Test diffing census reports of breakdown by "count".
const BREAKDOWN = { by: "count", count: true, bytes: true };
const REPORT1 = {
"count": 10,
"bytes": 100,
};
const REPORT2 = {
"count": 11,
"bytes": 110,
};
const EXPECTED = {
"count": 1,
"bytes": 10,
};
function run_test() {
assertDiff(BREAKDOWN, REPORT1, REPORT2, EXPECTED);
}

View File

@ -0,0 +1,73 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
// Test diffing census reports of breakdown by "coarseType".
const BREAKDOWN = {
by: "coarseType",
objects: { by: "count", count: true, bytes: true },
scripts: { by: "count", count: true, bytes: true },
strings: { by: "count", count: true, bytes: true },
other: { by: "count", count: true, bytes: true },
};
const REPORT1 = {
objects: {
count: 1,
bytes: 10,
},
scripts: {
count: 1,
bytes: 10,
},
strings: {
count: 1,
bytes: 10,
},
other: {
count: 3,
bytes: 30,
},
};
const REPORT2 = {
objects: {
count: 1,
bytes: 10,
},
scripts: {
count: 0,
bytes: 0,
},
strings: {
count: 2,
bytes: 20,
},
other: {
count: 4,
bytes: 40,
},
};
const EXPECTED = {
objects: {
count: 0,
bytes: 0,
},
scripts: {
count: -1,
bytes: -10,
},
strings: {
count: 1,
bytes: 10,
},
other: {
count: 1,
bytes: 10,
},
};
function run_test() {
assertDiff(BREAKDOWN, REPORT1, REPORT2, EXPECTED);
}

View File

@ -0,0 +1,63 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
// Test diffing census reports of breakdown by "objectClass".
const BREAKDOWN = {
by: "objectClass",
then: { by: "count", count: true, bytes: true },
other: { by: "count", count: true, bytes: true },
};
const REPORT1 = {
"Array": {
count: 1,
bytes: 100,
},
"Function": {
count: 10,
bytes: 10,
},
"other": {
count: 10,
bytes: 100,
}
};
const REPORT2 = {
"Object": {
count: 1,
bytes: 100,
},
"Function": {
count: 20,
bytes: 20,
},
"other": {
count: 10,
bytes: 100,
}
};
const EXPECTED = {
"Array": {
count: -1,
bytes: -100,
},
"Function": {
count: 10,
bytes: 10,
},
"other": {
count: 0,
bytes: 0,
},
"Object": {
count: 1,
bytes: 100,
},
};
function run_test() {
assertDiff(BREAKDOWN, REPORT1, REPORT2, EXPECTED);
}

View File

@ -0,0 +1,34 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
// Test diffing census reports of breakdown by "allocationStack".
const BREAKDOWN = {
by: "allocationStack",
then: { by: "count", count: true, bytes: true },
noStack: { by: "count", count: true, bytes: true },
};
const stack1 = saveStack();
const stack2 = saveStack();
const stack3 = saveStack();
const REPORT1 = new Map([
[stack1, { "count": 10, "bytes": 100 }],
[stack2, { "count": 1, "bytes": 10 }],
]);
const REPORT2 = new Map([
[stack2, { "count": 10, "bytes": 100 }],
[stack3, { "count": 1, "bytes": 10 }],
]);
const EXPECTED = new Map([
[stack1, { "count": -10, "bytes": -100 }],
[stack2, { "count": 9, "bytes": 90 }],
[stack3, { "count": 1, "bytes": 10 }],
]);
function run_test() {
assertDiff(BREAKDOWN, REPORT1, REPORT2, EXPECTED);
}

View File

@ -0,0 +1,137 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
// Test diffing census reports of a "complex" and "realistic" breakdown.
const BREAKDOWN = {
by: "coarseType",
objects: {
by: "allocationStack",
then: {
by: "objectClass",
then: { by: "count", count: false, bytes: true },
other: { by: "count", count: false, bytes: true }
},
noStack: {
by: "objectClass",
then: { by: "count", count: false, bytes: true },
other: { by: "count", count: false, bytes: true }
}
},
strings: {
by: "internalType",
then: { by: "count", count: false, bytes: true }
},
scripts: {
by: "internalType",
then: { by: "count", count: false, bytes: true }
},
other: {
by: "internalType",
then: { by: "count", count: false, bytes: true }
},
};
const stack1 = saveStack();
const stack2 = saveStack();
const stack3 = saveStack();
const REPORT1 = {
objects: new Map([
[stack1, { Function: { bytes: 1 },
Object: { bytes: 2 },
other: { bytes: 0 },
}],
[stack2, { Array: { bytes: 3 },
Date: { bytes: 4 },
other: { bytes: 0 },
}],
["noStack", { Object: { bytes: 3 }}],
]),
strings: {
JSAtom: { bytes: 10 },
JSLinearString: { bytes: 5 },
},
scripts: {
JSScript: { bytes: 1 },
"js::jit::JitCode": { bytes: 2 },
},
other: {
"mozilla::dom::Thing": { bytes: 1 },
}
};
const REPORT2 = {
objects: new Map([
[stack2, { Array: { bytes: 1 },
Date: { bytes: 2 },
other: { bytes: 3 },
}],
[stack3, { Function: { bytes: 1 },
Object: { bytes: 2 },
other: { bytes: 0 },
}],
["noStack", { Object: { bytes: 3 }}],
]),
strings: {
JSAtom: { bytes: 5 },
JSLinearString: { bytes: 10 },
},
scripts: {
JSScript: { bytes: 2 },
"js::LazyScript": { bytes: 42 },
"js::jit::JitCode": { bytes: 1 },
},
other: {
"mozilla::dom::OtherThing": { bytes: 1 },
}
};
const EXPECTED = {
"objects": new Map([
[stack1, { Function: { bytes: -1 },
Object: { bytes: -2 },
other: { bytes: 0 },
}],
[stack2, { Array: { bytes: -2 },
Date: { bytes: -2 },
other: { bytes: 3 },
}],
[stack3, { Function: { bytes: 1 },
Object: { bytes: 2 },
other: { bytes: 0 },
}],
["noStack", { Object: { bytes: 0 }}],
]),
"scripts": {
"JSScript": {
"bytes": 1
},
"js::jit::JitCode": {
"bytes": -1
},
"js::LazyScript": {
"bytes": 42
}
},
"strings": {
"JSAtom": {
"bytes": -5
},
"JSLinearString": {
"bytes": 5
}
},
"other": {
"mozilla::dom::Thing": {
"bytes": -1
},
"mozilla::dom::OtherThing": {
"bytes": 1
}
}
};
function run_test() {
assertDiff(BREAKDOWN, REPORT1, REPORT2, EXPECTED);
}

View File

@ -9,10 +9,17 @@ support-files =
heap-snapshot-worker.js
Match.jsm
[test_census_diff_01.js]
[test_census_diff_02.js]
[test_census_diff_03.js]
[test_census_diff_04.js]
[test_census_diff_05.js]
[test_census_diff_06.js]
[test_census-tree-node-01.js]
[test_census-tree-node-02.js]
[test_census-tree-node-03.js]
[test_HeapAnalyses_readHeapSnapshot_01.js]
[test_HeapAnalyses_takeCensusDiff_01.js]
[test_HeapAnalyses_takeCensus_01.js]
[test_HeapAnalyses_takeCensus_02.js]
[test_HeapAnalyses_takeCensus_03.js]

View File

@ -35,6 +35,8 @@ namespace {
static BluetoothA2dpInterface* sBtA2dpInterface;
} // namespace
const int BluetoothA2dpManager::MAX_NUM_CLIENTS = 1;
NS_IMETHODIMP
BluetoothA2dpManager::Observe(nsISupports* aSubject,
const char* aTopic,
@ -86,40 +88,52 @@ AvStatusToSinkString(BluetoothA2dpConnectionState aState, nsAString& aString)
}
}
class BluetoothA2dpManager::InitResultHandler final
: public BluetoothA2dpResultHandler
class BluetoothA2dpManager::RegisterModuleResultHandler final
: public BluetoothSetupResultHandler
{
public:
InitResultHandler(BluetoothProfileResultHandler* aRes)
: mRes(aRes)
RegisterModuleResultHandler(BluetoothA2dpInterface* aInterface,
BluetoothProfileResultHandler* aRes)
: mInterface(aInterface)
, mRes(aRes)
{ }
void OnError(BluetoothStatus aStatus) override
{
BT_WARNING("BluetoothA2dpInterface::Init failed: %d",
MOZ_ASSERT(NS_IsMainThread());
BT_WARNING("BluetoothSetupInterface::RegisterModule failed for A2DP: %d",
(int)aStatus);
mInterface->SetNotificationHandler(nullptr);
if (mRes) {
mRes->OnError(NS_ERROR_FAILURE);
}
}
void Init() override
void RegisterModule() override
{
MOZ_ASSERT(NS_IsMainThread());
sBtA2dpInterface = mInterface;
if (mRes) {
mRes->Init();
}
}
private:
BluetoothA2dpInterface* mInterface;
nsRefPtr<BluetoothProfileResultHandler> mRes;
};
class BluetoothA2dpManager::OnErrorProfileResultHandlerRunnable final
class BluetoothA2dpManager::InitProfileResultHandlerRunnable final
: public nsRunnable
{
public:
OnErrorProfileResultHandlerRunnable(BluetoothProfileResultHandler* aRes,
nsresult aRv)
InitProfileResultHandlerRunnable(BluetoothProfileResultHandler* aRes,
nsresult aRv)
: mRes(aRes)
, mRv(aRv)
{
@ -128,7 +142,13 @@ public:
NS_IMETHOD Run() override
{
mRes->OnError(mRv);
MOZ_ASSERT(NS_IsMainThread());
if (NS_SUCCEEDED(mRv)) {
mRes->Init();
} else {
mRes->OnError(mRv);
}
return NS_OK;
}
@ -147,32 +167,64 @@ private:
void
BluetoothA2dpManager::InitA2dpInterface(BluetoothProfileResultHandler* aRes)
{
BluetoothInterface* btInf = BluetoothInterface::GetInstance();
MOZ_ASSERT(NS_IsMainThread());
if (sBtA2dpInterface) {
BT_LOGR("Bluetooth A2DP interface is already initalized.");
nsRefPtr<nsRunnable> r =
new InitProfileResultHandlerRunnable(aRes, NS_OK);
if (NS_FAILED(NS_DispatchToMainThread(r))) {
BT_LOGR("Failed to dispatch A2DP Init runnable");
}
return;
}
auto btInf = BluetoothInterface::GetInstance();
if (NS_WARN_IF(!btInf)) {
// If there's no HFP interface, we dispatch a runnable
// If there's no Bluetooth interface, we dispatch a runnable
// that calls the profile result handler.
nsRefPtr<nsRunnable> r =
new OnErrorProfileResultHandlerRunnable(aRes, NS_ERROR_FAILURE);
new InitProfileResultHandlerRunnable(aRes, NS_ERROR_FAILURE);
if (NS_FAILED(NS_DispatchToMainThread(r))) {
BT_LOGR("Failed to dispatch HFP OnError runnable");
BT_LOGR("Failed to dispatch A2DP OnError runnable");
}
return;
}
sBtA2dpInterface = btInf->GetBluetoothA2dpInterface();
if (NS_WARN_IF(!sBtA2dpInterface)) {
// If there's no HFP interface, we dispatch a runnable
auto setupInterface = btInf->GetBluetoothSetupInterface();
if (NS_WARN_IF(!setupInterface)) {
// If there's no Setup interface, we dispatch a runnable
// that calls the profile result handler.
nsRefPtr<nsRunnable> r =
new OnErrorProfileResultHandlerRunnable(aRes, NS_ERROR_FAILURE);
new InitProfileResultHandlerRunnable(aRes, NS_ERROR_FAILURE);
if (NS_FAILED(NS_DispatchToMainThread(r))) {
BT_LOGR("Failed to dispatch HFP OnError runnable");
BT_LOGR("Failed to dispatch A2DP OnError runnable");
}
return;
}
BluetoothA2dpManager* a2dpManager = BluetoothA2dpManager::Get();
sBtA2dpInterface->Init(a2dpManager, new InitResultHandler(aRes));
auto a2dpInterface = btInf->GetBluetoothA2dpInterface();
if (NS_WARN_IF(!a2dpInterface)) {
// If there's no A2DP interface, we dispatch a runnable
// that calls the profile result handler.
nsRefPtr<nsRunnable> r =
new InitProfileResultHandlerRunnable(aRes, NS_ERROR_FAILURE);
if (NS_FAILED(NS_DispatchToMainThread(r))) {
BT_LOGR("Failed to dispatch A2DP OnError runnable");
}
return;
}
// Set notification handler _before_ registering the module. It could
// happen that we receive notifications, before the result handler runs.
a2dpInterface->SetNotificationHandler(BluetoothA2dpManager::Get());
setupInterface->RegisterModule(
SETUP_SERVICE_ID_A2DP, 0, MAX_NUM_CLIENTS,
new RegisterModuleResultHandler(a2dpInterface, aRes));
}
BluetoothA2dpManager::~BluetoothA2dpManager()
@ -227,19 +279,22 @@ BluetoothA2dpManager::Get()
return sBluetoothA2dpManager;
}
class BluetoothA2dpManager::CleanupResultHandler final
: public BluetoothA2dpResultHandler
class BluetoothA2dpManager::UnregisterModuleResultHandler final
: public BluetoothSetupResultHandler
{
public:
CleanupResultHandler(BluetoothProfileResultHandler* aRes)
UnregisterModuleResultHandler(BluetoothProfileResultHandler* aRes)
: mRes(aRes)
{ }
void OnError(BluetoothStatus aStatus) override
{
BT_WARNING("BluetoothA2dpInterface::Cleanup failed: %d",
MOZ_ASSERT(NS_IsMainThread());
BT_WARNING("BluetoothSetupInterface::UnregisterModule failed for A2DP: %d",
(int)aStatus);
sBtA2dpInterface->SetNotificationHandler(nullptr);
sBtA2dpInterface = nullptr;
if (mRes) {
@ -247,8 +302,11 @@ public:
}
}
void Cleanup() override
void UnregisterModule() override
{
MOZ_ASSERT(NS_IsMainThread());
sBtA2dpInterface->SetNotificationHandler(nullptr);
sBtA2dpInterface = nullptr;
if (mRes) {
@ -260,26 +318,33 @@ private:
nsRefPtr<BluetoothProfileResultHandler> mRes;
};
class BluetoothA2dpManager::CleanupResultHandlerRunnable final
class BluetoothA2dpManager::DeinitProfileResultHandlerRunnable final
: public nsRunnable
{
public:
CleanupResultHandlerRunnable(BluetoothProfileResultHandler* aRes)
DeinitProfileResultHandlerRunnable(BluetoothProfileResultHandler* aRes,
nsresult aRv)
: mRes(aRes)
{ }
, mRv(aRv)
{
MOZ_ASSERT(mRes);
}
NS_IMETHOD Run() override
{
sBtA2dpInterface = nullptr;
MOZ_ASSERT(NS_IsMainThread());
if (mRes) {
if (NS_SUCCEEDED(mRv)) {
mRes->Deinit();
} else {
mRes->OnError(mRv);
}
return NS_OK;
}
private:
nsRefPtr<BluetoothProfileResultHandler> mRes;
nsresult mRv;
};
// static
@ -288,16 +353,45 @@ BluetoothA2dpManager::DeinitA2dpInterface(BluetoothProfileResultHandler* aRes)
{
MOZ_ASSERT(NS_IsMainThread());
if (sBtA2dpInterface) {
sBtA2dpInterface->Cleanup(new CleanupResultHandler(aRes));
} else if (aRes) {
// We dispatch a runnable here to make the profile resource handler
// behave as if A2DP was initialized.
nsRefPtr<nsRunnable> r = new CleanupResultHandlerRunnable(aRes);
if (!sBtA2dpInterface) {
BT_LOGR("Bluetooth A2DP interface has not been initalized.");
nsRefPtr<nsRunnable> r =
new DeinitProfileResultHandlerRunnable(aRes, NS_OK);
if (NS_FAILED(NS_DispatchToMainThread(r))) {
BT_LOGR("Failed to dispatch cleanup-result-handler runnable");
BT_LOGR("Failed to dispatch A2DP Deinit runnable");
}
return;
}
auto btInf = BluetoothInterface::GetInstance();
if (NS_WARN_IF(!btInf)) {
// If there's no backend interface, we dispatch a runnable
// that calls the profile result handler.
nsRefPtr<nsRunnable> r =
new DeinitProfileResultHandlerRunnable(aRes, NS_ERROR_FAILURE);
if (NS_FAILED(NS_DispatchToMainThread(r))) {
BT_LOGR("Failed to dispatch A2DP OnError runnable");
}
return;
}
auto setupInterface = btInf->GetBluetoothSetupInterface();
if (NS_WARN_IF(!setupInterface)) {
// If there's no Setup interface, we dispatch a runnable
// that calls the profile result handler.
nsRefPtr<nsRunnable> r =
new DeinitProfileResultHandlerRunnable(aRes, NS_ERROR_FAILURE);
if (NS_FAILED(NS_DispatchToMainThread(r))) {
BT_LOGR("Failed to dispatch A2DP OnError runnable");
}
return;
}
setupInterface->UnregisterModule(
SETUP_SERVICE_ID_A2DP,
new UnregisterModuleResultHandler(aRes));
}
void

View File

@ -17,6 +17,8 @@ class BluetoothA2dpManager : public BluetoothProfileManagerBase
, public BluetoothA2dpNotificationHandler
{
public:
static const int MAX_NUM_CLIENTS;
BT_DECL_PROFILE_MGR_BASE
virtual void GetName(nsACString& aName)
{
@ -47,12 +49,12 @@ protected:
virtual ~BluetoothA2dpManager();
private:
class CleanupResultHandler;
class CleanupResultHandlerRunnable;
class ConnectResultHandler;
class DeinitProfileResultHandlerRunnable;
class DisconnectResultHandler;
class InitResultHandler;
class OnErrorProfileResultHandlerRunnable;
class InitProfileResultHandlerRunnable;
class RegisterModuleResultHandler;
class UnregisterModuleResultHandler;
BluetoothA2dpManager();

View File

@ -35,6 +35,8 @@ namespace {
static BluetoothAvrcpInterface* sBtAvrcpInterface;
} // namespace
const int BluetoothAvrcpManager::MAX_NUM_CLIENTS = 1;
/*
* This function maps attribute id and returns corresponding values
*/
@ -118,18 +120,27 @@ BluetoothAvrcpManager::Reset()
mPlayStatus = ControlPlayStatus::PLAYSTATUS_STOPPED;
}
class BluetoothAvrcpManager::InitResultHandler final
: public BluetoothAvrcpResultHandler
class BluetoothAvrcpManager::RegisterModuleResultHandler final
: public BluetoothSetupResultHandler
{
public:
InitResultHandler(BluetoothProfileResultHandler* aRes)
: mRes(aRes)
{ }
RegisterModuleResultHandler(BluetoothAvrcpInterface* aInterface,
BluetoothProfileResultHandler* aRes)
: mInterface(aInterface)
, mRes(aRes)
{
MOZ_ASSERT(mInterface);
}
void OnError(BluetoothStatus aStatus) override
{
BT_WARNING("BluetoothAvrcpInterface::Init failed: %d",
MOZ_ASSERT(NS_IsMainThread());
BT_WARNING("BluetoothSetupInterface::RegisterModule failed for AVRCP: %d",
(int)aStatus);
mInterface->SetNotificationHandler(nullptr);
if (mRes) {
if (aStatus == STATUS_UNSUPPORTED) {
/* Not all versions of Bluedroid support AVRCP. So if the
@ -143,23 +154,28 @@ public:
}
}
void Init() override
void RegisterModule() override
{
MOZ_ASSERT(NS_IsMainThread());
sBtAvrcpInterface = mInterface;
if (mRes) {
mRes->Init();
}
}
private:
BluetoothAvrcpInterface* mInterface;
nsRefPtr<BluetoothProfileResultHandler> mRes;
};
class BluetoothAvrcpManager::OnErrorProfileResultHandlerRunnable final
class BluetoothAvrcpManager::InitProfileResultHandlerRunnable final
: public nsRunnable
{
public:
OnErrorProfileResultHandlerRunnable(BluetoothProfileResultHandler* aRes,
nsresult aRv)
InitProfileResultHandlerRunnable(BluetoothProfileResultHandler* aRes,
nsresult aRv)
: mRes(aRes)
, mRv(aRv)
{
@ -168,7 +184,13 @@ public:
NS_IMETHOD Run() override
{
mRes->OnError(mRv);
MOZ_ASSERT(NS_IsMainThread());
if (NS_SUCCEEDED(mRv)) {
mRes->Init();
} else {
mRes->OnError(mRv);
}
return NS_OK;
}
@ -184,32 +206,64 @@ private:
void
BluetoothAvrcpManager::InitAvrcpInterface(BluetoothProfileResultHandler* aRes)
{
BluetoothInterface* btInf = BluetoothInterface::GetInstance();
if (NS_WARN_IF(!btInf)) {
// If there's no HFP interface, we dispatch a runnable
// that calls the profile result handler.
MOZ_ASSERT(NS_IsMainThread());
if (sBtAvrcpInterface) {
BT_LOGR("Bluetooth AVRCP interface is already initalized.");
nsRefPtr<nsRunnable> r =
new OnErrorProfileResultHandlerRunnable(aRes, NS_ERROR_FAILURE);
new InitProfileResultHandlerRunnable(aRes, NS_OK);
if (NS_FAILED(NS_DispatchToMainThread(r))) {
BT_LOGR("Failed to dispatch HFP OnError runnable");
BT_LOGR("Failed to dispatch AVRCP Init runnable");
}
return;
}
sBtAvrcpInterface = btInf->GetBluetoothAvrcpInterface();
if (NS_WARN_IF(!sBtAvrcpInterface)) {
auto btInf = BluetoothInterface::GetInstance();
if (NS_WARN_IF(!btInf)) {
// If there's no Bluetooth interface, we dispatch a runnable
// that calls the profile result handler.
nsRefPtr<nsRunnable> r =
new InitProfileResultHandlerRunnable(aRes, NS_ERROR_FAILURE);
if (NS_FAILED(NS_DispatchToMainThread(r))) {
BT_LOGR("Failed to dispatch AVRCP OnError runnable");
}
return;
}
auto setupInterface = btInf->GetBluetoothSetupInterface();
if (NS_WARN_IF(!setupInterface)) {
// If there's no Setup interface, we dispatch a runnable
// that calls the profile result handler.
nsRefPtr<nsRunnable> r =
new InitProfileResultHandlerRunnable(aRes, NS_ERROR_FAILURE);
if (NS_FAILED(NS_DispatchToMainThread(r))) {
BT_LOGR("Failed to dispatch AVRCP OnError runnable");
}
return;
}
auto avrcpInterface = btInf->GetBluetoothAvrcpInterface();
if (NS_WARN_IF(!avrcpInterface)) {
// If there's no AVRCP interface, we dispatch a runnable
// that calls the profile result handler.
nsRefPtr<nsRunnable> r =
new OnErrorProfileResultHandlerRunnable(aRes, NS_ERROR_FAILURE);
new InitProfileResultHandlerRunnable(aRes, NS_ERROR_FAILURE);
if (NS_FAILED(NS_DispatchToMainThread(r))) {
BT_LOGR("Failed to dispatch HFP OnError runnable");
BT_LOGR("Failed to dispatch AVRCP OnError runnable");
}
return;
}
BluetoothAvrcpManager* avrcpManager = BluetoothAvrcpManager::Get();
sBtAvrcpInterface->Init(avrcpManager, new InitResultHandler(aRes));
// Set notification handler _before_ registering the module. It could
// happen that we receive notifications, before the result handler runs.
avrcpInterface->SetNotificationHandler(BluetoothAvrcpManager::Get());
setupInterface->RegisterModule(
SETUP_SERVICE_ID_AVRCP, 0, MAX_NUM_CLIENTS,
new RegisterModuleResultHandler(avrcpInterface, aRes));
}
BluetoothAvrcpManager::~BluetoothAvrcpManager()
@ -245,37 +299,36 @@ BluetoothAvrcpManager::Get()
return sBluetoothAvrcpManager;
}
class BluetoothAvrcpManager::CleanupResultHandler final
: public BluetoothAvrcpResultHandler
class BluetoothAvrcpManager::UnregisterModuleResultHandler final
: public BluetoothSetupResultHandler
{
public:
CleanupResultHandler(BluetoothProfileResultHandler* aRes)
UnregisterModuleResultHandler(BluetoothProfileResultHandler* aRes)
: mRes(aRes)
{ }
void OnError(BluetoothStatus aStatus) override
{
BT_WARNING("BluetoothAvrcpInterface::Cleanup failed: %d",
MOZ_ASSERT(NS_IsMainThread());
BT_WARNING("BluetoothSetupInterface::UnregisterModule failed for AVRCP: %d",
(int)aStatus);
sBtAvrcpInterface->SetNotificationHandler(nullptr);
sBtAvrcpInterface = nullptr;
if (mRes) {
if (aStatus == STATUS_UNSUPPORTED) {
/* Not all versions of Bluedroid support AVRCP. So if the
* cleanup fails with STATUS_UNSUPPORTED, we still signal
* success.
*/
mRes->Deinit();
} else {
mRes->OnError(NS_ERROR_FAILURE);
}
mRes->OnError(NS_ERROR_FAILURE);
}
}
void Cleanup() override
void UnregisterModule() override
{
MOZ_ASSERT(NS_IsMainThread());
sBtAvrcpInterface->SetNotificationHandler(nullptr);
sBtAvrcpInterface = nullptr;
if (mRes) {
mRes->Deinit();
}
@ -285,25 +338,33 @@ private:
nsRefPtr<BluetoothProfileResultHandler> mRes;
};
class BluetoothAvrcpManager::CleanupResultHandlerRunnable final
class BluetoothAvrcpManager::DeinitProfileResultHandlerRunnable final
: public nsRunnable
{
public:
CleanupResultHandlerRunnable(BluetoothProfileResultHandler* aRes)
DeinitProfileResultHandlerRunnable(BluetoothProfileResultHandler* aRes,
nsresult aRv)
: mRes(aRes)
, mRv(aRv)
{
MOZ_ASSERT(mRes);
}
NS_IMETHOD Run() override
{
mRes->Deinit();
MOZ_ASSERT(NS_IsMainThread());
if (NS_SUCCEEDED(mRv)) {
mRes->Deinit();
} else {
mRes->OnError(mRv);
}
return NS_OK;
}
private:
nsRefPtr<BluetoothProfileResultHandler> mRes;
nsresult mRv;
};
// static
@ -312,16 +373,45 @@ BluetoothAvrcpManager::DeinitAvrcpInterface(BluetoothProfileResultHandler* aRes)
{
MOZ_ASSERT(NS_IsMainThread());
if (sBtAvrcpInterface) {
sBtAvrcpInterface->Cleanup(new CleanupResultHandler(aRes));
} else if (aRes) {
// We dispatch a runnable here to make the profile resource handler
// behave as if AVRCP was initialized.
nsRefPtr<nsRunnable> r = new CleanupResultHandlerRunnable(aRes);
if (!sBtAvrcpInterface) {
BT_LOGR("Bluetooth AVRCP interface has not been initalized.");
nsRefPtr<nsRunnable> r =
new DeinitProfileResultHandlerRunnable(aRes, NS_OK);
if (NS_FAILED(NS_DispatchToMainThread(r))) {
BT_LOGR("Failed to dispatch cleanup-result-handler runnable");
BT_LOGR("Failed to dispatch AVRCP Deinit runnable");
}
return;
}
auto btInf = BluetoothInterface::GetInstance();
if (NS_WARN_IF(!btInf)) {
// If there's no backend interface, we dispatch a runnable
// that calls the profile result handler.
nsRefPtr<nsRunnable> r =
new DeinitProfileResultHandlerRunnable(aRes, NS_ERROR_FAILURE);
if (NS_FAILED(NS_DispatchToMainThread(r))) {
BT_LOGR("Failed to dispatch AVRCP OnError runnable");
}
return;
}
auto setupInterface = btInf->GetBluetoothSetupInterface();
if (NS_WARN_IF(!setupInterface)) {
// If there's no Setup interface, we dispatch a runnable
// that calls the profile result handler.
nsRefPtr<nsRunnable> r =
new DeinitProfileResultHandlerRunnable(aRes, NS_ERROR_FAILURE);
if (NS_FAILED(NS_DispatchToMainThread(r))) {
BT_LOGR("Failed to dispatch AVRCP OnError runnable");
}
return;
}
setupInterface->UnregisterModule(
SETUP_SERVICE_ID_AVRCP,
new UnregisterModuleResultHandler(aRes));
}
void

View File

@ -17,6 +17,8 @@ class BluetoothAvrcpManager : public BluetoothProfileManagerBase
, public BluetoothAvrcpNotificationHandler
{
public:
static const int MAX_NUM_CLIENTS;
BT_DECL_PROFILE_MGR_BASE
virtual void GetName(nsACString& aName)
{
@ -60,12 +62,12 @@ protected:
virtual ~BluetoothAvrcpManager();
private:
class CleanupResultHandler;
class CleanupResultHandlerRunnable;
class ConnectRunnable;
class DeinitProfileResultHandlerRunnable;
class DisconnectRunnable;
class InitResultHandler;
class OnErrorProfileResultHandlerRunnable;
class InitProfileResultHandlerRunnable;
class RegisterModuleResultHandler;
class UnregisterModuleResultHandler;
BluetoothAvrcpManager();

View File

@ -15,8 +15,6 @@ using namespace mozilla::ipc;
// A2DP module
//
const int BluetoothDaemonA2dpModule::MAX_NUM_CLIENTS = 1;
BluetoothA2dpNotificationHandler*
BluetoothDaemonA2dpModule::sNotificationHandler;
@ -235,109 +233,13 @@ BluetoothDaemonA2dpInterface::BluetoothDaemonA2dpInterface(
BluetoothDaemonA2dpInterface::~BluetoothDaemonA2dpInterface()
{ }
class BluetoothDaemonA2dpInterface::InitResultHandler final
: public BluetoothSetupResultHandler
{
public:
InitResultHandler(BluetoothA2dpResultHandler* aRes)
: mRes(aRes)
{
MOZ_ASSERT(mRes);
}
void OnError(BluetoothStatus aStatus) override
{
MOZ_ASSERT(NS_IsMainThread());
mRes->OnError(aStatus);
}
void RegisterModule() override
{
MOZ_ASSERT(NS_IsMainThread());
mRes->Init();
}
private:
nsRefPtr<BluetoothA2dpResultHandler> mRes;
};
void
BluetoothDaemonA2dpInterface::Init(
BluetoothA2dpNotificationHandler* aNotificationHandler,
BluetoothA2dpResultHandler* aRes)
BluetoothDaemonA2dpInterface::SetNotificationHandler(
BluetoothA2dpNotificationHandler* aNotificationHandler)
{
// Set notification handler _before_ registering the module. It could
// happen that we receive notifications, before the result handler runs.
MOZ_ASSERT(mModule);
mModule->SetNotificationHandler(aNotificationHandler);
InitResultHandler* res;
if (aRes) {
res = new InitResultHandler(aRes);
} else {
// We don't need a result handler if the caller is not interested.
res = nullptr;
}
nsresult rv = mModule->RegisterModule(BluetoothDaemonA2dpModule::SERVICE_ID,
0x00, BluetoothDaemonA2dpModule::MAX_NUM_CLIENTS, res);
if (NS_FAILED(rv) && aRes) {
DispatchError(aRes, rv);
}
}
class BluetoothDaemonA2dpInterface::CleanupResultHandler final
: public BluetoothSetupResultHandler
{
public:
CleanupResultHandler(BluetoothDaemonA2dpModule* aModule,
BluetoothA2dpResultHandler* aRes)
: mModule(aModule)
, mRes(aRes)
{
MOZ_ASSERT(mModule);
}
void OnError(BluetoothStatus aStatus) override
{
MOZ_ASSERT(NS_IsMainThread());
if (mRes) {
mRes->OnError(aStatus);
}
}
void UnregisterModule() override
{
MOZ_ASSERT(NS_IsMainThread());
// Clear notification handler _after_ module has been
// unregistered. While unregistering the module, we might
// still receive notifications.
mModule->SetNotificationHandler(nullptr);
if (mRes) {
mRes->Cleanup();
}
}
private:
BluetoothDaemonA2dpModule* mModule;
nsRefPtr<BluetoothA2dpResultHandler> mRes;
};
void
BluetoothDaemonA2dpInterface::Cleanup(
BluetoothA2dpResultHandler* aRes)
{
nsresult rv = mModule->UnregisterModule(
BluetoothDaemonA2dpModule::SERVICE_ID,
new CleanupResultHandler(mModule, aRes));
if (NS_FAILED(rv)) {
DispatchError(aRes, rv);
}
}
/* Connect / Disconnect */

View File

@ -30,18 +30,9 @@ public:
OPCODE_DISCONNECT = 0x02
};
static const int MAX_NUM_CLIENTS;
virtual nsresult Send(DaemonSocketPDU* aPDU,
DaemonSocketResultHandler* aRes) = 0;
virtual nsresult RegisterModule(uint8_t aId, uint8_t aMode,
uint32_t aMaxNumClients,
BluetoothSetupResultHandler* aRes) = 0;
virtual nsresult UnregisterModule(uint8_t aId,
BluetoothSetupResultHandler* aRes) = 0;
void SetNotificationHandler(
BluetoothA2dpNotificationHandler* aNotificationHandler);
@ -129,17 +120,12 @@ protected:
class BluetoothDaemonA2dpInterface final
: public BluetoothA2dpInterface
{
class CleanupResultHandler;
class InitResultHandler;
public:
BluetoothDaemonA2dpInterface(BluetoothDaemonA2dpModule* aModule);
~BluetoothDaemonA2dpInterface();
void Init(
BluetoothA2dpNotificationHandler* aNotificationHandler,
BluetoothA2dpResultHandler* aRes) override;
void Cleanup(BluetoothA2dpResultHandler* aRes) override;
void SetNotificationHandler(
BluetoothA2dpNotificationHandler* aNotificationHandler) override;
/* Connect / Disconnect */

View File

@ -15,8 +15,6 @@ using namespace mozilla::ipc;
// AVRCP module
//
const int BluetoothDaemonAvrcpModule::MAX_NUM_CLIENTS = 1;
BluetoothAvrcpNotificationHandler*
BluetoothDaemonAvrcpModule::sNotificationHandler;
@ -805,115 +803,13 @@ BluetoothDaemonAvrcpInterface::BluetoothDaemonAvrcpInterface(
BluetoothDaemonAvrcpInterface::~BluetoothDaemonAvrcpInterface()
{ }
class BluetoothDaemonAvrcpInterface::InitResultHandler final
: public BluetoothSetupResultHandler
{
public:
InitResultHandler(BluetoothAvrcpResultHandler* aRes)
: mRes(aRes)
{
MOZ_ASSERT(mRes);
}
void OnError(BluetoothStatus aStatus) override
{
MOZ_ASSERT(NS_IsMainThread());
mRes->OnError(aStatus);
}
void RegisterModule() override
{
MOZ_ASSERT(NS_IsMainThread());
mRes->Init();
}
private:
nsRefPtr<BluetoothAvrcpResultHandler> mRes;
};
void
BluetoothDaemonAvrcpInterface::Init(
BluetoothAvrcpNotificationHandler* aNotificationHandler,
BluetoothAvrcpResultHandler* aRes)
BluetoothDaemonAvrcpInterface::SetNotificationHandler(
BluetoothAvrcpNotificationHandler* aNotificationHandler)
{
MOZ_ASSERT(mModule);
// Set notification handler _before_ registering the module. It could
// happen that we receive notifications, before the result handler runs.
mModule->SetNotificationHandler(aNotificationHandler);
InitResultHandler* res;
if (aRes) {
res = new InitResultHandler(aRes);
} else {
// We don't need a result handler if the caller is not interested.
res = nullptr;
}
nsresult rv = mModule->RegisterModule(
BluetoothDaemonAvrcpModule::SERVICE_ID,
BluetoothDaemonAvrcpModule::MAX_NUM_CLIENTS, 0x00, res);
if (NS_FAILED(rv) && aRes) {
DispatchError(aRes, rv);
}
}
class BluetoothDaemonAvrcpInterface::CleanupResultHandler final
: public BluetoothSetupResultHandler
{
public:
CleanupResultHandler(BluetoothDaemonAvrcpModule* aModule,
BluetoothAvrcpResultHandler* aRes)
: mModule(aModule)
, mRes(aRes)
{
MOZ_ASSERT(mModule);
}
void OnError(BluetoothStatus aStatus) override
{
MOZ_ASSERT(NS_IsMainThread());
if (mRes) {
mRes->OnError(aStatus);
}
}
void UnregisterModule() override
{
MOZ_ASSERT(NS_IsMainThread());
// Clear notification handler _after_ module has been
// unregistered. While unregistering the module, we might
// still receive notifications.
mModule->SetNotificationHandler(nullptr);
if (mRes) {
mRes->Cleanup();
}
}
private:
BluetoothDaemonAvrcpModule* mModule;
nsRefPtr<BluetoothAvrcpResultHandler> mRes;
};
void
BluetoothDaemonAvrcpInterface::Cleanup(
BluetoothAvrcpResultHandler* aRes)
{
MOZ_ASSERT(mModule);
nsresult rv = mModule->UnregisterModule(
BluetoothDaemonAvrcpModule::SERVICE_ID,
new CleanupResultHandler(mModule, aRes));
if (NS_FAILED(rv)) {
DispatchError(aRes, rv);
}
}
void

View File

@ -62,18 +62,9 @@ public:
#endif
};
static const int MAX_NUM_CLIENTS;
virtual nsresult Send(DaemonSocketPDU* aPDU,
DaemonSocketResultHandler* aRes) = 0;
virtual nsresult RegisterModule(uint8_t aId, uint8_t aMode,
uint32_t aMaxNumClients,
BluetoothSetupResultHandler* aRes) = 0;
virtual nsresult UnregisterModule(uint8_t aId,
BluetoothSetupResultHandler* aRes) = 0;
void SetNotificationHandler(
BluetoothAvrcpNotificationHandler* aNotificationHandler);
@ -305,10 +296,8 @@ public:
BluetoothDaemonAvrcpInterface(BluetoothDaemonAvrcpModule* aModule);
~BluetoothDaemonAvrcpInterface();
void Init(BluetoothAvrcpNotificationHandler* aNotificationHandler,
BluetoothAvrcpResultHandler* aRes) override;
void Cleanup(BluetoothAvrcpResultHandler* aRes) override;
void SetNotificationHandler(
BluetoothAvrcpNotificationHandler* aNotificationHandler) override;
void GetPlayStatusRsp(ControlPlayStatus aPlayStatus,
uint32_t aSongLen, uint32_t aSongPos,

View File

@ -15,8 +15,6 @@ using namespace mozilla::ipc;
// GATT module
//
const int BluetoothDaemonGattModule::MAX_NUM_CLIENTS = 1;
BluetoothGattNotificationHandler*
BluetoothDaemonGattModule::sNotificationHandler;
@ -2010,6 +2008,7 @@ BluetoothDaemonGattInterface::BluetoothDaemonGattInterface(
BluetoothDaemonGattInterface::~BluetoothDaemonGattInterface()
{ }
#if 0
class BluetoothDaemonGattInterface::InitResultHandler final
: public BluetoothSetupResultHandler
{
@ -2057,8 +2056,8 @@ BluetoothDaemonGattInterface::Init(
}
nsresult rv = mModule->RegisterModule(
BluetoothDaemonGattModule::SERVICE_ID, 0x00,
BluetoothDaemonGattModule::MAX_NUM_CLIENTS, res);
SETUP_SERVICE_ID_GATT, 0x00, BluetoothDaemonGattModule::MAX_NUM_CLIENTS,
res);
if (NS_FAILED(rv) && aRes) {
DispatchError(aRes, rv);
@ -2110,12 +2109,21 @@ BluetoothDaemonGattInterface::Cleanup(
BluetoothGattResultHandler* aRes)
{
nsresult rv = mModule->UnregisterModule(
BluetoothDaemonGattModule::SERVICE_ID,
new CleanupResultHandler(mModule, aRes));
SETUP_SERVICE_ID_GATT, new CleanupResultHandler(mModule, aRes));
if (NS_FAILED(rv)) {
DispatchError(aRes, rv);
}
}
#endif
void
BluetoothDaemonGattInterface::SetNotificationHandler(
BluetoothGattNotificationHandler* aNotificationHandler)
{
MOZ_ASSERT(mModule);
mModule->SetNotificationHandler(aNotificationHandler);
}
/* Register / Unregister */
void

View File

@ -64,18 +64,9 @@ public:
// TODO: Add L support
};
static const int MAX_NUM_CLIENTS;
virtual nsresult Send(DaemonSocketPDU* aPDU,
DaemonSocketResultHandler* aRes) = 0;
virtual nsresult RegisterModule(uint8_t aId, uint8_t aMode,
uint32_t aMaxNumClients,
BluetoothSetupResultHandler* aRes) = 0;
virtual nsresult UnregisterModule(uint8_t aId,
BluetoothSetupResultHandler* aRes) = 0;
void SetNotificationHandler(
BluetoothGattNotificationHandler* aNotificationHandler);
@ -798,9 +789,8 @@ public:
BluetoothDaemonGattInterface(BluetoothDaemonGattModule* aModule);
~BluetoothDaemonGattInterface();
void Init(BluetoothGattNotificationHandler* aNotificationHandler,
BluetoothGattResultHandler* aRes) override;
void Cleanup(BluetoothGattResultHandler* aRes) override;
void SetNotificationHandler(
BluetoothGattNotificationHandler* aNotificationHandler) override;
/* Register / Unregister */
void RegisterClient(const BluetoothUuid& aUuid,

View File

@ -1409,115 +1409,12 @@ BluetoothDaemonHandsfreeInterface::BluetoothDaemonHandsfreeInterface(
BluetoothDaemonHandsfreeInterface::~BluetoothDaemonHandsfreeInterface()
{ }
class BluetoothDaemonHandsfreeInterface::InitResultHandler final
: public BluetoothSetupResultHandler
void BluetoothDaemonHandsfreeInterface::SetNotificationHandler(
BluetoothHandsfreeNotificationHandler* aNotificationHandler)
{
public:
InitResultHandler(BluetoothHandsfreeResultHandler* aRes)
: mRes(aRes)
{
MOZ_ASSERT(mRes);
}
MOZ_ASSERT(mModule);
void OnError(BluetoothStatus aStatus) override
{
MOZ_ASSERT(NS_IsMainThread());
mRes->OnError(aStatus);
}
void RegisterModule() override
{
MOZ_ASSERT(NS_IsMainThread());
mRes->Init();
}
private:
nsRefPtr<BluetoothHandsfreeResultHandler> mRes;
};
void
BluetoothDaemonHandsfreeInterface::Init(
BluetoothHandsfreeNotificationHandler* aNotificationHandler,
int aMaxNumClients, BluetoothHandsfreeResultHandler* aRes)
{
// Set notification handler _before_ registering the module. It could
// happen that we receive notifications, before the result handler runs.
mModule->SetNotificationHandler(aNotificationHandler);
InitResultHandler* res;
if (aRes) {
res = new InitResultHandler(aRes);
} else {
// We don't need a result handler if the caller is not interested.
res = nullptr;
}
nsresult rv = mModule->RegisterModule(
BluetoothDaemonHandsfreeModule::SERVICE_ID, MODE_NARROWBAND_SPEECH,
aMaxNumClients, res);
if (NS_FAILED(rv) && aRes) {
DispatchError(aRes, rv);
}
}
class BluetoothDaemonHandsfreeInterface::CleanupResultHandler final
: public BluetoothSetupResultHandler
{
public:
CleanupResultHandler(BluetoothDaemonHandsfreeModule* aModule,
BluetoothHandsfreeResultHandler* aRes)
: mModule(aModule)
, mRes(aRes)
{
MOZ_ASSERT(mModule);
}
void OnError(BluetoothStatus aStatus) override
{
MOZ_ASSERT(NS_IsMainThread());
BT_LOGR("%s:%d", __func__, __LINE__);
if (mRes) {
mRes->OnError(aStatus);
}
}
void UnregisterModule() override
{
MOZ_ASSERT(NS_IsMainThread());
BT_LOGR("%s:%d", __func__, __LINE__);
// Clear notification handler _after_ module has been
// unregistered. While unregistering the module, we might
// still receive notifications.
mModule->SetNotificationHandler(nullptr);
if (mRes) {
mRes->Cleanup();
}
}
private:
BluetoothDaemonHandsfreeModule* mModule;
nsRefPtr<BluetoothHandsfreeResultHandler> mRes;
};
void
BluetoothDaemonHandsfreeInterface::Cleanup(
BluetoothHandsfreeResultHandler* aRes)
{
BT_LOGR("%s:%d", __func__, __LINE__);
nsresult rv = mModule->UnregisterModule(
BluetoothDaemonHandsfreeModule::SERVICE_ID,
new CleanupResultHandler(mModule, aRes));
BT_LOGR("%s:%d", __func__, __LINE__);
if (NS_FAILED(rv)) {
DispatchError(aRes, rv);
}
}
/* Connect / Disconnect */

View File

@ -46,13 +46,6 @@ public:
virtual nsresult Send(DaemonSocketPDU* aPDU,
DaemonSocketResultHandler* aRes) = 0;
virtual nsresult RegisterModule(uint8_t aId, uint8_t aMode,
uint32_t aMaxNumClients,
BluetoothSetupResultHandler* aRes) = 0;
virtual nsresult UnregisterModule(uint8_t aId,
BluetoothSetupResultHandler* aRes) = 0;
void SetNotificationHandler(
BluetoothHandsfreeNotificationHandler* aNotificationHandler);
@ -400,23 +393,12 @@ protected:
class BluetoothDaemonHandsfreeInterface final
: public BluetoothHandsfreeInterface
{
class CleanupResultHandler;
class InitResultHandler;
enum {
MODE_HEADSET = 0x00,
MODE_NARROWBAND_SPEECH = 0x01,
MODE_NARRAWBAND_WIDEBAND_SPEECH = 0x02
};
public:
BluetoothDaemonHandsfreeInterface(BluetoothDaemonHandsfreeModule* aModule);
~BluetoothDaemonHandsfreeInterface();
void Init(
BluetoothHandsfreeNotificationHandler* aNotificationHandler,
int aMaxNumClients, BluetoothHandsfreeResultHandler* aRes) override;
void Cleanup(BluetoothHandsfreeResultHandler* aRes) override;
void SetNotificationHandler(
BluetoothHandsfreeNotificationHandler* aNotificationHandler) override;
/* Connect / Disconnect */

View File

@ -841,6 +841,35 @@ Convert(BluetoothScanMode aIn, int32_t& aOut)
return NS_OK;
}
nsresult
Convert(BluetoothSetupServiceId aIn, uint8_t& aOut)
{
static const uint8_t sServiceId[] = {
[SETUP_SERVICE_ID_SETUP] = 0x00,
[SETUP_SERVICE_ID_CORE] = 0x01,
[SETUP_SERVICE_ID_SOCKET] = 0x02,
[SETUP_SERVICE_ID_HID] = 0x03,
[SETUP_SERVICE_ID_PAN] = 0x04,
[SETUP_SERVICE_ID_HANDSFREE] = 0x05,
[SETUP_SERVICE_ID_A2DP] = 0x06,
[SETUP_SERVICE_ID_HEALTH] = 0x07,
[SETUP_SERVICE_ID_AVRCP] = 0x08,
[SETUP_SERVICE_ID_GATT] = 0x09,
[SETUP_SERVICE_ID_HANDSFREE_CLIENT] = 0x0a,
[SETUP_SERVICE_ID_MAP_CLIENT] = 0x0b,
[SETUP_SERVICE_ID_AVRCP_CONTROLLER] = 0x0c,
[SETUP_SERVICE_ID_A2DP_SINK] = 0x0d
};
if (MOZ_HAL_IPC_CONVERT_WARN_IF(
aIn >= MOZ_ARRAY_LENGTH(sServiceId),
BluetoothServiceSetupId, uint8_t)) {
aOut = 0; // silences compiler warning
return NS_ERROR_ILLEGAL_VALUE;
}
aOut = sServiceId[aIn];
return NS_OK;
}
nsresult
Convert(BluetoothSspVariant aIn, uint8_t& aOut)
{
@ -1267,6 +1296,12 @@ PackPDU(BluetoothScanMode aIn, DaemonSocketPDU& aPDU)
return PackPDU(PackConversion<BluetoothScanMode, int32_t>(aIn), aPDU);
}
nsresult
PackPDU(BluetoothSetupServiceId aIn, DaemonSocketPDU& aPDU)
{
return PackPDU(PackConversion<BluetoothSetupServiceId, uint8_t>(aIn), aPDU);
}
nsresult
PackPDU(const BluetoothServiceName& aIn, DaemonSocketPDU& aPDU)
{

View File

@ -97,12 +97,6 @@ struct BluetoothAvrcpEventParamPair {
const BluetoothAvrcpNotificationParam& mParam;
};
struct BluetoothConfigurationParameter {
uint8_t mType;
uint16_t mLength;
nsAutoArrayPtr<uint8_t> mValue;
};
//
// Conversion
//
@ -236,6 +230,9 @@ Convert(BluetoothPropertyType aIn, uint8_t& aOut);
nsresult
Convert(BluetoothScanMode aIn, uint8_t& aOut);
nsresult
Convert(BluetoothSetupServiceId aIn, uint8_t& aOut);
nsresult
Convert(BluetoothSocketType aIn, uint8_t& aOut);
@ -335,6 +332,9 @@ PackPDU(BluetoothPropertyType aIn, DaemonSocketPDU& aPDU);
nsresult
PackPDU(const BluetoothServiceName& aIn, DaemonSocketPDU& aPDU);
nsresult
PackPDU(BluetoothSetupServiceId aIn, DaemonSocketPDU& aPDU);
nsresult
PackPDU(BluetoothSocketType aIn, DaemonSocketPDU& aPDU);

View File

@ -39,24 +39,14 @@ static const int sRetryInterval = 100; // ms
//
// Each |BluetoothDaemon*Module| class implements an individual
// module of the HAL protocol. Each class contains the abstract
// methods
// method
//
// - |Send|,
// - |RegisterModule|, and
// - |UnregisterModule|.
// - |Send|.
//
// Module classes use |Send| to send out command PDUs. The socket
// in |BluetoothDaemonProtocol| is required for sending. The abstract
// method hides all these internal details from the modules.
//
// |RegisterModule| is required during module initialization, when
// modules must register themselves at the daemon. The register command
// is not part of the module itself, but contained in the Setup module
// (id of 0x00). The abstract method |RegisterModule| allows modules to
// call into the Setup module for generating the register command.
//
// |UnregisterModule| works like |RegisterModule|, but for cleanups.
//
// |BluetoothDaemonProtocol| also handles PDU receiving. It implements
// the method |Handle| from |DaemonSocketIOConsumer|. The socket
// connections of type |DaemonSocket| invoke this method
@ -66,11 +56,9 @@ static const int sRetryInterval = 100; // ms
// |HandleSvc|. Further PDU processing is module-dependent.
//
// To summarize the interface between |BluetoothDaemonProtocol| and
// modules; the former implements the abstract methods
// modules; the former implements the abstract method
//
// - |Send|,
// - |RegisterModule|, and
// - |UnregisterModule|,
//
// which allow modules to send out data. Each module implements the
// method
@ -95,12 +83,6 @@ public:
void SetConnection(DaemonSocket* aConnection);
nsresult RegisterModule(uint8_t aId, uint8_t aMode, uint32_t aMaxNumClients,
BluetoothSetupResultHandler* aRes) override;
nsresult UnregisterModule(uint8_t aId,
BluetoothSetupResultHandler* aRes) override;
// Outgoing PDUs
//
@ -153,22 +135,6 @@ BluetoothDaemonProtocol::SetConnection(DaemonSocket* aConnection)
mConnection = aConnection;
}
nsresult
BluetoothDaemonProtocol::RegisterModule(uint8_t aId, uint8_t aMode,
uint32_t aMaxNumClients,
BluetoothSetupResultHandler* aRes)
{
return BluetoothDaemonSetupModule::RegisterModuleCmd(aId, aMode,
aMaxNumClients, aRes);
}
nsresult
BluetoothDaemonProtocol::UnregisterModule(uint8_t aId,
BluetoothSetupResultHandler* aRes)
{
return BluetoothDaemonSetupModule::UnregisterModuleCmd(aId, aRes);
}
nsresult
BluetoothDaemonProtocol::Send(DaemonSocketPDU* aPDU,
DaemonSocketResultHandler* aRes)
@ -422,7 +388,9 @@ public:
if (!mRegisteredSocketModule) {
mRegisteredSocketModule = true;
// Init, step 5: Register Socket module
mInterface->mProtocol->RegisterModuleCmd(0x02, 0x00,
mInterface->mProtocol->RegisterModuleCmd(
SETUP_SERVICE_ID_SOCKET,
0x00,
BluetoothDaemonSocketModule::MAX_NUM_CLIENTS, this);
} else if (mRes) {
// Init, step 6: Signal success to caller
@ -556,7 +524,7 @@ private:
if (!mUnregisteredCoreModule) {
mUnregisteredCoreModule = true;
// Cleanup, step 2: Unregister Core module
mInterface->mProtocol->UnregisterModuleCmd(0x01, this);
mInterface->mProtocol->UnregisterModuleCmd(SETUP_SERVICE_ID_CORE, this);
} else {
// Cleanup, step 3: Close command channel
mInterface->mCmdChannel->Close();
@ -600,7 +568,7 @@ BluetoothDaemonInterface::Cleanup(BluetoothResultHandler* aRes)
// Cleanup, step 1: Unregister Socket module
nsresult rv = mProtocol->UnregisterModuleCmd(
0x02, new CleanupResultHandler(this));
SETUP_SERVICE_ID_SOCKET, new CleanupResultHandler(this));
if (NS_FAILED(rv)) {
DispatchError(aRes, rv);
return;
@ -889,9 +857,21 @@ BluetoothDaemonInterface::DispatchError(BluetoothResultHandler* aRes,
DispatchError(aRes, status);
}
// Profile Interfaces
// Service Interfaces
//
BluetoothSetupInterface*
BluetoothDaemonInterface::GetBluetoothSetupInterface()
{
if (mSetupInterface) {
return mSetupInterface;
}
mSetupInterface = new BluetoothDaemonSetupInterface(mProtocol);
return mSetupInterface;
}
BluetoothSocketInterface*
BluetoothDaemonInterface::GetBluetoothSocketInterface()
{
@ -1002,7 +982,9 @@ BluetoothDaemonInterface::OnConnectSuccess(int aIndex)
// Init, step 4: Register Core module
nsresult rv = mProtocol->RegisterModuleCmd(
0x01, 0x00, BluetoothDaemonCoreModule::MAX_NUM_CLIENTS,
SETUP_SERVICE_ID_CORE,
0x00,
BluetoothDaemonCoreModule::MAX_NUM_CLIENTS,
new InitResultHandler(this, res));
if (NS_FAILED(rv) && res) {
DispatchError(res, STATUS_FAIL);

View File

@ -27,6 +27,7 @@ class BluetoothDaemonAvrcpInterface;
class BluetoothDaemonGattInterface;
class BluetoothDaemonHandsfreeInterface;
class BluetoothDaemonProtocol;
class BluetoothDaemonSetupInterface;
class BluetoothDaemonSocketInterface;
class BluetoothDaemonInterface final
@ -125,8 +126,9 @@ public:
void ReadEnergyInfo(BluetoothResultHandler* aRes) override;
/* Profile Interfaces */
/* Service Interfaces */
BluetoothSetupInterface* GetBluetoothSetupInterface() override;
BluetoothSocketInterface* GetBluetoothSocketInterface() override;
BluetoothHandsfreeInterface* GetBluetoothHandsfreeInterface() override;
BluetoothA2dpInterface* GetBluetoothA2dpInterface() override;
@ -162,6 +164,7 @@ private:
nsTArray<nsRefPtr<BluetoothResultHandler> > mResultHandlerQ;
nsAutoPtr<BluetoothDaemonSetupInterface> mSetupInterface;
nsAutoPtr<BluetoothDaemonSocketInterface> mSocketInterface;
nsAutoPtr<BluetoothDaemonHandsfreeInterface> mHandsfreeInterface;
nsAutoPtr<BluetoothDaemonA2dpInterface> mA2dpInterface;

View File

@ -9,6 +9,8 @@
BEGIN_BLUETOOTH_NAMESPACE
using namespace mozilla::ipc;
//
// Setup module
//
@ -54,7 +56,7 @@ BluetoothDaemonSetupModule::HandleSvc(const DaemonSocketPDUHeader& aHeader,
nsresult
BluetoothDaemonSetupModule::RegisterModuleCmd(
uint8_t aId, uint8_t aMode, uint32_t aMaxNumClients,
BluetoothSetupServiceId aId, uint8_t aMode, uint32_t aMaxNumClients,
BluetoothSetupResultHandler* aRes)
{
MOZ_ASSERT(NS_IsMainThread());
@ -81,7 +83,7 @@ BluetoothDaemonSetupModule::RegisterModuleCmd(
nsresult
BluetoothDaemonSetupModule::UnregisterModuleCmd(
uint8_t aId, BluetoothSetupResultHandler* aRes)
BluetoothSetupServiceId aId, BluetoothSetupResultHandler* aRes)
{
MOZ_ASSERT(NS_IsMainThread());
@ -167,4 +169,77 @@ BluetoothDaemonSetupModule::ConfigurationRsp(
UnpackPDUInitOp(aPDU));
}
//
// Setup interface
//
BluetoothDaemonSetupInterface::BluetoothDaemonSetupInterface(
BluetoothDaemonSetupModule* aModule)
: mModule(aModule)
{ }
BluetoothDaemonSetupInterface::~BluetoothDaemonSetupInterface()
{ }
void
BluetoothDaemonSetupInterface::RegisterModule(
BluetoothSetupServiceId aId, uint8_t aMode, uint32_t aMaxNumClients,
BluetoothSetupResultHandler* aRes)
{
MOZ_ASSERT(mModule);
nsresult rv = mModule->RegisterModuleCmd(aId, aMode, aMaxNumClients, aRes);
if (NS_FAILED(rv)) {
DispatchError(aRes, rv);
}
}
void
BluetoothDaemonSetupInterface::UnregisterModule(
BluetoothSetupServiceId aId,
BluetoothSetupResultHandler* aRes)
{
MOZ_ASSERT(mModule);
nsresult rv = mModule->UnregisterModuleCmd(aId, aRes);
if (NS_FAILED(rv)) {
DispatchError(aRes, rv);
}
}
void
BluetoothDaemonSetupInterface::Configuration(
const BluetoothConfigurationParameter* aParam, uint8_t aLen,
BluetoothSetupResultHandler* aRes)
{
MOZ_ASSERT(mModule);
nsresult rv = mModule->ConfigurationCmd(aParam, aLen, aRes);
if (NS_FAILED(rv)) {
DispatchError(aRes, rv);
}
}
void
BluetoothDaemonSetupInterface::DispatchError(
BluetoothSetupResultHandler* aRes, BluetoothStatus aStatus)
{
DaemonResultRunnable1<BluetoothSetupResultHandler, void,
BluetoothStatus, BluetoothStatus>::Dispatch(
aRes, &BluetoothSetupResultHandler::OnError,
ConstantInitOp1<BluetoothStatus>(aStatus));
}
void
BluetoothDaemonSetupInterface::DispatchError(
BluetoothSetupResultHandler* aRes, nsresult aRv)
{
BluetoothStatus status;
if (NS_WARN_IF(NS_FAILED(Convert(aRv, status)))) {
status = STATUS_FAIL;
}
DispatchError(aRes, status);
}
END_BLUETOOTH_NAMESPACE

View File

@ -37,11 +37,11 @@ public:
// Commands
//
nsresult RegisterModuleCmd(uint8_t aId, uint8_t aMode,
nsresult RegisterModuleCmd(BluetoothSetupServiceId aId, uint8_t aMode,
uint32_t aMaxNumClients,
BluetoothSetupResultHandler* aRes);
nsresult UnregisterModuleCmd(uint8_t aId,
nsresult UnregisterModuleCmd(BluetoothSetupServiceId aId,
BluetoothSetupResultHandler* aRes);
nsresult ConfigurationCmd(const BluetoothConfigurationParameter* aParam,
@ -86,6 +86,32 @@ private:
BluetoothSetupResultHandler* aRes);
};
class BluetoothDaemonSetupInterface final
: public BluetoothSetupInterface
{
public:
BluetoothDaemonSetupInterface(BluetoothDaemonSetupModule* aModule);
~BluetoothDaemonSetupInterface();
void RegisterModule(BluetoothSetupServiceId aId, uint8_t aMode,
uint32_t aMaxNumClients,
BluetoothSetupResultHandler* aRes) override;
void UnregisterModule(BluetoothSetupServiceId aId,
BluetoothSetupResultHandler* aRes) override;
void Configuration(const BluetoothConfigurationParameter* aParam,
uint8_t aLen,
BluetoothSetupResultHandler* aRes) override;
private:
void DispatchError(BluetoothSetupResultHandler* aRes,
BluetoothStatus aStatus);
void DispatchError(BluetoothSetupResultHandler* aRes, nsresult aRv);
BluetoothDaemonSetupModule* mModule;
};
END_BLUETOOTH_NAMESPACE
#endif // mozilla_dom_bluetooth_bluedroid_BluetoothDaemonSetupInterface_h

View File

@ -37,6 +37,8 @@ namespace {
static BluetoothGattInterface* sBluetoothGattInterface;
} // namespace
const int BluetoothGattManager::MAX_NUM_CLIENTS = 1;
bool BluetoothGattManager::mInShutdown = false;
static StaticAutoPtr<nsTArray<nsRefPtr<BluetoothGattClient> > > sClients;
@ -376,52 +378,126 @@ BluetoothGattManager::Get()
return sBluetoothGattManager;
}
class BluetoothGattManager::InitGattResultHandler final
: public BluetoothGattResultHandler
class BluetoothGattManager::RegisterModuleResultHandler final
: public BluetoothSetupResultHandler
{
public:
InitGattResultHandler(BluetoothProfileResultHandler* aRes)
: mRes(aRes)
RegisterModuleResultHandler(BluetoothGattInterface* aInterface,
BluetoothProfileResultHandler* aRes)
: mInterface(aInterface)
, mRes(aRes)
{ }
void OnError(BluetoothStatus aStatus) override
{
BT_WARNING("BluetoothGattInterface::Init failed: %d",
MOZ_ASSERT(NS_IsMainThread());
BT_WARNING("BluetoothSetupInterface::RegisterModule failed for GATT: %d",
(int)aStatus);
mInterface->SetNotificationHandler(nullptr);
if (mRes) {
mRes->OnError(NS_ERROR_FAILURE);
}
}
void Init() override
void RegisterModule() override
{
MOZ_ASSERT(NS_IsMainThread());
sBluetoothGattInterface = mInterface;
if (mRes) {
mRes->Init();
}
}
private:
BluetoothGattInterface* mInterface;
nsRefPtr<BluetoothProfileResultHandler> mRes;
};
class BluetoothGattManager::InitProfileResultHandlerRunnable final
: public nsRunnable
{
public:
InitProfileResultHandlerRunnable(BluetoothProfileResultHandler* aRes,
nsresult aRv)
: mRes(aRes)
, mRv(aRv)
{
MOZ_ASSERT(mRes);
}
NS_IMETHOD Run() override
{
MOZ_ASSERT(NS_IsMainThread());
if (NS_SUCCEEDED(mRv)) {
mRes->Init();
} else {
mRes->OnError(mRv);
}
return NS_OK;
}
private:
nsRefPtr<BluetoothProfileResultHandler> mRes;
nsresult mRv;
};
// static
void
BluetoothGattManager::InitGattInterface(BluetoothProfileResultHandler* aRes)
{
BluetoothInterface* btInf = BluetoothInterface::GetInstance();
if (!btInf) {
BT_LOGR("Error: Bluetooth interface not available");
if (aRes) {
aRes->OnError(NS_ERROR_FAILURE);
MOZ_ASSERT(NS_IsMainThread());
if (sBluetoothGattInterface) {
BT_LOGR("Bluetooth GATT interface is already initalized.");
nsRefPtr<nsRunnable> r =
new InitProfileResultHandlerRunnable(aRes, NS_OK);
if (NS_FAILED(NS_DispatchToMainThread(r))) {
BT_LOGR("Failed to dispatch GATT Init runnable");
}
return;
}
sBluetoothGattInterface = btInf->GetBluetoothGattInterface();
if (!sBluetoothGattInterface) {
BT_LOGR("Error: Bluetooth GATT interface not available");
if (aRes) {
aRes->OnError(NS_ERROR_FAILURE);
auto btInf = BluetoothInterface::GetInstance();
if (NS_WARN_IF(!btInf)) {
// If there's no Bluetooth interface, we dispatch a runnable
// that calls the profile result handler.
nsRefPtr<nsRunnable> r =
new InitProfileResultHandlerRunnable(aRes, NS_ERROR_FAILURE);
if (NS_FAILED(NS_DispatchToMainThread(r))) {
BT_LOGR("Failed to dispatch GATT OnError runnable");
}
return;
}
auto setupInterface = btInf->GetBluetoothSetupInterface();
if (NS_WARN_IF(!setupInterface)) {
// If there's no Setup interface, we dispatch a runnable
// that calls the profile result handler.
nsRefPtr<nsRunnable> r =
new InitProfileResultHandlerRunnable(aRes, NS_ERROR_FAILURE);
if (NS_FAILED(NS_DispatchToMainThread(r))) {
BT_LOGR("Failed to dispatch GATT OnError runnable");
}
return;
}
auto gattInterface = btInf->GetBluetoothGattInterface();
if (NS_WARN_IF(!gattInterface)) {
// If there's no GATT interface, we dispatch a runnable
// that calls the profile result handler.
nsRefPtr<nsRunnable> r =
new InitProfileResultHandlerRunnable(aRes, NS_ERROR_FAILURE);
if (NS_FAILED(NS_DispatchToMainThread(r))) {
BT_LOGR("Failed to dispatch GATT OnError runnable");
}
return;
}
@ -434,30 +510,43 @@ BluetoothGattManager::InitGattInterface(BluetoothProfileResultHandler* aRes)
sServers = new nsTArray<nsRefPtr<BluetoothGattServer> >;
}
BluetoothGattManager* gattManager = BluetoothGattManager::Get();
sBluetoothGattInterface->Init(gattManager,
new InitGattResultHandler(aRes));
// Set notification handler _before_ registering the module. It could
// happen that we receive notifications, before the result handler runs.
gattInterface->SetNotificationHandler(BluetoothGattManager::Get());
setupInterface->RegisterModule(
SETUP_SERVICE_ID_GATT, 0, MAX_NUM_CLIENTS,
new RegisterModuleResultHandler(gattInterface, aRes));
}
class BluetoothGattManager::CleanupResultHandler final
: public BluetoothGattResultHandler
class BluetoothGattManager::UnregisterModuleResultHandler final
: public BluetoothSetupResultHandler
{
public:
CleanupResultHandler(BluetoothProfileResultHandler* aRes)
: mRes(aRes)
UnregisterModuleResultHandler(BluetoothProfileResultHandler* aRes)
: mRes(aRes)
{ }
void OnError(BluetoothStatus aStatus) override
{
BT_WARNING("BluetoothGattInterface::Cleanup failed: %d",
MOZ_ASSERT(NS_IsMainThread());
BT_WARNING("BluetoothSetupInterface::UnregisterModule failed for GATT: %d",
(int)aStatus);
sBluetoothGattInterface->SetNotificationHandler(nullptr);
sBluetoothGattInterface = nullptr;
if (mRes) {
mRes->OnError(NS_ERROR_FAILURE);
}
}
void Cleanup() override
void UnregisterModule() override
{
MOZ_ASSERT(NS_IsMainThread());
sBluetoothGattInterface->SetNotificationHandler(nullptr);
sBluetoothGattInterface = nullptr;
sClients = nullptr;
sServers = nullptr;
@ -471,24 +560,33 @@ private:
nsRefPtr<BluetoothProfileResultHandler> mRes;
};
class BluetoothGattManager::CleanupResultHandlerRunnable final
class BluetoothGattManager::DeinitProfileResultHandlerRunnable final
: public nsRunnable
{
public:
CleanupResultHandlerRunnable(BluetoothProfileResultHandler* aRes)
: mRes(aRes)
DeinitProfileResultHandlerRunnable(BluetoothProfileResultHandler* aRes,
nsresult aRv)
: mRes(aRes)
, mRv(aRv)
{
MOZ_ASSERT(mRes);
}
NS_IMETHOD Run() override
{
mRes->Deinit();
MOZ_ASSERT(NS_IsMainThread());
if (NS_SUCCEEDED(mRv)) {
mRes->Deinit();
} else {
mRes->OnError(mRv);
}
return NS_OK;
}
private:
nsRefPtr<BluetoothProfileResultHandler> mRes;
nsresult mRv;
};
// static
@ -497,16 +595,45 @@ BluetoothGattManager::DeinitGattInterface(BluetoothProfileResultHandler* aRes)
{
MOZ_ASSERT(NS_IsMainThread());
if (sBluetoothGattInterface) {
sBluetoothGattInterface->Cleanup(new CleanupResultHandler(aRes));
} else if (aRes) {
// We dispatch a runnable here to make the profile resource handler
// behave as if GATT was initialized.
nsRefPtr<nsRunnable> r = new CleanupResultHandlerRunnable(aRes);
if (!sBluetoothGattInterface) {
BT_LOGR("Bluetooth GATT interface has not been initalized.");
nsRefPtr<nsRunnable> r =
new DeinitProfileResultHandlerRunnable(aRes, NS_OK);
if (NS_FAILED(NS_DispatchToMainThread(r))) {
BT_LOGR("Failed to dispatch cleanup-result-handler runnable");
BT_LOGR("Failed to dispatch GATT Deinit runnable");
}
return;
}
auto btInf = BluetoothInterface::GetInstance();
if (NS_WARN_IF(!btInf)) {
// If there's no backend interface, we dispatch a runnable
// that calls the profile result handler.
nsRefPtr<nsRunnable> r =
new DeinitProfileResultHandlerRunnable(aRes, NS_ERROR_FAILURE);
if (NS_FAILED(NS_DispatchToMainThread(r))) {
BT_LOGR("Failed to dispatch GATT OnError runnable");
}
return;
}
auto setupInterface = btInf->GetBluetoothSetupInterface();
if (NS_WARN_IF(!setupInterface)) {
// If there's no Setup interface, we dispatch a runnable
// that calls the profile result handler.
nsRefPtr<nsRunnable> r =
new DeinitProfileResultHandlerRunnable(aRes, NS_ERROR_FAILURE);
if (NS_FAILED(NS_DispatchToMainThread(r))) {
BT_LOGR("Failed to dispatch GATT OnError runnable");
}
return;
}
setupInterface->UnregisterModule(
SETUP_SERVICE_ID_GATT,
new UnregisterModuleResultHandler(aRes));
}
class BluetoothGattManager::RegisterClientResultHandler final

View File

@ -20,6 +20,8 @@ class BluetoothGattManager final : public nsIObserver
, public BluetoothGattNotificationHandler
{
public:
static const int MAX_NUM_CLIENTS;
NS_DECL_ISUPPORTS
NS_DECL_NSIOBSERVER
@ -165,9 +167,16 @@ public:
private:
~BluetoothGattManager();
#if 0
class CleanupResultHandler;
class CleanupResultHandlerRunnable;
class InitGattResultHandler;
#endif
class DeinitProfileResultHandlerRunnable;
class InitProfileResultHandlerRunnable;
class RegisterModuleResultHandler;
class UnregisterModuleResultHandler;
class RegisterClientResultHandler;
class UnregisterClientResultHandler;
class StartLeScanResultHandler;

View File

@ -276,12 +276,12 @@ BluetoothHfpManager::Init()
return true;
}
class BluetoothHfpManager::CleanupInitResultHandler final
: public BluetoothHandsfreeResultHandler
class BluetoothHfpManager::RegisterModuleResultHandler final
: public BluetoothSetupResultHandler
{
public:
CleanupInitResultHandler(BluetoothHandsfreeInterface* aInterface,
BluetoothProfileResultHandler* aRes)
RegisterModuleResultHandler(BluetoothHandsfreeInterface* aInterface,
BluetoothProfileResultHandler* aRes)
: mInterface(aInterface)
, mRes(aRes)
{
@ -290,68 +290,40 @@ public:
void OnError(BluetoothStatus aStatus) override
{
BT_WARNING("BluetoothHandsfreeInterface::Init failed: %d", (int)aStatus);
MOZ_ASSERT(NS_IsMainThread());
BT_WARNING("BluetoothSetupInterface::RegisterModule failed for HFP: %d",
(int)aStatus);
mInterface->SetNotificationHandler(nullptr);
if (mRes) {
mRes->OnError(NS_ERROR_FAILURE);
}
}
void Init() override
void RegisterModule() override
{
MOZ_ASSERT(NS_IsMainThread());
sBluetoothHfpInterface = mInterface;
if (mRes) {
mRes->Init();
}
}
void Cleanup() override
{
sBluetoothHfpInterface = nullptr;
/* During re-initialization, a previouly initialized
* |BluetoothHandsfreeInterface| has now been cleaned
* up, so we start initialization.
*/
RunInit();
}
void RunInit()
{
BluetoothHfpManager* hfpManager = BluetoothHfpManager::Get();
mInterface->Init(hfpManager, BluetoothHfpManager::MAX_NUM_CLIENTS, this);
}
private:
BluetoothHandsfreeInterface* mInterface;
nsRefPtr<BluetoothProfileResultHandler> mRes;
};
class BluetoothHfpManager::InitResultHandlerRunnable final
class BluetoothHfpManager::InitProfileResultHandlerRunnable final
: public nsRunnable
{
public:
InitResultHandlerRunnable(CleanupInitResultHandler* aRes)
: mRes(aRes)
{
MOZ_ASSERT(mRes);
}
NS_IMETHOD Run() override
{
mRes->RunInit();
return NS_OK;
}
private:
nsRefPtr<CleanupInitResultHandler> mRes;
};
class BluetoothHfpManager::OnErrorProfileResultHandlerRunnable final
: public nsRunnable
{
public:
OnErrorProfileResultHandlerRunnable(BluetoothProfileResultHandler* aRes,
nsresult aRv)
InitProfileResultHandlerRunnable(BluetoothProfileResultHandler* aRes,
nsresult aRv)
: mRes(aRes)
, mRv(aRv)
{
@ -360,7 +332,13 @@ public:
NS_IMETHOD Run() override
{
mRes->OnError(mRv);
MOZ_ASSERT(NS_IsMainThread());
if (NS_SUCCEEDED(mRv)) {
mRes->Init();
} else {
mRes->OnError(mRv);
}
return NS_OK;
}
@ -373,45 +351,64 @@ private:
void
BluetoothHfpManager::InitHfpInterface(BluetoothProfileResultHandler* aRes)
{
BluetoothInterface* btInf = BluetoothInterface::GetInstance();
MOZ_ASSERT(NS_IsMainThread());
if (sBluetoothHfpInterface) {
BT_LOGR("Bluetooth Handsfree interface is already initalized.");
nsRefPtr<nsRunnable> r =
new InitProfileResultHandlerRunnable(aRes, NS_OK);
if (NS_FAILED(NS_DispatchToMainThread(r))) {
BT_LOGR("Failed to dispatch HFP Init runnable");
}
return;
}
auto btInf = BluetoothInterface::GetInstance();
if (NS_WARN_IF(!btInf)) {
// If there's no backend interface, we dispatch a runnable
// that calls the profile result handler.
nsRefPtr<nsRunnable> r =
new OnErrorProfileResultHandlerRunnable(aRes, NS_ERROR_FAILURE);
new InitProfileResultHandlerRunnable(aRes, NS_ERROR_FAILURE);
if (NS_FAILED(NS_DispatchToMainThread(r))) {
BT_LOGR("Failed to dispatch HFP OnError runnable");
}
return;
}
BluetoothHandsfreeInterface *interface =
btInf->GetBluetoothHandsfreeInterface();
auto setupInterface = btInf->GetBluetoothSetupInterface();
if (NS_WARN_IF(!setupInterface)) {
// If there's no Setup interface, we dispatch a runnable
// that calls the profile result handler.
nsRefPtr<nsRunnable> r =
new InitProfileResultHandlerRunnable(aRes, NS_ERROR_FAILURE);
if (NS_FAILED(NS_DispatchToMainThread(r))) {
BT_LOGR("Failed to dispatch HFP OnError runnable");
}
return;
}
auto interface = btInf->GetBluetoothHandsfreeInterface();
if (NS_WARN_IF(!interface)) {
// If there's no HFP interface, we dispatch a runnable
// that calls the profile result handler.
nsRefPtr<nsRunnable> r =
new OnErrorProfileResultHandlerRunnable(aRes, NS_ERROR_FAILURE);
new InitProfileResultHandlerRunnable(aRes, NS_ERROR_FAILURE);
if (NS_FAILED(NS_DispatchToMainThread(r))) {
BT_LOGR("Failed to dispatch HFP OnError runnable");
}
return;
}
nsRefPtr<CleanupInitResultHandler> res =
new CleanupInitResultHandler(interface, aRes);
// Set notification handler _before_ registering the module. It could
// happen that we receive notifications, before the result handler runs.
interface->SetNotificationHandler(BluetoothHfpManager::Get());
if (sBluetoothHfpInterface) {
// Cleanup an initialized HFP before initializing again.
sBluetoothHfpInterface->Cleanup(res);
} else {
// If there's no HFP interface to cleanup first, we dispatch
// a runnable that calls the profile result handler.
nsRefPtr<nsRunnable> r = new InitResultHandlerRunnable(res);
if (NS_FAILED(NS_DispatchToMainThread(r))) {
BT_LOGR("Failed to dispatch HFP init runnable");
}
}
setupInterface->RegisterModule(
SETUP_SERVICE_ID_HANDSFREE, MODE_NARROWBAND_SPEECH, MAX_NUM_CLIENTS,
new RegisterModuleResultHandler(interface, aRes));
}
BluetoothHfpManager::~BluetoothHfpManager()
@ -432,18 +429,22 @@ BluetoothHfpManager::~BluetoothHfpManager()
hal::UnregisterBatteryObserver(this);
}
class BluetoothHfpManager::CleanupResultHandler final
: public BluetoothHandsfreeResultHandler
class BluetoothHfpManager::UnregisterModuleResultHandler final
: public BluetoothSetupResultHandler
{
public:
CleanupResultHandler(BluetoothProfileResultHandler* aRes)
UnregisterModuleResultHandler(BluetoothProfileResultHandler* aRes)
: mRes(aRes)
{ }
void OnError(BluetoothStatus aStatus) override
{
BT_WARNING("BluetoothHandsfreeInterface::Cleanup failed: %d", (int)aStatus);
MOZ_ASSERT(NS_IsMainThread());
BT_WARNING("BluetoothSetupInterface::UnregisterModule failed for HFP: %d",
(int)aStatus);
sBluetoothHfpInterface->SetNotificationHandler(nullptr);
sBluetoothHfpInterface = nullptr;
if (mRes) {
@ -451,9 +452,13 @@ public:
}
}
void Cleanup() override
void UnregisterModule() override
{
MOZ_ASSERT(NS_IsMainThread());
sBluetoothHfpInterface->SetNotificationHandler(nullptr);
sBluetoothHfpInterface = nullptr;
if (mRes) {
mRes->Deinit();
}
@ -463,40 +468,80 @@ private:
nsRefPtr<BluetoothProfileResultHandler> mRes;
};
class BluetoothHfpManager::DeinitResultHandlerRunnable final
class BluetoothHfpManager::DeinitProfileResultHandlerRunnable final
: public nsRunnable
{
public:
DeinitResultHandlerRunnable(BluetoothProfileResultHandler* aRes)
DeinitProfileResultHandlerRunnable(BluetoothProfileResultHandler* aRes,
nsresult aRv)
: mRes(aRes)
, mRv(aRv)
{
MOZ_ASSERT(mRes);
}
NS_IMETHOD Run() override
{
mRes->Deinit();
MOZ_ASSERT(NS_IsMainThread());
if (NS_SUCCEEDED(mRv)) {
mRes->Deinit();
} else {
mRes->OnError(mRv);
}
return NS_OK;
}
private:
nsRefPtr<BluetoothProfileResultHandler> mRes;
nsresult mRv;
};
// static
void
BluetoothHfpManager::DeinitHfpInterface(BluetoothProfileResultHandler* aRes)
{
if (sBluetoothHfpInterface) {
sBluetoothHfpInterface->Cleanup(new CleanupResultHandler(aRes));
} else if (aRes) {
// We dispatch a runnable here to make the profile resource handler
// behave as if HFP was initialized.
nsRefPtr<nsRunnable> r = new DeinitResultHandlerRunnable(aRes);
MOZ_ASSERT(NS_IsMainThread());
if (!sBluetoothHfpInterface) {
BT_LOGR("Bluetooth Handsfree interface has not been initialized.");
nsRefPtr<nsRunnable> r =
new DeinitProfileResultHandlerRunnable(aRes, NS_OK);
if (NS_FAILED(NS_DispatchToMainThread(r))) {
BT_LOGR("Failed to dispatch cleanup-result-handler runnable");
BT_LOGR("Failed to dispatch HFP Deinit runnable");
}
return;
}
auto btInf = BluetoothInterface::GetInstance();
if (NS_WARN_IF(!btInf)) {
// If there's no backend interface, we dispatch a runnable
// that calls the profile result handler.
nsRefPtr<nsRunnable> r =
new DeinitProfileResultHandlerRunnable(aRes, NS_ERROR_FAILURE);
if (NS_FAILED(NS_DispatchToMainThread(r))) {
BT_LOGR("Failed to dispatch HFP OnError runnable");
}
return;
}
auto setupInterface = btInf->GetBluetoothSetupInterface();
if (NS_WARN_IF(!setupInterface)) {
// If there's no Setup interface, we dispatch a runnable
// that calls the profile result handler.
nsRefPtr<nsRunnable> r =
new DeinitProfileResultHandlerRunnable(aRes, NS_ERROR_FAILURE);
if (NS_FAILED(NS_DispatchToMainThread(r))) {
BT_LOGR("Failed to dispatch HFP OnError runnable");
}
return;
}
setupInterface->UnregisterModule(
SETUP_SERVICE_ID_HANDSFREE,
new UnregisterModuleResultHandler(aRes));
}
//static

View File

@ -73,6 +73,12 @@ class BluetoothHfpManager : public BluetoothHfpManagerBase
, public BluetoothHandsfreeNotificationHandler
, public BatteryObserver
{
enum {
MODE_HEADSET = 0x00,
MODE_NARROWBAND_SPEECH = 0x01,
MODE_NARRAWBAND_WIDEBAND_SPEECH = 0x02
};
public:
BT_DECL_HFP_MGR_BASE
@ -151,21 +157,20 @@ private:
class ConnectResultHandler;
class CopsResponseResultHandler;
class ClccResponseResultHandler;
class CleanupInitResultHandler;
class CleanupResultHandler;
class CloseScoRunnable;
class CloseScoTask;
class DeinitResultHandlerRunnable;
class DeinitProfileResultHandlerRunnable;
class DeviceStatusNotificationResultHandler;
class DisconnectAudioResultHandler;
class DisconnectResultHandler;
class FormattedAtResponseResultHandler;
class GetVolumeTask;
class InitResultHandlerRunnable;
class InitProfileResultHandlerRunnable;
class MainThreadTask;
class OnErrorProfileResultHandlerRunnable;
class PhoneStateChangeResultHandler;
class RegisterModuleResultHandler;
class RespondToBLDNTask;
class UnregisterModuleResultHandler;
class VolumeControlResultHandler;
friend class BluetoothHfpManagerObserver;

View File

@ -9,6 +9,7 @@
#include "mozilla/Compiler.h"
#include "mozilla/Observer.h"
#include "nsAutoPtr.h"
#include "nsPrintfCString.h"
#include "nsString.h"
#include "nsTArray.h"
@ -325,6 +326,23 @@ enum BluetoothBondState {
BOND_STATE_BONDED
};
enum BluetoothSetupServiceId {
SETUP_SERVICE_ID_SETUP,
SETUP_SERVICE_ID_CORE,
SETUP_SERVICE_ID_SOCKET,
SETUP_SERVICE_ID_HID,
SETUP_SERVICE_ID_PAN,
SETUP_SERVICE_ID_HANDSFREE,
SETUP_SERVICE_ID_A2DP,
SETUP_SERVICE_ID_HEALTH,
SETUP_SERVICE_ID_AVRCP,
SETUP_SERVICE_ID_GATT,
SETUP_SERVICE_ID_HANDSFREE_CLIENT,
SETUP_SERVICE_ID_MAP_CLIENT,
SETUP_SERVICE_ID_AVRCP_CONTROLLER,
SETUP_SERVICE_ID_A2DP_SINK
};
/* Physical transport for GATT connections to remote dual-mode devices */
enum BluetoothTransport {
TRANSPORT_AUTO, /* No preference of physical transport */
@ -480,6 +498,12 @@ struct BluetoothAddress {
};
struct BluetoothConfigurationParameter {
uint8_t mType;
uint16_t mLength;
nsAutoArrayPtr<uint8_t> mValue;
};
struct BluetoothUuid {
uint8_t mUuid[16];

View File

@ -39,6 +39,12 @@ void
BluetoothSetupResultHandler::Configuration()
{ }
// Interface
//
BluetoothSetupInterface::~BluetoothSetupInterface()
{ }
//
// Socket Interface
//
@ -181,14 +187,6 @@ BluetoothHandsfreeResultHandler::OnError(BluetoothStatus aStatus)
BT_WARNING("Received error code %d", (int)aStatus);
}
void
BluetoothHandsfreeResultHandler::Init()
{ }
void
BluetoothHandsfreeResultHandler::Cleanup()
{ }
void
BluetoothHandsfreeResultHandler::Connect()
{ }
@ -295,14 +293,6 @@ BluetoothA2dpResultHandler::OnError(BluetoothStatus aStatus)
BT_WARNING("Received error code %d", (int)aStatus);
}
void
BluetoothA2dpResultHandler::Init()
{ }
void
BluetoothA2dpResultHandler::Cleanup()
{ }
void
BluetoothA2dpResultHandler::Connect()
{ }
@ -400,14 +390,6 @@ BluetoothAvrcpResultHandler::OnError(BluetoothStatus aStatus)
BT_WARNING("Received error code %d", (int)aStatus);
}
void
BluetoothAvrcpResultHandler::Init()
{ }
void
BluetoothAvrcpResultHandler::Cleanup()
{ }
void
BluetoothAvrcpResultHandler::GetPlayStatusRsp()
{ }
@ -679,14 +661,6 @@ BluetoothGattResultHandler::OnError(BluetoothStatus aStatus)
BT_WARNING("Received error code %d", (int)aStatus);
}
void
BluetoothGattResultHandler::Init()
{ }
void
BluetoothGattResultHandler::Cleanup()
{ }
void
BluetoothGattResultHandler::RegisterClient()
{ }

View File

@ -30,6 +30,25 @@ protected:
virtual ~BluetoothSetupResultHandler() { }
};
class BluetoothSetupInterface
{
public:
virtual void RegisterModule(BluetoothSetupServiceId aId,
uint8_t aMode,
uint32_t aMaxNumClients,
BluetoothSetupResultHandler* aRes) = 0;
virtual void UnregisterModule(BluetoothSetupServiceId aId,
BluetoothSetupResultHandler* aRes) = 0;
virtual void Configuration(const BluetoothConfigurationParameter* aParam,
uint8_t aLen,
BluetoothSetupResultHandler* aRes) = 0;
protected:
virtual ~BluetoothSetupInterface();
};
//
// Socket Interface
//
@ -155,9 +174,6 @@ class BluetoothHandsfreeResultHandler
public:
virtual void OnError(BluetoothStatus aStatus);
virtual void Init();
virtual void Cleanup();
virtual void Connect();
virtual void Disconnect();
virtual void ConnectAudio();
@ -186,10 +202,8 @@ protected:
class BluetoothHandsfreeInterface
{
public:
virtual void Init(
BluetoothHandsfreeNotificationHandler* aNotificationHandler,
int aMaxNumClients, BluetoothHandsfreeResultHandler* aRes) = 0;
virtual void Cleanup(BluetoothHandsfreeResultHandler* aRes) = 0;
virtual void SetNotificationHandler(
BluetoothHandsfreeNotificationHandler* aNotificationHandler) = 0;
/* Connect / Disconnect */
@ -295,8 +309,6 @@ class BluetoothA2dpResultHandler
public:
virtual void OnError(BluetoothStatus aStatus);
virtual void Init();
virtual void Cleanup();
virtual void Connect();
virtual void Disconnect();
@ -307,9 +319,8 @@ protected:
class BluetoothA2dpInterface
{
public:
virtual void Init(BluetoothA2dpNotificationHandler* aNotificationHandler,
BluetoothA2dpResultHandler* aRes) = 0;
virtual void Cleanup(BluetoothA2dpResultHandler* aRes) = 0;
virtual void SetNotificationHandler(
BluetoothA2dpNotificationHandler* aNotificationHandler) = 0;
virtual void Connect(const BluetoothAddress& aBdAddr,
BluetoothA2dpResultHandler* aRes) = 0;
@ -382,9 +393,6 @@ class BluetoothAvrcpResultHandler
public:
virtual void OnError(BluetoothStatus aStatus);
virtual void Init();
virtual void Cleanup();
virtual void GetPlayStatusRsp();
virtual void ListPlayerAppAttrRsp();
@ -409,9 +417,8 @@ protected:
class BluetoothAvrcpInterface
{
public:
virtual void Init(BluetoothAvrcpNotificationHandler* aNotificationHandler,
BluetoothAvrcpResultHandler* aRes) = 0;
virtual void Cleanup(BluetoothAvrcpResultHandler* aRes) = 0;
virtual void SetNotificationHandler(
BluetoothAvrcpNotificationHandler* aNotificationHandler) = 0;
virtual void GetPlayStatusRsp(ControlPlayStatus aPlayStatus,
uint32_t aSongLen, uint32_t aSongPos,
@ -663,9 +670,6 @@ class BluetoothGattResultHandler
public:
virtual void OnError(BluetoothStatus aStatus);
virtual void Init();
virtual void Cleanup();
virtual void RegisterClient();
virtual void UnregisterClient();
@ -723,9 +727,8 @@ protected:
class BluetoothGattInterface
{
public:
virtual void Init(BluetoothGattNotificationHandler* aNotificationHandler,
BluetoothGattResultHandler* aRes) = 0;
virtual void Cleanup(BluetoothGattResultHandler* aRes) = 0;
virtual void SetNotificationHandler(
BluetoothGattNotificationHandler* aNotificationHandler) = 0;
/* Register / Unregister */
virtual void RegisterClient(const BluetoothUuid& aUuid,
@ -1111,6 +1114,7 @@ public:
/* Profile Interfaces */
virtual BluetoothSetupInterface* GetBluetoothSetupInterface() = 0;
virtual BluetoothSocketInterface* GetBluetoothSocketInterface() = 0;
virtual BluetoothHandsfreeInterface* GetBluetoothHandsfreeInterface() = 0;
virtual BluetoothA2dpInterface* GetBluetoothA2dpInterface() = 0;

View File

@ -998,7 +998,7 @@ BrowserElementParent.prototype = {
try {
let nfcContentHelper =
Cc["@mozilla.org/nfc/content-helper;1"].getService(Ci.nsINfcBrowserAPI);
nfcContentHelper.setFocusApp(tabId, isFocus);
nfcContentHelper.setFocusTab(tabId, isFocus);
} catch(e) {
// Not all platforms support NFC
}

View File

@ -97,8 +97,8 @@ NfcContentHelper.prototype = {
return cpmm.sendSyncMessage("NFC:QueryInfo")[0].rfState;
},
setFocusApp: function setFocusApp(tabId, isFocus) {
cpmm.sendAsyncMessage("NFC:SetFocusApp", {
setFocusTab: function setFocusTab(tabId, isFocus) {
cpmm.sendAsyncMessage("NFC:SetFocusTab", {
tabId: tabId,
isFocus: isFocus
});

View File

@ -79,7 +79,7 @@ const NFC_IPC_MSG_ENTRIES = [
"NFC:NotifyUserAcceptedP2P",
"NFC:NotifySendFileStatus",
"NFC:ChangeRFState",
"NFC:SetFocusApp"] }
"NFC:SetFocusTab"] }
];
// Should be consistent with NfcRequestType defined in NfcOptions.webidl.
@ -148,7 +148,7 @@ XPCOMUtils.defineLazyGetter(this, "gMessageManager", function () {
eventListeners: {},
focusApp: NFC.SYSTEM_APP_ID,
focusId: NFC.SYSTEM_APP_ID,
init: function init(nfc) {
this.nfc = nfc;
@ -214,7 +214,7 @@ XPCOMUtils.defineLazyGetter(this, "gMessageManager", function () {
});
},
notifyFocusApp: function notifyFocusApp(options) {
notifyFocusTab: function notifyFocusTab(options) {
let tabId = this.getFocusTabId();
options.tabId = tabId;
@ -231,29 +231,29 @@ XPCOMUtils.defineLazyGetter(this, "gMessageManager", function () {
},
getFocusTabId: function getFocusTabId() {
return this.eventListeners[this.focusApp] ? this.focusApp
: NFC.SYSTEM_APP_ID;
return this.eventListeners[this.focusId] ? this.focusId
: NFC.SYSTEM_APP_ID;
},
setFocusApp: function setFocusApp(id, isFocus) {
setFocusTab: function setFocusTab(id, isFocus) {
// if calling setNFCFocus(true) on the browser-element which is already
// focused, or calling setNFCFocus(false) on the browser-element which has
// lost focus already, ignore.
if (isFocus == (id == this.focusApp)) {
if (isFocus == (id == this.focusId)) {
return;
}
if (this.focusApp != NFC.SYSTEM_APP_ID) {
this.onFocusChanged(this.focusApp, false);
if (this.focusId != NFC.SYSTEM_APP_ID) {
this.onFocusChanged(this.focusId, false);
}
if (isFocus) {
// Now we only support one focus app.
this.focusApp = id;
this.onFocusChanged(this.focusApp, true);
} else if (this.focusApp == id){
// Set focusApp to SystemApp means currently there is no foreground app.
this.focusApp = NFC.SYSTEM_APP_ID;
this.focusId = id;
this.onFocusChanged(this.focusId, true);
} else if (this.focusId == id){
// Set focusId to SystemApp means currently there is no foreground app.
this.focusId = NFC.SYSTEM_APP_ID;
}
},
@ -325,17 +325,17 @@ XPCOMUtils.defineLazyGetter(this, "gMessageManager", function () {
onTagFound: function onTagFound(message) {
message.event = NFC.TAG_EVENT_FOUND;
this.notifyFocusApp(message);
this.notifyFocusTab(message);
delete message.event;
},
onTagLost: function onTagLost(sessionToken) {
this.notifyFocusApp({ event: NFC.TAG_EVENT_LOST,
this.notifyFocusTab({ event: NFC.TAG_EVENT_LOST,
sessionToken: sessionToken });
},
onPeerEvent: function onPeerEvent(eventType, sessionToken) {
this.notifyFocusApp({ event: eventType,
this.notifyFocusTab({ event: eventType,
sessionToken: sessionToken });
},
@ -348,13 +348,13 @@ XPCOMUtils.defineLazyGetter(this, "gMessageManager", function () {
}
},
onFocusChanged: function onFocusChanged(focusApp, focus) {
let target = this.eventListeners[focusApp];
onFocusChanged: function onFocusChanged(focusId, focus) {
let target = this.eventListeners[focusId];
if (!target) {
return;
}
this.notifyDOMEvent(target, { tabId: this.focusApp,
this.notifyDOMEvent(target, { tabId: this.focusId,
event: NFC.FOCUS_CHANGED,
focus: focus });
},
@ -386,8 +386,8 @@ XPCOMUtils.defineLazyGetter(this, "gMessageManager", function () {
}
switch (message.name) {
case "NFC:SetFocusApp":
this.setFocusApp(message.data.tabId, message.data.isFocus);
case "NFC:SetFocusTab":
this.setFocusTab(message.data.tabId, message.data.isFocus);
return null;
case "NFC:AddEventListener":
this.addEventListener(message.target, message.data.tabId);

View File

@ -115,12 +115,12 @@ interface nsINfcRequestCallback : nsISupports
void notifyError(in DOMString errorMsg);
};
[scriptable, uuid(2dbc73d4-ba16-4c89-bce5-3c22cee6b50a)]
[scriptable, uuid(9f86c799-6959-4ad2-bdd6-6fbf49b52d1c)]
interface nsINfcBrowserAPI : nsISupports
{
const int32_t SYSTEM_APP_ID = 0;
void setFocusApp(in uint64_t tabId,
void setFocusTab(in uint64_t tabId,
in boolean isFocus);
};

View File

@ -243,7 +243,7 @@ class BaseStackFrame {
// Get a unique identifier for this StackFrame. The identifier is not valid
// across garbage collections.
virtual uint64_t identifier() const { return reinterpret_cast<uint64_t>(ptr); }
virtual uint64_t identifier() const { return uint64_t(uintptr_t(ptr)); }
// Get this frame's parent frame.
virtual StackFrame parent() const = 0;
@ -418,7 +418,11 @@ class StackFrame : public JS::Traceable {
// Methods that forward to virtual calls through BaseStackFrame.
void trace(JSTracer* trc) { base()->trace(trc); }
uint64_t identifier() const { return base()->identifier(); }
uint64_t identifier() const {
auto id = base()->identifier();
MOZ_ASSERT(JS::Value::isNumberRepresentable(id));
return id;
}
uint32_t line() const { return base()->line(); }
uint32_t column() const { return base()->column(); }
AtomOrTwoByteChars source() const { return base()->source(); }
@ -564,7 +568,7 @@ class Base {
// caveats about multiple objects allocated at the same address for
// 'ubi::Node::operator=='.)
using Id = uint64_t;
virtual Id identifier() const { return reinterpret_cast<Id>(ptr); }
virtual Id identifier() const { return Id(uintptr_t(ptr)); }
// Returns true if this node is pointing to something on the live heap, as
// opposed to something from a deserialized core dump. Returns false,
@ -790,7 +794,11 @@ class Node {
}
using Id = Base::Id;
Id identifier() const { return base()->identifier(); }
Id identifier() const {
auto id = base()->identifier();
MOZ_ASSERT(JS::Value::isNumberRepresentable(id));
return id;
}
// A hash policy for ubi::Nodes.
// This simply uses the stock PointerHasher on the ubi::Node's pointer.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 785 B

After

Width:  |  Height:  |  Size: 502 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 90 B

After

Width:  |  Height:  |  Size: 254 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 904 B

After

Width:  |  Height:  |  Size: 555 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 108 B

After

Width:  |  Height:  |  Size: 315 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 688 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 391 B

View File

@ -732,7 +732,7 @@ nsHttpHandler::InitUserAgentComponents()
#endif
#if defined(ANDROID) || defined(FXOS_SIMULATOR)
#ifdef ANDROID
nsCOMPtr<nsIPropertyBag2> infoService = do_GetService("@mozilla.org/system-info;1");
MOZ_ASSERT(infoService, "Could not find a system info service");
nsresult rv;
@ -753,15 +753,35 @@ nsHttpHandler::InitUserAgentComponents()
}
}
#endif
// Add the `Mobile` or `Tablet` token when running on device or in the
// b2g desktop simulator.
// Add the `Mobile` or `Tablet` or `TV` token when running on device.
bool isTablet;
rv = infoService->GetPropertyAsBool(NS_LITERAL_STRING("tablet"), &isTablet);
if (NS_SUCCEEDED(rv) && isTablet)
if (NS_SUCCEEDED(rv) && isTablet) {
mCompatDevice.AssignLiteral("Tablet");
else
mCompatDevice.AssignLiteral("Mobile");
#endif
} else {
bool isTV;
rv = infoService->GetPropertyAsBool(NS_LITERAL_STRING("tv"), &isTV);
if (NS_SUCCEEDED(rv) && isTV) {
mCompatDevice.AssignLiteral("TV");
} else {
mCompatDevice.AssignLiteral("Mobile");
}
}
#endif // ANDROID
#ifdef FXOS_SIMULATOR
{
// Add the `Mobile` or `Tablet` or `TV` token when running in the b2g
// desktop simulator via preference.
nsCString deviceType;
nsresult rv = Preferences::GetCString("devtools.useragent.device_type", &deviceType);
if (NS_SUCCEEDED(rv)) {
mCompatDevice.Assign(deviceType);
} else {
mCompatDevice.AssignLiteral("Mobile");
}
}
#endif // FXOS_SIMULATOR
#if defined(MOZ_WIDGET_GONK)
// Device model identifier should be a simple token, which can be composed

8
testing/talos/.gitignore vendored Normal file
View File

@ -0,0 +1,8 @@
.Python
bin/
include/
lib/
talos.egg-info
talos/tests/tp5n.zip
talos/tests/tp5n
talos/tests/devtools/damp.manifest.develop

View File

@ -358,6 +358,7 @@ class damp(PageloaderTest):
sps_profile_entries = 1000000
win_counters = w7_counters = linux_counters = mac_counters = None
filters = filter.ignore_first.prepare(1) + filter.median.prepare()
preferences = {'devtools.memory.enabled': True}
@register_test()

View File

@ -16,6 +16,7 @@ var defaultConfig = {
styleEditorOpen: true,
performanceOpen: true,
netmonitorOpen: true,
saveAndReadHeapSnapshot: true,
}
};
@ -25,7 +26,8 @@ var testsInfo = {
debuggerOpen: "Measure open/close toolbox on debugger panel",
styleEditorOpen: "Measure open/close toolbox on style editor panel",
performanceOpen: "Measure open/close toolbox on performance panel",
netmonitorOpen: "Measure open/close toolbox on network monitor panel"
netmonitorOpen: "Measure open/close toolbox on network monitor panel",
saveAndReadHeapSnapshot: "Measure open/close toolbox on memory panel and save/read heap snapshot",
};
function updateConfig() {

View File

@ -1,14 +1,19 @@
Components.utils.import("resource:///modules/devtools/client/framework/gDevTools.jsm");
const {devtools} =
Components.utils.import("resource://gre/modules/devtools/shared/Loader.jsm", {});
const { getActiveTab } = devtools.require("sdk/tabs/utils");
const { getMostRecentBrowserWindow } = devtools.require("sdk/window/utils");
const ThreadSafeChromeUtils = devtools.require("ThreadSafeChromeUtils");
const SIMPLE_URL = "chrome://damp/content/pages/simple.html";
const COMPLICATED_URL = "http://localhost/tests/tp5n/bild.de/www.bild.de/index.html";
function Damp() {
// Path to the temp file where the heap snapshot file is saved. Set by
// saveHeapSnapshot and read by readHeapSnapshot.
this._heapSnapshotFilePath = null;
// HeapSnapshot instance. Set by readHeapSnapshot, used by takeCensus.
this._snapshot = null;
}
Damp.prototype = {
@ -69,6 +74,71 @@ Damp.prototype = {
});
},
saveHeapSnapshot: function(label) {
let tab = getActiveTab(getMostRecentBrowserWindow());
let target = devtools.TargetFactory.forTab(tab);
let toolbox = gDevTools.getToolbox(target);
let panel = toolbox.getCurrentPanel();
let memoryFront = panel.panelWin.gFront;
let start = performance.now();
return memoryFront.saveHeapSnapshot().then(filePath => {
this._heapSnapshotFilePath = filePath;
let end = performance.now();
this._results.push({
name: label + ".saveHeapSnapshot",
value: end - start
});
});
},
readHeapSnapshot: function(label) {
let start = performance.now();
this._snapshot = ThreadSafeChromeUtils.readHeapSnapshot(this._heapSnapshotFilePath);
let end = performance.now();
this._results.push({
name: label + ".readHeapSnapshot",
value: end - start
});
return Promise.resolve();
},
takeCensus: function(label) {
let start = performance.now();
this._snapshot.takeCensus({
breakdown: {
by: "coarseType",
objects: {
by: "objectClass",
then: { by: "count", bytes: true, count: true },
other: { by: "count", bytes: true, count: true }
},
strings: {
by: "internalType",
then: { by: "count", bytes: true, count: true }
},
scripts: {
by: "internalType",
then: { by: "count", bytes: true, count: true }
},
other: {
by: "internalType",
then: { by: "count", bytes: true, count: true }
}
}
});
let end = performance.now();
this._results.push({
name: label + ".takeCensus",
value: end - start
});
return Promise.resolve();
},
_startTest: function() {
var self = this;
@ -76,6 +146,9 @@ Damp.prototype = {
var closeToolbox = this.closeToolbox.bind(this);
var reloadPage = this.reloadPage.bind(this);
var next = this._nextCommand.bind(this);
var saveHeapSnapshot = this.saveHeapSnapshot.bind(this);
var readHeapSnapshot = this.readHeapSnapshot.bind(this);
var takeCensus = this.takeCensus.bind(this);
var config = this._config;
var rest = config.rest; // How long to wait in between opening the tab and starting the test.
@ -135,6 +208,14 @@ Damp.prototype = {
() => { closeToolbox(label + ".netmonitor").then(next); },
],
saveAndReadHeapSnapshot: [
() => { openToolbox(label + ".memory", "memory").then(next); },
() => { reloadPage(label + ".memory").then(next); },
() => { saveHeapSnapshot(label).then(next); },
() => { readHeapSnapshot(label).then(next); },
() => { takeCensus(label).then(next); },
() => { closeToolbox(label + ".memory").then(next); },
]
};
// Construct the sequence array: config.repeat times config.subtests,

View File

@ -186,6 +186,8 @@ tests:
allowed_build_tasks:
tasks/builds/b2g_emulator_ics_opt.yml:
task: tasks/tests/b2g_emulator_crashtest.yml
tasks/builds/b2g_emulator_x86_kk_opt.yml:
task: tasks/tests/b2g_emulator_crashtest.yml
gaia-build:
allowed_build_tasks:
tasks/builds/b2g_desktop_opt.yml:

View File

@ -32,7 +32,9 @@ task:
extra:
chunks:
total: 5
treeherderEnv:
- production
- staging
treeherder:
groupName: Reftest
groupSymbol: tc-R

View File

@ -13,6 +13,8 @@ Cu.import("resource://gre/modules/XPCOMUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "devtools",
"resource://gre/modules/devtools/shared/Loader.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "LoginManagerContent",
"resource://gre/modules/LoginManagerContent.jsm");
Object.defineProperty(this, "WebConsoleUtils", {
get: function() {
@ -47,50 +49,6 @@ this.InsecurePasswordUtils = {
Services.console.logMessage(consoleMsg);
},
/*
* Checks whether the passed uri is secure
* Check Protocol Flags to determine if scheme is secure:
* URI_DOES_NOT_RETURN_DATA - e.g.
* "mailto"
* URI_IS_LOCAL_RESOURCE - e.g.
* "data",
* "resource",
* "moz-icon"
* URI_INHERITS_SECURITY_CONTEXT - e.g.
* "javascript"
* URI_SAFE_TO_LOAD_IN_SECURE_CONTEXT - e.g.
* "https",
* "moz-safe-about"
*
* The use of this logic comes directly from nsMixedContentBlocker.cpp
* At the time it was decided to include these protocols since a secure
* uri for mixed content blocker means that the resource can't be
* easily tampered with because 1) it is sent over an encrypted channel or
* 2) it is a local resource that never hits the network
* or 3) it is a request sent without any response that could alter
* the behavior of the page. It was decided to include the same logic
* here both to be consistent with MCB and to make sure we cover all
* "safe" protocols. Eventually, the code here and the code in MCB
* will be moved to a common location that will be referenced from
* both places. Look at
* https://bugzilla.mozilla.org/show_bug.cgi?id=899099 for more info.
*/
_checkIfURIisSecure : function(uri) {
let isSafe = false;
let netutil = Cc["@mozilla.org/network/util;1"].getService(Ci.nsINetUtil);
let ph = Ci.nsIProtocolHandler;
if (netutil.URIChainHasFlags(uri, ph.URI_IS_LOCAL_RESOURCE) ||
netutil.URIChainHasFlags(uri, ph.URI_DOES_NOT_RETURN_DATA) ||
netutil.URIChainHasFlags(uri, ph.URI_INHERITS_SECURITY_CONTEXT) ||
netutil.URIChainHasFlags(uri, ph.URI_SAFE_TO_LOAD_IN_SECURE_CONTEXT)) {
isSafe = true;
}
return isSafe;
},
/*
* Checks whether the passed nested document is insecure
* or is inside an insecure parent document.
@ -109,7 +67,7 @@ this.InsecurePasswordUtils = {
// We are at the top, nothing to check here
return false;
}
if (!this._checkIfURIisSecure(uri)) {
if (!LoginManagerContent.checkIfURIisSecure(uri)) {
// We are insecure
return true;
}
@ -127,7 +85,7 @@ this.InsecurePasswordUtils = {
checkForInsecurePasswords : function (aForm) {
var domDoc = aForm.ownerDocument;
let pageURI = domDoc.defaultView.top.document.documentURIObject;
let isSafePage = this._checkIfURIisSecure(pageURI);
let isSafePage = LoginManagerContent.checkIfURIisSecure(pageURI);
if (!isSafePage) {
this._sendWebConsoleMessage("InsecurePasswordsPresentOnPage", domDoc);

View File

@ -414,6 +414,18 @@ var LoginManagerContent = {
return null;
};
// Returns true if this window or any subframes have insecure login forms.
let hasInsecureLoginForms = (thisWindow, parentIsInsecure) => {
let doc = thisWindow.document;
let isInsecure =
parentIsInsecure ||
!this.checkIfURIisSecure(doc.documentURIObject);
let hasLoginForm = !!this.stateForDocument(doc).loginForm;
return (hasLoginForm && isInsecure) ||
Array.some(thisWindow.frames,
frame => hasInsecureLoginForms(frame, isInsecure));
};
// Store the actual form to use on the state for the top-level document.
let topState = this.stateForDocument(topWindow.document);
topState.loginFormForFill = getFirstLoginForm(topWindow);
@ -423,6 +435,7 @@ var LoginManagerContent = {
messageManager.sendAsyncMessage("RemoteLogins:updateLoginFormPresence", {
loginFormOrigin,
loginFormPresent: !!topState.loginFormForFill,
hasInsecureLoginForms: hasInsecureLoginForms(topWindow, false),
});
},
@ -1089,6 +1102,49 @@ var LoginManagerContent = {
};
},
/*
* Checks whether the passed uri is secure
* Check Protocol Flags to determine if scheme is secure:
* URI_DOES_NOT_RETURN_DATA - e.g.
* "mailto"
* URI_IS_LOCAL_RESOURCE - e.g.
* "data",
* "resource",
* "moz-icon"
* URI_INHERITS_SECURITY_CONTEXT - e.g.
* "javascript"
* URI_SAFE_TO_LOAD_IN_SECURE_CONTEXT - e.g.
* "https",
* "moz-safe-about"
*
* The use of this logic comes directly from nsMixedContentBlocker.cpp
* At the time it was decided to include these protocols since a secure
* uri for mixed content blocker means that the resource can't be
* easily tampered with because 1) it is sent over an encrypted channel or
* 2) it is a local resource that never hits the network
* or 3) it is a request sent without any response that could alter
* the behavior of the page. It was decided to include the same logic
* here both to be consistent with MCB and to make sure we cover all
* "safe" protocols. Eventually, the code here and the code in MCB
* will be moved to a common location that will be referenced from
* both places. Look at
* https://bugzilla.mozilla.org/show_bug.cgi?id=899099 for more info.
*/
checkIfURIisSecure : function(uri) {
let isSafe = false;
let netutil = Cc["@mozilla.org/network/util;1"].getService(Ci.nsINetUtil);
let ph = Ci.nsIProtocolHandler;
if (netutil.URIChainHasFlags(uri, ph.URI_IS_LOCAL_RESOURCE) ||
netutil.URIChainHasFlags(uri, ph.URI_DOES_NOT_RETURN_DATA) ||
netutil.URIChainHasFlags(uri, ph.URI_INHERITS_SECURITY_CONTEXT) ||
netutil.URIChainHasFlags(uri, ph.URI_SAFE_TO_LOAD_IN_SECURE_CONTEXT)) {
isSafe = true;
}
return isSafe;
},
};
var LoginUtils = {

View File

@ -569,12 +569,23 @@ var LoginManagerParent = {
return loginFormState;
},
/**
* Returns true if the page currently loaded in the given browser element has
* insecure login forms. This state may be updated asynchronously, in which
* case a custom event named InsecureLoginFormsStateChange will be dispatched
* on the browser element.
*/
hasInsecureLoginForms(browser) {
return !!this.stateForBrowser(browser).hasInsecureLoginForms;
},
/**
* Called to indicate whether a login form on the currently loaded page is
* present or not. This is one of the factors used to control the visibility
* of the password fill doorhanger.
*/
updateLoginFormPresence(browser, { loginFormOrigin, loginFormPresent }) {
updateLoginFormPresence(browser, { loginFormOrigin, loginFormPresent,
hasInsecureLoginForms }) {
const ANCHOR_DELAY_MS = 200;
let state = this.stateForBrowser(browser);
@ -583,8 +594,13 @@ var LoginManagerParent = {
// processed in order, this will always be the latest version to use.
state.loginFormOrigin = loginFormOrigin;
state.loginFormPresent = loginFormPresent;
state.hasInsecureLoginForms = hasInsecureLoginForms;
// Apply the data to the currently displayed icon later.
// Report the insecure login form state immediately.
browser.dispatchEvent(new browser.ownerDocument.defaultView
.CustomEvent("InsecureLoginFormsStateChange"));
// Apply the data to the currently displayed login fill icon later.
if (!state.anchorDeferredTask) {
state.anchorDeferredTask = new DeferredTask(
() => this.updateLoginAnchor(browser),

View File

@ -2,11 +2,14 @@
support-files =
authenticate.sjs
form_basic.html
insecure_test.html
insecure_test_subframe.html
multiple_forms.html
[browser_DOMFormHasPassword.js]
[browser_DOMInputPasswordAdded.js]
[browser_filldoorhanger.js]
[browser_hasInsecureLoginForms.js]
[browser_notifications.js]
skip-if = true # Intermittent failures: Bug 1182296, bug 1148771
[browser_passwordmgr_editing.js]

View File

@ -0,0 +1,93 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
Cu.import("resource://gre/modules/LoginManagerParent.jsm", this);
const testUrlPath =
"://example.com/browser/toolkit/components/passwordmgr/test/browser/";
/**
* Waits for the given number of occurrences of InsecureLoginFormsStateChange
* on the given browser element.
*/
function waitForInsecureLoginFormsStateChange(browser, count) {
return BrowserTestUtils.waitForEvent(browser, "InsecureLoginFormsStateChange",
false, () => --count == 0);
}
/**
* Checks that hasInsecureLoginForms is true for a simple HTTP page and false
* for a simple HTTPS page.
*/
add_task(function* test_simple() {
for (let scheme of ["http", "https"]) {
let tab = gBrowser.addTab(scheme + testUrlPath + "form_basic.html");
let browser = tab.linkedBrowser;
yield Promise.all([
BrowserTestUtils.switchTab(gBrowser, tab),
BrowserTestUtils.browserLoaded(browser),
// One event is triggered by pageshow and one by DOMFormHasPassword.
waitForInsecureLoginFormsStateChange(browser, 2),
]);
Assert.equal(LoginManagerParent.hasInsecureLoginForms(browser),
scheme == "http");
gBrowser.removeTab(tab);
}
});
/**
* Checks that hasInsecureLoginForms is true if a password field is present in
* an HTTP page loaded as a subframe of a top-level HTTPS page, when mixed
* active content blocking is disabled.
*
* When the subframe is navigated to an HTTPS page, hasInsecureLoginForms should
* be set to false.
*
* Moving back in history should set hasInsecureLoginForms to true again.
*/
add_task(function* test_subframe_navigation() {
yield new Promise(resolve => SpecialPowers.pushPrefEnv({
"set": [["security.mixed_content.block_active_content", false]],
}, resolve));
// Load the page with the subframe in a new tab.
let tab = gBrowser.addTab("https" + testUrlPath + "insecure_test.html");
let browser = tab.linkedBrowser;
yield Promise.all([
BrowserTestUtils.switchTab(gBrowser, tab),
BrowserTestUtils.browserLoaded(browser),
// Two events are triggered by pageshow and one by DOMFormHasPassword.
waitForInsecureLoginFormsStateChange(browser, 3),
]);
Assert.ok(LoginManagerParent.hasInsecureLoginForms(browser));
// Navigate the subframe to a secure page.
let promiseSubframeReady = Promise.all([
BrowserTestUtils.browserLoaded(browser, true),
// One event is triggered by pageshow and one by DOMFormHasPassword.
waitForInsecureLoginFormsStateChange(browser, 2),
]);
yield ContentTask.spawn(browser, null, function* () {
content.document.getElementById("test-iframe")
.contentDocument.getElementById("test-link").click();
});
yield promiseSubframeReady;
Assert.ok(!LoginManagerParent.hasInsecureLoginForms(browser));
// Navigate back to the insecure page. We only have to wait for the
// InsecureLoginFormsStateChange event that is triggered by pageshow.
let promise = waitForInsecureLoginFormsStateChange(browser, 1);
yield ContentTask.spawn(browser, null, function* () {
content.document.getElementById("test-iframe")
.contentWindow.history.back();
});
yield promise;
Assert.ok(LoginManagerParent.hasInsecureLoginForms(browser));
gBrowser.removeTab(tab);
});

View File

@ -0,0 +1,9 @@
<!DOCTYPE html><html><head><meta charset="utf-8"></head><body>
<!-- Any copyright is dedicated to the Public Domain.
- http://creativecommons.org/publicdomain/zero/1.0/ -->
<!-- This frame is initially loaded over HTTP. -->
<iframe id="test-iframe"
src="http://example.org/browser/toolkit/components/passwordmgr/test/browser/insecure_test_subframe.html"/>
</body></html>

View File

@ -0,0 +1,13 @@
<!DOCTYPE html><html><head><meta charset="utf-8"></head><body>
<!-- Any copyright is dedicated to the Public Domain.
- http://creativecommons.org/publicdomain/zero/1.0/ -->
<form>
<input name="password" type="password">
</form>
<!-- Link to reload this page over HTTPS. -->
<a id="test-link"
href="https://example.org/browser/toolkit/components/passwordmgr/test/browser/insecure_test_subframe.html">HTTPS</a>
</body></html>

View File

@ -810,11 +810,13 @@ var StackRenderer = {
}
};
function SymbolicationRequest(aPrefix, aRenderHeader, aMemoryMap, aStacks) {
function SymbolicationRequest(aPrefix, aRenderHeader,
aMemoryMap, aStacks, aDurations = null) {
this.prefix = aPrefix;
this.renderHeader = aRenderHeader;
this.memoryMap = aMemoryMap;
this.stacks = aStacks;
this.durations = aDurations;
}
/**
* A callback for onreadystatechange. It replaces the numeric stack with
@ -848,7 +850,7 @@ function SymbolicationRequest_handleSymbolResponse() {
for (let i = 0; i < jsonResponse.length; ++i) {
let stack = jsonResponse[i];
this.renderHeader(i);
this.renderHeader(i, this.durations);
for (let symbol of stack) {
div.appendChild(document.createTextNode(symbol));
@ -894,14 +896,14 @@ var ChromeHangs = {
let stacks = hangs.stacks;
let memoryMap = hangs.memoryMap;
let durations = hangs.durations;
StackRenderer.renderStacks("chrome-hangs", stacks, memoryMap,
(index) => this.renderHangHeader(aPing, index));
(index) => this.renderHangHeader(index, durations));
},
renderHangHeader: function ChromeHangs_renderHangHeader(aPing, aIndex) {
let durations = aPing.payload.chromeHangs.durations;
StackRenderer.renderHeader("chrome-hangs", [aIndex + 1, durations[aIndex]]);
renderHangHeader: function ChromeHangs_renderHangHeader(aIndex, aDurations) {
StackRenderer.renderHeader("chrome-hangs", [aIndex + 1, aDurations[aIndex]]);
}
};
@ -1391,7 +1393,9 @@ function setupListeners() {
let hangs = gPingData.payload.chromeHangs;
let req = new SymbolicationRequest("chrome-hangs",
ChromeHangs.renderHangHeader,
hangs.memoryMap, hangs.stacks);
hangs.memoryMap,
hangs.stacks,
hangs.durations);
req.fetchSymbols();
}, false);

View File

@ -743,6 +743,8 @@ nsSystemInfo::Init()
if (__system_property_get("ro.build.characteristics", characteristics)) {
if (!strcmp(characteristics, "tablet")) {
SetPropertyAsBool(NS_LITERAL_STRING("tablet"), true);
} else if (!strcmp(characteristics, "tv")) {
SetPropertyAsBool(NS_LITERAL_STRING("tv"), true);
}
}