Merge fx-team to m-c. a=merge

This commit is contained in:
Ryan VanderMeulen 2014-09-11 17:58:43 -04:00
commit 9f26e8f932
36 changed files with 765 additions and 449 deletions

View File

@ -22,6 +22,57 @@ loop.panel = (function(_, mozL10n) {
*/
var router;
var TabView = React.createClass({displayName: 'TabView',
getInitialState: function() {
return {
selectedTab: "call"
};
},
handleSelectTab: function(event) {
var tabName = event.target.dataset.tabName;
this.setState({selectedTab: tabName});
if (this.props.onSelect) {
this.props.onSelect(tabName);
}
},
render: function() {
var cx = React.addons.classSet;
var tabButtons = [];
var tabs = [];
React.Children.forEach(this.props.children, function(tab, i) {
var tabName = tab.props.name;
var isSelected = (this.state.selectedTab == tabName);
tabButtons.push(
React.DOM.li({className: cx({selected: isSelected}),
key: i,
'data-tab-name': tabName,
onClick: this.handleSelectTab}
)
);
tabs.push(
React.DOM.div({key: i, className: cx({tab: true, selected: isSelected})},
tab.props.children
)
);
}, this);
return (
React.DOM.div({className: "tab-view-container"},
React.DOM.ul({className: "tab-view"}, tabButtons),
tabs
)
);
}
});
var Tab = React.createClass({displayName: 'Tab',
render: function() {
return null;
}
});
/**
* Dropdown menu mixin.
* @type {Object}
@ -424,10 +475,17 @@ loop.panel = (function(_, mozL10n) {
return (
React.DOM.div(null,
NotificationListView({notifications: this.props.notifications}),
CallUrlResult({client: this.props.client,
notifications: this.props.notifications,
callUrl: this.props.callUrl}),
ToSView(null),
TabView({onSelect: this.selectTab},
Tab({name: "call"},
CallUrlResult({client: this.props.client,
notifications: this.props.notifications,
callUrl: this.props.callUrl}),
ToSView(null)
),
Tab({name: "contacts"},
React.DOM.span(null, "contacts")
)
),
React.DOM.div({className: "footer"},
AvailabilityDropdown(null),
AuthLink(null),

View File

@ -22,6 +22,57 @@ loop.panel = (function(_, mozL10n) {
*/
var router;
var TabView = React.createClass({
getInitialState: function() {
return {
selectedTab: "call"
};
},
handleSelectTab: function(event) {
var tabName = event.target.dataset.tabName;
this.setState({selectedTab: tabName});
if (this.props.onSelect) {
this.props.onSelect(tabName);
}
},
render: function() {
var cx = React.addons.classSet;
var tabButtons = [];
var tabs = [];
React.Children.forEach(this.props.children, function(tab, i) {
var tabName = tab.props.name;
var isSelected = (this.state.selectedTab == tabName);
tabButtons.push(
<li className={cx({selected: isSelected})}
key={i}
data-tab-name={tabName}
onClick={this.handleSelectTab}>
</li>
);
tabs.push(
<div key={i} className={cx({tab: true, selected: isSelected})}>
{tab.props.children}
</div>
);
}, this);
return (
<div className="tab-view-container">
<ul className="tab-view">{tabButtons}</ul>
{tabs}
</div>
);
}
});
var Tab = React.createClass({
render: function() {
return null;
}
});
/**
* Dropdown menu mixin.
* @type {Object}
@ -424,10 +475,17 @@ loop.panel = (function(_, mozL10n) {
return (
<div>
<NotificationListView notifications={this.props.notifications} />
<CallUrlResult client={this.props.client}
notifications={this.props.notifications}
callUrl={this.props.callUrl} />
<ToSView />
<TabView onSelect={this.selectTab}>
<Tab name="call">
<CallUrlResult client={this.props.client}
notifications={this.props.notifications}
callUrl={this.props.callUrl} />
<ToSView />
</Tab>
<Tab name="contacts">
<span>contacts</span>
</Tab>
</TabView>
<div className="footer">
<AvailabilityDropdown />
<AuthLink />

View File

@ -4,12 +4,6 @@
/* Panel styles */
.panel {
/* XXX force proper content positioning by adding extra margin space
* taken away by reset.css
*/
margin-top: 7px;
margin-bottom: 7px;
/* hide the extra margin space that the panel resizer now wants to show */
overflow: hidden;
}
@ -18,6 +12,74 @@
margin-bottom: 1em;
}
.tab-view,
.tab-view > li {
margin: 0;
padding: 0;
border: 0;
vertical-align: baseline;
}
.tab-view {
display: flex;
flex-direction: row;
padding: 10px;
border-bottom: 1px solid #ccc;
background: #fafafa;
border-top-right-radius: 2px;
border-top-left-radius: 2px;
list-style: none;
}
.tab-view > li {
flex: 1;
text-align: center;
color: #ccc;
border-right: 1px solid #ccc;
padding: 0 10px;
height: 16px;
cursor: pointer;
background-repeat: no-repeat;
background-size: 16px 16px;
background-position: center;
}
.tab-view > li[data-tab-name="call"] {
background-image: url("../img/icons-16x16.svg#precall");
}
.tab-view > li[data-tab-name="call"]:hover {
background-image: url("../img/icons-16x16.svg#precall-hover");
}
.tab-view > li[data-tab-name="call"].selected {
background-image: url("../img/icons-16x16.svg#precall-active");
}
.tab-view > li[data-tab-name="contacts"] {
background-image: url("../img/icons-16x16.svg#contacts");
}
.tab-view > li[data-tab-name="contacts"]:hover {
background-image: url("../img/icons-16x16.svg#contacts-hover");
}
.tab-view > li[data-tab-name="contacts"].selected {
background-image: url("../img/icons-16x16.svg#contacts-active");
}
.tab-view > li:last-child {
border-right: 0;
}
.tab {
display: none;
}
.tab.selected {
display: block;
}
.share {
background: #fbfbfb;
}

View File

@ -0,0 +1,117 @@
<?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"
xmlns:xlink="http://www.w3.org/1999/xlink"
x="0px" y="0px"
viewBox="0 0 16 16"
enable-background="new 0 0 16 16"
xml:space="preserve">
<style>
use:not(:target) {
display: none;
}
use {
fill: #ccc;
}
use[id$="-hover"] {
fill: #444;
}
use[id$="-active"] {
fill: #0095dd;
}
</style>
<defs style="display:none">
<path id="audio-shape" fill-rule="evenodd" clip-rule="evenodd" d="M11.429,6.857v2.286c0,1.894-1.535,3.429-3.429,3.429
c-1.894,0-3.429-1.535-3.429-3.429V6.857H3.429v2.286c0,2.129,1.458,3.913,3.429,4.422v1.293H6.286
c-0.746,0-1.379,0.477-1.615,1.143h6.658c-0.236-0.665-0.869-1.143-1.615-1.143H9.143v-1.293c1.971-0.508,3.429-2.292,3.429-4.422
V6.857H11.429z M8,12c1.578,0,2.857-1.279,2.857-2.857V2.857C10.857,1.279,9.578,0,8,0C6.422,0,5.143,1.279,5.143,2.857v6.286
C5.143,10.721,6.422,12,8,12z"/>
<path id="block-shape" fill-rule="evenodd" clip-rule="evenodd" d="M8,0C3.582,0,0,3.582,0,8c0,4.418,3.582,8,8,8
c4.418,0,8-3.582,8-8C16,3.582,12.418,0,8,0z M8,2.442c1.073,0,2.075,0.301,2.926,0.821l-7.673,7.673
C2.718,10.085,2.408,9.079,2.408,8C2.408,4.931,4.911,2.442,8,2.442z M8,13.557c-1.073,0-2.075-0.301-2.926-0.821l7.673-7.673
C13.282,5.915,13.592,6.921,13.592,8C13.592,11.069,11.089,13.557,8,13.557z"/>
<path id="contacts-shape" fill-rule="evenodd" clip-rule="evenodd" d="M8,6.526c1.802,0,3.263-1.461,3.263-3.263
C11.263,1.461,9.802,0,8,0C6.198,0,4.737,1.461,4.737,3.263C4.737,5.066,6.198,6.526,8,6.526z M14.067,11.421c0,0,0-0.001,0-0.001
c0-1.676-1.397-3.119-3.419-3.807L8.001,10.26L5.354,7.613C3.331,8.3,1.933,9.744,1.933,11.42v0.001H1.93
c0,1.679,0.328,3.246,0.896,4.579h10.348c0.568-1.333,0.896-2.9,0.896-4.579H14.067z"/>
<g id="google-shape">
<path fill-rule="evenodd" clip-rule="evenodd" d="M8.001,9.278c-0.9,0.03-1.989,0.454-2.144,1.274
c-0.292,1.54,1.284,2.004,2.455,1.932c1.097-0.067,1.737-0.593,1.813-1.26c0.063-0.554-0.184-1.153-0.959-1.644
c-0.142-0.09-0.28-0.185-0.413-0.282C8.504,9.291,8.25,9.27,8.001,9.278z"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M7.381,3.409C6.638,3.64,6.32,4.405,6.627,5.61
C6.908,6.708,7.78,7.322,8.569,7.104c0.77-0.213,0.987-1.021,0.847-1.873C9.201,3.929,8.261,3.136,7.381,3.409z"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M8,0C3.582,0,0,3.582,0,8s3.582,8,8,8c4.418,0,8-3.582,8-8
S12.418,0,8,0z M10.544,4.471c0.17,0.453,0.194,0.954,0.021,1.416c-0.163,0.436-0.495,0.811-0.982,1.096
C9.307,7.146,9.167,7.351,9.151,7.548c-0.045,0.575,0.658,0.993,1.064,1.297c0.889,0.666,1.236,1.758,0.648,2.813
c-0.562,1.007-1.901,1.457-3.322,1.462c-1.766-0.008-2.88-0.817-2.938-1.918C4.527,9.779,5.987,9.101,7.307,8.947
c0.369-0.043,0.7-0.036,1.01-0.014C7.85,8.625,7.675,7.998,7.914,7.58c0.062-0.109,0.023-0.072-0.095-0.054
C6.739,7.689,5.628,6.985,5.367,5.92c-0.132-0.54-0.05-1.105,0.156-1.547C5.97,3.413,6.964,2.88,8.067,2.88
c1.147,0,2.209,0,3.334,0.009L10.612,3.4H9.714C10.093,3.665,10.384,4.046,10.544,4.471z"/>
</g>
<path id="history-shape" fill-rule="evenodd" clip-rule="evenodd" d="M8,16c-4.418,0-8-3.582-8-8c0-4.418,3.582-8,8-8
c4.418,0,8,3.582,8,8C16,12.418,12.418,16,8,16z M8,2.442C4.911,2.442,2.408,4.931,2.408,8c0,3.069,2.504,5.557,5.592,5.557
S13.592,11.069,13.592,8C13.592,4.931,11.089,2.442,8,2.442z M7.649,9.048C7.206,8.899,6.882,8.493,6.882,8V4.645
c0-0.618,0.501-1.119,1.118-1.119c0.618,0,1.119,0.501,1.119,1.119v3.078c1.176,1.22,2.237,3.633,2.237,3.633
S8.844,10.252,7.649,9.048z"/>
<path id="precall-shape" fill-rule="evenodd" clip-rule="evenodd" d="M8.014,0.003c-4.411,0-7.987,3.576-7.987,7.986
c0,1.642,0.496,3.168,1.346,4.437L0,15.997l3.568-1.372c1.271,0.853,2.8,1.352,4.446,1.352c4.411,0,7.986-3.576,7.986-7.987
C16,3.579,12.424,0.003,8.014,0.003z"/>
<path id="settings-shape" fill-rule="evenodd" clip-rule="evenodd" d="M14.77,8c0,0.804,0.262,1.548,0.634,1.678L16,9.887
c-0.205,0.874-0.553,1.692-1.011,2.434l-0.567-0.272c-0.355-0.171-1.066,0.17-1.635,0.738c-0.569,0.569-0.909,1.279-0.738,1.635
l0.273,0.568c-0.741,0.46-1.566,0.79-2.438,0.998l-0.205-0.584c-0.13-0.372-0.874-0.634-1.678-0.634s-1.548,0.262-1.678,0.634
l-0.209,0.596c-0.874-0.205-1.692-0.553-2.434-1.011l0.272-0.567c0.171-0.355-0.17-1.066-0.739-1.635
c-0.568-0.568-1.279-0.909-1.635-0.738l-0.568,0.273c-0.46-0.741-0.79-1.566-0.998-2.439l0.584-0.205
C0.969,9.547,1.231,8.804,1.231,8c0-0.804-0.262-1.548-0.634-1.678L0,6.112c0.206-0.874,0.565-1.685,1.025-2.427l0.554,0.266
c0.355,0.171,1.066-0.17,1.635-0.738c0.569-0.568,0.909-1.28,0.739-1.635L3.686,1.025c0.742-0.46,1.553-0.818,2.427-1.024
l0.209,0.596C6.453,0.969,7.197,1.23,8.001,1.23s1.548-0.262,1.678-0.634l0.209-0.596c0.874,0.205,1.692,0.553,2.434,1.011
l-0.272,0.567c-0.171,0.355,0.17,1.066,0.738,1.635c0.569,0.568,1.279,0.909,1.635,0.738l0.568-0.273
c0.46,0.741,0.79,1.566,0.998,2.438l-0.584,0.205C15.032,6.452,14.77,7.196,14.77,8z M8.001,3.661C5.604,3.661,3.661,5.603,3.661,8
c0,2.397,1.943,4.34,4.339,4.34c2.397,0,4.339-1.943,4.339-4.34C12.34,5.603,10.397,3.661,8.001,3.661z"/>
<path id="tag-shape" fill-rule="evenodd" clip-rule="evenodd" d="M15.578,7.317L9.659,1.398
C9.374,1.033,8.955,0.777,8.471,0.761L2.556,0C1.72-0.027-0.027,1.72,0,2.556l0.761,5.916c0.016,0.484,0.272,0.902,0.637,1.188
l5.919,5.919c0.591,0.591,1.584,0.557,2.218-0.076l5.966-5.966C16.135,8.902,16.169,7.909,15.578,7.317z M4.222,4.163
c-0.511,0.511-1.339,0.511-1.85,0c-0.511-0.511-0.511-1.339,0-1.85c0.511-0.511,1.339-0.511,1.85,0
C4.733,2.823,4.733,3.652,4.222,4.163z"/>
<path id="unblock-shape" fill-rule="evenodd" clip-rule="evenodd" d="M8,16c-4.418,0-8-3.582-8-8c0-4.418,3.582-8,8-8
c4.418,0,8,3.582,8,8C16,12.418,12.418,16,8,16z M8,2.442C4.911,2.442,2.408,4.931,2.408,8c0,3.069,2.504,5.557,5.592,5.557
S13.592,11.069,13.592,8C13.592,4.931,11.089,2.442,8,2.442z"/>
<path id="video-shape" fill-rule="evenodd" clip-rule="evenodd" d="M14.9,3.129l-3.476,3.073V3.873c0-0.877-0.663-1.587-1.482-1.587
H1.482C0.663,2.286,0,2.996,0,3.873v8.254c0,0.877,0.663,1.587,1.482,1.587h8.461c0.818,0,1.482-0.711,1.482-1.587V9.762
l3.476,3.073c0.3,0.321,0.714,0.416,1.1,0.331V2.798C15.614,2.713,15.2,2.808,14.9,3.129z"/>
</defs>
<use id="audio" xlink:href="#audio-shape"/>
<use id="audio-hover" xlink:href="#audio-shape"/>
<use id="audio-active" xlink:href="#audio-shape"/>
<use id="block" xlink:href="#block-shape"/>
<use id="block-hover" xlink:href="#block-shape"/>
<use id="block-active" xlink:href="#block-shape"/>
<use id="contacts" xlink:href="#contacts-shape"/>
<use id="contacts-hover" xlink:href="#contacts-shape"/>
<use id="contacts-active" xlink:href="#contacts-shape"/>
<use id="google" xlink:href="#google-shape"/>
<use id="google-hover" xlink:href="#google-shape"/>
<use id="google-active" xlink:href="#google-shape"/>
<use id="history" xlink:href="#history-shape"/>
<use id="history-hover" xlink:href="#history-shape"/>
<use id="history-active" xlink:href="#history-shape"/>
<use id="precall" xlink:href="#precall-shape"/>
<use id="precall-hover" xlink:href="#precall-shape"/>
<use id="precall-active" xlink:href="#precall-shape"/>
<use id="settings" xlink:href="#settings-shape"/>
<use id="settings-hover" xlink:href="#settings-shape"/>
<use id="settings-active" xlink:href="#settings-shape"/>
<use id="tag" xlink:href="#tag-shape"/>
<use id="tag-hover" xlink:href="#tag-shape"/>
<use id="tag-active" xlink:href="#tag-shape"/>
<use id="unblock" xlink:href="#unblock-shape"/>
<use id="unblock-hover" xlink:href="#unblock-shape"/>
<use id="unblock-active" xlink:href="#unblock-shape"/>
<use id="video" xlink:href="#video-shape"/>
<use id="video-hover" xlink:href="#video-shape"/>
<use id="video-active" xlink:href="#video-shape"/>
</svg>

After

Width:  |  Height:  |  Size: 8.1 KiB

View File

@ -16,12 +16,6 @@ loop.shared.router = (function() {
* @link http://mikeygee.com/blog/backbone.html
*/
var BaseRouter = Backbone.Router.extend({
/**
* Active view.
* @type {Object}
*/
_activeView: undefined,
/**
* Notifications collection.
* @type {loop.shared.models.NotificationCollection}
@ -46,17 +40,6 @@ loop.shared.router = (function() {
Backbone.Router.apply(this, arguments);
},
/**
* Loads and render current active view.
*
* @param {loop.shared.views.BaseView} view View.
*/
loadView: function(view) {
this.clearActiveView();
this._activeView = {type: "backbone", view: view.render().show()};
this.updateView(this._activeView.view.$el);
},
/**
* Renders a React component as current active view.
*
@ -64,34 +47,15 @@ loop.shared.router = (function() {
*/
loadReactComponent: function(reactComponent) {
this.clearActiveView();
this._activeView = {
type: "react",
view: React.renderComponent(reactComponent,
document.querySelector("#main"))
};
React.renderComponent(reactComponent,
document.querySelector("#main"));
},
/**
* Clears current active view.
*/
clearActiveView: function() {
if (!this._activeView) {
return;
}
if (this._activeView.type === "react") {
React.unmountComponentAtNode(document.querySelector("#main"));
} else {
this._activeView.view.remove();
}
},
/**
* Updates main div element with provided contents.
*
* @param {jQuery} $el Element.
*/
updateView: function($el) {
$("#main").html($el);
React.unmountComponentAtNode(document.querySelector("#main"));
}
});

View File

@ -14,83 +14,6 @@ loop.shared.views = (function(_, OT, l10n) {
var sharedModels = loop.shared.models;
var WINDOW_AUTOCLOSE_TIMEOUT_IN_SECONDS = 5;
/**
* L10n view. Translates resulting view DOM fragment once rendered.
*/
var L10nView = (function() {
var L10nViewImpl = Backbone.View.extend(), // Original View constructor
originalExtend = L10nViewImpl.extend; // Original static extend fn
/**
* Patches View extend() method so we can hook and patch any declared render
* method.
*
* @return {Backbone.View} Extended view with patched render() method.
*/
L10nViewImpl.extend = function() {
var ExtendedView = originalExtend.apply(this, arguments),
originalRender = ExtendedView.prototype.render;
/**
* Wraps original render() method to translate contents once they're
* rendered.
*
* @return {Backbone.View} Extended view instance.
*/
ExtendedView.prototype.render = function() {
if (originalRender) {
originalRender.apply(this, arguments);
l10n.translate(this.el);
}
return this;
};
return ExtendedView;
};
return L10nViewImpl;
})();
/**
* Base view.
*/
var BaseView = L10nView.extend({
/**
* Hides view element.
*
* @return {BaseView}
*/
hide: function() {
this.$el.hide();
return this;
},
/**
* Shows view element.
*
* @return {BaseView}
*/
show: function() {
this.$el.show();
return this;
},
/**
* Base render implementation: renders an attached template if available.
*
* Note: You need to override this if you want to do fancier stuff, eg.
* rendering the template using model data.
*
* @return {BaseView}
*/
render: function() {
if (this.template) {
this.$el.html(this.template());
}
return this;
}
});
/**
* Media control button.
*
@ -704,43 +627,11 @@ loop.shared.views = (function(_, OT, l10n) {
}
});
/**
* Unsupported Browsers view.
*/
var UnsupportedBrowserView = BaseView.extend({
template: _.template([
'<div>',
' <h2 data-l10n-id="incompatible_browser"></h2>',
' <p data-l10n-id="powered_by_webrtc"></p>',
' <p data-l10n-id="use_latest_firefox" ',
' data-l10n-args=\'{"ff_url": "https://www.mozilla.org/firefox/"}\'>',
' </p>',
'</div>'
].join(""))
});
/**
* Unsupported Browsers view.
*/
var UnsupportedDeviceView = BaseView.extend({
template: _.template([
'<div>',
' <h2 data-l10n-id="incompatible_device"></h2>',
' <p data-l10n-id="sorry_device_unsupported"></p>',
' <p data-l10n-id="use_firefox_windows_mac_linux"></p>',
'</div>'
].join(""))
});
return {
L10nView: L10nView,
BaseView: BaseView,
ConversationView: ConversationView,
ConversationToolbar: ConversationToolbar,
FeedbackView: FeedbackView,
MediaControlButton: MediaControlButton,
NotificationListView: NotificationListView,
UnsupportedBrowserView: UnsupportedBrowserView,
UnsupportedDeviceView: UnsupportedDeviceView
NotificationListView: NotificationListView
};
})(_, window.OT, navigator.mozL10n || document.mozL10n);

View File

@ -14,83 +14,6 @@ loop.shared.views = (function(_, OT, l10n) {
var sharedModels = loop.shared.models;
var WINDOW_AUTOCLOSE_TIMEOUT_IN_SECONDS = 5;
/**
* L10n view. Translates resulting view DOM fragment once rendered.
*/
var L10nView = (function() {
var L10nViewImpl = Backbone.View.extend(), // Original View constructor
originalExtend = L10nViewImpl.extend; // Original static extend fn
/**
* Patches View extend() method so we can hook and patch any declared render
* method.
*
* @return {Backbone.View} Extended view with patched render() method.
*/
L10nViewImpl.extend = function() {
var ExtendedView = originalExtend.apply(this, arguments),
originalRender = ExtendedView.prototype.render;
/**
* Wraps original render() method to translate contents once they're
* rendered.
*
* @return {Backbone.View} Extended view instance.
*/
ExtendedView.prototype.render = function() {
if (originalRender) {
originalRender.apply(this, arguments);
l10n.translate(this.el);
}
return this;
};
return ExtendedView;
};
return L10nViewImpl;
})();
/**
* Base view.
*/
var BaseView = L10nView.extend({
/**
* Hides view element.
*
* @return {BaseView}
*/
hide: function() {
this.$el.hide();
return this;
},
/**
* Shows view element.
*
* @return {BaseView}
*/
show: function() {
this.$el.show();
return this;
},
/**
* Base render implementation: renders an attached template if available.
*
* Note: You need to override this if you want to do fancier stuff, eg.
* rendering the template using model data.
*
* @return {BaseView}
*/
render: function() {
if (this.template) {
this.$el.html(this.template());
}
return this;
}
});
/**
* Media control button.
*
@ -704,43 +627,11 @@ loop.shared.views = (function(_, OT, l10n) {
}
});
/**
* Unsupported Browsers view.
*/
var UnsupportedBrowserView = BaseView.extend({
template: _.template([
'<div>',
' <h2 data-l10n-id="incompatible_browser"></h2>',
' <p data-l10n-id="powered_by_webrtc"></p>',
' <p data-l10n-id="use_latest_firefox" ',
' data-l10n-args=\'{"ff_url": "https://www.mozilla.org/firefox/"}\'>',
' </p>',
'</div>'
].join(""))
});
/**
* Unsupported Browsers view.
*/
var UnsupportedDeviceView = BaseView.extend({
template: _.template([
'<div>',
' <h2 data-l10n-id="incompatible_device"></h2>',
' <p data-l10n-id="sorry_device_unsupported"></p>',
' <p data-l10n-id="use_firefox_windows_mac_linux"></p>',
'</div>'
].join(""))
});
return {
L10nView: L10nView,
BaseView: BaseView,
ConversationView: ConversationView,
ConversationToolbar: ConversationToolbar,
FeedbackView: FeedbackView,
MediaControlButton: MediaControlButton,
NotificationListView: NotificationListView,
UnsupportedBrowserView: UnsupportedBrowserView,
UnsupportedDeviceView: UnsupportedDeviceView
NotificationListView: NotificationListView
};
})(_, window.OT, navigator.mozL10n || document.mozL10n);

View File

@ -44,7 +44,8 @@ browser.jar:
content/browser/loop/shared/img/svg/glyph-account-16x16.svg (content/shared/img/svg/glyph-account-16x16.svg)
content/browser/loop/shared/img/svg/glyph-signin-16x16.svg (content/shared/img/svg/glyph-signin-16x16.svg)
content/browser/loop/shared/img/svg/glyph-signout-16x16.svg (content/shared/img/svg/glyph-signout-16x16.svg)
content/browser/loop/shared/img/audio-call-avatar.svg (content/shared/img/audio-call-avatar.svg)
content/browser/loop/shared/img/audio-call-avatar.svg (content/shared/img/audio-call-avatar.svg)
content/browser/loop/shared/img/icons-16x16.svg (content/shared/img/icons-16x16.svg)
# Shared scripts
content/browser/loop/shared/js/feedbackApiClient.js (content/shared/js/feedbackApiClient.js)

View File

@ -27,8 +27,47 @@ loop.webapp = (function($, _, OT, mozL10n) {
/**
* Homepage view.
*/
var HomeView = sharedViews.BaseView.extend({
template: _.template('<p data-l10n-id="welcome"></p>')
var HomeView = React.createClass({displayName: 'HomeView',
render: function() {
return (
React.DOM.p(null, mozL10n.get("welcome"))
)
}
});
/**
* Unsupported Browsers view.
*/
var UnsupportedBrowserView = React.createClass({displayName: 'UnsupportedBrowserView',
render: function() {
var useLatestFF = mozL10n.get("use_latest_firefox", {
"firefoxBrandNameLink": React.renderComponentToStaticMarkup(
React.DOM.a({target: "_blank", href: "https://www.mozilla.org/firefox/"}, "Firefox")
)
});
return (
React.DOM.div(null,
React.DOM.h2(null, mozL10n.get("incompatible_browser")),
React.DOM.p(null, mozL10n.get("powered_by_webrtc")),
React.DOM.p({dangerouslySetInnerHTML: {__html: useLatestFF}})
)
);
}
});
/**
* Unsupported Device view.
*/
var UnsupportedDeviceView = React.createClass({displayName: 'UnsupportedDeviceView',
render: function() {
return (
React.DOM.div(null,
React.DOM.h2(null, mozL10n.get("incompatible_device")),
React.DOM.p(null, mozL10n.get("sorry_device_unsupported")),
React.DOM.p(null, mozL10n.get("use_firefox_windows_mac_linux"))
)
);
}
});
/**
@ -324,7 +363,7 @@ loop.webapp = (function($, _, OT, mozL10n) {
}
// Load default view
this.loadView(new HomeView());
this.loadReactComponent(HomeView(null));
this.listenTo(this._conversation, "timeout", this._onTimeout);
},
@ -470,15 +509,15 @@ loop.webapp = (function($, _, OT, mozL10n) {
* Default entry point.
*/
home: function() {
this.loadView(new HomeView());
this.loadReactComponent(HomeView(null));
},
unsupportedDevice: function() {
this.loadView(new sharedViews.UnsupportedDeviceView());
this.loadReactComponent(UnsupportedDeviceView(null));
},
unsupportedBrowser: function() {
this.loadView(new sharedViews.UnsupportedBrowserView());
this.loadReactComponent(UnsupportedBrowserView(null));
},
expired: function() {
@ -579,6 +618,8 @@ loop.webapp = (function($, _, OT, mozL10n) {
CallUrlExpiredView: CallUrlExpiredView,
StartConversationView: StartConversationView,
HomeView: HomeView,
UnsupportedBrowserView: UnsupportedBrowserView,
UnsupportedDeviceView: UnsupportedDeviceView,
init: init,
PromoteFirefoxView: PromoteFirefoxView,
WebappHelper: WebappHelper,

View File

@ -27,8 +27,47 @@ loop.webapp = (function($, _, OT, mozL10n) {
/**
* Homepage view.
*/
var HomeView = sharedViews.BaseView.extend({
template: _.template('<p data-l10n-id="welcome"></p>')
var HomeView = React.createClass({
render: function() {
return (
<p>{mozL10n.get("welcome")}</p>
)
}
});
/**
* Unsupported Browsers view.
*/
var UnsupportedBrowserView = React.createClass({
render: function() {
var useLatestFF = mozL10n.get("use_latest_firefox", {
"firefoxBrandNameLink": React.renderComponentToStaticMarkup(
<a target="_blank" href="https://www.mozilla.org/firefox/">Firefox</a>
)
});
return (
<div>
<h2>{mozL10n.get("incompatible_browser")}</h2>
<p>{mozL10n.get("powered_by_webrtc")}</p>
<p dangerouslySetInnerHTML={{__html: useLatestFF}}></p>
</div>
);
}
});
/**
* Unsupported Device view.
*/
var UnsupportedDeviceView = React.createClass({
render: function() {
return (
<div>
<h2>{mozL10n.get("incompatible_device")}</h2>
<p>{mozL10n.get("sorry_device_unsupported")}</p>
<p>{mozL10n.get("use_firefox_windows_mac_linux")}</p>
</div>
);
}
});
/**
@ -324,7 +363,7 @@ loop.webapp = (function($, _, OT, mozL10n) {
}
// Load default view
this.loadView(new HomeView());
this.loadReactComponent(<HomeView />);
this.listenTo(this._conversation, "timeout", this._onTimeout);
},
@ -470,15 +509,15 @@ loop.webapp = (function($, _, OT, mozL10n) {
* Default entry point.
*/
home: function() {
this.loadView(new HomeView());
this.loadReactComponent(<HomeView />);
},
unsupportedDevice: function() {
this.loadView(new sharedViews.UnsupportedDeviceView());
this.loadReactComponent(<UnsupportedDeviceView />);
},
unsupportedBrowser: function() {
this.loadView(new sharedViews.UnsupportedBrowserView());
this.loadReactComponent(<UnsupportedBrowserView />);
},
expired: function() {
@ -579,6 +618,8 @@ loop.webapp = (function($, _, OT, mozL10n) {
CallUrlExpiredView: CallUrlExpiredView,
StartConversationView: StartConversationView,
HomeView: HomeView,
UnsupportedBrowserView: UnsupportedBrowserView,
UnsupportedDeviceView: UnsupportedDeviceView,
init: init,
PromoteFirefoxView: PromoteFirefoxView,
WebappHelper: WebappHelper,

View File

@ -23,7 +23,7 @@ call_with_contact_title=Conversation with {{incomingCallIdentity}}
welcome=Welcome to the {{clientShortname}} web client.
incompatible_browser=Incompatible Browser
powered_by_webrtc=The audio and video components of {{clientShortname}} are powered by WebRTC.
use_latest_firefox.innerHTML=Please try this link in a WebRTC-enabled browser, such as <a href="{{ff_url}}">{{brandShortname}}</a>.
use_latest_firefox=Please try this link in a WebRTC-enabled browser, such as {{firefoxBrandNameLink}}.
incompatible_device=Incompatible device
sorry_device_unsupported=Sorry, {{clientShortname}} does not currently support your device.
use_firefox_windows_mac_linux=Please open this page using the latest {{brandShortname}} on Windows, Android, Mac or Linux.

View File

@ -126,7 +126,6 @@ describe("loop.conversation", function() {
conversation: conversation,
notifications: notifications
});
sandbox.stub(router, "loadView");
sandbox.stub(conversation, "incoming");
});

View File

@ -78,7 +78,6 @@ describe("loop.panel", function() {
addEventListener: sandbox.spy()
});
sandbox.stub(router, "loadView");
sandbox.stub(router, "loadReactComponent");
});

View File

@ -48,44 +48,6 @@ describe("loop.shared.router", function() {
});
});
});
describe("constructed", function() {
var router, view, TestRouter;
beforeEach(function() {
TestRouter = loop.shared.router.BaseRouter.extend({});
var TestView = loop.shared.views.BaseView.extend({
template: _.template("<p>plop</p>")
});
view = new TestView();
router = new TestRouter({notifications: notifications});
});
describe("#loadView", function() {
it("should set the active view", function() {
router.loadView(view);
expect(router._activeView).eql({
type: "backbone",
view: view
});
});
it("should load and render the passed view", function() {
router.loadView(view);
expect($("#main p").text()).eql("plop");
});
});
describe("#updateView", function() {
it("should update the main element with provided contents", function() {
router.updateView($("<p>plip</p>"));
expect($("#main p").text()).eql("plip");
});
});
});
});
describe("BaseConversationRouter", function() {

View File

@ -27,22 +27,6 @@ describe("loop.shared.views", function() {
sandbox.restore();
});
describe("L10nView", function() {
beforeEach(function() {
sandbox.stub(l10n, "translate");
});
it("should translate generated contents on render()", function() {
var TestView = loop.shared.views.L10nView.extend();
var view = new TestView();
view.render();
sinon.assert.calledOnce(l10n.translate);
sinon.assert.calledWithExactly(l10n.translate, view.el);
});
});
describe("MediaControlButton", function() {
it("should render an enabled local audio button", function() {
var comp = TestUtils.renderIntoDocument(sharedViews.MediaControlButton({

View File

@ -35,6 +35,7 @@ describe("loop.webapp", function() {
beforeEach(function() {
WebappRouter = loop.webapp.WebappRouter;
sandbox.stub(WebappRouter.prototype, "navigate");
sandbox.stub(WebappRouter.prototype, "loadReactComponent");
});
afterEach(function() {
@ -84,7 +85,6 @@ describe("loop.webapp", function() {
conversation: conversation,
notifications: notifications
});
sandbox.stub(router, "loadView");
sandbox.stub(router, "navigate");
});
@ -273,14 +273,23 @@ describe("loop.webapp", function() {
});
describe("Routes", function() {
beforeEach(function() {
// In the router's constructor, it loads the home view, we don't
// need to test it here, so reset the stub.
router.loadReactComponent.reset();
});
describe("#home", function() {
it("should load the HomeView", function() {
router.home();
sinon.assert.calledOnce(router.loadView);
sinon.assert.calledWith(router.loadView,
sinon.match.instanceOf(loop.webapp.HomeView));
});
sinon.assert.calledOnce(router.loadReactComponent);
sinon.assert.calledWith(router.loadReactComponent,
sinon.match(function(value) {
return React.addons.TestUtils.isDescriptorOfType(
value, loop.webapp.HomeView);
}));
});
});
describe("#expired", function() {
@ -352,9 +361,12 @@ describe("loop.webapp", function() {
it("should load the UnsupportedDeviceView", function() {
router.unsupportedDevice();
sinon.assert.calledOnce(router.loadView);
sinon.assert.calledWith(router.loadView,
sinon.match.instanceOf(sharedViews.UnsupportedDeviceView));
sinon.assert.calledOnce(router.loadReactComponent);
sinon.assert.calledWith(router.loadReactComponent,
sinon.match(function(value) {
return React.addons.TestUtils.isDescriptorOfType(
value, loop.webapp.UnsupportedDeviceView);
}));
});
});
@ -362,9 +374,12 @@ describe("loop.webapp", function() {
it("should load the UnsupportedBrowserView", function() {
router.unsupportedBrowser();
sinon.assert.calledOnce(router.loadView);
sinon.assert.calledWith(router.loadView,
sinon.match.instanceOf(sharedViews.UnsupportedBrowserView));
sinon.assert.calledOnce(router.loadReactComponent);
sinon.assert.calledWith(router.loadReactComponent,
sinon.match(function(value) {
return React.addons.TestUtils.isDescriptorOfType(
value, loop.webapp.UnsupportedBrowserView);
}));
});
});
});

View File

@ -27,14 +27,6 @@ var loopServer;
// Ensure loop is always enabled for tests
Services.prefs.setBoolPref("loop.enabled", true);
function hawkGetCallsRequest() {
let response = {body: JSON.stringify({calls: [{callId: 4444333221, websocketToken: "0deadbeef0"}]})},
// Call the first non-null then(resolve) function attached to the fakePromise.
fakePromise = {then: (resolve) => {return resolve ? resolve(response) : fakePromise;},
catch: () => {return fakePromise;}};
return fakePromise;
}
function setupFakeLoopServer() {
loopServer = new HttpServer();
loopServer.start(-1);
@ -49,6 +41,26 @@ function setupFakeLoopServer() {
});
}
function waitForCondition(aConditionFn, aMaxTries=50, aCheckInterval=100) {
function tryAgain() {
function tryNow() {
tries++;
if (aConditionFn()) {
deferred.resolve();
} else if (tries < aMaxTries) {
tryAgain();
} else {
deferred.reject("Condition timed out: " + aConditionFn.toSource());
}
}
do_timeout(aCheckInterval, tryNow);
}
let deferred = Promise.defer();
let tries = 0;
tryAgain();
return deferred.promise;
}
/**
* This is used to fake push registration and notifications for
* MozLoopService tests. There is only one object created per test instance, as

View File

@ -6,8 +6,6 @@ XPCOMUtils.defineLazyModuleGetter(this, "Chat",
"resource:///modules/Chat.jsm");
let openChatOrig = Chat.open;
const loopServiceModule = Cu.import("resource:///modules/loop/MozLoopService.jsm", {});
add_test(function test_get_do_not_disturb() {
Services.prefs.setBoolPref("loop.do_not_disturb", false);
@ -39,20 +37,17 @@ add_test(function test_do_not_disturb_disabled_should_open_chat_window() {
opened = true;
};
let savedHawkClient = loopServiceModule.gHawkClient;
loopServiceModule.gHawkClient = {request: hawkGetCallsRequest};
mockPushHandler.notify(1);
do_check_true(opened, "should open a chat window");
loopServiceModule.gHawkClient = savedHawkClient;
run_next_test();
waitForCondition(function() opened).then(() => {
run_next_test();
}, () => {
do_throw("should have opened a chat window");
});
});
});
add_task(function test_do_not_disturb_enabled_shouldnt_open_chat_window() {
add_test(function test_do_not_disturb_enabled_shouldnt_open_chat_window() {
MozLoopService.doNotDisturb = true;
// We registered in the previous test, so no need to do that on this one.
@ -63,7 +58,10 @@ add_task(function test_do_not_disturb_enabled_shouldnt_open_chat_window() {
mockPushHandler.notify(1);
do_check_false(opened, "should not open a chat window");
do_timeout(500, function() {
do_check_false(opened, "should not open a chat window");
run_next_test();
});
});
function run_test()
@ -75,6 +73,12 @@ function run_test()
response.processAsync();
response.finish();
});
loopServer.registerPathHandler("/calls", (request, response) => {
response.setStatusLine(null, 200, "OK");
response.write(JSON.stringify({calls: [{callId: 4444333221, websocketToken: "0deadbeef0"}]}));
response.processAsync();
response.finish();
});
do_register_cleanup(function() {
// Revert original Chat.open implementation

View File

@ -18,19 +18,19 @@ add_test(function test_openChatWindow_on_notification() {
opened = true;
};
let savedHawkClient = loopServiceModule.gHawkClient;
loopServiceModule.gHawkClient = {request: hawkGetCallsRequest};
mockPushHandler.notify(1);
do_check_true(opened, "should open a chat window");
waitForCondition(function() opened).then(() => {
do_check_true(opened, "should open a chat window");
do_check_eq(Services.prefs.getCharPref("loop.seenToS"), "seen",
"should set the pref to 'seen'");
do_check_eq(Services.prefs.getCharPref("loop.seenToS"), "seen",
"should set the pref to 'seen'");
loopServiceModule.gHawkClient = savedHawkClient;
run_next_test();
}, () => {
do_throw("should have opened a chat window");
});
run_next_test();
});
});
@ -43,6 +43,12 @@ function run_test()
response.processAsync();
response.finish();
});
loopServer.registerPathHandler("/calls", (request, response) => {
response.setStatusLine(null, 200, "OK");
response.write(JSON.stringify({calls: [{callId: 4444333221, websocketToken: "0deadbeef0"}]}));
response.processAsync();
response.finish();
});
do_register_cleanup(function() {
// Revert original Chat.open implementation

View File

@ -17,6 +17,9 @@
var IncomingCallView = loop.conversation.IncomingCallView;
// 2. Standalone webapp
var HomeView = loop.webapp.HomeView;
var UnsupportedBrowserView = loop.webapp.UnsupportedBrowserView;
var UnsupportedDeviceView = loop.webapp.UnsupportedDeviceView;
var CallUrlExpiredView = loop.webapp.CallUrlExpiredView;
var StartConversationView = loop.webapp.StartConversationView;
@ -314,6 +317,31 @@
)
)
)
),
Section({name: "HomeView"},
Example({summary: "Standalone Home View"},
React.DOM.div({className: "standalone"},
HomeView(null)
)
)
),
Section({name: "UnsupportedBrowserView"},
Example({summary: "Standalone Unsupported Browser"},
React.DOM.div({className: "standalone"},
UnsupportedBrowserView(null)
)
)
),
Section({name: "UnsupportedDeviceView"},
Example({summary: "Standalone Unsupported Device"},
React.DOM.div({className: "standalone"},
UnsupportedDeviceView(null)
)
)
)
)

View File

@ -17,6 +17,9 @@
var IncomingCallView = loop.conversation.IncomingCallView;
// 2. Standalone webapp
var HomeView = loop.webapp.HomeView;
var UnsupportedBrowserView = loop.webapp.UnsupportedBrowserView;
var UnsupportedDeviceView = loop.webapp.UnsupportedDeviceView;
var CallUrlExpiredView = loop.webapp.CallUrlExpiredView;
var StartConversationView = loop.webapp.StartConversationView;
@ -316,6 +319,31 @@
</Example>
</Section>
<Section name="HomeView">
<Example summary="Standalone Home View">
<div className="standalone">
<HomeView />
</div>
</Example>
</Section>
<Section name="UnsupportedBrowserView">
<Example summary="Standalone Unsupported Browser">
<div className="standalone">
<UnsupportedBrowserView />
</div>
</Example>
</Section>
<Section name="UnsupportedDeviceView">
<Example summary="Standalone Unsupported Device">
<div className="standalone">
<UnsupportedDeviceView />
</div>
</Example>
</Section>
</ShowCase>
);
}

View File

@ -627,7 +627,7 @@ OnSharedPreferenceChangeListener
// This logic will need to be extended when
// content language selection (Bug 881510) is implemented.
if (!localeSwitchingIsEnabled &&
"preferences_locale".equals(pref.getExtras().getString("resource", null))) {
"preferences_locale".equals(pref.getExtras().getString("resource"))) {
preferences.removePreference(pref);
i--;
continue;

Binary file not shown.

Before

Width:  |  Height:  |  Size: 323 B

After

Width:  |  Height:  |  Size: 245 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 368 B

After

Width:  |  Height:  |  Size: 245 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 272 B

After

Width:  |  Height:  |  Size: 207 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 296 B

After

Width:  |  Height:  |  Size: 207 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 425 B

After

Width:  |  Height:  |  Size: 296 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 495 B

After

Width:  |  Height:  |  Size: 296 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

After

Width:  |  Height:  |  Size: 381 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

After

Width:  |  Height:  |  Size: 381 B

View File

@ -66,6 +66,9 @@ function execute_search_test(test) {
}
function prep_search_test(test) {
// Syncrhonously load the search service.
Services.search.getVisibleEngines();
setHandlerFunc(execute_search_test, test);
let rel = test.rel || "search";

View File

@ -10,6 +10,7 @@ import java.util.Arrays;
import org.mozilla.gecko.R;
import org.mozilla.gecko.Tab;
import org.mozilla.gecko.Tabs;
import org.mozilla.gecko.Tabs.TabEvents;
import org.mozilla.gecko.animation.PropertyAnimator;
import org.mozilla.gecko.animation.ViewHelper;
@ -19,7 +20,6 @@ import android.util.AttributeSet;
import android.view.View;
import android.view.animation.AccelerateInterpolator;
import android.view.animation.Interpolator;
import android.widget.Button;
import android.widget.ImageButton;
import android.widget.LinearLayout;
@ -37,7 +37,12 @@ abstract class BrowserToolbarTabletBase extends BrowserToolbar {
protected final LinearLayout actionItemBar;
protected final BackButton backButton;
private final OnClickListener backButtonOnClickListener;
private final OnLongClickListener backButtonOnLongClickListener;
protected final ForwardButton forwardButton;
private final OnClickListener forwardButtonOnClickListener;
private final OnLongClickListener forwardButtonOnLongClickListener;
private final Interpolator buttonsInterpolator = new AccelerateInterpolator();
@ -52,39 +57,57 @@ abstract class BrowserToolbarTabletBase extends BrowserToolbar {
setButtonEnabled(backButton, false);
forwardButton = (ForwardButton) findViewById(R.id.forward);
setButtonEnabled(forwardButton, false);
initButtonListeners();
backButtonOnClickListener = new BackButtonOnClickListener();
backButtonOnLongClickListener = new BackButtonOnLongClickListener();
forwardButtonOnClickListener = new ForwardButtonOnClickListener();
forwardButtonOnLongClickListener = new ForwardButtonOnLongClickListener();
setNavigationButtonListeners(true);
focusOrder.addAll(Arrays.asList(tabsButton, (View) backButton, (View) forwardButton, this));
focusOrder.addAll(urlDisplayLayout.getFocusOrder());
focusOrder.addAll(Arrays.asList(actionItemBar, menuButton));
}
private void initButtonListeners() {
backButton.setOnClickListener(new Button.OnClickListener() {
@Override
public void onClick(View view) {
Tabs.getInstance().getSelectedTab().doBack();
}
});
backButton.setOnLongClickListener(new Button.OnLongClickListener() {
@Override
public boolean onLongClick(View view) {
return Tabs.getInstance().getSelectedTab().showBackHistory();
}
});
/**
* Enables or disables the click listeners on the back and forward buttons.
*
* This method is useful to remove and later add the listeners when a navigation button is hit
* because calling `browser.go*()` twice in succession can cause the UI buttons to get out of
* sync with gecko's browser state (bug 960746).
*
* @param disabled True if the listeners should be removed, false for them to be added.
*/
private void setNavigationButtonListeners(final boolean enabled) {
if (enabled) {
backButton.setOnClickListener(backButtonOnClickListener);
backButton.setOnLongClickListener(backButtonOnLongClickListener);
forwardButton.setOnClickListener(new Button.OnClickListener() {
@Override
public void onClick(View view) {
Tabs.getInstance().getSelectedTab().doForward();
}
});
forwardButton.setOnLongClickListener(new Button.OnLongClickListener() {
@Override
public boolean onLongClick(View view) {
return Tabs.getInstance().getSelectedTab().showForwardHistory();
}
});
forwardButton.setOnClickListener(forwardButtonOnClickListener);
forwardButton.setOnLongClickListener(forwardButtonOnLongClickListener);
} else {
backButton.setOnClickListener(null);
backButton.setOnLongClickListener(null);
forwardButton.setOnClickListener(null);
forwardButton.setOnLongClickListener(null);
}
}
@Override
public void onTabChanged(final Tab tab, final Tabs.TabEvents msg, final Object data) {
// STOP appears to be the first page load event where async nav issues are prevented,
// SELECTED is for switching tabs, and LOAD_ERROR is called when a JavaScript exception
// is thrown while loading a URI, which can prevent STOP from ever being called.
//
// See `setNavigationButtonListeners` javadoc for more information.
if (msg == TabEvents.STOP ||
msg == TabEvents.SELECTED ||
msg == TabEvents.LOAD_ERROR) {
setNavigationButtonListeners(true);
}
super.onTabChanged(tab, msg, data);
}
@Override
@ -164,4 +187,36 @@ abstract class BrowserToolbarTabletBase extends BrowserToolbar {
button.setEnabled(enabled);
}
private class BackButtonOnClickListener implements OnClickListener {
@Override
public void onClick(final View view) {
setNavigationButtonListeners(false);
Tabs.getInstance().getSelectedTab().doBack();
}
}
private class BackButtonOnLongClickListener implements OnLongClickListener {
@Override
public boolean onLongClick(final View view) {
setNavigationButtonListeners(false);
return Tabs.getInstance().getSelectedTab().showBackHistory();
}
}
private class ForwardButtonOnClickListener implements OnClickListener {
@Override
public void onClick(final View view) {
setNavigationButtonListeners(false);
Tabs.getInstance().getSelectedTab().doForward();
}
}
private class ForwardButtonOnLongClickListener implements OnLongClickListener {
@Override
public boolean onLongClick(final View view) {
setNavigationButtonListeners(false);
return Tabs.getInstance().getSelectedTab().showForwardHistory();
}
}
}

View File

@ -3936,50 +3936,52 @@ Tab.prototype = {
// Check that type matches opensearch.
let isOpenSearch = (type == "application/opensearchdescription+xml");
if (isOpenSearch && target.title && /^(?:https?|ftp):/i.test(target.href)) {
let visibleEngines = Services.search.getVisibleEngines();
// NOTE: Engines are currently identified by name, but this can be changed
// when Engines are identified by URL (see bug 335102).
if (visibleEngines.some(function(e) {
return e.name == target.title;
})) {
// This engine is already present, do nothing.
return;
}
if (this.browser.engines) {
// This engine has already been handled, do nothing.
if (this.browser.engines.some(function(e) {
return e.url == target.href;
Services.search.init(() => {
let visibleEngines = Services.search.getVisibleEngines();
// NOTE: Engines are currently identified by name, but this can be changed
// when Engines are identified by URL (see bug 335102).
if (visibleEngines.some(function(e) {
return e.name == target.title;
})) {
return;
// This engine is already present, do nothing.
return;
}
} else {
this.browser.engines = [];
}
// Get favicon.
let iconURL = target.ownerDocument.documentURIObject.prePath + "/favicon.ico";
if (this.browser.engines) {
// This engine has already been handled, do nothing.
if (this.browser.engines.some(function(e) {
return e.url == target.href;
})) {
return;
}
} else {
this.browser.engines = [];
}
let newEngine = {
title: target.title,
url: target.href,
iconURL: iconURL
};
// Get favicon.
let iconURL = target.ownerDocument.documentURIObject.prePath + "/favicon.ico";
this.browser.engines.push(newEngine);
let newEngine = {
title: target.title,
url: target.href,
iconURL: iconURL
};
// Don't send a message to display engines if we've already handled an engine.
if (this.browser.engines.length > 1)
return;
this.browser.engines.push(newEngine);
// Broadcast message that this tab contains search engines that should be visible.
let newEngineMessage = {
type: "Link:OpenSearch",
tabID: this.id,
visible: true
};
// Don't send a message to display engines if we've already handled an engine.
if (this.browser.engines.length > 1)
return;
Messaging.sendRequest(newEngineMessage);
// Broadcast message that this tab contains search engines that should be visible.
let newEngineMessage = {
type: "Link:OpenSearch",
tabID: this.id,
visible: true
};
Messaging.sendRequest(newEngineMessage);
});
}
}
break;

View File

@ -1583,8 +1583,6 @@ nsAutoCompleteController::GetFinalDefaultCompleteValue(nsAString &_retval)
_retval = finalCompleteValue;
}
MOZ_ASSERT(FindInReadable(inputValue, _retval, nsCaseInsensitiveStringComparator()),
"Return value must include input value.");
return NS_OK;
}

View File

@ -730,9 +730,40 @@ Search.prototype = {
let match = yield PlacesSearchAutocompleteProvider.findMatchByToken(
this._searchString);
if (match) {
// The match doesn't contain a 'scheme://www.' prefix, but since we have
// stripped it from the search string, here we could still be matching
// 'https://www.g' to 'google.com'.
// There are a couple cases where we don't want to match though:
//
// * If the protocol differs we should not match. For example if the user
// searched https we should not return http.
try {
let prefixURI = NetUtil.newURI(this._strippedPrefix);
let finalURI = NetUtil.newURI(match.url);
if (prefixURI.scheme != finalURI.scheme)
return;
} catch (e) {}
// * If the user typed "www." but the final url doesn't have it, we
// should not match as well, the two urls may point to different pages.
if (this._strippedPrefix.endsWith("www.") &&
!stripHttpAndTrim(match.url).startsWith("www."))
return;
let value = this._strippedPrefix + match.token;
// In any case, we should never arrive here with a value that doesn't
// match the search string. If this happens there is some case we
// are not handling properly yet.
if (!value.startsWith(this._originalSearchString)) {
Components.utils.reportError(`Trying to inline complete in-the-middle
${this._originalSearchString} to ${value}`);
return;
}
this._result.setDefaultIndex(0);
this._addFrecencyMatch({
value: match.token,
value: value,
comment: match.engineName,
icon: match.iconUrl,
style: "priority-search",

View File

@ -81,6 +81,72 @@ add_task(function* test_searchEngine_trailing_space_noautofill() {
yield cleanup();
});
add_task(function* test_searchEngine_www_noautofill() {
Services.search.addEngineWithDetails("HamSearch", "", "", "",
"GET", "http://ham.search/");
let engine = Services.search.getEngineByName("HamSearch");
engine.addParam("q", "{searchTerms}", null);
do_register_cleanup(() => Services.search.removeEngine(engine));
do_log_info("Should not autoFill search engine if search string contains www. but engine doesn't");
yield check_autocomplete({
search: "www.ham",
autofilled: "www.ham",
completed: "www.ham"
});
yield cleanup();
});
add_task(function* test_searchEngine_different_scheme_noautofill() {
Services.search.addEngineWithDetails("PieSearch", "", "", "",
"GET", "https://pie.search/");
let engine = Services.search.getEngineByName("PieSearch");
engine.addParam("q", "{searchTerms}", null);
do_register_cleanup(() => Services.search.removeEngine(engine));
do_log_info("Should not autoFill search engine if search string has a different scheme.");
yield check_autocomplete({
search: "http://pie",
autofilled: "http://pie",
completed: "http://pie"
});
yield cleanup();
});
add_task(function* test_searchEngine_matching_prefix_autofill() {
Services.search.addEngineWithDetails("BeanSearch", "", "", "",
"GET", "http://www.bean.search/");
let engine = Services.search.getEngineByName("BeanSearch");
engine.addParam("q", "{searchTerms}", null);
do_register_cleanup(() => Services.search.removeEngine(engine));
do_log_info("Should autoFill search engine if search string has matching prefix.");
yield check_autocomplete({
search: "http://www.be",
autofilled: "http://www.bean.search",
completed: "http://www.bean.search"
})
do_log_info("Should autoFill search engine if search string has www prefix.");
yield check_autocomplete({
search: "www.be",
autofilled: "www.bean.search",
completed: "http://www.bean.search"
});
do_log_info("Should autoFill search engine if search string has matching scheme.");
yield check_autocomplete({
search: "http://be",
autofilled: "http://bean.search",
completed: "http://www.bean.search"
});
yield cleanup();
});
add_task(function* test_prefix_autofill() {
yield promiseAddVisits({ uri: NetUtil.newURI("http://mozilla.org/test/"),
transition: TRANSITION_TYPED });