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
116
gaia/tools/cache_sync.py
Executable file
116
gaia/tools/cache_sync.py
Executable file
|
|
@ -0,0 +1,116 @@
|
|||
# Synchronizes the offline cache of an external app into a local directory
|
||||
# structure. Run from the base directory of an app in gaia/external-apps.
|
||||
# Builds a directory for each known origin in the offline cache.
|
||||
|
||||
import json
|
||||
import os
|
||||
import requests
|
||||
|
||||
|
||||
def read_manifest():
|
||||
file = open('manifest.webapp', 'r').read()
|
||||
content = json.loads(file)
|
||||
return content
|
||||
|
||||
|
||||
def read_origin():
|
||||
# Extract the origin from the origin file
|
||||
return open('origin', 'r').readline().rstrip()
|
||||
|
||||
|
||||
def parse_offline_manifest(s, origin):
|
||||
lines = s.split('\n')
|
||||
|
||||
# Bail if this is an invalid appcache manifest.
|
||||
if lines[0] != 'CACHE MANIFEST':
|
||||
return None
|
||||
|
||||
sections = ['CACHE:', 'NETWORK:', 'FALLBACK:']
|
||||
|
||||
current_section = 'CACHE'
|
||||
|
||||
cache = []
|
||||
|
||||
for line in lines[1:]:
|
||||
# ignore comments, empty lines, and wildcards
|
||||
if line.startswith('#') or line.startswith('*') or not line.rstrip():
|
||||
continue
|
||||
if line in sections:
|
||||
current_section = line[:len(line)-1]
|
||||
continue
|
||||
|
||||
# Don't cache NETWORK entries.
|
||||
if current_section == 'NETWORK':
|
||||
continue
|
||||
|
||||
# if we're in FALLBACK, extract the fallback path.
|
||||
if current_section == 'FALLBACK':
|
||||
line = line.split(' ')[1]
|
||||
|
||||
# Ensure that all paths start with an origin.
|
||||
if not line.startswith('http'):
|
||||
if line.startswith('/'):
|
||||
line = line[1:]
|
||||
line = origin + line
|
||||
|
||||
cache.append(line)
|
||||
|
||||
return cache
|
||||
|
||||
|
||||
def fetch_with_path(url):
|
||||
# are we dealing with something URL-esque?
|
||||
if not url.find('//') + 1:
|
||||
return None
|
||||
|
||||
print 'fetching %s' % url
|
||||
file = requests.get(url, verify=False)
|
||||
if file.status_code != 200:
|
||||
return None
|
||||
|
||||
# base our paths in the 'cache' directory.
|
||||
path = 'cache/' + url.split('//')[1]
|
||||
|
||||
directory, fn = path.rsplit('/', 1)
|
||||
# make the directory structure
|
||||
if not os.path.exists(directory):
|
||||
os.makedirs(directory)
|
||||
|
||||
# write our file
|
||||
out_file = open(path, 'w')
|
||||
out_file.write(file.content)
|
||||
|
||||
return file.content;
|
||||
|
||||
|
||||
def main():
|
||||
|
||||
# Learn about the external app.
|
||||
app_manifest = read_manifest()
|
||||
origin = read_origin()
|
||||
|
||||
if 'appcache_path' not in app_manifest:
|
||||
print 'no appcache found'
|
||||
return
|
||||
|
||||
# Construct the appcache url and drop the first slash from appcache_path.
|
||||
appcache_url = origin + app_manifest.get('appcache_path')[1:]
|
||||
print 'fetching appcache at %s' % appcache_url
|
||||
|
||||
cache_manifest = fetch_with_path(appcache_url)
|
||||
if not cache_manifest:
|
||||
print 'failed to fetch appcache'
|
||||
return
|
||||
|
||||
cache = parse_offline_manifest(cache_manifest, origin)
|
||||
|
||||
if not cache:
|
||||
print 'bad offline cache'
|
||||
return
|
||||
|
||||
# fetch each file, and store it in the right place
|
||||
for entry in cache:
|
||||
fetch_with_path(entry);
|
||||
|
||||
|
||||
main()
|
||||
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
|
||||
110
gaia/tools/html_transformer/html_transformer.js
Executable file
110
gaia/tools/html_transformer/html_transformer.js
Executable file
|
|
@ -0,0 +1,110 @@
|
|||
/**
|
||||
* @fileoverview A collection of utilities for performing static analysis on
|
||||
* and transforming apps' html files.
|
||||
*/
|
||||
var Promise = require('es6-promise').Promise;
|
||||
var jsdom = require('jsdom');
|
||||
|
||||
function load(filename) {
|
||||
return new Promise(function(resolve, reject) {
|
||||
jsdom.env(filename, function(errors, window) {
|
||||
if (errors && errors.length) {
|
||||
return reject(errors[0]);
|
||||
}
|
||||
|
||||
return resolve(new HTMLTransformer(window));
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
function HTMLTransformer(window) {
|
||||
this.window = window;
|
||||
}
|
||||
|
||||
HTMLTransformer.prototype = {
|
||||
getSharedDependencies: function() {
|
||||
var doc = this.window.document;
|
||||
var links = getSharedElements(doc, 'link');
|
||||
var scripts = getSharedElements(doc, 'script');
|
||||
var commented = flatmap(getComments(doc), getSharedElementsFromComment);
|
||||
return links.concat(scripts).concat(commented);
|
||||
}
|
||||
};
|
||||
|
||||
function getSharedElements(doc, selector) {
|
||||
return querySelectorAll(doc, selector).map(getShared).filter(function(x) {
|
||||
return !!x;
|
||||
});
|
||||
}
|
||||
|
||||
function getSharedElementsFromComment(comment) {
|
||||
var sharedLink = /<link\s.*href="(app:\/\/.+\.gaiamobile\.org)?\/?(shared\/.+)".*>/;
|
||||
var sharedScript = /<script\s.*src="(app:\/\/.+\.gaiamobile\.org)?\/?(shared\/.+)".*>/;
|
||||
|
||||
return flatmap(comment.split(/\n/), function(commentLine) {
|
||||
var links = commentLine.match(sharedLink) || [];
|
||||
var scripts = commentLine.match(sharedScript) || [];
|
||||
var results = links.concat(scripts);
|
||||
return results.filter(function(result) {
|
||||
return result && result.indexOf('shared') === 0;
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
// Neither document.createTreeWalker nor document.createNodeIterator
|
||||
// are supported by jsdom so we're going to do this the old fashioned way...
|
||||
function getComments(doc) {
|
||||
var results = [];
|
||||
var queue = [doc.body];
|
||||
while (queue.length) {
|
||||
var next = queue.shift();
|
||||
if (next.nodeType === 8 /* COMMENT_NODE */) {
|
||||
results.push(next.textContent);
|
||||
}
|
||||
|
||||
var children = next.childNodes;
|
||||
if (children && children.length) {
|
||||
for (var i = 0; i < children.length; i++) {
|
||||
queue.push(children[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
function querySelectorAll(doc, selector) {
|
||||
return Array.prototype.slice.call(doc.querySelectorAll(selector));
|
||||
}
|
||||
|
||||
function getShared(node) {
|
||||
var sharedUrl = /^(app:\/\/.+\.gaiamobile\.org)?\/?(shared\/.+)/;
|
||||
var match;
|
||||
switch (node.nodeName) {
|
||||
case 'LINK':
|
||||
match = node.href.match(sharedUrl);
|
||||
break;
|
||||
case 'SCRIPT':
|
||||
match = node.src.match(sharedUrl);
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
return (match && match.length) ? match.filter(function(str) {
|
||||
return str && str.indexOf('shared') === 0;
|
||||
})[0] : false;
|
||||
}
|
||||
|
||||
function flatmap(array, fn) {
|
||||
var result = [];
|
||||
array.map(fn).forEach(function(subresult) {
|
||||
if (Array.isArray(subresult)) {
|
||||
result = result.concat(subresult);
|
||||
}
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
module.exports = { HTMLTransformer: HTMLTransformer, load: load };
|
||||
20
gaia/tools/html_transformer/package.json
Executable file
20
gaia/tools/html_transformer/package.json
Executable file
|
|
@ -0,0 +1,20 @@
|
|||
{
|
||||
"name": "html-transformer",
|
||||
"version": "0.0.1",
|
||||
"description": "Figures out app shared deps, localizes your strings, other mischief",
|
||||
"main": "html_transformer.js",
|
||||
|
||||
"dependencies": {
|
||||
"es6-promise": "^2.3.0",
|
||||
"jsdom": "^3"
|
||||
},
|
||||
|
||||
"devDependencies": {
|
||||
"chai": "^3.0.0",
|
||||
"mocha": "^2.2.5"
|
||||
},
|
||||
|
||||
"scripts": {
|
||||
"test": "./node_modules/.bin/mocha"
|
||||
}
|
||||
}
|
||||
18
gaia/tools/html_transformer/test/fixtures/basic.html
vendored
Executable file
18
gaia/tools/html_transformer/test/fixtures/basic.html
vendored
Executable file
|
|
@ -0,0 +1,18 @@
|
|||
<html>
|
||||
<head>
|
||||
<title>Basic html page</title>
|
||||
</head>
|
||||
<body>
|
||||
<!-- hello -->
|
||||
<p>A really great thing to say</p>
|
||||
<link rel="stylesheet" type="text/css" href="/shared/style/icons.css">
|
||||
<link rel="stylesheet" type="text/css" href="app://theme.gaiamobile.org/shared/elements/gaia-theme/style.css">
|
||||
<script defer src="shared/js/component_utils.js"></script>
|
||||
<script defer src="shared/elements/gaia_subheader/script.js"></script>
|
||||
<!--
|
||||
<link rel="stylesheet" type="text/css" href="shared/style/tabs.css">
|
||||
<link rel="stylesheet" type="text/css" href="app://theme.gaiamobile.org/shared/elements/gaia-theme/other.css">
|
||||
<script defer src="/shared/js/gesture_detector.js"></script>
|
||||
-->
|
||||
</body>
|
||||
</html>
|
||||
38
gaia/tools/html_transformer/test/html_transformer_test.js
Executable file
38
gaia/tools/html_transformer/test/html_transformer_test.js
Executable file
|
|
@ -0,0 +1,38 @@
|
|||
var expect = require('chai').expect;
|
||||
var htmlTransformer = require('../html_transformer');
|
||||
|
||||
suite('htmlTransformer', function() {
|
||||
var subject;
|
||||
|
||||
setup(function() {
|
||||
return htmlTransformer.load(__dirname + '/fixtures/basic.html')
|
||||
.then(function(result) {
|
||||
subject = result;
|
||||
});
|
||||
});
|
||||
|
||||
test('should be an HTMLTransformer', function() {
|
||||
expect(subject).to.be.an.instanceOf(htmlTransformer.HTMLTransformer);
|
||||
});
|
||||
|
||||
test('should load html', function() {
|
||||
var doc = subject.window.document;
|
||||
var p = doc.querySelector('p');
|
||||
expect(p.textContent).to.equal('A really great thing to say');
|
||||
});
|
||||
|
||||
test('#getSharedDependencies', function() {
|
||||
expect(subject.getSharedDependencies())
|
||||
.to
|
||||
.deep
|
||||
.equal([
|
||||
'shared/style/icons.css',
|
||||
'shared/elements/gaia-theme/style.css',
|
||||
'shared/js/component_utils.js',
|
||||
'shared/elements/gaia_subheader/script.js',
|
||||
'shared/style/tabs.css',
|
||||
'shared/elements/gaia-theme/other.css',
|
||||
'shared/js/gesture_detector.js'
|
||||
]);
|
||||
});
|
||||
});
|
||||
1
gaia/tools/html_transformer/test/mocha.opts
Executable file
1
gaia/tools/html_transformer/test/mocha.opts
Executable file
|
|
@ -0,0 +1 @@
|
|||
--ui tdd
|
||||
323
gaia/tools/png_recompress.sh
Executable file
323
gaia/tools/png_recompress.sh
Executable file
|
|
@ -0,0 +1,323 @@
|
|||
#!/bin/sh
|
||||
|
||||
###############################################################################
|
||||
# Option variables
|
||||
|
||||
opt_verbose=no
|
||||
opt_recursive=no
|
||||
opt_backup=no
|
||||
opt_keepgoing=no
|
||||
opt_optipng_args="-o7 -strip all -clobber -quiet"
|
||||
opt_optipng_args_nostrip="-o7 -clobber -quiet"
|
||||
opt_advpng_args="-z -4 -q"
|
||||
opt_help=no
|
||||
opt_paths=""
|
||||
|
||||
###############################################################################
|
||||
# Other globals
|
||||
|
||||
# Temporary directory and log file used through the script
|
||||
tmpdir=`mktemp -d -t tmp.XXXXXXXXXX`
|
||||
|
||||
if [ $? -ne 0 ]; then
|
||||
printf "error: Cannot create a temporary directory"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
tmplog=`mktemp -t tmp.XXXXXXXXXX`
|
||||
|
||||
if [ $? -ne 0 ]; then
|
||||
printf "error: Cannot create a temporary log file"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
trap 'rm -rf "$tmpdir $tmplog"' EXIT INT TERM HUP
|
||||
|
||||
size_cmd="printf 1" # Command used to obtain the size of a file
|
||||
total_src_size="0" # Total size of the source files
|
||||
total_comp_size="0" # Total size of the compressed files
|
||||
processed_files="0" # Number of files having been processed
|
||||
|
||||
###############################################################################
|
||||
# Print the help message
|
||||
|
||||
print_help() {
|
||||
printf "Usage: `basename $0` [OPTION...] [PATH...]
|
||||
Recompresses PNG files using the 'optipng' and 'advpng' tools.
|
||||
|
||||
Application options:
|
||||
-v, --verbose Print information on the files being processed
|
||||
-r, --recursive Recurse into subdirectories and process all files
|
||||
-b, --backup Keep a backup of the modified files
|
||||
-k, --keepgoing Do not stop in case of errors
|
||||
|
||||
Compression options:
|
||||
--optipng-args Arguments passed to optipng (default: $opt_optipng_args)
|
||||
--advpng-args Arguments passed to advpng (default: $opt_advpng_args)
|
||||
|
||||
Help options:
|
||||
-h, --help Show this help message
|
||||
"
|
||||
}
|
||||
|
||||
suggest_help() {
|
||||
printf "Try \``basename $0` --help' for more information\n"
|
||||
}
|
||||
|
||||
###############################################################################
|
||||
# Parse the program arguments
|
||||
|
||||
parse_args() {
|
||||
# Set the options variables depending on the passed arguments
|
||||
while [ $# -gt 0 ]; do
|
||||
if [ `expr "$1" : "^-"` -eq 1 ]; then
|
||||
if [ $1 = "-v" ] || [ $1 = "--verbose" ]; then
|
||||
shift
|
||||
opt_verbose=yes
|
||||
elif [ $1 = "-r" ] || [ $1 = "--recursive" ]; then
|
||||
shift
|
||||
opt_recursive=yes
|
||||
elif [ $1 = "-b" ] || [ $1 = "--backup" ]; then
|
||||
shift
|
||||
opt_backup=yes
|
||||
elif [ $1 = "-k" ] || [ $1 = "--keepgoing" ]; then
|
||||
shift
|
||||
opt_keepgoing=yes
|
||||
elif [ $1 = "--optipng-args" ]; then
|
||||
if [ $# -le 1 ]; then
|
||||
printf "error: No argument specified for --optipng-args\n"
|
||||
suggest_help
|
||||
exit 1
|
||||
else
|
||||
opt_optipng_args="$2"
|
||||
opt_optipng_args_nostrip=`echo $2 | sed -e 's/-strip all//'`
|
||||
shift 2
|
||||
fi
|
||||
elif [ $1 = "--advpng-args" ]; then
|
||||
if [ $# -le 1 ]; then
|
||||
printf "error: No argument specified for --advpng-args\n"
|
||||
suggest_help
|
||||
exit 1
|
||||
else
|
||||
opt_advpng_args="$2"
|
||||
shift 2
|
||||
fi
|
||||
elif [ $1 = "-h" ] || [ $1 = "--help" ]; then
|
||||
opt_help=yes
|
||||
shift 1
|
||||
else
|
||||
printf "error: Unknown option $1\n"
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
# No more arguments, the following parameters will be paths
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
if [ $# -eq 0 ] && [ $opt_help != yes ]; then
|
||||
printf "error: no files specified\n"
|
||||
suggest_help
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Gather all the paths
|
||||
while [ $# -gt 0 ]; do
|
||||
opt_paths="$opt_paths$1\n"
|
||||
shift
|
||||
done
|
||||
|
||||
# Adjust the arguments
|
||||
if [ $opt_backup = yes ]; then
|
||||
opt_optipng_args="$opt_optipng_args -backup"
|
||||
fi
|
||||
}
|
||||
|
||||
###############################################################################
|
||||
# Check for programs
|
||||
|
||||
check_optipng_version() {
|
||||
version=$(optipng --version | head -n 1)
|
||||
version="${version%%: Advanced PNG optimizer.}"
|
||||
version="${version##OptiPNG version }"
|
||||
version="${version##OptiPNG }"
|
||||
|
||||
if [ ${#version} -le 3 ]; then
|
||||
version="$version.0"
|
||||
fi
|
||||
|
||||
if [ "$version" \< 0.7.0 ]; then
|
||||
printf "error: the version of optipng you have installed ($version) is"\
|
||||
" too old, you need at least version 0.7.0 for this script to work correctly\n"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
check_programs() {
|
||||
which optipng 1>/dev/null 2>/dev/null
|
||||
|
||||
if [ $? -ne 0 ]; then
|
||||
printf "error: optipng not found\n"
|
||||
exit 1;
|
||||
fi
|
||||
|
||||
check_optipng_version
|
||||
|
||||
which advpng 1>/dev/null 2>/dev/null
|
||||
|
||||
if [ $? -ne 0 ]; then
|
||||
printf "error: advpng not found\n"
|
||||
exit 1;
|
||||
fi
|
||||
|
||||
if stat --printf %s . 1>/dev/null 2>/dev/null; then
|
||||
size_cmd="stat --printf %s"
|
||||
elif stat -f %z . 1>/dev/null 2>/dev/null; then
|
||||
size_cmd="stat -f %z"
|
||||
fi
|
||||
}
|
||||
|
||||
###############################################################################
|
||||
# Recompress all files in a directory, if recursive behavior was requested will
|
||||
# recurse in all subdirectories
|
||||
|
||||
recompress_dir() {
|
||||
local files
|
||||
|
||||
if [ $opt_recursive = yes ]; then
|
||||
files=`find "$1" -iname "*.png" -type f`
|
||||
else
|
||||
files=`find "$1" -maxdepth 1 -iname "*.png" -type f`
|
||||
fi
|
||||
|
||||
mkfifo "$tmpdir/files_pipe"
|
||||
printf -- "$files\n" > "$tmpdir/files_pipe" &
|
||||
|
||||
while read -r fd; do
|
||||
recompress_file "$fd"
|
||||
done < "$tmpdir/files_pipe"
|
||||
}
|
||||
|
||||
###############################################################################
|
||||
# Recompress a single file using the specified options
|
||||
|
||||
recompress_file() {
|
||||
local rv=0
|
||||
local apng=0
|
||||
local src_size=`$size_cmd "$1"`
|
||||
|
||||
if [ $opt_backup = yes ]; then
|
||||
if [ -e "$1.bak" ] ; then
|
||||
printf "error: backup file $1.bak already exists\n"
|
||||
|
||||
if [ $opt_keepgoing = no ]; then
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
cp -p "$1" "$1.bak"
|
||||
fi
|
||||
fi
|
||||
|
||||
optipng -o0 -simulate "$1" 1>"$tmplog" 2>"$tmplog"
|
||||
grep -q APNG "$tmplog"
|
||||
apng=$?
|
||||
|
||||
if [ $apng -eq 0 ]; then
|
||||
# Do not strip animated PNG files otherwise the frames will be gone
|
||||
optipng $opt_optipng_args_nostrip "$1"
|
||||
rv=$?
|
||||
else
|
||||
optipng $opt_optipng_args "$1"
|
||||
rv=$?
|
||||
fi
|
||||
|
||||
if [ $rv -ne 0 ]; then
|
||||
printf "error: optipng $opt_optipng_args \"$1\" returned $rv\n"
|
||||
|
||||
if [ $opt_keepgoing = no ]; then
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ $apng -ne 0 ]; then
|
||||
# Do not invoke advpng on animated PNG files as it will strip the frames
|
||||
advpng $opt_advpng_args "$1"
|
||||
rv=$?
|
||||
|
||||
if [ $rv -ne 0 ]; then
|
||||
printf "error: advpng $opt_optipng_args \"$1\" returned $rv\n"
|
||||
|
||||
if [ $opt_keepgoing = no ]; then
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
local comp_size=`$size_cmd "$1"`
|
||||
local ratio=`compression_ratio $src_size $comp_size`
|
||||
|
||||
if [ $opt_verbose = yes ]; then
|
||||
printf "$1 $src_size $comp_size $ratio\n"
|
||||
fi
|
||||
|
||||
total_src_size=$((total_src_size + src_size))
|
||||
total_comp_size=$((total_comp_size + comp_size))
|
||||
processed_files=$((processed_files + 1))
|
||||
}
|
||||
|
||||
###############################################################################
|
||||
# Prints out the compression ratio, $1 is the source file size, $2 is the
|
||||
# compressed file size
|
||||
|
||||
compression_ratio() {
|
||||
echo "scale=3;
|
||||
result = $2.0 / $1.0;
|
||||
if (result >= 0 && result < 1) {
|
||||
print \"0\";
|
||||
}
|
||||
|
||||
if (result == 0) {
|
||||
print \".\";
|
||||
}
|
||||
print result;" | bc
|
||||
}
|
||||
|
||||
###############################################################################
|
||||
# Main script
|
||||
|
||||
parse_args "$@"
|
||||
check_programs
|
||||
|
||||
if [ $opt_help = yes ] || [ $# -eq 0 ]; then
|
||||
print_help
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Process all the specified paths
|
||||
mkfifo "$tmpdir/paths_pipe"
|
||||
printf -- "$opt_paths" > "$tmpdir/paths_pipe" &
|
||||
|
||||
while read -r fd; do
|
||||
if [ -f "$fd" ]; then
|
||||
recompress_file "$fd"
|
||||
elif [ -d "$fd" ]; then
|
||||
recompress_dir "$fd"
|
||||
elif ! [ -e "$fd" ]; then
|
||||
printf "error: $fd no such file or directory\n"
|
||||
|
||||
if [ $opt_keepgoing = no ]; then
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
printf "warning: skipping $fd, as it is neither a file nor a directory\n"
|
||||
fi
|
||||
done < "$tmpdir/paths_pipe"
|
||||
|
||||
if [ $opt_verbose = yes ]; then
|
||||
printf "
|
||||
Number of files processed: $processed_files
|
||||
Total size of the files prior to recompression: $total_src_size
|
||||
Total size of the files after recompression: $total_comp_size
|
||||
Compression ratio: `compression_ratio $total_src_size $total_comp_size`
|
||||
"
|
||||
fi
|
||||
211
gaia/tools/pre-commit
Executable file
211
gaia/tools/pre-commit
Executable file
|
|
@ -0,0 +1,211 @@
|
|||
#!/bin/bash
|
||||
|
||||
export LC_ALL=C
|
||||
|
||||
############################### JS Lint #####################################
|
||||
|
||||
LINTED_DIRECTORIES="apps shared build/test/unit tv_apps"
|
||||
JSHINT_EXECUTABLE="node_modules/.bin/jshint"
|
||||
JSHINT_ARGS="--reporter=build/jshint/reporter $JSHINT_ARGS"
|
||||
ESLINT_EXECUTABLE="node_modules/.bin/eslint"
|
||||
ESLINT_XFAIL_LIST="build/eslint/xfail.list"
|
||||
ESLINT_ARGS="--ignore-path ${ESLINT_XFAIL_LIST}.tmp -f compact -c .eslintrc"
|
||||
excluded_list=".jshintignore"
|
||||
L10N_XFAIL_LIST="build/l10n/xfail.list"
|
||||
|
||||
jshint_information() {
|
||||
echo "Please read https://github.com/mozilla-b2g/gaia/tree/master/build/jshint/README.md for more information.\n"
|
||||
}
|
||||
|
||||
mozL10n_information() {
|
||||
echo "Please read https://github.com/mozilla-b2g/gaia/tree/master/build/l10n/README.md for more information.\n"
|
||||
}
|
||||
eslint_information() {
|
||||
echo "Please read https://github.com/mozilla-b2g/gaia/tree/master/build/eslint/README.md for more information.\n"
|
||||
}
|
||||
|
||||
|
||||
if [ ! -r $excluded_list ];
|
||||
then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
hash node > /dev/null 2>&1
|
||||
if [ $? -eq 1 ];
|
||||
then
|
||||
echo >&2 "You should install node to lint your patch"
|
||||
echo >&2 "http://nodejs.org/download/"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [ ! -x "$JSHINT_EXECUTABLE" ];
|
||||
then
|
||||
echo >&2 'You should run `npm install` to lint your patch'
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Skips if this repo is on an older commit where eslint is not yet available
|
||||
if [ ! -x "$ESLINT_EXECUTABLE" -a -r ".eslintrc" ];
|
||||
then
|
||||
echo >&2 'You should run `npm install` to lint your patch'
|
||||
exit 0
|
||||
fi
|
||||
|
||||
js_changed_files=`git diff --staged --name-only --diff-filter=ACMRT -- $LINTED_DIRECTORIES | grep '\.js$'`
|
||||
if [ -n "$js_changed_files" ]
|
||||
then
|
||||
|
||||
echo "jshint check:"
|
||||
|
||||
# using the real jshint instead of the make target to prevent running npm
|
||||
# install at commit time
|
||||
$JSHINT_EXECUTABLE $JSHINT_ARGS $js_changed_files
|
||||
jshint_result=$?
|
||||
|
||||
eslint_result=0
|
||||
# Skip if this repo is on an older commit where eslint is not yet available
|
||||
if [ -r ".eslintrc" ];
|
||||
then
|
||||
echo "eslint check:"
|
||||
|
||||
# filter comments from xfail.list:
|
||||
sed 's/\s*#.*$//' "$ESLINT_XFAIL_LIST" | sort -u > "$ESLINT_XFAIL_LIST".tmp
|
||||
|
||||
# check if a changed file is in the eslint xfail list
|
||||
xfailed_eslint=`echo "$js_changed_files" | sort | comm -12 "$ESLINT_XFAIL_LIST".tmp - | tr '[:space:]' ' '`
|
||||
|
||||
if [ -n "$xfailed_eslint" ] ; then
|
||||
# warn about files being xfailed
|
||||
for file in $js_changed_files ; do
|
||||
one_was_ignored=""
|
||||
if grep -q $file "$ESLINT_XFAIL_LIST" ; then
|
||||
one_was_ignored="1"
|
||||
echo "$file: eslint errors are ignored. Please consider fixing these errors and removing the filename from $ESLINT_XFAIL_LIST."
|
||||
fi
|
||||
done
|
||||
test -n "$one_was_ignored" && eslint_information
|
||||
fi
|
||||
|
||||
$ESLINT_EXECUTABLE $ESLINT_ARGS $js_changed_files
|
||||
eslint_result=$?
|
||||
rm "$ESLINT_XFAIL_LIST".tmp
|
||||
fi
|
||||
|
||||
if [ $jshint_result -ne 0 -o $eslint_result -ne 0 ] ; then
|
||||
echo "There were errors while linting the files, please see above."
|
||||
test $jshint_result -ne 0 && jshint_information
|
||||
test $eslint_result -ne 0 && eslint_information
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# XXX migrate this to an eslint plugin: https://bugzil.la/1159667
|
||||
if [ -r "$L10N_XFAIL_LIST" ]; then
|
||||
echo
|
||||
echo "mozL10n check:"
|
||||
# warn about files being xfailed
|
||||
for file in $js_changed_files ; do
|
||||
one_was_ignored=""
|
||||
if grep -q $file "$L10N_XFAIL_LIST" ; then
|
||||
one_was_ignored="1"
|
||||
echo "$file: mozL10n errors are ignored. Please consider fixing errors and removing it from $L10N_XFAIL_LIST."
|
||||
fi
|
||||
done
|
||||
|
||||
test -n "$one_was_ignored" && mozL10n_information
|
||||
|
||||
to_lint=`echo "$js_changed_files" | sort | comm -13 "$L10N_XFAIL_LIST" - | tr '[:space:]' ' '`
|
||||
# check for deprecated mozL10n API uses
|
||||
for file in $to_lint ; do
|
||||
one_failed=""
|
||||
if grep -q "mozL10n.get\b" "$file" ; then
|
||||
one_failed="1"
|
||||
echo "$file uses mozL10n.get which is deprecated."
|
||||
fi
|
||||
done
|
||||
|
||||
test -n "$one_failed" && mozL10n_information && exit 1
|
||||
fi
|
||||
|
||||
|
||||
fi
|
||||
|
||||
|
||||
############################### CSS Lint #####################################
|
||||
|
||||
css_changed_files=`git diff --staged --name-only --diff-filter=ACMRT -- apps shared/ | grep '\.css$' | tr '\n' ' '`
|
||||
if [ -n "$css_changed_files" ]
|
||||
then
|
||||
XULRUNNER_SDK=$(make xulrunner_sdk)
|
||||
XPCSHELL_SDK=$(make xpcshell_sdk)
|
||||
|
||||
# $XULRUNNER_SDK is an environment variable assignment on some platforms, for
|
||||
# example: LD_LIBRARY_PATH=/xxx/xxx/xxx
|
||||
# so we need to use "env" to properly execute this command expanding this
|
||||
# variable assignment as intended.
|
||||
env GAIA_DIR=$(pwd) $XULRUNNER_SDK $XPCSHELL_SDK \
|
||||
-f build/xpcshell-commonjs.js \
|
||||
-e "quit(require('csslint').lint('$(pwd)', '$css_changed_files'));"
|
||||
|
||||
if [ $? -ne 0 ]
|
||||
then
|
||||
echo "There were errors while linting the files, please see above."
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "No errors - committing"
|
||||
|
||||
############################# locale check #####################################
|
||||
locales_changed_files=`git diff --staged --name-only --diff-filter=ACMRT | grep '\.properties$'`
|
||||
if [ -n "$locales_changed_files" ]
|
||||
then
|
||||
updatedL10nIds=()
|
||||
needToHintDevelopers=0
|
||||
|
||||
for file in $locales_changed_files ; do
|
||||
grepRuleForBoth="^[+-].*=.*"
|
||||
grepRuleForAddition="^[+](.*)="
|
||||
grepRuleForDeletion="^[-](.*)="
|
||||
|
||||
addition_locale_ids=()
|
||||
deletion_locale_ids=()
|
||||
count=0
|
||||
|
||||
while read -r eachLine
|
||||
do
|
||||
if [[ "$eachLine" =~ $grepRuleForAddition ]]
|
||||
then
|
||||
addition_locale_ids+=(${BASH_REMATCH[1]})
|
||||
fi
|
||||
|
||||
if [[ "$eachLine" =~ $grepRuleForDeletion ]]
|
||||
then
|
||||
deletion_locale_ids+=(${BASH_REMATCH[1]})
|
||||
fi
|
||||
# check http://mywiki.wooledge.org/BashFAQ/024
|
||||
done < <(git diff HEAD~ --staged $file | grep $grepRuleForBoth)
|
||||
|
||||
# final compare
|
||||
for eachAdditionId in "${addition_locale_ids[@]}"
|
||||
do
|
||||
for eachDeletionId in "${deletion_locale_ids[@]}"
|
||||
do
|
||||
if [[ "$eachAdditionId" =~ "$eachDeletionId" ]]
|
||||
then
|
||||
needToHintDevelopers=1
|
||||
updatedL10nIds+=($eachAdditionId)
|
||||
fi
|
||||
done
|
||||
done
|
||||
done
|
||||
|
||||
if [ $needToHintDevelopers -ne 0 ]
|
||||
then
|
||||
echo "[NOTE] You are updating the same translation under these locale ids :"
|
||||
for eachL10nId in "${updatedL10nIds[@]}"
|
||||
do
|
||||
echo "Updated id : $eachL10nId"
|
||||
done
|
||||
echo "Please double check whether you should introduce a new locale id or not."
|
||||
fi
|
||||
fi
|
||||
367
gaia/tools/preload.py
Executable file
367
gaia/tools/preload.py
Executable file
|
|
@ -0,0 +1,367 @@
|
|||
# preload.py comes from https://github.com/mozilla-b2g/preload-app-toolkit/
|
||||
# DO NOT EDIT DIRECTLY, make PR's to above repo and copy across file
|
||||
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import sys
|
||||
import urllib
|
||||
import base64
|
||||
import mimetypes
|
||||
import os
|
||||
import json
|
||||
import codecs
|
||||
from urlparse import urlparse
|
||||
from zipfile import ZipFile
|
||||
from datetime import datetime
|
||||
import time
|
||||
import shutil
|
||||
import re
|
||||
import logging
|
||||
import argparse
|
||||
|
||||
APPCACHE_LOCAL_DEFAULT_PATH = 'cache/'
|
||||
APPCACHE_LOCAL_DEFAULT_NAME = 'manifest.appcache'
|
||||
|
||||
logger = logging.getLogger('GaiaPreloadApp')
|
||||
logger.setLevel(logging.INFO)
|
||||
sh = logging.StreamHandler()
|
||||
logger.addHandler(sh)
|
||||
|
||||
def set_logger_level(lv):
|
||||
logger.setLevel(lv)
|
||||
|
||||
def has_scheme(url):
|
||||
return bool(url.scheme)
|
||||
|
||||
|
||||
def retrieve_from_url(url, target):
|
||||
"""
|
||||
save file from url
|
||||
"""
|
||||
if (os.path.isdir(target)):
|
||||
target += 'index.html'
|
||||
return urllib.urlretrieve(url, target)
|
||||
|
||||
|
||||
def open_from_url(url):
|
||||
"""
|
||||
return file object from url
|
||||
"""
|
||||
return urllib.urlopen(url)
|
||||
|
||||
|
||||
def get_absolute_url(domain, path, icon):
|
||||
icon_path = None
|
||||
origin = urlparse(''.join([domain, path]))
|
||||
if has_scheme(icon):
|
||||
return icon.geturl()
|
||||
if icon.path[0] == '/':
|
||||
icon_path = icon.path
|
||||
else:
|
||||
icon_path = '%s/%s' % (os.path.dirname(origin.path), icon.path)
|
||||
|
||||
if(path.startswith('http')):
|
||||
return icon_path
|
||||
else:
|
||||
return '%s://%s%s' % (origin.scheme, origin.netloc, icon_path)
|
||||
|
||||
|
||||
def get_directory_name(appname):
|
||||
return re.sub(r'[\W\s]', '', appname).lower()
|
||||
|
||||
|
||||
def split_url(manifest_url):
|
||||
path = None
|
||||
url = urlparse(manifest_url)
|
||||
domain = '%s://%s' % (url.scheme, url.netloc)
|
||||
if url.path.count('/') > 1:
|
||||
path = ''.join([os.path.dirname(url.path), '/'])
|
||||
else:
|
||||
path = '/'
|
||||
return (domain, path)
|
||||
|
||||
|
||||
def convert_icon(image, mimetype):
|
||||
return 'data:%s;base64,%s' % (mimetype, base64.b64encode(image))
|
||||
|
||||
|
||||
def fetch_icon_from_url(url):
|
||||
image = open_from_url(url).read()
|
||||
return convert_icon(image, mimetypes.guess_type(url)[0])
|
||||
|
||||
|
||||
def fetch_icon(key, icons, domain, path, apppath):
|
||||
iconurl = get_absolute_url(domain, path,
|
||||
urlparse(icons[key]))
|
||||
icon_base64 = ''
|
||||
if iconurl[0] == '/':
|
||||
logger.info('locally...')
|
||||
icon_base64 = iconurl
|
||||
# fetch icon from url
|
||||
elif (iconurl.startswith('http') and
|
||||
(iconurl.endswith(".png") or iconurl.endswith(".jpg"))):
|
||||
logger.info(' ' + key + ' from internet...',)
|
||||
subfix = "/icon.png" if iconurl.endswith(".png") else "/icon.jpg"
|
||||
retrieve_from_url(iconurl, apppath + subfix)
|
||||
with open(apppath + subfix) as fd:
|
||||
image = fd.read()
|
||||
icon_base64 = convert_icon(image,
|
||||
mimetypes.guess_type(iconurl)[0])
|
||||
os.remove(apppath + subfix)
|
||||
# fetch icon from local
|
||||
else:
|
||||
icon_base64 = fetch_icon_from_url(iconurl)
|
||||
return icon_base64
|
||||
|
||||
def get_resource_path_and_url(domain, workingdir, resource_path):
|
||||
if has_scheme(urlparse(resource_path)):
|
||||
resource_url = resource_path
|
||||
elif resource_path[0] == '/':
|
||||
resource_url = ''.join([domain, resource_path])
|
||||
resource_path = resource_path.lstrip('/')
|
||||
else:
|
||||
resource_url = ''.join([domain, workingdir, resource_path])
|
||||
resource_path = ''.join([workingdir, resource_path]).lstrip('/')
|
||||
return (resource_url, resource_path)
|
||||
|
||||
def fetch_resource(domain, workingdir, local_dir, resource_path):
|
||||
"""
|
||||
fetch resource described in appcache manifest
|
||||
"""
|
||||
try:
|
||||
url, path = get_resource_path_and_url(domain, workingdir, resource_path)
|
||||
# create directories if not exist
|
||||
local_resource_path = ''.join([local_dir, path])
|
||||
local_resource_dir = os.path.dirname(local_resource_path)
|
||||
if not os.path.exists(local_resource_dir):
|
||||
os.makedirs(local_resource_dir)
|
||||
|
||||
(_filename, _headers) = retrieve_from_url(url, local_resource_path)
|
||||
# I need to save the header at some place so they can be written (and processed at install time)
|
||||
return (path.lstrip('/'), _headers)
|
||||
except IOError as e:
|
||||
logger.info('IO failed ', e)
|
||||
except urllib.URLError as e:
|
||||
logger.info('fetch failed ', e)
|
||||
|
||||
|
||||
def get_appcache_manifest_dir(manifest_dir, appcache_path):
|
||||
logger.info('Manifest dir: ' + manifest_dir + '. appcache_path: ' + appcache_path)
|
||||
if (appcache_path[0] != '/'):
|
||||
result = os.path.join(manifest_dir, os.path.dirname(appcache_path))
|
||||
else:
|
||||
result = os.path.dirname(appcache_path)
|
||||
return result if result[-1] == '/' else result + '/'
|
||||
|
||||
|
||||
def get_appcache_manifest(domain, manifest_dir, app_dir, appcache_path):
|
||||
manifest = {
|
||||
'remote_dir': get_appcache_manifest_dir(manifest_dir, appcache_path),
|
||||
'filename': os.path.basename(appcache_path),
|
||||
'local_dir': os.path.join(app_dir, APPCACHE_LOCAL_DEFAULT_PATH)
|
||||
}
|
||||
manifest['local_path'] = os.path.join(manifest[
|
||||
'local_dir'], APPCACHE_LOCAL_DEFAULT_NAME)
|
||||
origin = urlparse(domain)
|
||||
base_path = '%s://%s' % (origin.scheme, origin.netloc)
|
||||
manifest['url'] = ''.join([base_path,
|
||||
manifest['remote_dir'],
|
||||
manifest['filename']])
|
||||
return manifest
|
||||
|
||||
|
||||
def format_resource_metadata(file, header):
|
||||
"""
|
||||
documentation here
|
||||
"""
|
||||
|
||||
lastModified = header.getheader('last-modified', 'Epoch')
|
||||
lastFetched = header.getheader('date', 'Now')
|
||||
return '"' + file + '": { "lastModified": "' + lastModified + '", "lastFetched": "' + lastFetched + '"}'
|
||||
|
||||
|
||||
def fetch_appcache(domain, remote_dir, local_dir, lines):
|
||||
"""
|
||||
fetch appcache file described in manifest.webapp
|
||||
|
||||
output:
|
||||
|
||||
[appname]/cache/[name].appcache
|
||||
[appname]/cache/[resources] (if defined)
|
||||
"""
|
||||
newlines = []
|
||||
headers = []
|
||||
try:
|
||||
# retrieve resources from appcache
|
||||
curr = None
|
||||
SECTIONS = ['CACHE MANIFEST', 'NETWORK:', 'CACHE:', 'FALLBACK:']
|
||||
for line in lines:
|
||||
line = line.strip()
|
||||
if line in SECTIONS:
|
||||
curr = line
|
||||
newlines.append(line)
|
||||
continue
|
||||
if len(line) == 0 or line[0] == '#':
|
||||
newlines.append(line)
|
||||
continue
|
||||
|
||||
if curr == SECTIONS[0] or curr == SECTIONS[2]:
|
||||
logger.info(' get resource ' + line + '...')
|
||||
(line, header) = fetch_resource(domain, remote_dir, local_dir, line)
|
||||
elif curr == SECTIONS[3]:
|
||||
logger.info(' get resource ' + line + '...')
|
||||
(_line, header) = fetch_resource(domain, remote_dir, local_dir,
|
||||
line.split(' ')[1])
|
||||
newlines.append(line)
|
||||
headers.append(format_resource_metadata(line, header))
|
||||
except Exception as e:
|
||||
logger.info(' fetch failed ', e)
|
||||
return (newlines, headers)
|
||||
|
||||
|
||||
def fetch_webapp(app_url, directory=None):
|
||||
"""
|
||||
get webapp file and parse for preinstalled webapp
|
||||
|
||||
output:
|
||||
|
||||
[appname]/manifest.webapp
|
||||
[appname]/metadata.json
|
||||
[appname]/update.webapp (if package_path is defined)
|
||||
[appname]/cache/ (if appcache_path is defined)
|
||||
"""
|
||||
domain, path = split_url(app_url)
|
||||
url = urlparse(app_url)
|
||||
metadata = {'origin': domain}
|
||||
manifest_filename = 'manifest.webapp'
|
||||
|
||||
if url.scheme:
|
||||
logger.info('manifest: ' + app_url)
|
||||
logger.info('fetching manifest...')
|
||||
manifest_url = open_from_url(app_url)
|
||||
manifest = json.loads(manifest_url.read().decode('utf-8-sig'))
|
||||
metadata['installOrigin'] = 'https://marketplace.firefox.com'
|
||||
if 'etag' in manifest_url.headers:
|
||||
metadata['etag'] = manifest_url.headers['etag']
|
||||
else:
|
||||
logger.info('extract manifest from zip...')
|
||||
appzip = ZipFile(app_url, 'r').read('manifest.webapp')
|
||||
manifest = json.loads(appzip.decode('utf-8-sig'))
|
||||
|
||||
appname = get_directory_name(manifest['name'])
|
||||
app_dir = appname
|
||||
if directory is not None:
|
||||
app_dir = os.path.join(directory, appname)
|
||||
|
||||
if not os.path.exists(app_dir):
|
||||
os.mkdir(app_dir)
|
||||
|
||||
if 'package_path' in manifest or not url.scheme:
|
||||
manifest_filename = 'update.webapp'
|
||||
filename = 'application.zip'
|
||||
metadata.pop('origin', None)
|
||||
|
||||
if url.scheme:
|
||||
logger.info('downloading app...')
|
||||
path = manifest['package_path']
|
||||
retrieve_from_url(
|
||||
manifest['package_path'],
|
||||
os.path.join(app_dir, filename))
|
||||
metadata['manifestURL'] = url.geturl()
|
||||
|
||||
package_url = open_from_url(path)
|
||||
if 'etag' in package_url.headers:
|
||||
metadata['packageEtag'] = package_url.headers['etag']
|
||||
else:
|
||||
logger.info('copying app...')
|
||||
shutil.copyfile(app_url, '%s%s%s' % (appname, os.sep, filename))
|
||||
metadata['manifestURL'] = ''.join(
|
||||
[domain, path, 'manifest.webapp'])
|
||||
|
||||
manifest['package_path'] = ''.join(['/', filename])
|
||||
|
||||
logger.info('fetching icons...')
|
||||
for key in manifest['icons']:
|
||||
manifest['icons'][key] = fetch_icon(
|
||||
key, manifest['icons'], domain, path, app_dir)
|
||||
|
||||
if 'appcache_path' in manifest:
|
||||
metadata_info = [];
|
||||
logger.info('fetching appcache...',)
|
||||
appcache_manifest = get_appcache_manifest(
|
||||
domain, path, app_dir, manifest['appcache_path'])
|
||||
if not os.path.exists(appcache_manifest['local_dir']):
|
||||
os.makedirs(appcache_manifest['local_dir'])
|
||||
|
||||
logger.info(' from ' + appcache_manifest['url'])
|
||||
logger.info(' save to ' + appcache_manifest['local_path'],)
|
||||
( _filename, headerAC ) = retrieve_from_url(appcache_manifest['url'],
|
||||
appcache_manifest['local_path'])
|
||||
lines = []
|
||||
with open(appcache_manifest['local_path']) as fd:
|
||||
lines = fd.readlines()
|
||||
|
||||
(lines, headers) = fetch_appcache(domain, appcache_manifest['remote_dir'],
|
||||
appcache_manifest['local_dir'], lines)
|
||||
with open(appcache_manifest['local_path'], 'w') as fd:
|
||||
logger.info('overwrite new appcache')
|
||||
lines.append('')
|
||||
fd.write('\n'.join(lines))
|
||||
with open(appcache_manifest['local_dir'] + '../resources_metadata.json', 'w') as resources:
|
||||
resources.write('{\n');
|
||||
headers.append(format_resource_metadata(appcache_manifest['local_path'], headerAC))
|
||||
resources.write(',\n'.join(headers))
|
||||
resources.write('\n}\n');
|
||||
|
||||
# add manifestURL for update
|
||||
metadata['manifestURL'] = app_url
|
||||
metadata['external'] = True
|
||||
|
||||
f = file(os.path.join(app_dir, 'metadata.json'), 'w')
|
||||
f.write(json.dumps(metadata))
|
||||
f.close()
|
||||
|
||||
f = codecs.open(os.path.join(app_dir, manifest_filename), 'w', 'utf-8')
|
||||
f.write(json.dumps(manifest, ensure_ascii=False))
|
||||
return manifest
|
||||
|
||||
|
||||
def main():
|
||||
|
||||
parser = argparse.ArgumentParser(description = 'Preload webapps.')
|
||||
parser.add_argument('--root', default=os.getcwd(),
|
||||
help = 'Root directory to run from')
|
||||
parser.add_argument('--icon',
|
||||
help = 'Fetch webapp icon')
|
||||
parser.add_argument('webapp', nargs = '?')
|
||||
args = parser.parse_args()
|
||||
|
||||
os.chdir(args.root)
|
||||
|
||||
print args.root
|
||||
|
||||
# icon convertion script
|
||||
if (args.icon):
|
||||
result = fetch_icon_from_url(args.icon)
|
||||
logger.info(result.replace('/', '\/'))
|
||||
|
||||
# fetch single webapp
|
||||
elif (args.webapp):
|
||||
fetch_webapp(args.webapp)
|
||||
|
||||
else:
|
||||
# automatically read and compose customized webapp from list
|
||||
# support csv like list format with ',' separator, ex:
|
||||
#
|
||||
# Youtube,http://m.youtube.com/mozilla_youtube_webapp
|
||||
with open('list') as fd:
|
||||
while True:
|
||||
line = fd.readline()
|
||||
if (len(line.split(',')) > 1):
|
||||
fetch_webapp(line.split(',')[1].rstrip('\n'))
|
||||
else:
|
||||
break
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
95
gaia/tools/svg_recompress.sh
Executable file
95
gaia/tools/svg_recompress.sh
Executable file
|
|
@ -0,0 +1,95 @@
|
|||
#!/bin/sh
|
||||
|
||||
###############################################################################
|
||||
# Compress SVG files using svgo (https://github.com/svg/svgo)
|
||||
#
|
||||
# The following svgo plugins are disabled:
|
||||
# * convertPathData
|
||||
# Though the resulting files are OK in any browsers, they're rendered in a
|
||||
# weird way on Ubuntu files explorer.
|
||||
# * mergePaths
|
||||
# Can result on wrong odd/even filling if some paths are overlapping.
|
||||
|
||||
###############################################################################
|
||||
# Option variables
|
||||
|
||||
opt_help=no
|
||||
opt_paths=""
|
||||
|
||||
###############################################################################
|
||||
# Print the help message
|
||||
|
||||
print_help() {
|
||||
printf "Usage: `basename $0` [OPTION...] [PATH...]
|
||||
Recompresses SVG files using 'svgo' tools.
|
||||
|
||||
Help options:
|
||||
-h, --help Show this help message
|
||||
"
|
||||
}
|
||||
|
||||
suggest_help() {
|
||||
printf "Try \``basename $0` --help' for more information\n"
|
||||
}
|
||||
|
||||
###############################################################################
|
||||
# Parse the program arguments
|
||||
|
||||
parse_args() {
|
||||
# Set the options variables depending on the passed arguments
|
||||
while [ $# -gt 0 ]; do
|
||||
if [ `expr "$1" : "^-"` -eq 1 ]; then
|
||||
if [ $1 = "-h" ] || [ $1 = "--help" ]; then
|
||||
opt_help=yes
|
||||
shift 1
|
||||
else
|
||||
printf "error: Unknown option $1\n"
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
# No more arguments, the following parameters will be paths
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
if [ $# -eq 0 ] && [ $opt_help != yes ]; then
|
||||
printf "error: no files specified\n"
|
||||
suggest_help
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Gather all the paths
|
||||
while [ $# -gt 0 ]; do
|
||||
opt_paths="$opt_paths$1\n"
|
||||
shift
|
||||
done
|
||||
}
|
||||
|
||||
###############################################################################
|
||||
# Check for program
|
||||
|
||||
check_program() {
|
||||
which svgo 1>/dev/null 2>/dev/null
|
||||
|
||||
if [ $? -ne 0 ]; then
|
||||
printf "error: svgo not found\n"
|
||||
exit 1;
|
||||
fi
|
||||
}
|
||||
|
||||
###############################################################################
|
||||
# Main script
|
||||
|
||||
parse_args "$@"
|
||||
check_program
|
||||
|
||||
if [ $opt_help = yes ] || [ $# -eq 0 ]; then
|
||||
print_help
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Process all the specified paths
|
||||
while [ $# -gt 0 ]; do
|
||||
svgo --disable=convertPathData --disable=mergePaths --multipass --folder="$1"
|
||||
shift
|
||||
done
|
||||
Loading…
Reference in a new issue