This commit is contained in:
Ryan VanderMeulen 2014-08-29 15:10:18 -04:00
Родитель 5c2f080f01 7e6d458629
Коммит bc0492cc53
26 изменённых файлов: 413 добавлений и 69 удалений

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

@ -347,6 +347,14 @@ function injectLoopAPI(targetWindow) {
}
},
logInToFxA: {
enumerable: true,
writable: true,
value: function() {
return MozLoopService.logInToFxA();
}
},
/**
* Copies passed string onto the system clipboard.
*

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

@ -780,6 +780,10 @@ this.MozLoopService = {
* @return {Promise} that resolves when the FxA login flow is complete.
*/
logInToFxA: function() {
if (gFxAOAuthTokenData) {
return Promise.resolve(gFxAOAuthTokenData);
}
return MozLoopServiceInternal.promiseFxAOAuthAuthorization().then(response => {
return MozLoopServiceInternal.promiseFxAOAuthToken(response.code, response.state);
}).then(tokenData => {

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

@ -194,6 +194,9 @@ loop.conversation = (function(OT, mozL10n) {
this.navigate("call/declineAndBlock", {trigger: true});
}.bind(this));
this._conversation.once("call:incoming", this.startCall, this);
this._conversation.once("change:publishedStream", this._checkConnected, this);
this._conversation.once("change:subscribedStream", this._checkConnected, this);
this._client.requestCallsInfo(loopVersion, function(err, sessionData) {
if (err) {
console.error("Failed to get the sessionData", err);
@ -235,11 +238,24 @@ loop.conversation = (function(OT, mozL10n) {
}.bind(this));
},
/**
* Checks if the streams have been connected, and notifies the
* websocket that the media is now connected.
*/
_checkConnected: function() {
// Check we've had both local and remote streams connected before
// sending the media up message.
if (this._conversation.streamsConnected()) {
this._websocket.mediaUp();
}
},
/**
* Accepts an incoming call.
*/
accept: function() {
navigator.mozLoop.stopAlerting();
this._websocket.accept();
this._conversation.incoming();
},

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

@ -194,6 +194,9 @@ loop.conversation = (function(OT, mozL10n) {
this.navigate("call/declineAndBlock", {trigger: true});
}.bind(this));
this._conversation.once("call:incoming", this.startCall, this);
this._conversation.once("change:publishedStream", this._checkConnected, this);
this._conversation.once("change:subscribedStream", this._checkConnected, this);
this._client.requestCallsInfo(loopVersion, function(err, sessionData) {
if (err) {
console.error("Failed to get the sessionData", err);
@ -235,11 +238,24 @@ loop.conversation = (function(OT, mozL10n) {
}.bind(this));
},
/**
* Checks if the streams have been connected, and notifies the
* websocket that the media is now connected.
*/
_checkConnected: function() {
// Check we've had both local and remote streams connected before
// sending the media up message.
if (this._conversation.streamsConnected()) {
this._websocket.mediaUp();
}
},
/**
* Accepts an incoming call.
*/
accept: function() {
navigator.mozLoop.stopAlerting();
this._websocket.accept();
this._conversation.incoming();
},

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

@ -77,24 +77,22 @@ loop.panel = (function(_, mozL10n) {
__("display_name_available_status");
return (
React.DOM.div({className: "footer"},
React.DOM.div({className: "do-not-disturb"},
React.DOM.div({className: "dnd-status", onClick: this.showDropdownMenu},
React.DOM.span(null, availabilityText),
React.DOM.i({className: availabilityStatus})
React.DOM.div({className: "do-not-disturb"},
React.DOM.p({className: "dnd-status", onClick: this.showDropdownMenu},
React.DOM.span(null, availabilityText),
React.DOM.i({className: availabilityStatus})
),
React.DOM.ul({className: availabilityDropdown,
onMouseLeave: this.hideDropdownMenu},
React.DOM.li({onClick: this.changeAvailability("available"),
className: "dnd-menu-item dnd-make-available"},
React.DOM.i({className: "status status-available"}),
React.DOM.span(null, __("display_name_available_status"))
),
React.DOM.ul({className: availabilityDropdown,
onMouseLeave: this.hideDropdownMenu},
React.DOM.li({onClick: this.changeAvailability("available"),
className: "dnd-menu-item dnd-make-available"},
React.DOM.i({className: "status status-available"}),
React.DOM.span(null, __("display_name_available_status"))
),
React.DOM.li({onClick: this.changeAvailability("do-not-disturb"),
className: "dnd-menu-item dnd-make-unavailable"},
React.DOM.i({className: "status status-dnd"}),
React.DOM.span(null, __("display_name_dnd_status"))
)
React.DOM.li({onClick: this.changeAvailability("do-not-disturb"),
className: "dnd-menu-item dnd-make-unavailable"},
React.DOM.i({className: "status status-dnd"}),
React.DOM.span(null, __("display_name_dnd_status"))
)
)
)
@ -272,6 +270,10 @@ loop.panel = (function(_, mozL10n) {
callUrl: React.PropTypes.string
},
handleSignUpLinkClick: function() {
navigator.mozLoop.logInToFxA();
},
render: function() {
return (
React.DOM.div(null,
@ -279,7 +281,12 @@ loop.panel = (function(_, mozL10n) {
notifier: this.props.notifier,
callUrl: this.props.callUrl}),
ToSView(null),
AvailabilityDropdown(null)
React.DOM.div({className: "footer"},
AvailabilityDropdown(null),
React.DOM.a({className: "signin-link", href: "#", onClick: this.handleSignUpLinkClick},
__("panel_footer_signin_or_signup_link")
)
)
)
);
}

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

@ -77,26 +77,24 @@ loop.panel = (function(_, mozL10n) {
__("display_name_available_status");
return (
<div className="footer">
<div className="do-not-disturb">
<div className="dnd-status" onClick={this.showDropdownMenu}>
<span>{availabilityText}</span>
<i className={availabilityStatus}></i>
</div>
<ul className={availabilityDropdown}
onMouseLeave={this.hideDropdownMenu}>
<li onClick={this.changeAvailability("available")}
className="dnd-menu-item dnd-make-available">
<i className="status status-available"></i>
<span>{__("display_name_available_status")}</span>
</li>
<li onClick={this.changeAvailability("do-not-disturb")}
className="dnd-menu-item dnd-make-unavailable">
<i className="status status-dnd"></i>
<span>{__("display_name_dnd_status")}</span>
</li>
</ul>
</div>
<div className="do-not-disturb">
<p className="dnd-status" onClick={this.showDropdownMenu}>
<span>{availabilityText}</span>
<i className={availabilityStatus}></i>
</p>
<ul className={availabilityDropdown}
onMouseLeave={this.hideDropdownMenu}>
<li onClick={this.changeAvailability("available")}
className="dnd-menu-item dnd-make-available">
<i className="status status-available"></i>
<span>{__("display_name_available_status")}</span>
</li>
<li onClick={this.changeAvailability("do-not-disturb")}
className="dnd-menu-item dnd-make-unavailable">
<i className="status status-dnd"></i>
<span>{__("display_name_dnd_status")}</span>
</li>
</ul>
</div>
);
}
@ -272,6 +270,10 @@ loop.panel = (function(_, mozL10n) {
callUrl: React.PropTypes.string
},
handleSignUpLinkClick: function() {
navigator.mozLoop.logInToFxA();
},
render: function() {
return (
<div>
@ -279,7 +281,12 @@ loop.panel = (function(_, mozL10n) {
notifier={this.props.notifier}
callUrl={this.props.callUrl} />
<ToSView />
<AvailabilityDropdown />
<div className="footer">
<AvailabilityDropdown />
<a className="signin-link" href="#" onClick={this.handleSignUpLinkClick}>
{__("panel_footer_signin_or_signup_link")}
</a>
</div>
</div>
);
}

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

@ -138,6 +138,17 @@
border: 1px solid #888;
}
/* Sign in/up link */
.signin-link {
display: none; /* XXX This should be removed as soon bugs 1047144 & 979845 land */
line-height: 100%;
font-size: .9em;
text-decoration: none;
color: #888;
margin-top: 16px;
}
/* Terms of Service */
.terms-service {
@ -157,14 +168,17 @@
/* Footer */
.footer {
display: flex;
flex-direction: row;
flex-wrap: nowrap;
justify-content: space-between;
align-content: stretch;
align-items: flex-start;
font-size: 1em;
border-top: 1px solid #D1D1D1;
background: #EAEAEA;
color: #7F7F7F;
display: flex;
align-items: center;
margin-top: 14px;
flex-direction: row;
padding: 14px;
margin-top: 14px;
}

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

@ -34,8 +34,12 @@ loop.shared.models = (function() {
// other peer ("audio" or "audio-video")
selectedCallType: undefined, // The selected type for the call that was
// initiated ("audio" or "audio-video")
callToken: undefined // Incoming call token.
callToken: undefined, // Incoming call token.
// Used for blocking a call url
subscribedStream: false, // Used to indicate that a stream has been
// subscribed to
publishedStream: false // Used to indicate that a stream has been
// published
},
/**
@ -219,6 +223,39 @@ loop.shared.models = (function() {
return undefined;
},
/**
* Publishes a local stream.
*
* @param {Publisher} publisher The publisher object to publish
* to the session.
*/
publish: function(publisher) {
this.session.publish(publisher);
this.set("publishedStream", true);
},
/**
* Subscribes to a remote stream.
*
* @param {Stream} stream The remote stream to subscribe to.
* @param {DOMElement} element The element to display the stream in.
* @param {Object} config The display properties to set on the stream as
* documented in:
* https://tokbox.com/opentok/libraries/client/js/reference/Session.html#subscribe
*/
subscribe: function(stream, element, config) {
this.session.subscribe(stream, element, config);
this.set("subscribedStream", true);
},
/**
* Returns true if a stream has been published and a stream has been
* subscribed to.
*/
streamsConnected: function() {
return this.get("publishedStream") && this.get("subscribedStream");
},
/**
* Handle a loop-server error, which has an optional `errno` property which
* is server error identifier.

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

@ -278,13 +278,7 @@ loop.shared.views = (function(_, OT, l10n) {
*/
_streamCreated: function(event) {
var incoming = this.getDOMNode().querySelector(".remote");
event.streams.forEach(function(stream) {
if (stream.connection.connectionId !==
this.props.model.session.connection.connectionId) {
this.props.model.session.subscribe(stream, incoming,
this.publisherConfig);
}
}, this);
this.props.model.subscribe(event.stream, incoming, this.publisherConfig);
},
/**
@ -321,7 +315,7 @@ loop.shared.views = (function(_, OT, l10n) {
});
}.bind(this));
this.props.model.session.publish(this.publisher);
this.props.model.publish(this.publisher);
},
/**

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

@ -278,13 +278,7 @@ loop.shared.views = (function(_, OT, l10n) {
*/
_streamCreated: function(event) {
var incoming = this.getDOMNode().querySelector(".remote");
event.streams.forEach(function(stream) {
if (stream.connection.connectionId !==
this.props.model.session.connection.connectionId) {
this.props.model.session.subscribe(stream, incoming,
this.publisherConfig);
}
}, this);
this.props.model.subscribe(event.stream, incoming, this.publisherConfig);
},
/**
@ -321,7 +315,7 @@ loop.shared.views = (function(_, OT, l10n) {
});
}.bind(this));
this.props.model.session.publish(this.publisher);
this.props.model.publish(this.publisher);
},
/**

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

@ -127,6 +127,27 @@ loop.CallConnectionWebSocket = (function() {
});
},
/**
* Notifies the server that the user has accepted the call.
*/
accept: function() {
this._send({
messageType: "action",
event: "accept"
});
},
/**
* Notifies the server that the outgoing media is up, and the
* incoming media is being received.
*/
mediaUp: function() {
this._send({
messageType: "action",
event: "media-up"
});
},
/**
* Sends data on the websocket.
*

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

@ -410,6 +410,18 @@ loop.webapp = (function($, _, OT, webL10n) {
this._websocket.on("progress", this._handleWebSocketProgress, this);
},
/**
* Checks if the streams have been connected, and notifies the
* websocket that the media is now connected.
*/
_checkConnected: function() {
// Check we've had both local and remote streams connected before
// sending the media up message.
if (this._conversation.streamsConnected()) {
this._websocket.mediaUp();
}
},
/**
* Used to receive websocket progress and to determine how to handle
* it if appropraite.
@ -495,6 +507,8 @@ loop.webapp = (function($, _, OT, webL10n) {
client: this._client
});
this._conversation.once("call:outgoing:setup", this.setupOutgoingCall, this);
this._conversation.once("change:publishedStream", this._checkConnected, this);
this._conversation.once("change:subscribedStream", this._checkConnected, this);
this.loadReactComponent(startView);
},

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

@ -410,6 +410,18 @@ loop.webapp = (function($, _, OT, webL10n) {
this._websocket.on("progress", this._handleWebSocketProgress, this);
},
/**
* Checks if the streams have been connected, and notifies the
* websocket that the media is now connected.
*/
_checkConnected: function() {
// Check we've had both local and remote streams connected before
// sending the media up message.
if (this._conversation.streamsConnected()) {
this._websocket.mediaUp();
}
},
/**
* Used to receive websocket progress and to determine how to handle
* it if appropraite.
@ -495,6 +507,8 @@ loop.webapp = (function($, _, OT, webL10n) {
client: this._client
});
this._conversation.once("call:outgoing:setup", this.setupOutgoingCall, this);
this._conversation.once("change:publishedStream", this._checkConnected, this);
this._conversation.once("change:subscribedStream", this._checkConnected, this);
this.loadReactComponent(startView);
},

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

@ -37,6 +37,7 @@ describe("loop.conversation", function() {
},
setLoopCharPref: sandbox.stub(),
getLoopCharPref: sandbox.stub(),
getLoopBoolPref: sandbox.stub(),
startAlerting: function() {},
stopAlerting: function() {},
ensureRegistered: function() {},
@ -311,14 +312,35 @@ describe("loop.conversation", function() {
});
describe("#accept", function() {
beforeEach(function() {
conversation.setIncomingSessionData({
sessionId: "sessionId",
sessionToken: "sessionToken",
apiKey: "apiKey",
callType: "callType",
callId: "Hello",
progressURL: "http://progress.example.com",
websocketToken: 123
});
router._setupWebSocketAndCallView();
sandbox.stub(router._websocket, "accept");
sandbox.stub(navigator.mozLoop, "stopAlerting");
});
it("should initiate the conversation", function() {
router.accept();
sinon.assert.calledOnce(conversation.incoming);
});
it("should notify the websocket of the user acceptance", function() {
router.accept();
sinon.assert.calledOnce(router._websocket.accept);
});
it("should stop alerting", function() {
sandbox.stub(navigator.mozLoop, "stopAlerting");
router.accept();
sinon.assert.calledOnce(navigator.mozLoop.stopAlerting);
@ -531,6 +553,49 @@ describe("loop.conversation", function() {
sinon.assert.calledOnce(router.navigate);
sinon.assert.calledWith(router.navigate, "call/feedback");
});
describe("Published and Subscribed Streams", function() {
beforeEach(function() {
router._websocket = {
mediaUp: sinon.spy()
};
router.incoming("fakeVersion");
});
describe("publishStream", function() {
it("should not notify the websocket if only one stream is up",
function() {
conversation.set("publishedStream", true);
sinon.assert.notCalled(router._websocket.mediaUp);
});
it("should notify the websocket that media is up if both streams" +
"are connected", function() {
conversation.set("subscribedStream", true);
conversation.set("publishedStream", true);
sinon.assert.calledOnce(router._websocket.mediaUp);
});
});
describe("subscribedStream", function() {
it("should not notify the websocket if only one stream is up",
function() {
conversation.set("subscribedStream", true);
sinon.assert.notCalled(router._websocket.mediaUp);
});
it("should notify the websocket that media is up if both streams" +
"are connected", function() {
conversation.set("publishedStream", true);
conversation.set("subscribedStream", true);
sinon.assert.calledOnce(router._websocket.mediaUp);
});
});
});
});
});

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

@ -217,12 +217,23 @@ describe("loop.panel", function() {
}));
});
describe("FxA sign in/up link", function() {
it("should trigger the FxA sign in/up process when clicking the link",
function() {
navigator.mozLoop.logInToFxA = sandbox.stub();
TestUtils.Simulate.click(
view.getDOMNode().querySelector(".signin-link"));
sinon.assert.calledOnce(navigator.mozLoop.logInToFxA);
});
});
describe("#render", function() {
it("should render a ToSView", function() {
TestUtils.findRenderedComponentWithType(view, loop.panel.ToSView);
});
});
});
describe("loop.panel.CallUrlResult", function() {

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

@ -357,15 +357,14 @@ describe("loop.shared.views", function() {
sinon.assert.calledOnce(fakeSDK.initPublisher);
});
it("should publish remote streams on session:stream-created",
it("should publish remote stream on session:stream-created",
function() {
var s1 = {connection: {connectionId: 42}};
var s2 = {connection: {connectionId: 43}};
model.trigger("session:stream-created", {streams: [s1, s2]});
model.trigger("session:stream-created", {stream: s1});
sinon.assert.calledOnce(fakeSession.subscribe);
sinon.assert.calledWith(fakeSession.subscribe, s2);
sinon.assert.calledWith(fakeSession.subscribe, s1);
});
it("should unpublish local stream on session:ended", function() {

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

@ -148,6 +148,34 @@ describe("loop.CallConnectionWebSocket", function() {
});
});
describe("#accept", function() {
it("should send an accept message to the server", function() {
callWebSocket.promiseConnect();
callWebSocket.accept();
sinon.assert.calledOnce(dummySocket.send);
sinon.assert.calledWithExactly(dummySocket.send, JSON.stringify({
messageType: "action",
event: "accept"
}));
});
});
describe("#mediaUp", function() {
it("should send a media-up message to the server", function() {
callWebSocket.promiseConnect();
callWebSocket.mediaUp();
sinon.assert.calledOnce(dummySocket.send);
sinon.assert.calledWithExactly(dummySocket.send, JSON.stringify({
messageType: "action",
event: "media-up"
}));
});
});
describe("Events", function() {
beforeEach(function() {
sandbox.stub(callWebSocket, "trigger");

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

@ -415,6 +415,49 @@ describe("loop.webapp", function() {
sinon.assert.calledWithMatch(router.navigate, "call/fakeToken");
});
describe("Published and Subscribed Streams", function() {
beforeEach(function() {
router._websocket = {
mediaUp: sinon.spy()
};
router.initiate();
});
describe("publishStream", function() {
it("should not notify the websocket if only one stream is up",
function() {
conversation.set("publishedStream", true);
sinon.assert.notCalled(router._websocket.mediaUp);
});
it("should notify the websocket that media is up if both streams" +
"are connected", function() {
conversation.set("subscribedStream", true);
conversation.set("publishedStream", true);
sinon.assert.calledOnce(router._websocket.mediaUp);
});
});
describe("subscribedStream", function() {
it("should not notify the websocket if only one stream is up",
function() {
conversation.set("subscribedStream", true);
sinon.assert.notCalled(router._websocket.mediaUp);
});
it("should notify the websocket that media is up if both streams" +
"are connected", function() {
conversation.set("publishedStream", true);
conversation.set("subscribedStream", true);
sinon.assert.calledOnce(router._websocket.mediaUp);
});
});
});
describe("#setupOutgoingCall", function() {
beforeEach(function() {
router.initiate();

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

@ -166,7 +166,9 @@ let CommandUtils = {
command.state.onChange(target, onChange);
onChange("", { target: target });
document.defaultView.addEventListener("unload", () => {
command.state.offChange(target, onChange);
if (command.state.offChange) {
command.state.offChange(target, onChange);
}
}, false);
}

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

@ -68,3 +68,5 @@ share_email_body2=Please click this link to call me:\r\n\r\n{{callUrl}}
share_button=Email
copy_url_button=Copy
copied_url_button=Copied!
panel_footer_signin_or_signup_link=Sign In or Sign Up

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

@ -1792,6 +1792,21 @@ toolbarbutton[sdk-button="true"][cui-areatype="toolbar"] > .toolbarbutton-icon {
}
}
@media (-moz-mac-yosemite-theme) {
.searchbar-textbox,
#urlbar {
border-color: #fff;
border-radius: 3px;
box-shadow: 0 1px 0 0 #aeaeae, 1px 2px 0 0 #d8d8d8;
background-image: none;
}
.searchbar-textbox:-moz-window-inactive,
#urlbar:-moz-window-inactive {
box-shadow: none;
border-color: #dbdbdb;
}
}
#urlbar[focused="true"],
.searchbar-textbox[focused="true"] {
border-color: -moz-mac-focusring;

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

@ -19,9 +19,9 @@ ifndef JAVA_BOOTCLASSPATH
JAVA_BOOTCLASSPATH = $(ANDROID_SDK)/android.jar
endif
# For Android, we default to 1.5
# For Android, we default to 1.7
ifndef JAVA_VERSION
JAVA_VERSION = 1.5
JAVA_VERSION = 1.7
endif
JAVAC_FLAGS = \

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

@ -5622,6 +5622,24 @@ if test -n "${JAVA_BIN_PATH}" -o \
if test -z "$KEYTOOL" -o "$KEYTOOL" = ":"; then
AC_MSG_ERROR([The program keytool was not found. Set \$JAVA_HOME to your Java SDK directory or use --with-java-bin-path={java-bin-dir}])
fi
AC_MSG_CHECKING([for minimum required javac version = 1.7])
dnl Javac spits out something like `javac 1.7.0`. This line cuts off the 'javac'
_javac_version=$($JAVAC -version 2>&1 | cut -d ' ' -f 2)
dnl Here, we extract the major (1) and minor (7) version numbers from the
dnl acquired version string.
_javac_major_version=$(echo $_javac_version | cut -d '.' -f 1)
_javac_minor_version=$(echo $_javac_version | cut -d '.' -f 2)
AC_MSG_RESULT([$_javac_version])
dnl Fail if we have a version other than 1.7.X
if test "$_javac_major_version" -ne "1" -o \
\( "$_javac_minor_version" -ne "7" \); then
AC_MSG_ERROR([javac 1.7 is required.])
fi
fi
dnl ========================================================

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

@ -138,6 +138,10 @@ struct Paths {
* system.
*/
nsString macLocalApplicationsDir;
/**
* The user's trash directory.
*/
nsString macTrashDir;
#endif // defined(XP_MACOSX)
Paths()
@ -158,6 +162,7 @@ struct Paths {
#if defined(XP_MACOSX)
macUserLibDir.SetIsVoid(true);
macLocalApplicationsDir.SetIsVoid(true);
macTrashDir.SetIsVoid(true);
#endif // defined(XP_MACOSX)
}
};
@ -306,6 +311,7 @@ nsresult InitOSFileConstants()
#if defined(XP_MACOSX)
GetPathToSpecialDir(NS_MAC_USER_LIB_DIR, paths->macUserLibDir);
GetPathToSpecialDir(NS_OSX_LOCAL_APPLICATIONS_DIR, paths->macLocalApplicationsDir);
GetPathToSpecialDir(NS_MAC_TRASH_DIR, paths->macTrashDir);
#endif // defined(XP_MACOSX)
gPaths = paths.forget();
@ -980,6 +986,10 @@ bool DefineOSFileConstants(JSContext *cx, JS::Handle<JSObject*> global)
if (!SetStringProperty(cx, objPath, "macLocalApplicationsDir", gPaths->macLocalApplicationsDir)) {
return false;
}
if (!SetStringProperty(cx, objPath, "macTrashDir", gPaths->macTrashDir)) {
return false;
}
#endif // defined(XP_MACOSX)
// sqlite3 is linked from different places depending on the platform

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

@ -494,6 +494,10 @@ Tester.prototype = {
promise = ContentSearch.destroy();
}
// Simulate memory pressure so that we're forced to free more resources
// and thus get rid of more false leaks like already terminated workers.
Services.obs.notifyObservers(null, "memory-pressure", "heap-minimize");
// Schedule GC and CC runs before finishing in order to detect
// DOM windows leaked by our tests or the tested code. Note that we
// use a shrinking GC so that the JS engine will discard JIT code and

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

@ -73,6 +73,7 @@ add_task(function* test_desktop_paths() {
compare_paths(OS.Constants.Path.macUserLibDir, "ULibDir");
compare_paths(OS.Constants.Path.macLocalApplicationsDir, "LocApp");
compare_paths(OS.Constants.Path.macTrashDir, "Trsh");
});
// Open libxul