зеркало из https://github.com/mozilla/commonplace.git
Merge pull request #32 from ngokevin/sync
upstream and resync fireplace to commonplace (bug 999703)
This commit is contained in:
Коммит
cddfeef4d6
|
@ -0,0 +1,116 @@
|
|||
define('buckets', [], function() {
|
||||
|
||||
function noop() {return '';}
|
||||
|
||||
var aelem = document.createElement('audio');
|
||||
var velem = document.createElement('video');
|
||||
|
||||
// Compatibilty with PhantomJS, which doesn't implement canPlayType
|
||||
if (!('canPlayType' in aelem)) {
|
||||
velem = aelem = {canPlayType: noop};
|
||||
}
|
||||
|
||||
var prefixes = ['moz', 'webkit', 'ms'];
|
||||
|
||||
function prefixed(property, context) {
|
||||
if (!context) {
|
||||
context = window;
|
||||
}
|
||||
try {
|
||||
if (property in context) {
|
||||
return context[property];
|
||||
}
|
||||
} catch(e) {
|
||||
return false;
|
||||
}
|
||||
// Camel-case it.
|
||||
property = property[0].toUpperCase() + property.substr(1);
|
||||
|
||||
for (var i = 0, e; e = prefixes[i++];) {
|
||||
try {
|
||||
if ((e + property) in context) {
|
||||
return context[e + property];
|
||||
}
|
||||
} catch(err) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var has_gum = prefixed('getUserMedia', navigator);
|
||||
if (has_gum && navigator.mozGetUserMedia) {
|
||||
// Gecko 18's gum is a noop.
|
||||
try {
|
||||
navigator.mozGetUserMedia(); // Should throw a TypeError.
|
||||
has_gum = false;
|
||||
} catch(e) {}
|
||||
}
|
||||
|
||||
var audiocontext = window.webkitAudioContext || window.AudioContext;
|
||||
var has_audiocontext = !!(audiocontext);
|
||||
|
||||
var capabilities = [
|
||||
'mozApps' in navigator,
|
||||
'mozApps' in navigator && navigator.mozApps.installPackage,
|
||||
'mozPay' in navigator,
|
||||
// FF 18 and earlier throw an exception on this key
|
||||
(function() {try{return !!window.MozActivity;} catch(e) {return false;}})(),
|
||||
'ondevicelight' in window,
|
||||
'ArchiveReader' in window,
|
||||
'battery' in navigator,
|
||||
'mozBluetooth' in navigator,
|
||||
'mozContacts' in navigator,
|
||||
'getDeviceStorage' in navigator,
|
||||
(function() { try{return window.mozIndexedDB || window.indexedDB;} catch(e) {return false;}})(),
|
||||
'geolocation' in navigator && 'getCurrentPosition' in navigator.geolocation,
|
||||
'addIdleObserver' in navigator && 'removeIdleObserver' in navigator,
|
||||
'mozConnection' in navigator && (navigator.mozConnection.metered === true || navigator.mozConnection.metered === false),
|
||||
'mozNetworkStats' in navigator,
|
||||
'ondeviceproximity' in window,
|
||||
'mozPush' in navigator || 'push' in navigator,
|
||||
'ondeviceorientation' in window,
|
||||
'mozTime' in navigator,
|
||||
'vibrate' in navigator,
|
||||
'mozFM' in navigator || 'mozFMRadio' in navigator,
|
||||
'mozSms' in navigator,
|
||||
!!(('ontouchstart' in window) || window.DocumentTouch && document instanceof DocumentTouch),
|
||||
window.screen.width <= 540 && window.screen.height <= 960, // qHD support
|
||||
!!aelem.canPlayType('audio/mpeg').replace(/^no$/, ''), // mp3 support
|
||||
!!(window.Audio), // Audio Data API
|
||||
has_audiocontext, // Web Audio API
|
||||
!!velem.canPlayType('video/mp4; codecs="avc1.42E01E"').replace(/^no$/,''), // H.264
|
||||
!!velem.canPlayType('video/webm; codecs="vp8"').replace(/^no$/,''), // WebM
|
||||
!!prefixed('cancelFullScreen', document), // Full Screen API
|
||||
!!prefixed('getGamepads', navigator), // Gamepad API
|
||||
!!(prefixed('persistentStorage') || window.StorageInfo), // Quota Management API
|
||||
// WebRTC:
|
||||
has_gum && !prefixed('cameras', navigator), // Can take photos
|
||||
has_gum && has_audiocontext &&
|
||||
!!((new audiocontext()).createMediaStreamSource), // Can record audio
|
||||
has_gum && false, // XXX: Google WebRTC issue 2088
|
||||
'MediaStream' in window,
|
||||
'DataChannel' in window,
|
||||
prefixed('RTCPeerConnection'),
|
||||
prefixed('SpeechSynthesisEvent'), // WebSpeech Synthesis
|
||||
prefixed('SpeechInputEvent'), // WebSpeech Input
|
||||
prefixed('requestPointerLock', document.documentElement), // Pointer lock
|
||||
prefixed('notification', navigator), // TODO: window.webkitNotifications?
|
||||
prefixed('alarms', navigator), // Alarms
|
||||
'mozSystem' in (new XMLHttpRequest()), // mozSystemXHR
|
||||
prefixed('TCPSocket', navigator), // mozTCPSocket/mozTCPSocketServer
|
||||
prefixed('mozInputMethod', navigator),
|
||||
prefixed('mozMobileConnections', navigator)
|
||||
];
|
||||
|
||||
var profile = parseInt(capabilities.map(function(x) {return !!x ? '1' : '0';}).join(''), 2).toString(16);
|
||||
// Add a count.
|
||||
profile += '.' + capabilities.length;
|
||||
// Add a version number.
|
||||
profile += '.4';
|
||||
|
||||
return {
|
||||
capabilities: capabilities,
|
||||
profile: profile
|
||||
};
|
||||
|
||||
});
|
|
@ -250,6 +250,11 @@ define('builder',
|
|||
});
|
||||
} else {
|
||||
var done = function(data) {
|
||||
if (signature.filters) {
|
||||
signature.filters.forEach(function(filterName) {
|
||||
data = env.filters[filterName](data);
|
||||
});
|
||||
}
|
||||
document.getElementById(uid).innerHTML = data;
|
||||
};
|
||||
request.done(done).fail(function() {
|
||||
|
|
|
@ -1,8 +1,99 @@
|
|||
define('cache', ['log', 'rewriters', 'storage'], function(log, rewriters, storage) {
|
||||
define('cache',
|
||||
['log', 'rewriters', 'settings', 'storage', 'user', 'utils', 'z'],
|
||||
function(log, rewriters, settings, storage, user, utils, z) {
|
||||
|
||||
var console = log('cache');
|
||||
|
||||
var cache = {};
|
||||
var cache_key = 'request_cache';
|
||||
|
||||
if (settings.offline_cache_enabled()) {
|
||||
cache = JSON.parse(storage.getItem(cache_key) || '{}');
|
||||
flush_expired();
|
||||
}
|
||||
|
||||
// Persist the cache for whitelisted URLs.
|
||||
window.addEventListener('beforeunload', save, false);
|
||||
|
||||
function get_ttl(url) {
|
||||
// Returns TTL for an API URL in microseconds.
|
||||
var path = utils.urlparse(url).pathname;
|
||||
if (path in settings.offline_cache_whitelist) {
|
||||
// Convert from seconds to microseconds.
|
||||
return settings.offline_cache_whitelist[path] * 1000;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function save() {
|
||||
if (!settings.offline_cache_enabled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
var cache_to_save = {};
|
||||
Object.keys(cache).forEach(function (url) {
|
||||
// If there is a TTL assigned to this URL, then we can cache it.
|
||||
if (get_ttl(url) !== null) {
|
||||
cache_to_save[url] = cache[url];
|
||||
}
|
||||
});
|
||||
|
||||
// Trigger an event to save the cache. (We do size checks to see if
|
||||
// the combined request+model cache is too large to persist.)
|
||||
z.doc.trigger('save_cache', cache_key);
|
||||
|
||||
// Persist only if the data has changed.
|
||||
var cache_to_save_str = JSON.stringify(cache_to_save);
|
||||
if (storage.getItem(cache_key) !== cache_to_save_str) {
|
||||
storage.setItem(cache_key, cache_to_save_str);
|
||||
console.log('Persisting request cache');
|
||||
}
|
||||
}
|
||||
|
||||
function flush() {
|
||||
cache = {};
|
||||
}
|
||||
|
||||
function flush_signed() {
|
||||
// This gets called when a user logs out, so we remove any requests
|
||||
// with user data in them.
|
||||
|
||||
// First, we remove every signed URL from the request cache.
|
||||
Object.keys(cache).forEach(function (url) {
|
||||
if (url.indexOf('_user=' + user.get_token()) !== -1) {
|
||||
console.log('Removing signed URL', url);
|
||||
delete cache[url];
|
||||
}
|
||||
});
|
||||
|
||||
// Then, we persist the cache.
|
||||
save();
|
||||
}
|
||||
|
||||
function flush_expired() {
|
||||
// This gets called once when the page loads to purge any expired
|
||||
// persisted responses.
|
||||
|
||||
var now = +new Date();
|
||||
var time;
|
||||
var ttl = null;
|
||||
|
||||
Object.keys(cache).forEach(function (url) {
|
||||
// Get the timestamp.
|
||||
time = cache[url].__time;
|
||||
|
||||
// Get the TTL if this URL is allowed to be cached.
|
||||
ttl = get_ttl(url);
|
||||
|
||||
// If the item is expired, remove it from the cache.
|
||||
if (!time || time + ttl <= now) {
|
||||
console.log('Removing expired URL', url);
|
||||
return delete cache[url];
|
||||
}
|
||||
});
|
||||
|
||||
save();
|
||||
}
|
||||
|
||||
function has(key) {
|
||||
return key in cache;
|
||||
|
@ -48,7 +139,7 @@ define('cache', ['log', 'rewriters', 'storage'], function(log, rewriters, storag
|
|||
get: function(key) {
|
||||
if (has(key)) {
|
||||
return get(key);
|
||||
} else if (key in storageKeys) {
|
||||
} else {
|
||||
var val = storage.getItem(persistentCachePrefix + key);
|
||||
set(key, val);
|
||||
return val;
|
||||
|
@ -59,9 +150,7 @@ define('cache', ['log', 'rewriters', 'storage'], function(log, rewriters, storag
|
|||
set(key, val);
|
||||
},
|
||||
bust: function(key) {
|
||||
if (key in storageKeys) {
|
||||
storage.removeItem(persistentCachePrefix + key);
|
||||
}
|
||||
storage.removeItem(persistentCachePrefix + key);
|
||||
bust(key);
|
||||
},
|
||||
has: function(key) {
|
||||
|
@ -100,15 +189,18 @@ define('cache', ['log', 'rewriters', 'storage'], function(log, rewriters, storag
|
|||
}
|
||||
|
||||
return {
|
||||
has: has,
|
||||
get: get,
|
||||
set: set,
|
||||
bust: bust,
|
||||
purge: purge,
|
||||
|
||||
attemptRewrite: rewrite,
|
||||
bust: bust,
|
||||
cache: cache,
|
||||
flush: flush,
|
||||
flush_expired: flush_expired,
|
||||
flush_signed: flush_signed,
|
||||
get: get,
|
||||
get_ttl: get_ttl,
|
||||
has: has,
|
||||
persist: persistent,
|
||||
purge: purge,
|
||||
raw: cache,
|
||||
|
||||
persist: persistent
|
||||
set: set
|
||||
};
|
||||
});
|
||||
|
|
|
@ -22,16 +22,15 @@ define('capabilities', [], function() {
|
|||
'webactivities': !!(navigator.setMessageHandler || navigator.mozSetMessageHandler),
|
||||
'firefoxOS': navigator.mozApps && navigator.mozApps.installPackage &&
|
||||
navigator.userAgent.indexOf('Android') === -1 &&
|
||||
navigator.userAgent.indexOf('Mobile') !== -1,
|
||||
'persona': !!navigator.id,
|
||||
(navigator.userAgent.indexOf('Mobile') !== -1 || navigator.userAgent.indexOf('Tablet') !== -1),
|
||||
'phantom': navigator.userAgent.match(/Phantom/) // Don't use this if you can help it.
|
||||
};
|
||||
|
||||
static_caps.persona = !!navigator.id && !static_caps.phantom;
|
||||
static_caps.persona = function() { return (!!navigator.id || !!navigator.mozId) && !static_caps.phantom; };
|
||||
|
||||
// True if the login should inherit mobile behaviors such as allowUnverified.
|
||||
// The _shimmed check is for B2G where identity is native (not shimmed).
|
||||
static_caps.mobileLogin = static_caps.persona && (!navigator.id._shimmed || static_caps.firefoxAndroid);
|
||||
static_caps.mobileLogin = function() { return static_caps.persona() && (!navigator.id._shimmed || static_caps.firefoxAndroid); };
|
||||
|
||||
static_caps.device_type = function() {
|
||||
if (static_caps.firefoxOS) {
|
||||
|
|
|
@ -1,11 +1,14 @@
|
|||
define('forms', ['jquery', 'z'], function($, z) {
|
||||
define('forms', ['z'], function(z) {
|
||||
|
||||
function checkValid(form) {
|
||||
if (form) {
|
||||
$(form).filter(':not([novalidate])').find('button[type=submit]').attr('disabled', !form.checkValidity());
|
||||
}
|
||||
}
|
||||
z.body.on('change keyup paste', 'input, select, textarea', function(e) {
|
||||
// Note 'input' event is required for FF android (bug 977642)
|
||||
z.body.on('change input', 'input, textarea', function(e) {
|
||||
checkValid(e.target.form);
|
||||
}).on('change', 'select', function(e) {
|
||||
checkValid(e.target.form);
|
||||
}).on('loaded decloak', function() {
|
||||
$('form:not([novalidate])').each(function() {
|
||||
|
|
|
@ -117,6 +117,7 @@ define('helpers',
|
|||
// Functions provided in the default context.
|
||||
var helpers = {
|
||||
api: require('urls').api.url,
|
||||
apiHost: require('urls').api.host,
|
||||
apiParams: require('urls').api.params,
|
||||
anonApi: require('urls').api.unsigned.url,
|
||||
anonApiParams: require('urls').api.unsigned.params,
|
||||
|
@ -127,10 +128,11 @@ define('helpers',
|
|||
_plural: make_safe(l10n.ngettext),
|
||||
format: require('format').format,
|
||||
settings: require('settings'),
|
||||
capabilities: require('capabilities'),
|
||||
user: userobj,
|
||||
|
||||
escape: utils.escape_,
|
||||
len: function(x) {return x ? x.length || 0 : 0;},
|
||||
len: function(x) {return x.length;},
|
||||
max: Math.max,
|
||||
min: Math.min,
|
||||
range: _.range,
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
(function() {
|
||||
|
||||
// This is a little misleading. If you're using the Marketplace this is likely
|
||||
// overridden below with body_langs. See bug 892741 for details.
|
||||
var languages = [
|
||||
'bg', 'ca', 'cs', 'de', 'el', 'en-US', 'es', 'eu', 'fr', 'ga-IE', 'hr',
|
||||
'hu', 'it', 'ja', 'mk', 'nl', 'pl', 'pt-BR', 'ro', 'ru', 'sk', 'sr',
|
||||
'sr-Latn', 'tr', 'zh-TW', 'dbg'
|
||||
'bg', 'bn-BD', 'ca', 'cs', 'da', 'de', 'el', 'en-US', 'es', 'eu', 'fr',
|
||||
'ga-IE', 'hr', 'hu', 'it', 'ja', 'ko', 'mk', 'nb-NO', 'nl', 'pl', 'pt-BR',
|
||||
'ro', 'ru', 'sk', 'sq', 'sr', 'sr-Latn', 'tr', 'zh-CN', 'zh-TW', 'dbg'
|
||||
];
|
||||
var body_langs;
|
||||
if (body_langs = document.body.getAttribute('data-languages')) {
|
||||
|
|
|
@ -182,6 +182,27 @@ exports.groupBy = function(obj, val) {
|
|||
return result;
|
||||
};
|
||||
|
||||
exports.toArray = function(obj) {
|
||||
return Array.prototype.slice.call(obj);
|
||||
};
|
||||
|
||||
exports.without = function(array) {
|
||||
var result = [];
|
||||
if (!array) {
|
||||
return result;
|
||||
}
|
||||
var index = -1,
|
||||
length = array.length,
|
||||
contains = exports.toArray(arguments).slice(1);
|
||||
|
||||
while(++index < length) {
|
||||
if(contains.indexOf(array[index]) === -1) {
|
||||
result.push(array[index]);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
exports.extend = function(obj, obj2) {
|
||||
for(var k in obj2) {
|
||||
obj[k] = obj2[k];
|
||||
|
@ -233,6 +254,27 @@ exports.map = function(obj, func) {
|
|||
return results;
|
||||
};
|
||||
|
||||
exports.asyncParallel = function(funcs, done) {
|
||||
var count = funcs.length,
|
||||
result = new Array(count),
|
||||
current = 0;
|
||||
|
||||
var makeNext = function(i) {
|
||||
return function(res) {
|
||||
result[i] = res;
|
||||
current += 1;
|
||||
|
||||
if (current === count) {
|
||||
done(result);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
for (var i = 0; i < count; i++) {
|
||||
funcs[i](makeNext(i));
|
||||
}
|
||||
};
|
||||
|
||||
exports.asyncIter = function(arr, iter, cb) {
|
||||
var i = -1;
|
||||
|
||||
|
@ -270,6 +312,44 @@ exports.asyncFor = function(obj, iter, cb) {
|
|||
next();
|
||||
};
|
||||
|
||||
if(!Array.prototype.indexOf) {
|
||||
Array.prototype.indexOf = function(array, searchElement /*, fromIndex */) {
|
||||
if (array === null) {
|
||||
throw new TypeError();
|
||||
}
|
||||
var t = Object(array);
|
||||
var len = t.length >>> 0;
|
||||
if (len === 0) {
|
||||
return -1;
|
||||
}
|
||||
var n = 0;
|
||||
if (arguments.length > 2) {
|
||||
n = Number(arguments[2]);
|
||||
if (n != n) { // shortcut for verifying if it's NaN
|
||||
n = 0;
|
||||
} else if (n !== 0 && n !== Infinity && n !== -Infinity) {
|
||||
n = (n > 0 || -1) * Math.floor(Math.abs(n));
|
||||
}
|
||||
}
|
||||
if (n >= len) {
|
||||
return -1;
|
||||
}
|
||||
var k = n >= 0 ? n : Math.max(len - Math.abs(n), 0);
|
||||
for (; k < len; k++) {
|
||||
if (k in t && t[k] === searchElement) {
|
||||
return k;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
};
|
||||
}
|
||||
|
||||
if(!Array.prototype.map) {
|
||||
Array.prototype.map = function() {
|
||||
throw new Error("map is unimplemented for this js engine");
|
||||
};
|
||||
}
|
||||
|
||||
exports.keys = function(obj) {
|
||||
if(Object.prototype.keys) {
|
||||
return obj.keys();
|
||||
|
@ -497,6 +577,17 @@ function memberLookup(obj, val) {
|
|||
return obj[val];
|
||||
}
|
||||
|
||||
function callWrap(obj, name, args) {
|
||||
if(!obj) {
|
||||
throw new Error('Unable to call `' + name + '`, which is undefined or falsey');
|
||||
}
|
||||
else if(typeof obj !== 'function') {
|
||||
throw new Error('Unable to call `' + name + '`, which is not a function');
|
||||
}
|
||||
|
||||
return obj.apply(this, args);
|
||||
}
|
||||
|
||||
function contextOrFrameLookup(context, frame, name) {
|
||||
var val = frame.lookup(name);
|
||||
return (val !== undefined && val !== null) ?
|
||||
|
@ -596,6 +687,7 @@ modules.runtime = {
|
|||
suppressValue: suppressValue,
|
||||
memberLookup: memberLookup,
|
||||
contextOrFrameLookup: contextOrFrameLookup,
|
||||
callWrap: callWrap,
|
||||
handleError: handleError,
|
||||
isArray: lib.isArray,
|
||||
keys: lib.keys,
|
||||
|
@ -1298,7 +1390,11 @@ nunjucks.configure = function(templatesPath, opts) {
|
|||
opts = templatesPath;
|
||||
templatesPath = null;
|
||||
}
|
||||
return e = new env.Environment(null, opts);
|
||||
|
||||
var noWatch = 'watch' in opts ? !opts.watch : false;
|
||||
e = new env.Environment(null, opts);
|
||||
|
||||
return e;
|
||||
};
|
||||
|
||||
nunjucks.render = function(name, ctx, cb) {
|
||||
|
|
|
@ -64,7 +64,7 @@ define('log', ['storage', 'utils'], function(storage, utils) {
|
|||
// Have log('payments') but want log('payments', 'mock')?
|
||||
// log('payments').tagged('mock') gives you the latter.
|
||||
tagged: function(newTag) {
|
||||
return logger(type, (tag ? tag + '][' : '') + newTag, onlog);
|
||||
return logger(type, tag + '][' + newTag, onlog);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
define('login',
|
||||
['capabilities', 'defer', 'jquery', 'log', 'notification', 'settings', 'underscore', 'urls', 'user', 'requests', 'z'],
|
||||
function(capabilities, defer, $, log, notification, settings, _, urls, user, requests, z) {
|
||||
['cache', 'capabilities', 'defer', 'jquery', 'log', 'notification', 'settings', 'underscore', 'urls', 'user', 'utils', 'requests', 'z'],
|
||||
function(cache, capabilities, defer, $, log, notification, settings, _, urls, user, utils, requests, z) {
|
||||
|
||||
var console = log('login');
|
||||
|
||||
|
@ -25,18 +25,19 @@ define('login',
|
|||
e.preventDefault();
|
||||
|
||||
requests.del(urls.api.url('logout')).done(function() {
|
||||
// Moved here from the onlogout callback for now until
|
||||
// https://github.com/mozilla/browserid/issues/3229
|
||||
// gets fixed.
|
||||
cache.flush_signed();
|
||||
user.clear_token();
|
||||
z.body.removeClass('logged-in');
|
||||
z.page.trigger('reload_chrome').trigger('before_logout');
|
||||
|
||||
if (capabilities.persona) {
|
||||
if (capabilities.persona()) {
|
||||
console.log('Triggering Persona logout');
|
||||
navigator.id.logout();
|
||||
}
|
||||
|
||||
// Moved here from the onlogout callback for now until
|
||||
// https://github.com/mozilla/browserid/issues/3229
|
||||
// gets fixed.
|
||||
if (!z.context.dont_reload_on_login) {
|
||||
require('views').reload().done(function(){
|
||||
z.page.trigger('logged_out');
|
||||
|
@ -72,7 +73,7 @@ define('login',
|
|||
// See bug 910938.
|
||||
opt.experimental_forceIssuer = settings.persona_unverified_issuer;
|
||||
}
|
||||
if (capabilities.mobileLogin) {
|
||||
if (capabilities.mobileLogin()) {
|
||||
// On mobile we don't require new accounts to verify their email.
|
||||
// On desktop, we do.
|
||||
opt.experimental_allowUnverified = true;
|
||||
|
@ -81,10 +82,13 @@ define('login',
|
|||
console.log('Not allowing unverified emails');
|
||||
}
|
||||
|
||||
if (capabilities.persona) {
|
||||
console.log('Requesting login from Persona');
|
||||
navigator.id.request(opt);
|
||||
}
|
||||
persona_loaded.done(function() {
|
||||
if (capabilities.persona()) {
|
||||
console.log('Requesting login from Persona');
|
||||
navigator.id.request(opt);
|
||||
}
|
||||
});
|
||||
|
||||
return def.promise();
|
||||
}
|
||||
|
||||
|
@ -94,7 +98,7 @@ define('login',
|
|||
var data = {
|
||||
assertion: assertion,
|
||||
audience: window.location.protocol + '//' + window.location.host,
|
||||
is_mobile: capabilities.mobileLogin
|
||||
is_mobile: capabilities.mobileLogin()
|
||||
};
|
||||
|
||||
z.page.trigger('before_login');
|
||||
|
@ -146,24 +150,66 @@ define('login',
|
|||
});
|
||||
}
|
||||
|
||||
var email = user.get_setting('email') || '';
|
||||
if (email) {
|
||||
console.log('Detected user', email);
|
||||
} else {
|
||||
console.log('No previous user detected');
|
||||
var persona_def = defer.Deferred();
|
||||
var persona_loaded = persona_def.promise();
|
||||
|
||||
var persona_loading_start = +(new Date());
|
||||
var persona_loading_time = 0;
|
||||
var persona_step = 25; // 25 milliseconds
|
||||
|
||||
var GET = utils.getVars();
|
||||
|
||||
var persona_shim_included = $('script[src="' + settings.persona_shim_url + '"]').length;
|
||||
|
||||
// If for some reason Zamboni got `?nativepersona=true` but we actually
|
||||
// don't have native Persona, then let's inject a script to load the shim.
|
||||
if (!persona_shim_included && !capabilities.persona()) {
|
||||
var s = document.createElement('script');
|
||||
s.async = true;
|
||||
s.src = settings.persona_shim_url;
|
||||
document.body.appendChild(s);
|
||||
}
|
||||
|
||||
if (capabilities.persona) {
|
||||
console.log('Calling navigator.id.watch');
|
||||
navigator.id.watch({
|
||||
loggedInUser: email,
|
||||
onlogin: gotVerifiedEmail,
|
||||
onlogout: function() {
|
||||
z.body.removeClass('logged-in');
|
||||
z.page.trigger('reload_chrome').trigger('logout');
|
||||
}
|
||||
var persona_interval = setInterval(function() {
|
||||
persona_loading_time = +(new Date()) - persona_loading_start;
|
||||
if (capabilities.persona()) {
|
||||
console.log('Persona loaded (' + persona_loading_time / 1000 + 's)');
|
||||
persona_def.resolve();
|
||||
clearInterval(persona_interval);
|
||||
} else if (persona_loading_time >= settings.persona_timeout) {
|
||||
console.error('Persona timeout (' + persona_loading_time / 1000 + 's)');
|
||||
persona_def.reject();
|
||||
clearInterval(persona_interval);
|
||||
}
|
||||
}, persona_step);
|
||||
|
||||
persona_loaded.done(function() {
|
||||
// This lets us change the cursor for the "Sign in" link.
|
||||
z.body.addClass('persona-loaded');
|
||||
|
||||
var email = user.get_setting('email') || '';
|
||||
if (email) {
|
||||
console.log('Detected user', email);
|
||||
} else {
|
||||
console.log('No previous user detected');
|
||||
}
|
||||
|
||||
if (capabilities.persona()) {
|
||||
console.log('Calling navigator.id.watch');
|
||||
navigator.id.watch({
|
||||
loggedInUser: email,
|
||||
onlogin: gotVerifiedEmail,
|
||||
onlogout: function() {
|
||||
z.body.removeClass('logged-in');
|
||||
z.page.trigger('reload_chrome').trigger('logout');
|
||||
}
|
||||
});
|
||||
}
|
||||
}).fail(function() {
|
||||
notification.notification({
|
||||
message: gettext('Persona cannot be reached. Try again later.')
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return {login: startLogin};
|
||||
});
|
||||
|
|
|
@ -1,10 +1,68 @@
|
|||
define('models', ['defer', 'log', 'requests', 'settings', 'underscore'], function(defer, log, requests, settings, _) {
|
||||
define('models',
|
||||
['cache', 'defer', 'log', 'requests', 'settings', 'storage', 'underscore', 'z'],
|
||||
function(cache, defer, log, requests, settings, storage, _, z) {
|
||||
|
||||
var console = log('model');
|
||||
|
||||
// {'type': {'<id>': object}}
|
||||
var cache_key = 'model_cache';
|
||||
var data_store = {};
|
||||
|
||||
if (settings.offline_cache_enabled()) {
|
||||
data_store = JSON.parse(storage.getItem(cache_key) || '{}');
|
||||
}
|
||||
|
||||
// Persist the model cache.
|
||||
window.addEventListener('beforeunload', save, false);
|
||||
|
||||
z.doc.on('saving_offline_cache', function (e, cache_key) {
|
||||
// Really, this should be an LRU cache but the builder has an
|
||||
// expectation that a hit to the request cache means that the models
|
||||
// have been casted and exist already in the model cache too.
|
||||
//
|
||||
// It gets too complicated having one LRU cache for the request cache
|
||||
// and then independent LRU caches for app, category, and collection
|
||||
// model caches. It's fine. It's fine.
|
||||
|
||||
var data = {
|
||||
'request_cache': JSON.stringify(cache.cache),
|
||||
'model_cache': JSON.stringify(data_store)
|
||||
};
|
||||
|
||||
var size = (JSON.stringify(data.request_cache).length +
|
||||
JSON.stringify(data.model_cache).length);
|
||||
if (size >= settings.offline_cache_limit) {
|
||||
console.warn('Quota exceeded for request/model offline cache; ' +
|
||||
'flushing cache');
|
||||
cache.flush();
|
||||
flush();
|
||||
storage.setItem(cache_key, data_store_str);
|
||||
} else {
|
||||
// Persist only if the data has changed.
|
||||
var data_store_str = data[cache_key];
|
||||
if (storage.getItem(cache_key) !== data_store_str) {
|
||||
storage.setItem(cache_key, data_store_str);
|
||||
console.log('Persisting model cache');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
function flush() {
|
||||
// Purge cache for every type of model.
|
||||
data_store = {};
|
||||
}
|
||||
|
||||
function save() {
|
||||
z.doc.trigger('save_cache', cache_key);
|
||||
|
||||
// Persist only if the data has changed.
|
||||
var data_store_str = JSON.stringify(data_store);
|
||||
if (storage.getItem(cache_key) !== data_store_str) {
|
||||
storage.setItem(cache_key, data_store_str);
|
||||
console.log('Persisting model cache');
|
||||
}
|
||||
}
|
||||
|
||||
var prototypes = settings.model_prototypes;
|
||||
|
||||
return function(type) {
|
||||
|
@ -96,11 +154,13 @@ define('models', ['defer', 'log', 'requests', 'settings', 'underscore'], functio
|
|||
|
||||
return {
|
||||
cast: cast,
|
||||
uncast: uncast,
|
||||
data_store: data_store,
|
||||
del: del,
|
||||
flush: flush,
|
||||
get: get,
|
||||
lookup: lookup,
|
||||
purge: purge,
|
||||
del: del
|
||||
uncast: uncast
|
||||
};
|
||||
};
|
||||
|
||||
|
|
|
@ -10,6 +10,7 @@ define('navigation',
|
|||
{path: '/', type: 'root'}
|
||||
];
|
||||
var initialized = false;
|
||||
var scrollTimer;
|
||||
|
||||
function extract_nav_url(url) {
|
||||
// This function returns the URL that we should use for navigation.
|
||||
|
@ -69,8 +70,16 @@ define('navigation',
|
|||
}
|
||||
top = state.scrollTop;
|
||||
}
|
||||
console.log('Setting scroll to', top);
|
||||
window.scrollTo(0, top);
|
||||
|
||||
// Introduce small delay to ensure content
|
||||
// is ready to scroll. (Bug 976466)
|
||||
if (scrollTimer) {
|
||||
window.clearTimeout(scrollTimer);
|
||||
}
|
||||
scrollTimer = window.setTimeout(function() {
|
||||
console.log('Setting scroll to', top);
|
||||
window.scrollTo(0, top);
|
||||
}, 250);
|
||||
|
||||
// Clean the path's parameters.
|
||||
// /foo/bar?foo=bar&q=blah -> /foo/bar?q=blah
|
||||
|
@ -190,7 +199,7 @@ define('navigation',
|
|||
el.getAttribute('rel') === 'external';
|
||||
}
|
||||
|
||||
z.doc.on('click', 'a', function(e) {
|
||||
z.body.on('click', 'a', function(e) {
|
||||
var href = this.getAttribute('href');
|
||||
var $elm = $(this);
|
||||
var preserveScrollData = $elm.data('preserveScroll');
|
||||
|
@ -214,16 +223,47 @@ define('navigation',
|
|||
}
|
||||
var state = e.originalEvent.state;
|
||||
if (state) {
|
||||
console.log('popstate navigate');
|
||||
navigate(state.path, true, state);
|
||||
if (state.closeModalName) {
|
||||
console.log('popstate closing modal');
|
||||
cleanupModal(state.closeModalName);
|
||||
} else {
|
||||
console.log('popstate navigate');
|
||||
navigate(state.path, true, state);
|
||||
}
|
||||
}
|
||||
}).on('submit', 'form', function() {
|
||||
console.error("Form submissions are not allowed.");
|
||||
return false;
|
||||
});
|
||||
|
||||
function modal(name) {
|
||||
console.log('Opening modal', name);
|
||||
stack[0].closeModalName = name;
|
||||
history.replaceState(stack[0], false, stack[0].path);
|
||||
history.pushState(null, name, '#' + name);
|
||||
var path = window.location.href + '#' + name;
|
||||
stack.unshift({path: path, type: 'modal', name: name});
|
||||
}
|
||||
|
||||
function cleanupModal(name) {
|
||||
stack.shift();
|
||||
delete stack[0].closeModalName;
|
||||
z.win.trigger('closeModal', name);
|
||||
}
|
||||
|
||||
function closeModal(name) {
|
||||
if (stack[0].type === 'modal' && stack[0].name === name) {
|
||||
console.log('Closing modal', name);
|
||||
history.back();
|
||||
} else {
|
||||
console.log('Attempted to close modal', name, 'that was not open');
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
'back': back,
|
||||
'modal': modal,
|
||||
'closeModal': closeModal,
|
||||
'stack': function() {return stack;},
|
||||
'navigationFilter': navigationFilter,
|
||||
'extract_nav_url': extract_nav_url
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
define('requests',
|
||||
['cache', 'defer', 'log', 'utils'],
|
||||
function(cache, defer, log, utils) {
|
||||
['cache', 'defer', 'log', 'settings', 'utils'],
|
||||
function(cache, defer, log, settings, utils) {
|
||||
|
||||
var console = log('req');
|
||||
|
||||
|
@ -96,32 +96,58 @@ define('requests',
|
|||
return def;
|
||||
}
|
||||
|
||||
function get(url, nocache, persistent) {
|
||||
// During a single session, we never want to fetch the same URL more than
|
||||
// once. Because our persistent offline cache does XHRs in the background
|
||||
// to keep the cache fresh, we want to do that only once per session. In
|
||||
// order to do all this magic, we have to keep an array of all of the URLs
|
||||
// we hit per session.
|
||||
var urls_fetched = {};
|
||||
|
||||
function get(url, nocache) {
|
||||
var cache_offline = settings.offline_cache_enabled();
|
||||
|
||||
var cached;
|
||||
if (cache.has(url) && !nocache) {
|
||||
console.log('GETing from cache', url);
|
||||
cached = cache.get(url);
|
||||
} else if (cache.persist.has(url) && persistent && !nocache) {
|
||||
console.log('GETing from persistent cache', url);
|
||||
cached = cache.persist.get(url);
|
||||
}
|
||||
if (cached) {
|
||||
return defer.Deferred()
|
||||
.resolve(cached)
|
||||
.promise({__cached: true});
|
||||
}
|
||||
return _get.apply(this, arguments, persistent);
|
||||
}
|
||||
|
||||
function _get(url, nocache, persistent) {
|
||||
var def_cached;
|
||||
if (cached) {
|
||||
def_cached = defer.Deferred()
|
||||
.resolve(cached)
|
||||
.promise({__cached: true});
|
||||
if (!cache_offline || url in urls_fetched) {
|
||||
// If we don't need to make an XHR in the background to update
|
||||
// the cache, then let's bail now.
|
||||
return def_cached;
|
||||
}
|
||||
}
|
||||
|
||||
console.log('GETing', url);
|
||||
return ajax('GET', url).done(function(data, xhr) {
|
||||
urls_fetched[url] = null;
|
||||
|
||||
var def_ajax = ajax('GET', url).done(function(data) {
|
||||
console.log('GOT', url);
|
||||
if (!nocache) {
|
||||
data.__time = +(new Date());
|
||||
cache.set(url, data);
|
||||
if (persistent) cache.persist.set(url, data);
|
||||
}
|
||||
if (cached && cache_offline) {
|
||||
console.log('Updating request cache', url);
|
||||
}
|
||||
});
|
||||
|
||||
if (cached && cache_offline) {
|
||||
// If the response was cached, we still want to fire off the
|
||||
// AJAX request so the cache can get updated in the background,
|
||||
// but let's resolve this deferred with the cached response
|
||||
// so the request pool can get closed and the builder can render
|
||||
// the template for the `defer` block.
|
||||
return def_cached;
|
||||
}
|
||||
|
||||
return def_ajax;
|
||||
}
|
||||
|
||||
function handle_errors(xhr, type, status) {
|
||||
|
@ -175,6 +201,7 @@ define('requests',
|
|||
var initiated = 0;
|
||||
var marked_to_finish = false;
|
||||
var closed = false;
|
||||
var failed = false;
|
||||
|
||||
function finish() {
|
||||
if (closed) {
|
||||
|
@ -185,11 +212,7 @@ define('requests',
|
|||
closed = true;
|
||||
|
||||
// Resolve the deferred whenevs.
|
||||
if (window.setImmediate) {
|
||||
setImmediate(def.resolve);
|
||||
} else {
|
||||
setTimeout(def.resolve, 0);
|
||||
}
|
||||
setTimeout(failed ? def.reject : def.resolve, 0);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -202,6 +225,9 @@ define('requests',
|
|||
var req = func.apply(this, args);
|
||||
initiated++;
|
||||
requests.push(req);
|
||||
req.fail(function() {
|
||||
failed = true;
|
||||
});
|
||||
req.always(function() {
|
||||
initiated--;
|
||||
finish();
|
||||
|
|
|
@ -1,6 +1,14 @@
|
|||
define('settings', ['l10n', 'settings_local', 'underscore'], function(l10n, settings_local, _) {
|
||||
var gettext = l10n.gettext;
|
||||
|
||||
function offline_cache_enabled() {
|
||||
var storage = require('storage');
|
||||
if (storage.getItem('offline_cache_disabled') || require('capabilities').phantom) {
|
||||
return false;
|
||||
}
|
||||
return window.location.search.indexOf('cache=false') === -1;
|
||||
}
|
||||
|
||||
return _.defaults(settings_local, {
|
||||
app_name: 'commonplace app',
|
||||
init_module: 'main',
|
||||
|
@ -11,6 +19,15 @@ define('settings', ['l10n', 'settings_local', 'underscore'], function(l10n, sett
|
|||
|
||||
param_whitelist: ['q', 'sort'],
|
||||
api_param_blacklist: null,
|
||||
api_cdn_whitelist: {},
|
||||
|
||||
// These are the only URLs that should be cached
|
||||
// (key: URL; value: TTL [time to live] in seconds).
|
||||
// Keep in mind that the cache is always refreshed asynchronously;
|
||||
// these TTLs apply to only when the app is first launched.
|
||||
offline_cache_whitelist: {},
|
||||
offline_cache_enabled: offline_cache_enabled,
|
||||
offline_cache_limit: 1024 * 1024 * 4, // 4 MB
|
||||
|
||||
model_prototypes: {},
|
||||
|
||||
|
|
|
@ -1,6 +1,20 @@
|
|||
define('urls',
|
||||
['format', 'routes_api', 'routes_api_args', 'settings', 'user', 'utils'],
|
||||
function(format, api_endpoints, api_args, settings, user) {
|
||||
['format', 'log', 'routes_api', 'routes_api_args', 'settings', 'user', 'utils'],
|
||||
function(format, log, api_endpoints, api_args, settings, user, utils) {
|
||||
|
||||
var console = log('urls');
|
||||
|
||||
// The CDN URL is the same as the media URL but without the `/media/` path.
|
||||
if ('media_url' in settings) {
|
||||
var a = document.createElement('a');
|
||||
a.href = settings.media_url;
|
||||
settings.cdn_url = a.protocol + '//' + a.host;
|
||||
console.log('Using settings.media_url: ' + settings.media_url);
|
||||
console.log('Changed settings.cdn_url: ' + settings.cdn_url);
|
||||
} else {
|
||||
settings.cdn_url = settings.api_url;
|
||||
console.log('Changed settings.cdn_url to settings.api_url: ' + settings.api_url);
|
||||
}
|
||||
|
||||
var group_pattern = /\([^\)]+\)/;
|
||||
var optional_pattern = /(\(.*\)|\[.*\]|.)\?/g;
|
||||
|
@ -44,7 +58,7 @@ define('urls',
|
|||
args._user = user.get_token();
|
||||
}
|
||||
_removeBlacklistedParams(args);
|
||||
return require('utils').urlparams(out, args);
|
||||
return utils.urlparams(out, args);
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -53,7 +67,7 @@ define('urls',
|
|||
var out = func.apply(this, arguments);
|
||||
var args = api_args();
|
||||
_removeBlacklistedParams(args);
|
||||
return require('utils').urlparams(out, args);
|
||||
return utils.urlparams(out, args);
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -71,9 +85,12 @@ define('urls',
|
|||
console.error('Invalid API endpoint: ' + endpoint);
|
||||
return '';
|
||||
}
|
||||
var url = settings.api_url + format.format(api_endpoints[endpoint], args || []);
|
||||
|
||||
var path = format.format(api_endpoints[endpoint], args || []);
|
||||
var url = apiHost(path) + path;
|
||||
|
||||
if (params) {
|
||||
return require('utils').urlparams(url, params);
|
||||
return utils.urlparams(url, params);
|
||||
}
|
||||
return url;
|
||||
}
|
||||
|
@ -82,6 +99,16 @@ define('urls',
|
|||
return api(endpoint, [], params);
|
||||
}
|
||||
|
||||
function apiHost(path) {
|
||||
// If the API URL is already reversed, then here's where we determine
|
||||
// whether that URL gets served from the API or CDN.
|
||||
var host = settings.api_url;
|
||||
if (utils.baseurl(path) in settings.api_cdn_whitelist) {
|
||||
host = settings.cdn_url;
|
||||
}
|
||||
return host;
|
||||
}
|
||||
|
||||
function media(path) {
|
||||
var media_url = settings.media_url;
|
||||
if (media_url[media_url.length - 1] !== '/') {
|
||||
|
@ -97,6 +124,7 @@ define('urls',
|
|||
reverse: reverse,
|
||||
api: {
|
||||
url: _userArgs(api),
|
||||
host: apiHost,
|
||||
params: _userArgs(apiParams),
|
||||
sign: _userArgs(function(url) {return url;}),
|
||||
unsign: _anonymousArgs(function(url) {return url;}),
|
||||
|
@ -106,6 +134,7 @@ define('urls',
|
|||
},
|
||||
base: {
|
||||
url: api,
|
||||
host: apiHost,
|
||||
params: apiParams
|
||||
}
|
||||
},
|
||||
|
|
|
@ -33,7 +33,8 @@ define('utils', ['jquery', 'l10n', 'underscore'], function($, l10n, _) {
|
|||
var $cc = $(this);
|
||||
$cc.closest('form')
|
||||
.find('#' + $cc.data('for'))
|
||||
.on('keyup blur', _.throttle(function() {countChars(this, $cc);}, 250))
|
||||
// Note 'input' event is need for FF android see (bug 976262)
|
||||
.on('input blur', _.throttle(function() {countChars(this, $cc);}, 250))
|
||||
.trigger('blur');
|
||||
});
|
||||
}
|
||||
|
@ -179,6 +180,13 @@ define('utils', ['jquery', 'l10n', 'underscore'], function($, l10n, _) {
|
|||
return 'other';
|
||||
}
|
||||
|
||||
var a = document.createElement('a');
|
||||
|
||||
function urlparse(url) {
|
||||
a.href = url;
|
||||
return a;
|
||||
}
|
||||
|
||||
return {
|
||||
'_pd': _pd,
|
||||
'baseurl': baseurl,
|
||||
|
@ -193,6 +201,7 @@ define('utils', ['jquery', 'l10n', 'underscore'], function($, l10n, _) {
|
|||
'slugify': slugify,
|
||||
'urlencode': urlencode,
|
||||
'urlparams': urlparams,
|
||||
'urlparse': urlparse,
|
||||
'urlunparam': urlunparam,
|
||||
'translate': translate
|
||||
};
|
||||
|
|
|
@ -1,13 +1,36 @@
|
|||
define('views/debug',
|
||||
['cache', 'capabilities', 'log', 'notification', 'requests', 'settings', 'storage', 'utils', 'z'],
|
||||
function(cache, capabilities, log, notification, requests, settings, storage, utils, z) {
|
||||
['buckets', 'cache', 'capabilities', 'log', 'models', 'notification', 'requests', 'settings', 'storage', 'user', 'utils', 'z'],
|
||||
function(buckets, cache, capabilities, log, models, notification, requests, settings, storage, user, utils, z) {
|
||||
'use strict';
|
||||
|
||||
var persistent_console_debug = log.persistent('debug', 'change');
|
||||
var persistent_console_network = log.persistent('mobilenetwork', 'change');
|
||||
|
||||
var label = $(document.getElementById('debug-status'));
|
||||
z.doc.on('click', '#clear-localstorage', function(e) {
|
||||
storage.clear();
|
||||
notification.notification({message: 'localStorage cleared', timeout: 1000});
|
||||
|
||||
}).on('click', '#enable-offline-cache', function() {
|
||||
storage.removeItem('offline_cache_disabled');
|
||||
persistent_console_debug.log('Offline cache enabled:', new Date());
|
||||
require('views').reload();
|
||||
notification.notification({message: 'Offline cache enabled', timeout: 1000});
|
||||
|
||||
}).on('click', '#disable-offline-cache', function() {
|
||||
storage.setItem('offline_cache_disabled', '1');
|
||||
persistent_console_debug.log('Offline cache disabled:', new Date());
|
||||
require('views').reload();
|
||||
notification.notification({message: 'Offline cache disabled', timeout: 1000});
|
||||
|
||||
}).on('click', '#clear-offline-cache', function() {
|
||||
cache.flush();
|
||||
// This actually flushes all model caches.
|
||||
models('app').flush();
|
||||
persistent_console_debug.log('Offline cache cleared:', new Date());
|
||||
notification.notification({message: 'Offline cache cleared', timeout: 1000});
|
||||
window.location.reload();
|
||||
|
||||
}).on('click', '#clear-cookies', function() {
|
||||
var cookies = document.cookie.split(';');
|
||||
for (var i = 0; i < cookies.length; i++) {
|
||||
|
@ -17,6 +40,10 @@ define('views/debug',
|
|||
}
|
||||
notification.notification({message: 'cookies cleared', timeout: 1000});
|
||||
|
||||
}).on('click', '#nukecounter', function(e) {
|
||||
storage.removeItem('newscounter');
|
||||
notification.notification({message: 'newscounter reset', timeout: 1000});
|
||||
|
||||
}).on('click', '.cache-menu a', function(e) {
|
||||
e.preventDefault();
|
||||
var data = cache.get($(this).data('url'));
|
||||
|
@ -33,7 +60,8 @@ define('views/debug',
|
|||
persistent_logs: log.persistent.all,
|
||||
capabilities: capabilities,
|
||||
settings: settings,
|
||||
report_version: 1.0
|
||||
report_version: 1.0,
|
||||
profile: buckets.profile
|
||||
})};
|
||||
requests.post('https://ashes.paas.allizom.org/post_report', data).done(function(data) {
|
||||
notification.notification({
|
||||
|
@ -41,17 +69,40 @@ define('views/debug',
|
|||
timeout: 30000
|
||||
});
|
||||
});
|
||||
|
||||
}).on('change', '#debug-page select[name=region]', function(e) {
|
||||
var val = $(this).val();
|
||||
var current_region = user.get_setting('region_override');
|
||||
if (current_region !== val) {
|
||||
persistent_console_network.log('Manual region override change:', current_region, '→', val);
|
||||
}
|
||||
user.update_settings({region_override: val});
|
||||
z.page.trigger('reload_chrome');
|
||||
notification.notification({message: 'Region updated to ' + (settings.REGION_CHOICES_SLUG[val] || '---')});
|
||||
|
||||
}).on('change', '#debug-page select[name=carrier]', function(e) {
|
||||
var val = $(this).val();
|
||||
var current_carrier = user.get_setting('carrier_override');
|
||||
if (current_carrier !== val) {
|
||||
persistent_console_network.log('Manual carrier override change:', current_carrier, '→', val);
|
||||
}
|
||||
user.update_settings({carrier_override: val});
|
||||
z.page.trigger('reload_chrome');
|
||||
notification.notification({message: 'Carrier updated to ' + val});
|
||||
});
|
||||
|
||||
return function(builder, args) {
|
||||
var recent_logs = log.get_recent(100);
|
||||
|
||||
builder.start('debug.html', {
|
||||
carriers: require('mobilenetwork').carriers,
|
||||
cache: cache.raw,
|
||||
capabilities: capabilities,
|
||||
profile: buckets.profile,
|
||||
recent_logs: recent_logs,
|
||||
persistent_logs: log.persistent.all,
|
||||
filter: log.filter
|
||||
filter: log.filter,
|
||||
request_cache: JSON.parse(storage.getItem('request_cache') || '{}')
|
||||
});
|
||||
|
||||
builder.z('type', 'leaf debug');
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
<section class="main infobox prose">
|
||||
<section class="main infobox prose" id="debug-page">
|
||||
<div>
|
||||
<style>
|
||||
dt {
|
||||
|
@ -8,6 +8,12 @@
|
|||
dd {
|
||||
float: left;
|
||||
}
|
||||
#debug-page label {
|
||||
font-size: 18px;
|
||||
}
|
||||
#debug-page select {
|
||||
margin-left: 10px;
|
||||
}
|
||||
</style>
|
||||
<h2>Debug</h2>
|
||||
<p>
|
||||
|
@ -18,16 +24,61 @@
|
|||
<button class="button" id="clear-localstorage">Clear <code>localStorage</code></button>
|
||||
</p>
|
||||
|
||||
{% if settings.offline_cache_enabled() %}
|
||||
<p>
|
||||
<button class="button" id="disable-offline-cache">Disable offline cache</button>
|
||||
</p>
|
||||
{% else %}
|
||||
<p>
|
||||
<button class="button" id="enable-offline-cache">Enable offline cache</button>
|
||||
</p>
|
||||
{% endif %}
|
||||
|
||||
<p>
|
||||
<button class="button" id="clear-offline-cache">Clear offline cache</button>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<button class="button" id="clear-cookies">Clear cookies</button>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<button class="button" id="nukecounter">Clear <code>newscounter</code></button>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<label>
|
||||
Region Override
|
||||
<select name="region" id="region">
|
||||
{% set user_region = user.get_setting('region_override') %}
|
||||
<option value=""{{ ' selected' if not user_region }}>---</option>
|
||||
{% for code, region in REGIONS.items() %}
|
||||
<option value="{{ code }}"{{ ' selected' if code == user_region }}>
|
||||
{{ region }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</label>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<label>
|
||||
Carrier Override
|
||||
<select name="carrier" id="carrier">
|
||||
{% set user_carrier = user.get_setting('carrier_override') %}
|
||||
<option value=""{{ ' selected' if not user_carrier }}>---</option>
|
||||
{% for carrier in carriers %}
|
||||
<option{{ ' selected' if carrier == user_carrier }}>{{ carrier }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</label>
|
||||
</p>
|
||||
|
||||
<h3>Site Settings</h3>
|
||||
|
||||
<dl class="site-settings c">
|
||||
{% for setting in settings.items() %}
|
||||
<dt>{{ setting[0] }}</dt>
|
||||
<dd>{{ setting[1] or '––' }}</dd>
|
||||
<dd>{{ '—— truncated ——' if setting[0] == 'persona_site_logo' else setting[1] or '——' }}</dd>
|
||||
{% endfor %}
|
||||
</dl>
|
||||
|
||||
|
@ -47,6 +98,12 @@
|
|||
<dt>{{ cap[0] }}</dt>
|
||||
<dd>{{ cap[1] }}</dd>
|
||||
{% endfor %}
|
||||
{% for k, v in screen.items() %}
|
||||
<dt>window.screen.{{ k }}</dt>
|
||||
<dd>{{ v }}</dd>
|
||||
{% endfor %}
|
||||
<dt>Feature Profile</dt>
|
||||
<dd>{{ profile }} (<a href="/debug/features">view feature profile information</a>)</dd>
|
||||
</dl>
|
||||
|
||||
<h3>Cache</h3>
|
||||
|
@ -75,5 +132,15 @@
|
|||
{% endfor %}
|
||||
</ol>
|
||||
{% endfor %}
|
||||
|
||||
<h3>Offline Cache</h3>
|
||||
<ol>
|
||||
{% for url, response in request_cache.items() %}
|
||||
<li>{{ url }}</li>
|
||||
{% endfor %}
|
||||
</ol>
|
||||
<pre>
|
||||
{{ request_cache|stringify(null, 2) }}
|
||||
</pre>
|
||||
</div>
|
||||
</section>
|
||||
|
|
|
@ -4,6 +4,7 @@ var _ = require('underscore');
|
|||
var assert = a.assert;
|
||||
var eq_ = a.eq_;
|
||||
var eeq_ = a.eeq_;
|
||||
var feq_ = a.feq_;
|
||||
var mock = a.mock;
|
||||
|
||||
var cache = require('cache');
|
||||
|
@ -67,7 +68,11 @@ test('cache purge', function(done) {
|
|||
test('cache purge filter', function(done) {
|
||||
mock(
|
||||
'cache',
|
||||
{},
|
||||
{
|
||||
settings: {
|
||||
offline_cache_enabled: function () { return false; }
|
||||
}
|
||||
},
|
||||
function(cache) {
|
||||
var key = 'test2:';
|
||||
var str = 'poop';
|
||||
|
@ -243,4 +248,108 @@ test('cache deep rewrite on set', function(done) {
|
|||
);
|
||||
});
|
||||
|
||||
test('cache get_ttl', function(done) {
|
||||
mock(
|
||||
'cache',
|
||||
{
|
||||
settings: {
|
||||
offline_cache_enabled: function () { return true; },
|
||||
offline_cache_whitelist: {
|
||||
'/api/v1/fireplace/consumer-info/': 60 * 60, // 1 hour in seconds
|
||||
'/api/v1/fireplace/search/featured/': 60 * 60 * 6, // 6 hours
|
||||
'/api/v1/apps/category/': 60 * 60 * 24 // 1 day
|
||||
}
|
||||
}
|
||||
},
|
||||
function (cache) {
|
||||
eq_(cache.get_ttl('https://omg.org/api/v1/fireplace/consumer-info/'),
|
||||
60 * 60 * 1000); // 1 hour in microseconds
|
||||
eq_(cache.get_ttl('https://omg.org/api/v1/apps/category/'),
|
||||
60 * 60 * 24 * 1000); // 1 hour in microseconds
|
||||
eq_(cache.get_ttl('https://omg.org/api/v1/swag/yolo/foreva/'), null);
|
||||
done();
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
test('cache flush_signed', function(done) {
|
||||
mock(
|
||||
'cache',
|
||||
{
|
||||
user: {
|
||||
logged_in: function() { return true; },
|
||||
get_setting: function(x) {},
|
||||
get_token: function() { return 'SwaggasaurusRex';}
|
||||
}
|
||||
},
|
||||
function (cache) {
|
||||
var data = 'ratchet data';
|
||||
|
||||
var signed_url = 'https://omg.org/api/v1/app/yolo/?_user=SwaggasaurusRex';
|
||||
cache.set(signed_url, data);
|
||||
eq_(cache.get(signed_url), data);
|
||||
|
||||
var unsigned_url = 'https://omg.org/api/v1/app/swag/';
|
||||
cache.set(unsigned_url, data);
|
||||
eq_(cache.get(unsigned_url), data);
|
||||
|
||||
feq_(Object.keys(cache.cache).sort(), [unsigned_url, signed_url]);
|
||||
|
||||
// Calling this should clear all cache keys whose URLs contain
|
||||
// `_user=<token>`.
|
||||
cache.flush_signed();
|
||||
|
||||
feq_(Object.keys(cache.cache), [unsigned_url]);
|
||||
|
||||
done();
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
test('cache flush_expired', function(done) {
|
||||
mock(
|
||||
'cache',
|
||||
{
|
||||
settings: {
|
||||
offline_cache_enabled: function () { return true; },
|
||||
offline_cache_whitelist: {
|
||||
'/api/v1/fireplace/consumer-info/': 60 * 60, // 1 hour in seconds
|
||||
'/api/v1/fireplace/search/featured/': 60 * 60 * 6, // 6 hours
|
||||
'/api/v1/apps/category/': 60 * 60 * 24 // 1 day
|
||||
}
|
||||
}
|
||||
},
|
||||
function (cache) {
|
||||
// Both were just added and unexpired ...
|
||||
cache.set('https://omg.org/api/v1/fireplace/consumer-info/', {
|
||||
'__time': +new Date()
|
||||
});
|
||||
cache.set('https://omg.org/api/v1/fireplace/search/featured/', {
|
||||
'__time': +new Date()
|
||||
});
|
||||
cache.flush_expired();
|
||||
assert(cache.has('https://omg.org/api/v1/fireplace/consumer-info/'));
|
||||
assert(cache.has('https://omg.org/api/v1/fireplace/search/featured/'));
|
||||
|
||||
// Neither has expired ...
|
||||
cache.set('https://omg.org/api/v1/fireplace/consumer-info/', {
|
||||
'__time': +new Date() - (60 * 59 * 1000) // 59 min ago in microseconds
|
||||
});
|
||||
cache.flush_expired();
|
||||
assert(cache.has('https://omg.org/api/v1/fireplace/consumer-info/'));
|
||||
assert(cache.has('https://omg.org/api/v1/fireplace/search/featured/'));
|
||||
|
||||
// One has expired!
|
||||
cache.set('https://omg.org/api/v1/fireplace/consumer-info/', {
|
||||
'__time': +new Date() - (60 * 65 * 1000) // 1 hr 5 min ago in microseconds
|
||||
});
|
||||
cache.flush_expired();
|
||||
assert(!cache.has('https://omg.org/api/v1/fireplace/consumer-info/'));
|
||||
assert(cache.has('https://omg.org/api/v1/fireplace/search/featured/'));
|
||||
|
||||
done();
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
})();
|
||||
|
|
|
@ -20,7 +20,12 @@ test('model invalid type', function(done, fail) {
|
|||
test('model cast/lookup/purge', function(done, fail) {
|
||||
mock(
|
||||
'models',
|
||||
{settings: {model_prototypes: {'dummy': 'id', 'dummy2': 'id'}}},
|
||||
{
|
||||
settings: {
|
||||
offline_cache_enabled: function () { return false; },
|
||||
model_prototypes: {'dummy': 'id', 'dummy2': 'id'}
|
||||
}
|
||||
},
|
||||
function(models) {
|
||||
var d1 = models('dummy');
|
||||
var d2 = models('dummy2');
|
||||
|
@ -57,7 +62,12 @@ test('model cast/lookup/purge', function(done, fail) {
|
|||
test('model cast/lookup/delete', function(done, fail) {
|
||||
mock(
|
||||
'models',
|
||||
{settings: {model_prototypes: {'dummy': 'id'}}},
|
||||
{
|
||||
settings: {
|
||||
offline_cache_enabled: function () { return false; },
|
||||
model_prototypes: {'dummy': 'id'}
|
||||
}
|
||||
},
|
||||
function(models) {
|
||||
var d1 = models('dummy');
|
||||
d1.cast({
|
||||
|
@ -79,7 +89,12 @@ test('model cast/lookup/delete', function(done, fail) {
|
|||
test('model cast/lookup/delete val', function(done, fail) {
|
||||
mock(
|
||||
'models',
|
||||
{settings: {model_prototypes: {'dummy': 'id'}}},
|
||||
{
|
||||
settings: {
|
||||
offline_cache_enabled: function () { return false; },
|
||||
model_prototypes: {'dummy': 'id'}
|
||||
}
|
||||
},
|
||||
function(models) {
|
||||
var d1 = models('dummy');
|
||||
d1.cast({
|
||||
|
@ -101,7 +116,12 @@ test('model cast/lookup/delete val', function(done, fail) {
|
|||
test('model cast/uncast', function(done, fail) {
|
||||
mock(
|
||||
'models',
|
||||
{settings: {model_prototypes: {'dummy': 'id'}}},
|
||||
{
|
||||
settings: {
|
||||
offline_cache_enabled: function () { return false; },
|
||||
model_prototypes: {'dummy': 'id'}
|
||||
}
|
||||
},
|
||||
function(models) {
|
||||
var d1 = models('dummy');
|
||||
|
||||
|
@ -125,7 +145,12 @@ test('model cast/uncast', function(done, fail) {
|
|||
test('model cast/uncast lists', function(done, fail) {
|
||||
mock(
|
||||
'models',
|
||||
{settings: {model_prototypes: {'dummy': 'id'}}},
|
||||
{
|
||||
settings: {
|
||||
offline_cache_enabled: function () { return false; },
|
||||
model_prototypes: {'dummy': 'id'}
|
||||
}
|
||||
},
|
||||
function(models) {
|
||||
var d1 = models('dummy');
|
||||
|
||||
|
@ -165,7 +190,10 @@ test('model get hit', function(done, fail) {
|
|||
'models',
|
||||
{
|
||||
requests: {get: function(x) {return 'surprise! ' + x;}},
|
||||
settings: {model_prototypes: {'dummy': 'id'}}
|
||||
settings: {
|
||||
offline_cache_enabled: function () { return false; },
|
||||
model_prototypes: {'dummy': 'id'}
|
||||
}
|
||||
},
|
||||
function(models) {
|
||||
var d1 = models('dummy');
|
||||
|
@ -190,7 +218,10 @@ test('model get miss', function(done, fail) {
|
|||
'models',
|
||||
{
|
||||
requests: {get: function(x) {return 'surprise! ' + x;}},
|
||||
settings: {model_prototypes: {'dummy': 'id'}}
|
||||
settings: {
|
||||
offline_cache_enabled: function () { return false; },
|
||||
model_prototypes: {'dummy': 'id'}
|
||||
}
|
||||
},
|
||||
function(models) {
|
||||
var d1 = models('dummy');
|
||||
|
@ -207,7 +238,10 @@ test('model get getter', function(done, fail) {
|
|||
'models',
|
||||
{
|
||||
requests: {get: function(x) {return "not the droids you're looking for";}},
|
||||
settings: {model_prototypes: {'dummy': 'id'}}
|
||||
settings: {
|
||||
offline_cache_enabled: function () { return false; },
|
||||
model_prototypes: {'dummy': 'id'}
|
||||
}
|
||||
},
|
||||
function(models) {
|
||||
var d1 = models('dummy');
|
||||
|
@ -224,7 +258,12 @@ test('model get getter', function(done, fail) {
|
|||
test('model lookup by', function(done, fail) {
|
||||
mock(
|
||||
'models',
|
||||
{settings: {model_prototypes: {'dummy': 'id'}}},
|
||||
{
|
||||
settings: {
|
||||
offline_cache_enabled: function () { return false; },
|
||||
model_prototypes: {'dummy': 'id'}
|
||||
}
|
||||
},
|
||||
function(models) {
|
||||
var d1 = models('dummy');
|
||||
|
||||
|
@ -250,7 +289,12 @@ test('model lookup by', function(done, fail) {
|
|||
test('model lookup miss', function(done, fail) {
|
||||
mock(
|
||||
'models',
|
||||
{settings: {model_prototypes: {'dummy': 'id'}}},
|
||||
{
|
||||
settings: {
|
||||
offline_cache_enabled: function () { return false; },
|
||||
model_prototypes: {'dummy': 'id'}
|
||||
}
|
||||
},
|
||||
function(models) {
|
||||
var d1 = models('dummy');
|
||||
|
||||
|
@ -270,7 +314,12 @@ test('model lookup miss', function(done, fail) {
|
|||
test('model cast list', function(done, fail) {
|
||||
mock(
|
||||
'models',
|
||||
{settings: {model_prototypes: {'dummy': 'id'}}},
|
||||
{
|
||||
settings: {
|
||||
offline_cache_enabled: function () { return false; },
|
||||
model_prototypes: {'dummy': 'id'}
|
||||
}
|
||||
},
|
||||
function(models) {
|
||||
var d1 = models('dummy');
|
||||
|
||||
|
|
|
@ -77,12 +77,13 @@ test('api url', function(done, fail) {
|
|||
{
|
||||
capabilities: {firefoxOS: true, widescreen: function() { return false; }, touch: 'foo'},
|
||||
routes_api: {'homepage': '/foo/homepage'},
|
||||
routes_api_args: function() {return function() {return function() {return {foo: 'bar'};};};}, // Functions get pre-evaluated.
|
||||
settings: {api_url: 'api:'}
|
||||
settings: {
|
||||
api_url: 'api:',
|
||||
api_cdn_whitelist: {},
|
||||
}
|
||||
}, function(urls) {
|
||||
var homepage_url = urls.api.url('homepage');
|
||||
eq_(homepage_url.substr(0, 17), 'api:/foo/homepage');
|
||||
contains(homepage_url, 'foo=bar');
|
||||
done();
|
||||
},
|
||||
fail
|
||||
|
@ -95,8 +96,10 @@ test('api url signage', function(done, fail) {
|
|||
{
|
||||
capabilities: {firefoxOS: true, widescreen: function() { return false; }, touch: 'foo'},
|
||||
routes_api: {'homepage': '/foo/homepage'},
|
||||
routes_api_args: function() {return function() {return function() {return {foo: 'bar'};};};}, // Functions get pre-evaluated.
|
||||
settings: {api_url: 'api:'},
|
||||
settings: {
|
||||
api_url: 'api:',
|
||||
api_cdn_whitelist: {}
|
||||
},
|
||||
user: {
|
||||
logged_in: function() { return true; },
|
||||
get_setting: function(x) {},
|
||||
|
@ -127,7 +130,11 @@ test('api url blacklist', function(done, fail) {
|
|||
{
|
||||
capabilities: {firefoxOS: true, widescreen: function() { return false; }, touch: 'foo'},
|
||||
routes_api: {'homepage': '/foo/homepage'},
|
||||
settings: {api_url: 'api:', api_param_blacklist: ['region']}
|
||||
settings: {
|
||||
api_cdn_whitelist: {},
|
||||
api_url: 'api:',
|
||||
api_param_blacklist: ['region']
|
||||
}
|
||||
}, function(urls) {
|
||||
var homepage_url = urls.api.url('homepage');
|
||||
eq_(homepage_url.substr(0, 17), 'api:/foo/homepage');
|
||||
|
@ -138,12 +145,45 @@ test('api url blacklist', function(done, fail) {
|
|||
);
|
||||
});
|
||||
|
||||
test('api url CDN whitelist', function(done, fail) {
|
||||
mock(
|
||||
'urls',
|
||||
{
|
||||
routes_api: {
|
||||
'homepage': '/api/v1/homepage/',
|
||||
'search': '/api/v1/fireplace/search/?swag=yolo'
|
||||
},
|
||||
settings: {
|
||||
api_url: 'api:',
|
||||
api_cdn_whitelist: {
|
||||
'/api/v1/fireplace/search/': 60, // 1 minute
|
||||
'/api/v1/fireplace/search/featured/': 60 * 2, // 2 minutes
|
||||
},
|
||||
media_url: 'http://cdn.so.fast.omg.org'
|
||||
}
|
||||
}, function(urls) {
|
||||
var homepage_url = urls.api.url('homepage');
|
||||
eq_(homepage_url.substr(0, 21), 'api:/api/v1/homepage/');
|
||||
|
||||
var search_url = urls.api.url('search');
|
||||
eq_(search_url.substr(0, 51),
|
||||
'http://cdn.so.fast.omg.org/api/v1/fireplace/search/');
|
||||
|
||||
done();
|
||||
},
|
||||
fail
|
||||
);
|
||||
});
|
||||
|
||||
test('api url params', function(done, fail) {
|
||||
mock(
|
||||
'urls',
|
||||
{
|
||||
routes_api: {'homepage': '/foo/asdf'},
|
||||
settings: {api_url: 'api:'}
|
||||
settings: {
|
||||
api_url: 'api:',
|
||||
api_cdn_whitelist: {}
|
||||
}
|
||||
}, function(urls) {
|
||||
var homepage_url = urls.api.params('homepage', {q: 'poop'});
|
||||
eq_(homepage_url.substr(0, 13), 'api:/foo/asdf');
|
||||
|
|
|
@ -135,9 +135,6 @@ test('translate', function(done) {
|
|||
eq_(filters.translate({'blah': '3'}, dlobj, 'es-PD'), '3');
|
||||
eq_(filters.translate({'foo': 'bar', 'en-US': '3'}, null, 'es-PD'), '3');
|
||||
eq_(filters.translate({}, dlobj, 'es-PD'), '');
|
||||
eq_(filters.translate('', dlobj, 'es-PD'), '');
|
||||
eq_(filters.translate(null, dlobj, 'es-PD'), '');
|
||||
eq_(filters.translate(undefined, dlobj, 'es-PD'), '');
|
||||
done();
|
||||
});
|
||||
|
||||
|
|
Загрузка…
Ссылка в новой задаче