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,730 @@
'use strict';
/* global eventSafety */
/* global Service, SearchWindow, places, Promise, UtilityTray */
(function(exports) {
/**
* The Rocketbar is a system-wide URL/search/title bar.
* @requires SearchWindow
* @requires SettingsListener
* @class Rocketbar
*/
function Rocketbar() {
// States
this.enabled = false;
this.focused = false;
this.active = false;
// Properties
this._port = null; // Inter-app communications port
this._wasClicked = false; // Remember when transition triggered by a click
this._pendingMessage = null;
// Get DOM elements
this.body = document.body;
this.screen = document.getElementById('screen');
this.rocketbar = document.getElementById('rocketbar');
this.form = document.getElementById('rocketbar-form');
this.input = document.getElementById('rocketbar-input');
this.cancel = document.getElementById('rocketbar-cancel');
this.clearBtn = document.getElementById('rocketbar-clear');
this.results = document.getElementById('rocketbar-results');
this.backdrop = document.getElementById('rocketbar-backdrop');
}
Rocketbar.prototype = {
EVENT_PREFIX: 'rocketbar',
name: 'Rocketbar',
/**
* True during the rocketbar closing animation.
*/
isClosing: false,
publish: function(name) {
window.dispatchEvent(new CustomEvent(this.EVENT_PREFIX + name, {
detail: this
}));
},
isActive: function() {
return this.active;
},
getActiveWindow: function() {
return this.isActive() ? this.searchWindow : null;
},
setHierarchy: function(active) {
this.searchWindow && this.searchWindow.setVisibleForScreenReader(active);
},
setFocus: function(active) {
if (active) {
this.focus();
}
return true;
},
/**
* Starts Rocketbar.
* @memberof Rocketbar.prototype
*/
start: function() {
this.addEventListeners();
this.enabled = true;
Service.request('registerHierarchy', this);
Service.registerState('enabled', this);
Service.register('handleInput', this);
},
/**
* Put Rocketbar in the active state.
*
* Input is displayed, title is hidden and search app is loaded, input
* not always focused.
* @return {Promise}
* @memberof Rocketbar.prototype
*/
activate: function() {
if (this.isClosing) {
return Promise.reject();
}
this._activateCall = new Promise(resolve => {
if (this.active) {
resolve();
return;
}
this.active = true;
this.rocketbar.classList.add('active');
this.form.classList.remove('hidden');
this.screen.classList.add('rocketbar-focused');
// We wait for the transition do be over and the search app to be loaded
// before moving on (and resolving the promise).
var searchLoaded = false;
var transitionEnded = false;
var waitOver = () => {
if (searchLoaded && transitionEnded) {
resolve();
this._activateCall = null;
this.publish('-activated');
}
};
var backdrop = this.backdrop;
var finishTransition = () => {
window.dispatchEvent(new CustomEvent('rocketbar-overlayopened'));
transitionEnded = true;
waitOver();
};
backdrop.classList.remove('hidden');
eventSafety(backdrop, 'transitionend', finishTransition, 200);
this.loadSearchApp().then(() => {
if (this.input.value.length) {
this.handleInput();
}
searchLoaded = true;
waitOver();
});
this.publish('-activating');
});
// Immediately hide if the utility tray is active.
// In the future we might be able to streamline this flow, but for now we
// need to ensure that all events are properly fired so that the chrome
// collapses. If for example we early exit, currently the chrome will
// not collapse.
if (UtilityTray.shown) {
this._activateCall
.then(this._closeSearch.bind(this));
}
return this._activateCall;
},
/**
* Take Rocketbar out of active state.
*
* Title is displayed, input is hidden, input is always blurred.
* @memberof Rocketbar.prototype
*/
deactivate: function() {
if (!this.active) {
return;
}
this.active = false;
this.isClosing = true;
var backdrop = this.backdrop;
var self = this;
var finish = () => {
this.form.classList.add('hidden');
this.rocketbar.classList.remove('active');
this.screen.classList.remove('rocketbar-focused');
backdrop.classList.add('hidden');
eventSafety(backdrop, 'transitionend', () => {
window.dispatchEvent(new CustomEvent('rocketbar-overlayclosed'));
self.publish('-deactivated');
self.isClosing = false;
}, 200);
};
if (this.focused) {
eventSafety(window, 'keyboardhidden', finish, 1000);
this.blur();
} else {
finish();
}
this.publish('-deactivating');
},
/**
* Add event listeners. Only called when Rocketbar is turned on.
* @memberof Rocketbar.prototype
*/
addEventListeners: function() {
// Listen for events from window manager
window.addEventListener('apploading', this);
window.addEventListener('appforeground', this);
window.addEventListener('apptitlechange', this);
window.addEventListener('lockscreen-appopened', this);
window.addEventListener('appopened', this);
window.addEventListener('launchapp', this);
window.addEventListener('searchterminated', this);
window.addEventListener('permissiondialoghide', this);
window.addEventListener('global-search-request', this);
window.addEventListener('attentionopening', this);
window.addEventListener('attentionopened', this);
window.addEventListener('activityrequesting', this);
window.addEventListener('searchopened', this);
window.addEventListener('searchclosed', this);
window.addEventListener('utilitytray-overlayopening', this);
window.addEventListener('utility-tray-overlayopened', this);
window.addEventListener('simlockrequestfocus', this);
window.addEventListener('cardviewbeforeshow', this);
// Listen for events from Rocketbar
this.input.addEventListener('focus', this);
this.input.addEventListener('blur', this);
this.input.addEventListener('input', this);
this.cancel.addEventListener('click', this);
this.clearBtn.addEventListener('click', this);
this.form.addEventListener('submit', this);
this.backdrop.addEventListener('click', this);
// Listen for messages from search app
window.addEventListener('iac-search-results', this);
},
'_handle_system-resize': function(evt) {
if (this.isActive()) {
var p = this.searchWindow &&
this.searchWindow.frontWindow &&
this.searchWindow.frontWindow.resize();
if (evt.detail && typeof evt.detail.waitUntil === 'function') {
evt.detail.waitUntil(p);
}
return false;
}
return true;
},
respondToHierarchyEvent: function(evt) {
if (this['_handle_' + evt.type]) {
return this['_handle_' + evt.type](evt);
}
return true;
},
/**
* Dispatch events to correct event handlers.
*
* @param {Event} e Event.
* @memberof Rocketbar.prototype
*/
handleEvent: function(e) {
switch(e.type) {
case 'searchopened':
window.addEventListener('open-app', this);
break;
case 'searchclosed':
window.removeEventListener('open-app', this);
break;
case 'apploading':
case 'launchapp':
// Do not close the search app if something opened in the background.
var detail = e.detail;
if (detail && detail.stayBackground) {
return;
}
this._closeSearch();
break;
case 'open-app':
// Do not hide the searchWindow if we have a frontWindow.
if (this.searchWindow && this.searchWindow.frontWindow) {
return;
}
if (e.detail && !e.detail.showApp) {
return;
}
/* falls through */
case 'attentionopening':
case 'attentionopened':
case 'appforeground':
case 'appopened':
case 'activityrequesting':
case 'simlockrequestfocus':
case 'cardviewbeforeshow':
// Hide rocketbar if the user opens the utility tray.
// The utility tray and rocketbar share the same space in the mental
// model - only one can be active at any given time. For consistency,
// any activities are also closed along with rocketbar when the
// utility tray is opened.
case 'utilitytray-overlayopening':
case 'utility-tray-overlayopened':
this._closeSearch();
break;
case 'lockscreen-appopened':
this.handleLock(e);
break;
case 'focus':
this.handleFocus(e);
break;
case 'blur':
this.handleBlur(e);
break;
case 'input':
this.handleInput(e);
break;
case 'click':
if (e.target == this.cancel) {
this.handleCancel(e);
} else if (e.target == this.clearBtn) {
this.clear();
this.focus();
} else if (e.target == this.backdrop) {
this._closeSearch();
}
break;
case 'searchterminated':
this.handleSearchTerminated(e);
break;
case 'submit':
this.handleSubmit(e);
break;
case 'iac-search-results':
this.handleSearchMessage(e);
break;
case 'permissiondialoghide':
if (this.active) {
this.focus();
}
break;
case 'global-search-request':
var app = Service.query('AppWindowManager.getActiveWindow');
var afterActivate;
if (app && !app.isActive()) {
return;
}
// If the app is not a browser, retain the search value and activate.
if (app && !app.isBrowser()) {
afterActivate = this.focus.bind(this);
} else {
this.setInput(app.config.url);
afterActivate = () => {
this.hideResults();
setTimeout(() => {
this.focus();
this.selectAll();
});
};
}
if (app && app.appChrome && !app.appChrome.isMaximized()) {
app.appChrome.maximize(() => {
this.activate().then(afterActivate);
});
} else {
this.activate().then(afterActivate);
}
break;
}
},
/**
* Set URL and generate manifest URL of search app.
* @param {String} url The search app URL.
* @memberof Rocketbar.prototype
*/
setSearchAppURL: function(url) {
this._searchAppURL = url;
this._searchManifestURL = url ? url.match(/(^.*?:\/\/.*?\/)/)[1] +
'manifest.webapp' : '';
},
setInput: function(input) {
this.input.value = input;
this.rocketbar.classList.toggle('has-text', input.length);
},
/**
* Show the Rocketbar results pane.
* @memberof Rocketbar.prototype
*/
showResults: function() {
if (this.searchWindow && !this.searchWindow.isDead()) {
this.searchWindow.open();
}
this.results.classList.remove('hidden');
this.backdrop.classList.add('results-shown');
},
/**
* Hide the Rocketbar results pane.
* @memberof Rocketbar.prototype
*/
hideResults: function() {
if (this.searchWindow) {
this.searchWindow.close();
this.searchWindow.hideContextMenu();
}
this.results.classList.add('hidden');
this.backdrop.classList.remove('results-shown');
// Send a message to the search app to clear results
if (this._port) {
this._port.postMessage({
action: 'clear'
});
}
},
/**
* Reset the Rocketbar to its initial empty state.
*/
clear: function() {
this.setInput('');
this.hideResults();
},
/**
* Enable back button.
*/
enableNavigation: function() {
this.rocketbar.classList.add('navigation');
},
/**
* Disable back button.
*/
disableNavigation: function() {
this.rocketbar.classList.remove('navigation');
},
/**
* Focus Rocketbar input.
* @memberof Rocketbar.prototype
*/
focus: function() {
if (this.active) {
this.input.focus();
}
},
/**
* SelectAll text content from Rocketbar input.
* @memberof Rocketbar.prototype
*/
selectAll: function() {
this.input.setSelectionRange(0, this.input.value.length, 'forward');
},
/**
* Handle a focus event.
* @memberof Rocketbar.prototype
*/
handleFocus: function() {
this.focused = true;
},
/**
* Handle press of hardware home button.
* @memberof Rocketbar.prototype
*/
_handle_home: function() {
this._closeSearch();
return true;
},
/**
* Blur Rocketbar input.
* @memberof Rocketbar.prototype
*/
blur: function() {
this.input.blur();
},
/**
* Handle a blur event.
* @memberof Rocketbar.prototype
*/
handleBlur: function() {
this.focused = false;
},
/**
* Handle a lock event.
* @memberof Rocketbar.prototype
*/
handleLock: function() {
this._closeSearch();
},
/**
* This function is called in respondToHierarchyEvent()
* when there is a value selector event and rocketbar
* is the current top most UI by HierarchyManager.
* @param {Object} evt Event object
*/
'_handle_inputfocus': function(evt) {
if (this.searchWindow) {
this.searchWindow.getTopMostWindow()
.broadcast('inputfocus', evt.detail);
return false;
}
return true;
},
'_handle_inputblur': function(evt) {
if (this.searchWindow) {
this.searchWindow.getTopMostWindow().broadcast('inputblur');
return false;
}
return true;
},
/**
* Handles activities for the search app.
* @memberof Rocketbar.prototype
*/
_handle_launchactivity: function(e) {
if (e.detail.isActivity && e.detail.inline && this.searchWindow &&
this.searchWindow.manifestURL === e.detail.parentApp) {
this.searchWindow.broadcast('launchactivity', e.detail);
return false;
}
return true;
},
_closeSearch: function() {
var hideAndDeactivate = () => {
this.hideResults();
this.deactivate();
};
if (this._activateCall) {
this._activateCall
.then(hideAndDeactivate);
} else {
hideAndDeactivate();
}
},
/**
* Handle text input in Rocketbar.
* @memberof Rocketbar.prototype
*/
handleInput: function() {
var input = this.input.value;
this.rocketbar.classList.toggle('has-text', input.length);
if (UtilityTray.shown) {
this._closeSearch();
return;
}
if (!input && !this.results.classList.contains('hidden')) {
this.hideResults();
return;
}
if (this.results.classList.contains('hidden')) {
this.showResults();
}
if (this._port) {
this._port.postMessage({
action: 'change',
input: input,
isPrivateBrowser:
Service.query('AppWindowManager.getActiveWindow').isPrivateBrowser()
});
}
},
/**
* Handle click of cancel button.
* @memberof Rocketbar.prototype
*/
handleCancel: function(e) {
this.setInput('');
this._closeSearch();
},
/**
* Handle submission of the Rocketbar.
*
* @param {Event} e Submit event.
* @memberof Rocketbar.prototype
*/
handleSubmit: function(e) {
e.preventDefault();
this.input.blur();
if (this.results.classList.contains('hidden')) {
this.showResults();
}
this._port.postMessage({
action: 'submit',
input: this.input.value
});
// Do not persist search submissions from private windows.
if (Service.query('AppWindowManager.getActiveWindow')
.isPrivateBrowser()) {
this.setInput('');
}
},
/**
* Instantiates a new SearchWindow.
* @return {Promise}
* @memberof Rocketbar.prototype
*/
loadSearchApp: function() {
if (!this.searchWindow) {
this.searchWindow = new SearchWindow();
}
return this.initSearchConnection();
},
/**
* Handles when the search app terminates.
* @memberof Rocketbar.prototype
*/
handleSearchTerminated: function(e) {
if (!this.searchWindow) {
return;
}
this._closeSearch();
this.searchWindow = null;
this._port = null;
},
/**
* Initialise inter-app connection with search app.
* @return {Promise}
* @memberof Rocketbar.prototype
*/
initSearchConnection: function() {
if (this.pendingInitConnection) {
return this.pendingInitConnection;
}
this.pendingInitConnection = new Promise((resolve, reject) => {
navigator.mozApps.getSelf().onsuccess = (event) => {
var app = event.target.result;
if (!app) {
reject();
return;
}
app.connect('search').then(ports => {
ports.forEach(port => {
this._port = port;
});
if (this._pendingMessage) {
this.handleSearchMessage(this._pendingMessage);
delete this._pendingMessage;
}
delete this.pendingInitConnection;
resolve();
}, reject);
};
});
return this.pendingInitConnection;
},
/**
* Handle messages from the search app.
*
* @param {Event} e Message event.
* @memberof Rocketbar.prototype
*/
handleSearchMessage: function(e) {
// Open the search connection if we receive a message before it's open
if (!this._port) {
this._pendingMessage = e;
this.initSearchConnection();
return;
}
switch (e.detail.action) {
case 'private-window':
window.dispatchEvent(new CustomEvent('new-private-window'));
break;
case 'render':
this.activate().then(this.focus.bind(this));
break;
case 'focus':
this.focus();
break;
case 'input':
this.setInput(e.detail.input);
this.focus();
this.handleInput();
break;
case 'request-screenshot':
places.screenshotRequested(e.detail.url);
break;
case 'hide':
this._closeSearch();
break;
}
},
/**
* Tell the search app to update its index.
* @memberof Rocketbar.prototype
*/
updateSearchIndex: function() {
if (this._port) {
this._port.postMessage({
action: 'syncPlaces'
});
}
}
};
exports.Rocketbar = Rocketbar;
}(window));