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

View file

@ -0,0 +1,455 @@
'use strict';
/**
* Abstraction around homescreen.
* @constructor
*/
function Homescreen(client) {
this.client = client;
this.system = client.loader.getAppClass('system');
}
Homescreen.URL = 'app://homescreen.gaiamobile.org';
Homescreen.Selectors = {
appsScrollable: '#apps-panel .scrollable',
apps: '#apps',
pages: '#pages',
icon: '#apps gaia-app-icon',
card: '#pages gaia-pin-card',
bottomBar: '#bottombar',
remove: '#remove',
rename: '#rename',
done: '#done',
cancelDownload: '#cancel-download',
resumeDownload: '#resume-download'
};
Homescreen.prototype = {
URL: Homescreen.URL,
Selectors: Homescreen.Selectors,
get appsScrollable() {
return this.client.findElement(Homescreen.Selectors.appsScrollable);
},
get container() {
return this.client.findElement(Homescreen.Selectors.apps);
},
get icons() {
return this.client.findElements(Homescreen.Selectors.icon);
},
get cards() {
return this.client.findElements(Homescreen.Selectors.card);
},
get visibleIcons() {
return this.icons.filter(function(el) {
return el.scriptWith(function(el) {
return el.parentNode.style.display !== 'none';
});
});
},
get visibleCards() {
return this.client.executeScript(function(selector) {
var cards = document.body.querySelectorAll(selector);
var visibles = [];
for (var i = cards.length - 1; i >= 0; i--) {
if (cards[i].parentNode.style.display !== 'none') {
visibles.push(cards[i]);
}
}
return visibles;
}, [Homescreen.Selectors.card]);
},
get removeButton() {
return this.client.helper.waitForElement(Homescreen.Selectors.remove);
},
get renameButton() {
return this.client.helper.waitForElement(Homescreen.Selectors.rename);
},
get doneButton() {
return this.client.helper.waitForElement(Homescreen.Selectors.done);
},
get cancelDownloadDialog() {
return this.client.findElement(Homescreen.Selectors.cancelDownload);
},
get resumeDownloadDialog() {
return this.client.findElement(Homescreen.Selectors.resumeDownload);
},
/**
* Waits for the homescreen to launch and switches to the frame.
*/
waitForLaunch: function() {
var client = this.client;
client.helper.waitForElement('body');
client.apps.switchToApp(Homescreen.URL);
// Wait for startup to complete
client.waitFor(function() {
return client.executeScript(function() {
return window.wrappedJSObject.appWindow.apps.startupMetadata === null;
});
});
},
/**
* Waits for the edit bar to fully appear.
*/
waitForEditBar: function() {
var body = this.client.findElement('body');
var bar = this.client.helper.waitForElement(Homescreen.Selectors.bottomBar);
this.client.waitFor(function() {
var barRect = bar.rect();
return barRect.y === (body.rect().height - barRect.height);
});
},
/**
* Find and return every id for all the items on the grid... Each element
* can be used with `.getIcon` to find the element for a given id.
*
* @return {Array[String]}
*/
getIconIdentifiers: function() {
return this.visibleIcons.map(function(el) {
return this.getIconId(el);
}, this);
},
/**
* Fetch an icon element on the homescreen by its identifier.
* For apps, the identifier is the manifestURL, or its manifestURL,
* followed by a '/' followed by its entry point. For bookmarks, the
* identifier is the bookmarked URL.
*
* @param {String} identifier The identifier of the icon.
* @return {Marionette.Element}
*/
getIcon: function(identifier) {
return this.client.findElement(
Homescreen.Selectors.apps + ' [data-identifier*="' + identifier + '"]');
},
/**
* Fetch a card element on the homescreen by its identifier.
* For apps, the identifier is the manifestURL, or its manifestURL,
* followed by a '/' followed by its entry point. For pin, the
* identifier is the pinned URL.
*/
getCard: function(identifier) {
return this.client.findElement(
Homescreen.Selectors.pages + ' [data-id="' + identifier + '"]');
},
/**
* Fetch an icon element on the homescreen by its name.
*
* @param {String} name The name of the icon.
* @return {Marionette.Element}
*/
getIconByName: function(name) {
function getName(icon) {
return icon.shadowRoot.querySelector('#subtitle').textContent;
}
var icons = this.icons;
for (var i = 0, iLen = icons.length; i < iLen; i++) {
if (icons[i].scriptWith(getName) === name) {
return icons[i];
}
}
return null;
},
/**
* Returns a homescreen icon element's identifier.
*
* @param {Marionette.Element} icon A homescreen icon element reference.
*/
getIconId: function(icon) {
return icon.scriptWith(function(el) {
return el.dataset.identifier;
});
},
/**
* Returns a homescreen icon element's text.
*
* @param {Marionette.Element} icon A homescreen icon element reference.
*/
getIconText: function(icon) {
return icon.scriptWith(function(el) {
return el.shadowRoot.querySelector('#subtitle').textContent;
});
},
/**
* Returns a data-url of the image element in a homescreen icon, if it exists.
*
* @param {Marionette.Element} icon A homescreen icon element reference.
*/
getIconImage: function(icon) {
return icon.scriptWith(function(el) {
var image = el.shadowRoot.querySelector('#image-container img');
if (!image) {
return null;
}
var canvas = document.createElement('canvas');
canvas.width = image.width;
canvas.height = image.height;
var ctx = canvas.getContext('2d', { willReadFrequently: true });
ctx.drawImage(image, 0, 0);
return canvas.toDataURL();
});
},
/**
* Returns a homescreen icon element's image URL.
*
* @param {Marionette.Element} icon A homescreen icon element reference.
*/
getIconImageUrl: function(icon) {
return icon.scriptWith(function(el) {
return el.dataset.testIconUrl;
});
},
/**
* Returns a homescreen card element's image URL.
*
* @param {Marionette.Element} card A homescreen card element reference.
*/
getCardImageUrl: function(card) {
return card.scriptWith(function(el) {
var icon = el.shadowRoot.querySelector('.icon-container i');
return icon.style.backgroundImage;
});
},
/**
* Waits for an icon's image URL to contain a certain value.
*
* @param {Marionette.Element} icon A homescreen icon element reference.
* @0aram {String} string A string to search for in the image URL.
*/
waitForIconImageUrl: function(icon, string) {
this.client.waitFor(function() {
var src = this.getIconImageUrl(icon);
return src && src.indexOf(string) !== -1;
}.bind(this));
},
/**
* Returns true if a homescreen icon is in a loading state, false otherwise.
*
* @param {Marionette.Element} icon A homescreen icon element reference.
*/
iconIsLoading: function(icon) {
return icon.scriptWith(function(el) {
return el.shadowRoot.querySelector('#image-container').
classList.contains('downloading');
});
},
/**
* Launches an icon and switches to the corresponding application.
*
* @param {Marionette.Element} icon A homescreen icon element reference.
*/
launchIcon: function(icon) {
var identifier =
icon.scriptWith(function(el) { return el.dataset.identifier; });
icon.tap();
var identifierWithoutEntryPoint = identifier.replace(/\/[^\/]*$/, '');
var client = this.client.scope({ searchTimeout: 100 });
var frame;
client.waitFor(function() {
// wait for the app to show up
client.switchToFrame();
try {
frame = client.findElement('iframe[mozapp="' + identifier + '"]');
} catch (e1) {
try {
frame = client.findElement(
'iframe[mozapp="' + identifierWithoutEntryPoint + '"]');
} catch(e2) {
try {
frame = client.findElement(
'iframe[data-frame-origin*="' + identifier + '"]');
} catch (e3) {
// try again...
client.switchToFrame(this.system.getHomescreenIframe());
icon.tap();
return false;
}
}
}
client.switchToFrame(frame);
return true;
}.bind(this));
},
/**
* Scrolls the homescreen so that the given icon is located roughly in
* the center of the screen, vertically.
*
* @param {Marionette.Element} icon A homescreen icon element reference.
*/
scrollIconToCenter: function(icon) {
this.client.executeScript(function(icon, scrollable, position) {
var midPoint = scrollable.clientHeight / 2;
scrollable.scrollTop = scrollable.scrollTop + (position - midPoint);
}, [icon, this.appsScrollable, icon.location().y]);
},
/**
* Restart the homescreen then refocus on it.
*/
restart: function() {
this.client.executeScript(function() {
window.close();
});
// initialize our frames again since we killed the iframe
this.client.switchToFrame();
this.client.switchToFrame(this.system.getHomescreenIframe());
},
/**
* Perform an action in an action dialog.
*
* @param {Marionette.Element} dialog An action dialog element reference.
* @param {String} action The data-l10n-id of the action to perform.
*/
actionDialog: function(dialog, action) {
var button = this.client.helper.waitForElement(
'button[data-l10n-id="' + action + '"]');
button.tap();
this.client.helper.waitForElementToDisappear(button);
},
/**
* Click confirm on a particular type of confirmation dialog.
*
* @param {String} type of dialog.
* @param {String} selector of the button. Defaults to .confirm.
*/
confirmDialog: function(type, button) {
var selector = 'gaia-confirm[data-type="' + type + '"]';
var dialog = this.client.helper.waitForElement(selector);
var confirm;
this.client.waitFor(function() {
confirm = dialog.findElement(button || '.confirm');
return confirm && confirm.displayed();
});
// XXX: Hack to use faster polling
var quickly = this.client.scope({ searchTimeout: 50 });
confirm.client = quickly;
// tricky logic to ensure the dialog has been removed and clicked
this.client.waitFor(function() {
try {
// click the dialog to dismiss it
confirm.click();
// ensure it is either hidden or hits the stale element ref
return !confirm.displayed();
} catch (e) {
if (e.type === 'StaleElementReference') {
// element was successfully removed
return true;
}
throw e;
}
});
},
/**
* Emulates pressing of the hardware home button.
*/
pressHomeButton: function() {
this.client.executeScript(function() {
var home = new CustomEvent('home');
window.dispatchEvent(home);
});
this.client.apps.switchToApp(Homescreen.URL);
},
/**
* Gets a localized application name from a manifest.
* @param {String} app to open
* @param {String} entryPoint to open
* @param {String} locale
*/
localizedAppName: function(app, entryPoint, locale) {
if (!locale) {
locale = entryPoint;
entryPoint = null;
}
var file = 'app://' + app + '.gaiamobile.org/manifest.webapp';
// use a chrome-scoped Marionette client for the cross-domain XHR
var chromeClient = this.client.scope({context: 'chrome'});
var manifest = chromeClient.executeAsyncScript(function(file) {
var xhr = new XMLHttpRequest();
xhr.open('GET', file, true);
xhr.onload = function(o) {
var data = JSON.parse(xhr.response);
marionetteScriptFinished(data);
};
xhr.send(null);
}, [file]);
var locales;
if (entryPoint) {
locales = manifest.entry_points[entryPoint].locales;
} else {
locales = manifest.locales;
}
if (!locales) {
return false;
}
if (locale.indexOf('-x-ps') > -1) {
return this.client.executeScript(function(locale, name) {
var mozL10n = window.wrappedJSObject.navigator.mozL10n;
return mozL10n.qps[locale].translate(name);
}, [locale, locales['en-US'].name]);
}
return locales[locale].name;
},
/**
* Returns a localized string from a properties file.
* @param {String} key of the string to lookup.
*/
l10n: function(key) {
var string = this.client.executeScript(function(key) {
return window.wrappedJSObject.navigator.mozL10n.get(key);
}, [key]);
return string;
}
};
module.exports = Homescreen;