Publish Vulpes Preview 15 sources for desktop, Android and Tundra

Share Gaia assets and adapters, keep private profiles and development documents local, and update both public READMEs.
This commit is contained in:
OverSu 2026-09-30 23:13:03 +02:00
commit b38dea0597
25901 changed files with 9982925 additions and 369 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7 KiB

View file

@ -0,0 +1,82 @@
(function(exports) {
'use strict';
// Fake drafts stored in local store
const timestamp = Date.now(),
store = new Map();
const draftIndex = [
[11, [{
recipients: ['555', '666'],
subject: '',
content: ['This is a draft message'],
timestamp: timestamp - (3600000 * 24),
threadId: 11,
type: 'sms'
}]],
[null, [{
recipients: [],
subject: '',
content: ['This is a draft SMS, with no recipient'],
timestamp: timestamp,
threadId: null,
type: 'sms'
}, {
recipients: ['555-666-1234'],
subject: '',
content: ['This is a draft SMS, with a recipient, but no thread'],
timestamp: timestamp - 3600000,
threadId: null,
type: 'sms'
}]],
[8, [{
recipients: ['123456'],
subject: '',
content: [
'This is a draft MMS...',
{
blob: {
type: 'audio/ogg',
size: 12345
},
name: 'audio.oga'
},
'...with a recipient and a thread'
],
timestamp: timestamp - (3600000 * 2),
threadId: 8,
type: 'mms'
}]]
];
store.set('draft index', draftIndex);
function callCallback(value, callback) {
if (typeof callback === 'function') {
setTimeout(callback.bind(null, value), 0);
}
}
Object.defineProperty(exports, 'asyncStorage', {
value: {
getItem: function asm_getItem(key, callback) {
callCallback(store.get(key) || null, callback);
},
setItem: function asm_setItem(key, value, callback) {
callCallback(store.set(key, value), callback);
},
removeItem: function asm_removeItem(key, callback) {
callCallback(store.delete(key), callback);
},
clear: function asm_clear(callback) {
callCallback(store.clear(), callback);
},
length: function(callback) {
callCallback(store.size, callback);
},
key: function() {
throw new Error('Not Implemented');
}
}
});
})(window);

View file

@ -0,0 +1,464 @@
/*global Utils, Promise */
'use strict';
/* ***********************************************************
Code below is for desktop testing!
Adapted from:
https://mxr.mozilla.org/mozilla-b2g18/source/dom/contacts/fallback/ContactDB.jsm
https://mxr.mozilla.org/mozilla-b2g18/source/dom/contacts/ContactManager.js
apps/sms/js/contacts.js
*********************************************************** */
(function(window) {
function getAsset(filename) {
return new Promise(function(resolve, reject) {
var req = new XMLHttpRequest();
req.open('GET', filename, true);
req.responseType = 'blob';
req.onload = function() {
resolve(req.response);
};
req.send();
});
}
function stringOrBust(aObj) {
if (typeof aObj !== 'string') {
return undefined;
} else {
return aObj;
}
}
function sanitizeStringArray(aArray) {
if (!Array.isArray(aArray)) {
aArray = [aArray];
}
return aArray.map(stringOrBust).filter(function(el) {
return el !== undefined;
});
}
function _isVanillaObj(aObj) {
return Object.prototype.toString.call(aObj) == '[object Object]';
}
function Normalize(opts) {
opts.fields.forEach(function(field) {
var value = opts.record[field.name];
this[field.name] = typeof value !== 'undefined' ?
field.filter(value) : null;
}, this);
}
function ContactField(field) {
Normalize.call(this, {
fields: [
{ name: 'type', filter: sanitize },
{ name: 'value', filter: stringOrBust }
],
record: field
});
}
function ContactTelField(tel) {
Normalize.call(this, {
fields: [
{ name: 'type', filter: sanitize },
{ name: 'value', filter: stringOrBust },
{ name: 'carrier', filter: stringOrBust }
],
record: tel
});
}
function ContactAddress(address) {
Normalize.call(this, {
fields: [
{ name: 'type', filter: sanitize },
{ name: 'streetAddress', filter: stringOrBust },
{ name: 'locality', filter: stringOrBust },
{ name: 'region', filter: stringOrBust },
{ name: 'postalCode', filter: stringOrBust },
{ name: 'countryName', filter: stringOrBust }
],
record: address
});
}
function multiValue(source, ctor) {
var result = [];
source = Array.isArray(source) ? source : [source];
for (var entry of source) {
if (_isVanillaObj(entry)) {
result.push(new ctor(entry));
}
}
return result;
}
function Contact(contact) {
Normalize.call(this, {
fields: [
{ name: 'name', filter: sanitize },
{ name: 'honorificPrefix', filter: sanitize },
{ name: 'honorificSuffix', filter: sanitize },
{ name: 'givenName', filter: sanitize },
{ name: 'additionalName', filter: sanitize },
{ name: 'familyName', filter: sanitize },
{ name: 'nickname', filter: sanitize },
{ name: 'email', filter: sanitize },
{ name: 'photo', filter: sanitize },
{ name: 'url', filter: sanitize },
{ name: 'category', filter: sanitize },
{ name: 'org', filter: sanitize },
{ name: 'jobTitle', filter: sanitize },
{ name: 'note', filter: sanitize },
{ name: 'impp', filter: sanitize }
],
record: contact
});
this.id = ++cid;
this.name = [
this.givenName[0] + ' ' + this.familyName[0]
];
this.email = typeof contact.email !== 'undefined' ?
multiValue(contact.email, ContactField) : null;
this.adr = typeof contact.adr !== 'undefined' ?
multiValue(contact.adr, ContactAddress) : null;
this.tel = typeof contact.tel !== 'undefined' ?
multiValue(contact.tel, ContactTelField) : null;
this.url = typeof contact.url !== 'undefined' ?
multiValue(contact.url, ContactField) : null;
this.bday = typeof contact.bday !== 'undefined' ?
new Date(contact.bday) : null;
this.anniversary = contact.anniversary ?
new Date(contact.anniversary) : null;
}
var ContactsDB = [];
var sanitize = sanitizeStringArray;
var cid = 0;
var methods = {
contains: 'startsWith',
match: 'includes',
equals: 'includes'
};
function isMatch(contact, filter) {
/**
* Validation Strategy
*
* 1. Let _found_ be a register of confirmed terms
* 2. Let _contact_ be a contact record.
* 3. For each _term_ [...input list], with the label _outer_
* - For each _field_ of [givenName, familyName]
* - For each _value_ in _contact_[ _field_ ]
* - Let _found_[ _term_ ] be the result of calling
* _value_.startsWith( _term_ )
* - If _found_[ _term_ ] is **true**, continue to
* loop labelled _outer_
* 4. If every value of _key_ in _found_ is **true** return **true**,
* else return **false**
*/
var found = {};
var method = methods[filter.filterOp];
var term = filter.filterValue.toLowerCase();
// The outer loop is specifically labelled to allow the
// nested condition a way out of the second and third loop.
outer:
for (var field of filter.filterBy) {
if (!contact[field]) {
continue;
}
for (var value of contact[field]) {
if (typeof value !== 'string') {
value = value.value;
}
if ((found[term] = value.toLowerCase()[method](term))) {
return true;
}
if (field === 'tel') {
if (Utils.probablyMatches(value, term)) {
return true;
}
}
}
}
return false;
}
// // Do not use the native API.
navigator.mozContacts = {
addEventListener: function(type, handler, capture) {
return this;
},
find: function(filter) {
// attribute DOMString filterValue; // e.g. 'Tom'
// attribute DOMString filterOp; // e.g. 'contains'
// attribute DOMString[] filterBy; // e.g. 'givenName'
// attribute DOMString sortBy; // e.g. 'givenName'
// attribute DOMString sortOrder; // e.g. 'descending'
// attribute unsigned long filterLimit;
// Implement a mock that gives results that appear to
// match the real behaviour of filtered contacts results
var result = ContactsDB.filter(function(contact) {
return isMatch(contact, filter);
});
return readyPromise.then(() => result);
}
};
ContactsDB.push(
new Contact({
familyName: 'Turing',
givenName: 'Alan',
tel: {
value: '101',
type: 'Mobile',
carrier: 'Telco'
}
})
);
/**
// 102 is omitted intentionally.
ContactsDB.push(
new Contact({
familyName: '',
givenName: '',
tel: {
value: '102'
}
})
);
*/
ContactsDB.push(
new Contact({
familyName: 'Shannon',
givenName: 'Claude',
tel: {
value: '103',
type: 'Mobile'
}
})
);
ContactsDB.push(
new Contact({
familyName: 'Lovelace',
givenName: 'Ada',
tel: {
value: '104'
},
email: {
value: 'ada@lovelace.com'
}
})
);
ContactsDB.push(
new Contact({
familyName: 'Tesla',
givenName: 'Nikola',
email: {
value: 'nikola@tesla.com'
}
})
);
ContactsDB.push(
new Contact({
familyName: 'Babbage',
givenName: 'Charles',
tel: {
value: '105'
}
})
);
ContactsDB.push(
new Contact({
familyName: 'Backus',
givenName: 'John',
tel: {
value: '106'
}
})
);
ContactsDB.push(
new Contact({
familyName: 'McCarthy',
givenName: 'John',
tel: {
value: '107'
}
})
);
ContactsDB.push(
new Contact({
familyName: 'Steele',
givenName: 'Guy',
tel: {
value: '108'
}
})
);
ContactsDB.push(
new Contact({
familyName: 'Kay',
givenName: 'Alan',
tel: {
value: '109',
type: 'Mobile',
carrier: 'Nynex'
}
})
);
var grace = new Contact({
familyName: 'Hopper',
givenName: 'Grace',
tel: {
value: '999'
}
});
var tom = new Contact({
familyName: 'O\'Hare',
givenName: 'Tom',
tel: {
value: '123456',
type: 'Mobile',
carrier: 'Nynex'
}
});
var longname = new Contact({
familyName: 'Taumatawhakatangihangakoauauota',
givenName: 'Mateapokaiwhenuakitanatahu',
tel: [{
value: '+18001114321',
type: ['Mobile']
}]
});
var rtlName = new Contact({
familyName: 'الخيام',
givenName: 'عمر',
tel: [{
value: '+97121111111',
type: ['Mobile'],
carrier: 'اتصالات'
}]
});
var readyPromise = Promise.all([
getAsset('/desktop-mock/assets/photo-man.jpg'),
getAsset('/desktop-mock/assets/photo-woman.jpg'),
getAsset('/desktop-mock/assets/photo-man-bowtie.jpg')
]).then(function gotall(blobs) {
tom.photo = [blobs[0]];
grace.photo = [blobs[1]];
longname.photo = [blobs[2]];
});
ContactsDB.push(grace);
ContactsDB.push(tom);
ContactsDB.push(longname);
ContactsDB.push(rtlName);
ContactsDB.push(
new Contact({
familyName: 'Carrier',
givenName: 'Igotno',
tel: {
value: '436797',
type: 'Mobile',
carrier: null
}
})
);
ContactsDB.push(
new Contact({
familyName: 'Jekyll',
givenName: 'Doctor',
tel: [
{
value: '+12125551234',
type: ['Mobile']
},
{
value: '+15551237890',
type: ['Home']
}
]
})
);
ContactsDB.push(
new Contact({
familyName: 'Non-digit',
givenName: 'Multiple',
tel: [
{
value: '900-BUY-A-CAR',
type: ['Mobile'],
carrier: 'Megaphones'
},
{
value: '800-BUY-A-CAR',
type: ['Home'],
carrier: 'Nynex'
}
]
})
);
ContactsDB.push(
new Contact({
familyName: '',
givenName: 'Bob',
tel: [
{
value: '555-666-1234',
type: ['Mobile'],
carrier: 'ScamCo'
}
]
})
);
// console.log( ContactsDB );
}(window));

View file

@ -0,0 +1,30 @@
'use strict';
(function(window) {
if (navigator.mozMobileConnections) {
console.error('navigator.mozMobileConnections exists!');
return;
}
navigator.mozMobileConnections = {
0: {
iccId: 'SIM 1',
addEventListener: () => {},
removeEventListener: () => {},
data: {
state: 'searching'
}
},
1: {
iccId: 'SIM 2',
addEventListener: () => {},
removeEventListener: () => {},
data: {
state: 'searching'
}
},
length: 2
};
}(window));

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,18 @@
'use strict';
/* ***********************************************************
Workaround to force the mozMobileMessage in shim context use window one.
Need to be removed once all the event listeners are ready for shim.
*********************************************************** */
(function(window) {
if (!parent.navigator.mozMobileMessage) {
console.error('mozMobileMessage for main window context is not ready!');
return;
}
navigator.mozMobileMessage = parent.navigator.mozMobileMessage;
}(window));

View file

@ -0,0 +1,39 @@
(function() {
'use strict';
if (navigator.mozSettings) {
return;
}
const KNOWN_SETTINGS = new Map();
KNOWN_SETTINGS.set('locale.hour12', true);
navigator.mozSettings = {
createLock: function() {
return {
get: function(key) {
var request = {
addEventListener: function(eventName, callback) {
request[eventName] = callback;
}
};
window.setTimeout(function() {
if (typeof request.onsuccess === 'function') {
request.result = {};
request.result[key] = KNOWN_SETTINGS.get(key);
request.onsuccess.call(request);
}
}, 0);
return request;
},
set() {}
};
},
addObserver: function() {}
};
})();

View file

@ -0,0 +1,18 @@
'use strict';
/* ***********************************************************
Workaround to force the mozSettings in shim context use window one.
Need to be removed once all the event listeners are ready for shim.
*********************************************************** */
(function(window) {
if (!parent.navigator.mozSettings) {
console.error('mozSettings for main window context is not ready!');
return;
}
navigator.mozSettings = parent.navigator.mozSettings;
}(window));

View file

@ -0,0 +1,11 @@
(function() {
'use strict';
if (!navigator.mozHasPendingMessage) {
navigator.mozHasPendingMessage = () => false;
}
if (!navigator.mozSetMessageHandler) {
navigator.mozSetMessageHandler = () => {};
}
})();