зеркало из https://github.com/mozilla/gecko-dev.git
Merge m-c to mozilla-inbound
This commit is contained in:
Коммит
fbbaf7b2d4
|
@ -28,7 +28,6 @@ html xul|scrollbar {
|
|||
background-image: none !important;
|
||||
border: 0px solid transparent !important;
|
||||
pointer-events: none;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
xul|scrollbar[orient="vertical"] {
|
||||
|
@ -56,7 +55,6 @@ xul|scrollbar[orient="horizontal"] xul|thumb {
|
|||
xul|scrollbar:not([active="true"]),
|
||||
xul|scrollbar[disabled] {
|
||||
opacity: 0;
|
||||
transition: opacity 1s ease;
|
||||
}
|
||||
|
||||
xul|scrollbarbutton {
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
<project name="platform_build" path="build" remote="b2g" revision="3a2947df41a480de1457a6dcdbf46ad0af70d8e0">
|
||||
<copyfile dest="Makefile" src="core/root.mk"/>
|
||||
</project>
|
||||
<project name="gaia" path="gaia" remote="mozillaorg" revision="abef62c0623e5504a97b4fd411e879a67b285b52"/>
|
||||
<project name="gaia" path="gaia" remote="mozillaorg" revision="1daf2dadcd0d554c733661a4c0be1b82001e9da0"/>
|
||||
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
|
||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="cc1f362ce43dce92ac786187ff4abf39060094bd"/>
|
||||
<project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>
|
||||
|
|
|
@ -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="abef62c0623e5504a97b4fd411e879a67b285b52"/>
|
||||
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="1daf2dadcd0d554c733661a4c0be1b82001e9da0"/>
|
||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="cc1f362ce43dce92ac786187ff4abf39060094bd"/>
|
||||
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
|
||||
<project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="cd88d860656c31c7da7bb310d6a160d0011b0961"/>
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
</project>
|
||||
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
|
||||
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
|
||||
<project name="gaia" path="gaia" remote="mozillaorg" revision="abef62c0623e5504a97b4fd411e879a67b285b52"/>
|
||||
<project name="gaia" path="gaia" remote="mozillaorg" revision="1daf2dadcd0d554c733661a4c0be1b82001e9da0"/>
|
||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="cc1f362ce43dce92ac786187ff4abf39060094bd"/>
|
||||
<project name="moztt" path="external/moztt" remote="b2g" revision="562d357b72279a9e35d4af5aeecc8e1ffa2f44f1"/>
|
||||
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="9f6b7471c881ee689183d681658cf2ba3dfc5610"/>
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
<project name="platform_build" path="build" remote="b2g" revision="3a2947df41a480de1457a6dcdbf46ad0af70d8e0">
|
||||
<copyfile dest="Makefile" src="core/root.mk"/>
|
||||
</project>
|
||||
<project name="gaia" path="gaia" remote="mozillaorg" revision="abef62c0623e5504a97b4fd411e879a67b285b52"/>
|
||||
<project name="gaia" path="gaia" remote="mozillaorg" revision="1daf2dadcd0d554c733661a4c0be1b82001e9da0"/>
|
||||
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
|
||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="cc1f362ce43dce92ac786187ff4abf39060094bd"/>
|
||||
<project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>
|
||||
|
|
|
@ -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="abef62c0623e5504a97b4fd411e879a67b285b52"/>
|
||||
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="1daf2dadcd0d554c733661a4c0be1b82001e9da0"/>
|
||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="cc1f362ce43dce92ac786187ff4abf39060094bd"/>
|
||||
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
|
||||
<project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="cd88d860656c31c7da7bb310d6a160d0011b0961"/>
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
<project name="platform_build" path="build" remote="b2g" revision="3a2947df41a480de1457a6dcdbf46ad0af70d8e0">
|
||||
<copyfile dest="Makefile" src="core/root.mk"/>
|
||||
</project>
|
||||
<project name="gaia" path="gaia" remote="mozillaorg" revision="abef62c0623e5504a97b4fd411e879a67b285b52"/>
|
||||
<project name="gaia" path="gaia" remote="mozillaorg" revision="1daf2dadcd0d554c733661a4c0be1b82001e9da0"/>
|
||||
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
|
||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="cc1f362ce43dce92ac786187ff4abf39060094bd"/>
|
||||
<project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
</project>
|
||||
<project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>
|
||||
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
|
||||
<project name="gaia" path="gaia" remote="mozillaorg" revision="abef62c0623e5504a97b4fd411e879a67b285b52"/>
|
||||
<project name="gaia" path="gaia" remote="mozillaorg" revision="1daf2dadcd0d554c733661a4c0be1b82001e9da0"/>
|
||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="cc1f362ce43dce92ac786187ff4abf39060094bd"/>
|
||||
<project name="moztt" path="external/moztt" remote="b2g" revision="562d357b72279a9e35d4af5aeecc8e1ffa2f44f1"/>
|
||||
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="9f6b7471c881ee689183d681658cf2ba3dfc5610"/>
|
||||
|
|
|
@ -4,6 +4,6 @@
|
|||
"remote": "",
|
||||
"branch": ""
|
||||
},
|
||||
"revision": "63436aa17e7fa3ad521fdeffdc22b81c36e5d69b",
|
||||
"revision": "934b8c3014a3e20dd5d90ecf95f4b6b704dddb1e",
|
||||
"repo_path": "/integration/gaia-central"
|
||||
}
|
||||
|
|
|
@ -17,7 +17,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="abef62c0623e5504a97b4fd411e879a67b285b52"/>
|
||||
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="1daf2dadcd0d554c733661a4c0be1b82001e9da0"/>
|
||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="cc1f362ce43dce92ac786187ff4abf39060094bd"/>
|
||||
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
|
||||
<project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>
|
||||
|
|
|
@ -15,7 +15,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="abef62c0623e5504a97b4fd411e879a67b285b52"/>
|
||||
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="1daf2dadcd0d554c733661a4c0be1b82001e9da0"/>
|
||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="cc1f362ce43dce92ac786187ff4abf39060094bd"/>
|
||||
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
|
||||
<project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
</project>
|
||||
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
|
||||
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
|
||||
<project name="gaia" path="gaia" remote="mozillaorg" revision="abef62c0623e5504a97b4fd411e879a67b285b52"/>
|
||||
<project name="gaia" path="gaia" remote="mozillaorg" revision="1daf2dadcd0d554c733661a4c0be1b82001e9da0"/>
|
||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="cc1f362ce43dce92ac786187ff4abf39060094bd"/>
|
||||
<project name="moztt" path="external/moztt" remote="b2g" revision="562d357b72279a9e35d4af5aeecc8e1ffa2f44f1"/>
|
||||
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="9f6b7471c881ee689183d681658cf2ba3dfc5610"/>
|
||||
|
|
|
@ -17,7 +17,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="abef62c0623e5504a97b4fd411e879a67b285b52"/>
|
||||
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="1daf2dadcd0d554c733661a4c0be1b82001e9da0"/>
|
||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="cc1f362ce43dce92ac786187ff4abf39060094bd"/>
|
||||
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
|
||||
<project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>
|
||||
|
|
|
@ -782,7 +782,15 @@ function gKeywordURIFixup({ target: browser, data: fixupInfo }) {
|
|||
notification.persistence = 1;
|
||||
};
|
||||
|
||||
gDNSService.asyncResolve(hostName, 0, onLookupComplete, Services.tm.mainThread);
|
||||
try {
|
||||
gDNSService.asyncResolve(hostName, 0, onLookupComplete, Services.tm.mainThread);
|
||||
} catch (ex) {
|
||||
// Do nothing if the URL is invalid (we don't want to show a notification in that case).
|
||||
if (ex.result != Cr.NS_ERROR_UNKNOWN_HOST) {
|
||||
// ... otherwise, report:
|
||||
Cu.reportError(ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Called when a docshell has attempted to load a page in an incorrect process.
|
||||
|
@ -6503,7 +6511,7 @@ var gIdentityHandler = {
|
|||
|
||||
// Chrome URIs however get special treatment. Some chrome URIs are
|
||||
// whitelisted to provide a positive security signal to the user.
|
||||
let whitelist = /^about:(accounts|addons|app-manager|config|crashes|customizing|healthreport|home|newaddon|permissions|preferences|privatebrowsing|sessionrestore|support|welcomeback)/i;
|
||||
let whitelist = /^about:(accounts|addons|app-manager|config|crashes|customizing|healthreport|home|license|newaddon|permissions|preferences|privatebrowsing|rights|sessionrestore|support|welcomeback)/i;
|
||||
let isChromeUI = uri.schemeIs("about") && whitelist.test(uri.spec);
|
||||
if (isChromeUI) {
|
||||
this.setMode(this.IDENTITY_MODE_CHROMEUI);
|
||||
|
|
|
@ -57,6 +57,7 @@ support-files =
|
|||
file_dom_notifications.html
|
||||
file_double_close_tab.html
|
||||
file_favicon_change.html
|
||||
file_favicon_change_not_in_document.html
|
||||
file_fullscreen-window-open.html
|
||||
get_user_media.html
|
||||
head.js
|
||||
|
@ -324,6 +325,8 @@ skip-if = e10s
|
|||
skip-if = true # browser_drag.js is disabled, as it needs to be updated for the new behavior from bug 320638.
|
||||
[browser_favicon_change.js]
|
||||
skip-if = e10s
|
||||
[browser_favicon_change_not_in_document.js]
|
||||
skip-if = e10s
|
||||
[browser_findbarClose.js]
|
||||
skip-if = e10s # Bug ?????? - test directly manipulates content (tries to grab an iframe directly from content)
|
||||
[browser_fullscreen-window-open.js]
|
||||
|
|
|
@ -0,0 +1,34 @@
|
|||
"use strict";
|
||||
|
||||
const TEST_URL = "http://mochi.test:8888/browser/browser/base/content/test/general/file_favicon_change_not_in_document.html"
|
||||
|
||||
add_task(function*() {
|
||||
let extraTab = gBrowser.selectedTab = gBrowser.addTab();
|
||||
let tabLoaded = promiseTabLoaded(extraTab);
|
||||
extraTab.linkedBrowser.loadURI(TEST_URL);
|
||||
let expectedFavicon = "http://example.org/one-icon";
|
||||
let haveChanged = new Promise.defer();
|
||||
let observer = new MutationObserver(function(mutations) {
|
||||
for (let mut of mutations) {
|
||||
if (mut.attributeName != "image") {
|
||||
continue;
|
||||
}
|
||||
let imageVal = extraTab.getAttribute("image").replace(/#.*$/, "");
|
||||
if (!imageVal) {
|
||||
// The value gets removed because it doesn't load.
|
||||
continue;
|
||||
}
|
||||
is(imageVal, expectedFavicon, "Favicon image should correspond to expected image.");
|
||||
haveChanged.resolve();
|
||||
}
|
||||
});
|
||||
observer.observe(extraTab, {attributes: true});
|
||||
yield tabLoaded;
|
||||
expectedFavicon = "http://example.org/yet-another-icon";
|
||||
haveChanged = new Promise.defer();
|
||||
yield haveChanged.promise;
|
||||
observer.disconnect();
|
||||
gBrowser.removeTab(extraTab);
|
||||
});
|
||||
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
<!DOCTYPE html>
|
||||
<html><head>
|
||||
<meta http-equiv="content-type" content="text/html; charset=utf-8">
|
||||
<link rel="icon" href="http://example.org/one-icon" type="image/ico" id="i">
|
||||
</head>
|
||||
<body onload="onload()">
|
||||
<script>
|
||||
function onload() {
|
||||
var ico = document.createElement("link");
|
||||
ico.setAttribute("rel", "icon");
|
||||
ico.setAttribute("type", "image/ico");
|
||||
ico.setAttribute("href", "http://example.org/other-icon");
|
||||
setTimeout(function() {
|
||||
ico.setAttribute("href", "http://example.org/yet-another-icon");
|
||||
document.getElementById("i").remove();
|
||||
document.head.appendChild(ico);
|
||||
}, 1000);
|
||||
}
|
||||
</script>
|
||||
</body></html>
|
||||
|
|
@ -601,15 +601,18 @@ function injectLoopAPI(targetWindow) {
|
|||
/**
|
||||
* Composes an email via the external protocol service.
|
||||
*
|
||||
* @param {String} subject Subject of the email to send
|
||||
* @param {String} body Body message of the email to send
|
||||
* @param {String} subject Subject of the email to send
|
||||
* @param {String} body Body message of the email to send
|
||||
* @param {String} recipient Recipient email address (optional)
|
||||
*/
|
||||
composeEmail: {
|
||||
enumerable: true,
|
||||
writable: true,
|
||||
value: function(subject, body) {
|
||||
let mailtoURL = "mailto:?subject=" + encodeURIComponent(subject) + "&" +
|
||||
"body=" + encodeURIComponent(body);
|
||||
value: function(subject, body, recipient) {
|
||||
recipient = recipient || "";
|
||||
let mailtoURL = "mailto:" + encodeURIComponent(recipient) +
|
||||
"?subject=" + encodeURIComponent(subject) +
|
||||
"&body=" + encodeURIComponent(body);
|
||||
extProtocolSvc.loadURI(CommonUtils.makeURI(mailtoURL));
|
||||
}
|
||||
},
|
||||
|
|
|
@ -12,8 +12,20 @@ loop.conversationViews = (function(mozL10n) {
|
|||
var CALL_STATES = loop.store.CALL_STATES;
|
||||
var CALL_TYPES = loop.shared.utils.CALL_TYPES;
|
||||
var sharedActions = loop.shared.actions;
|
||||
var sharedUtils = loop.shared.utils;
|
||||
var sharedViews = loop.shared.views;
|
||||
|
||||
// This duplicates a similar function in contacts.jsx that isn't used in the
|
||||
// conversation window. If we get too many of these, we might want to consider
|
||||
// finding a logical place for them to be shared.
|
||||
function _getPreferredEmail(contact) {
|
||||
// A contact may not contain email addresses, but only a phone number.
|
||||
if (!contact.email || contact.email.length === 0) {
|
||||
return { value: "" };
|
||||
}
|
||||
return contact.email.find(e => e.pref) || contact.email[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays information about the call
|
||||
* Caller avatar, name & conversation creation date
|
||||
|
@ -93,17 +105,6 @@ loop.conversationViews = (function(mozL10n) {
|
|||
contact: React.PropTypes.object
|
||||
},
|
||||
|
||||
// This duplicates a similar function in contacts.jsx that isn't used in the
|
||||
// conversation window. If we get too many of these, we might want to consider
|
||||
// finding a logical place for them to be shared.
|
||||
_getPreferredEmail: function(contact) {
|
||||
// A contact may not contain email addresses, but only a phone number.
|
||||
if (!contact.email || contact.email.length == 0) {
|
||||
return { value: "" };
|
||||
}
|
||||
return contact.email.find(e => e.pref) || contact.email[0];
|
||||
},
|
||||
|
||||
render: function() {
|
||||
var contactName;
|
||||
|
||||
|
@ -111,7 +112,7 @@ loop.conversationViews = (function(mozL10n) {
|
|||
this.props.contact.name[0]) {
|
||||
contactName = this.props.contact.name[0];
|
||||
} else {
|
||||
contactName = this._getPreferredEmail(this.props.contact).value;
|
||||
contactName = _getPreferredEmail(this.props.contact).value;
|
||||
}
|
||||
|
||||
document.title = contactName;
|
||||
|
@ -170,12 +171,10 @@ loop.conversationViews = (function(mozL10n) {
|
|||
React.DOM.p({className: "btn-label"}, pendingStateString),
|
||||
|
||||
React.DOM.div({className: "btn-group call-action-group"},
|
||||
React.DOM.div({className: "fx-embedded-call-button-spacer"}),
|
||||
React.DOM.button({className: btnCancelStyles,
|
||||
onClick: this.cancelCall},
|
||||
mozL10n.get("initiate_call_cancel_button")
|
||||
),
|
||||
React.DOM.div({className: "fx-embedded-call-button-spacer"})
|
||||
React.DOM.button({className: btnCancelStyles,
|
||||
onClick: this.cancelCall},
|
||||
mozL10n.get("initiate_call_cancel_button")
|
||||
)
|
||||
)
|
||||
|
||||
)
|
||||
|
@ -187,8 +186,54 @@ loop.conversationViews = (function(mozL10n) {
|
|||
* Call failed view. Displayed when a call fails.
|
||||
*/
|
||||
var CallFailedView = React.createClass({displayName: 'CallFailedView',
|
||||
mixins: [Backbone.Events],
|
||||
|
||||
propTypes: {
|
||||
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired
|
||||
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
|
||||
store: React.PropTypes.instanceOf(
|
||||
loop.store.ConversationStore).isRequired,
|
||||
contact: React.PropTypes.object.isRequired,
|
||||
// This is used by the UI showcase.
|
||||
emailLinkError: React.PropTypes.bool,
|
||||
},
|
||||
|
||||
getInitialState: function() {
|
||||
return {
|
||||
emailLinkError: this.props.emailLinkError,
|
||||
emailLinkButtonDisabled: false
|
||||
};
|
||||
},
|
||||
|
||||
componentDidMount: function() {
|
||||
this.listenTo(this.props.store, "change:emailLink",
|
||||
this._onEmailLinkReceived);
|
||||
this.listenTo(this.props.store, "error:emailLink",
|
||||
this._onEmailLinkError);
|
||||
},
|
||||
|
||||
componentWillUnmount: function() {
|
||||
this.stopListening(this.props.store);
|
||||
},
|
||||
|
||||
_onEmailLinkReceived: function() {
|
||||
var emailLink = this.props.store.get("emailLink");
|
||||
var contactEmail = _getPreferredEmail(this.props.contact).value;
|
||||
sharedUtils.composeCallUrlEmail(emailLink, contactEmail);
|
||||
window.close();
|
||||
},
|
||||
|
||||
_onEmailLinkError: function() {
|
||||
this.setState({
|
||||
emailLinkError: true,
|
||||
emailLinkButtonDisabled: false
|
||||
});
|
||||
},
|
||||
|
||||
_renderError: function() {
|
||||
if (!this.state.emailLinkError) {
|
||||
return;
|
||||
}
|
||||
return React.DOM.p({className: "error"}, mozL10n.get("unable_retrieve_url"));
|
||||
},
|
||||
|
||||
retryCall: function() {
|
||||
|
@ -199,25 +244,38 @@ loop.conversationViews = (function(mozL10n) {
|
|||
this.props.dispatcher.dispatch(new sharedActions.CancelCall());
|
||||
},
|
||||
|
||||
emailLink: function() {
|
||||
this.setState({
|
||||
emailLinkError: false,
|
||||
emailLinkButtonDisabled: true
|
||||
});
|
||||
|
||||
this.props.dispatcher.dispatch(new sharedActions.FetchEmailLink());
|
||||
},
|
||||
|
||||
render: function() {
|
||||
return (
|
||||
React.DOM.div({className: "call-window"},
|
||||
React.DOM.h2(null, mozL10n.get("generic_failure_title")),
|
||||
|
||||
React.DOM.p({className: "btn-label"}, mozL10n.get("generic_failure_no_reason2")),
|
||||
React.DOM.p({className: "btn-label"}, mozL10n.get("generic_failure_with_reason2")),
|
||||
|
||||
this._renderError(),
|
||||
|
||||
React.DOM.div({className: "btn-group call-action-group"},
|
||||
React.DOM.div({className: "fx-embedded-call-button-spacer"}),
|
||||
React.DOM.button({className: "btn btn-accept btn-retry",
|
||||
onClick: this.retryCall},
|
||||
mozL10n.get("retry_call_button")
|
||||
),
|
||||
React.DOM.div({className: "fx-embedded-call-button-spacer"}),
|
||||
React.DOM.button({className: "btn btn-cancel",
|
||||
onClick: this.cancelCall},
|
||||
mozL10n.get("cancel_button")
|
||||
),
|
||||
React.DOM.div({className: "fx-embedded-call-button-spacer"})
|
||||
React.DOM.button({className: "btn btn-cancel",
|
||||
onClick: this.cancelCall},
|
||||
mozL10n.get("cancel_button")
|
||||
),
|
||||
React.DOM.button({className: "btn btn-info btn-retry",
|
||||
onClick: this.retryCall},
|
||||
mozL10n.get("retry_call_button")
|
||||
),
|
||||
React.DOM.button({className: "btn btn-info btn-email",
|
||||
onClick: this.emailLink,
|
||||
disabled: this.state.emailLinkButtonDisabled},
|
||||
mozL10n.get("share_button2")
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
|
@ -425,7 +483,9 @@ loop.conversationViews = (function(mozL10n) {
|
|||
}
|
||||
case CALL_STATES.TERMINATED: {
|
||||
return (CallFailedView({
|
||||
dispatcher: this.props.dispatcher}
|
||||
dispatcher: this.props.dispatcher,
|
||||
store: this.props.store,
|
||||
contact: this.state.contact}
|
||||
));
|
||||
}
|
||||
case CALL_STATES.ONGOING: {
|
||||
|
@ -445,7 +505,7 @@ loop.conversationViews = (function(mozL10n) {
|
|||
callState: this.state.callState,
|
||||
contact: this.state.contact,
|
||||
enableCancelButton: this._isCancellable()}
|
||||
))
|
||||
));
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
|
@ -12,8 +12,20 @@ loop.conversationViews = (function(mozL10n) {
|
|||
var CALL_STATES = loop.store.CALL_STATES;
|
||||
var CALL_TYPES = loop.shared.utils.CALL_TYPES;
|
||||
var sharedActions = loop.shared.actions;
|
||||
var sharedUtils = loop.shared.utils;
|
||||
var sharedViews = loop.shared.views;
|
||||
|
||||
// This duplicates a similar function in contacts.jsx that isn't used in the
|
||||
// conversation window. If we get too many of these, we might want to consider
|
||||
// finding a logical place for them to be shared.
|
||||
function _getPreferredEmail(contact) {
|
||||
// A contact may not contain email addresses, but only a phone number.
|
||||
if (!contact.email || contact.email.length === 0) {
|
||||
return { value: "" };
|
||||
}
|
||||
return contact.email.find(e => e.pref) || contact.email[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays information about the call
|
||||
* Caller avatar, name & conversation creation date
|
||||
|
@ -93,17 +105,6 @@ loop.conversationViews = (function(mozL10n) {
|
|||
contact: React.PropTypes.object
|
||||
},
|
||||
|
||||
// This duplicates a similar function in contacts.jsx that isn't used in the
|
||||
// conversation window. If we get too many of these, we might want to consider
|
||||
// finding a logical place for them to be shared.
|
||||
_getPreferredEmail: function(contact) {
|
||||
// A contact may not contain email addresses, but only a phone number.
|
||||
if (!contact.email || contact.email.length == 0) {
|
||||
return { value: "" };
|
||||
}
|
||||
return contact.email.find(e => e.pref) || contact.email[0];
|
||||
},
|
||||
|
||||
render: function() {
|
||||
var contactName;
|
||||
|
||||
|
@ -111,7 +112,7 @@ loop.conversationViews = (function(mozL10n) {
|
|||
this.props.contact.name[0]) {
|
||||
contactName = this.props.contact.name[0];
|
||||
} else {
|
||||
contactName = this._getPreferredEmail(this.props.contact).value;
|
||||
contactName = _getPreferredEmail(this.props.contact).value;
|
||||
}
|
||||
|
||||
document.title = contactName;
|
||||
|
@ -170,12 +171,10 @@ loop.conversationViews = (function(mozL10n) {
|
|||
<p className="btn-label">{pendingStateString}</p>
|
||||
|
||||
<div className="btn-group call-action-group">
|
||||
<div className="fx-embedded-call-button-spacer"></div>
|
||||
<button className={btnCancelStyles}
|
||||
onClick={this.cancelCall}>
|
||||
{mozL10n.get("initiate_call_cancel_button")}
|
||||
</button>
|
||||
<div className="fx-embedded-call-button-spacer"></div>
|
||||
<button className={btnCancelStyles}
|
||||
onClick={this.cancelCall}>
|
||||
{mozL10n.get("initiate_call_cancel_button")}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
</ConversationDetailView>
|
||||
|
@ -187,8 +186,54 @@ loop.conversationViews = (function(mozL10n) {
|
|||
* Call failed view. Displayed when a call fails.
|
||||
*/
|
||||
var CallFailedView = React.createClass({
|
||||
mixins: [Backbone.Events],
|
||||
|
||||
propTypes: {
|
||||
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired
|
||||
dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
|
||||
store: React.PropTypes.instanceOf(
|
||||
loop.store.ConversationStore).isRequired,
|
||||
contact: React.PropTypes.object.isRequired,
|
||||
// This is used by the UI showcase.
|
||||
emailLinkError: React.PropTypes.bool,
|
||||
},
|
||||
|
||||
getInitialState: function() {
|
||||
return {
|
||||
emailLinkError: this.props.emailLinkError,
|
||||
emailLinkButtonDisabled: false
|
||||
};
|
||||
},
|
||||
|
||||
componentDidMount: function() {
|
||||
this.listenTo(this.props.store, "change:emailLink",
|
||||
this._onEmailLinkReceived);
|
||||
this.listenTo(this.props.store, "error:emailLink",
|
||||
this._onEmailLinkError);
|
||||
},
|
||||
|
||||
componentWillUnmount: function() {
|
||||
this.stopListening(this.props.store);
|
||||
},
|
||||
|
||||
_onEmailLinkReceived: function() {
|
||||
var emailLink = this.props.store.get("emailLink");
|
||||
var contactEmail = _getPreferredEmail(this.props.contact).value;
|
||||
sharedUtils.composeCallUrlEmail(emailLink, contactEmail);
|
||||
window.close();
|
||||
},
|
||||
|
||||
_onEmailLinkError: function() {
|
||||
this.setState({
|
||||
emailLinkError: true,
|
||||
emailLinkButtonDisabled: false
|
||||
});
|
||||
},
|
||||
|
||||
_renderError: function() {
|
||||
if (!this.state.emailLinkError) {
|
||||
return;
|
||||
}
|
||||
return <p className="error">{mozL10n.get("unable_retrieve_url")}</p>;
|
||||
},
|
||||
|
||||
retryCall: function() {
|
||||
|
@ -199,25 +244,38 @@ loop.conversationViews = (function(mozL10n) {
|
|||
this.props.dispatcher.dispatch(new sharedActions.CancelCall());
|
||||
},
|
||||
|
||||
emailLink: function() {
|
||||
this.setState({
|
||||
emailLinkError: false,
|
||||
emailLinkButtonDisabled: true
|
||||
});
|
||||
|
||||
this.props.dispatcher.dispatch(new sharedActions.FetchEmailLink());
|
||||
},
|
||||
|
||||
render: function() {
|
||||
return (
|
||||
<div className="call-window">
|
||||
<h2>{mozL10n.get("generic_failure_title")}</h2>
|
||||
|
||||
<p className="btn-label">{mozL10n.get("generic_failure_no_reason2")}</p>
|
||||
<p className="btn-label">{mozL10n.get("generic_failure_with_reason2")}</p>
|
||||
|
||||
{this._renderError()}
|
||||
|
||||
<div className="btn-group call-action-group">
|
||||
<div className="fx-embedded-call-button-spacer"></div>
|
||||
<button className="btn btn-accept btn-retry"
|
||||
onClick={this.retryCall}>
|
||||
{mozL10n.get("retry_call_button")}
|
||||
</button>
|
||||
<div className="fx-embedded-call-button-spacer"></div>
|
||||
<button className="btn btn-cancel"
|
||||
onClick={this.cancelCall}>
|
||||
{mozL10n.get("cancel_button")}
|
||||
</button>
|
||||
<div className="fx-embedded-call-button-spacer"></div>
|
||||
<button className="btn btn-cancel"
|
||||
onClick={this.cancelCall}>
|
||||
{mozL10n.get("cancel_button")}
|
||||
</button>
|
||||
<button className="btn btn-info btn-retry"
|
||||
onClick={this.retryCall}>
|
||||
{mozL10n.get("retry_call_button")}
|
||||
</button>
|
||||
<button className="btn btn-info btn-email"
|
||||
onClick={this.emailLink}
|
||||
disabled={this.state.emailLinkButtonDisabled}>
|
||||
{mozL10n.get("share_button2")}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
@ -426,6 +484,8 @@ loop.conversationViews = (function(mozL10n) {
|
|||
case CALL_STATES.TERMINATED: {
|
||||
return (<CallFailedView
|
||||
dispatcher={this.props.dispatcher}
|
||||
store={this.props.store}
|
||||
contact={this.state.contact}
|
||||
/>);
|
||||
}
|
||||
case CALL_STATES.ONGOING: {
|
||||
|
@ -445,7 +505,7 @@ loop.conversationViews = (function(mozL10n) {
|
|||
callState={this.state.callState}
|
||||
contact={this.state.contact}
|
||||
enableCancelButton={this._isCancellable()}
|
||||
/>)
|
||||
/>);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
|
@ -15,6 +15,7 @@ loop.panel = (function(_, mozL10n) {
|
|||
var sharedModels = loop.shared.models;
|
||||
var sharedMixins = loop.shared.mixins;
|
||||
var sharedActions = loop.shared.actions;
|
||||
var sharedUtils = loop.shared.utils;
|
||||
var Button = sharedViews.Button;
|
||||
var ButtonGroup = sharedViews.ButtonGroup;
|
||||
var ContactsList = loop.contacts.ContactsList;
|
||||
|
@ -362,11 +363,7 @@ loop.panel = (function(_, mozL10n) {
|
|||
handleEmailButtonClick: function(event) {
|
||||
this.handleLinkExfiltration(event);
|
||||
|
||||
navigator.mozLoop.composeEmail(
|
||||
__("share_email_subject4", { clientShortname: __("clientShortname2")}),
|
||||
__("share_email_body4", { callUrl: this.state.callUrl,
|
||||
clientShortname: __("clientShortname2"),
|
||||
learnMoreUrl: navigator.mozLoop.getLoopCharPref("learnMoreUrl") }));
|
||||
sharedUtils.composeCallUrlEmail(this.state.callUrl);
|
||||
},
|
||||
|
||||
handleCopyButtonClick: function(event) {
|
||||
|
|
|
@ -15,6 +15,7 @@ loop.panel = (function(_, mozL10n) {
|
|||
var sharedModels = loop.shared.models;
|
||||
var sharedMixins = loop.shared.mixins;
|
||||
var sharedActions = loop.shared.actions;
|
||||
var sharedUtils = loop.shared.utils;
|
||||
var Button = sharedViews.Button;
|
||||
var ButtonGroup = sharedViews.ButtonGroup;
|
||||
var ContactsList = loop.contacts.ContactsList;
|
||||
|
@ -362,11 +363,7 @@ loop.panel = (function(_, mozL10n) {
|
|||
handleEmailButtonClick: function(event) {
|
||||
this.handleLinkExfiltration(event);
|
||||
|
||||
navigator.mozLoop.composeEmail(
|
||||
__("share_email_subject4", { clientShortname: __("clientShortname2")}),
|
||||
__("share_email_body4", { callUrl: this.state.callUrl,
|
||||
clientShortname: __("clientShortname2"),
|
||||
learnMoreUrl: navigator.mozLoop.getLoopCharPref("learnMoreUrl") }));
|
||||
sharedUtils.composeCallUrlEmail(this.state.callUrl);
|
||||
},
|
||||
|
||||
handleCopyButtonClick: function(event) {
|
||||
|
|
|
@ -240,16 +240,27 @@
|
|||
min-height: 230px;
|
||||
}
|
||||
|
||||
.call-window > .btn-label {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.call-window > .error {
|
||||
text-align: center;
|
||||
color: #f00;
|
||||
font-size: 90%;
|
||||
}
|
||||
|
||||
.call-action-group {
|
||||
display: flex;
|
||||
padding: 2.5em 0 0 0;
|
||||
padding: 2.5em 4px 0 4px;
|
||||
width: 100%;
|
||||
justify-content: space-around;
|
||||
}
|
||||
|
||||
.call-action-group > .btn {
|
||||
margin-left: .5em;
|
||||
height: 26px;
|
||||
border-radius: 2px;
|
||||
margin: 0 4px;
|
||||
min-width: 64px;
|
||||
}
|
||||
|
||||
.call-action-group .btn-group-chevron,
|
||||
|
|
|
@ -30,6 +30,13 @@ loop.shared.actions = (function() {
|
|||
};
|
||||
|
||||
return {
|
||||
/**
|
||||
* Fetch a new call url from the server, intended to be sent over email when
|
||||
* a contact can't be reached.
|
||||
*/
|
||||
FetchEmailLink: Action.define("fetchEmailLink", {
|
||||
}),
|
||||
|
||||
/**
|
||||
* Used to trigger gathering of initial call data.
|
||||
*/
|
||||
|
|
|
@ -126,7 +126,8 @@ loop.store.ConversationStore = (function() {
|
|||
"cancelCall",
|
||||
"retryCall",
|
||||
"mediaConnected",
|
||||
"setMute"
|
||||
"setMute",
|
||||
"fetchEmailLink"
|
||||
]);
|
||||
},
|
||||
|
||||
|
@ -303,16 +304,47 @@ loop.store.ConversationStore = (function() {
|
|||
this.set(muteType, !actionData.enabled);
|
||||
},
|
||||
|
||||
/**
|
||||
* Fetches a new call URL intended to be sent over email when a contact
|
||||
* can't be reached.
|
||||
*/
|
||||
fetchEmailLink: function() {
|
||||
// XXX This is an empty string as a conversation identifier. Bug 1015938 implements
|
||||
// a user-set string.
|
||||
this.client.requestCallUrl("", function(err, callUrlData) {
|
||||
if (err) {
|
||||
this.trigger("error:emailLink");
|
||||
return;
|
||||
}
|
||||
this.set("emailLink", callUrlData.callUrl);
|
||||
}.bind(this));
|
||||
},
|
||||
|
||||
/**
|
||||
* Obtains the outgoing call data from the server and handles the
|
||||
* result.
|
||||
*/
|
||||
_setupOutgoingCall: function() {
|
||||
var contactAddresses = [];
|
||||
var contact = this.get("contact");
|
||||
|
||||
this.get("contact").email.forEach(function(address) {
|
||||
contactAddresses.push(address.value);
|
||||
});
|
||||
function appendContactValues(property, strip) {
|
||||
if (contact.hasOwnProperty(property)) {
|
||||
contact[property].forEach(function(item) {
|
||||
if (strip) {
|
||||
contactAddresses.push(item.value
|
||||
.replace(/^(\+)?(.*)$/g, function(m, prefix, number) {
|
||||
return (prefix || "") + number.replace(/[\D]+/g, "");
|
||||
}));
|
||||
} else {
|
||||
contactAddresses.push(item.value);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
appendContactValues("email");
|
||||
appendContactValues("tel", true);
|
||||
|
||||
this.client.setupOutgoingCall(contactAddresses,
|
||||
this.get("callType"),
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
|
||||
var loop = loop || {};
|
||||
loop.shared = loop.shared || {};
|
||||
loop.shared.utils = (function() {
|
||||
loop.shared.utils = (function(mozL10n) {
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
|
@ -96,11 +96,37 @@ loop.shared.utils = (function() {
|
|||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Generates and opens a mailto: url with call URL information prefilled.
|
||||
* Note: This only works for Desktop.
|
||||
*
|
||||
* @param {String} callUrl The call URL.
|
||||
* @param {String} recipient The recipient email address (optional).
|
||||
*/
|
||||
function composeCallUrlEmail(callUrl, recipient) {
|
||||
if (typeof navigator.mozLoop === "undefined") {
|
||||
console.warn("composeCallUrlEmail isn't available for Loop standalone.");
|
||||
return;
|
||||
}
|
||||
navigator.mozLoop.composeEmail(
|
||||
mozL10n.get("share_email_subject4", {
|
||||
clientShortname: mozL10n.get("clientShortname2")
|
||||
}),
|
||||
mozL10n.get("share_email_body4", {
|
||||
callUrl: callUrl,
|
||||
clientShortname: mozL10n.get("clientShortname2"),
|
||||
learnMoreUrl: navigator.mozLoop.getLoopCharPref("learnMoreUrl")
|
||||
}),
|
||||
recipient
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
CALL_TYPES: CALL_TYPES,
|
||||
Helper: Helper,
|
||||
composeCallUrlEmail: composeCallUrlEmail,
|
||||
formatDate: formatDate,
|
||||
getTargetPlatform: getTargetPlatform,
|
||||
getBoolPreference: getBoolPreference
|
||||
};
|
||||
})();
|
||||
})(document.mozL10n || navigator.mozL10n);
|
||||
|
|
|
@ -6,6 +6,7 @@ var expect = chai.expect;
|
|||
describe("loop.conversationViews", function () {
|
||||
"use strict";
|
||||
|
||||
var sharedUtils = loop.shared.utils;
|
||||
var sandbox, oldTitle, view, dispatcher, contact;
|
||||
|
||||
var CALL_STATES = loop.store.CALL_STATES;
|
||||
|
@ -201,13 +202,25 @@ describe("loop.conversationViews", function () {
|
|||
});
|
||||
|
||||
describe("CallFailedView", function() {
|
||||
var store;
|
||||
|
||||
function mountTestComponent(props) {
|
||||
return TestUtils.renderIntoDocument(
|
||||
loop.conversationViews.CallFailedView({
|
||||
dispatcher: dispatcher
|
||||
dispatcher: dispatcher,
|
||||
store: store,
|
||||
contact: {email: [{value: "test@test.tld"}]}
|
||||
}));
|
||||
}
|
||||
|
||||
beforeEach(function() {
|
||||
store = new loop.store.ConversationStore({}, {
|
||||
dispatcher: dispatcher,
|
||||
client: {},
|
||||
sdkDriver: {}
|
||||
});
|
||||
});
|
||||
|
||||
it("should dispatch a retryCall action when the retry button is pressed",
|
||||
function() {
|
||||
view = mountTestComponent();
|
||||
|
@ -233,6 +246,66 @@ describe("loop.conversationViews", function () {
|
|||
sinon.assert.calledWithMatch(dispatcher.dispatch,
|
||||
sinon.match.hasOwn("name", "cancelCall"));
|
||||
});
|
||||
|
||||
it("should dispatch a fetchEmailLink action when the cancel button is pressed",
|
||||
function() {
|
||||
view = mountTestComponent();
|
||||
|
||||
var emailLinkBtn = view.getDOMNode().querySelector('.btn-email');
|
||||
|
||||
React.addons.TestUtils.Simulate.click(emailLinkBtn);
|
||||
|
||||
sinon.assert.calledOnce(dispatcher.dispatch);
|
||||
sinon.assert.calledWithMatch(dispatcher.dispatch,
|
||||
sinon.match.hasOwn("name", "fetchEmailLink"));
|
||||
});
|
||||
|
||||
it("should disable the email link button once the action is dispatched",
|
||||
function() {
|
||||
view = mountTestComponent();
|
||||
var emailLinkBtn = view.getDOMNode().querySelector('.btn-email');
|
||||
React.addons.TestUtils.Simulate.click(emailLinkBtn);
|
||||
|
||||
expect(view.getDOMNode().querySelector(".btn-email").disabled).eql(true);
|
||||
});
|
||||
|
||||
it("should compose an email once the email link is received", function() {
|
||||
var composeCallUrlEmail = sandbox.stub(sharedUtils, "composeCallUrlEmail");
|
||||
view = mountTestComponent();
|
||||
store.set("emailLink", "http://fake.invalid/");
|
||||
|
||||
sinon.assert.calledOnce(composeCallUrlEmail);
|
||||
sinon.assert.calledWithExactly(composeCallUrlEmail,
|
||||
"http://fake.invalid/", "test@test.tld");
|
||||
});
|
||||
|
||||
it("should close the conversation window once the email link is received",
|
||||
function() {
|
||||
sandbox.stub(window, "close");
|
||||
view = mountTestComponent();
|
||||
|
||||
store.set("emailLink", "http://fake.invalid/");
|
||||
|
||||
sinon.assert.calledOnce(window.close);
|
||||
});
|
||||
|
||||
it("should display an error message in case email link retrieval failed",
|
||||
function() {
|
||||
view = mountTestComponent();
|
||||
|
||||
store.trigger("error:emailLink");
|
||||
|
||||
expect(view.getDOMNode().querySelector(".error")).not.eql(null);
|
||||
});
|
||||
|
||||
it("should allow retrying to get a call url if it failed previously",
|
||||
function() {
|
||||
view = mountTestComponent();
|
||||
|
||||
store.trigger("error:emailLink");
|
||||
|
||||
expect(view.getDOMNode().querySelector(".btn-email").disabled).eql(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("OngoingConversationView", function() {
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
var expect = chai.expect;
|
||||
var TestUtils = React.addons.TestUtils;
|
||||
var sharedActions = loop.shared.actions;
|
||||
var sharedUtils = loop.shared.utils;
|
||||
|
||||
describe("loop.panel", function() {
|
||||
"use strict";
|
||||
|
@ -449,6 +450,7 @@ describe("loop.panel", function() {
|
|||
|
||||
it("should display a share button for email", function() {
|
||||
fakeClient.requestCallUrl = sandbox.stub();
|
||||
var composeCallUrlEmail = sandbox.stub(sharedUtils, "composeCallUrlEmail");
|
||||
var view = TestUtils.renderIntoDocument(loop.panel.CallUrlResult({
|
||||
notifications: notifications,
|
||||
client: fakeClient
|
||||
|
@ -457,7 +459,9 @@ describe("loop.panel", function() {
|
|||
|
||||
TestUtils.findRenderedDOMComponentWithClass(view, "button-email");
|
||||
TestUtils.Simulate.click(view.getDOMNode().querySelector(".button-email"));
|
||||
sinon.assert.calledOnce(navigator.mozLoop.composeEmail);
|
||||
|
||||
sinon.assert.calledOnce(composeCallUrlEmail);
|
||||
sinon.assert.calledWithExactly(composeCallUrlEmail, "http://example.com");
|
||||
});
|
||||
|
||||
it("should feature a copy button capable of copying the call url when clicked", function() {
|
||||
|
|
|
@ -38,7 +38,8 @@ describe("loop.store.ConversationStore", function () {
|
|||
|
||||
dispatcher = new loop.Dispatcher();
|
||||
client = {
|
||||
setupOutgoingCall: sinon.stub()
|
||||
setupOutgoingCall: sinon.stub(),
|
||||
requestCallUrl: sinon.stub()
|
||||
};
|
||||
sdkDriver = {
|
||||
connectSession: sinon.stub(),
|
||||
|
@ -275,6 +276,79 @@ describe("loop.store.ConversationStore", function () {
|
|||
["fakeEmail"], sharedUtils.CALL_TYPES.AUDIO_VIDEO);
|
||||
});
|
||||
|
||||
it("should include all email addresses in the call data", function() {
|
||||
contact = {
|
||||
name: [ "Mr Smith" ],
|
||||
email: [{
|
||||
type: "home",
|
||||
value: "fakeEmail",
|
||||
pref: true
|
||||
},
|
||||
{
|
||||
type: "work",
|
||||
value: "emailFake",
|
||||
pref: false
|
||||
}]
|
||||
};
|
||||
|
||||
dispatcher.dispatch(
|
||||
new sharedActions.GatherCallData(outgoingCallData));
|
||||
|
||||
sinon.assert.calledOnce(client.setupOutgoingCall);
|
||||
sinon.assert.calledWith(client.setupOutgoingCall,
|
||||
["fakeEmail", "emailFake"], sharedUtils.CALL_TYPES.AUDIO_VIDEO);
|
||||
});
|
||||
|
||||
it("should include trim phone numbers for the call data", function() {
|
||||
contact = {
|
||||
name: [ "Mr Smith" ],
|
||||
tel: [{
|
||||
type: "home",
|
||||
value: "+44-5667+345 496(2335)45+ 456+",
|
||||
pref: true
|
||||
}]
|
||||
};
|
||||
|
||||
dispatcher.dispatch(
|
||||
new sharedActions.GatherCallData(outgoingCallData));
|
||||
|
||||
sinon.assert.calledOnce(client.setupOutgoingCall);
|
||||
sinon.assert.calledWith(client.setupOutgoingCall,
|
||||
["+445667345496233545456"], sharedUtils.CALL_TYPES.AUDIO_VIDEO);
|
||||
});
|
||||
|
||||
it("should include all email and telephone values in the call data", function() {
|
||||
contact = {
|
||||
name: [ "Mr Smith" ],
|
||||
email: [{
|
||||
type: "home",
|
||||
value: "fakeEmail",
|
||||
pref: true
|
||||
}, {
|
||||
type: "work",
|
||||
value: "emailFake",
|
||||
pref: false
|
||||
}],
|
||||
tel: [{
|
||||
type: "work",
|
||||
value: "01234567890",
|
||||
pref: false
|
||||
}, {
|
||||
type: "home",
|
||||
value: "09876543210",
|
||||
pref: false
|
||||
}]
|
||||
};
|
||||
|
||||
dispatcher.dispatch(
|
||||
new sharedActions.GatherCallData(outgoingCallData));
|
||||
|
||||
sinon.assert.calledOnce(client.setupOutgoingCall);
|
||||
sinon.assert.calledWith(client.setupOutgoingCall,
|
||||
["fakeEmail", "emailFake", "01234567890", "09876543210"],
|
||||
sharedUtils.CALL_TYPES.AUDIO_VIDEO);
|
||||
});
|
||||
|
||||
describe("server response handling", function() {
|
||||
beforeEach(function() {
|
||||
sandbox.stub(dispatcher, "dispatch");
|
||||
|
@ -566,6 +640,37 @@ describe("loop.store.ConversationStore", function () {
|
|||
});
|
||||
});
|
||||
|
||||
describe("#fetchEmailLink", function() {
|
||||
it("should request a new call url to the server", function() {
|
||||
dispatcher.dispatch(new sharedActions.FetchEmailLink());
|
||||
|
||||
sinon.assert.calledOnce(client.requestCallUrl);
|
||||
sinon.assert.calledWith(client.requestCallUrl, "");
|
||||
});
|
||||
|
||||
it("should update the emailLink attribute when the new call url is received",
|
||||
function() {
|
||||
client.requestCallUrl = function(callId, cb) {
|
||||
cb(null, {callUrl: "http://fake.invalid/"});
|
||||
};
|
||||
dispatcher.dispatch(new sharedActions.FetchEmailLink());
|
||||
|
||||
expect(store.get("emailLink")).eql("http://fake.invalid/");
|
||||
});
|
||||
|
||||
it("should trigger an error:emailLink event in case of failure",
|
||||
function() {
|
||||
var trigger = sandbox.stub(store, "trigger");
|
||||
client.requestCallUrl = function(callId, cb) {
|
||||
cb("error");
|
||||
};
|
||||
dispatcher.dispatch(new sharedActions.FetchEmailLink());
|
||||
|
||||
sinon.assert.calledOnce(trigger);
|
||||
sinon.assert.calledWithExactly(trigger, "error:emailLink");
|
||||
});
|
||||
});
|
||||
|
||||
describe("Events", function() {
|
||||
describe("Websocket progress", function() {
|
||||
beforeEach(function() {
|
||||
|
|
|
@ -18,6 +18,7 @@ describe("loop.shared.utils", function() {
|
|||
});
|
||||
|
||||
afterEach(function() {
|
||||
navigator.mozLoop = undefined;
|
||||
sandbox.restore();
|
||||
});
|
||||
|
||||
|
@ -110,7 +111,6 @@ describe("loop.shared.utils", function() {
|
|||
|
||||
describe("#getBoolPreference", function() {
|
||||
afterEach(function() {
|
||||
navigator.mozLoop = undefined;
|
||||
localStorage.removeItem("test.true");
|
||||
});
|
||||
|
||||
|
@ -142,4 +142,31 @@ describe("loop.shared.utils", function() {
|
|||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("#composeCallUrlEmail", function() {
|
||||
var composeEmail;
|
||||
|
||||
beforeEach(function() {
|
||||
// fake mozL10n
|
||||
sandbox.stub(navigator.mozL10n, "get", function(id) {
|
||||
switch(id) {
|
||||
case "share_email_subject4": return "subject";
|
||||
case "share_email_body4": return "body";
|
||||
}
|
||||
});
|
||||
composeEmail = sandbox.spy();
|
||||
navigator.mozLoop = {
|
||||
getLoopCharPref: sandbox.spy(),
|
||||
composeEmail: composeEmail
|
||||
};
|
||||
});
|
||||
|
||||
it("should compose a call url email", function() {
|
||||
sharedUtils.composeCallUrlEmail("http://invalid", "fake@invalid.tld");
|
||||
|
||||
sinon.assert.calledOnce(composeEmail);
|
||||
sinon.assert.calledWith(composeEmail,
|
||||
"subject", "body", "fake@invalid.tld");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -312,6 +312,12 @@
|
|||
React.DOM.div({className: "fx-embedded"},
|
||||
CallFailedView({dispatcher: dispatcher})
|
||||
)
|
||||
),
|
||||
Example({summary: "Call Failed — with call URL error", dashed: "true",
|
||||
style: {width: "260px", height: "265px"}},
|
||||
React.DOM.div({className: "fx-embedded"},
|
||||
CallFailedView({dispatcher: dispatcher, emailLinkError: true})
|
||||
)
|
||||
)
|
||||
),
|
||||
|
||||
|
|
|
@ -313,6 +313,12 @@
|
|||
<CallFailedView dispatcher={dispatcher} />
|
||||
</div>
|
||||
</Example>
|
||||
<Example summary="Call Failed — with call URL error" dashed="true"
|
||||
style={{width: "260px", height: "265px"}}>
|
||||
<div className="fx-embedded">
|
||||
<CallFailedView dispatcher={dispatcher} emailLinkError={true} />
|
||||
</div>
|
||||
</Example>
|
||||
</Section>
|
||||
|
||||
<Section name="StartConversationView">
|
||||
|
|
|
@ -41,7 +41,7 @@ FontInspector.prototype = {
|
|||
*/
|
||||
destroy: function FI_destroy() {
|
||||
this.chromeDoc = null;
|
||||
this.inspector.sidebar.off("layoutview-selected", this.onNewNode);
|
||||
this.inspector.sidebar.off("fontinspector-selected", this.onNewNode);
|
||||
this.inspector.selection.off("new-node", this.onNewNode);
|
||||
this.showAllButton.removeEventListener("click", this.showAll);
|
||||
},
|
||||
|
|
|
@ -307,7 +307,9 @@ HTMLLinkElement::SetAttr(int32_t aNameSpaceID, nsIAtom* aName,
|
|||
// to get updated information about the visitedness from Link.
|
||||
if (aName == nsGkAtoms::href && kNameSpaceID_None == aNameSpaceID) {
|
||||
Link::ResetLinkState(!!aNotify, true);
|
||||
CreateAndDispatchEvent(OwnerDoc(), NS_LITERAL_STRING("DOMLinkChanged"));
|
||||
if (IsInUncomposedDoc()) {
|
||||
CreateAndDispatchEvent(OwnerDoc(), NS_LITERAL_STRING("DOMLinkChanged"));
|
||||
}
|
||||
}
|
||||
|
||||
if (NS_SUCCEEDED(rv) && aNameSpaceID == kNameSpaceID_None &&
|
||||
|
@ -370,7 +372,9 @@ HTMLLinkElement::UnsetAttr(int32_t aNameSpaceID, nsIAtom* aAttribute,
|
|||
// to get updated information about the visitedness from Link.
|
||||
if (aAttribute == nsGkAtoms::href && kNameSpaceID_None == aNameSpaceID) {
|
||||
Link::ResetLinkState(!!aNotify, false);
|
||||
CreateAndDispatchEvent(OwnerDoc(), NS_LITERAL_STRING("DOMLinkChanged"));
|
||||
if (IsInUncomposedDoc()) {
|
||||
CreateAndDispatchEvent(OwnerDoc(), NS_LITERAL_STRING("DOMLinkChanged"));
|
||||
}
|
||||
}
|
||||
|
||||
return rv;
|
||||
|
|
|
@ -5,6 +5,8 @@
|
|||
package org.mozilla.gecko;
|
||||
|
||||
import org.mozilla.gecko.util.GeckoEventListener;
|
||||
import org.mozilla.gecko.util.GeckoRequest;
|
||||
import org.mozilla.gecko.util.NativeJSObject;
|
||||
import org.mozilla.gecko.util.ThreadUtils;
|
||||
|
||||
import org.json.JSONObject;
|
||||
|
@ -19,12 +21,14 @@ import android.view.LayoutInflater;
|
|||
import android.view.View;
|
||||
import android.view.inputmethod.InputMethodManager;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
public class FindInPageBar extends LinearLayout implements TextWatcher, View.OnClickListener, GeckoEventListener {
|
||||
private static final String REQUEST_ID = "FindInPageBar";
|
||||
|
||||
private final Context mContext;
|
||||
private CustomEditText mFindText;
|
||||
private TextView mStatusText;
|
||||
private boolean mInflated;
|
||||
|
||||
public FindInPageBar(Context context, AttributeSet attrs) {
|
||||
|
@ -58,6 +62,8 @@ public class FindInPageBar extends LinearLayout implements TextWatcher, View.OnC
|
|||
}
|
||||
});
|
||||
|
||||
mStatusText = (TextView) content.findViewById(R.id.find_status);
|
||||
|
||||
mInflated = true;
|
||||
EventDispatcher.getInstance().registerGeckoThreadListener(this, "TextSelection:Data");
|
||||
}
|
||||
|
@ -71,6 +77,7 @@ public class FindInPageBar extends LinearLayout implements TextWatcher, View.OnC
|
|||
|
||||
// handleMessage() receives response message and determines initial state of softInput
|
||||
GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("TextSelection:Get", REQUEST_ID));
|
||||
GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("FindInPage:Opened", null));
|
||||
}
|
||||
|
||||
public void hide() {
|
||||
|
@ -95,7 +102,7 @@ public class FindInPageBar extends LinearLayout implements TextWatcher, View.OnC
|
|||
|
||||
@Override
|
||||
public void afterTextChanged(Editable s) {
|
||||
GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("FindInPage:Find", s.toString()));
|
||||
sendRequestToFinderHelper("FindInPage:Find", s.toString());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -115,13 +122,13 @@ public class FindInPageBar extends LinearLayout implements TextWatcher, View.OnC
|
|||
final int viewId = v.getId();
|
||||
|
||||
if (viewId == R.id.find_prev) {
|
||||
GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("FindInPage:Prev", mFindText.getText().toString()));
|
||||
sendRequestToFinderHelper("FindInPage:Prev", mFindText.getText().toString());
|
||||
getInputMethodManager(mFindText).hideSoftInputFromWindow(mFindText.getWindowToken(), 0);
|
||||
return;
|
||||
}
|
||||
|
||||
if (viewId == R.id.find_next) {
|
||||
GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("FindInPage:Next", mFindText.getText().toString()));
|
||||
sendRequestToFinderHelper("FindInPage:Next", mFindText.getText().toString());
|
||||
getInputMethodManager(mFindText).hideSoftInputFromWindow(mFindText.getWindowToken(), 0);
|
||||
return;
|
||||
}
|
||||
|
@ -170,4 +177,28 @@ public class FindInPageBar extends LinearLayout implements TextWatcher, View.OnC
|
|||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Request find operation, and update matchCount results (current count and total).
|
||||
*/
|
||||
private void sendRequestToFinderHelper(String request, String searchString) {
|
||||
GeckoAppShell.sendRequestToGecko(new GeckoRequest(request, searchString) {
|
||||
@Override
|
||||
public void onResponse(NativeJSObject nativeJSObject) {
|
||||
final int total = nativeJSObject.optInt("total", 0);
|
||||
final int current = nativeJSObject.optInt("current", 0);
|
||||
|
||||
final Boolean statusVisibility = (total > 0);
|
||||
final String statusText = current + "/" + total;
|
||||
|
||||
ThreadUtils.postToUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
mStatusText.setVisibility(statusVisibility ? View.VISIBLE : View.GONE);
|
||||
mStatusText.setText(statusText);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -206,6 +206,13 @@ public class SendTab extends ShareMethod {
|
|||
i++;
|
||||
}
|
||||
|
||||
if (validGUIDs.isEmpty()) {
|
||||
// Guess we'd better override. We have no clients.
|
||||
// This does the broadcast for us.
|
||||
setOverrideIntent(FxAccountGetStartedActivity.class);
|
||||
return;
|
||||
}
|
||||
|
||||
Intent uiStateIntent = getUIStateIntent();
|
||||
uiStateIntent.putExtra(EXTRA_CLIENT_RECORDS, records);
|
||||
broadcastUIState(uiStateIntent);
|
||||
|
@ -230,6 +237,7 @@ public class SendTab extends ShareMethod {
|
|||
|
||||
Intent uiStateIntent = getUIStateIntent();
|
||||
uiStateIntent.putExtra(OVERRIDE_INTENT, intent);
|
||||
|
||||
broadcastUIState(uiStateIntent);
|
||||
}
|
||||
|
||||
|
|
|
@ -112,24 +112,27 @@ public class SendTabDeviceListArrayAdapter extends ArrayAdapter<ParcelableClient
|
|||
}
|
||||
|
||||
// The remaining states delegate to the SentTabTargetSelectedListener.
|
||||
final String listenerGUID;
|
||||
|
||||
ParcelableClientRecord clientRecord = getItem(position);
|
||||
final ParcelableClientRecord clientRecord = getItem(position);
|
||||
if (currentState == State.LIST) {
|
||||
row.setText(clientRecord.name);
|
||||
row.setCompoundDrawablesWithIntrinsicBounds(getImage(clientRecord), 0, 0, 0);
|
||||
|
||||
listenerGUID = clientRecord.guid;
|
||||
} else {
|
||||
listenerGUID = null;
|
||||
}
|
||||
final String listenerGUID = clientRecord.guid;
|
||||
|
||||
row.setOnClickListener(new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
listener.onSendTabTargetSelected(listenerGUID);
|
||||
}
|
||||
});
|
||||
row.setOnClickListener(new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
listener.onSendTabTargetSelected(listenerGUID);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
row.setOnClickListener(new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
listener.onSendTabActionSelected();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return row;
|
||||
}
|
||||
|
|
|
@ -6,7 +6,6 @@ package org.mozilla.gecko.overlays.ui;
|
|||
|
||||
import static org.mozilla.gecko.overlays.ui.SendTabList.State.LIST;
|
||||
import static org.mozilla.gecko.overlays.ui.SendTabList.State.LOADING;
|
||||
import static org.mozilla.gecko.overlays.ui.SendTabList.State.NONE;
|
||||
import static org.mozilla.gecko.overlays.ui.SendTabList.State.SHOW_DEVICES;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
@ -111,16 +110,9 @@ public class SendTabList extends ListView {
|
|||
public void setSyncClients(final ParcelableClientRecord[] c) {
|
||||
final ParcelableClientRecord[] clients = c == null ? new ParcelableClientRecord[0] : c;
|
||||
|
||||
int size = clients.length;
|
||||
if (size == 0) {
|
||||
// Just show a button to set up Sync (or whatever).
|
||||
switchState(NONE);
|
||||
return;
|
||||
}
|
||||
|
||||
clientListAdapter.setClientRecordList(Arrays.asList(clients));
|
||||
|
||||
if (size <= MAXIMUM_INLINE_ELEMENTS) {
|
||||
if (clients.length <= MAXIMUM_INLINE_ELEMENTS) {
|
||||
// Show the list of devices in-line.
|
||||
switchState(LIST);
|
||||
return;
|
||||
|
@ -133,7 +125,7 @@ public class SendTabList extends ListView {
|
|||
/**
|
||||
* Get an AlertDialog listing all devices, allowing the user to select the one they want.
|
||||
* Used when more than MAXIMUM_INLINE_ELEMENTS devices are found (to avoid displaying them all
|
||||
* inline and looking crazy.
|
||||
* inline and looking crazy).
|
||||
*/
|
||||
public AlertDialog getDialog() {
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(getContext());
|
||||
|
|
|
@ -15,4 +15,11 @@ public interface SendTabTargetSelectedListener {
|
|||
* @param targetGUID The GUID of the ClientRecord the element represents (if any, otherwise null)
|
||||
*/
|
||||
public void onSendTabTargetSelected(String targetGUID);
|
||||
|
||||
/**
|
||||
* Called when the overall Send Tab item is clicked.
|
||||
*
|
||||
* This implies that the clients list was unavailable.
|
||||
*/
|
||||
public void onSendTabActionSelected();
|
||||
}
|
||||
|
|
|
@ -295,14 +295,17 @@ public class ShareDialog extends LocaleAware.LocaleAwareActivity implements Send
|
|||
* launching Fennec").
|
||||
*/
|
||||
|
||||
public void sendTab(String targetGUID) {
|
||||
// If an override intent has been set, dispatch it.
|
||||
if (sendTabOverrideIntent != null) {
|
||||
startActivity(sendTabOverrideIntent);
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
@Override
|
||||
public void onSendTabActionSelected() {
|
||||
// This requires an override intent.
|
||||
Assert.isTrue(sendTabOverrideIntent != null);
|
||||
|
||||
startActivity(sendTabOverrideIntent);
|
||||
finish();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSendTabTargetSelected(String targetGUID) {
|
||||
// targetGUID being null with no override intent should be an impossible state.
|
||||
Assert.isTrue(targetGUID != null);
|
||||
|
||||
|
@ -320,11 +323,6 @@ public class ShareDialog extends LocaleAware.LocaleAwareActivity implements Send
|
|||
slideOut();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSendTabTargetSelected(String targetGUID) {
|
||||
sendTab(targetGUID);
|
||||
}
|
||||
|
||||
public void addToReadingList() {
|
||||
startService(getServiceIntent(ShareMethod.Type.ADD_TO_READING_LIST));
|
||||
slideOut();
|
||||
|
|
Двоичный файл не отображается.
После Ширина: | Высота: | Размер: 341 B |
|
@ -6,34 +6,44 @@
|
|||
android:layout_width="0dip"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1.0"
|
||||
android:layout_marginLeft="5dip"
|
||||
android:layout_marginRight="5dip"
|
||||
android:layout_marginLeft="@dimen/find_in_page_text_margin_left"
|
||||
android:layout_marginRight="@dimen/find_in_page_text_margin_right"
|
||||
android:contentDescription="@string/find_text"
|
||||
android:background="@drawable/url_bar_entry"
|
||||
android:singleLine="true"
|
||||
android:textColor="#000000"
|
||||
android:textCursorDrawable="@null"
|
||||
android:inputType="text"
|
||||
android:paddingLeft="15dip"
|
||||
android:paddingRight="15dip"
|
||||
android:paddingLeft="@dimen/find_in_page_text_padding_left"
|
||||
android:paddingRight="@dimen/find_in_page_text_padding_right"
|
||||
android:textColorHighlight="@color/url_bar_text_highlight"
|
||||
android:imeOptions="actionSearch"
|
||||
android:selectAllOnFocus="true"
|
||||
android:gravity="center_vertical|left"/>
|
||||
|
||||
<TextView android:id="@+id/find_status"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginRight="@dimen/find_in_page_status_margin_right"
|
||||
android:textColor="@color/find_status_default"
|
||||
android:visibility="gone"/>
|
||||
|
||||
<ImageButton android:id="@+id/find_prev"
|
||||
style="@style/FindBar.ImageButton"
|
||||
android:contentDescription="@string/find_prev"
|
||||
android:layout_marginTop="@dimen/find_in_page_control_margin_top"
|
||||
android:src="@drawable/find_prev"/>
|
||||
|
||||
<ImageButton android:id="@+id/find_next"
|
||||
style="@style/FindBar.ImageButton"
|
||||
android:contentDescription="@string/find_next"
|
||||
android:layout_marginTop="@dimen/find_in_page_control_margin_top"
|
||||
android:src="@drawable/find_next"/>
|
||||
|
||||
<ImageButton android:id="@+id/find_close"
|
||||
style="@style/FindBar.ImageButton"
|
||||
android:contentDescription="@string/find_close"
|
||||
android:layout_marginTop="@dimen/find_in_page_control_margin_top"
|
||||
android:src="@drawable/find_close"/>
|
||||
|
||||
</merge>
|
||||
|
|
|
@ -0,0 +1,59 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- This Source Code Form is subject to the terms of the Mozilla Public
|
||||
- License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
|
||||
|
||||
<org.mozilla.gecko.tabs.TabsLayoutItemView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
style="@style/TabsItem"
|
||||
android:focusable="true"
|
||||
android:id="@+id/info"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingTop="6dip"
|
||||
android:paddingBottom="6dip"
|
||||
android:paddingLeft="1dip"
|
||||
android:paddingRight="1dip"
|
||||
android:gravity="center"
|
||||
android:orientation="vertical">
|
||||
|
||||
<LinearLayout android:layout_width="@dimen/new_tablet_tab_thumbnail_width"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:padding="3dip">
|
||||
|
||||
<TextView android:id="@+id/title"
|
||||
android:layout_width="0dip"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1.0"
|
||||
android:paddingTop="4dip"
|
||||
style="@style/TabRowTextAppearance"
|
||||
android:textSize="12sp"
|
||||
android:textColor="#FFFFFFFF"
|
||||
android:singleLine="true"
|
||||
android:duplicateParentState="true"/>
|
||||
|
||||
<ImageButton android:id="@+id/close"
|
||||
style="@style/TabsItemClose"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@drawable/action_bar_button_inverse"
|
||||
android:scaleType="center"
|
||||
android:contentDescription="@string/close_tab"
|
||||
android:src="@drawable/new_tablet_tab_close"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<RelativeLayout android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="3dp"
|
||||
android:background="@drawable/tab_thumbnail"
|
||||
android:duplicateParentState="true">
|
||||
|
||||
<org.mozilla.gecko.widget.ThumbnailView android:id="@+id/thumbnail"
|
||||
android:layout_width="@dimen/new_tablet_tab_thumbnail_width"
|
||||
android:layout_height="@dimen/new_tablet_tab_thumbnail_height"
|
||||
android:src="@drawable/tab_thumbnail_default"/>
|
||||
|
||||
</RelativeLayout>
|
||||
|
||||
</org.mozilla.gecko.tabs.TabsLayoutItemView>
|
|
@ -8,4 +8,5 @@
|
|||
<!-- Remote Tabs static view top padding. Less in landscape on phones. -->
|
||||
<dimen name="home_remote_tabs_top_padding">16dp</dimen>
|
||||
|
||||
<dimen name="new_tablet_tab_panel_grid_padding">48dp</dimen>
|
||||
</resources>
|
||||
|
|
|
@ -137,4 +137,8 @@
|
|||
<color name="toast_button_background">#00000000</color>
|
||||
<color name="toast_button_pressed">#DD2C3136</color>
|
||||
<color name="toast_button_text">#FFFFFFFF</color>
|
||||
|
||||
<!-- Colour used for Find-In-Page dialog -->
|
||||
<color name="find_status_default">#AFB1B3</color>
|
||||
|
||||
</resources>
|
||||
|
|
|
@ -118,6 +118,11 @@
|
|||
<dimen name="url_bar_offset_left">32dp</dimen>
|
||||
<dimen name="history_tab_indicator_height">50dp</dimen>
|
||||
|
||||
|
||||
<dimen name="new_tablet_tab_thumbnail_height">180dp</dimen>
|
||||
<dimen name="new_tablet_tab_thumbnail_width">180dp</dimen>
|
||||
<dimen name="new_tablet_tab_panel_grid_padding">24dp</dimen>
|
||||
|
||||
<!-- PageActionButtons dimensions -->
|
||||
<dimen name="page_action_button_width">32dp</dimen>
|
||||
|
||||
|
@ -144,4 +149,13 @@
|
|||
<dimen name="arrow_popup_arrow_width">40dip</dimen>
|
||||
<dimen name="arrow_popup_arrow_height">12dip</dimen>
|
||||
<dimen name="arrow_popup_arrow_offset">8dp</dimen>
|
||||
|
||||
<!-- Find-In-Page dialog dimensions. -->
|
||||
<dimen name="find_in_page_text_margin_left">5dip</dimen>
|
||||
<dimen name="find_in_page_text_margin_right">12dip</dimen>
|
||||
<dimen name="find_in_page_text_padding_left">10dip</dimen>
|
||||
<dimen name="find_in_page_text_padding_right">10dip</dimen>
|
||||
<dimen name="find_in_page_status_margin_right">5dip</dimen>
|
||||
<dimen name="find_in_page_control_margin_top">2dip</dimen>
|
||||
|
||||
</resources>
|
||||
|
|
|
@ -16,8 +16,11 @@ import org.mozilla.gecko.tabs.TabsPanel.TabsLayout;
|
|||
import org.mozilla.gecko.Tabs;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
import android.content.res.TypedArray;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.TypedValue;
|
||||
import android.view.Gravity;
|
||||
import android.view.View;
|
||||
import android.widget.GridView;
|
||||
import android.view.ViewGroup;
|
||||
|
@ -58,6 +61,18 @@ class TabsGridLayout extends GridView
|
|||
item.setThumbnail(null);
|
||||
}
|
||||
});
|
||||
|
||||
setScrollBarStyle(View.SCROLLBARS_OUTSIDE_OVERLAY);
|
||||
setStretchMode(GridView.STRETCH_SPACING);
|
||||
setGravity(Gravity.CENTER);
|
||||
setNumColumns(GridView.AUTO_FIT);
|
||||
|
||||
final Resources resources = getResources();
|
||||
final int columnWidth = resources.getDimensionPixelSize(R.dimen.new_tablet_tab_thumbnail_width);
|
||||
setColumnWidth(columnWidth);
|
||||
|
||||
final int padding = resources.getDimensionPixelSize(R.dimen.new_tablet_tab_panel_grid_padding);
|
||||
setPadding(padding, 0, padding, 0);
|
||||
}
|
||||
|
||||
private class TabsGridLayoutAdapter extends TabsLayoutAdapter {
|
||||
|
@ -66,7 +81,7 @@ class TabsGridLayout extends GridView
|
|||
final private View.OnClickListener mSelectClickListener;
|
||||
|
||||
public TabsGridLayoutAdapter (Context context) {
|
||||
super(context);
|
||||
super(context, R.layout.new_tablet_tabs_item_cell);
|
||||
|
||||
mCloseClickListener = new Button.OnClickListener() {
|
||||
@Override
|
||||
|
|
|
@ -21,12 +21,14 @@ public class TabsLayoutAdapter extends BaseAdapter {
|
|||
public static final String LOGTAG = "Gecko" + TabsLayoutAdapter.class.getSimpleName();
|
||||
|
||||
private final Context mContext;
|
||||
private final int mTabLayoutId;
|
||||
private ArrayList<Tab> mTabs;
|
||||
private final LayoutInflater mInflater;
|
||||
|
||||
public TabsLayoutAdapter (Context context) {
|
||||
public TabsLayoutAdapter (Context context, int tabLayoutId) {
|
||||
mContext = context;
|
||||
mInflater = LayoutInflater.from(mContext);
|
||||
mTabLayoutId = tabLayoutId;
|
||||
}
|
||||
|
||||
final void setTabs (ArrayList<Tab> tabs) {
|
||||
|
@ -83,7 +85,7 @@ public class TabsLayoutAdapter extends BaseAdapter {
|
|||
}
|
||||
|
||||
TabsLayoutItemView newView(int position, ViewGroup parent) {
|
||||
return (TabsLayoutItemView) mInflater.inflate(R.layout.tabs_layout_item_view, parent, false);
|
||||
return (TabsLayoutItemView) mInflater.inflate(mTabLayoutId, parent, false);
|
||||
}
|
||||
|
||||
void bindView(TabsLayoutItemView view, Tab tab) {
|
||||
|
|
|
@ -89,7 +89,7 @@ class TabsListLayout extends TwoWayView
|
|||
private class TabsListLayoutAdapter extends TabsLayoutAdapter {
|
||||
private final Button.OnClickListener mCloseOnClickListener;
|
||||
public TabsListLayoutAdapter (Context context) {
|
||||
super(context);
|
||||
super(context, R.layout.tabs_layout_item_view);
|
||||
|
||||
mCloseOnClickListener = new Button.OnClickListener() {
|
||||
@Override
|
||||
|
|
|
@ -8,35 +8,100 @@ var FindHelper = {
|
|||
_targetTab: null,
|
||||
_initialViewport: null,
|
||||
_viewportChanged: false,
|
||||
_matchesCountResult: null,
|
||||
|
||||
observe: function(aMessage, aTopic, aData) {
|
||||
switch(aTopic) {
|
||||
case "FindInPage:Find":
|
||||
this.doFind(aData);
|
||||
case "FindInPage:Opened": {
|
||||
this._findOpened();
|
||||
this._init();
|
||||
break;
|
||||
}
|
||||
|
||||
case "FindInPage:Prev":
|
||||
this.findAgain(aData, true);
|
||||
case "Tab:Selected": {
|
||||
// Allow for page switching.
|
||||
this._uninit();
|
||||
break;
|
||||
}
|
||||
|
||||
case "FindInPage:Next":
|
||||
this.findAgain(aData, false);
|
||||
break;
|
||||
|
||||
case "Tab:Selected":
|
||||
case "FindInPage:Closed":
|
||||
this.findClosed();
|
||||
this._uninit();
|
||||
this._findClosed();
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
_findOpened: function() {
|
||||
Messaging.addListener((data) => {
|
||||
this.doFind(data);
|
||||
return this._getMatchesCountResult(data);
|
||||
}, "FindInPage:Find");
|
||||
|
||||
Messaging.addListener((data) => {
|
||||
this.findAgain(data, false);
|
||||
return this._getMatchesCountResult(data);
|
||||
}, "FindInPage:Next");
|
||||
|
||||
Messaging.addListener((data) => {
|
||||
this.findAgain(data, true);
|
||||
return this._getMatchesCountResult(data);
|
||||
}, "FindInPage:Prev");
|
||||
},
|
||||
|
||||
_init: function() {
|
||||
// If there's no find in progress, start one.
|
||||
if (this._finder) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._targetTab = BrowserApp.selectedTab;
|
||||
this._finder = this._targetTab.browser.finder;
|
||||
this._finder.addResultListener(this);
|
||||
this._initialViewport = JSON.stringify(this._targetTab.getViewport());
|
||||
this._viewportChanged = false;
|
||||
},
|
||||
|
||||
_uninit: function() {
|
||||
// If there's no find in progress, there's nothing to clean up.
|
||||
if (!this._finder) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._finder.removeSelection();
|
||||
this._finder.removeResultListener(this);
|
||||
this._finder = null;
|
||||
this._targetTab = null;
|
||||
this._initialViewport = null;
|
||||
this._viewportChanged = false;
|
||||
},
|
||||
|
||||
_findClosed: function() {
|
||||
Messaging.removeListener("FindInPage:Find");
|
||||
Messaging.removeListener("FindInPage:Next");
|
||||
Messaging.removeListener("FindInPage:Prev");
|
||||
},
|
||||
|
||||
/**
|
||||
* Request, wait for, and return the current matchesCount results for a string.
|
||||
*/
|
||||
_getMatchesCountResult: function(findString) {
|
||||
// Sync call to Finder, results available immediately.
|
||||
this._matchesCountResult = null;
|
||||
this._finder.requestMatchesCount(findString);
|
||||
|
||||
return this._matchesCountResult;
|
||||
},
|
||||
|
||||
/**
|
||||
* Pass along the count results to FindInPageBar for display.
|
||||
*/
|
||||
onMatchesCountResult: function(result) {
|
||||
this._matchesCountResult = result;
|
||||
},
|
||||
|
||||
doFind: function(aSearchString) {
|
||||
if (!this._finder) {
|
||||
this._targetTab = BrowserApp.selectedTab;
|
||||
this._finder = this._targetTab.browser.finder;
|
||||
this._finder.addResultListener(this);
|
||||
this._initialViewport = JSON.stringify(this._targetTab.getViewport());
|
||||
this._viewportChanged = false;
|
||||
this._init();
|
||||
}
|
||||
|
||||
this._finder.fastFind(aSearchString, false);
|
||||
|
@ -52,19 +117,6 @@ var FindHelper = {
|
|||
this._finder.findAgain(aFindBackwards, false, false);
|
||||
},
|
||||
|
||||
findClosed: function() {
|
||||
// If there's no find in progress, there's nothing to clean up
|
||||
if (!this._finder)
|
||||
return;
|
||||
|
||||
this._finder.removeSelection();
|
||||
this._finder.removeResultListener(this);
|
||||
this._finder = null;
|
||||
this._targetTab = null;
|
||||
this._initialViewport = null;
|
||||
this._viewportChanged = false;
|
||||
},
|
||||
|
||||
onFindResult: function(aData) {
|
||||
if (aData.result == Ci.nsITypeAheadFind.FIND_NOTFOUND) {
|
||||
if (this._viewportChanged) {
|
||||
|
|
|
@ -585,7 +585,6 @@ Readability.prototype = {
|
|||
}
|
||||
|
||||
let topCandidate = topCandidates[0] || null;
|
||||
let lastTopCandidate = (topCandidates.length > 3 ? topCandidates[topCandidates.length - 1] : null);
|
||||
|
||||
// If we still have no top candidate, just use the body as a last resort.
|
||||
// We also have to copy the body node so it is something we can modify.
|
||||
|
@ -704,17 +703,6 @@ Readability.prototype = {
|
|||
return null;
|
||||
}
|
||||
} else {
|
||||
if (lastTopCandidate !== null) {
|
||||
// EXPERIMENTAL: Contrast ratio is how we measure the level of competition between candidates in the
|
||||
// readability algorithm. This is to avoid offering reader mode on pages that are more like
|
||||
// a list or directory of links with summaries. It takes the score of the last top candidate
|
||||
// (see N_TOP_CANDIDATES) and checks how it compares to the top candidate's. On pages that are not
|
||||
// actual articles, there will likely be many candidates with similar score (i.e. higher contrast ratio).
|
||||
let contrastRatio = lastTopCandidate.readability.contentScore / topCandidate.readability.contentScore;
|
||||
if (contrastRatio > 0.45)
|
||||
return null;
|
||||
}
|
||||
|
||||
return articleContent;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -127,7 +127,7 @@ XPCOMUtils.defineLazyModuleGetter(this, "SharedPreferences",
|
|||
#endif
|
||||
["MemoryObserver", ["memory-pressure", "Memory:Dump"], "chrome://browser/content/MemoryObserver.js"],
|
||||
["ConsoleAPI", ["console-api-log-event"], "chrome://browser/content/ConsoleAPI.js"],
|
||||
["FindHelper", ["FindInPage:Find", "FindInPage:Prev", "FindInPage:Next", "FindInPage:Closed", "Tab:Selected"], "chrome://browser/content/FindHelper.js"],
|
||||
["FindHelper", ["FindInPage:Opened", "FindInPage:Closed", "Tab:Selected"], "chrome://browser/content/FindHelper.js"],
|
||||
["PermissionsHelper", ["Permissions:Get", "Permissions:Clear"], "chrome://browser/content/PermissionsHelper.js"],
|
||||
["FeedHandler", ["Feeds:Subscribe"], "chrome://browser/content/FeedHandler.js"],
|
||||
["Feedback", ["Feedback:Show"], "chrome://browser/content/Feedback.js"],
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
"use strict";
|
||||
|
||||
this.EXPORTED_SYMBOLS = [
|
||||
"Assert_rejects",
|
||||
"initializeIdentityWithTokenServerResponse",
|
||||
];
|
||||
|
||||
|
@ -14,17 +13,6 @@ Cu.import("resource://services-common/tokenserverclient.js");
|
|||
Cu.import("resource://testing-common/services/common/logging.js");
|
||||
Cu.import("resource://testing-common/services/sync/utils.js");
|
||||
|
||||
// This shouldn't be here - it should be part of the xpcshell harness.
|
||||
// Maybe as Assert.rejects - so we name it like that.
|
||||
function Assert_rejects(promise, message) {
|
||||
let deferred = Promise.defer();
|
||||
promise.then(
|
||||
() => deferred.reject(message || "Expected the promise to be rejected"),
|
||||
deferred.resolve
|
||||
);
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
// Create a new browserid_identity object and initialize it with a
|
||||
// mocked TokenServerClient which always receives the specified response.
|
||||
this.initializeIdentityWithTokenServerResponse = function(response) {
|
||||
|
|
|
@ -269,12 +269,12 @@ add_task(function test_ensureLoggedIn() {
|
|||
Assert.ok(!browseridManager._shouldHaveSyncKeyBundle,
|
||||
"_shouldHaveSyncKeyBundle should be false so we know we are testing what we think we are.");
|
||||
Status.login = LOGIN_FAILED_NO_USERNAME;
|
||||
yield Assert_rejects(browseridManager.ensureLoggedIn(), "expecting rejection due to no user");
|
||||
yield Assert.rejects(browseridManager.ensureLoggedIn(), "expecting rejection due to no user");
|
||||
Assert.ok(browseridManager._shouldHaveSyncKeyBundle,
|
||||
"_shouldHaveSyncKeyBundle should always be true after ensureLogin completes.");
|
||||
fxa.internal.currentAccountState.signedInUser = signedInUser;
|
||||
Status.login = LOGIN_FAILED_LOGIN_REJECTED;
|
||||
yield Assert_rejects(browseridManager.ensureLoggedIn(),
|
||||
yield Assert.rejects(browseridManager.ensureLoggedIn(),
|
||||
"LOGIN_FAILED_LOGIN_REJECTED should have caused immediate rejection");
|
||||
Assert.equal(Status.login, LOGIN_FAILED_LOGIN_REJECTED,
|
||||
"status should remain LOGIN_FAILED_LOGIN_REJECTED");
|
||||
|
@ -360,7 +360,7 @@ add_task(function test_getTokenErrors() {
|
|||
let browseridManager = Service.identity;
|
||||
|
||||
yield browseridManager.initializeWithCurrentIdentity();
|
||||
yield Assert_rejects(browseridManager.whenReadyToAuthenticate.promise,
|
||||
yield Assert.rejects(browseridManager.whenReadyToAuthenticate.promise,
|
||||
"should reject due to 401");
|
||||
Assert.equal(Status.login, LOGIN_FAILED_LOGIN_REJECTED, "login was rejected");
|
||||
|
||||
|
@ -376,7 +376,7 @@ add_task(function test_getTokenErrors() {
|
|||
});
|
||||
browseridManager = Service.identity;
|
||||
yield browseridManager.initializeWithCurrentIdentity();
|
||||
yield Assert_rejects(browseridManager.whenReadyToAuthenticate.promise,
|
||||
yield Assert.rejects(browseridManager.whenReadyToAuthenticate.promise,
|
||||
"should reject due to non-JSON response");
|
||||
Assert.equal(Status.login, LOGIN_FAILED_NETWORK_ERROR, "login state is LOGIN_FAILED_NETWORK_ERROR");
|
||||
});
|
||||
|
@ -397,7 +397,7 @@ add_task(function test_getTokenErrorWithRetry() {
|
|||
let browseridManager = Service.identity;
|
||||
|
||||
yield browseridManager.initializeWithCurrentIdentity();
|
||||
yield Assert_rejects(browseridManager.whenReadyToAuthenticate.promise,
|
||||
yield Assert.rejects(browseridManager.whenReadyToAuthenticate.promise,
|
||||
"should reject due to 503");
|
||||
|
||||
// The observer should have fired - check it got the value in the response.
|
||||
|
@ -416,7 +416,7 @@ add_task(function test_getTokenErrorWithRetry() {
|
|||
browseridManager = Service.identity;
|
||||
|
||||
yield browseridManager.initializeWithCurrentIdentity();
|
||||
yield Assert_rejects(browseridManager.whenReadyToAuthenticate.promise,
|
||||
yield Assert.rejects(browseridManager.whenReadyToAuthenticate.promise,
|
||||
"should reject due to no token in response");
|
||||
|
||||
// The observer should have fired - check it got the value in the response.
|
||||
|
@ -448,7 +448,7 @@ add_task(function test_getKeysErrorWithBackoff() {
|
|||
});
|
||||
|
||||
let browseridManager = Service.identity;
|
||||
yield Assert_rejects(browseridManager.whenReadyToAuthenticate.promise,
|
||||
yield Assert.rejects(browseridManager.whenReadyToAuthenticate.promise,
|
||||
"should reject due to 503");
|
||||
|
||||
// The observer should have fired - check it got the value in the response.
|
||||
|
@ -482,7 +482,7 @@ add_task(function test_getKeysErrorWithRetry() {
|
|||
});
|
||||
|
||||
let browseridManager = Service.identity;
|
||||
yield Assert_rejects(browseridManager.whenReadyToAuthenticate.promise,
|
||||
yield Assert.rejects(browseridManager.whenReadyToAuthenticate.promise,
|
||||
"should reject due to 503");
|
||||
|
||||
// The observer should have fired - check it got the value in the response.
|
||||
|
@ -666,7 +666,7 @@ function* initializeIdentityWithHAWKResponseFactory(config, cbGetResponse) {
|
|||
browseridManager._fxaService = fxa;
|
||||
browseridManager._signedInUser = null;
|
||||
yield browseridManager.initializeWithCurrentIdentity();
|
||||
yield Assert_rejects(browseridManager.whenReadyToAuthenticate.promise,
|
||||
yield Assert.rejects(browseridManager.whenReadyToAuthenticate.promise,
|
||||
"expecting rejection due to hawk error");
|
||||
}
|
||||
|
||||
|
|
|
@ -17,7 +17,7 @@ add_task(function test_findCluster() {
|
|||
});
|
||||
|
||||
yield Service.identity.initializeWithCurrentIdentity();
|
||||
yield Assert_rejects(Service.identity.whenReadyToAuthenticate.promise,
|
||||
yield Assert.rejects(Service.identity.whenReadyToAuthenticate.promise,
|
||||
"should reject due to 500");
|
||||
|
||||
Assert.throws(function() {
|
||||
|
@ -32,7 +32,7 @@ add_task(function test_findCluster() {
|
|||
});
|
||||
|
||||
yield Service.identity.initializeWithCurrentIdentity();
|
||||
yield Assert_rejects(Service.identity.whenReadyToAuthenticate.promise,
|
||||
yield Assert.rejects(Service.identity.whenReadyToAuthenticate.promise,
|
||||
"should reject due to 401");
|
||||
|
||||
cluster = Service._clusterManager._findCluster();
|
||||
|
|
|
@ -16,6 +16,10 @@ this.EXPORTED_SYMBOLS = [
|
|||
"Assert"
|
||||
];
|
||||
|
||||
Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "Promise",
|
||||
"resource://gre/modules/Promise.jsm");
|
||||
/**
|
||||
* 1. The assert module provides functions that throw AssertionError's when
|
||||
* particular conditions are not met.
|
||||
|
@ -442,3 +446,34 @@ proto.throws = function(block, expected, message) {
|
|||
|
||||
this.report(false, expected, expected, message);
|
||||
};
|
||||
|
||||
/**
|
||||
* A promise that is expected to reject:
|
||||
* assert.rejects(promise, expected, message);
|
||||
*
|
||||
* @param promise
|
||||
* (promise) A promise that is expected to reject
|
||||
* @param expected (optional)
|
||||
* (mixed) Test reference to evaluate against the rejection result
|
||||
* @param message (optional)
|
||||
* (string) Short explanation of the expected result
|
||||
*/
|
||||
proto.rejects = function(promise, expected, message) {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (typeof expected === "string") {
|
||||
message = expected;
|
||||
expected = null;
|
||||
}
|
||||
return promise.then(
|
||||
() => this.report(true, null, expected, "Missing expected exception " + message),
|
||||
err => {
|
||||
if (expected && !expectedException(err, expected)) {
|
||||
reject(err);
|
||||
return;
|
||||
}
|
||||
this.report(false, err, expected, message);
|
||||
resolve();
|
||||
}
|
||||
).then(null, reject);
|
||||
});
|
||||
};
|
||||
|
|
|
@ -297,4 +297,47 @@ function run_test() {
|
|||
expected: "foo",
|
||||
operator: "="
|
||||
}).message, "[object Object] = \"foo\"");
|
||||
|
||||
run_next_test();
|
||||
}
|
||||
|
||||
add_task(function* test_rejects() {
|
||||
let ns = {};
|
||||
Components.utils.import("resource://testing-common/Assert.jsm", ns);
|
||||
let assert = new ns.Assert();
|
||||
|
||||
// A helper function to test failures.
|
||||
function* checkRejectsFails(err, expected) {
|
||||
try {
|
||||
yield assert.rejects(Promise.reject(err), expected);
|
||||
ok(false, "should have thrown");
|
||||
} catch(ex) {
|
||||
deepEqual(ex, err, "Assert.rejects threw the original unexpected error");
|
||||
}
|
||||
}
|
||||
|
||||
// A "throwable" error that's not an actual Error().
|
||||
let SomeErrorLikeThing = function() {};
|
||||
|
||||
// The actual tests...
|
||||
// No "expected" or "message" values supplied.
|
||||
yield assert.rejects(Promise.reject(new Error("oh no")));
|
||||
yield assert.rejects(Promise.reject("oh no"));
|
||||
|
||||
// An explicit error object:
|
||||
// An instance to check against.
|
||||
yield assert.rejects(Promise.reject(new Error("oh no")), Error, "rejected");
|
||||
// A regex to match against the message.
|
||||
yield assert.rejects(Promise.reject(new Error("oh no")), /oh no/, "rejected");
|
||||
|
||||
// Failure cases:
|
||||
// An instance to check against that doesn't match.
|
||||
yield checkRejectsFails(new Error("something else"), SomeErrorLikeThing);
|
||||
// A regex that doesn't match.
|
||||
yield checkRejectsFails(new Error("something else"), /oh no/);
|
||||
|
||||
// Check simple string messages.
|
||||
yield assert.rejects(Promise.reject("oh no"), /oh no/, "rejected");
|
||||
// Wrong message.
|
||||
yield checkRejectsFails("something else", /oh no/);
|
||||
});
|
||||
|
|
|
@ -401,7 +401,7 @@
|
|||
}
|
||||
|
||||
.addon[active="false"] .icon {
|
||||
filter: url("chrome://mozapps/skin/extensions/extensions.svg#greyscale");
|
||||
filter: grayscale(1);
|
||||
}
|
||||
|
||||
.addon-view[type="theme"] .icon {
|
||||
|
|
|
@ -146,7 +146,7 @@
|
|||
.addon:not([active]) .addon-icon,
|
||||
#disable-list .addon-icon,
|
||||
#incompatible-list .addon-icon {
|
||||
filter: url("chrome://mozapps/skin/extensions/extensions.svg#greyscale");
|
||||
filter: grayscale(1);
|
||||
}
|
||||
|
||||
#footer {
|
||||
|
|
|
@ -466,7 +466,7 @@
|
|||
}
|
||||
|
||||
.addon[active="false"] .icon {
|
||||
filter: url("chrome://mozapps/skin/extensions/extensions.svg#greyscale");
|
||||
filter: grayscale(1);
|
||||
}
|
||||
|
||||
.addon-view[type="theme"] .icon {
|
||||
|
|
|
@ -1,10 +0,0 @@
|
|||
<?xml version="1.0"?>
|
||||
<!-- 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/. -->
|
||||
|
||||
<svg xmlns="http://www.w3.org/2000/svg">
|
||||
<filter id="greyscale">
|
||||
<feColorMatrix type="saturate" values="0"/>
|
||||
</filter>
|
||||
</svg>
|
До Ширина: | Высота: | Размер: 369 B |
|
@ -145,7 +145,7 @@
|
|||
.addon:not([active]) .addon-icon,
|
||||
#disable-list .addon-icon,
|
||||
#incompatible-list .addon-icon {
|
||||
filter: url("chrome://mozapps/skin/extensions/extensions.svg#greyscale");
|
||||
filter: grayscale(1);
|
||||
}
|
||||
|
||||
#footer {
|
||||
|
|
|
@ -47,7 +47,6 @@ toolkit.jar:
|
|||
skin/classic/mozapps/extensions/search.png (extensions/search.png)
|
||||
skin/classic/mozapps/extensions/about.css (extensions/about.css)
|
||||
* skin/classic/mozapps/extensions/extensions.css (extensions/extensions.css)
|
||||
skin/classic/mozapps/extensions/extensions.svg (extensions/extensions.svg)
|
||||
* skin/classic/mozapps/extensions/selectAddons.css (extensions/selectAddons.css)
|
||||
skin/classic/mozapps/extensions/update.css (extensions/update.css)
|
||||
skin/classic/mozapps/extensions/eula.css (extensions/eula.css)
|
||||
|
|
|
@ -473,7 +473,7 @@
|
|||
}
|
||||
|
||||
.addon[active="false"] .icon {
|
||||
filter: url("chrome://mozapps/skin/extensions/extensions.svg#greyscale");
|
||||
filter: grayscale(1);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -1,10 +0,0 @@
|
|||
<?xml version="1.0"?>
|
||||
<!-- 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/. -->
|
||||
|
||||
<svg xmlns="http://www.w3.org/2000/svg">
|
||||
<filter id="greyscale">
|
||||
<feColorMatrix type="saturate" values="0"/>
|
||||
</filter>
|
||||
</svg>
|
До Ширина: | Высота: | Размер: 369 B |
|
@ -158,7 +158,7 @@
|
|||
.addon:not([active]) .addon-icon,
|
||||
#disable-list .addon-icon,
|
||||
#incompatible-list .addon-icon {
|
||||
filter: url("chrome://mozapps/skin/extensions/extensions.svg#greyscale");
|
||||
filter: grayscale(1);
|
||||
}
|
||||
|
||||
#footer {
|
||||
|
|
|
@ -16,7 +16,6 @@ toolkit.jar:
|
|||
* skin/classic/mozapps/extensions/extensions.css (extensions/extensions.css)
|
||||
* skin/classic/mozapps/extensions/selectAddons.css (extensions/selectAddons.css)
|
||||
skin/classic/mozapps/extensions/update.css (extensions/update.css)
|
||||
skin/classic/mozapps/extensions/extensions.svg (extensions/extensions.svg)
|
||||
skin/classic/mozapps/extensions/category-search.png (extensions/category-search.png)
|
||||
skin/classic/mozapps/extensions/category-discover.png (extensions/category-discover.png)
|
||||
skin/classic/mozapps/extensions/category-languages.png (extensions/localeGeneric.png)
|
||||
|
@ -99,7 +98,6 @@ toolkit.jar:
|
|||
* skin/classic/aero/mozapps/extensions/extensions.css (extensions/extensions-aero.css)
|
||||
* skin/classic/aero/mozapps/extensions/selectAddons.css (extensions/selectAddons-aero.css)
|
||||
skin/classic/aero/mozapps/extensions/update.css (extensions/update.css)
|
||||
skin/classic/aero/mozapps/extensions/extensions.svg (extensions/extensions.svg)
|
||||
skin/classic/aero/mozapps/extensions/category-search.png (extensions/category-search.png)
|
||||
skin/classic/aero/mozapps/extensions/category-discover.png (extensions/category-discover-aero.png)
|
||||
skin/classic/aero/mozapps/extensions/category-languages.png (extensions/localeGeneric-aero.png)
|
||||
|
|
Загрузка…
Ссылка в новой задаче