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:
parent
93e3a48004
commit
b38dea0597
25901 changed files with 9982925 additions and 369 deletions
5
gaia/tools/extensions/activities/chrome.manifest
Executable file
5
gaia/tools/extensions/activities/chrome.manifest
Executable file
|
|
@ -0,0 +1,5 @@
|
|||
# Activities
|
||||
component {70a83123-7467-4389-a309-3e81c74ad002} components/ActivitiesGlue.js
|
||||
contract @mozilla.org/dom/activities/ui-glue;1 {70a83123-7467-4389-a309-3e81c74ad002}
|
||||
|
||||
interfaces components/activity_glue.xpt
|
||||
76
gaia/tools/extensions/activities/components/ActivitiesGlue.js
Executable file
76
gaia/tools/extensions/activities/components/ActivitiesGlue.js
Executable file
|
|
@ -0,0 +1,76 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
"use strict"
|
||||
|
||||
const Cc = Components.classes;
|
||||
const Ci = Components.interfaces;
|
||||
const Cu = Components.utils;
|
||||
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
|
||||
function ActivitiesDialog() {
|
||||
this._id = 0;
|
||||
|
||||
this.activities = [];
|
||||
}
|
||||
|
||||
ActivitiesDialog.prototype = {
|
||||
run: function ap_run() {
|
||||
let id = "activity-choice" + this._id++;
|
||||
let activity = this.activities.shift();
|
||||
|
||||
let choices = [];
|
||||
activity.list.forEach(function(item) {
|
||||
choices.push({ manifest: item.manifest, icon: item.icon });
|
||||
});
|
||||
|
||||
|
||||
// Keep up the frond-end of an activity choice. The messages contains
|
||||
// a list of {names, icons} for applications able to handle this particular
|
||||
// activity. The front-end should display a UI to pick one.
|
||||
let browser = Services.wm.getMostRecentWindow("navigator:browser");
|
||||
let content = browser.gBrowser.selectedBrowser.contentWindow;
|
||||
let detail = {
|
||||
type: "activity-choice",
|
||||
id: id,
|
||||
name: activity.name,
|
||||
choices: choices
|
||||
};
|
||||
|
||||
// Listen the resulting choice from the front-end. If there is no choice,
|
||||
// let's return -1, which means the user has cancelled the dialog.
|
||||
content.addEventListener("mozContentEvent", function act_getChoice(evt) {
|
||||
if (evt.detail.id != id)
|
||||
return;
|
||||
|
||||
content.removeEventListener("mozContentEvent", act_getChoice);
|
||||
activity.callback.handleEvent(evt.detail.value !== undefined
|
||||
? evt.detail.value
|
||||
: -1);
|
||||
});
|
||||
|
||||
let event = content.document.createEvent('CustomEvent');
|
||||
event.initCustomEvent('mozChromeEvent', true, true,
|
||||
Cu.cloneInto(detail, content));
|
||||
content.dispatchEvent(event);
|
||||
},
|
||||
|
||||
chooseActivity: function ap_chooseActivity(aName, aActivities, aCallback) {
|
||||
this.activities.push({
|
||||
name: aName,
|
||||
list: aActivities,
|
||||
callback: aCallback
|
||||
});
|
||||
Services.tm.currentThread.dispatch(this, Ci.nsIEventTarget.DISPATCH_NORMAL);
|
||||
},
|
||||
|
||||
classID: Components.ID("{70a83123-7467-4389-a309-3e81c74ad002}"),
|
||||
|
||||
QueryInterface: XPCOMUtils.generateQI([Ci.nsIActivityUIGlue, Ci.nsIRunnable])
|
||||
}
|
||||
|
||||
this.NSGetFactory = XPCOMUtils.generateNSGetFactory([ActivitiesDialog]);
|
||||
|
||||
BIN
gaia/tools/extensions/activities/components/activity_glue.xpt
Executable file
BIN
gaia/tools/extensions/activities/components/activity_glue.xpt
Executable file
Binary file not shown.
25
gaia/tools/extensions/activities/components/nsIActivityUIGlue.idl
Executable file
25
gaia/tools/extensions/activities/components/nsIActivityUIGlue.idl
Executable file
|
|
@ -0,0 +1,25 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "nsISupports.idl"
|
||||
|
||||
[scriptable, function, uuid(7a16feb4-5a78-4589-9174-b728f26942e2)]
|
||||
interface nsIActivityUIGlueCallback : nsISupports
|
||||
{
|
||||
void handleEvent(in long choice);
|
||||
};
|
||||
|
||||
/**
|
||||
* To be implemented by @mozilla.org/dom/activities/ui-glue;1
|
||||
*/
|
||||
[scriptable, uuid(8624ad73-937a-400f-9d93-39ab5449b867)]
|
||||
interface nsIActivityUIGlue : nsISupports
|
||||
{
|
||||
/**
|
||||
* @param name The name of the activity to handle (eg. "share", "pick").
|
||||
* @param activities A json blob which is an array of { "title":"...", "icon":"..." }.
|
||||
* @param onresult The callback to send the index of the choosen activity. Send -1 if no choice is made.
|
||||
*/
|
||||
void chooseActivity(in DOMString title, in jsval activities, in nsIActivityUIGlueCallback onresult);
|
||||
};
|
||||
24
gaia/tools/extensions/activities/install.rdf
Executable file
24
gaia/tools/extensions/activities/install.rdf
Executable file
|
|
@ -0,0 +1,24 @@
|
|||
<?xml version="1.0"?>
|
||||
|
||||
<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:em="http://www.mozilla.org/2004/em-rdf#">
|
||||
|
||||
<Description about="urn:mozilla:install-manifest">
|
||||
<em:id>activities@gaiamobile.org</em:id>
|
||||
<em:name>activities.js</em:name>
|
||||
<em:version>0.2</em:version>
|
||||
<em:iconURL>chrome://activities.js/skin/logo.png</em:iconURL>
|
||||
<em:targetApplication>
|
||||
<Description>
|
||||
<em:id>{ec8030f7-c20a-464f-9b0e-13a3a9e97384}</em:id> <!-- Firefox -->
|
||||
<em:minVersion>6.0</em:minVersion>
|
||||
<em:maxVersion>25.*</em:maxVersion>
|
||||
</Description>
|
||||
</em:targetApplication>
|
||||
<em:unpack>true</em:unpack>
|
||||
<em:bootstrap>false</em:bootstrap>
|
||||
<em:creator>Vivien Nicolas</em:creator>
|
||||
<em:description>activities.js</em:description>
|
||||
<em:homepageURL>https://github.com/mozilla-b2g/gaia/</em:homepageURL>
|
||||
</Description>
|
||||
</RDF>
|
||||
1
gaia/tools/extensions/activities@gaiamobile.org
Executable file
1
gaia/tools/extensions/activities@gaiamobile.org
Executable file
|
|
@ -0,0 +1 @@
|
|||
activities
|
||||
1
gaia/tools/extensions/alarms/chrome.manifest
Executable file
1
gaia/tools/extensions/alarms/chrome.manifest
Executable file
|
|
@ -0,0 +1 @@
|
|||
interfaces components/alarms.xpt
|
||||
BIN
gaia/tools/extensions/alarms/components/alarms.xpt
Executable file
BIN
gaia/tools/extensions/alarms/components/alarms.xpt
Executable file
Binary file not shown.
14
gaia/tools/extensions/alarms/components/nsIAlarmHalService.idl
Executable file
14
gaia/tools/extensions/alarms/components/nsIAlarmHalService.idl
Executable file
|
|
@ -0,0 +1,14 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "domstubs.idl"
|
||||
|
||||
[scriptable, uuid(157b1ee4-f696-486d-bd55-205e21e88fab)]
|
||||
interface nsIAlarmHalService : nsISupports
|
||||
{
|
||||
bool setAlarm(in long aSeconds, in long aNanoseconds);
|
||||
void setAlarmFiredCb(in jsval aAlarmFiredCb);
|
||||
void setTimezoneChangedCb(in jsval aTimezoneChangedCb);
|
||||
};
|
||||
|
||||
24
gaia/tools/extensions/alarms/install.rdf
Executable file
24
gaia/tools/extensions/alarms/install.rdf
Executable file
|
|
@ -0,0 +1,24 @@
|
|||
<?xml version="1.0"?>
|
||||
|
||||
<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:em="http://www.mozilla.org/2004/em-rdf#">
|
||||
|
||||
<Description about="urn:mozilla:install-manifest">
|
||||
<em:id>alarms@gaiamobile.org</em:id>
|
||||
<em:name>alarms.js</em:name>
|
||||
<em:version>0.1</em:version>
|
||||
<em:iconURL>chrome://alarms.js/skin/logo.png</em:iconURL>
|
||||
<em:targetApplication>
|
||||
<Description>
|
||||
<em:id>toolkit@mozilla.org</em:id>
|
||||
<em:minVersion>6.0</em:minVersion>
|
||||
<em:maxVersion>22.0.*</em:maxVersion>
|
||||
</Description>
|
||||
</em:targetApplication>
|
||||
<em:unpack>true</em:unpack>
|
||||
<em:bootstrap>false</em:bootstrap>
|
||||
<em:creator>Vivien Nicolas</em:creator>
|
||||
<em:description>alarms.js</em:description>
|
||||
<em:homepageURL>https://github.com/mozilla-b2g/gaia/</em:homepageURL>
|
||||
</Description>
|
||||
</RDF>
|
||||
1
gaia/tools/extensions/alarms@gaiamobile.org
Executable file
1
gaia/tools/extensions/alarms@gaiamobile.org
Executable file
|
|
@ -0,0 +1 @@
|
|||
alarms
|
||||
292
gaia/tools/extensions/browser-helper@gaiamobile.org/bootstrap.js
vendored
Executable file
292
gaia/tools/extensions/browser-helper@gaiamobile.org/bootstrap.js
vendored
Executable file
|
|
@ -0,0 +1,292 @@
|
|||
/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- /
|
||||
/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
|
||||
|
||||
'use strict';
|
||||
|
||||
const Cc = Components.classes;
|
||||
const Ci = Components.interfaces;
|
||||
const Cu = Components.utils;
|
||||
|
||||
Cu.import('resource://gre/modules/Services.jsm');
|
||||
|
||||
function debug(data) {
|
||||
dump('browser-helper: ' + data + '\n');
|
||||
}
|
||||
|
||||
function initResponsiveDesign(browserWindow) {
|
||||
// Inject custom controls in responsive view
|
||||
Cu.import('resource:///modules/devtools/responsivedesign.jsm');
|
||||
ResponsiveUIManager.once('on', function(event, {tab:tab}) {
|
||||
let responsive = tab.__responsiveUI;
|
||||
let document = tab.ownerDocument;
|
||||
|
||||
browserWindow.shell = {
|
||||
sendChromeEvent: sendChromeEvent
|
||||
};
|
||||
|
||||
// Ensure tweaking only the first responsive mode opened
|
||||
responsive.stack.classList.add('os-mode');
|
||||
|
||||
let sleepButton = document.createElement('button');
|
||||
sleepButton.id = 'os-sleep-button';
|
||||
sleepButton.setAttribute('top', 0);
|
||||
sleepButton.setAttribute('right', 0);
|
||||
sleepButton.addEventListener('mousedown', function() {
|
||||
sendChromeEvent({type: 'sleep-button-press'});
|
||||
});
|
||||
sleepButton.addEventListener('mouseup', function() {
|
||||
sendChromeEvent({type: 'sleep-button-release'});
|
||||
});
|
||||
responsive.stack.appendChild(sleepButton);
|
||||
|
||||
let volumeButtons = document.createElement('vbox');
|
||||
volumeButtons.id = 'os-volume-buttons';
|
||||
volumeButtons.setAttribute('top', 0);
|
||||
volumeButtons.setAttribute('left', 0);
|
||||
|
||||
let volumeUp = document.createElement('button');
|
||||
volumeUp.id = 'os-volume-up-button';
|
||||
volumeUp.addEventListener('mousedown', function() {
|
||||
sendChromeEvent({type: 'volume-up-button-press'});
|
||||
});
|
||||
volumeUp.addEventListener('mouseup', function() {
|
||||
sendChromeEvent({type: 'volume-up-button-release'});
|
||||
});
|
||||
|
||||
let volumeDown = document.createElement('button');
|
||||
volumeDown.id = 'os-volume-down-button';
|
||||
volumeDown.addEventListener('mousedown', function() {
|
||||
sendChromeEvent({type: 'volume-down-button-press'});
|
||||
});
|
||||
volumeDown.addEventListener('mouseup', function() {
|
||||
sendChromeEvent({type: 'volume-down-button-release'});
|
||||
});
|
||||
|
||||
volumeButtons.appendChild(volumeUp);
|
||||
volumeButtons.appendChild(volumeDown);
|
||||
responsive.stack.appendChild(volumeButtons);
|
||||
|
||||
// <toolbar id="os-hardware-button">
|
||||
// <toolbarbutton id="os-home-button" />
|
||||
// </toolbar>
|
||||
let bottomToolbar = document.createElement('toolbar');
|
||||
bottomToolbar.id = 'os-hardware-buttons';
|
||||
bottomToolbar.setAttribute('align', 'center');
|
||||
bottomToolbar.setAttribute('pack', 'center');
|
||||
|
||||
let homeButton = document.createElement('toolbarbutton');
|
||||
homeButton.id = 'os-home-button';
|
||||
homeButton.setAttribute('class', 'devtools-toolbarbutton');
|
||||
|
||||
homeButton.addEventListener('mousedown', function() {
|
||||
sendChromeEvent({type: 'home-button-press'});
|
||||
});
|
||||
homeButton.addEventListener('mouseup', function() {
|
||||
sendChromeEvent({type: 'home-button-release'});
|
||||
});
|
||||
bottomToolbar.appendChild(homeButton);
|
||||
responsive.container.appendChild(bottomToolbar);
|
||||
|
||||
// Simulate chrome event.
|
||||
function sendChromeEvent(detail) {
|
||||
let contentWindow = responsive.browser.contentWindow;
|
||||
var contentDetail = Components.utils.createObjectIn(contentWindow);
|
||||
for (var i in detail) {
|
||||
contentDetail[i] = detail[i];
|
||||
}
|
||||
Components.utils.makeObjectPropsNormal(contentDetail);
|
||||
|
||||
var customEvt = contentWindow.document.createEvent('CustomEvent');
|
||||
customEvt.initCustomEvent('mozChromeEvent', true, true, contentDetail);
|
||||
contentWindow.dispatchEvent(customEvt);
|
||||
}
|
||||
});
|
||||
|
||||
// Cleanup responsive mode if it's disabled
|
||||
ResponsiveUIManager.on('off', function(event, tab, responsive) {
|
||||
if (responsive.stack.classList.contains('os-mode')) {
|
||||
responsive.stack.classList.remove('os-mode');
|
||||
let document = tab.ownerDocument;
|
||||
let sleepButton = document.getElementById('os-sleep-button');
|
||||
responsive.stack.removeChild(sleepButton);
|
||||
let volumeButtons = document.getElementById('os-volume-buttons');
|
||||
responsive.stack.removeChild(volumeButtons);
|
||||
let bottomToolbar = document.getElementById('os-hardware-buttons');
|
||||
responsive.container.removeChild(bottomToolbar);
|
||||
}
|
||||
});
|
||||
|
||||
// Automatically toggle responsive design mode
|
||||
let mgr = browserWindow.ResponsiveUI.ResponsiveUIManager;
|
||||
mgr.handleGcliCommand(browserWindow,
|
||||
browserWindow.gBrowser.selectedTab,
|
||||
'resize on');
|
||||
|
||||
// Enable touch events
|
||||
browserWindow.gBrowser.selectedTab.__responsiveUI.enableTouch();
|
||||
}
|
||||
|
||||
function startup(data, reason) {
|
||||
try {
|
||||
// Initialize various JSM instanciated by shell.js
|
||||
// All of them are usefull even if we don't use them in this file.
|
||||
Cu.import('resource://gre/modules/ContactService.jsm');
|
||||
Cu.import('resource://gre/modules/SettingsRequestManager.jsm');
|
||||
Cu.import('resource://gre/modules/ActivitiesService.jsm');
|
||||
|
||||
var mm = Cc['@mozilla.org/globalmessagemanager;1']
|
||||
.getService(Ci.nsIMessageBroadcaster);
|
||||
// Allow panning to work in the system app.
|
||||
mm.loadFrameScript('chrome://global/content/BrowserElementPanning.js', true);
|
||||
|
||||
// Enable mozApps.getAll and app.launch to work.
|
||||
// By default on firefox, apps are consider as not launchable
|
||||
// and only launchable apps appear in getAll().
|
||||
Cu.import('resource://gre/modules/Webapps.jsm');
|
||||
DOMApplicationRegistry.allAppsLaunchable = true;
|
||||
|
||||
// Track loading of the system app in order to load our striped down copy
|
||||
// of shell.xul
|
||||
Services.obs.addObserver(function(document) {
|
||||
// Some documents like XBL don't have location and should be ignored
|
||||
if (!document.location)
|
||||
return;
|
||||
let domain = document.location.toString();
|
||||
if (domain.indexOf('system.gaiamobile.org') != -1) {
|
||||
let scope = {
|
||||
content: document.defaultView
|
||||
};
|
||||
Services.scriptloader.loadSubScript(
|
||||
'chrome://browser-helper.js/content/shell.js', scope);
|
||||
}
|
||||
}, 'document-element-inserted', false);
|
||||
|
||||
// Watch for app load start in firefox tabs.
|
||||
// We have to set the app id on each docshell where we are trying to load
|
||||
// an app. That's because we can't set mozapp attribute on firefox iframes
|
||||
// as Firefox is still using xul:browser.
|
||||
let nsIWebProgressListener = Ci.nsIWebProgressListener;
|
||||
let appsService = Cc['@mozilla.org/AppsService;1'].getService(Ci.nsIAppsService);
|
||||
let docShellListener = {
|
||||
onStateChange: function onStateChange(webProgress, request, flags, status) {
|
||||
if (!webProgress.chromeEventHandler ||
|
||||
webProgress.chromeEventHandler.tagName != "xul:browser") {
|
||||
// Only set app id for firefox tab <xul:browser> frames
|
||||
return;
|
||||
}
|
||||
|
||||
// Catch any load start request
|
||||
if (flags & nsIWebProgressListener.STATE_START &&
|
||||
flags & nsIWebProgressListener.STATE_IS_DOCUMENT &&
|
||||
(request instanceof Ci.nsIChannel || 'URI' in request)) {
|
||||
// Try to compute the manifest URL out of document URL
|
||||
// We should end up with urls like:
|
||||
// http://system.gaiamobile.org:8080/manifest.webapp
|
||||
// app://system.gaiamobile.org/manifest.webapp
|
||||
let manifestURL = 'app://'+ request.URI.host +'/manifest.webapp';
|
||||
let manifest = appsService.getAppByManifestURL(manifestURL);
|
||||
if (manifest) {
|
||||
let app = manifest.QueryInterface(Ci.mozIApplication);
|
||||
// test-agent expects its iframes to load apps without any permissions
|
||||
// so prevent setting the correct app id to its iframes
|
||||
if (!webProgress.DOMWindow || !webProgress.DOMWindow.top ||
|
||||
!webProgress.DOMWindow.top.location.host.startsWith("test-agent.gaiamobile.org")) {
|
||||
webProgress.QueryInterface(Ci.nsIDocShell).setIsApp(app.localId);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Watch for new Firefox top level windows
|
||||
Services.obs.addObserver(function(win) {
|
||||
win.addEventListener('DOMContentLoaded', function loaded() {
|
||||
win.removeEventListener('DOMContentLoaded', loaded);
|
||||
if (win.gBrowser) {
|
||||
// Listen for loads happening in all browser windows.
|
||||
win.gBrowser.addProgressListener(docShellListener);
|
||||
}
|
||||
});
|
||||
}, 'domwindowopened', false);
|
||||
|
||||
Services.obs.addObserver(function() {
|
||||
let browserWindow = Services.wm.getMostRecentWindow('navigator:browser');
|
||||
|
||||
// Inject CSS in browser to customize responsive view
|
||||
let doc = browserWindow.document;
|
||||
let pi = doc.createProcessingInstruction('xml-stylesheet', 'href="chrome://browser-helper.js/content/browser.css" type="text/css"');
|
||||
doc.insertBefore(pi, doc.firstChild);
|
||||
|
||||
// Start responsive design mode
|
||||
initResponsiveDesign(browserWindow);
|
||||
|
||||
// And devtool panel while maximizing its size according to screen size
|
||||
Services.prefs.setIntPref('devtools.toolbox.sidebar.width',
|
||||
browserWindow.outerWidth - 550);
|
||||
gDevToolsBrowser.selectToolCommand(browserWindow.gBrowser);
|
||||
|
||||
let KeyboardGlobal = Cu.import('resource://gre/modules/Keyboard.jsm');
|
||||
|
||||
// SystemAppProxy.jsm doesn't ship into Firefox.
|
||||
// Keyboard.jsm is the only one user of it outside of b2g/ folder.
|
||||
// So hack it, in order to allow keyboard to send events to the system app
|
||||
Object.defineProperty(KeyboardGlobal, "SystemAppProxy", {
|
||||
value: {
|
||||
dispatchEvent: function (detail) {
|
||||
let contentWindow = browserWindow.content;
|
||||
var contentDetail = Components.utils.createObjectIn(contentWindow);
|
||||
for (var i in detail) {
|
||||
contentDetail[i] = detail[i];
|
||||
}
|
||||
Components.utils.makeObjectPropsNormal(contentDetail);
|
||||
|
||||
var customEvt = contentWindow.document.createEvent('CustomEvent');
|
||||
customEvt.initCustomEvent('mozChromeEvent', true, true, contentDetail);
|
||||
contentWindow.dispatchEvent(customEvt);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Keyboard.initFormsFrameScript(mm);
|
||||
mm.loadFrameScript('chrome://global/content/forms.js', true);
|
||||
}, 'sessionstore-windows-restored', false);
|
||||
|
||||
try {
|
||||
// Register a new devtool panel with various OS controls
|
||||
Cu.import('resource:///modules/devtools/gDevTools.jsm');
|
||||
gDevTools.registerTool({
|
||||
id: 'firefox-os-controls',
|
||||
key: 'F',
|
||||
modifiers: 'accel,shift',
|
||||
icon: 'chrome://browser-helper.js/content/panel/icon.gif',
|
||||
url: 'chrome://browser-helper.js/content/panel/index.html',
|
||||
label: 'FFOS Control',
|
||||
tooltip: 'Set of controls to tune FirefoxOS apps in Desktop browser',
|
||||
isTargetSupported: function(target) {
|
||||
return target.isLocalTab;
|
||||
},
|
||||
build: function(iframeWindow, toolbox) {
|
||||
iframeWindow.wrappedJSObject.tab = toolbox.target.window;
|
||||
return {
|
||||
destroy: function () {}
|
||||
};
|
||||
}
|
||||
});
|
||||
} catch(e) {
|
||||
debug('Can\'t load the devtools panel. Likely because this version of Gecko is too old');
|
||||
}
|
||||
|
||||
} catch (e) {
|
||||
debug('Something went wrong while trying to start browser-helper: ' + e);
|
||||
}
|
||||
}
|
||||
|
||||
function shutdown(data, reason) {
|
||||
// prevent stdout warnings
|
||||
}
|
||||
|
||||
function install(data, reason) {
|
||||
}
|
||||
|
||||
function uninstall(data, reason) {
|
||||
}
|
||||
1
gaia/tools/extensions/browser-helper@gaiamobile.org/chrome.manifest
Executable file
1
gaia/tools/extensions/browser-helper@gaiamobile.org/chrome.manifest
Executable file
|
|
@ -0,0 +1 @@
|
|||
content browser-helper.js content/
|
||||
102
gaia/tools/extensions/browser-helper@gaiamobile.org/content/browser.css
Executable file
102
gaia/tools/extensions/browser-helper@gaiamobile.org/content/browser.css
Executable file
|
|
@ -0,0 +1,102 @@
|
|||
|
||||
|
||||
/* Ignore devtools usual resizer */
|
||||
stack.browserStack.os-mode box.devtools-responsiveui-resizehandle,
|
||||
stack.browserStack.os-mode box.devtools-responsiveui-resizebar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Gives responsive mode a phone look'n feel */
|
||||
stack.browserStack.os-mode {
|
||||
padding: 60px 15px 0;
|
||||
|
||||
border-radius: 25px / 20px;
|
||||
border-bottom-left-radius: 0;
|
||||
border-bottom-right-radius: 0;
|
||||
border: 1px solid #FFFFFF;
|
||||
border-bottom-width: 0;
|
||||
|
||||
background-color: #353535;
|
||||
|
||||
box-shadow: 0 3px 0.7px 1px #777777, 0 5px rgba(0, 0, 0, 0.4) inset !important;
|
||||
|
||||
background-image: -moz-linear-gradient(right, #111 11%, #333 56%);
|
||||
min-width: 320px;
|
||||
}
|
||||
|
||||
#os-hardware-buttons {
|
||||
-moz-appearance: none;
|
||||
padding: 20px;
|
||||
|
||||
border: 1px solid #FFFFFF;
|
||||
border-bottom-left-radius: 25px;
|
||||
border-bottom-right-radius: 25px;
|
||||
border-top-width: 0;
|
||||
|
||||
box-shadow: 0 3px 0.7px 1px #777777, 0 -7px rgba(0, 0, 0, 0.4) inset;
|
||||
|
||||
background-image: -moz-linear-gradient(right, #111 11%, #333 56%);
|
||||
}
|
||||
|
||||
#os-home-button {
|
||||
width: 40px;
|
||||
height: 30px;
|
||||
list-style-image: url("chrome://browser-helper.js/content/icons/home.png");
|
||||
-moz-image-region: rect(0, 12px, 12px, 0);
|
||||
}
|
||||
|
||||
#os-home-button:hover:active {
|
||||
-moz-image-region: rect(0, 24px, 12px, 12px);
|
||||
}
|
||||
|
||||
#os-sleep-button {
|
||||
-moz-appearance: none;
|
||||
/* compensate browserStack top padding */
|
||||
margin-top: -67px;
|
||||
margin-right: 10px;
|
||||
|
||||
min-width: 10px;
|
||||
width: 50px;
|
||||
height: 5px;
|
||||
|
||||
border: 1px solid #444;
|
||||
border-top-right-radius: 12px;
|
||||
border-top-left-radius: 12px;
|
||||
border-bottom-color: transparent;
|
||||
|
||||
background-image: -moz-linear-gradient(top, #111 11%, #333 56%);
|
||||
}
|
||||
|
||||
#os-sleep-button:hover:active {
|
||||
background-image: -moz-linear-gradient(top, #aaa 11%, #ddd 56%);
|
||||
}
|
||||
|
||||
#os-volume-buttons {
|
||||
margin-left: -29px;
|
||||
}
|
||||
|
||||
#os-volume-up-button,
|
||||
#os-volume-down-button {
|
||||
-moz-appearance: none;
|
||||
border: 1px solid red;
|
||||
min-width: 8px;
|
||||
height: 40px;
|
||||
|
||||
border: 1px solid #444;
|
||||
border-right-color: transparent;
|
||||
|
||||
background-image: -moz-linear-gradient(right, #111 11%, #333 56%);
|
||||
}
|
||||
|
||||
#os-volume-up-button:hover:active,
|
||||
#os-volume-down-button:hover:active {
|
||||
background-image: -moz-linear-gradient(right, #aaa 11%, #ddd 56%);
|
||||
}
|
||||
|
||||
#os-volume-up-button {
|
||||
border-top-left-radius: 12px;
|
||||
}
|
||||
|
||||
#os-volume-down-button {
|
||||
border-bottom-left-radius: 12px;
|
||||
}
|
||||
BIN
gaia/tools/extensions/browser-helper@gaiamobile.org/content/icons/home.png
Executable file
BIN
gaia/tools/extensions/browser-helper@gaiamobile.org/content/icons/home.png
Executable file
Binary file not shown.
|
After Width: | Height: | Size: 470 B |
78
gaia/tools/extensions/browser-helper@gaiamobile.org/content/panel/controls.js
vendored
Executable file
78
gaia/tools/extensions/browser-helper@gaiamobile.org/content/panel/controls.js
vendored
Executable file
|
|
@ -0,0 +1,78 @@
|
|||
!function() {
|
||||
|
||||
function sendChromeEvent(detail) {
|
||||
var contentDetail = Components.utils.createObjectIn(tab);
|
||||
for (var i in detail) {
|
||||
contentDetail[i] = detail[i];
|
||||
}
|
||||
Components.utils.makeObjectPropsNormal(contentDetail);
|
||||
|
||||
var customEvt = tab.document.createEvent('CustomEvent');
|
||||
customEvt.initCustomEvent('mozChromeEvent', true, true, contentDetail);
|
||||
tab.dispatchEvent(customEvt);
|
||||
}
|
||||
|
||||
function sendEvent(type, details) {
|
||||
var event = new tab.CustomEvent(type, details);
|
||||
tab.dispatchEvent(event);
|
||||
}
|
||||
|
||||
function takeScreenshot() {
|
||||
var canvas = document.createElementNS('http://www.w3.org/1999/xhtml',
|
||||
'canvas');
|
||||
var width = tab.innerWidth;
|
||||
var height = tab.innerHeight;
|
||||
canvas.setAttribute('width', width);
|
||||
canvas.setAttribute('height', height);
|
||||
|
||||
var context = canvas.getContext('2d');
|
||||
var flags =
|
||||
context.DRAWWINDOW_DRAW_CARET |
|
||||
context.DRAWWINDOW_DRAW_VIEW |
|
||||
context.DRAWWINDOW_USE_WIDGET_LAYERS;
|
||||
context.drawWindow(tab, 0, 0, width, height,
|
||||
'rgb(255,255,255)', flags);
|
||||
|
||||
sendChromeEvent({
|
||||
type: 'take-screenshot-success',
|
||||
file: canvas.mozGetAsFile('screenshot', 'image/png')
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
function Emulation() {
|
||||
|
||||
}
|
||||
|
||||
Emulation.prototype = {
|
||||
notification: function() {
|
||||
sendChromeEvent({
|
||||
type: 'desktop-notification',
|
||||
id: 123,
|
||||
title: 'Some Notification',
|
||||
text: 'I love notifications.',
|
||||
manifestURL: 'http://sytem.gaiamobile.org:8080/manifest.webapp'
|
||||
});
|
||||
}
|
||||
};
|
||||
window.emulation = new Emulation();
|
||||
|
||||
function Workflow() {
|
||||
|
||||
}
|
||||
|
||||
Workflow.prototype = {
|
||||
reload: function() {
|
||||
var global = tab.wrappedJSObject;
|
||||
var app = global.System.curentApp;
|
||||
|
||||
app.reload();
|
||||
},
|
||||
|
||||
screenshot: function() {
|
||||
takeScreenshot();
|
||||
}
|
||||
}
|
||||
window.workflow = new Workflow();
|
||||
|
||||
}();
|
||||
BIN
gaia/tools/extensions/browser-helper@gaiamobile.org/content/panel/icon.gif
Executable file
BIN
gaia/tools/extensions/browser-helper@gaiamobile.org/content/panel/icon.gif
Executable file
Binary file not shown.
|
After Width: | Height: | Size: 610 B |
17
gaia/tools/extensions/browser-helper@gaiamobile.org/content/panel/index.html
Executable file
17
gaia/tools/extensions/browser-helper@gaiamobile.org/content/panel/index.html
Executable file
|
|
@ -0,0 +1,17 @@
|
|||
<link rel="stylesheet" href="stylesheet.css">
|
||||
<script defer src="controls.js"></script>
|
||||
<script defer src="workloads.js"></script>
|
||||
|
||||
<h3>Emulation</h3>
|
||||
<section>
|
||||
<button onclick="emulation.notification()">Notification</button>
|
||||
</section>
|
||||
|
||||
<h3>Developer</h3>
|
||||
<section>
|
||||
<button onclick="workflow.reload()">Reload Application</button>
|
||||
<button onclick="workflow.screenshot()">Take a screenshot</button>
|
||||
<select id="reference-workloads">
|
||||
<option value="-1">Import workload:</option>
|
||||
</select>
|
||||
</section>
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
* {
|
||||
font-family: sans-serif;
|
||||
}
|
||||
|
||||
section {
|
||||
border-bottom: 1px solid #ccc;
|
||||
padding-bottom: 1rem;
|
||||
}
|
||||
|
||||
h3 {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
|
@ -0,0 +1,78 @@
|
|||
!function() {
|
||||
|
||||
var select = document.getElementById('reference-workloads');
|
||||
|
||||
var options = [
|
||||
{
|
||||
name: 'contacts',
|
||||
db: 'contacts',
|
||||
counts: [0, 200, 500, 1000, 2000]
|
||||
},
|
||||
{
|
||||
name: 'messages',
|
||||
db: 'sms',
|
||||
counts: [0, 200, 500, 1000, 2000]
|
||||
},
|
||||
{
|
||||
name: 'dialer recents',
|
||||
db: 'dialer',
|
||||
counts: [0, 50, 100, 200, 500]
|
||||
}
|
||||
];
|
||||
|
||||
var targetFiles = {
|
||||
sms: '226660312ssm.sqlite',
|
||||
contacts: '3406066227csotncta.sqlite',
|
||||
dialer: '2584670174dsitanleecreR.sqlite'
|
||||
};
|
||||
|
||||
for (var i = 0, option; option = options[i]; i++) {
|
||||
for (var j = 0, cLen = option.counts.length; j < cLen; j++) {
|
||||
var count = option.counts[j];
|
||||
var optionEl = document.createElement('option');
|
||||
optionEl.innerHTML = count + ' ' + option.name;
|
||||
optionEl.value = option.db + '-' + count;
|
||||
select.appendChild(optionEl);
|
||||
}
|
||||
}
|
||||
|
||||
select.addEventListener('change', function(e) {
|
||||
var parts = this.value.split('-');
|
||||
var database = parts[0];
|
||||
var count = parts[1];
|
||||
|
||||
importRecords(database, count);
|
||||
|
||||
tab.alert('Imported: ' + count + ' ' + database + ' records.');
|
||||
this.value = '-1';
|
||||
});
|
||||
|
||||
function importRecords(database, count) {
|
||||
var Ci = Components.interfaces;
|
||||
|
||||
Components.utils.import('resource://gre/modules/Services.jsm');
|
||||
var currProfD = Services.dirsvc.get('ProfD', Ci.nsIFile);
|
||||
var profileDir = currProfD.path;
|
||||
|
||||
var file = database + 'Db-' + count + '.sqlite';
|
||||
|
||||
var sourceFile = Services.dirsvc.get('ProfD', Ci.nsILocalFile);
|
||||
sourceFile.append('..');
|
||||
sourceFile.append('test_media');
|
||||
sourceFile.append('reference-workload');
|
||||
sourceFile.append(file);
|
||||
|
||||
var targetFolder = Services.dirsvc.get('ProfD', Ci.nsILocalFile);
|
||||
targetFolder.append('storage');
|
||||
targetFolder.append('persistent');
|
||||
targetFolder.append('chrome');
|
||||
targetFolder.append('idb');
|
||||
|
||||
try {
|
||||
sourceFile.copyTo(targetFolder, targetFiles[database]);
|
||||
} catch (e) {
|
||||
tab.alert('Could not import file: ' + e);
|
||||
}
|
||||
}
|
||||
|
||||
}();
|
||||
187
gaia/tools/extensions/browser-helper@gaiamobile.org/content/shell.js
Executable file
187
gaia/tools/extensions/browser-helper@gaiamobile.org/content/shell.js
Executable file
|
|
@ -0,0 +1,187 @@
|
|||
|
||||
dump('======== browser-helper: content.js loaded ========\n')
|
||||
|
||||
function debug(str) {
|
||||
//dump('browser-helper (frame-script): ' + str + '\n');
|
||||
}
|
||||
|
||||
let CC = Components.Constructor;
|
||||
let Cc = Components.classes;
|
||||
let Ci = Components.interfaces;
|
||||
let Cu = Components.utils;
|
||||
let Cr = Components.results;
|
||||
|
||||
Cu.import('resource://gre/modules/Services.jsm');
|
||||
Cu.import('resource://gre/modules/Keyboard.jsm');
|
||||
|
||||
// Various helpers coming from /b2g/chrome/content/shell.js
|
||||
function getContentWindow() {
|
||||
return content;
|
||||
}
|
||||
|
||||
function sendChromeEvent(details, type) {
|
||||
type = type || 'mozChromeEvent';
|
||||
let content = getContentWindow();
|
||||
details = details || {};
|
||||
|
||||
let event = content.document.createEvent('CustomEvent');
|
||||
event.initCustomEvent(type, true, true,
|
||||
Cu.cloneInto(details, content));
|
||||
content.dispatchEvent(event);
|
||||
}
|
||||
|
||||
// Listen for app.launch calls and forward them to the the system app.
|
||||
// Copy of /b2g/chrome/content/shell.js
|
||||
Cu.import('resource://gre/modules/Webapps.jsm');
|
||||
Cu.import('resource://gre/modules/AppsUtils.jsm');
|
||||
|
||||
Services.obs.addObserver(function onLaunch(subject, topic, data) {
|
||||
let json = JSON.parse(data);
|
||||
|
||||
DOMApplicationRegistry.getManifestFor(json.manifestURL).then((aManifest) => {
|
||||
if (!aManifest)
|
||||
return;
|
||||
|
||||
let manifest = new ManifestHelper(aManifest, json.origin,
|
||||
json.manifestURL);
|
||||
let data = {
|
||||
'timestamp': json.timestamp,
|
||||
'url': manifest.fullLaunchPath(json.startPoint),
|
||||
'manifestURL': json.manifestURL
|
||||
};
|
||||
sendChromeEvent(data, 'webapps-launch');
|
||||
});
|
||||
}, 'webapps-launch', false);
|
||||
|
||||
|
||||
// Listen for system messages and relay them to Gaia.
|
||||
// Copy of /b2g/chrome/content/shell.js
|
||||
// XXX This code should be loaded in the activities/ extension
|
||||
Services.obs.addObserver(function onSystemMessage(subject, topic, data) {
|
||||
let msg = JSON.parse(data);
|
||||
|
||||
let origin = Services.io.newURI(msg.manifest, null, null).prePath;
|
||||
sendChromeEvent({
|
||||
url: msg.uri,
|
||||
manifestURL: msg.manifest,
|
||||
isActivity: (msg.type == 'activity'),
|
||||
target: msg.target,
|
||||
expectingSystemMessage: true
|
||||
}, 'open-app');
|
||||
}, 'system-messages-open-app', false);
|
||||
|
||||
Services.obs.addObserver(function(aSubject, aTopic, aData) {
|
||||
let data = JSON.parse(aData);
|
||||
sendChromeEvent({
|
||||
type: 'activity-done',
|
||||
success: data.success,
|
||||
manifestURL: data.manifestURL,
|
||||
pageURL: data.pageURL
|
||||
});
|
||||
}, 'activity-done', false);
|
||||
|
||||
|
||||
|
||||
// =================== Settings ====================
|
||||
// Copy of /b2g/chrome/content/settings.js
|
||||
var SettingsListener = {
|
||||
_callbacks: {},
|
||||
|
||||
init: function sl_init() {
|
||||
if ('mozSettings' in content.navigator && content.navigator.mozSettings) {
|
||||
content.navigator.mozSettings.onsettingchange = this.onchange.bind(this);
|
||||
}
|
||||
},
|
||||
|
||||
onchange: function sl_onchange(evt) {
|
||||
var callback = this._callbacks[evt.settingName];
|
||||
if (callback) {
|
||||
callback(evt.settingValue);
|
||||
}
|
||||
},
|
||||
|
||||
observe: function sl_observe(name, defaultValue, callback) {
|
||||
var settings = content.navigator.mozSettings;
|
||||
if (!settings) {
|
||||
content.setTimeout(function() { callback(defaultValue); });
|
||||
return;
|
||||
}
|
||||
|
||||
if (!callback || typeof callback !== 'function') {
|
||||
throw new Error('Callback is not a function');
|
||||
}
|
||||
|
||||
var req = settings.createLock().get(name);
|
||||
req.addEventListener('success', (function onsuccess() {
|
||||
callback(typeof(req.result[name]) != 'undefined' ?
|
||||
req.result[name] : defaultValue);
|
||||
}));
|
||||
|
||||
this._callbacks[name] = callback;
|
||||
}
|
||||
};
|
||||
|
||||
SettingsListener.init();
|
||||
|
||||
// =================== Languages ====================
|
||||
// Modified copy of /b2g/chrome/content/settings.js
|
||||
SettingsListener.observe('language.current', 'en-US', function(value) {
|
||||
Services.prefs.setCharPref('general.useragent.locale', value);
|
||||
|
||||
let prefName = 'intl.accept_languages';
|
||||
if (Services.prefs.prefHasUserValue(prefName)) {
|
||||
Services.prefs.clearUserPref(prefName);
|
||||
}
|
||||
|
||||
let intl = '';
|
||||
try {
|
||||
intl = Services.prefs.getComplexValue(prefName,
|
||||
Ci.nsIPrefLocalizedString).data;
|
||||
} catch(e) {}
|
||||
|
||||
// Bug 830782 - Homescreen is in English instead of selected locale after
|
||||
// the first run experience.
|
||||
// In order to ensure the current intl value is reflected on the child
|
||||
// process let's always write a user value, even if this one match the
|
||||
// current localized pref value.
|
||||
if (!((new RegExp('^' + value + '[^a-z-_] *[,;]?', 'i')).test(intl))) {
|
||||
value = value + ', ' + intl;
|
||||
} else {
|
||||
value = intl;
|
||||
}
|
||||
Services.prefs.setCharPref(prefName, value);
|
||||
|
||||
//XXX: modification of settings.js here:
|
||||
var window = getContentWindow();
|
||||
if (!('hasStarted' in window) && window.hasStarted != true) {
|
||||
window.hasStarted = true;
|
||||
|
||||
window.addEventListener('load', function onload() {
|
||||
window.removeEventListener('load', onload);
|
||||
window.setTimeout(function() {
|
||||
sendChromeEvent({'type': 'webapps-registry-ready'});
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* This code comes from b2g/chrome/content/shell.js
|
||||
* For now just the keyboard stuff, should copy everything over at some point
|
||||
*/
|
||||
getContentWindow().addEventListener('mozContentEvent', function(evt) {
|
||||
let detail = evt.detail;
|
||||
debug('XXX FIXME : Got a mozContentEvent: ' + detail.type + "\n");
|
||||
|
||||
switch(detail.type) {
|
||||
case 'inputmethod-update-layouts':
|
||||
KeyboardHelper.handleEvent(detail);
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
let KeyboardHelper = {
|
||||
handleEvent: function keyboard_handleEvent(aMessage) {
|
||||
Keyboard.setLayouts(aMessage.layouts);
|
||||
}
|
||||
};
|
||||
23
gaia/tools/extensions/browser-helper@gaiamobile.org/install.rdf
Executable file
23
gaia/tools/extensions/browser-helper@gaiamobile.org/install.rdf
Executable file
|
|
@ -0,0 +1,23 @@
|
|||
<?xml version="1.0"?>
|
||||
|
||||
<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:em="http://www.mozilla.org/2004/em-rdf#">
|
||||
|
||||
<Description about="urn:mozilla:install-manifest">
|
||||
<em:id>browser-helper@gaiamobile.org</em:id>
|
||||
<em:name>browser-helper.js</em:name>
|
||||
<em:version>0.1</em:version>
|
||||
<em:targetApplication>
|
||||
<Description>
|
||||
<em:id>{ec8030f7-c20a-464f-9b0e-13a3a9e97384}</em:id>
|
||||
<em:minVersion>6.0</em:minVersion>
|
||||
<em:maxVersion>*</em:maxVersion>
|
||||
</Description>
|
||||
</em:targetApplication>
|
||||
<em:unpack>true</em:unpack>
|
||||
<em:bootstrap>true</em:bootstrap>
|
||||
<em:creator>Mozilla</em:creator>
|
||||
<em:description>browser shims to make gaia work in desktop browser</em:description>
|
||||
<em:homepageURL>https://github.com/mozilla-b2g/gaia/</em:homepageURL>
|
||||
</Description>
|
||||
</RDF>
|
||||
95
gaia/tools/extensions/desktop-helper/bootstrap.js
vendored
Executable file
95
gaia/tools/extensions/desktop-helper/bootstrap.js
vendored
Executable file
|
|
@ -0,0 +1,95 @@
|
|||
/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- /
|
||||
/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
|
||||
|
||||
'use strict';
|
||||
|
||||
const CC = Components.Constructor;
|
||||
const Cc = Components.classes;
|
||||
const Ci = Components.interfaces;
|
||||
const Cu = Components.utils;
|
||||
|
||||
Cu.import('resource://gre/modules/Services.jsm');
|
||||
|
||||
function debug(data) {
|
||||
//dump('desktop-helper: ' + data + '\n');
|
||||
}
|
||||
|
||||
const kChromeRootPath = 'chrome://desktop-helper.js/content/data/';
|
||||
|
||||
// XXX Scripts should be loaded based on the permissions of the apps not
|
||||
// based on the domain.
|
||||
const kScriptsPerDomain = {
|
||||
'.gaiamobile.org': [
|
||||
'ffos_runtime.js',
|
||||
'lib/bluetooth.js',
|
||||
'lib/cameras.js',
|
||||
'lib/mobile_connection.js',
|
||||
'lib/icc_manager.js',
|
||||
'lib/telephony.js',
|
||||
'lib/wifi.js'
|
||||
]
|
||||
};
|
||||
|
||||
function injectMocks() {
|
||||
// Track loading of apps to inject mock APIs
|
||||
Services.obs.addObserver(function(document) {
|
||||
// Some documents like XBL don't have location and should be ignored
|
||||
if (!document.location || !document.defaultView)
|
||||
return;
|
||||
let currentDomain = document.location.toString();
|
||||
let window = document.defaultView;
|
||||
|
||||
// Do not include mocks for unit test sandboxes
|
||||
if (window.wrappedJSObject.mocha &&
|
||||
currentDomain.indexOf('_sandbox.html') !== -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
debug('+++ loading scripts for app: ' + currentDomain + "\n");
|
||||
// Inject mocks based on domain
|
||||
for (let domain in kScriptsPerDomain) {
|
||||
if (currentDomain.indexOf(domain) == -1)
|
||||
continue;
|
||||
|
||||
let includes = kScriptsPerDomain[domain];
|
||||
for (let i = 0; i < includes.length; i++) {
|
||||
debug('loading ' + includes[i] + '...');
|
||||
|
||||
Services.scriptloader.loadSubScript(kChromeRootPath + includes[i],
|
||||
window.wrappedJSObject);
|
||||
}
|
||||
}
|
||||
}, 'document-element-inserted', false);
|
||||
}
|
||||
|
||||
function hotfixAlarms() {
|
||||
// Replace existing alarm service xpcom factory by a new working one,
|
||||
// until a working fallback is implemented in platform code (bug 867868)
|
||||
|
||||
// Sigh. Seems like there is a registration issue between all the addons.
|
||||
// This is dirty but delaying this seems to make it.
|
||||
var timer = Cc['@mozilla.org/timer;1'].createInstance(Ci.nsITimer);
|
||||
timer.initWithCallback(function() {
|
||||
Services.scriptloader.loadSubScript('chrome://desktop-helper.js/content/alarms.js', {});
|
||||
}, 1000, Ci.nsITimer.TYPE_ONE_SHOT);
|
||||
}
|
||||
|
||||
function startup(data, reason) {
|
||||
try {
|
||||
hotfixAlarms();
|
||||
|
||||
injectMocks();
|
||||
} catch (e) {
|
||||
debug('Something went wrong while trying to start desktop-helper: ' + e);
|
||||
}
|
||||
}
|
||||
|
||||
function shutdown(data, reason) {
|
||||
}
|
||||
|
||||
function install(data, reason) {
|
||||
}
|
||||
|
||||
function uninstall(data, reason) {
|
||||
}
|
||||
|
||||
1
gaia/tools/extensions/desktop-helper/chrome.manifest
Executable file
1
gaia/tools/extensions/desktop-helper/chrome.manifest
Executable file
|
|
@ -0,0 +1 @@
|
|||
content desktop-helper.js content/
|
||||
102
gaia/tools/extensions/desktop-helper/content/alarms.js
Executable file
102
gaia/tools/extensions/desktop-helper/content/alarms.js
Executable file
|
|
@ -0,0 +1,102 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
let Cc = Components.classes;
|
||||
let Ci = Components.interfaces;
|
||||
let Cu = Components.utils;
|
||||
let Cm = Components.manager.QueryInterface(Ci.nsIComponentRegistrar);
|
||||
|
||||
Cu.import('resource://gre/modules/XPCOMUtils.jsm');
|
||||
Cu.import('resource://gre/modules/Services.jsm');
|
||||
|
||||
function debug(str) {
|
||||
//dump('AlarmHalService: ' + str + '\n');
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// AlarmHalService
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
function AlarmHalService() {
|
||||
debug('Constructor');
|
||||
}
|
||||
|
||||
AlarmHalService.prototype = {
|
||||
_timer: Cc['@mozilla.org/timer;1'].createInstance(Ci.nsITimer),
|
||||
setAlarm: function setAlarm(seconds, nanoseconds) {
|
||||
debug('setAlarm: ' + seconds);
|
||||
this._currentAlarm = seconds;
|
||||
|
||||
this._timer.cancel();
|
||||
this._timer.initWithCallback(this, 1000, Ci.nsITimer.TYPE_REPEATING_SLACK);
|
||||
return true;
|
||||
},
|
||||
|
||||
notify: function notify() {
|
||||
debug('notify: ' + (Date.now() / 1000) + ' < ' + this._currentAlarm + '\n');
|
||||
if ((Date.now() / 1000) < this._currentAlarm)
|
||||
return;
|
||||
|
||||
debug('fired!!!');
|
||||
// Let's stop the timer to make sure it does not fire again...
|
||||
this._timer.cancel();
|
||||
|
||||
if (this._alarmFiredCb) {
|
||||
try {
|
||||
this._alarmFiredCb();
|
||||
} catch (e) {}
|
||||
}
|
||||
},
|
||||
|
||||
_alarmFiredCb: null,
|
||||
setAlarmFiredCb: function setAlarmFiredCb(callback) {
|
||||
debug('setAlarmFiredCb!!!');
|
||||
this._alarmFiredCb = callback;
|
||||
},
|
||||
|
||||
_timezoneChangeDb: null,
|
||||
setTimezoneChangedCb: function setTimezoneChangedCb(callback) {
|
||||
debug('setTzFiredCb!!!');
|
||||
this._timezoneChangedCb = callback;
|
||||
},
|
||||
|
||||
classID: newClassID,
|
||||
|
||||
QueryInterface: XPCOMUtils.generateQI([
|
||||
Ci.nsIAlarmHalService, Ci.nsITimerCallback
|
||||
])
|
||||
};
|
||||
|
||||
// Unregister the embedded low level component for alarm and
|
||||
// replace it with our own.
|
||||
let contract = '@mozilla.org/alarmHalService;1';
|
||||
var newClassID = Cc['@mozilla.org/uuid-generator;1']
|
||||
.getService(Ci.nsIUUIDGenerator)
|
||||
.generateUUID();
|
||||
|
||||
// Unregister the old factory.
|
||||
let oldCid = Cm.contractIDToCID(contract);
|
||||
let oldFactory = Cm.getClassObjectByContractID(contract, Ci.nsIFactory);
|
||||
Cm.unregisterFactory(oldCid, oldFactory);
|
||||
|
||||
// Register a new one.
|
||||
let instance = null;
|
||||
let newFactory = {
|
||||
createInstance: function(outer, iid) {
|
||||
if (outer)
|
||||
throw Components.results.NS_ERROR_NO_AGGREGATION;
|
||||
if (instance === null)
|
||||
instance = new AlarmHalService();
|
||||
instance.QueryInterface(iid);
|
||||
return instance.QueryInterface(iid);
|
||||
},
|
||||
lockFactory: function(aLock) {
|
||||
throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
|
||||
},
|
||||
QueryInterface: XPCOMUtils.generateQI([Ci.nsIFactory])
|
||||
};
|
||||
Cm.registerFactory(newClassID, '', contract, newFactory);
|
||||
|
||||
// Then let's start the AlarmService!
|
||||
Cu.import('resource://gre/modules/AlarmService.jsm');
|
||||
65
gaia/tools/extensions/desktop-helper/content/data/ffos_runtime.js
Executable file
65
gaia/tools/extensions/desktop-helper/content/data/ffos_runtime.js
Executable file
|
|
@ -0,0 +1,65 @@
|
|||
FFOS_RUNTIME = {
|
||||
|
||||
/**
|
||||
* Emulates a dom request
|
||||
* Returns an object and calls it's onsuccess handler.
|
||||
*/
|
||||
domRequest: function(result, timeout) {
|
||||
timeout = timeout || 1000;
|
||||
|
||||
return function() {
|
||||
var request = { result: result };
|
||||
|
||||
setTimeout(function() {
|
||||
if (request.onsuccess) {
|
||||
request.onsuccess();
|
||||
}
|
||||
}, timeout);
|
||||
|
||||
return request;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* This is so we have a single entry-point for the APP window
|
||||
* This is needed as the interface to window may be changing soon
|
||||
*/
|
||||
getAppWindow: function(callback) {
|
||||
callback(window);
|
||||
},
|
||||
|
||||
/**
|
||||
* Proxies console.log statements to the app window
|
||||
*/
|
||||
debug: function() {
|
||||
var args = Array.slice(arguments);
|
||||
|
||||
this.getAppWindow(function(win) {
|
||||
win.console.log.apply(win.console, args);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Creates a navigator shim on the window
|
||||
* @param {String} property name.
|
||||
* @param {Object} property definition.
|
||||
* @param {Boolean} if true, makes a navigator setter for this object. Useful for testing.
|
||||
*/
|
||||
makeNavigatorShim: function(property, definition, makeSetter) {
|
||||
try {
|
||||
window.navigator.__defineGetter__(property, function() {
|
||||
return definition;
|
||||
});
|
||||
|
||||
if (makeSetter) {
|
||||
window.navigator.__defineSetter__(property, function(prop) {
|
||||
definition = prop;
|
||||
});
|
||||
}
|
||||
|
||||
} catch (e) {
|
||||
alert('Error intializing shim (' + property + '): ' + e);
|
||||
}
|
||||
}
|
||||
};
|
||||
var debug = FFOS_RUNTIME.debug;
|
||||
34
gaia/tools/extensions/desktop-helper/content/data/lib/bluetooth.js
Executable file
34
gaia/tools/extensions/desktop-helper/content/data/lib/bluetooth.js
Executable file
|
|
@ -0,0 +1,34 @@
|
|||
!function() {
|
||||
|
||||
function debug(str) {
|
||||
//dump('Bluetooth : ' + str + '\n');
|
||||
}
|
||||
|
||||
function MockBluetoothRequest() {
|
||||
}
|
||||
|
||||
MockBluetoothRequest.prototype = {
|
||||
};
|
||||
|
||||
FFOS_RUNTIME.makeNavigatorShim('mozBluetooth', {
|
||||
enabled: true,
|
||||
addEventListener: function(type, callback, bubble) {},
|
||||
onenabled: function(event) {},
|
||||
ondisabled: function(event) {},
|
||||
onadapteradded: function() {
|
||||
debug('onadapteradded');
|
||||
},
|
||||
getDefaultAdapter: FFOS_RUNTIME.domRequest({
|
||||
ondevicefound: function() {},
|
||||
setDiscoverable: function() {},
|
||||
getPairedDevices: FFOS_RUNTIME.domRequest([]),
|
||||
startDiscovery: FFOS_RUNTIME.domRequest(),
|
||||
name: 'I like blue',
|
||||
setName: FFOS_RUNTIME.domRequest(),
|
||||
getConnectedDevices: FFOS_RUNTIME.domRequest()
|
||||
}),
|
||||
isConnected: function() {
|
||||
debug('isConnected');
|
||||
}
|
||||
});
|
||||
}();
|
||||
88
gaia/tools/extensions/desktop-helper/content/data/lib/cameras.js
Executable file
88
gaia/tools/extensions/desktop-helper/content/data/lib/cameras.js
Executable file
|
|
@ -0,0 +1,88 @@
|
|||
!function() {
|
||||
|
||||
var CameraManager = {
|
||||
|
||||
// The camera stream
|
||||
stream: false,
|
||||
|
||||
capabilities: {
|
||||
focusModes: '',
|
||||
pictureSizes: [{
|
||||
width: 600,
|
||||
height: 600
|
||||
}],
|
||||
previewSizes: [{
|
||||
width: 600,
|
||||
height: 600
|
||||
}],
|
||||
recorderProfiles: {
|
||||
cif: {
|
||||
video: {
|
||||
width: 600,
|
||||
height: 600
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
getListOfCameras: function() {
|
||||
console.log('mozCamera.getListOfCameras');
|
||||
return [this];
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets a camera
|
||||
*/
|
||||
getCamera: function(options, callback) {
|
||||
console.log('mozCamera.getCamera');
|
||||
|
||||
navigator.mozGetUserMedia({video: true},
|
||||
function(stream) {
|
||||
this.stream = stream;
|
||||
callback(this);
|
||||
this.onPreviewStateChange('started');
|
||||
}.bind(this),
|
||||
function() {
|
||||
console.log('Could not initialize camera.');
|
||||
}
|
||||
);
|
||||
},
|
||||
|
||||
getPreviewStream: function(config, callback) {
|
||||
console.log('mozCamera.getPreviewStream');
|
||||
callback(this.stream);
|
||||
},
|
||||
|
||||
getPreviewStreamVideoMode: function(config, callback) {
|
||||
console.log('mozCamera.getPreviewStreamVideoMode');
|
||||
callback(this.stream);
|
||||
},
|
||||
|
||||
resumePreview: function() {
|
||||
console.log('mozCamera.resumePreview');
|
||||
},
|
||||
|
||||
autoFocus: function() {
|
||||
console.log('mozCamera.autoFocus');
|
||||
},
|
||||
|
||||
takePicture: function(config, onSuccess, onError) {
|
||||
console.log('mozCamera.takePicture');
|
||||
var cnvs = document.createElement('canvas');
|
||||
|
||||
cnvs.width = config.pictureSize.width;
|
||||
cnvs.height = config.pictureSize.height;
|
||||
var ctx = cnvs.getContext('2d');
|
||||
ctx.drawImage(window.document.querySelector('video'), 0, 0, cnvs.width, cnvs.height);
|
||||
cnvs.toBlob(function(blob) {
|
||||
onSuccess(blob);
|
||||
}, 'image/jpeg');
|
||||
},
|
||||
|
||||
release: function() {
|
||||
console.log('mozCamera.release');
|
||||
}
|
||||
};
|
||||
|
||||
FFOS_RUNTIME.makeNavigatorShim('mozCameras', CameraManager);
|
||||
}();
|
||||
103
gaia/tools/extensions/desktop-helper/content/data/lib/icc_manager.js
Executable file
103
gaia/tools/extensions/desktop-helper/content/data/lib/icc_manager.js
Executable file
|
|
@ -0,0 +1,103 @@
|
|||
!function() {
|
||||
|
||||
function debug(str) {
|
||||
//dump('mozIccManager: ' + str + '\n');
|
||||
}
|
||||
|
||||
var iccs = {
|
||||
111: {
|
||||
_retryCount: 3,
|
||||
cardState: 'ready',
|
||||
iccInfo: {
|
||||
iccid: true,
|
||||
msisdn: '5555555555'
|
||||
},
|
||||
setCardLock: function() {
|
||||
debug('setCardLock');
|
||||
},
|
||||
getCardLock: FFOS_RUNTIME.domRequest({ enabled: false }),
|
||||
getCardLockRetryCount: function(type) {
|
||||
var request = {};
|
||||
setTimeout(function() {
|
||||
request.result = {
|
||||
retryCount: this._retryCount
|
||||
};
|
||||
request.onsuccess && request.onsuccess();
|
||||
}.bind(this));
|
||||
return request;
|
||||
},
|
||||
unlockCardLock: function() {
|
||||
// simulate invalid input
|
||||
if (this._retryCount > 1) {
|
||||
var request = {};
|
||||
setTimeout(function() {
|
||||
request.error = {
|
||||
retryCount: --this._retryCount,
|
||||
lockType: 'pin'
|
||||
};
|
||||
request.onerror && request.onerror();
|
||||
}.bind(this), 200);
|
||||
return request;
|
||||
}
|
||||
this.cardState = 'ready';
|
||||
return FFOS_RUNTIME.domRequest()();
|
||||
},
|
||||
addEventListener: function() {
|
||||
debug('addEventListener');
|
||||
},
|
||||
removeEventListener: function() {
|
||||
debug('removeEventListener');
|
||||
},
|
||||
},
|
||||
222: {
|
||||
_retryCount: 3,
|
||||
cardState: 'ready',
|
||||
iccInfo: {
|
||||
iccid: true
|
||||
},
|
||||
setCardLock: function() {
|
||||
debug('setCardLock');
|
||||
},
|
||||
getCardLock: FFOS_RUNTIME.domRequest({ enabled: false }),
|
||||
getCardLockRetryCount: function(type) {
|
||||
var request = {};
|
||||
setTimeout(function() {
|
||||
request.result = {
|
||||
retryCount: this._retryCount
|
||||
};
|
||||
request.onsuccess && request.onsuccess();
|
||||
}.bind(this));
|
||||
return request;
|
||||
},
|
||||
unlockCardLock: function() {
|
||||
this.cardState = 'ready';
|
||||
return FFOS_RUNTIME.domRequest()();
|
||||
},
|
||||
addEventListener: function() {
|
||||
debug('addEventListener');
|
||||
},
|
||||
removeEventListener: function() {
|
||||
debug('removeEventListener');
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
var iccManager = {
|
||||
addEventListener: function() {
|
||||
debug('addEventListener');
|
||||
},
|
||||
removeEventListener: function() {
|
||||
debug('removeEventListener');
|
||||
},
|
||||
getIccById: function(iccId) {
|
||||
return iccs[iccId];
|
||||
},
|
||||
iccIds: [111]
|
||||
};
|
||||
|
||||
if (window._shimDualSim) {
|
||||
iccManager.iccIds.push(222);
|
||||
}
|
||||
|
||||
FFOS_RUNTIME.makeNavigatorShim('mozIccManager', iccManager, true);
|
||||
}();
|
||||
96
gaia/tools/extensions/desktop-helper/content/data/lib/mobile_connection.js
Executable file
96
gaia/tools/extensions/desktop-helper/content/data/lib/mobile_connection.js
Executable file
|
|
@ -0,0 +1,96 @@
|
|||
!function() {
|
||||
|
||||
function debug(str) {
|
||||
//dump('mozMobileConnection: ' + str + '\n');
|
||||
}
|
||||
|
||||
var initialized = true;
|
||||
var fakeNetwork = { shortName: 'Fake Orange F', mcc: '208', mnc: '1' };
|
||||
var fakeVoice = {
|
||||
network: fakeNetwork,
|
||||
state: 'notSearching',
|
||||
roaming: true,
|
||||
connected: true,
|
||||
emergencyCallsOnly: false
|
||||
};
|
||||
var fakeNetwork2 = { shortName: 'Verizon Fake', mcc: '208', mnc: '1' };
|
||||
var fakeVoice2 = {
|
||||
network: fakeNetwork2,
|
||||
state: 'notSearching',
|
||||
roaming: true,
|
||||
connected: true,
|
||||
emergencyCallsOnly: false
|
||||
};
|
||||
|
||||
function fakeEventListener(type, callback, bubble) {
|
||||
if (initialized) {
|
||||
if (typeof callback === 'function') {
|
||||
setTimeout(callback.bind(this, { iccId: this.iccId }));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// simulates a connection to a data network;
|
||||
setTimeout(function fakeCallback() {
|
||||
initialized = true;
|
||||
if (typeof callback === 'function') {
|
||||
callback({ iccId: this.iccId });
|
||||
}
|
||||
}.bind(this), 5000);
|
||||
}
|
||||
|
||||
var mobileConnections = [{
|
||||
iccId: 111,
|
||||
addEventListener: fakeEventListener,
|
||||
removeEventListener: function() {},
|
||||
getCallWaitingOption: FFOS_RUNTIME.domRequest(true),
|
||||
getCallForwardingOption: FFOS_RUNTIME.domRequest(1),
|
||||
setCallWaitingOption: FFOS_RUNTIME.domRequest(),
|
||||
getCallingLineIdRestriction: FFOS_RUNTIME.domRequest(),
|
||||
setVoicePrivacyMode: FFOS_RUNTIME.domRequest(),
|
||||
getVoicePrivacyMode: FFOS_RUNTIME.domRequest(),
|
||||
setRadioEnabled: FFOS_RUNTIME.domRequest(),
|
||||
getRadioEnabled: FFOS_RUNTIME.domRequest(),
|
||||
getRoamingPreference: FFOS_RUNTIME.domRequest(),
|
||||
setRoamingPreference: FFOS_RUNTIME.domRequest(),
|
||||
getPreferredNetworkType: FFOS_RUNTIME.domRequest(),
|
||||
setPreferredNetworkType: FFOS_RUNTIME.domRequest(),
|
||||
get data() {
|
||||
return initialized ? { network: fakeNetwork } : null;
|
||||
},
|
||||
get voice() {
|
||||
return initialized ? fakeVoice : null;
|
||||
}
|
||||
}];
|
||||
|
||||
if (window._shimDualSim) {
|
||||
mobileConnections.push({
|
||||
iccId: 222,
|
||||
addEventListener: fakeEventListener,
|
||||
removeEventListener: function() {},
|
||||
getCallWaitingOption: FFOS_RUNTIME.domRequest(true),
|
||||
getCallForwardingOption: FFOS_RUNTIME.domRequest(1),
|
||||
setCallWaitingOption: FFOS_RUNTIME.domRequest(),
|
||||
getCallingLineIdRestriction: FFOS_RUNTIME.domRequest(),
|
||||
setVoicePrivacyMode: FFOS_RUNTIME.domRequest(),
|
||||
getVoicePrivacyMode: FFOS_RUNTIME.domRequest(),
|
||||
setRadioEnabled: FFOS_RUNTIME.domRequest(),
|
||||
getRadioEnabled: FFOS_RUNTIME.domRequest(),
|
||||
getRoamingPreference: FFOS_RUNTIME.domRequest(),
|
||||
setRoamingPreference: FFOS_RUNTIME.domRequest(),
|
||||
getPreferredNetworkType: FFOS_RUNTIME.domRequest(),
|
||||
setPreferredNetworkType: FFOS_RUNTIME.domRequest(),
|
||||
get data() {
|
||||
return initialized ? { network: fakeNetwork2 } : null;
|
||||
},
|
||||
get voice() {
|
||||
return initialized ? fakeVoice2 : null;
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
FFOS_RUNTIME.makeNavigatorShim('mozMobileConnections',
|
||||
mobileConnections, true);
|
||||
|
||||
}();
|
||||
16
gaia/tools/extensions/desktop-helper/content/data/lib/telephony.js
Executable file
16
gaia/tools/extensions/desktop-helper/content/data/lib/telephony.js
Executable file
|
|
@ -0,0 +1,16 @@
|
|||
!function() {
|
||||
|
||||
function debug(str) {
|
||||
//dump('mozTelephony: ' + str + '\n');
|
||||
}
|
||||
|
||||
FFOS_RUNTIME.makeNavigatorShim('mozTelephony', {
|
||||
calls: [],
|
||||
addEventListener: function() {
|
||||
debug('addEventListener');
|
||||
},
|
||||
removeEventListener: function() {
|
||||
debug('removeEventListener');
|
||||
}
|
||||
}, true);
|
||||
}();
|
||||
132
gaia/tools/extensions/desktop-helper/content/data/lib/wifi.js
Executable file
132
gaia/tools/extensions/desktop-helper/content/data/lib/wifi.js
Executable file
|
|
@ -0,0 +1,132 @@
|
|||
!function() {
|
||||
function debug(str) {
|
||||
//dump('wifi: ' + str + '\n');
|
||||
}
|
||||
|
||||
/**
|
||||
* fake network list, where each network object looks like:
|
||||
* {
|
||||
* ssid : SSID string (human-readable name)
|
||||
* bssid : network identifier string
|
||||
* security : array of strings (supported authentication methods)
|
||||
* relSignalStrength : 0-100 signal level (integer)
|
||||
* connected : boolean state
|
||||
* }
|
||||
*/
|
||||
|
||||
var fakeNetworks = [
|
||||
{
|
||||
ssid: 'Mozilla-G',
|
||||
bssid: 'xx:xx:xx:xx:xx:xx',
|
||||
security: ['WPA-EAP'],
|
||||
relSignalStrength: 67,
|
||||
connected: false
|
||||
},
|
||||
{
|
||||
ssid: 'Livebox 6752',
|
||||
bssid: 'xx:xx:xx:xx:xx:xx',
|
||||
security: ['WEP'],
|
||||
relSignalStrength: 32,
|
||||
connected: false
|
||||
},
|
||||
{
|
||||
ssid: 'Mozilla Guest',
|
||||
bssid: 'xx:xx:xx:xx:xx:xx',
|
||||
security: [],
|
||||
relSignalStrength: 98,
|
||||
connected: false
|
||||
},
|
||||
{
|
||||
ssid: 'Freebox 8953',
|
||||
bssid: 'xx:xx:xx:xx:xx:xx',
|
||||
security: ['WPA2-PSK'],
|
||||
relSignalStrength: 89,
|
||||
connected: false
|
||||
}
|
||||
];
|
||||
|
||||
FFOS_RUNTIME.makeNavigatorShim('mozWifiManager', {
|
||||
enabled: true,
|
||||
macAddress: 'xx:xx:xx:xx:xx:xx',
|
||||
|
||||
// enables/disables the wifi
|
||||
setEnabled: function fakeSetEnabled(bool) {
|
||||
var self = this;
|
||||
var request = { result: bool };
|
||||
|
||||
setTimeout(function() {
|
||||
if (request.onsuccess) {
|
||||
request.onsuccess();
|
||||
}
|
||||
if (bool) {
|
||||
self.onenabled();
|
||||
} else {
|
||||
self.ondisabled();
|
||||
}
|
||||
});
|
||||
|
||||
self.enabled = bool;
|
||||
return request;
|
||||
},
|
||||
|
||||
// returns a list of visible/known networks
|
||||
getNetworks: FFOS_RUNTIME.domRequest(fakeNetworks),
|
||||
getKnownNetworks: FFOS_RUNTIME.domRequest(fakeNetworks),
|
||||
|
||||
// selects a network
|
||||
associate: function fakeAssociate(network) {
|
||||
var self = this;
|
||||
var connection = { result: network };
|
||||
var networkEvent = { network: network };
|
||||
|
||||
setTimeout(function fakeConnecting() {
|
||||
self.connection.network = network;
|
||||
self.connection.status = 'connecting';
|
||||
self.onstatuschange(networkEvent);
|
||||
}, 0);
|
||||
|
||||
setTimeout(function fakeAssociated() {
|
||||
self.connection.network = network;
|
||||
self.connection.status = 'associated';
|
||||
self.onstatuschange(networkEvent);
|
||||
}, 1000);
|
||||
|
||||
setTimeout(function fakeConnected() {
|
||||
network.connected = true;
|
||||
self.connected = network;
|
||||
self.connection.network = network;
|
||||
self.connection.status = 'connected';
|
||||
self.onstatuschange(networkEvent);
|
||||
}, 2000);
|
||||
|
||||
return connection;
|
||||
},
|
||||
|
||||
// forgets a network (disconnect)
|
||||
forget: function fakeForget(network) {
|
||||
var self = this;
|
||||
var networkEvent = { network: network };
|
||||
|
||||
setTimeout(function() {
|
||||
network.connected = false;
|
||||
self.connected = null;
|
||||
self.connection.network = null;
|
||||
self.connection.status = 'disconnected';
|
||||
self.onstatuschange(networkEvent);
|
||||
}, 0);
|
||||
},
|
||||
|
||||
// event listeners
|
||||
onenabled: function(event) {},
|
||||
ondisabled: function(event) {},
|
||||
onstatuschange: function(event) {},
|
||||
|
||||
// returns a network object for the currently connected network (if any)
|
||||
connected: null,
|
||||
|
||||
connection: {
|
||||
status: 'disconnected',
|
||||
network: null
|
||||
}
|
||||
}, true);
|
||||
}();
|
||||
30
gaia/tools/extensions/desktop-helper/install.rdf
Executable file
30
gaia/tools/extensions/desktop-helper/install.rdf
Executable file
|
|
@ -0,0 +1,30 @@
|
|||
<?xml version="1.0"?>
|
||||
|
||||
<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:em="http://www.mozilla.org/2004/em-rdf#">
|
||||
|
||||
<Description about="urn:mozilla:install-manifest">
|
||||
<em:id>desktop-helper@gaiamobile.org</em:id>
|
||||
<em:name>desktop-helper.js</em:name>
|
||||
<em:version>0.1</em:version>
|
||||
<em:targetApplication>
|
||||
<Description>
|
||||
<em:id>toolkit@mozilla.org</em:id>
|
||||
<em:minVersion>6.0</em:minVersion>
|
||||
<em:maxVersion>*</em:maxVersion>
|
||||
</Description>
|
||||
</em:targetApplication>
|
||||
<em:targetApplication>
|
||||
<Description>
|
||||
<em:id>{ec8030f7-c20a-464f-9b0e-13a3a9e97384}</em:id> <!-- Firefox -->
|
||||
<em:minVersion>6.0</em:minVersion>
|
||||
<em:maxVersion>*</em:maxVersion>
|
||||
</Description>
|
||||
</em:targetApplication>
|
||||
<em:unpack>true</em:unpack>
|
||||
<em:bootstrap>true</em:bootstrap>
|
||||
<em:creator>Kevin Grandon</em:creator>
|
||||
<em:description>Shims to make gaia work on top of Xulrunner runtime</em:description>
|
||||
<em:homepageURL>https://github.com/mozilla-b2g/gaia/</em:homepageURL>
|
||||
</Description>
|
||||
</RDF>
|
||||
1
gaia/tools/extensions/desktop-helper@gaiamobile.org
Executable file
1
gaia/tools/extensions/desktop-helper@gaiamobile.org
Executable file
|
|
@ -0,0 +1 @@
|
|||
desktop-helper
|
||||
8
gaia/tools/extensions/gaia-build@gaiamobile.org/Makefile
Executable file
8
gaia/tools/extensions/gaia-build@gaiamobile.org/Makefile
Executable file
|
|
@ -0,0 +1,8 @@
|
|||
xpi:
|
||||
zip gaia-build@gaiamobile.org.xpi -r bootstrap.js install.rdf chrome.manifest content/
|
||||
|
||||
run:
|
||||
mkdir -p profile/extensions/
|
||||
echo "../../" > profile/extensions/gaia-build@gaiamobile.org
|
||||
echo "user_pref(\"extensions.autoDisableScopes\", 14);" > profile/user.js
|
||||
$(FIREFOX) -profile $(shell pwd)/profile -no-remote about:gaia
|
||||
68
gaia/tools/extensions/gaia-build@gaiamobile.org/bootstrap.js
vendored
Executable file
68
gaia/tools/extensions/gaia-build@gaiamobile.org/bootstrap.js
vendored
Executable file
|
|
@ -0,0 +1,68 @@
|
|||
'use strict';
|
||||
|
||||
const CC = Components.Constructor;
|
||||
const Cc = Components.classes;
|
||||
const Ci = Components.interfaces;
|
||||
const Cu = Components.utils;
|
||||
const Cm = Components.manager.QueryInterface(Ci.nsIComponentRegistrar);
|
||||
|
||||
Cu.import('resource://gre/modules/XPCOMUtils.jsm');
|
||||
Cu.import('resource://gre/modules/Services.jsm');
|
||||
|
||||
// Register about:gaia page
|
||||
let classID = Components.ID('{58d667ee-f9e8-4545-841c-927d86906620}');
|
||||
|
||||
function AboutGaia() {}
|
||||
AboutGaia.prototype = {
|
||||
classID: classID,
|
||||
QueryInterface: XPCOMUtils.generateQI([Ci.nsIAboutModule]),
|
||||
|
||||
getURIFlags: function(aURI) {
|
||||
return Ci.nsIAboutModule.ALLOW_SCRIPT;
|
||||
},
|
||||
|
||||
newChannel: function(aURI, aLoadInfo) {
|
||||
let spec = 'chrome://gaia-build/content/aboutGaia.html';
|
||||
let uri = Services.io.newURI(spec, null, null);
|
||||
let channel = Services.io.newChannelFromURIWithLoadInfo(uri, aLoadInfo);
|
||||
channel.originalURI = aURI;
|
||||
return channel;
|
||||
}
|
||||
};
|
||||
|
||||
function registerAbout() {
|
||||
let contract = '@mozilla.org/network/protocol/about;1?what=gaia';
|
||||
let instance = null;
|
||||
let newFactory = {
|
||||
createInstance: function(outer, iid) {
|
||||
if (outer)
|
||||
throw Components.results.NS_ERROR_NO_AGGREGATION;
|
||||
if (instance === null)
|
||||
instance = new AboutGaia();
|
||||
return instance;
|
||||
},
|
||||
lockFactory: function(aLock) {
|
||||
throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
|
||||
},
|
||||
QueryInterface: XPCOMUtils.generateQI([Ci.nsIFactory])
|
||||
};
|
||||
Cm.registerFactory(classID, '', contract, newFactory);
|
||||
}
|
||||
|
||||
function startup(data, reason) {
|
||||
try {
|
||||
registerAbout();
|
||||
} catch (e) {
|
||||
Cu.reportError(e + '\n' + e.stack);
|
||||
}
|
||||
}
|
||||
|
||||
function shutdown(data, reason) {
|
||||
}
|
||||
|
||||
function install(data, reason) {
|
||||
}
|
||||
|
||||
function uninstall(data, reason) {
|
||||
}
|
||||
|
||||
1
gaia/tools/extensions/gaia-build@gaiamobile.org/chrome.manifest
Executable file
1
gaia/tools/extensions/gaia-build@gaiamobile.org/chrome.manifest
Executable file
|
|
@ -0,0 +1 @@
|
|||
content gaia-build content/
|
||||
410
gaia/tools/extensions/gaia-build@gaiamobile.org/content/aboutGaia.css
Executable file
410
gaia/tools/extensions/gaia-build@gaiamobile.org/content/aboutGaia.css
Executable file
|
|
@ -0,0 +1,410 @@
|
|||
body {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
select, button {
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
#tools > label {
|
||||
display: block;
|
||||
}
|
||||
|
||||
#available-apps > li {
|
||||
display: inline-block;
|
||||
margin: 0.2rem;
|
||||
}
|
||||
|
||||
.page > ul {
|
||||
padding-left: 0.2rem;
|
||||
font-size: 0.7em;
|
||||
}
|
||||
|
||||
.page > ul > li {
|
||||
display: inline-block;
|
||||
margin: 0.2rem;
|
||||
}
|
||||
|
||||
.page > .container > div {
|
||||
font-size: 0.7em;
|
||||
display: inline-block;
|
||||
margin: 0.2rem;
|
||||
width: 3rem;
|
||||
height: 3rem;
|
||||
overflow: hidden;
|
||||
background-color: white;
|
||||
border: 1px solid black;
|
||||
border-radius: 0.4rem;
|
||||
word-wrap:break-word;
|
||||
}
|
||||
|
||||
#available-apps div.app {
|
||||
display: inline-block;
|
||||
margin: 0.2rem;
|
||||
padding: 0.2rem;
|
||||
background-color: white;
|
||||
border: 1px solid black;
|
||||
border-radius: 0.4rem;
|
||||
}
|
||||
|
||||
#available-apps div.app[data-source="external"] {
|
||||
background-color: #fcaf3e;
|
||||
}
|
||||
|
||||
#available-apps div.app:hover {
|
||||
background-color: #204a87;
|
||||
color: white;
|
||||
}
|
||||
|
||||
#available-apps {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
#main {
|
||||
flex: 1;
|
||||
padding: 2rem;
|
||||
}
|
||||
|
||||
#tools {
|
||||
width: 18rem;
|
||||
border-right: 1px solid black;
|
||||
padding: 2rem;
|
||||
}
|
||||
|
||||
#content {
|
||||
display: flex;
|
||||
flex-flow: row;
|
||||
}
|
||||
|
||||
#notification {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.page {
|
||||
width: 15rem;
|
||||
height: 25rem;
|
||||
background-color: rgba(0,0,0,0.2);
|
||||
margin: 1rem;
|
||||
}
|
||||
|
||||
.close-button {
|
||||
display: block;
|
||||
float: right;
|
||||
width: 1.2rem;
|
||||
height: 1.2rem;
|
||||
line-height: 1.2rem;
|
||||
font-size: 0.8em;
|
||||
margin: 0.5rem;
|
||||
text-align: center;
|
||||
border-radius: 50%;
|
||||
border: 1px solid black;
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
#add-page {
|
||||
font-size: 5em;
|
||||
text-align: center;
|
||||
line-height: 25rem;
|
||||
}
|
||||
|
||||
.page > input[type="radio"], #dock > input[type="radio"] {
|
||||
position: absolute;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.page > input[type="radio"] + label, #dock > input[type="radio"] + label {
|
||||
opacity: 0.1;
|
||||
display: block;
|
||||
height: 0;
|
||||
margin-left: -1.5rem;
|
||||
margin-top:1.2rem;
|
||||
transform: rotate(-45deg);
|
||||
width: 4rem;
|
||||
line-height: 1.5rem;
|
||||
border-bottom: 1.5rem solid red;
|
||||
border-left: 1.5rem solid transparent;
|
||||
border-right: 1.5rem solid transparent;
|
||||
text-align: center;
|
||||
font-size: 0.9em;
|
||||
color: white;
|
||||
transition: opacity 0.5s;
|
||||
}
|
||||
|
||||
#dock > input[type="radio"] + label {
|
||||
padding-top: 1.3rem;
|
||||
margin-left:-2rem;
|
||||
}
|
||||
|
||||
.page > input[type="radio"]:checked + label,
|
||||
#dock > input[type="radio"]:checked + label {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.page > input[type="radio"]:checked + label:after {
|
||||
content: "selected";
|
||||
}
|
||||
|
||||
#dock {
|
||||
margin: 1rem;
|
||||
width: 24rem;
|
||||
height: 5rem;
|
||||
background-color: grey;
|
||||
}
|
||||
|
||||
#dock > .container {
|
||||
margin-top: -2rem;
|
||||
margin-left: 3rem;
|
||||
}
|
||||
|
||||
.page > .container {
|
||||
margin-top: 2rem;
|
||||
}
|
||||
|
||||
#grid {
|
||||
overflow-x: scroll;
|
||||
|
||||
display: -moz-box;
|
||||
-moz-box-orient: horizontal;
|
||||
-moz-box-pack: center;
|
||||
-moz-box-align: center;
|
||||
|
||||
box-orient: horizontal;
|
||||
box-pack: center;
|
||||
box-align: center;
|
||||
}
|
||||
|
||||
div.lightbox {
|
||||
display: none;
|
||||
position: fixed;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
div.lightbox:target {
|
||||
display: table;
|
||||
}
|
||||
|
||||
div.lightbox .lightbox-background {
|
||||
display: table-cell;
|
||||
padding: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
div.lightbox .lightbox-background .lightbox-container {
|
||||
display: block;
|
||||
margin: auto;
|
||||
padding: 2rem;
|
||||
background-color: white;
|
||||
height: 25rem;
|
||||
position: relative;
|
||||
overflow: auto;
|
||||
border: 1px #000000 solid;
|
||||
border-radius: 0.5rem;
|
||||
text-align: justify;
|
||||
font-size: 1.2em;
|
||||
}
|
||||
|
||||
div.lightbox .lightbox-background .closemsg {
|
||||
display: block;
|
||||
margin: auto;
|
||||
height: 0;
|
||||
overflow: visible;
|
||||
text-align: right;
|
||||
z-index: 5001;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
div.lightbox .lightbox-background .closemsg, div.lightbox .lightbox-background .lightbox-container {
|
||||
width: 50rem;
|
||||
}
|
||||
|
||||
.closemsg::after {
|
||||
content: "\00D7";
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
right: -2rem;
|
||||
top: -1rem;
|
||||
z-index: 5002;
|
||||
color: #ffffff;
|
||||
border: 1px #ffffff solid;
|
||||
border-radius: 1rem;
|
||||
width: 2rem;
|
||||
height: 2rem;
|
||||
line-height: 1.8rem;
|
||||
text-align: center;
|
||||
margin: 0;
|
||||
background-color: #000000;
|
||||
font-weight: bold;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.closemsg.hidden:after {
|
||||
content: none;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.closemsg::before {
|
||||
content: "";
|
||||
display: block;
|
||||
position: fixed;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: #000000;
|
||||
opacity: 0.85;
|
||||
}
|
||||
|
||||
.lightbox-buttons {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
}
|
||||
|
||||
.lightbox-buttons > a {
|
||||
display: inline-block;
|
||||
margin: 0.5rem;
|
||||
border-radius: 0.5rem;
|
||||
border: 1px solid black;
|
||||
padding: 0.5rem;
|
||||
text-decoration: none;
|
||||
color: black;
|
||||
}
|
||||
|
||||
#gaia-path-lightbox .lightbox-container {
|
||||
text-align: center;
|
||||
line-height: 25rem;
|
||||
}
|
||||
|
||||
#marketplace-search-result .lightbox-container {
|
||||
background-color: #BEBEBE;
|
||||
}
|
||||
|
||||
#reset-gaia {
|
||||
font-size: 0.8em;
|
||||
color: blue;
|
||||
text-decoration: underline;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.tool-section {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.marketplace-app-container {
|
||||
background-color: white;
|
||||
margin: 1rem;
|
||||
border-radius: 0.5rem;
|
||||
position: relative;
|
||||
height: 8rem;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.marketplace-app-container:hover {
|
||||
box-shadow: 0 0 0.5rem black;
|
||||
}
|
||||
|
||||
.marketplace-app-container > * {
|
||||
position: absolute;
|
||||
left: 1rem;
|
||||
top: 1rem;
|
||||
}
|
||||
|
||||
.marketplace-app-icon {
|
||||
left: 1rem;
|
||||
top: 1rem;
|
||||
}
|
||||
|
||||
.marketplace-app-ratings {
|
||||
top: 5rem;
|
||||
color: #3388C1;
|
||||
font-size: 1.1em;
|
||||
}
|
||||
|
||||
.marketplace-app-review {
|
||||
left: 6rem;
|
||||
top: 5.5rem;
|
||||
font-size: 0.8em;
|
||||
color: #3388C1;
|
||||
}
|
||||
|
||||
.marketplace-app-name {
|
||||
left: 6rem;
|
||||
top: 1rem;
|
||||
color: #333333;
|
||||
font-family: "Fira Sans","Open Sans","Helvetica Neue",Arial,sans-serif;
|
||||
font-size: 1.4em;
|
||||
}
|
||||
|
||||
.marketplace-app-author {
|
||||
left: 6rem;
|
||||
top: 3.5rem;
|
||||
font-size: 0.8em;
|
||||
color: #A7A7A7;
|
||||
}
|
||||
|
||||
.noted {
|
||||
font-size: 0.8em;
|
||||
margin: 0.5rem;
|
||||
}
|
||||
|
||||
.marketplace-app-type {
|
||||
position: relative;
|
||||
float: right;
|
||||
margin-right: 2rem;
|
||||
width: 10rem;
|
||||
height: 1.5rem;
|
||||
line-height: 1.5rem;
|
||||
border-radius: 0.5rem;
|
||||
font-size: 0.8em;
|
||||
text-align: center;
|
||||
color: #FFF;
|
||||
}
|
||||
|
||||
.marketplace-app-type[data-is-packaged="true"] {
|
||||
background-color: #204a78;
|
||||
}
|
||||
|
||||
.marketplace-app-type[data-is-packaged="true"]:before {
|
||||
content: "Packaged app";
|
||||
background-color: #204a78;
|
||||
}
|
||||
|
||||
.lightbox-title {
|
||||
font-size: 2em;
|
||||
border-bottom: 1px solid black;
|
||||
text-transform: capitalize;
|
||||
}
|
||||
|
||||
.lightbox-content {
|
||||
margin: 2rem;
|
||||
}
|
||||
|
||||
.lightbox-content a {
|
||||
color: blue;
|
||||
text-decoration: underline;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.operator input {
|
||||
margin: 0.2rem;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.description {
|
||||
margin: 0.6rem;
|
||||
font-size: 0.8em;
|
||||
}
|
||||
|
||||
#certain-mcc-mnc:not(:checked) ~ #operators-options {
|
||||
display: none
|
||||
}
|
||||
87
gaia/tools/extensions/gaia-build@gaiamobile.org/content/aboutGaia.html
Executable file
87
gaia/tools/extensions/gaia-build@gaiamobile.org/content/aboutGaia.html
Executable file
|
|
@ -0,0 +1,87 @@
|
|||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Homescreen customization</title>
|
||||
<link rel="stylesheet" href="chrome://gaia-build/content/aboutGaia.css">
|
||||
<script type="application/javascript;version=1.8" src="chrome://gaia-build/content/gaia.js"></script>
|
||||
<script type="application/javascript;version=1.8" src="chrome://gaia-build/content/aboutGaia.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="content">
|
||||
<div id="tools">
|
||||
<section class="tool-section">
|
||||
<div id="builds-label">Build config: </div>
|
||||
<select name="" id="gaia-builds"></select>
|
||||
</section>
|
||||
<section class="tool-section">
|
||||
<div id="available-apps-label">Available apps</div>
|
||||
<div id="available-apps"></div>
|
||||
</section>
|
||||
<section class="tool-section">
|
||||
<div id="marketplace-label">Search from Marketplace</div>
|
||||
<form>
|
||||
<input id="marketplace-keyword" type="text" placeholder="keyword"></input>
|
||||
<button type="submit" id="marketplace-search">Search</button>
|
||||
</form>
|
||||
</section>
|
||||
<button id="launch">Preview</button><button id="save">Save</button>
|
||||
<a id="reset-gaia">Reset gaia path</a>
|
||||
</div>
|
||||
<div id="main">
|
||||
<div id="notification">Your app will be added into selected page.</div>
|
||||
<div id="grid">
|
||||
<div class="page" id="add-page">+</div>
|
||||
</div>
|
||||
<div id="dock" class="page">
|
||||
<input type="radio" name="page-selection" id="page0" checked="checked">
|
||||
<label for="page0"></label>
|
||||
<div class="container"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="lightbox" id="options">
|
||||
<div class="lightbox-background">
|
||||
<a href="#" class="closemsg"></a>
|
||||
<div class="lightbox-container">
|
||||
<div class="lightbox-title" id="options-title"></div>
|
||||
<div id="operators-disabled-desc" class="description hidden">
|
||||
Built-in apps can't be disabled for certain operators
|
||||
</div>
|
||||
<div class="lightbox-content hidden" id="options-content">
|
||||
<input id="certain-mcc-mnc" type="checkbox">
|
||||
<label for="certain-mcc-mnc">Available only for certain operators</label>
|
||||
<hr>
|
||||
<div id="operators-options">
|
||||
<div id="operators"></div>
|
||||
<div class="description">Use commas to split different mcc/mnc pairs, e.g., "310-410, 310-260" (without double quotes)</div>
|
||||
<a id="add-operator">Add operator</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="lightbox-buttons">
|
||||
<a href="#">Close</a>
|
||||
<a href="#" id="options-remove">Remove</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="lightbox" id="gaia-path-lightbox">
|
||||
<div class="lightbox-background">
|
||||
<a class="closemsg hidden"></a>
|
||||
<div class="lightbox-container">
|
||||
Gaia path is necessary.
|
||||
<button id="select-gaia">Select</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="lightbox" id="marketplace-search-result">
|
||||
<div class="lightbox-background">
|
||||
<a href="#" class="closemsg"></a>
|
||||
<div class="lightbox-container">
|
||||
<div class="noted">Noted: Packaged app can't be executed in Browser, you need to verify it in b2g-desktop or device.</div>
|
||||
<div id="marketplace-search-container"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
862
gaia/tools/extensions/gaia-build@gaiamobile.org/content/aboutGaia.js
Executable file
862
gaia/tools/extensions/gaia-build@gaiamobile.org/content/aboutGaia.js
Executable file
|
|
@ -0,0 +1,862 @@
|
|||
'use strict';
|
||||
|
||||
const CC = Components.Constructor;
|
||||
const Cc = Components.classes;
|
||||
const Ci = Components.interfaces;
|
||||
const Cu = Components.utils;
|
||||
|
||||
const RE_MCCMNC = /^\d{3}-\d{3}$/;
|
||||
const PAGE_MAX_APP = 16;
|
||||
const DOCK_MAX_APP = 5;
|
||||
const MARKETPLACE_INSTALL_ORIGIN = 'https://marketplace.firefox.com';
|
||||
const EXTERNAL_APP_DIR = 'external-apps';
|
||||
const MARKETPLACE_SEARCH_HASH = '#marketplace-search-result';
|
||||
const MARKETPLACE_SEARCH_BASEURL = 'https://marketplace.firefox.com/api/v1/' +
|
||||
'apps/search/?device=firefoxos&q=';
|
||||
|
||||
Cu.import('resource://gre/modules/Services.jsm');
|
||||
Cu.import('resource://gre/modules/FileUtils.jsm');
|
||||
|
||||
const GAIA_DIR_PREF_NAME = 'extensions.gaia.dir';
|
||||
const DEFAULT_PAGE = 2;
|
||||
var utils, variant;
|
||||
|
||||
var Homescreen = {
|
||||
operators: {},
|
||||
|
||||
init: function hs_init() {
|
||||
var self = this;
|
||||
this.addPageButton = document.getElementById('add-page');
|
||||
this.grid = document.getElementById('grid');
|
||||
this.dock = document.getElementById('dock');
|
||||
this.apps = [];
|
||||
this.searchResultContainer =
|
||||
document.getElementById('marketplace-search-container');
|
||||
this.addPageButton.addEventListener('click', this.addPage.bind(this));
|
||||
this.operatorsElement = document.getElementById('operators');
|
||||
|
||||
if (this.grid.children.length <= 1) {
|
||||
for (var i = 0; i < DEFAULT_PAGE; i++) {
|
||||
this.addPage();
|
||||
}
|
||||
}
|
||||
|
||||
var gaiaBuilds = document.getElementById('gaia-builds');
|
||||
gaiaBuilds.addEventListener('change', function(evt) {
|
||||
var selected = gaiaBuilds.options[gaiaBuilds.options.selectedIndex];
|
||||
self.selectBuild(selected.value);
|
||||
});
|
||||
document.getElementById('save').addEventListener('click',
|
||||
this.save.bind(this));
|
||||
document.getElementById('select-gaia').addEventListener('click',
|
||||
this.selectGaiaFolder.bind(this));
|
||||
document.getElementById('options-remove').addEventListener('click',
|
||||
this.removeApp.bind(this));
|
||||
document.getElementById('reset-gaia').addEventListener('click',
|
||||
resetGaiaPath);
|
||||
document.getElementById('marketplace-keyword').addEventListener('click',
|
||||
function(evt) {
|
||||
this.select();
|
||||
});
|
||||
document.getElementById('marketplace-search').addEventListener('click',
|
||||
function(evt) {
|
||||
var keyword = document.getElementById('marketplace-keyword').value;
|
||||
if (keyword) {
|
||||
self.search(keyword);
|
||||
} else {
|
||||
alert('Please specify a keyword.');
|
||||
}
|
||||
});
|
||||
|
||||
function createOperator() {
|
||||
var div = document.createElement('div');
|
||||
div.classList.add('operator');
|
||||
self.createOperatorEditor(div);
|
||||
self.operatorsElement.appendChild(div);
|
||||
}
|
||||
|
||||
document.getElementById('add-operator').addEventListener('click',
|
||||
createOperator);
|
||||
document.getElementById('certain-mcc-mnc').addEventListener('change',
|
||||
function(evt) {
|
||||
var app = self.apps[self.editingAppElement.dataset.name];
|
||||
if (evt.target.checked) {
|
||||
app.forOperators = true;
|
||||
} else {
|
||||
app.forOperators = false;
|
||||
}
|
||||
});
|
||||
|
||||
window.addEventListener('hashchange', function(evt) {
|
||||
var oldURL = new URL(evt.oldURL);
|
||||
if (oldURL.hash === MARKETPLACE_SEARCH_HASH) {
|
||||
delete self.searchResult;
|
||||
}
|
||||
});
|
||||
createOperator();
|
||||
},
|
||||
|
||||
search: function hs_search(keyword) {
|
||||
var {downloadJSON} = utils;
|
||||
this.searchResultContainer.innerHTML = 'Loading...';
|
||||
window.location.hash = MARKETPLACE_SEARCH_HASH;
|
||||
var self = this;
|
||||
var url = MARKETPLACE_SEARCH_BASEURL + encodeURI(keyword);
|
||||
downloadJSON(url, function(json) {
|
||||
if (json) {
|
||||
self.searchResult = json;
|
||||
self.showSearchResult(self.searchResult);
|
||||
} else {
|
||||
var msg = 'Marketplace search request failed';
|
||||
alert(msg);
|
||||
throw new Error(msg);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
showSearchResult: function hs_showSearchResult(json) {
|
||||
var result = this.searchResultContainer;
|
||||
while(result.firstChild) {
|
||||
result.removeChild(result.firstChild);
|
||||
}
|
||||
json.objects.forEach(function(obj, index) {
|
||||
result.appendChild(this.createSearchEntry(obj, index));
|
||||
}.bind(this));
|
||||
},
|
||||
|
||||
createSearchEntry: function hs_createSearchEntry(obj, index) {
|
||||
const FULL_STARS = 5;
|
||||
var container = document.createElement('a');
|
||||
container.classList.add('marketplace-app-container');
|
||||
|
||||
var elements = {
|
||||
'icon': 'img',
|
||||
'name': 'div',
|
||||
'author': 'div',
|
||||
'ratings': 'div',
|
||||
'review': 'div',
|
||||
'type': 'div'
|
||||
};
|
||||
for (var key in elements) {
|
||||
var el = document.createElement(elements[key]);
|
||||
el.classList.add('marketplace-app-' + key);
|
||||
container.appendChild(el);
|
||||
elements[key] = el;
|
||||
}
|
||||
|
||||
elements.name.textContent = obj.name;
|
||||
elements.author.textContent = obj.author;
|
||||
elements.icon.src = obj.icons['64'];
|
||||
var ratings = Math.round(obj.ratings.average);
|
||||
elements.ratings.innerHTML = new Array(ratings + 1).join('★');
|
||||
var len = elements.ratings.innerHTML.length;
|
||||
elements.ratings.innerHTML += new Array(FULL_STARS - len + 1).join('☆');
|
||||
elements.ratings.dataset.average = obj.ratings.average;
|
||||
elements.review.textContent = obj.ratings.count + ' reviews';
|
||||
elements.type.dataset.isPackaged = obj.is_packaged;
|
||||
|
||||
container.dataset.index = index;
|
||||
container.addEventListener('click', this.addAppFromMarketplace.bind(this));
|
||||
|
||||
return container;
|
||||
},
|
||||
|
||||
selectBuild: function hs_selectBuild(buildname) {
|
||||
if (!this.gaiaConfig) {
|
||||
throw new Error('Homescreen.gaiaConfig doesn\'t exist');
|
||||
}
|
||||
var c = this.gaiaConfig;
|
||||
var {getAppsByList} = utils;
|
||||
var apps = getAppsByList(this.buildConfig[buildname].content,
|
||||
c.GAIA_DIR, c.GAIA_DISTRIBUTION_DIR);
|
||||
this.setApps(apps);
|
||||
},
|
||||
|
||||
selectGaiaFolder: function hs_selectGaiaFolder() {
|
||||
this.reset();
|
||||
let path = this.promptFolder();
|
||||
if (!path) {
|
||||
return;
|
||||
}
|
||||
Services.prefs.setCharPref(GAIA_DIR_PREF_NAME, path);
|
||||
},
|
||||
|
||||
promptFolder: function hs_promptFolder() {
|
||||
let fp = Cc['@mozilla.org/filepicker;1'].createInstance(Ci.nsIFilePicker);
|
||||
fp.init(window, 'Select Gaia directory', Ci.nsIFilePicker.modeGetFolder);
|
||||
let res = fp.show();
|
||||
if (res != Ci.nsIFilePicker.returnCancel) {
|
||||
return fp.file.path;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
|
||||
reset: function hs_reset() {
|
||||
var availableApps = document.getElementById('available-apps');
|
||||
while (availableApps.firstChild) {
|
||||
availableApps.removeChild(availableApps.firstChild);
|
||||
}
|
||||
|
||||
var pages = document.getElementsByClassName('container');
|
||||
Array.prototype.forEach.call(pages, function(page, index) {
|
||||
while (page.firstChild) {
|
||||
page.removeChild(page.firstChild);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
resetBuilds: function hs_resetBuilds() {
|
||||
var builds = document.getElementById('gaia-builds');
|
||||
Array.prototype.forEach.call(builds.children, function(option) {
|
||||
option.remove();
|
||||
});
|
||||
},
|
||||
|
||||
setApps: function hs_setApps(apps) {
|
||||
this.installedApps = {};
|
||||
var availableApps = document.getElementById('available-apps');
|
||||
this.apps = apps;
|
||||
this.reset();
|
||||
|
||||
for (var app in apps) {
|
||||
// app with role in manifest means it has specific purpose and those apps
|
||||
// usually doesn't show on homescreen. e.g., role of bluetooth, pdf.js &
|
||||
// ringtones is "system" which means they are executed by another apps but
|
||||
// doesn't show on homescreen, but those apps are essential for gaia .
|
||||
if (apps[app].manifest.role) {
|
||||
this.installedApps[app] = apps[app];
|
||||
continue;
|
||||
} else {
|
||||
this.createAppElements(apps[app]).forEach(function(el) {
|
||||
availableApps.appendChild(el);
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
showOptions: function hs_showOptions(evt) {
|
||||
var self = this;
|
||||
this.editingAppElement = evt.target;
|
||||
var operatorsCheckbox = document.getElementById('certain-mcc-mnc');
|
||||
var operatorsOptions = document.getElementById('options-content');
|
||||
var operatorsDisabledMsg = document.getElementById('operators-disabled-desc');
|
||||
var name = this.editingAppElement.dataset.name;
|
||||
document.getElementById('options-title').innerHTML = evt.target.innerHTML;
|
||||
window.location.hash = '#options';
|
||||
|
||||
function updateOperators(name) {
|
||||
var inputs =
|
||||
document.querySelectorAll('.operator input[type="checkbox"]');
|
||||
Array.prototype.forEach.call(inputs, function(input) {
|
||||
var operatorId = input.dataset.operatorId;
|
||||
input.checked = self.operators[operatorId].apps[name] ? true : false;
|
||||
});
|
||||
}
|
||||
|
||||
if (!this.apps[name]) {
|
||||
throw new Error('App doesn\'t exist: ' + name);
|
||||
} else if (this.apps[name].metadata &&
|
||||
this.apps[name].metadata.source === 'external') {
|
||||
updateOperators(name);
|
||||
operatorsCheckbox.checked = this.apps[name].forOperators || false;
|
||||
operatorsOptions.classList.remove('hidden');
|
||||
operatorsDisabledMsg.classList.add('hidden');
|
||||
operatorsCheckbox.disabled = false;
|
||||
} else {
|
||||
operatorsDisabledMsg.classList.remove('hidden');
|
||||
operatorsOptions.classList.add('hidden');
|
||||
operatorsCheckbox.disabled = true;
|
||||
}
|
||||
},
|
||||
|
||||
updateOperators: function hs_updateOperators(operator, appName) {
|
||||
var prevOperatorId;
|
||||
if (this.editingOperator) {
|
||||
prevOperatorId = this.editingOperator.id;
|
||||
} else {
|
||||
prevOperatorId = operator.id;
|
||||
}
|
||||
|
||||
if (this.operators[prevOperatorId] && prevOperatorId !== operator.id) {
|
||||
operator.apps = this.operators[prevOperatorId].apps;
|
||||
delete this.operators[prevOperatorId];
|
||||
}
|
||||
this.operators[operator.id] = operator;
|
||||
|
||||
if (appName) {
|
||||
operator.apps[appName] = true;
|
||||
}
|
||||
},
|
||||
|
||||
getOperator: function hs_getOperator(name) {
|
||||
var operator;
|
||||
this.operators.every(function(opr) {
|
||||
if (opr.id === name) {
|
||||
operator = opr;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
return operator;
|
||||
},
|
||||
|
||||
convertToOperator: function hs_convertToOperator(el) {
|
||||
var operatorId = el.querySelector('[data-field="operatorId"]').value;
|
||||
var mccmnc = el.querySelector('[data-field="mccmnc"]').value;
|
||||
var formatError = false;
|
||||
var mccmncArray = mccmnc.split(',').map(function(item) {
|
||||
var pair = item.trim();
|
||||
formatError = !RE_MCCMNC.test(pair) || formatError;
|
||||
return pair;
|
||||
});
|
||||
if (formatError) {
|
||||
throw new Error('MCC/MNC format incorrect, both of MCC and MNC need ' +
|
||||
'3 digits like "310-410" (without double quotes)');
|
||||
}
|
||||
return {
|
||||
'id': operatorId,
|
||||
'mcc-mnc': mccmncArray,
|
||||
'apps': {}
|
||||
};
|
||||
},
|
||||
|
||||
switchOperatorMode: function hs_switchOperatorMode(operatorDiv, toEdit) {
|
||||
function removeAllChildren(element) {
|
||||
while (element.firstChild) {
|
||||
element.removeChild(element.firstChild);
|
||||
}
|
||||
}
|
||||
|
||||
if (toEdit) {
|
||||
var operatorId =
|
||||
operatorDiv.querySelector('input[type="checkbox"]').dataset.operatorId;
|
||||
var operator = this.operators[operatorId];
|
||||
this.editingOperator = operator;
|
||||
removeAllChildren(operatorDiv);
|
||||
this.createOperatorEditor(operatorDiv, operator);
|
||||
} else {
|
||||
var operator = this.convertToOperator(operatorDiv);
|
||||
removeAllChildren(operatorDiv);
|
||||
this.createOperatorView(operatorDiv, operator.id);
|
||||
delete this.editingOperator;
|
||||
}
|
||||
},
|
||||
|
||||
createOperatorView: function hs_createOperatorView(div, operatorId) {
|
||||
var {normalizeString} = utils;
|
||||
var self = this;
|
||||
var operator = this.operators[operatorId];
|
||||
|
||||
var checkbox = document.createElement('input');
|
||||
checkbox.dataset.operatorId = operatorId;
|
||||
checkbox.dataset.normalizedName = normalizeString(operatorId);
|
||||
checkbox.type = 'checkbox';
|
||||
checkbox.id = 'checkbox-' + checkbox.dataset.normalizedName;
|
||||
|
||||
var label = document.createElement('label');
|
||||
label.setAttribute('for', checkbox.id);
|
||||
label.innerHTML = operatorId;
|
||||
|
||||
var span = document.createElement('span');
|
||||
span.innerHTML = ' - ';
|
||||
var edit = document.createElement('a');
|
||||
edit.innerHTML = 'edit';
|
||||
|
||||
checkbox.checked = (operator &&
|
||||
operator.apps[this.editingAppElement.dataset.name]);
|
||||
|
||||
checkbox.addEventListener('change', function(evt) {
|
||||
var operator = self.operators[evt.target.dataset.operatorId];
|
||||
if (evt.target.checked) {
|
||||
operator.apps[self.editingAppElement.dataset.name] = true;
|
||||
} else {
|
||||
delete operator.apps[self.editingAppElement.dataset.name];
|
||||
}
|
||||
});
|
||||
|
||||
edit.addEventListener('click', function(evt) {
|
||||
self.switchOperatorMode(div, true);
|
||||
});
|
||||
|
||||
span.appendChild(edit);
|
||||
[checkbox, label, span].forEach(function(child) {
|
||||
div.appendChild(child);
|
||||
});
|
||||
},
|
||||
|
||||
createOperatorEditor: function hs_createOperatorEditor(div, operator) {
|
||||
var self = this;
|
||||
function createInput(field, placeholder) {
|
||||
var input = document.createElement('input');
|
||||
input.type = 'text';
|
||||
input.placeholder = placeholder;
|
||||
input.dataset.field = field;
|
||||
return input;
|
||||
}
|
||||
|
||||
var idInput = createInput('operatorId', 'Operator ID');
|
||||
var mccmncInput = createInput('mccmnc', 'mcc/mnc');
|
||||
var save = document.createElement('a');
|
||||
save.innerHTML = 'Save';
|
||||
save.addEventListener('click', function(evt) {
|
||||
var operatorDiv = evt.target.parentElement;
|
||||
try {
|
||||
var operator = self.convertToOperator(operatorDiv);
|
||||
self.updateOperators(operator, self.editingAppElement.dataset.name);
|
||||
self.switchOperatorMode(operatorDiv, false);
|
||||
} catch (e) {
|
||||
alert(e.message);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
if (operator) {
|
||||
idInput.value = operator.id;
|
||||
mccmncInput.value = operator['mcc-mnc'].join(', ');
|
||||
}
|
||||
|
||||
[idInput, mccmncInput, save].forEach(function(el) {
|
||||
div.appendChild(el);
|
||||
});
|
||||
},
|
||||
|
||||
removeApp: function hs_removeApp() {
|
||||
if (!this.editingAppElement) {
|
||||
throw new Error('You didn\'t select an app to edit');
|
||||
}
|
||||
var availableApps = document.getElementById('available-apps');
|
||||
availableApps.appendChild(this.createApp(this.editingAppElement.dataset));
|
||||
this.editingAppElement.remove();
|
||||
delete this.editingAppElement;
|
||||
|
||||
for (var key in this.operators) {
|
||||
delete this.operators[key].apps[this.editingAppElement.dataset.name];
|
||||
}
|
||||
|
||||
window.location.hash = '';
|
||||
},
|
||||
|
||||
getMetadata: function hs_getMetadata(entry, manifest, callback) {
|
||||
var {normalizeString} = utils;
|
||||
var name = normalizeString(entry.name)
|
||||
var metadata = {
|
||||
'name': name,
|
||||
'installOrigin': MARKETPLACE_INSTALL_ORIGIN,
|
||||
'manifestURL': entry.manifest_url,
|
||||
'source': 'external'
|
||||
};
|
||||
|
||||
if (entry.is_packaged) {
|
||||
metadata.origin = 'app://' + name;
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.open('HEAD', manifest.package_path, false);
|
||||
xhr.addEventListener('load', function() {
|
||||
if (xhr.status !== 200 && xhr.status !== 304) {
|
||||
console.warn('Get header of packaged app failed, Status Code: ' +
|
||||
xhr.status);
|
||||
callback(metadata);
|
||||
return;
|
||||
}
|
||||
|
||||
var etag = xhr.getResponseHeader('etag');
|
||||
if (etag) {
|
||||
metadata.packageEtag = etag;
|
||||
}
|
||||
callback(metadata);
|
||||
});
|
||||
xhr.send();
|
||||
} else {
|
||||
var url = new URL(entry.manifest_url);
|
||||
metadata.origin = url.origin;
|
||||
callback(metadata);
|
||||
}
|
||||
},
|
||||
|
||||
filterProperties: function hs_filterProperties(obj, names) {
|
||||
var newObj = {};
|
||||
for (var prop in obj) {
|
||||
if (names.indexOf(prop) >= 0) {
|
||||
newObj[prop] = obj[prop];
|
||||
}
|
||||
}
|
||||
return newObj;
|
||||
},
|
||||
|
||||
processAppDownload: function hs_processAppDownload(metadata, json) {
|
||||
var {getFile, ensureFolderExists} = utils;
|
||||
var {downloadApp} = variant;
|
||||
var filteredMetadata = this.filterProperties(metadata, ['origin',
|
||||
'manifestURL', 'installOrigin', 'etag', 'packageEtag']);
|
||||
var appPath = getFile(this.gaiaConfig.GAIA_DISTRIBUTION_DIR,
|
||||
EXTERNAL_APP_DIR, metadata.name);
|
||||
ensureFolderExists(appPath);
|
||||
downloadApp(json, filteredMetadata, appPath.path, function() {
|
||||
var {getApp} = utils;
|
||||
var app = getApp(EXTERNAL_APP_DIR, metadata.name,
|
||||
this.gaiaConfig.GAIA_DIR,
|
||||
this.gaiaConfig.GAIA_DISTRIBUTION_DIR);
|
||||
app.metadata = metadata;
|
||||
this.apps[metadata.name] = app;
|
||||
window.location.hash = '';
|
||||
}.bind(this));
|
||||
},
|
||||
|
||||
addAppFromMarketplace: function hs_addAppFromMarketplace(evt) {
|
||||
var self = this;
|
||||
var {normalizeString} = utils;
|
||||
var entry = this.searchResult.objects[evt.currentTarget.dataset.index];
|
||||
var name = normalizeString(entry.name);
|
||||
if (this.apps[name]) {
|
||||
alert(name + ' app already exists.');
|
||||
return;
|
||||
}
|
||||
this.searchResultContainer.innerHTML = 'Downloading...';
|
||||
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.open('GET', entry.manifest_url, false);
|
||||
xhr.addEventListener('load', function() {
|
||||
if (xhr.status !== 200 && xhr.status !== 304) {
|
||||
alert('Download Error, Status Code: ' + xhr.status);
|
||||
window.location.hash = '';
|
||||
return;
|
||||
}
|
||||
|
||||
var manifest = JSON.parse(xhr.responseText);
|
||||
self.getMetadata(entry, manifest, function(metadata) {
|
||||
var app = self.createApp({
|
||||
'name': metadata.name,
|
||||
'source': 'external'
|
||||
});
|
||||
document.getElementById('available-apps').appendChild(app);
|
||||
var etag = xhr.getResponseHeader('etag');
|
||||
if (etag) {
|
||||
metadata.etag = etag;
|
||||
}
|
||||
self.processAppDownload(metadata, manifest);
|
||||
});
|
||||
});
|
||||
xhr.send();
|
||||
},
|
||||
|
||||
addToPage: function hs_addToPage(evt) {
|
||||
var props = {};
|
||||
Object.keys(evt.target.dataset).forEach(function(key) {
|
||||
props[key] = evt.target.dataset[key];
|
||||
});
|
||||
var selectedPage =
|
||||
document.querySelector('input[type=radio]:checked ~ .container');
|
||||
var app = this.createApp(props, true);
|
||||
var max = selectedPage.parentElement.id === 'dock' ?
|
||||
DOCK_MAX_APP : PAGE_MAX_APP;
|
||||
if (selectedPage.children.length < max) {
|
||||
selectedPage.appendChild(app);
|
||||
evt.target.remove();
|
||||
} else {
|
||||
alert('can\'t add anymore');
|
||||
}
|
||||
},
|
||||
|
||||
createAppElements: function hs_createAppElements(app) {
|
||||
function logMissingIcon(app, entryPoint) {
|
||||
var name = app + (entryPoint ? ':' + entryPoint : '');
|
||||
console.warn('icons for ' + name + ' doesn\'t exist');
|
||||
}
|
||||
var elements = [];
|
||||
if (!app.manifest.icons && !app.manifest.entry_points) {
|
||||
logMissingIcon(app.name);
|
||||
} else if (!app.manifest.entry_points && app.manifest.icons) {
|
||||
elements.push(this.createApp({'name': app.name}));
|
||||
} else {
|
||||
for (var entryPoint in app.manifest.entry_points) {
|
||||
if (!app.manifest.entry_points[entryPoint].icons) {
|
||||
logMissingIcon(app.name, entryPoint);
|
||||
continue;
|
||||
}
|
||||
elements.push(this.createApp({
|
||||
'name': app.name,
|
||||
'entryPoint': entryPoint
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
return elements;
|
||||
},
|
||||
|
||||
createApp: function hs_createApp(props, inPage) {
|
||||
var div = document.createElement('div');
|
||||
div.classList.add('app');
|
||||
div.innerHTML = props.name;
|
||||
if (props.entryPoint) {
|
||||
div.innerHTML += ':' + props.entryPoint;
|
||||
}
|
||||
|
||||
Object.keys(props).forEach(function(key) {
|
||||
div.dataset[key] = props[key];
|
||||
});
|
||||
|
||||
if (!div.dataset.source) {
|
||||
div.dataset.source = 'builtin';
|
||||
}
|
||||
|
||||
if (!inPage) {
|
||||
div.addEventListener('click', this.addToPage.bind(this));
|
||||
} else {
|
||||
div.addEventListener('click', this.showOptions.bind(this));
|
||||
}
|
||||
return div;
|
||||
},
|
||||
|
||||
setBuildConfig: function hs_setBuildConfig(config) {
|
||||
this.reset();
|
||||
this.buildConfig = config;
|
||||
var buildsElement = document.getElementById('gaia-builds');
|
||||
for (var build in config) {
|
||||
var option = document.createElement('option');
|
||||
option.setAttribute('value', build);
|
||||
option.innerHTML = build;
|
||||
|
||||
if (build === 'production') {
|
||||
option.setAttribute('selected', 'selected');
|
||||
this.selectBuild(build);
|
||||
}
|
||||
buildsElement.appendChild(option);
|
||||
}
|
||||
},
|
||||
|
||||
addPage: function hs_addPage() {
|
||||
var self = this;
|
||||
var lastInput =
|
||||
document.querySelector('#grid > div:nth-last-child(2) > input');
|
||||
var lastId = lastInput ? parseInt(lastInput.id.substr('page'.length)) : 0;
|
||||
var page = document.createElement('div');
|
||||
page.classList.add('page');
|
||||
|
||||
var input = document.createElement('input');
|
||||
input.setAttribute('type', 'radio');
|
||||
input.setAttribute('name', 'page-selection');
|
||||
input.id = 'page' + (lastId + 1);
|
||||
|
||||
var label = document.createElement('label');
|
||||
label.setAttribute('for', input.id);
|
||||
|
||||
var closeBtn = document.createElement('div');
|
||||
closeBtn.classList.add('close-button');
|
||||
closeBtn.innerHTML = '×';
|
||||
|
||||
closeBtn.addEventListener('click', function(evt) {
|
||||
var targetPage = evt.target.parentNode;
|
||||
var targetInput = targetPage.getElementsByTagName('input')[0];
|
||||
if (targetInput.checked) {
|
||||
document.getElementById('page0').checked = true;
|
||||
}
|
||||
|
||||
var apps = targetPage.querySelectorAll('.app');
|
||||
var availableApps = document.getElementById('available-apps');
|
||||
Array.prototype.forEach.call(apps, function(app) {
|
||||
var {name, entryPoint} = app.dataset;
|
||||
var restoredApp = self.createApp({
|
||||
'name': name, 'entryPoint': entryPoint});
|
||||
availableApps.appendChild(restoredApp);
|
||||
});
|
||||
|
||||
targetPage.remove();
|
||||
});
|
||||
|
||||
var container = document.createElement('div');
|
||||
container.classList.add('container');
|
||||
|
||||
page.appendChild(closeBtn);
|
||||
page.appendChild(input);
|
||||
page.appendChild(label);
|
||||
page.appendChild(container);
|
||||
|
||||
this.grid.insertBefore(page, this.addPageButton);
|
||||
},
|
||||
|
||||
save: function hs_save(evt) {
|
||||
var self = this;
|
||||
if (!utils) {
|
||||
throw new Error('utils module doesn\'t exist');
|
||||
}
|
||||
|
||||
var variant = {
|
||||
'apps': {},
|
||||
'operators': []
|
||||
};
|
||||
|
||||
var operators = JSON.parse(JSON.stringify(this.operators));
|
||||
var distDir = utils.getFile(this.gaiaConfig.GAIA_DISTRIBUTION_DIR);
|
||||
utils.ensureFolderExists(distDir);
|
||||
|
||||
var pages = document.querySelectorAll('div.page > div.container');
|
||||
var grid = {
|
||||
homescreens: [],
|
||||
search_page: { enabled: true }
|
||||
};
|
||||
|
||||
Array.prototype.forEach.call(pages, function(page, index) {
|
||||
var pageArray = [];
|
||||
Array.prototype.forEach.call(page.children, function(el, position) {
|
||||
var parent;
|
||||
var {name, entryPoint} = el.dataset;
|
||||
|
||||
if (!self.apps[name]) {
|
||||
throw new Error('App doesn\'t exist: ' + name);
|
||||
}
|
||||
|
||||
if (self.apps[name].forOperators) {
|
||||
for (var key in self.operators) {
|
||||
if (self.operators[key].apps[name]) {
|
||||
operators[key].apps[name] = {
|
||||
'id': name,
|
||||
'screen': index,
|
||||
'position': position
|
||||
};
|
||||
variant.apps[name] =
|
||||
self.filterProperties(self.apps[name].metadata,
|
||||
['origin', 'manifestURL', 'installOrigin', 'etag']
|
||||
);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
var f = utils.getFile(self.apps[name].path);
|
||||
parent = f.parent.leafName;
|
||||
|
||||
var appArray = [parent, name];
|
||||
if (entryPoint) {
|
||||
appArray.push(entryPoint);
|
||||
}
|
||||
pageArray.push(appArray);
|
||||
|
||||
self.installedApps[name] = self.apps[name];
|
||||
});
|
||||
|
||||
if (index !== pages.length - 1) {
|
||||
grid.homescreens.push(pageArray);
|
||||
} else {
|
||||
// insert apps in dock to first one element of pageArray
|
||||
grid.homescreens.splice(0, 0, pageArray);
|
||||
}
|
||||
});
|
||||
|
||||
var appslistFile = distDir.clone();
|
||||
appslistFile.append('apps.list');
|
||||
|
||||
var applistArray = [];
|
||||
for (var key in self.installedApps) {
|
||||
applistArray.push(self.installedApps[key].parent + '/' + key);
|
||||
}
|
||||
|
||||
utils.writeContent(appslistFile, applistArray.join('\n'));
|
||||
|
||||
var homescreenFile = distDir.clone();
|
||||
homescreenFile.append('homescreens.json');
|
||||
utils.writeContent(homescreenFile, JSON.stringify(grid, undefined, 2));
|
||||
|
||||
for (var key in operators) {
|
||||
var operator = operators[key];
|
||||
var apps = [];
|
||||
for (var appname in operator.apps) {
|
||||
apps.push(operator.apps[appname]);
|
||||
}
|
||||
operator.apps = apps;
|
||||
variant.operators.push(operators[key]);
|
||||
}
|
||||
|
||||
var variantFile = distDir.clone();
|
||||
variantFile.append('variant.json');
|
||||
utils.writeContent(variantFile, JSON.stringify(variant, undefined, 2));
|
||||
|
||||
// open directory in file manager if click save button
|
||||
if (evt && evt.target) {
|
||||
distDir.launch();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
function resetGaiaPath() {
|
||||
Homescreen.resetBuilds();
|
||||
Services.prefs.setCharPref(GAIA_DIR_PREF_NAME, '');
|
||||
window.location.hash = '#gaia-path-lightbox';
|
||||
}
|
||||
|
||||
window.addEventListener('load', function() {
|
||||
Homescreen.init();
|
||||
|
||||
Gaia.addEventListener('setupFinished', function() {
|
||||
utils = Gaia.require('utils');
|
||||
variant = Gaia.require('variant');
|
||||
Homescreen.gaiaConfig = Gaia.config;
|
||||
var {getFile, getBuildConfig} = utils;
|
||||
var configs = getBuildConfig(getFile(Gaia.config.GAIA_DIR, 'build',
|
||||
'config').path);
|
||||
Homescreen.setBuildConfig(configs);
|
||||
});
|
||||
|
||||
var observer = {
|
||||
observe: function(subject, topic, data) {
|
||||
if (data !== GAIA_DIR_PREF_NAME) {
|
||||
return;
|
||||
}
|
||||
var gaiaPath = Services.prefs.getCharPref(GAIA_DIR_PREF_NAME);
|
||||
|
||||
if (!gaiaPath) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
Gaia.setup(gaiaPath);
|
||||
window.location.hash = '';
|
||||
} catch (e) {
|
||||
alert(e.message);
|
||||
resetGaiaPath();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Services.prefs.addObserver(GAIA_DIR_PREF_NAME, observer, false);
|
||||
|
||||
document.getElementById('launch').addEventListener('click', function(evt) {
|
||||
Homescreen.save();
|
||||
var installedAppPaths = [];
|
||||
for (var key in Homescreen.installedApps) {
|
||||
installedAppPaths.push(Homescreen.installedApps[key].path);
|
||||
}
|
||||
|
||||
var original = evt.target.textContent;
|
||||
evt.target.textContent = 'Building gaia... please wait.';
|
||||
setTimeout(function() {
|
||||
if (!Services.prefs.prefHasUserValue(GAIA_DIR_PREF_NAME)) {
|
||||
Homescreen.selectGaiaFolder();
|
||||
}
|
||||
Gaia.profile.remove(true);
|
||||
utils.ensureFolderExists(Gaia.profile);
|
||||
Gaia.config.GAIA_APPDIRS = installedAppPaths.join(' ');
|
||||
Gaia.install();
|
||||
Gaia.launch();
|
||||
evt.target.textContent = original;
|
||||
}, 100);
|
||||
});
|
||||
if (Services.prefs.prefHasUserValue(GAIA_DIR_PREF_NAME)) {
|
||||
var gaiaPath = Services.prefs.getCharPref(GAIA_DIR_PREF_NAME);
|
||||
if (gaiaPath) {
|
||||
try {
|
||||
Gaia.setup(gaiaPath);
|
||||
} catch (e) {
|
||||
alert(e.message);
|
||||
resetGaiaPath();
|
||||
}
|
||||
} else {
|
||||
resetGaiaPath();
|
||||
}
|
||||
} else {
|
||||
resetGaiaPath();
|
||||
}
|
||||
});
|
||||
|
||||
// FIXME: this is a workaround because we can't use <a href="#something"> to
|
||||
// apply hash tag to url if using about:gaia.
|
||||
if (window.location.href === 'about:gaia') {
|
||||
window.location = 'chrome://gaia-build/content/aboutGaia.html';
|
||||
}
|
||||
171
gaia/tools/extensions/gaia-build@gaiamobile.org/content/gaia.js
Executable file
171
gaia/tools/extensions/gaia-build@gaiamobile.org/content/gaia.js
Executable file
|
|
@ -0,0 +1,171 @@
|
|||
var Gaia = {
|
||||
_listeners: {},
|
||||
buildModules: {},
|
||||
require: null,
|
||||
|
||||
loadXpcshellScript: function gaia_loadXpcshellScript(config, name) {
|
||||
let module =
|
||||
Cu.Sandbox(Services.scriptSecurityManager.getSystemPrincipal());
|
||||
for (var i in config) {
|
||||
module[i] = config[i];
|
||||
}
|
||||
Services.scriptloader.loadSubScript(
|
||||
'file:///' + config.GAIA_DIR.replace(/\\/g, '/') + '/build/' + name +
|
||||
'.js', module
|
||||
);
|
||||
return module;
|
||||
},
|
||||
|
||||
getConfig: function gaia_getConfig(profilePath, gaiaPath) {
|
||||
let os = Cc['@mozilla.org/xre/app-info;1'].getService(Ci.nsIXULRuntime).OS;
|
||||
const SEP = os === 'WINNT' ? '\\' : '/';
|
||||
let config = {};
|
||||
config.GAIA_DIR = gaiaPath;
|
||||
config.PROFILE_DIR = profilePath;
|
||||
config.PROFILE_FOLDER = 'profile-debug';
|
||||
config.GAIA_SCHEME = 'http://';
|
||||
config.GAIA_DOMAIN = 'gaiamobile.org';
|
||||
config.DEBUG = 1;
|
||||
config.DEVICE_DEBUG = 0;
|
||||
config.LOCAL_DOMAINS = 1;
|
||||
config.DESKTOP = 1;
|
||||
config.HOMESCREEN = config.GAIA_SCHEME + 'system.' + config.GAIA_DOMAIN;
|
||||
config.GAIA_PORT = ':8080';
|
||||
config.GAIA_LOCALES_PATH = 'locales';
|
||||
config.LOCALES_FILE = 'shared/resources/languages.json';
|
||||
config.LOCALE_BASEDIR = '';
|
||||
config.BUILD_APP_NAME = '*';
|
||||
config.PRODUCTION = '0';
|
||||
config.GAIA_OPTIMIZE = '0';
|
||||
config.GAIA_DEV_PIXELS_PER_PX = '1';
|
||||
config.DOGFOOD = '0';
|
||||
config.OFFICIAL = '';
|
||||
config.GAIA_DEFAULT_LOCALE = 'en-US';
|
||||
config.GAIA_PRETRANSLATE = '1';
|
||||
config.GAIA_CONCAT_LOCALES = '1';
|
||||
config.GAIA_ENGINE = 'xpcshell';
|
||||
config.TARGET_BUILD_VARIANT = '';
|
||||
config.NOFTU = '1';
|
||||
config.REMOTE_DEBUGGER = '0';
|
||||
config.SETTINGS_PATH = ['build', 'config',
|
||||
'custom-settings.json'].join(SEP);
|
||||
config.GAIA_DISTRIBUTION_DIR = [gaiaPath, 'distribution'].join(SEP);
|
||||
config.GAIA_BUILD_DIR = 'file://' + gaiaPath + '/build/';
|
||||
config.GAIA_KEYBOARD_LAYOUTS = 'en,pt-BR,es,de,fr,pl';
|
||||
return config;
|
||||
},
|
||||
|
||||
getTmpFolder: function gaia_getTmpFolder(name) {
|
||||
let file = Cc['@mozilla.org/file/directory_service;1']
|
||||
.getService(Ci.nsIProperties).get('TmpD', Ci.nsIFile);
|
||||
file.append(name);
|
||||
file.createUnique(Ci.nsIFile.DIRECTORY_TYPE, parseInt('0777', 8));
|
||||
return file;
|
||||
},
|
||||
|
||||
installExtension: function gaia_installExtension(extensionsDir, gaiaPath,
|
||||
extName, domain) {
|
||||
let utils = this.buildModules['utils'];
|
||||
let ext = extensionsDir.clone();
|
||||
let src = utils.getFile(gaiaPath, 'tools', 'extensions', extName);
|
||||
let destname = extName.contains('@') ? extName : extName + '@' + domain;
|
||||
ext.append(destname);
|
||||
utils.writeContent(ext, src.path);
|
||||
},
|
||||
|
||||
setup: function gaia_setup(gaiaPath) {
|
||||
var self = this;
|
||||
if (!gaiaPath) {
|
||||
throw new Error('gaiaPath is empty');
|
||||
}
|
||||
|
||||
this.profile = this.getTmpFolder('gaia');
|
||||
|
||||
// Generate global variables required by build scripts
|
||||
try {
|
||||
this.config = this.getConfig(this.profile.path, gaiaPath);
|
||||
} catch (e) {
|
||||
var msg = 'Can\'t load Gaia configuration, You may choose wrong gaia' +
|
||||
' directory';
|
||||
throw new Error(msg);
|
||||
}
|
||||
|
||||
try {
|
||||
this.require = this.loadXpcshellScript(this.config,
|
||||
'xpcshell-commonjs').require;
|
||||
} catch (e) {
|
||||
throw new Error('xpcshell-commonjs doesn\'t exist, you may choose wrong' +
|
||||
' gaia directory');
|
||||
}
|
||||
|
||||
var modules = ['utils', 'preferences', 'applications-data', 'variant',
|
||||
'webapp-manifests', 'webapp-optimize', 'webapp-zip', 'settings'];
|
||||
modules.forEach(function(m) { self.buildModules[m] = self.require(m); });
|
||||
|
||||
var evt = new CustomEvent('setupFinished');
|
||||
this.dispatchEvent(evt);
|
||||
},
|
||||
|
||||
install: function gaia_install() {
|
||||
var self = this;
|
||||
if (!this.config || !this.profile) {
|
||||
throw new Error('config or profile doesn\'t exist');
|
||||
}
|
||||
var config = this.config;
|
||||
|
||||
// Execute all parts of the build system
|
||||
this.buildModules['preferences'].execute(config);
|
||||
this.buildModules['applications-data'].execute(config);
|
||||
this.buildModules['webapp-manifests'].execute(config);
|
||||
this.buildModules['settings'].execute(config);
|
||||
|
||||
// Install all helper addons
|
||||
let extensions = this.profile.clone();
|
||||
extensions.append('extensions');
|
||||
this.buildModules['utils'].ensureFolderExists(extensions);
|
||||
|
||||
let exts = ['desktop-helper', 'browser-helper@gaiamobile.org', 'httpd'];
|
||||
exts.forEach(function(ext) {
|
||||
self.installExtension(extensions, config.GAIA_DIR, ext,
|
||||
config.GAIA_DOMAIN);
|
||||
});
|
||||
},
|
||||
|
||||
launch: function gaia_launch() {
|
||||
// Launch firefox with the custom profile
|
||||
let firefox = Cc['@mozilla.org/file/directory_service;1']
|
||||
.getService(Ci.nsIProperties).get('XREExeF', Ci.nsIFile);
|
||||
let process = Cc['@mozilla.org/process/util;1']
|
||||
.createInstance(Ci.nsIProcess);
|
||||
process.init(firefox);
|
||||
let args = ['-no-remote', '-profile', this.config.PROFILE_DIR];
|
||||
process.run(false, args, args.length);
|
||||
},
|
||||
|
||||
addEventListener: function gaia_addEventListener(type, listener) {
|
||||
if (!this._listeners[type]) {
|
||||
this._listeners[type] = [];
|
||||
}
|
||||
this._listeners[type].push(listener);
|
||||
},
|
||||
|
||||
removeEventListener: function gaia_removeEventListener(type, listener) {
|
||||
if (this._listeners[type]) {
|
||||
this._listeners[type].every(function(li, index) {
|
||||
if (li === listener) {
|
||||
this._listeners.splice(index, 1);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
dispatchEvent: function gaia_dispatchEvent(event) {
|
||||
if (this._listeners[event.type]) {
|
||||
this._listeners[event.type].forEach(function(listener) {
|
||||
listener(event);
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
22
gaia/tools/extensions/gaia-build@gaiamobile.org/install.rdf
Executable file
22
gaia/tools/extensions/gaia-build@gaiamobile.org/install.rdf
Executable file
|
|
@ -0,0 +1,22 @@
|
|||
<?xml version="1.0"?>
|
||||
|
||||
<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:em="http://www.mozilla.org/2004/em-rdf#">
|
||||
|
||||
<Description about="urn:mozilla:install-manifest">
|
||||
<em:id>gaia-build@gaiamobile.org</em:id>
|
||||
<em:name>gaia-build</em:name>
|
||||
<em:version>0.1</em:version>
|
||||
<em:targetApplication>
|
||||
<Description>
|
||||
<em:id>{ec8030f7-c20a-464f-9b0e-13a3a9e97384}</em:id>
|
||||
<em:minVersion>6.0</em:minVersion>
|
||||
<em:maxVersion>32.0.*</em:maxVersion>
|
||||
</Description>
|
||||
</em:targetApplication>
|
||||
<em:unpack>true</em:unpack>
|
||||
<em:bootstrap>true</em:bootstrap>
|
||||
<em:description>Build and setup gaia into a firefox instance</em:description>
|
||||
<em:homepageURL>https://github.com/mozilla-b2g/gaia/</em:homepageURL>
|
||||
</Description>
|
||||
</RDF>
|
||||
525
gaia/tools/extensions/httpd/bootstrap.js
vendored
Executable file
525
gaia/tools/extensions/httpd/bootstrap.js
vendored
Executable file
|
|
@ -0,0 +1,525 @@
|
|||
/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- /
|
||||
/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
|
||||
|
||||
'use strict';
|
||||
|
||||
function startup(data, reason) {
|
||||
const CC = Components.Constructor;
|
||||
const Cc = Components.classes;
|
||||
const Ci = Components.interfaces;
|
||||
const Cu = Components.utils;
|
||||
const Cm = Components.manager.QueryInterface(Ci.nsIComponentRegistrar);
|
||||
|
||||
Cu.import('resource://gre/modules/XPCOMUtils.jsm');
|
||||
Cu.import('resource://gre/modules/Services.jsm');
|
||||
try {
|
||||
// Gecko < 21
|
||||
Cu.import('resource:///modules/devtools/dbg-client.jsm');
|
||||
} catch (e) {
|
||||
// Gecko >= 21
|
||||
Cu.import('resource://gre/modules/devtools/dbg-client.jsm');
|
||||
}
|
||||
|
||||
// Make sure all applications are considered launchable
|
||||
Cu.import('resource://gre/modules/Webapps.jsm');
|
||||
const env = Cc['@mozilla.org/process/environment;1'].
|
||||
getService(Ci.nsIEnvironment);
|
||||
DOMApplicationRegistry.allAppsLaunchable = true;
|
||||
|
||||
const LANG_EN = 'en-US';
|
||||
const GAIA_DOMAIN = Services.prefs.getCharPref("extensions.gaia.domain");
|
||||
const GAIA_APPDIRS = Services.prefs.getCharPref("extensions.gaia.appdirs");
|
||||
const GAIA_ALLAPPDIRS = Services.prefs.getCharPref("extensions.gaia.allappdirs");
|
||||
const GAIA_DIR = Services.prefs.getCharPref("extensions.gaia.dir");
|
||||
const GAIA_PORT = Services.prefs.getIntPref("extensions.gaia.port");
|
||||
const GAIA_OFFICIAL = Services.prefs.getBoolPref("extensions.gaia.official");
|
||||
const LOCALES_FILE =
|
||||
Services.prefs.getCharPref("extensions.gaia.locales_file");
|
||||
const LOCALE_BASEDIR =
|
||||
Services.prefs.getCharPref("extensions.gaia.locale_basedir");
|
||||
|
||||
const LocalFile = CC('@mozilla.org/file/local;1',
|
||||
'nsILocalFile',
|
||||
'initWithPath');
|
||||
|
||||
const ScriptableInputStream = CC('@mozilla.org/scriptableinputstream;1',
|
||||
'nsIScriptableInputStream',
|
||||
'init');
|
||||
|
||||
let debugEnv = env.get('DEBUG');
|
||||
let DEBUG = debugEnv === '*' || debugEnv.includes('bootstrap');
|
||||
|
||||
let l10nManager, utils;
|
||||
function debug(data) {
|
||||
if (!DEBUG)
|
||||
return;
|
||||
dump(data + '\n');
|
||||
}
|
||||
|
||||
function isl10nFile(fname) {
|
||||
var matched;
|
||||
return fname.contains('.ini') || fname.contains('manifest.webapp');
|
||||
}
|
||||
|
||||
function startupHttpd(baseDir, port) {
|
||||
const httpdURL = 'chrome://httpd.js/content/httpd.js';
|
||||
let httpd = {};
|
||||
env.set('GAIA_DIR', GAIA_DIR);
|
||||
Services.scriptloader.loadSubScript(httpdURL, httpd);
|
||||
let server = new httpd.nsHttpServer();
|
||||
server.registerDirectory('/', new LocalFile(baseDir));
|
||||
server.registerContentType('appcache', 'text/cache-manifest');
|
||||
server.start(port);
|
||||
|
||||
// Add {appname}.domain.tld:port to the list of domains handle by httpd
|
||||
let identity = server.identity;
|
||||
let scheme = 'http';
|
||||
|
||||
let host = GAIA_DOMAIN;
|
||||
identity.add(scheme, host, port);
|
||||
|
||||
let directories = getDirectories(GAIA_ALLAPPDIRS.split(' '));
|
||||
directories.forEach(function appendDir(name) {
|
||||
// Some app names can cause a raise here, preventing other apps
|
||||
// from being added.
|
||||
try {
|
||||
identity.add(scheme, name + '.' + host, port);
|
||||
} catch (e) {
|
||||
dump(e);
|
||||
}
|
||||
});
|
||||
identity.add(scheme, 'theme.' + host, port);
|
||||
|
||||
let commonjs = {
|
||||
GAIA_BUILD_DIR: 'file://' + GAIA_DIR.replace(/\\/g, '/') + '/build/'
|
||||
};
|
||||
// Loading |require| then we can use build scripts of gaia.
|
||||
Services.scriptloader.loadSubScript(commonjs.GAIA_BUILD_DIR +
|
||||
'/xpcshell-commonjs.js', commonjs);
|
||||
utils = commonjs.require('./utils');
|
||||
|
||||
if (LOCALE_BASEDIR) {
|
||||
let appDirs = GAIA_APPDIRS.split(' ');
|
||||
let multilocale = commonjs.require('./multilocale');
|
||||
let sharedDir = utils.getFile(GAIA_DIR, 'shared');
|
||||
l10nManager = new multilocale.L10nManager(GAIA_DIR,
|
||||
LOCALES_FILE, LOCALE_BASEDIR);
|
||||
let filesInShared = utils.ls(sharedDir, true);
|
||||
let localesFile = utils.resolve(LOCALES_FILE, GAIA_DIR);
|
||||
|
||||
// Finding out which file we need to handle.
|
||||
appDirs.forEach(function(appDir) {
|
||||
let appDirFile = utils.getFile(appDir);
|
||||
let filesInApp = utils.ls(appDirFile, true);
|
||||
|
||||
[...filesInApp, ...filesInShared].forEach(function(file) {
|
||||
let fname = file.leafName;
|
||||
let relativePath = file.path.substr(GAIA_DIR.length);
|
||||
// localized ini and manifest files will be generated in runtime
|
||||
// so we need handle path if client request those files.
|
||||
// and we use "LocalizationHandler" to handle them.
|
||||
if (isl10nFile(fname)) {
|
||||
server.registerPathHandler(relativePath, LocalizationHandler);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// languages.json
|
||||
server.registerFile('/shared/resources/languages.json', localesFile);
|
||||
server.registerDirectory('/locales/',
|
||||
new LocalFile(l10nManager.localeBasedir));
|
||||
}
|
||||
|
||||
let testAgentBoilerplateDir = utils.getFile(GAIA_DIR, 'dev_apps',
|
||||
'test-agent', 'common', 'test', 'boilerplate');
|
||||
let proxyFile = testAgentBoilerplateDir.clone();
|
||||
let sandboxFile = testAgentBoilerplateDir.clone();
|
||||
proxyFile.append('_proxy.html');
|
||||
sandboxFile.append('_sandbox.html');
|
||||
server.registerFile('/test/unit/_proxy.html', proxyFile);
|
||||
server.registerFile('/test/unit/_sandbox.html', sandboxFile);
|
||||
|
||||
server.registerPathHandler('/marionette', MarionetteHandler);
|
||||
|
||||
server.registerDirectory(
|
||||
'/common/', new LocalFile(baseDir + '/dev_apps/test-agent/common')
|
||||
);
|
||||
server.registerDirectory('/shared/', new LocalFile(baseDir + '/shared'));
|
||||
|
||||
let brandingLocalFile = new LocalFile(
|
||||
baseDir + '/shared/resources/branding/' + (GAIA_OFFICIAL ? 'official' : 'unofficial')
|
||||
);
|
||||
|
||||
server.registerDirectory('/shared/resources/branding/', brandingLocalFile);
|
||||
}
|
||||
|
||||
function getDirectories(appDirs) {
|
||||
return appDirs.map(function (path) {
|
||||
let appsDir = Cc['@mozilla.org/file/local;1']
|
||||
.createInstance(Ci.nsILocalFile);
|
||||
appsDir.initWithPath(path);
|
||||
return appsDir.leafName;
|
||||
});
|
||||
}
|
||||
|
||||
function LongPoll(connid, transport) {
|
||||
this.transport = transport;
|
||||
this.connid = connid;
|
||||
this.buffer = [];
|
||||
|
||||
this.outstanding = {
|
||||
push: null,
|
||||
noop: null,
|
||||
timeout: null
|
||||
};
|
||||
}
|
||||
|
||||
LongPoll.prototype = {
|
||||
cast: function cast(msg) {
|
||||
let outstanding = this.outstanding;
|
||||
clearTimeout(outstanding.noop);
|
||||
this.buffer.push(msg);
|
||||
|
||||
if (outstanding.push === null)
|
||||
return;
|
||||
|
||||
debug('cast ' + JSON.stringify(msg) + ' ' + outstanding.push);
|
||||
this.drain_queue(outstanding.push);
|
||||
outstanding.push.finish();
|
||||
outstanding.push = null;
|
||||
|
||||
outstanding.timeout = setTimeout(function close(uid) {
|
||||
Connections.close(uid);
|
||||
}, 5000, this.connid);
|
||||
},
|
||||
|
||||
drain_queue: function drain_queue(response) {
|
||||
let queue = this.buffer;
|
||||
this.buffer = [];
|
||||
|
||||
let json = { 'messages': [] };
|
||||
queue.forEach(function(chunk) {
|
||||
if (chunk.noop) {
|
||||
//we need to send something to keep the client polling
|
||||
json.messages.push({});
|
||||
return;
|
||||
}
|
||||
json.messages.push({ 'id': this.connid, 'response': chunk });
|
||||
}.bind(this));
|
||||
|
||||
response.write(JSON.stringify(json));
|
||||
},
|
||||
|
||||
send: function send(obj) {
|
||||
this.transport.send(obj);
|
||||
}
|
||||
};
|
||||
|
||||
let transports = {};
|
||||
|
||||
function getUID() {
|
||||
return Cc['@mozilla.org/uuid-generator;1'].getService(Ci.nsIUUIDGenerator)
|
||||
.generateUUID()
|
||||
.toString();
|
||||
}
|
||||
|
||||
var Connections = {
|
||||
_connections: {},
|
||||
close: function connections_close(uid) {
|
||||
if (uid in this._connections) {
|
||||
this._connections[uid].transport.close();
|
||||
delete this._connections[uid];
|
||||
}
|
||||
},
|
||||
|
||||
add: function connections_add(uid, connection) {
|
||||
this._connections[uid] = connection;
|
||||
},
|
||||
|
||||
get: function connections_get(uid) {
|
||||
return this._connections[uid] || null;
|
||||
}
|
||||
};
|
||||
|
||||
var LocalizationHandler = {
|
||||
// we only handle GET method because we always use GET method to get those
|
||||
// files.
|
||||
handle: function lh_handle(request, response) {
|
||||
if (request.method === 'GET' && l10nManager) {
|
||||
let locales = JSON.parse(JSON.stringify(l10nManager.locales));
|
||||
// no need to handle en-US.
|
||||
var enIndex = locales.indexOf('en-US');
|
||||
if (enIndex !== -1) {
|
||||
locales.splice(enIndex, 1);
|
||||
}
|
||||
let {getFile, getFileContent} = utils;
|
||||
let {modifyLocaleIni, serializeIni} = l10nManager;
|
||||
|
||||
let file = getFile(GAIA_DIR, request.path);
|
||||
if (file.leafName.contains('.ini')) {
|
||||
response.setHeader('Content-Type', 'text/plain');
|
||||
let iniOriginal = getFileContent(file);
|
||||
let modifiedIni = serializeIni(modifyLocaleIni(iniOriginal, locales));
|
||||
response.write(modifiedIni);
|
||||
} else if (file.leafName.contains('manifest.webapp')) {
|
||||
let buildDirectoryFile = getFile(GAIA_DIR, 'build_stage',
|
||||
file.parent.leafName);
|
||||
response.setHeader('Content-Type', 'application/json');
|
||||
let webapp = {
|
||||
manifestFile: file,
|
||||
sourceDirectoryFile: file.parent,
|
||||
sourceDirectoryName: file.parent.leafName,
|
||||
buildDirectoryFile: buildDirectoryFile
|
||||
};
|
||||
let manifest = l10nManager.localizeManifest(webapp);
|
||||
response.write(JSON.stringify(manifest));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var MarionetteHandler = {
|
||||
|
||||
/**
|
||||
* Adding only test-agent for now.
|
||||
* In the future individual apps may need
|
||||
* access as well.
|
||||
*/
|
||||
crosDomains: [
|
||||
'http://test-agent.gaiamobile.org',
|
||||
'http://test-agent.gaiamobile.org:8080',
|
||||
'http://test-agent.trunk.gaiamobile.org',
|
||||
'http://test-agent.trunk.gaiamobile.org:8080'
|
||||
],
|
||||
|
||||
// nsIHttpRequestHandler
|
||||
handle: function mh_handle(request, response) {
|
||||
try {
|
||||
this.handleCrosHeaders(request, response);
|
||||
|
||||
switch (request.method) {
|
||||
case 'GET':
|
||||
this.onGet(request, response);
|
||||
break;
|
||||
case 'POST':
|
||||
this.onPost(request, response);
|
||||
break;
|
||||
case 'OPTIONS':
|
||||
this.onOptions(request, response);
|
||||
break;
|
||||
case 'PUT':
|
||||
this.onPut(request, response);
|
||||
break;
|
||||
case 'DELETE':
|
||||
this.onDelete(request, response);
|
||||
break;
|
||||
default:
|
||||
this.onDefault(request, response);
|
||||
break;
|
||||
}
|
||||
} catch (e) {
|
||||
response.setStatusLine('1.1', 500, 'Internal Server Error');
|
||||
response.write(e.toString() + e.stack == undefined ? '' : ' ' + e.stack);
|
||||
}
|
||||
},
|
||||
|
||||
onOptions: function mh_options(request, response) {
|
||||
response.setStatusLine('1.1', 200, 'OK');
|
||||
},
|
||||
|
||||
handleCrosHeaders: function mh_options(request, response) {
|
||||
if(!request.hasHeader('Origin'))
|
||||
return;
|
||||
|
||||
let methods = 'GET, POST, PUT, DELETE, OPTIONS';
|
||||
let origin = request.getHeader('Origin');
|
||||
|
||||
if (!origin || this.crosDomains.indexOf(origin) === -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
response.setHeader(
|
||||
'Access-Control-Allow-Origin',
|
||||
origin
|
||||
);
|
||||
|
||||
response.setHeader(
|
||||
'Access-Control-Allow-Credentials',
|
||||
'true'
|
||||
);
|
||||
|
||||
response.setHeader(
|
||||
'Access-Control-Max-Age',
|
||||
'2500'
|
||||
);
|
||||
|
||||
response.setHeader(
|
||||
'Access-Control-Allow-Methods',
|
||||
methods
|
||||
);
|
||||
|
||||
response.setHeader('Access-Control-Allow-Headers', 'Content-Type, *');
|
||||
},
|
||||
|
||||
onGet: function mh_get(request, response) {
|
||||
debug('/marionette?' + request.queryString);
|
||||
if (request.queryString.length === 0) {
|
||||
response.setStatusLine('1.1', 404, 'Not Found');
|
||||
response.write(JSON.stringify({ 'error': 'connection not found' }));
|
||||
return;
|
||||
}
|
||||
response.setStatusLine('1.1', 200, 'OK');
|
||||
response.setHeader('Content-Type', 'application/json');
|
||||
|
||||
let uid = request.queryString.split('=')[0];
|
||||
let connection = Connections.get(uid);
|
||||
if (!connection) {
|
||||
debug('Could not get connection for ' + request.queryString);
|
||||
return;
|
||||
}
|
||||
|
||||
clearTimeout(connection.outstanding.timeout);
|
||||
|
||||
if (connection.buffer.length !== 0) {
|
||||
connection.drain_queue(response);
|
||||
return;
|
||||
}
|
||||
|
||||
response.processAsync();
|
||||
|
||||
connection.outstanding.push = response;
|
||||
connection.outstanding.noop = setTimeout(function() {
|
||||
debug('sending noop');
|
||||
connection.cast({ 'noop': true });
|
||||
}, 25000);
|
||||
},
|
||||
|
||||
onPost: function mh_post(request, response) {
|
||||
response.setStatusLine('1.1', 200, 'OK');
|
||||
response.setHeader('Content-Type', 'application/json');
|
||||
|
||||
let uid = getUID();
|
||||
response.write(JSON.stringify({ 'id': uid }));
|
||||
|
||||
request.bodyInputStream.asyncWait(function(stream) {
|
||||
let scriptable = new ScriptableInputStream(stream);
|
||||
let json = scriptable.read(scriptable.available());
|
||||
let { server: server, port: port } = JSON.parse(json);
|
||||
|
||||
let hostport = server + ':' + port;
|
||||
if (hostport in transports) {
|
||||
Connections.close(transports[hostport]);
|
||||
}
|
||||
|
||||
debug('Opening a transport socket to the debugger...');
|
||||
let transport = debuggerSocketConnect(server, parseInt(port));
|
||||
let connection = new LongPoll(uid, transport);
|
||||
Connections.add(uid, connection);
|
||||
transports[hostport] = uid;
|
||||
|
||||
transport.hooks = {
|
||||
onPacket: function onPacket(pack) {
|
||||
debug('onpacket ' + JSON.stringify(pack));
|
||||
connection.cast(pack);
|
||||
},
|
||||
onClosed: function onClosed(status) {
|
||||
debug('onclosed ' + JSON.stringify(status));
|
||||
}
|
||||
};
|
||||
|
||||
transport.ready();
|
||||
}, 0, 0, Services.tm.currentThread);
|
||||
},
|
||||
|
||||
onPut: function mh_put(request, response) {
|
||||
response.setStatusLine('1.1', 201, 'Accepted');
|
||||
response.setHeader('Content-Type', 'text/plain', false);
|
||||
response.write('');
|
||||
|
||||
let uid = request.queryString.split('=')[0];
|
||||
let connection = Connections.get(uid);
|
||||
if (!connection) {
|
||||
debug('Could not get connection for ' + request.queryString);
|
||||
return;
|
||||
}
|
||||
|
||||
request.bodyInputStream.asyncWait(function(stream) {
|
||||
let scriptable = new ScriptableInputStream(stream);
|
||||
let json = scriptable.read(scriptable.available());
|
||||
connection.send(JSON.parse(json));
|
||||
}, 0, 0, Services.tm.currentThread);
|
||||
},
|
||||
|
||||
onDelete: function mh_delete(request, response) {
|
||||
Connections.close(request.queryString.split('=')[0]);
|
||||
},
|
||||
|
||||
onDefault: function mh_default(request, response) {
|
||||
response.setStatusLine('1.1', 405, 'Method Not Allowed');
|
||||
response.write('');
|
||||
}
|
||||
};
|
||||
|
||||
function registerAppProtocol() {
|
||||
let classID = Components.ID('{e3bace70-f074-11e3-ac10-0800200c9a66}');
|
||||
let contract = '@mozilla.org/network/protocol;1?name=app';
|
||||
let instance = null;
|
||||
|
||||
function AppProtocol() {}
|
||||
|
||||
AppProtocol.prototype = {
|
||||
scheme: 'app',
|
||||
classID: classID,
|
||||
QueryInterface: XPCOMUtils.generateQI([Ci.nsIProtocolHandler]),
|
||||
newURI: function(aSpec, aOriginCharset, aBaseURI) {
|
||||
let uri = Cc["@mozilla.org/network/standard-url;1"]
|
||||
.createInstance(Ci.nsIStandardURL);
|
||||
uri.init(Ci.nsIStandardURL.URLTYPE_STANDARD, -1, aSpec, aOriginCharset,
|
||||
aBaseURI);
|
||||
return uri.QueryInterface(Ci.nsIURI);
|
||||
},
|
||||
newChannel2: function(aURI, aLoadInfo) {
|
||||
let url = aURI.QueryInterface(Ci.nsIURL);
|
||||
let appId = aURI.host;
|
||||
let fileSpec = url.filePath;
|
||||
let spec = 'http://' + appId + ':' + GAIA_PORT + fileSpec;
|
||||
let uri = Services.io.newURI(spec, null, null);
|
||||
let channel = Services.io.newChannelFromURIWithLoadInfo(uri, aLoadInfo);
|
||||
channel.QueryInterface(Ci.nsIChannel).originalURI = aURI;
|
||||
return channel;
|
||||
},
|
||||
newChannel: function(aURI) {
|
||||
return newChannel2(aURI, null);
|
||||
}
|
||||
};
|
||||
|
||||
let newFactory = {
|
||||
createInstance: function(outer, iid) {
|
||||
if (outer) {
|
||||
throw Components.results.NS_ERROR_NO_AGGREGATION;
|
||||
}
|
||||
if (instance === null) {
|
||||
instance = new AppProtocol();
|
||||
}
|
||||
return instance;
|
||||
},
|
||||
lockFactory: function(aLock) {
|
||||
throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
|
||||
},
|
||||
QueryInterface: XPCOMUtils.generateQI([Ci.nsIFactory])
|
||||
};
|
||||
Cm.registerFactory(classID, '', contract, newFactory);
|
||||
}
|
||||
|
||||
registerAppProtocol();
|
||||
startupHttpd(GAIA_DIR, GAIA_PORT);
|
||||
}
|
||||
|
||||
function shutdown(data, reason) {
|
||||
}
|
||||
|
||||
function install(data, reason) {
|
||||
}
|
||||
|
||||
function uninstall(data, reason) {
|
||||
}
|
||||
1
gaia/tools/extensions/httpd/chrome.manifest
Executable file
1
gaia/tools/extensions/httpd/chrome.manifest
Executable file
|
|
@ -0,0 +1 @@
|
|||
content httpd.js content/
|
||||
5456
gaia/tools/extensions/httpd/content/httpd.js
Executable file
5456
gaia/tools/extensions/httpd/content/httpd.js
Executable file
File diff suppressed because it is too large
Load diff
29
gaia/tools/extensions/httpd/install.rdf
Executable file
29
gaia/tools/extensions/httpd/install.rdf
Executable file
|
|
@ -0,0 +1,29 @@
|
|||
<?xml version="1.0"?>
|
||||
|
||||
<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:em="http://www.mozilla.org/2004/em-rdf#">
|
||||
|
||||
<Description about="urn:mozilla:install-manifest">
|
||||
<em:id>httpd@gaiamobile.org</em:id>
|
||||
<em:name>httpd.js</em:name>
|
||||
<em:version>0.1</em:version>
|
||||
<em:iconURL>chrome://httpd.js/skin/logo.png</em:iconURL>
|
||||
<em:targetApplication>
|
||||
<Description>
|
||||
<em:id>{ec8030f7-c20a-464f-9b0e-13a3a9e97384}</em:id>
|
||||
<em:minVersion>6.0</em:minVersion>
|
||||
<em:maxVersion>*</em:maxVersion>
|
||||
</Description>
|
||||
<Description>
|
||||
<em:id>{3c2e2abc-06d4-11e1-ac3b-374f68613e61}</em:id>
|
||||
<em:minVersion>6.0</em:minVersion>
|
||||
<em:maxVersion>*</em:maxVersion>
|
||||
</Description>
|
||||
</em:targetApplication>
|
||||
<em:unpack>true</em:unpack>
|
||||
<em:bootstrap>true</em:bootstrap>
|
||||
<em:creator>Vivien Nicolas</em:creator>
|
||||
<em:description>httpd.js</em:description>
|
||||
<em:homepageURL>https://github.com/andreasgal/gaia/</em:homepageURL>
|
||||
</Description>
|
||||
</RDF>
|
||||
1
gaia/tools/extensions/httpd@gaiamobile.org
Executable file
1
gaia/tools/extensions/httpd@gaiamobile.org
Executable file
|
|
@ -0,0 +1 @@
|
|||
httpd
|
||||
Loading…
Reference in a new issue