Skip to content
This repository was archived by the owner on Mar 15, 2018. It is now read-only.

Commit d46e11c

Browse files
committed
tell users all the silly reasons why app installs are disabled (bug 793801)
1 parent 6d77dd9 commit d46e11c

10 files changed

Lines changed: 179 additions & 45 deletions

File tree

media/css/mkt/tile.less

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -393,7 +393,7 @@
393393
display: none;
394394
}
395395

396-
.bad-platform {
396+
.bad-app {
397397
color: @maroon;
398398
font-size: 11px;
399399
line-height: 12px;

media/js/mkt/init.js

Lines changed: 5 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -22,20 +22,15 @@ var z = {
2222

2323
z.prefixUpper = z.prefix[0].toUpperCase() + z.prefix.substr(1);
2424

25-
(function() {
26-
_.extend(z, {'nav': BrowserUtils()});
27-
if (!z.nav.browser.firefox ||
28-
z.nav.browser.mobile || z.nav.os.maemo ||
29-
VersionCompare.compareVersions(z.nav.browserVersion, '16.0') < 0 ||
30-
(z.nav.os.android && VersionCompare.compareVersions(z.nav.browserVersion, '17.0') < 0)) {
31-
z.canInstallApps = false;
32-
}
33-
})();
34-
3525
// Initialize webtrends tracking.
3626
z.page.on('fragmentloaded', webtrendsAsyncInit);
3727

3828
(function() {
29+
_.extend(z, {
30+
nav: BrowserUtils(),
31+
canInstallApps: z.body.data('installs')
32+
});
33+
3934
function trigger() {
4035
$(window).trigger('saferesize');
4136
}
@@ -77,8 +72,6 @@ $(document).ready(function() {
7772

7873

7974
z.page.on('fragmentloaded', function() {
80-
var badPlatform = '<div class="bad-platform">' + gettext('This app is unavailable for your platform.') + '</div>';
81-
8275
z.apps = {};
8376
if (z.capabilities.webApps) {
8477
// Get list of installed apps and mark as such.
@@ -90,17 +83,10 @@ z.page.on('fragmentloaded', function() {
9083
[val, {'manifest_url': val.manifestURL}, false]);
9184
});
9285
};
93-
if (!z.capabilities.gaia) {
94-
// Only Firefox OS currently supports packaged apps.
95-
// (The 'bad' class ensures we append the message only once.)
96-
$('.listing .product[data-is_packaged="true"]').addClass('disabled')
97-
.closest('.mkt-tile:not(.bad)').addClass('bad').append(badPlatform);
98-
}
9986
}
10087

10188
if (!z.canInstallApps) {
10289
$(window).trigger('app_install_disabled');
103-
$('.listing .mkt-tile:not(.bad)').addClass('bad').append(badPlatform);
10490
}
10591

10692
// Navigation toggle.

media/js/zamboni/browser.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ function BrowserUtils() {
88
var userAgentStrings = {
99
'firefox' : /Mozilla.*(Firefox|Minefield|Namoroka|Shiretoko|GranParadiso|BonEcho|Iceweasel|Fennec|MozillaDeveloperPreview)\/([^\s]*).*$/,
1010
'seamonkey': /Mozilla.*(SeaMonkey|Iceape)\/([^\s]*).*$/,
11-
'mobile': /Mozilla.*(Fennec)\/([^\s]*)$/,
11+
'mobile': /Mozilla.*(Fennec|Mobile)\/([^\s]*)$/,
1212
'thunderbird': /Mozilla.*(Thunderbird|Shredder|Lanikai)\/([^\s*]*).*$/
1313
},
1414
osStrings = {

mkt/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from mkt.constants import ratingsbodies, regions
1+
from mkt.constants import platforms, ratingsbodies, regions
22
from mkt.constants.submit import *
33

44
MKT_CUT = .70

mkt/constants/platforms.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import re
2+
3+
from versions.compare import version_int as vint
4+
5+
6+
# These are the minimum versions required for `navigator.mozApps` support.
7+
APP_PLATFORMS = [
8+
# Firefox for Desktop.
9+
(
10+
[
11+
re.compile('Firefox/([\d.]+)')
12+
],
13+
vint('16.0')
14+
),
15+
# Firefox for Android.
16+
(
17+
[
18+
re.compile('Fennec/([\d.]+)'),
19+
re.compile('Android; Mobile; rv:([\d.]+)'),
20+
re.compile('Mobile; rv:([\d.]+)')
21+
],
22+
vint('17.0')
23+
)
24+
]

mkt/site/helpers.py

Lines changed: 82 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,21 @@
22

33
from django.conf import settings
44

5+
import caching.base as caching
6+
import jinja2
7+
import waffle
58
from jingo import register, env
69
from jingo_minify import helpers as jingo_minify_helpers
7-
import jinja2
810
from tower import ugettext as _
9-
import waffle
1011

1112
from access import acl
1213
from amo.helpers import urlparams
1314
from amo.urlresolvers import reverse, get_outgoing_url
1415
from amo.utils import JSONEncoder
1516
from translations.helpers import truncate
17+
from versions.compare import version_int as vint
18+
19+
import mkt
1620

1721

1822
@jinja2.contextfunction
@@ -56,11 +60,11 @@ def no_results():
5660

5761
@jinja2.contextfunction
5862
@register.function
59-
def market_button(context, product, receipt_type=None):
63+
def market_button(context, product, receipt_type=None, classes=None):
6064
request = context['request']
6165
if product.is_webapp():
6266
purchased = False
63-
classes = ['button', 'product']
67+
classes = (classes or []) + ['button', 'product']
6468
data_attrs = {'manifest_url': product.get_manifest_url(),
6569
'is_packaged': json.dumps(product.is_packaged)}
6670

@@ -75,13 +79,6 @@ def market_button(context, product, receipt_type=None):
7579
request.check_ownership(product, require_author=True)):
7680
purchased = True
7781

78-
classes.append('premium')
79-
if waffle.switch_is_active('disabled-payments') or not request.GAIA:
80-
classes.append('disabled')
81-
82-
if not request.MOBILE and not 'disabled' in classes:
83-
classes.append('disabled')
84-
8582
if purchased:
8683
label = _('Install')
8784
else:
@@ -185,7 +182,8 @@ def product_as_dict_theme(request, product):
185182
def market_tile(context, product, link=True, src=''):
186183
request = context['request']
187184
if product.is_webapp():
188-
classes = ['product', 'mkt-tile']
185+
classes = []
186+
notices = []
189187
purchased = (request.amo_user and
190188
product.pk in request.amo_user.purchase_ids())
191189

@@ -202,10 +200,47 @@ def market_tile(context, product, link=True, src=''):
202200
'manifest_url': product.get_manifest_url(),
203201
'src': src
204202
}
203+
204+
ua = request.META.get('HTTP_USER_AGENT', '')
205+
need_firefox, need_upgrade = check_firefox(ua)
206+
205207
if product.is_premium() and product.premium:
206208
classes.append('premium')
209+
210+
if waffle.switch_is_active('disabled-payments'):
211+
notices.append(_('This app is temporarily unavailable for '
212+
'purchase.'))
213+
elif not request.GAIA:
214+
notices.append(_('This app is available for purchase only '
215+
'on Firefox OS.'))
216+
217+
if product.is_packaged and not request.GAIA:
218+
notices.append(_('This app is available only on Firefox OS.'))
219+
220+
if not request.MOBILE:
221+
notices.append(_('This app is available only on Firefox for '
222+
'Android and Firefox OS.'))
223+
if need_firefox:
224+
if request.MOBILE:
225+
url = ('https://www.mozilla.org/en-US/mobile/android-download'
226+
'.html')
227+
notices.append(_('To use this app, <a href="{url}" '
228+
'target="_blank">download and install '
229+
'Firefox for Android</a>.').format(url=url))
230+
# TODO: Enable when we allow installs on desktop again.
231+
#else:
232+
# url = 'https://www.mozilla.org/en-US/firefox/'
233+
# notices.append(_('To use this app, <a href="{url}" '
234+
# 'target="_blank">download and install '
235+
# 'Firefox</a>.').format(url=url))
236+
elif need_upgrade:
237+
notices.append(_('To use this app, upgrade Firefox.'))
238+
239+
if notices:
240+
classes += ['bad', 'disabled']
241+
207242
c = dict(request=request, product=product, data_attrs=data_attrs,
208-
classes=' '.join(classes), link=link)
243+
classes=classes, link=link, notices=notices[:1])
209244
t = env.get_template('site/tiles/app.html')
210245
return jinja2.Markup(t.render(c))
211246

@@ -354,3 +389,37 @@ def get_login_link(context, to=None):
354389
if to == url:
355390
to = None
356391
return urlparams(url, to=to)
392+
393+
394+
@register.function
395+
def check_firefox(ua):
396+
return caching.cached(lambda: _check_firefox(ua), 'check_firefox:%s' % ua)
397+
398+
399+
def _check_firefox(ua):
400+
need_firefox, need_upgrade = True, True
401+
402+
for ua_res, min_version in mkt.platforms.APP_PLATFORMS:
403+
for ua_re in ua_res:
404+
match = ua_re.search(ua)
405+
if match:
406+
v = match.groups()[0]
407+
408+
# If we found a version at all, then this is Firefox.
409+
need_firefox = False
410+
411+
# If we found a matching version, then we can install apps!
412+
need_upgrade = vint(v) < min_version
413+
414+
return need_firefox, need_upgrade
415+
416+
417+
@register.function
418+
@jinja2.contextfunction
419+
def allow_installs(context):
420+
"""
421+
This will return of a boolean of whether we're on a version of
422+
Firefox that supports `navigator.mozApps`.
423+
"""
424+
ua = context['request'].META.get('HTTP_USER_AGENT', '')
425+
return check_firefox(ua) == (False, False)
Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,4 @@
1-
<button
2-
{% for k, v in data_attrs.items() -%}
3-
data-{{ k }}="{{ v }}" {% endfor %}
4-
class="{{ classes }}">
5-
{{ label }}
1+
<button class="{{ classes }}"
2+
{%- for k, v in data_attrs.items() -%} data-{{ k }}="{{ v }}" {% endfor %}>
3+
{{ label }}
64
</button>

mkt/site/templates/site/tiles/app.html

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
previews[0].thumbnail_url %}
55
{% endif %}
66
{% set tag = 'a' if link else 'div' %}
7-
<{{ tag }} class="{{ classes }}"
7+
<{{ tag }} class="product mkt-tile {{ classes|join(' ') }}"
88
{% if link %}href="{{ product.get_url_path()|urlparams(src=data_attrs.src) }}"{% endif %}
99
{% for k, v in data_attrs.items() %} data-{{ k }}="{{ v }}"{% endfor %}>
1010
<div class="icon featured_tile" style="background-image:url({{ product.get_image_asset_url('featured_tile', 16) }})"></div>
@@ -16,7 +16,7 @@
1616
<h3>{{ product.name }}</h3>
1717
{# `current_version` won't show versions with invalid statuses #}
1818
{% if product.current_version %}
19-
{{ market_button(product) }}
19+
{{ market_button(product, classes=classes) }}
2020
{% endif %}
2121
{% if product.listed_authors %}
2222
<div class="author lineclamp vital">{{ product.listed_authors[0].name }}</div>
@@ -40,6 +40,9 @@ <h3>{{ product.name }}</h3>
4040
</div>
4141
{% endif %}
4242
</div>
43+
{% for notice in notices %}
44+
<div class="bad-app">{{ notice|safe }}</div>
45+
{% endfor %}
4346
</{{ tag }}>
4447
<div class="tray previews full">
4548
</div>

mkt/site/tests/test_helpers.py

Lines changed: 56 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,10 @@ def setUp(self):
3333
request.check_ownership.return_value = False
3434
request.GET = {'src': 'foo'}
3535
request.groups = ()
36-
request.GAIA = True
36+
request.GAIA = False
37+
request.MOBILE = True
38+
request.META = {'HTTP_USER_AGENT': 'Mozilla/5.0 (Mobile; rv:17.0) '
39+
'Gecko/17.0 Firefox/17.0'}
3740
self.context = {'request': request}
3841

3942
def test_not_webapp(self):
@@ -68,6 +71,21 @@ def test_is_premium_webapp(self):
6871
eq_(data['purchase'], self.webapp.get_purchase_url())
6972
eq_(data['isPurchased'], False)
7073

74+
cls = doc('button').attr('class')
75+
assert 'disabled' in cls, 'Unexpected: %r' % cls
76+
eq_(doc('.bad-app').text(),
77+
'This app is available for purchase only on Firefox OS.')
78+
79+
def test_is_premium_webapp_gaia(self):
80+
self.context['request'].GAIA = True
81+
self.make_premium(self.webapp)
82+
doc = pq(market_tile(self.context, self.webapp))
83+
eq_(doc('.price').text(), '$1.00')
84+
85+
cls = doc('button').attr('class')
86+
assert 'disabled' not in cls, 'Unexpected: %r' % cls
87+
eq_(doc('.bad-app').length, 0)
88+
7189
def test_is_premium_webapp_foreign(self):
7290
self.make_premium(self.webapp)
7391
with self.activate('fr'):
@@ -89,24 +107,59 @@ def test_is_premium_disabled(self):
89107
doc = pq(market_tile(self.context, self.webapp))
90108
cls = doc('button').attr('class')
91109
assert 'disabled' in cls, 'Unexpected: %r' % cls
110+
eq_(doc('.bad-app').text(),
111+
'This app is temporarily unavailable for purchase.')
92112

93113
def test_is_desktop_disabled(self):
94114
self.context['request'].MOBILE = False
115+
self.context['request'].META['HTTP_USER_AGENT'] = (
116+
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.6; rv:18.0) Gecko/18.0 '
117+
'Firefox/18.0')
118+
doc = pq(market_tile(self.context, self.webapp))
119+
cls = doc('button').attr('class')
120+
assert 'disabled' in cls, 'Unexpected: %r' % cls
121+
eq_(doc('.bad-app').text(),
122+
'This app is available only on Firefox for Android and Firefox '
123+
'OS.')
124+
125+
def test_needs_firefox_for_android(self):
126+
self.context['request'].META['HTTP_USER_AGENT'] = (
127+
'Mozilla/5.0 (Linux; U; Android 2.3.3; en-au; GT-I9100 Build)')
95128
doc = pq(market_tile(self.context, self.webapp))
96129
cls = doc('button').attr('class')
97130
assert 'disabled' in cls, 'Unexpected: %r' % cls
131+
eq_(doc('.bad-app').text(),
132+
'To use this app, download and install Firefox for Android .')
133+
134+
def test_needs_firefox_for_android_upgrade(self):
135+
# Only Firefox for Android 17.0+ has support for `navigator.mozApps`.
136+
self.context['request'].META['HTTP_USER_AGENT'] = (
137+
'Mozilla/5.0 (Mobile; rv:16.0) Gecko/16.0 Firefox/16.0')
138+
doc = pq(market_tile(self.context, self.webapp))
139+
cls = doc('button').attr('class')
140+
assert 'disabled' in cls, 'Unexpected: %r' % cls
141+
eq_(doc('.bad-app').text(), 'To use this app, upgrade Firefox.')
98142

99143
def test_is_premium_android_disabled(self):
100-
self.context['request'].GAIA = False
101144
self.make_premium(self.webapp)
102145
doc = pq(market_tile(self.context, self.webapp))
103146
cls = doc('button').attr('class')
104147
assert 'disabled' in cls, 'Unexpected: %r' % cls
148+
eq_(doc('.bad-app').text(),
149+
'This app is available for purchase only on Firefox OS.')
150+
151+
def test_is_free_enabled_android(self):
152+
doc = pq(market_tile(self.context, self.webapp))
153+
cls = doc('button').attr('class')
154+
assert 'disabled' not in cls, 'Unexpected: %r' % cls
155+
eq_(doc('.bad-app').length, 0)
105156

106-
def test_is_free_android_enabled(self):
157+
def test_is_free_enabled_gaia(self):
158+
self.context['request'].GAIA = True
107159
doc = pq(market_tile(self.context, self.webapp))
108160
cls = doc('button').attr('class')
109161
assert 'disabled' not in cls, 'Unexpected: %r' % cls
162+
eq_(doc('.bad-app').length, 0)
110163

111164
def test_xss(self):
112165
nasty = '<script>'

mkt/templates/mkt/base.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
</head>
3838
<body class="html-{{ DIR }} {{ CARRIER }} {{ FORCE_MOBILE }} {{ bodyclass }}"
3939
{% if waffle.switch('anonymous-free-installs') %}data-allow-anon-installs="true"{% endif %}
40+
data-allow-installs="{{ allow_installs()|json }}"
4041
data-user="{{ user_data(amo_user)|json }}"
4142
data-readonly="{{ settings.READ_ONLY|json }}"
4243
data-media-url="{{ MEDIA_URL }}"

0 commit comments

Comments
 (0)