Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Merge branch 'master' into Bluetooth

  • Loading branch information...
commit 3bf5a252fbaf7c89f97ad47185545865a1c345b9 2 parents 9c0745e + 3e162bb
Eric Chou authored
Showing with 176,953 additions and 142,429 deletions.
  1. +1 −0  .gitignore
  2. +10 −27 LICENCE
  3. +78 −10 Makefile
  4. +5 −7 apps/browser/index.html
  5. +84 −146 apps/browser/js/browser.js
  6. +139 −0 apps/browser/js/global_history.js
  7. +46 −0 apps/browser/js/session_history.js
  8. +15 −3 apps/browser/manifest.json
  9. +20 −13 apps/browser/style/browser.css
  10. BIN  apps/browser/style/images/favicon.png
  11. BIN  apps/browser/style/images/go.png
  12. BIN  apps/browser/style/images/loading.gif
  13. BIN  apps/browser/style/images/progress.gif
  14. BIN  apps/browser/style/images/refresh.png
  15. +12 −0 apps/calculator/manifest.json
  16. +12 −0 apps/camera/manifest.json
  17. +18 −0 apps/clock/locale/clock.properties
  18. +12 −0 apps/clock/manifest.json
  19. +12 −0 apps/crystalskull/manifest.json
  20. +12 −0 apps/cubevid/manifest.json
  21. +11 −0 apps/dialer/background.html
  22. +74 −0 apps/dialer/js/background.js
  23. +1 −0  apps/dialer/js/contacts.js
  24. +31 −0 apps/dialer/js/settings_listener.js
  25. +53 −6 apps/dialer/locale/dialer.properties
  26. +15 −1 apps/dialer/manifest.json
  27. 0  apps/{homescreen → dialer}/style/ringtones/README
  28. 0  apps/{homescreen → dialer}/style/ringtones/bosscaling.wav
  29. BIN  apps/dialer/style/ringtones/classic.wav
  30. 0  apps/{homescreen → dialer}/style/ringtones/goodnight.wav
  31. +1 −0  apps/gallery/index.html
  32. +806 −0 apps/gallery/js/GestureDetector.js
  33. +509 −0 apps/gallery/js/SyntheticGestures.js
  34. +540 −126 apps/gallery/js/gallery.js
  35. +9 −0 apps/gallery/locale/gallery.properties
  36. +14 −2 apps/gallery/manifest.json
  37. BIN  apps/gallery/sample_photos/3548856279_a215152cd5_o.jpg
  38. BIN  apps/gallery/sample_photos/3549661880_0c5565a518_o.jpg
  39. BIN  apps/gallery/sample_photos/3549662882_8e41d11d28_o.jpg
  40. BIN  apps/gallery/sample_photos/3551599565_db282cf840_o.jpg
  41. BIN  apps/gallery/sample_photos/6839255446_2f245d8f0c.jpg
  42. BIN  apps/gallery/sample_photos/6985376089_db00e0d18c_o.jpg
  43. BIN  apps/gallery/sample_photos/DSC_1677.jpg
  44. BIN  apps/gallery/sample_photos/DSC_1701.jpg
  45. BIN  apps/gallery/sample_photos/DSC_1727.jpg
  46. BIN  apps/gallery/sample_photos/DSC_1729.jpg
  47. BIN  apps/gallery/sample_photos/DSC_1759.jpg
  48. BIN  apps/gallery/sample_photos/DSC_4236.jpg
  49. BIN  apps/gallery/sample_photos/DSC_4767.jpg
  50. BIN  apps/gallery/sample_photos/DSC_4858.jpg
  51. BIN  apps/gallery/sample_photos/DSC_4861.jpg
  52. BIN  apps/gallery/sample_photos/DSC_4903.jpg
  53. BIN  apps/gallery/sample_photos/DSC_7150.jpg
  54. BIN  apps/gallery/sample_photos/IMG_0139.jpg
  55. BIN  apps/gallery/sample_photos/IMG_0160.jpg
  56. BIN  apps/gallery/sample_photos/IMG_0211.jpg
  57. BIN  apps/gallery/sample_photos/IMG_0225.jpg
  58. BIN  apps/gallery/sample_photos/IMG_0251.jpg
  59. BIN  apps/gallery/sample_photos/IMG_0281.jpg
  60. BIN  apps/gallery/sample_photos/IMG_0476.jpg
  61. BIN  apps/gallery/sample_photos/IMG_0498.jpg
  62. BIN  apps/gallery/sample_photos/IMG_0506.jpg
  63. BIN  apps/gallery/sample_photos/thumbnails/3548856279_a215152cd5_o.jpg
  64. BIN  apps/gallery/sample_photos/thumbnails/3549661880_0c5565a518_o.jpg
  65. BIN  apps/gallery/sample_photos/thumbnails/3549662882_8e41d11d28_o.jpg
  66. BIN  apps/gallery/sample_photos/thumbnails/3551599565_db282cf840_o.jpg
  67. BIN  apps/gallery/sample_photos/thumbnails/6839255446_2f245d8f0c.jpg
  68. BIN  apps/gallery/sample_photos/thumbnails/6985376089_db00e0d18c_o.jpg
  69. BIN  apps/gallery/sample_photos/thumbnails/DSC_7150.jpg
  70. BIN  apps/gallery/sample_photos/thumbnails/IMG_0139.jpg
  71. BIN  apps/gallery/sample_photos/thumbnails/IMG_0160.jpg
  72. BIN  apps/gallery/sample_photos/thumbnails/IMG_0211.jpg
  73. BIN  apps/gallery/sample_photos/thumbnails/IMG_0225.jpg
  74. BIN  apps/gallery/sample_photos/thumbnails/IMG_0251.jpg
  75. BIN  apps/gallery/sample_photos/thumbnails/IMG_0281.jpg
  76. BIN  apps/gallery/sample_photos/thumbnails/IMG_0476.jpg
  77. BIN  apps/gallery/sample_photos/thumbnails/IMG_0498.jpg
  78. BIN  apps/gallery/sample_photos/thumbnails/IMG_0506.jpg
  79. +18 −10 apps/gallery/style/gallery.css
  80. +0 −125,946 apps/homescreen/imes/jszhuyin/phrases.json
  81. +0 −14,175 apps/homescreen/imes/jszhuyin/words.json
  82. +15 −100 apps/homescreen/index.html
  83. +0 −15 apps/homescreen/js/app_manager.js
  84. +41 −1,192 apps/homescreen/js/homescreen.js
  85. +50 −24 apps/homescreen/js/request.js
  86. +54 −0 apps/homescreen/js/settings.js
  87. +12 −1 apps/homescreen/manifest.json
  88. 0  apps/homescreen/style/{themes/default → }/backgrounds/default.png
  89. 0  apps/homescreen/style/{themes/default → }/backgrounds/leaves.png
  90. 0  apps/homescreen/style/{themes/default → }/backgrounds/water.png
  91. 0  apps/homescreen/style/{themes/default → }/fonts/Open-Sans-Bold.woff
  92. 0  apps/homescreen/style/{themes/default → }/fonts/Open-Sans-Semibold.woff
  93. 0  apps/homescreen/style/{themes/default → }/fonts/Open-Sans.woff
  94. +68 −330 apps/homescreen/style/homescreen.css
  95. +42 −0 apps/homescreen/style/request.css
  96. BIN  apps/homescreen/style/ringtones/classic.wav
  97. BIN  apps/homescreen/style/themes/default/images/sleep/vibration.png
  98. 0  apps/homescreen/style/themes/default/statusbar.css
  99. +13 −1 apps/market/manifest.json
  100. +12 −0 apps/music/manifest.json
  101. +28 −0 apps/settings/index.html
  102. +1 −1  apps/settings/js/settings.js
  103. +282 −30 apps/settings/locale/settings.properties
  104. +14 −2 apps/settings/manifest.json
  105. +4 −4 apps/sms/js/sms.js
  106. +9 −0 apps/sms/js/utils.js
  107. +19 −1 apps/sms/locale/sms.properties
  108. +14 −2 apps/sms/manifest.json
  109. +1 −0  apps/{homescreen → system}/blank.html
  110. +127 −0 apps/system/index.html
  111. +12 −11 apps/{homescreen → system}/js/background_service.js
  112. +87 −0 apps/system/js/bookmarks/bookmarks.js
  113. 0  apps/{homescreen/js → system/js/bookmarks}/bookmarks.json
  114. +414 −0 apps/system/js/bootstrap.js
  115. +93 −0 apps/system/js/dialogs/request.js
  116. 0  apps/{homescreen → system/js/keyboard}/imes/jspinyin/COPYING
  117. 0  apps/{homescreen → system/js/keyboard}/imes/jspinyin/db.json
  118. 0  apps/{homescreen → system/js/keyboard}/imes/jspinyin/jspinyin.js
  119. 0  apps/{homescreen → system/js/keyboard}/imes/jszhuyin/README
  120. 0  apps/{homescreen → system/js/keyboard}/imes/jszhuyin/jszhuyin.js
  121. +128,060 −0 apps/system/js/keyboard/imes/jszhuyin/phrases.json
  122. 0  apps/{homescreen → system/js/keyboard}/imes/jszhuyin/tools/cook.js
  123. 0  apps/{homescreen → system/js/keyboard}/imes/jszhuyin/tools/cook.sh
  124. +14,178 −0 apps/system/js/keyboard/imes/jszhuyin/words.json
  125. +34 −36 apps/{homescreen/js → system/js/keyboard}/keyboard.js
  126. +23 −0 apps/{homescreen/js/keyboardLayout.js → system/js/keyboard/layout.js}
  127. +185 −0 apps/system/js/lockscreen/lockscreen.js
  128. +110 −0 apps/system/js/settings.js
  129. +363 −0 apps/system/js/statusbar/statusbar.js
  130. +6 −4 apps/{homescreen/js → system/js/windows}/window_manager.js
  131. +78 −12 apps/{homescreen/locale/homescreen.properties → system/locale/system.properties}
  132. +53 −0 apps/system/manifest.json
  133. +42 −0 apps/system/style/dialogs/request.css
  134. 0  apps/{homescreen → system}/style/images/grid.png
  135. 0  apps/{homescreen → system}/style/keyboard/fonts/Keyboard-Symbols.svg
  136. 0  apps/{homescreen → system}/style/keyboard/fonts/Keyboard-Symbols.woff
  137. 0  apps/{homescreen → system}/style/keyboard/images/left-accent.png
  138. 0  apps/{homescreen → system}/style/keyboard/images/left-active-accent.png
  139. 0  apps/{homescreen → system}/style/keyboard/images/left-active.png
  140. 0  apps/{homescreen → system}/style/keyboard/images/left-special.png
  141. 0  apps/{homescreen → system}/style/keyboard/images/left.png
  142. 0  apps/{homescreen → system}/style/keyboard/images/middle-special.png
  143. 0  apps/{homescreen → system}/style/keyboard/images/middle.png
  144. 0  apps/{homescreen → system}/style/keyboard/images/right-accent.png
  145. 0  apps/{homescreen → system}/style/keyboard/images/right-active-accent.png
  146. 0  apps/{homescreen → system}/style/keyboard/images/right-active.png
  147. 0  apps/{homescreen → system}/style/keyboard/images/right-special.png
  148. 0  apps/{homescreen → system}/style/keyboard/images/right.png
  149. 0  apps/{homescreen → system}/style/keyboard/keyboard.css
  150. +26 −0 apps/system/style/lockscreen/lockscreen.css
  151. 0  apps/{homescreen/style/images → system/style/statusbar}/notifications.png
  152. +106 −0 apps/system/style/statusbar/statusbar.css
  153. +204 −0 apps/system/style/system.css
  154. BIN  apps/system/style/themes/default/fonts/Open-Sans-Bold.woff
  155. BIN  apps/system/style/themes/default/fonts/Open-Sans-Semibold.woff
  156. BIN  apps/system/style/themes/default/fonts/Open-Sans.woff
  157. 0  ...escreen/style/themes/default/images → system/style/themes/default/images/battery}/battery-charging.png
  158. 0  ...{homescreen/style/themes/default/images → system/style/themes/default/images/battery}/battery-main.png
  159. 0  apps/{homescreen → system}/style/themes/default/images/grippy.png
  160. 0  ...e/themes/default/backgrounds/rings.png → system/style/themes/default/images/lockscreen/background.png}
  161. 0  apps/{homescreen → system}/style/themes/default/images/noise.png
  162. 0  ...yle/themes/default/images → system/style/themes/default/images/notifications}/desktop-notification.png
  163. 0  apps/{homescreen → system}/style/themes/default/images/sleep/airplane.png
  164. 0  apps/{homescreen → system}/style/themes/default/images/sleep/power-off.png
  165. 0  apps/{homescreen → system}/style/themes/default/images/sleep/restart.png
  166. 0  apps/{homescreen/style/themes/default/images → system/style/themes/default/images/sleep}/vibration.png
  167. 0  apps/{homescreen/style/themes/default/images → system/style/themes/default/images/tasks}/close-active.png
  168. 0  apps/{homescreen/style/themes/default/images → system/style/themes/default/images/tasks}/close.png
  169. BIN  apps/system/style/themes/default/images/volume/vibration.png
  170. 0  apps/{homescreen/style/themes/default/images → system/style/themes/default/images/volume}/volume-off.png
  171. 0  apps/{homescreen/style/themes/default/images → system/style/themes/default/images/volume}/volume-on.png
  172. 0  apps/{homescreen/style/themes/default/images → system/style/themes/default/images/wifi}/wifi-0.png
  173. 0  apps/{homescreen/style/themes/default/images → system/style/themes/default/images/wifi}/wifi-1.png
  174. 0  apps/{homescreen/style/themes/default/images → system/style/themes/default/images/wifi}/wifi-2.png
  175. 0  apps/{homescreen/style/themes/default/images → system/style/themes/default/images/wifi}/wifi-3.png
  176. 0  apps/{homescreen/style/themes/default/images → system/style/themes/default/images/wifi}/wifi-4.png
  177. 0  apps/{homescreen/style/themes/default/images → system/style/themes/default/images/wifi}/wifi.png
  178. +16 −143 apps/{homescreen/style/themes/default/homescreen.css → system/style/themes/default/system.css}
  179. +115 −21 apps/tasks/locale/tasks.properties
  180. +40 −8 apps/tasks/manifest.json
  181. +16 −0 apps/test-agent/index.html
  182. +15 −0 apps/test-agent/js/lib.js
  183. +18 −0 apps/test-agent/manifest.json
  184. +33 −0 apps/test-agent/sandbox.html
  185. +6 −0 apps/test-agent/test/config.json
  186. +35 −0 apps/test-agent/test/device/drive_test.js
  187. +49 −0 apps/test-agent/test/index.html
  188. +15 −0 apps/test-agent/test/lib_test.js
  189. +31 −0 apps/test-agent/test/sandbox.html
  190. +5 −0 apps/uitest/data/fakecontacts/LICENSE
  191. +18,004 −0 apps/uitest/data/fakecontacts/fakecontacts.json
  192. +1 −0  apps/uitest/index.html
  193. +100 −0 apps/uitest/js/contacts.js
  194. +27 −0 apps/uitest/tests/contacts.html
  195. +1 −0  apps/video/index.html
  196. +31 −0 apps/video/js/video.js
  197. +9 −0 apps/video/locale/video.properties
  198. +12 −0 apps/video/manifest.json
  199. +50 −0 apps/video/samples/manifesto.json
  200. +1 −0  apps/video/samples/meetthecubs.json
  201. +16 −0 apps/video/style/controls.css
  202. +153 −0 build/install-gaia.py
  203. 0  { → build}/offline-cache.js
  204. +6 −1 { → build}/preferences.js
  205. +59 −0 common/test/agent.js
  206. +74 −0 common/test/helper.js
  207. +34 −0 common/test/marionette.js
  208. +69 −0 common/test/mocha-generators.js
  209. +71 −0 common/test/mocha-task.js
  210. +49 −0 common/test/style/style.css
  211. +36 −0 common/test/test-url-resolver.js
  212. +2,229 −0 common/vendor/chai/chai.js
  213. +1,615 −0 common/vendor/marionette-client/marionette.js
  214. +137 −0 common/vendor/mocha/mocha.css
  215. +4,068 −0 common/vendor/mocha/mocha.js
  216. +68 −0 common/vendor/test-agent/test-agent.css
  217. +1,877 −0 common/vendor/test-agent/test-agent.js
  218. +8 −12 tools/extensions/httpd/content/httpd.js
  219. +286 −4 tools/extensions/httpd/content/loader.js
  220. +2 −2 tools/extensions/httpd/install.rdf
  221. +10 −0 tools/test-agent/package.json
  222. +16 −0 tools/test-agent/test-agent-server.js
  223. +2 −0  webapi.js
1  .gitignore
View
@@ -8,3 +8,4 @@ apps/settings/gaia-commit.txt
profile/
xulrunner/
xulrunner-sdk/
+node_modules
37 LICENCE
View
@@ -1,30 +1,13 @@
-Copyright (c) 2012, Mozilla. All rights reserved.
+Copyright 2012, Mozilla Foundation
-Redistribution and use of this software in source and binary forms,
-with or without modification, are permitted provided that the following
-conditions are met:
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
-* Redistributions of source code must retain the above
- copyright notice, this list of conditions and the
- following disclaimer.
+ http://www.apache.org/licenses/LICENSE-2.0
-* Redistributions in binary form must reproduce the above
- copyright notice, this list of conditions and the
- following disclaimer in the documentation and/or other
- materials provided with the distribution.
-
-* Neither the name of Mozilla. nor the names of its contributors
- may be used to endorse or promote products derived from this
- software without specific prior written permission of Mozilla.
-
-THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
-IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
-TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
-PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
-OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
-SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
-LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
-DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
-THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
-(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
-OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
88 Makefile
View
@@ -11,6 +11,28 @@
# DEBUG : debug mode enables mode output on the console and disable the #
# the offline cache. This is mostly for desktop debugging. #
# #
+# REPORTER : Mocha reporter to use for test output. #
+# #
+###############################################################################
+GAIA_DOMAIN?=gaiamobile.org
+
+ADB?=adb
+
+DEBUG?=0
+
+REPORTER=Spec
+
+
+###############################################################################
+# The above rules generate the profile/ folder and all its content. #
+# The profile folder content depends on different rules: #
+# 1. manifests #
+# A directory structure representing the applications installed using the #
+# Apps API. In Gaia all applications use this method. #
+# See https://developer.mozilla.org/en/Apps/Apps_JavaScript_API #
+# #
+# 2. offline #
+# An Application Cache database containing Gaia apps, so the phone can be #
###############################################################################
GAIA_DOMAIN?=gaiamobile.org
@@ -18,6 +40,8 @@ ADB?=adb
DEBUG?=0
+REPORTER=Spec
+
###############################################################################
# The above rules generate the profile/ folder and all its content. #
@@ -44,9 +68,9 @@ DEBUG?=0
# by editing /etc/hosts on linux/mac. This steps would not be required
# anymore once https://bugzilla.mozilla.org/show_bug.cgi?id=722197 will land.
ifeq ($(DEBUG),1)
-GAIA_PORT=:8080
+GAIA_PORT?=:8080
else
-GAIA_PORT=
+GAIA_PORT?=
endif
@@ -63,6 +87,16 @@ SED_INPLACE_NO_SUFFIX = sed -i
DOWNLOAD_CMD = wget
endif
+# Test agent setup
+TEST_AGENT_DIR=tools/test-agent/
+ifeq ($(strip $(NODEJS)),)
+ NODEJS := `which node`
+endif
+
+ifeq ($(strip $(NPM)),)
+ NPM := `which npm`
+endif
+
#Marionette testing variables
#make sure we're python 2.7.x
ifeq ($(strip $(PYTHON_27)),)
@@ -110,12 +144,15 @@ manifests:
# Generate profile/OfflineCache/
offline: install-xulrunner
+ifneq ($(DEBUG),1)
@echo "Building offline cache"
@rm -rf profile/OfflineCache
@mkdir -p profile/OfflineCache
@cd ..
- $(XULRUNNER) $(XPCSHELL) -e 'const GAIA_DIR = "$(CURDIR)"; const PROFILE_DIR = "$(CURDIR)/profile"; const GAIA_DOMAIN = "$(GAIA_DOMAIN)$(GAIA_PORT)"' offline-cache.js
+ $(XULRUNNER) $(XPCSHELL) -e 'const GAIA_DIR = "$(CURDIR)"; const PROFILE_DIR = "$(CURDIR)/profile"; const GAIA_DOMAIN = "$(GAIA_DOMAIN)$(GAIA_PORT)"' build/offline-cache.js
@echo "Done"
+endif
+
# The install-xulrunner target arranges to get xulrunner downloaded and sets up
# some commands for invoking it. But it is platform dependent
@@ -137,7 +174,7 @@ XULRUNNER_DOWNLOAD=http://ftp.mozilla.org/pub/mozilla.org/xulrunner/releases/11.
XULRUNNER=./xulrunner/run-mozilla.sh
XPCSHELL=./xulrunner/xpcshell
-install-xulrunner:
+install-xulrunner :
test -d xulrunner || ($(DOWNLOAD_CMD) $(XULRUNNER_DOWNLOAD) && tar xjf xulrunner*.tar.bz2 && rm xulrunner*.tar.bz2)
endif
@@ -145,7 +182,7 @@ endif
preferences: install-xulrunner
@echo "Generating prefs.js..."
@mkdir -p profile
- $(XULRUNNER) $(XPCSHELL) -e 'const GAIA_DIR = "$(CURDIR)"; const PROFILE_DIR = "$(CURDIR)/profile"; const GAIA_DOMAIN = "$(GAIA_DOMAIN)$(GAIA_PORT)"; const DEBUG = $(DEBUG)' preferences.js
+ $(XULRUNNER) $(XPCSHELL) -e 'const GAIA_DIR = "$(CURDIR)"; const PROFILE_DIR = "$(CURDIR)/profile"; const GAIA_DOMAIN = "$(GAIA_DOMAIN)$(GAIA_PORT)"; const DEBUG = $(DEBUG)' build/preferences.js
@echo "Done"
@@ -185,6 +222,36 @@ tests: manifests offline
test -L $(INJECTED_GAIA) || ln -s $(CURDIR) $(INJECTED_GAIA)
TEST_PATH=$(TEST_PATH) make -C $(MOZ_OBJDIR) mochitest-browser-chrome EXTRA_TEST_ARGS="--browser-arg=\"\" --extra-profile-file=$(CURDIR)/profile/webapps --extra-profile-file=$(CURDIR)/profile/OfflineCache --extra-profile-file=$(CURDIR)/profile/user.js"
+.PHONY: common-install
+common-install:
+ @test -x $(NODEJS) || (echo "Please Install NodeJS -- (use aptitude on linux or homebrew on osx)" && exit 1 )
+ @test -x $(NPM) || (echo "Please install NPM (node package manager) -- http://npmjs.org/" && exit 1 )
+
+ cd $(TEST_AGENT_DIR) && npm install .
+
+.PHONY: update-common
+update-common: common-install
+ mkdir -p common/vendor/test-agent/
+ mkdir -p common/vendor/marionette-client/
+ mkdir -p common/vendor/chai/
+ rm -f common/vendor/test-agent/test-agent*.js
+ rm -f common/vendor/marionette-client/*.js
+ rm -f common/vendor/chai/*.js
+ cp $(TEST_AGENT_DIR)/node_modules/test-agent/test-agent.js common/vendor/test-agent/
+ cp $(TEST_AGENT_DIR)/node_modules/test-agent/test-agent.css common/vendor/test-agent/
+ cp $(TEST_AGENT_DIR)/node_modules/marionette-client/marionette.js common/vendor/marionette-client/
+ cp $(TEST_AGENT_DIR)/node_modules/chai/chai.js common/vendor/chai/
+
+# Temp make file method until we can switch
+# over everything in test
+.PHONY: test-agent-test
+test-agent-test:
+ @$(TEST_AGENT_DIR)/node_modules/test-agent/bin/js-test-agent test --reporter $(REPORTER)
+
+.PHONY: test-agent-server
+test-agent-server: common-install
+ $(TEST_AGENT_DIR)/node_modules/test-agent/bin/js-test-agent server -c ./$(TEST_AGENT_DIR)/test-agent-server.js --http-path . --growl
+
.PHONY: marionette
marionette:
#need the profile
@@ -262,19 +329,20 @@ update-offline-manifests:
fi \
done
-
# If your gaia/ directory is a sub-directory of the B2G directory, then
# you should use the install-gaia target of the B2G Makefile. But if you're
# working on just gaia itself, and you already have B2G firmware on your
# phone, and you have adb in your path, then you can use the install-gaia
# target to update the gaia files and reboot b2g
+PROFILE_PATH = /data/b2g/mozilla/`$(ADB) shell ls -1 /data/b2g/mozilla/ | grep default | tr -d [:cntrl:]`
install-gaia: profile
$(ADB) start-server
- $(ADB) shell rm -r /data/local/*
$(ADB) shell rm -r /cache/*
- # just push the profile
- $(ADB) push profile/OfflineCache /data/local/OfflineCache
- $(ADB) push profile/webapps /data/local/webapps
+ python build/install-gaia.py "$(ADB)"
+
+ # Until bug 746121 lands, push user.js in the profile
+ $(ADB) push profile/user.js ${PROFILE_PATH}/user.js
+
@echo "Installed gaia into profile/."
$(ADB) shell kill $(shell $(ADB) shell toolbox ps | grep "b2g" | awk '{ print $$2; }')
@echo 'Rebooting b2g now'
12 apps/browser/index.html
View
@@ -6,13 +6,16 @@
<title>Browser</title>
<link rel="stylesheet" href="style/browser.css" type="text/css" />
+ <script type="text/javascript" src="js/session_history.js"></script>
+ <script type="text/javascript" src="js/global_history.js"></script>
<script type="text/javascript" src="js/browser.js"></script>
</head>
<body>
<menu type="toolbar" id="toolbar-start">
- <form id="address-bar">
- <input type="url" id="url-bar" value="http://www.google.com/m"></input>
+ <form id="url-bar">
+ <input type="url" id="url-input" value="http://www.google.com/m"></input>
+ <input type="image" id="url-button" src="style/images/go.png"></input>
</form>
</menu>
<iframe id="browser-content" mozbrowser>
@@ -22,11 +25,6 @@
<menu type="toolbar" id="toolbar-end">
<button type="button" value="" id="back-button" alt="Back" title="" disabled="true"></button>
<button type="button" value="" id="forward-button" alt="Forward" title="" disabled="true"></button>
- <button type="button" value="" id="menu-button" alt="Menu" title=""></button>
- <menu type="list" id="browser-menu" class="hidden">
- <li id="refresh"><a href="#">Reload</a></li>
- </menu>
- </menu>
</body>
</html>
230 apps/browser/js/browser.js
View
@@ -3,65 +3,31 @@
var Browser = {
- get backButton() {
- delete this.backButton;
- return this.backButton =
- document.getElementById('back-button');
- },
-
- get addressbar() {
- delete this.addressbar;
- return this.addressbar = document.getElementById('address-bar');
- },
-
- get urlbar() {
- delete this.urlbar;
- return this.urlbar = document.getElementById('url-bar');
- },
-
- /* Browser content */
- get content() {
- delete this.content;
- return this.content = document.getElementById('browser-content');
- },
-
- get menu() {
- delete this.menu;
- return this.menu = document.getElementById('browser-menu');
- },
-
- get shade() {
- delete this.shade;
- return this.shade = document.getElementById('shade');
- },
-
- get menuButton() {
- delete this.menuButton;
- return this.menuButton = document.getElementById('menu-button');
- },
-
- get refreshButton() {
- delete this.refreshButton;
- return this.refreshButton = document.getElementById('refresh');
- },
-
- get forwardButton() {
- delete this.forwardButton;
- return this.forwardButton = document.getElementById('forward-button');
- },
-
currentTitle: '',
-
currentUrl: '',
+ GO: 0,
+ REFRESH: 1,
+ urlButtonMode: this.GO,
init: function browser_init() {
+ // Open global history database
+ GlobalHistory.db.open();
+
+ // Assign UI elements to variables
+ this.toolbarStart = document.getElementById('toolbar-start');
+ this.urlBar = document.getElementById('url-bar');
+ this.urlInput = document.getElementById('url-input');
+ this.urlButton = document.getElementById('url-button');
+ this.content = document.getElementById('browser-content');
+ this.backButton = document.getElementById('back-button');
+ this.forwardButton = document.getElementById('forward-button');
+
+ // Add event listeners
this.backButton.addEventListener('click', this.goBack.bind(this));
- this.menuButton.addEventListener('click', this.toggleMenu.bind(this));
- this.refreshButton.addEventListener('click', this.refresh.bind(this));
+ this.urlButton.addEventListener('click', this.go.bind(this));
this.forwardButton.addEventListener('click', this.goForward.bind(this));
- this.shade.addEventListener('click', this.toggleMenu.bind(this));
- this.urlbar.addEventListener('focus', this.urlFocus.bind(this));
- this.urlbar.addEventListener('blur', this.urlBlur.bind(this));
+ this.urlInput.addEventListener('focus', this.urlFocus.bind(this));
+ this.urlInput.addEventListener('blur', this.urlBlur.bind(this));
window.addEventListener('submit', this);
window.addEventListener('keyup', this, true);
@@ -71,31 +37,23 @@ var Browser = {
this.content.addEventListener('mozbrowser' + type, this);
}).bind(this));
- var url = this.urlbar.value;
+ // Load homepage
+ var url = this.urlInput.value;
this.currentUrl = url;
this.navigate(url);
this.updateHistory(url);
},
handleEvent: function browser_handleEvent(evt) {
- var urlbar = this.urlbar;
+ var urlInput = this.urlInput;
switch (evt.type) {
case 'submit':
- var url = urlbar.value.trim();
- var protocolRegexp = /^([a-z]+:)(\/\/)?/i;
- var protocol = protocolRegexp.exec(url);
- if (!protocol)
- url = 'http://' + url;
-
- this.navigate(url);
- urlbar.value = url;
- urlbar.blur();
- evt.preventDefault();
+ this.go(evt);
break;
case 'keyup':
- if (!MockHistory.backLength() || evt.keyCode != evt.DOM_VK_ESCAPE)
+ if (!SessionHistory.backLength() || evt.keyCode != evt.DOM_VK_ESCAPE)
break;
this.goBack();
@@ -104,15 +62,16 @@ var Browser = {
case 'mozbrowserloadstart':
this.currentTitle = '';
- urlbar.classList.add('loading');
+ this.toolbarStart.classList.add('loading');
break;
case 'mozbrowserloadend':
- urlbar.classList.remove('loading');
+ this.toolbarStart.classList.remove('loading');
if (this.currentTitle)
- urlbar.value = this.currentTitle;
+ urlInput.value = this.currentTitle;
else
- urlbar.value = this.currentUrl;
+ urlInput.value = this.currentUrl;
+ this.setUrlButtonMode(this.REFRESH);
break;
case 'mozbrowserlocationchange':
@@ -121,8 +80,9 @@ var Browser = {
case 'mozbrowsertitlechange':
this.currentTitle = evt.detail;
- if (!this.addressbar.querySelector(':focus'))
- this.urlbar.value = this.currentTitle;
+ if (!this.urlBar.querySelector(':focus'))
+ urlInput.value = this.currentTitle;
+ GlobalHistory.setPageTitle(this.currentUrl, this.currentTitle);
break;
}
},
@@ -131,48 +91,74 @@ var Browser = {
this.content.setAttribute('src', url);
},
+ go: function browser_go(evt) {
+ evt.preventDefault();
+ if (this.urlButtonMode == this.REFRESH) {
+ this.navigate(this.currentUrl);
+ return;
+ }
+
+ var url = this.urlInput.value.trim();
+ var protocolRegexp = /^([a-z]+:)(\/\/)?/i;
+ var protocol = protocolRegexp.exec(url);
+ if (!protocol)
+ url = 'http://' + url;
+ if (url != this.currentUrl) {
+ this.urlInput.value = url;
+ this.currentUrl = url;
+ }
+ this.navigate(url);
+ this.urlInput.blur();
+ },
+
goBack: function browser_goBack() {
- MockHistory.back();
- this.backButton.disabled = !MockHistory.backLength();
- this.forwardButton.disabled = !MockHistory.forwardLength();
+ SessionHistory.back();
+ this.backButton.disabled = !SessionHistory.backLength();
+ this.forwardButton.disabled = !SessionHistory.forwardLength();
},
goForward: function browser_goForward() {
- MockHistory.forward();
- this.forwardButton.disabled = !MockHistory.forwardLength();
+ SessionHistory.forward();
+ this.backButton.disabled = !SessionHistory.backLength();
+ this.forwardButton.disabled = !SessionHistory.forwardLength();
},
updateHistory: function browser_updateHistory(url) {
- MockHistory.pushState(null, '', url);
- this.backButton.disabled = !MockHistory.backLength();
- },
-
- locationChange: function browser_locationChange(url) {
- this.currentUrl = url;
- this.updateHistory(url);
+ SessionHistory.pushState(null, '', url);
+ GlobalHistory.addVisit(url);
+ this.backButton.disabled = !SessionHistory.backLength();
+ this.forwardButton.disabled = !SessionHistory.forwardLength();
},
- refresh: function browser_refresh() {
- var url = this.currentUrl;
- this.content.setAttribute('src', url);
- this.toggleMenu();
- },
-
- toggleMenu: function browser_toggleMenu() {
- this.menu.classList.toggle('hidden');
- this.shade.classList.toggle('hidden');
+ locationChange: function browser_locationChange(url) {
+ if (url != this.currentUrl) {
+ this.currentUrl = url;
+ this.updateHistory(url);
+ }
},
urlFocus: function browser_urlFocus() {
- this.urlbar.value = this.currentUrl;
- this.urlbar.select();
+ this.urlInput.value = this.currentUrl;
+ this.urlInput.select();
+ this.setUrlButtonMode(this.GO);
},
urlBlur: function browser_urlBlur() {
if (this.currentTitle)
- this.urlbar.value = this.currentTitle;
- else
- this.urlbar.value = this.currentUrl;
+ this.urlInput.value = this.currentTitle;
+ this.setUrlButtonMode(this.REFRESH);
+ },
+
+ setUrlButtonMode: function browser_setUrlButtonMode(mode) {
+ this.urlButtonMode = mode;
+ switch (mode) {
+ case this.GO:
+ this.urlButton.src = 'style/images/go.png';
+ break;
+ case this.REFRESH:
+ this.urlButton.src = 'style/images/refresh.png';
+ break;
+ }
}
};
@@ -180,51 +166,3 @@ window.addEventListener('load', function browserOnLoad(evt) {
window.removeEventListener('load', browserOnLoad);
Browser.init();
});
-
-var MockHistory = {
- history: [],
- historyIndex: -1,
-
- back: function() {
- if (this.backLength() < 1)
- return;
- Browser.navigate(this.history[--this.historyIndex]);
- },
-
- forward: function() {
- if(this.forwardLength() < 1)
- return;
- Browser.navigate(this.history[++this.historyIndex]);
- },
-
- historyLength: function() {
- return this.history.length;
- },
-
- backLength: function() {
- if (this.history.length < 2)
- return 0;
- return this.historyIndex;
- },
-
- forwardLength: function() {
- return this.history.length - this.historyIndex - 1;
- },
-
- pushState: function(stateObj, title, url) {
- var history = this.history;
- var index = this.historyIndex;
- if (url == history[index])
- return;
-
- // If history contains forward entries, replace them with the new location
- if (this.forwardLength()) {
- history.splice(index + 1, this.forwardLength(), url);
- this.historyIndex++;
- } else {
- // Otherwise just append the new location to the end of the array
- this.historyIndex = history.push(url) - 1;
- }
- }
-};
-
139 apps/browser/js/global_history.js
View
@@ -0,0 +1,139 @@
+var indexedDB = window.indexedDB || window.webkitIndexedDB ||
+ window.mozIndexedDB || window.msIndexedDB;
+
+var GlobalHistory = {
+ addPlace: function gh_addPlace(uri) {
+ var place = {
+ uri: uri,
+ // Set the title to the URI for now, until a real title is received.
+ title: uri
+ };
+ this.db.savePlace(place);
+ },
+
+ addVisit: function gh_addVisit(uri) {
+ this.addPlace(uri);
+ var visit = {
+ uri: uri,
+ timestamp: new Date().getTime()
+ };
+ this.db.saveVisit(visit);
+ },
+
+ setPageTitle: function gh_setPageTitle(uri, title) {
+ var place = {
+ uri: uri,
+ title: title
+ };
+ this.db.updatePlace(place);
+ }
+
+};
+
+GlobalHistory.db = {
+ _db: null,
+
+ open: function db_open() {
+ const DB_VERSION = 1;
+ const DB_NAME = 'browser';
+ var request = indexedDB.open(DB_NAME, DB_VERSION);
+
+ request.onupgradeneeded = (function onUpgradeNeeded(e) {
+ console.log('Browser database upgrade needed, upgrading.');
+ this._db = e.target.result;
+ this._initializeDB();
+ }).bind(this);
+
+ request.onsuccess = (function onSuccess(e) {
+ this._db = e.target.result;
+ console.log('Successfully opened browser database');
+ }).bind(this);
+
+ request.onerror = (function onDatabaseError(e) {
+ console.log('Error opening browser database: ' + e.target.errorCode);
+ }).bind(this);
+ },
+
+ _initializeDB: function db_initializeDB() {
+ var db = this._db;
+
+ // Create or overwrite places object store
+ if (db.objectStoreNames.contains('places'))
+ db.deleteObjectStore('places');
+ var placeStore = db.createObjectStore('places', { keyPath: 'uri' });
+
+ // Create or overwrite visits object store
+ if (db.objectStoreNames.contains('visits'))
+ db.deleteObjectStore('visits');
+ var visitStore = db.createObjectStore('visits', { autoIncrement: true });
+
+ // Index visits by timestamp
+ visitStore.createIndex('timestamp', 'timestamp', { unique: false });
+
+ console.log("Initialised browser's global history database");
+ },
+
+ savePlace: function db_savePlace(place) {
+ var transaction = this._db.transaction(['places'],
+ IDBTransaction.READ_WRITE);
+ transaction.onerror = function dbTransactionError(e) {
+ console.log('Transaction error while trying to save place: ' +
+ place.uri);
+ };
+
+ var objectStore = transaction.objectStore('places');
+ var request = objectStore.add(place);
+
+ request.onsuccess = function onsuccess(e) {
+ console.log('Successfully wrote place to global history store: ' +
+ place.uri);
+ };
+
+ request.onerror = function onerror(e) {
+ console.log('Error while adding place to global history store: ' +
+ place.uri);
+ };
+ },
+
+ updatePlace: function db_updatePlace(place) {
+ var transaction = this._db.transaction(['places'],
+ IDBTransaction.READ_WRITE);
+ transaction.onerror = function dbTransactionError(e) {
+ console.log('Transaction error while trying to update place: ' +
+ place.uri);
+ };
+
+ var objectStore = transaction.objectStore('places');
+ var request = objectStore.put(place);
+
+ request.onsuccess = function onsuccess(e) {
+ console.log('Successfully updated place in global history store: ' +
+ place.uri);
+ };
+
+ request.onerror = function onerror(e) {
+ console.log('Error while updating place in global history store: ' +
+ place.uri);
+ };
+ },
+
+ saveVisit: function db_saveVisit(visit) {
+ var transaction = this._db.transaction(['visits'],
+ IDBTransaction.READ_WRITE);
+ transaction.onerror = function dbTransactionError(e) {
+ console.log('Transaction error while trying to save visit');
+ };
+
+ var objectStore = transaction.objectStore('visits');
+ var request = objectStore.add(visit);
+
+ request.onsuccess = function onsuccess(e) {
+ console.log('Successfully wrote visit to global history store');
+ };
+
+ request.onerror = function onerror(e) {
+ console.log('Error while adding visit to global history store');
+ };
+ }
+
+};
46 apps/browser/js/session_history.js
View
@@ -0,0 +1,46 @@
+var SessionHistory = {
+ history: [],
+ historyIndex: -1,
+
+ back: function sh_back() {
+ if (this.backLength() < 1)
+ return;
+ Browser.navigate(this.history[--this.historyIndex]);
+ },
+
+ forward: function sh_forward() {
+ if (this.forwardLength() < 1)
+ return;
+ Browser.navigate(this.history[++this.historyIndex]);
+ },
+
+ historyLength: function sh_historyLength() {
+ return this.history.length;
+ },
+
+ backLength: function sh_backLength() {
+ if (this.history.length < 2)
+ return 0;
+ return this.historyIndex;
+ },
+
+ forwardLength: function sh_forwardLength() {
+ return this.history.length - this.historyIndex - 1;
+ },
+
+ pushState: function sh_pushState(stateObj, title, url) {
+ var history = this.history;
+ var index = this.historyIndex;
+ if (url == history[index])
+ return;
+
+ // If history contains forward entries, replace them with the new location
+ if (this.forwardLength()) {
+ history.splice(index + 1, this.forwardLength(), url);
+ this.historyIndex++;
+ } else {
+ // Otherwise just append the new location to the end of the array
+ this.historyIndex = history.push(url) - 1;
+ }
+ }
+};
18 apps/browser/manifest.json
View
@@ -16,19 +16,31 @@
},
"de": {
"name": "Browser",
- "description": "Gaia Web Browser"
+ "description": "Gaia Webbrowser"
+ },
+ "el": {
+ "name": "\u03a6\u03c5\u03bb\u03bb\u03bf\u03bc\u03b5\u03c4\u03c1\u03b7\u03c4\u03ae\u03c2",
+ "description": "\u03a6\u03c5\u03bb\u03bb\u03bf\u03bc\u03b5\u03c4\u03c1\u03b7\u03c4\u03ae\u03c2 \u0399\u03c3\u03c4\u03bf\u03cd Gaia"
},
"en-US": {
"name": "Browser",
"description": "Gaia Web Browser"
},
+ "es": {
+ "name": "Navegador",
+ "description": "Navegador de Gaia"
+ },
"fr": {
"name": "Navigateur",
"description": "Navigateur Web Gaia"
},
+ "it": {
+ "name": "Browser",
+ "description": "Browser Web di Gaia"
+ },
"ru": {
- "name": "\u041d\u0430\u0432\u0438\u0433\u0430\u0442\u043e\u0440",
- "description": "\u0412\u0435\u0431-\u043d\u0430\u0432\u0438\u0433\u0430\u0442\u043e\u0440 Gaia"
+ "name": "\u0411\u0440\u0430\u0443\u0437\u0435\u0440",
+ "description": "\u0412\u0435\u0431-\u0431\u0440\u0430\u0443\u0437\u0435\u0440 Gaia"
},
"zh-TW": {
"name": "\u7db2\u8def\u700f\u89bd\u5668",
33 apps/browser/style/browser.css
View
@@ -37,35 +37,42 @@ body {
border-bottom: solid 2px #a3a3a3;
}
+#toolbar-start.loading {
+ border-bottom-width: 4px;
+ -moz-border-image: url('images/progress.gif') 0 1 100% 1;
+}
+
#toolbar-end {
border-top: solid 2px #a3a3a3;
}
-#address-bar {
+#url-bar {
-moz-box-flex: 1;
display: -moz-box;
-moz-box-orient: horizontal;
padding: 0;
margin: 0;
-}
-
-#url-bar {
- font-family: 'Open Sans';
- -moz-box-flex: 1;
border: solid 2px #9ca1a6;
border-radius: 22px;
height: 44px;
margin: 9px 8px;
- padding: 0 0 0 40px;
- font-size: 18px;
- background-image: url('images/favicon.png');
- background-repeat: no-repeat;
- background-position: 14px 12px;
+ padding: 0 0 0 16px;
+ background-color: #fff;
font-weight: 600;
}
-#url-bar.loading {
- background-image: url('images/loading.gif');
+#url-input {
+ font-family: 'Open Sans';
+ font-size: 18px;
+ height: 44px;
+ margin: 0;
+ -moz-box-flex: 1;
+ border: none;
+ background-color: transparent;
+}
+
+#url-input:invalid {
+ box-shadow: none;
}
menu[type="toolbar"] {
BIN  apps/browser/style/images/favicon.png
View
Deleted file not rendered
BIN  apps/browser/style/images/go.png
View
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
BIN  apps/browser/style/images/loading.gif
View
Deleted file not rendered
BIN  apps/browser/style/images/progress.gif
View
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
BIN  apps/browser/style/images/refresh.png
View
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
12 apps/calculator/manifest.json
View
@@ -15,14 +15,26 @@
"name": "Taschenrechner",
"description": "Taschenrechner"
},
+ "el": {
+ "name": "\u0391\u03c1\u03b9\u03b8\u03bc\u03bf\u03bc\u03b7\u03c7\u03b1\u03bd\u03ae",
+ "description": "\u0391\u03c1\u03b9\u03b8\u03bc\u03bf\u03bc\u03b7\u03c7\u03b1\u03bd\u03ae"
+ },
"en-US": {
"name": "Calculator",
"description": "Calculator"
},
+ "es": {
+ "name": "Calculadora",
+ "description": "Calculadora"
+ },
"fr": {
"name": "Calculatrice",
"description": "Calculatrice Gaia"
},
+ "it": {
+ "name": "Calcolatrice",
+ "description": "Calcolatrice"
+ },
"ru": {
"name": "\u041a\u0430\u043b\u044c\u043a\u0443\u043b\u044f\u0442\u043e\u0440",
"description": "\u041a\u0430\u043b\u044c\u043a\u0443\u043b\u044f\u0442\u043e\u0440"
12 apps/camera/manifest.json
View
@@ -15,14 +15,26 @@
"name": "Kamera",
"description": "Gaia Kamera"
},
+ "el": {
+ "name": "\u039a\u03ac\u03bc\u03b5\u03c1\u03b1",
+ "description": "\u039a\u03ac\u03bc\u03b5\u03c1\u03b1 Gaia"
+ },
"en-US": {
"name": "Camera",
"description": "Gaia Camera"
},
+ "es": {
+ "name": "C\u00e1mara",
+ "description": "C\u00e1mara de Gaia"
+ },
"fr": {
"name": "Photo",
"description": "Appareil photo Gaia"
},
+ "it": {
+ "name": "Camera",
+ "description": "Camera di Gaia"
+ },
"ru": {
"name": "\u041a\u0430\u043c\u0435\u0440\u0430",
"description": "\u041a\u0430\u043c\u0435\u0440\u0430 Gaia"
18 apps/clock/locale/clock.properties
View
@@ -10,18 +10,36 @@ start=Starten
stop=Stoppen
cancel=Abbrechen
+[el]
+reset=Επαναφορά
+start=Εκκίνηση
+stop=Διακοπή
+cancel=Ακύρωση
+
[en-US]
reset=Reset
start=Start
stop=Stop
cancel=Cancel
+[es]
+reset=Reiniciar
+start=Iniciar
+stop=Parar
+cancel=Cancelar
+
[fr]
reset=Remise à zéro
start=Démarrer
stop=Stop
cancel=Annuler
+[it]
+reset=Reset
+start=Avvia
+stop=Ferma
+cancel=Annulla
+
[ru]
reset=Сбросить
start=Старт
12 apps/clock/manifest.json
View
@@ -15,14 +15,26 @@
"name": "Uhr",
"description": "Gaia Uhr"
},
+ "el": {
+ "name": "\u03a1\u03bf\u03bb\u03cc\u03b9",
+ "description": "\u03a1\u03bf\u03bb\u03cc\u03b9 Gaia"
+ },
"en-US": {
"name": "Clock",
"description": "Gaia Clock"
},
+ "es": {
+ "name": "Reloj",
+ "description": "Reloj de Gaia"
+ },
"fr": {
"name": "Horloge",
"description": "Horloge Gaia"
},
+ "it": {
+ "name": "Orologio",
+ "description": "Orologio di Gaia"
+ },
"ru": {
"name": "\u0427\u0430\u0441\u044b",
"description": "\u0427\u0430\u0441\u044b Gaia"
12 apps/crystalskull/manifest.json
View
@@ -15,14 +15,26 @@
"name": "CrystalSkull",
"description": "WebGL Demo"
},
+ "el": {
+ "name": "\u039a\u03c1\u03c5\u03c3\u03c4\u03ac\u03bb\u03bb\u03b9\u03bd\u03bf \u039a\u03c1\u03b1\u03bd\u03af\u03bf",
+ "description": "\u0394\u03bf\u03ba\u03b9\u03bc\u03ae \u03c4\u03b7\u03c2 WebGL"
+ },
"en-US": {
"name": "CrystalSkull",
"description": "WebGL Demo"
},
+ "es": {
+ "name": "CrystalSkull",
+ "description": "Demo de WebGL"
+ },
"fr": {
"name": "CrystalSkull",
"description": "D\u00e9mo WebGL"
},
+ "it": {
+ "name": "CrystalSkull",
+ "description": "Demo di WebGL"
+ },
"ru": {
"name": "CrystalSkull",
"description": "\u0414\u0435\u043c\u043e\u043d\u0441\u0442\u0440\u0430\u0446\u0438\u044f WebGL"
12 apps/cubevid/manifest.json
View
@@ -15,14 +15,26 @@
"name": "CubeVid",
"description": "Video-W\u00fcrfel Demo"
},
+ "el": {
+ "name": "\u0392\u03b9\u03bd\u03c4\u03b5\u03bf\u03ba\u03cd\u03b2\u03bf\u03c2",
+ "description": "\u0394\u03bf\u03ba\u03b9\u03bc\u03ae \u0392\u03b9\u03bd\u03c4\u03b5\u03bf\u03ba\u03cd\u03b2\u03bf\u03c5"
+ },
"en-US": {
"name": "CubeVid",
"description": "Video Cube Demo"
},
+ "es": {
+ "name": "CubeVid",
+ "description": "Demo de un cubo con v\u00eddeo"
+ },
"fr": {
"name": "CubeVid",
"description": "D\u00e9mo de vid\u00e9o cube"
},
+ "it": {
+ "name": "CubeVid",
+ "description": "Demo di un cubo con video"
+ },
"ru": {
"name": "CubeVid",
"description": "\u0414\u0435\u043c\u043e\u043d\u0441\u0442\u0440\u0430\u0446\u0438\u044f Video Cube"
11 apps/dialer/background.html
View
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html manifest="manifest.appcache">
+<head>
+ <meta charset="utf-8">
+ <title>Dialer Background Service</title>
+ <script type="text/javascript" src="js/settings_listener.js"></script>
+ <script type="text/javascript" src="js/background.js"></script>
+</head>
+<body>
+</body>
+</html>
74 apps/dialer/js/background.js
View
@@ -0,0 +1,74 @@
+'use strict';
+
+(function() {
+ var telephony = navigator.mozTelephony;
+ if (!telephony) {
+ return;
+ }
+
+ /* === Setup === */
+ SettingsListener.init();
+
+ var ringtonePlayer = new Audio();
+ ringtonePlayer.loop = true;
+
+ var power = navigator.mozPower;
+
+ /* === Settings === */
+ var activePhoneSound = true;
+ SettingsListener.observe('phone.ring.incoming', true, function(value) {
+ activePhoneSound = !!value;
+ });
+
+ var selectedPhoneSound = '';
+ SettingsListener.observe('homescreen.ring', 'classic.wav', function(value) {
+ selectedPhoneSound = 'style/ringtones/' + value;
+ ringtonePlayer.src = selectedPhoneSound;
+ });
+
+ var activateVibration = false;
+ SettingsListener.observe('phone.vibration.incoming', false, function(value) {
+ activateVibration = !!value;
+ });
+
+ var preferredBrightness = 0.5;
+ SettingsListener.observe('screen.brightness', 0.5, function(value) {
+ preferredBrightness = parseFloat(value);
+ });
+
+ /* === Incoming handling === */
+ telephony.addEventListener('incoming', function incoming(evt) {
+ var vibrateInterval = 0;
+ if (activateVibration) {
+ vibrateInterval = window.setInterval(function vibrate() {
+ if ('mozVibrate' in navigator) {
+ navigator.mozVibrate([200]);
+ }
+ }, 600);
+ }
+
+ if (activePhoneSound && selectedPhoneSound) {
+ ringtonePlayer.play();
+ }
+
+ telephony.calls.forEach(function(call) {
+ if (call.state == 'incoming') {
+ call.onstatechange = function() {
+ call.onstatechange = null;
+ ringtonePlayer.pause();
+ window.clearInterval(vibrateInterval);
+ };
+ }
+ });
+
+ navigator.mozApps.getSelf().onsuccess = function(e) {
+ var app = e.target.result;
+ app.launch();
+
+ if (power) {
+ power.screenEnabled = true;
+ power.screenBrightness = preferredBrightness;
+ }
+ };
+ });
+}());
1  apps/dialer/js/contacts.js
View
@@ -251,6 +251,7 @@ var ContactDetails = {
this.focusNextField();
return false;
}
+ return true;
}).bind(this);
}
31 apps/dialer/js/settings_listener.js
View
@@ -0,0 +1,31 @@
+var SettingsListener = {
+ _callbacks: {},
+
+ init: function sl_init() {
+ if ('mozSettings' in navigator && navigator.mozSettings)
+ 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 = window.navigator.mozSettings;
+ if (!settings) {
+ window.setTimeout(function() { callback(defaultValue); });
+ return;
+ }
+
+ var req = settings.getLock().get(name);
+ req.addEventListener('success', (function onsuccess() {
+ callback(typeof(req.result[name]) != 'undefined' ?
+ req.result[name] : defaultValue);
+ }));
+
+ this._callbacks[name] = callback;
+ }
+};
59 apps/dialer/locale/dialer.properties
View
@@ -17,14 +17,13 @@ end=نهاية
[de]
call=Anrufen
-email=eMail
+email=E-Mail
phone=Telefon
firstName=Vorname
lastName=Nachname
edit=Bearbeiten
delete=Löschen
-# (FIXME)
-done.value=Done
+done.value=Speichern
mute=Stumm
keypad=Tastatur
speaker=Lautsprecher
@@ -32,6 +31,22 @@ hold=Halten
answer=Beantworten
end=Beenden
+[el]
+call=Κλήση
+email=ηλεκτρονική διεύθυνση
+phone=Τηλέφωνο
+firstName=Όνομα
+lastName=Επίθετο
+edit=Επεξεργασία
+delete=Διαγραφή
+done.value=Κατοχύρωση
+mute=Σίγαση
+keypad=Πληκτρολόγιο
+speaker=Ηχείο
+hold=Αναμονή
+answer=Απάντηση
+end=Τέλος
+
[en-US]
call=Call
email=e-mail
@@ -48,6 +63,22 @@ hold=Hold
answer=Answer
end=End
+[es]
+call=Llamar
+email=Correo
+phone=Teléfono
+firstName=Nombre
+lastName=Apellido
+edit=Editar
+delete=Borrar
+done.value=Hecho
+mute=Silencio
+keypad=Teclado
+speaker=Altavoz
+hold=Espera
+answer=Responder
+end=Fin
+
[fr]
call=Appel
email=courriel
@@ -64,6 +95,22 @@ hold=En attente
answer=Répondre
end=Terminer
+[it]
+call=Chiamata
+email=e-mail
+phone=Telefono
+firstName=Nome
+lastName=Cognome
+edit=Modifica
+delete=Elimina
+done.value=Fatto
+mute=Muto
+keypad=Tastierino
+speaker=Altoparlante
+hold=In attesa
+answer=Rispondi
+end=Fine
+
[ru]
call=Звонить
email=e-mail
@@ -73,10 +120,10 @@ lastName=Фамилия
edit=Изменить
delete=Удалить
done.value=Завершить
-mute=Молчание
+mute=Отключить звук
keypad=Клавиатура
-speaker=Громкоговоритель
-hold=Держать
+speaker=Громкая связь
+hold=Удержать
answer=Ответить
end=Закончить
16 apps/dialer/manifest.json
View
@@ -9,6 +9,7 @@
"permissions": [
"telephony",
"contacts",
+ "power",
"mozApps"
],
"locales": {
@@ -18,16 +19,28 @@
},
"de": {
"name": "Telefon",
- "description": "Gaia Telefonw\u00e4hler"
+ "description": "Gaia Telefon"
+ },
+ "el": {
+ "name": "\u03a4\u03b7\u03bb\u03ad\u03c6\u03c9\u03bd\u03bf",
+ "description": "\u03a4\u03b7\u03bb\u03ad\u03c6\u03c9\u03bd\u03bf Gaia"
},
"en-US": {
"name": "Dialer",
"description": "Gaia Dialer"
},
+ "es": {
+ "name": "Llamada",
+ "description": "Realizar llamadas en Gaia"
+ },
"fr": {
"name": "Appel",
"description": "Appel t\u00e9l\u00e9phonique Gaia"
},
+ "it": {
+ "name": "Chiamata",
+ "description": "Chiamata di Gaia"
+ },
"ru": {
"name": "\u0422\u0435\u043b\u0435\u0444\u043e\u043d",
"description": "\u0422\u0435\u043b\u0435\u0444\u043e\u043d Gaia"
@@ -38,6 +51,7 @@
}
},
"default_locale": "en-US",
+ "background_page": "/background.html",
"icons": {
"120": "/style/icons/Phone.png"
}
0  apps/homescreen/style/ringtones/README → apps/dialer/style/ringtones/README
View
File renamed without changes
0  apps/homescreen/style/ringtones/bosscaling.wav → apps/dialer/style/ringtones/bosscaling.wav
View
File renamed without changes
BIN  apps/dialer/style/ringtones/classic.wav
View
Binary file not shown
0  apps/homescreen/style/ringtones/goodnight.wav → apps/dialer/style/ringtones/goodnight.wav
View
File renamed without changes
1  apps/gallery/index.html
View
@@ -13,6 +13,7 @@
document.write('<script src="' + src + '"><\/script>');
</script>
</script>
+ <script defer src="js/GestureDetector.js"></script>
<script defer src="js/gallery.js"></script>
</head>
806 apps/gallery/js/GestureDetector.js
View
@@ -0,0 +1,806 @@
+// GestureDetector.js: generate events for one and two finger gestures
+//
+// A GestureDetector object listens for touch and mouse events on a specified
+// element and generates higher-level events that describe one and two finger
+// gestures on the element. The hope is that this will be useful for webapps
+// that need to run on mouse (or trackpad)-based desktop browsers and also
+// in touch-based mobile devices.
+//
+// Supported events:
+//
+// tap like a click event
+// dbltap like dblclick
+// pan one finger motion, or mousedown followed by mousemove
+// swipe when a finger is released following pan events
+// holdstart touch (or mousedown) and hold. Must set an option to get these.
+// holdmove motion after a holdstart event
+// holdend when the finger or mouse goes up after holdstart/holdmove
+// transform 2-finger pinch and twist gestures for scaling and rotation
+// These are touch-only; they can't be simulated with a mouse.
+//
+// Each of these events is a bubbling CustomEvent with important details
+// in the event.detail field. The event details are not yet stable
+// and are not yet documented. See the calls to emitEvent() for details.
+//
+// To use this library, create a GestureDetector object by passing an
+// element to the GestureDetector() constructor and then calling
+// startDetecting() on it. The element will be the target of all the
+// emitted gesture events. You can also pass an optional object as the
+// second constructor argument. If you're interested in
+// holdstart/holdmove/holdend events, pass {holdEvents:true} as this
+// second argument. Otherwise they will not be generated.
+//
+// Implementation note: event processing is done with a simple
+// finite-state machine. This means that in general, the various kinds
+// of gestures are mutually exclusive. You won't get pan events until
+// your finger or mouse has moved more than a minimum threshold, for
+// example, but it does, the FSM enters a new state in which it can
+// emit pan and swipe events and cannot emit hold events. Similarly,
+// if you've started a 1 finger pan/swipe gesture and accidentally
+// touch with a second finger, you'll continue to get pan events, and
+// won't suddenly start getting 2-finger transform events.
+//
+// This library never calls preventDefault() or stopPropagation on any
+// of the events it processes, so the raw touch or mouse events should
+// still be available for other code to process. It is not clear to me
+// whether this is a feature or a bug.
+//
+// Each GestureDetector listens for mouse and touch events on a single
+// target element and dispatches its synthetic events on the same
+// element. Its not clear to me whether this will be sufficient or
+// whether I need to do something more sophisticated. For example, the
+// GestureDetector() could take a CSS selector argument and only
+// detect gestures on elements that match that selector. Or it could
+// remember the originalTarget element on which the gesture began and
+// dispatch events on that object rather than the higher-level one.
+//
+var GestureDetector = (function() {
+
+ //
+ // Constructor
+ //
+ function GD(e, options) {
+ this.element = e;
+ this.options = options || {};
+ this.state = initialState;
+ this.timers = {};
+ }
+
+ //
+ // Public methods
+ //
+
+ GD.prototype.startDetecting = function() {
+ var self = this;
+ eventtypes.forEach(function(t) {
+ self.element.addEventListener(t, self);
+ });
+ };
+
+ GD.prototype.stopDetecting = function() {
+ var self = this;
+ eventtypes.forEach(function(t) {
+ this.element.removeEventListener(t, self);
+ });
+ };
+
+ //
+ // Internal methods
+ //
+
+ GD.prototype.handleEvent = function(e) {
+ var handler = this.state[e.type];
+ if (!handler) return;
+
+ // console.log("got input event", e.type);
+
+ // If this is a touch event handle each changed touch separately
+ if (e.changedTouches) {
+ for(var i = 0; i < e.changedTouches.length; i++) {
+ handler(this, e, e.changedTouches[i]);
+ }
+ }
+ else { // Otherwise, just dispatch the event to the handler
+ handler(this, e);
+ }
+ };
+
+ GD.prototype.startTimer = function(type, time) {
+ this.clearTimer(type);
+ var self = this;
+ this.timers[type] = setTimeout(function() {
+ self.timers[type] = null;
+ var handler = self.state[type];
+ if (handler)
+ handler(self, type);
+ }, time);
+ };
+
+ GD.prototype.clearTimer = function(type) {
+ if (this.timers[type]) {
+ clearTimeout(this.timers[type]);
+ this.timers[type] = null;
+ }
+ };
+
+ // Switch to a new FSM state, and call the init() function of that
+ // state, if it has one. The event and touch arguments are optional
+ // and are just passed through to the state init function.
+ GD.prototype.switchTo = function(state, event, touch) {
+ // console.log("switching to ", state.name);
+ this.state = state;
+ if (state.init)
+ state.init(this, event, touch);
+ };
+
+ GD.prototype.emitEvent = function(type, detail) {
+ // console.log("Sending output event", type);
+ var event = this.element.ownerDocument.createEvent('CustomEvent');
+ event.initCustomEvent(type, true, true, detail);
+ this.element.dispatchEvent(event);
+ }
+
+ //
+ // Tuneable parameters
+ //
+ GD.HOLD_INTERVAL = 1500; // Hold events after 1500 ms
+ GD.PAN_THRESHOLD = 50; // 50 pixels movement before touch panning
+ GD.MOUSE_PAN_THRESHOLD = 25; // Mice are more precise, so smaller threshold
+ GD.DOUBLE_TAP_DISTANCE = 50;
+ GD.DOUBLE_TAP_TIME = 500;
+ GD.VELOCITY_SMOOTHING = .5;
+
+ // Don't start sending transform events until the gesture exceeds a threshold
+ GD.SCALE_THRESHOLD = 40; // pixels
+ GD.ROTATE_THRESHOLD = 22.5; // degrees
+
+
+ //
+ // Helpful shortcuts and utility functions
+ //
+
+ var abs = Math.abs, floor = Math.floor, sqrt = Math.sqrt, atan2 = Math.atan2;
+ var PI = Math.PI;
+
+ // The names of events that we need to register handlers for
+ var eventtypes = [
+ 'touchstart',
+ 'touchmove',
+ 'touchend',
+ 'mousedown', // We register mousemove and mouseup manually
+ // XXX: add MozMagnifyGesture events
+ ];
+
+ // Return the event's timestamp in ms
+ function eventTime(e) {
+ // In gecko, synthetic events seem to be in microseconds rather than ms.
+ // So if the timestamp is much larger than the current time, assue it is
+ // in microseconds and divide by 1000
+ var ts = e.timeStamp;
+ if (ts > 2*Date.now())
+ return Math.floor(ts/1000);
+ else
+ return ts;
+ }
+
+
+ // Return an object containg the space and time coordinates of
+ // and event and touch. We freeze the object to make it immutable so
+ // we can pass it in events and not worry about values being changed.
+ function coordinates(e, t) {
+ return Object.freeze({
+ screenX: t.screenX,
+ screenY: t.screenY,
+ clientX: t.clientX,
+ clientY: t.clientY,
+ timeStamp: eventTime(e)
+ });
+ }
+
+ // Like coordinates(), but return the midpoint between two touches
+ function midpoints(e, t1, t2) {
+ return Object.freeze({
+ screenX: floor((t1.screenX + t2.screenX)/2),
+ screenY: floor((t1.screenY + t2.screenY)/2),
+ clientX: floor((t1.clientX + t2.clientX)/2),
+ clientY: floor((t1.clientY + t2.clientY)/2),
+ timeStamp: eventTime(e)
+ });
+ }
+
+ // Like coordinates(), but for a mouse event
+ function mouseCoordinates(e) {
+ return Object.freeze({
+ screenX: e.screenX,
+ screenY: e.screenY,
+ clientX: e.clientX,
+ clientY: e.clientY,
+ timeStamp: e.timeStamp
+ });
+ }
+
+
+ // Compute the distance between two touches
+ function touchDistance(t1, t2) {
+ var dx = t2.screenX - t1.screenX;
+ var dy = t2.screenY - t1.screenY;
+ return sqrt(dx * dx + dy * dy);
+ }
+
+ // Compute the direction (as an angle) of the line between two touches
+ // Returns a number d, -180 < d <= 180
+ function touchDirection(t1, t2) {
+ return atan2(t2.screenY - t1.screenY,
+ t2.screenX - t1.screenX) * 180 / PI;
+ }
+
+ // Compute the clockwise angle between direction d1 and direction d2.
+ // Returns an angle a -180 < a <= 180.
+ function touchRotation(d1, d2) {
+ var angle = d2 - d1;
+ if (angle > 180)
+ angle -= 360;
+ else if (angle <= -180)
+ angle += 360;
+ return angle;
+ }
+
+ // Determine if two taps are close enough in time and space to
+ // trigger a dbltap event. The arguments are objects returned
+ // by the coordinates() function.
+ function isDoubleTap(lastTap, thisTap) {
+ var dx = abs(thisTap.screenX - lastTap.screenX);
+ var dy = abs(thisTap.screenY - lastTap.screenY);
+ var dt = thisTap.timeStamp - lastTap.timeStamp;
+ return (dx < GD.DOUBLE_TAP_DISTANCE &&
+ dy < GD.DOUBLE_TAP_DISTANCE &&
+ dt < GD.DOUBLE_TAP_TIME)
+ }
+
+ //
+ // The following objects are the states of our Finite State Machine
+ //
+
+ // In this state we're not processing any gestures, just waiting
+ // for an event to start a gesture and ignoring others
+ var initialState = {
+ name: "initialState",
+ init: function(d) {
+ // When we enter or return to the initial state, clear
+ // the detector properties that were tracking gestures
+ // Don't clear d.lastTap here, though. We need it for dbltap events
+ d.start = d.last = null;
+ d.touch1 = d.touch2 = null;
+ d.vx = d.vy = null;
+ d.startDistance = d.lastDistance = null;
+ d.startDirection = d.lastDirection = null;
+ d.scaled = d.rotated = null;
+ },
+
+ // Switch to the touchstarted state and process the touch event there
+ // Once we've started processing a touch gesture we'll ignore mouse events
+ touchstart: function(d, e, t) {
+ d.switchTo(touchStartedState, e, t);
+ },
+
+ // Or if we see a mouse event first, then start processing a mouse-based
+ // gesture, and ignore any touch events
+ mousedown: function(d, e) {
+ d.switchTo(mouseDownState, e);
+ }
+ };
+
+ // One finger is down but we haven't generated any event yet. We're
+ // waiting to see... If the finger goes up soon, its a tap. If the finger
+ // stays down and still, its a hold. If the finger moves its a pan/swipe.
+ // And if a second finger goes down, its a transform
+ var touchStartedState = {
+ name: "touchStartedState",
+ init: function(d, e, t) {
+ // Remember the id of the touch that started
+ d.touch1 = t.identifier;
+ // Get the coordinates of the touch
+ d.start = d.last = coordinates(e,t);
+ // Start a timer for a hold
+ // If we're doing hold events, start a timer for them
+ if (d.options.holdEvents)
+ d.startTimer('holdtimeout', GD.HOLD_INTERVAL);
+ },
+
+ touchstart: function(d, e, t) {
+ // If another finger goes down in this state, then
+ // go to transform state to start 2-finger gestures.
+ d.clearTimer('holdtimeout');
+ d.switchTo(transformState, e, t);
+ },
+ touchmove: function(d, e, t) {
+ // Ignore any touches but the initial one
+ // This could happen if there was still a finger down after
+ // the end of a previous 2-finger gesture, e.g.
+ if (t.identifier !== d.touch1)
+ return;
+
+ if (abs(t.screenX - d.start.screenX) > GD.PAN_THRESHOLD ||
+ abs(t.screenY - d.start.screenY) > GD.PAN_THRESHOLD) {
+ d.clearTimer('holdtimeout');
+ d.switchTo(panStartedState, e, t);
+ }
+ },
+ touchend: function(d, e, t) {
+ // Ignore any touches but the initial one
+ if (t.identifier !== d.touch1)
+ return;
+
+ // If there was a previous tap that was close enough in time
+ // and space, then emit a 'dbltap' event
+ if (d.lastTap && isDoubleTap(d.lastTap, d.start)) {
+ d.emitEvent('tap', d.start);
+ d.emitEvent('dbltap', d.start);
+ // clear the lastTap property, so we don't get another one
+ d.lastTap = null;
+ }
+ else {
+ // Emit a 'tap' event using the starting coordinates
+ // as the event details
+ d.emitEvent('tap', d.start);
+
+ // Remember the coordinates of this tap so we can detect double taps
+ d.lastTap = coordinates(e, t);
+ }
+
+ // In either case clear the timer and go back to the initial state
+ d.clearTimer('holdtimeout');
+ d.switchTo(initialState);
+ },
+
+ holdtimeout: function(d) {
+ d.switchTo(holdState);
+ },
+
+ };
+
+ // A single touch has moved enough to exceed the pan threshold and now
+ // we're going to generate pan events after each move and a swipe event
+ // when the touch ends. We ignore any other touches that occur while this
+ // pan/swipe gesture is in progress.
+ var panStartedState = {
+ name: "panStartedState",
+ init: function(d, e, t) {
+ // If we transition into this state with a touchmove event,
+ // then process it with that handler. If we don't do this then
+ // we can end up with swipe events that don't know their velocity
+ if (e.type === "touchmove")
+ panStartedState.touchmove(d, e, t);
+ },
+
+ touchmove: function(d, e, t) {
+ // Ignore any fingers other than the one we're tracking
+ if (t.identifier !== d.touch1)
+ return;
+
+ // Each time the touch moves, emit a pan event but stay in this state
+ var current = coordinates(e,t);
+ d.emitEvent('pan', {
+ absolute: {
+ dx: current.screenX - d.start.screenX,
+ dy: current.screenY - d.start.screenY
+ },
+ relative: {
+ dx: current.screenX - d.last.screenX,
+ dy: current.screenY - d.last.screenY
+ },
+ position: current
+ });
+
+ // Track the pan velocity so we can report this with the swipe
+ // Use a exponential moving average for a bit of smoothing
+ // on the velocity
+ var dt = current.timeStamp - d.last.timeStamp;
+ var vx = (current.screenX - d.last.screenX)/dt;
+ var vy = (current.screenY - d.last.screenY)/dt;
+
+ if (d.vx == null) { // first time; no average
+ d.vx = vx;
+ d.vy = vy;
+ }
+ else {
+ d.vx = d.vx * GD.VELOCITY_SMOOTHING +
+ vx * (1 - GD.VELOCITY_SMOOTHING);
+ d.vy = d.vy * GD.VELOCITY_SMOOTHING +
+ vy * (1 - GD.VELOCITY_SMOOTHING);
+ }
+
+ d.last = current;
+ },
+ touchend: function(d, e, t) {
+ // Ignore any fingers other than the one we're tracking
+ if (t.identifier !== d.touch1)
+ return;
+
+ // Emit a swipe event when the finger goes up.
+ // Report start and end point, dx, dy, dt, velocity and direction
+ var current = coordinates(e, t);
+ var dx = current.screenX - d.start.screenX;
+ var dy = current.screenY - d.start.screenY;
+ // angle is a positive number of degrees, starting at 0 on the
+ // positive x axis and increasing clockwise.
+ var angle = atan2(dy,dx) * 180 / PI;
+ if (angle < 0)
+ angle += 360;
+
+ // Direction is 'right', 'down', 'left' or 'up'
+ var direction;
+ if (angle >= 315 || angle < 45)
+ direction = 'right'
+ else if (angle >= 45 && angle < 135)
+ direction = 'down'
+ else if (angle >= 135 && angle < 225)
+ direction = 'left';
+ else if (angle >= 225 && angle < 315)
+ direction = 'up';
+
+ d.emitEvent('swipe', {
+ start: d.start,
+ end: current,
+ dx: dx,
+ dy: dy,
+ dt: e.timeStamp - d.start.timeStamp,
+ vx: d.vx,
+ vy: d.vy,
+ direction: direction,
+ angle: angle
+ });
+
+ // Go back to the initial state
+ d.switchTo(initialState);
+ }
+ };
+
+ // We enter this state if the user touches and holds for long enough
+ // without moving much. When we enter we emit a holdstart event. Motion
+ // after the holdstart generates holdmove events. And when the touch ends
+ // we generate a holdend event. holdmove and holdend events can be used
+ // kind of like drag and drop events in a mouse-based UI. Currently,
+ // these events just report the coordinates of the touch. Do we need
+ // other details?
+ var holdState = {
+ name: "holdState",
+ init: function(d) {
+ d.emitEvent('holdstart', d.start);
+ },
+
+ touchmove: function(d, e, t) {
+ var current = coordinates(e, t);
+ d.emitEvent('holdmove', {
+ absolute: {
+ dx: current.screenX - d.start.screenX,
+ dy: current.screenY - d.start.screenY
+ },
+ relative: {
+ dx: current.screenX - d.last.screenX,
+ dy: current.screenY - d.last.screenY
+ },
+ position: current
+ });
+
+ d.last = current;
+ },
+
+ touchend: function(d, e, t) {
+ var current = coordinates(e, t);
+ d.emitEvent('holdend', {
+ start: d.start,
+ end: current,
+ dx: current.screenX - d.start.screenX,
+ dy: current.screenY - d.start.screenY,
+ });
+ d.switchTo(initialState);
+ },
+ };
+
+ // We enter this state if a second touch starts before we start
+ // recoginzing any other gesture. As the touches move we track the
+ // distance and angle between them to report scale and rotation values
+ // in transform events.
+ var transformState = {
+ name: "transformState",
+ init: function(d, e, t) {
+ // Remember the id of the second touch
+ d.touch2 = t.identifier;
+
+ // Get the two Touch objects
+ var t1 = e.touches.identifiedTouch(d.touch1);
+ var t2 = e.touches.identifiedTouch(d.touch2);
+
+ // Compute and remember the initial distance and angle
+ d.startDistance = d.lastDistance = touchDistance(t1, t2);
+ d.startDirection = d.lastDirection = touchDirection(t1, t2);
+
+ // Don't start emitting events until we're past a threshold
+ d.scaled = d.rotated = false;
+ },
+
+ touchmove: function(d, e, t) {
+ // Ignore touches we're not tracking
+ if (t.identifier !== d.touch1 && t.identifier !== d.touch2)
+ return;
+
+ // Get the two Touch objects
+ var t1 = e.touches.identifiedTouch(d.touch1);
+ var t2 = e.touches.identifiedTouch(d.touch2);
+
+ // Compute the new midpoints, distance and direction
+ var midpoint = midpoints(e, t1, t2);
+ var distance = touchDistance(t1, t2);
+ var direction = touchDirection(t1, t2);
+ var rotation = touchRotation(d.startDirection, direction);
+
+ // Check all of these numbers against the thresholds. Otherwise
+ // the transforms are too jittery even when you try to hold your
+ // fingers still.
+ if (!d.scaled) {
+ if (abs(distance - d.startDistance) > GD.SCALE_THRESHOLD)
+ d.scaled = true;
+ else
+ distance = d.startDistance;
+ }
+ if (!d.rotated) {
+ if (abs(rotation) > GD.ROTATE_THRESHOLD)
+ d.rotated = true;
+ else
+ direction = d.startDirection;
+ }
+
+ // If nothing has exceeded the threshold yet, then we
+ // don't even have to fire an event.
+ if (d.scaled || d.rotated) {
+ // The detail field for the transform gesture event includes
+ // 'absolute' transformations against the initial values and
+ // 'relative' transformations against the values from the last
+ // transformgesture event.
+ d.emitEvent('transform', {
+ absolute: { // transform details since gesture start
+ scale: distance / d.startDistance,
+ rotate: touchRotation(d.startDirection, direction),
+ },
+ relative: { // transform since last gesture change
+ scale: distance / d.lastDistance,
+ rotate: touchRotation(d.lastDirection, direction),
+ },
+ midpoint: midpoint
+ });
+
+ d.lastDistance = distance;
+ d.lastDirection = direction;
+ }
+ },
+
+ touchend: function(d, e, t) {
+ // If either finger goes up, we're done with the gesture.
+ // The user might move that finger and put it right back down
+ // again to begin another 2-finger gesture, so we can't go
+ // back to the initial state while one of the fingers remains up.
+ // On the other hand, we can't go back to touchStartedState because
+ // that would mean that the finger left down could cause a tap or
+ // pan event. So we need an afterTransform state that waits for
+ // a finger to come back down or the other finger to go up.
+ if (t.identifier === d.touch2)
+ d.touch2 = null;
+ else if (t.identifier === d.touch1) {
+ d.touch1 = d.touch2;
+ d.touch2 = null;
+ }
+ else
+ return; // It was a touch we weren't tracking
+
+ d.switchTo(afterTransformState);
+ },
+ };
+
+ // We did a tranform and one finger went up. Wait for that finger to
+ // come back down or the other finger to go up too.
+ var afterTransformState = {
+ name: "afterTransformState",
+ touchstart: function(d, e, t) {
+ d.switchTo(transformState, e, t);
+ },
+
+ touchend: function(d, e, t) {
+ if (t.identifier === d.touch1)
+ d.switchTo(initialState);
+ }
+ };
+
+ var mouseDownState = {
+ name: "mouseDownState",
+ init: function(d, e) {
+ // Register this detector as a *capturing* handler on the document
+ // so we get all subsequent mouse events until we remove these handlers
+ var doc = d.element.ownerDocument;
+ doc.addEventListener('mousemove', d, true);
+ doc.addEventListener('mouseup', d, true);
+
+ // Get the coordinates of the mouse event
+ d.start = d.last = mouseCoordinates(e);
+
+ // Start a timer for a hold
+ // If we're doing hold events, start a timer for them
+ if (d.options.holdEvents)
+ d.startTimer('holdtimeout', GD.HOLD_INTERVAL);
+ },
+
+ mousemove: function(d, e) {
+ // If the mouse has moved more than the panning threshold,
+ // then switch to the mouse panning state. Otherwise remain
+ // in this state
+
+ if (abs(e.screenX - d.start.screenX) > GD.MOUSE_PAN_THRESHOLD ||
+ abs(e.screenY - d.start.screenY) > GD.MOUSE_PAN_THRESHOLD) {
+ d.clearTimer('holdtimeout');
+ d.switchTo(mousePannedState, e);
+ }
+ },
+
+ mouseup: function(d, e) {
+ // Remove the capturing event handlers
+ var doc = d.element.ownerDocument;
+ doc.removeEventListener('mousemove', d, true);
+ doc.removeEventListener('mouseup', d, true);
+
+ // If there was a previous tap that was close enough in time
+ // and space, then emit a 'dbltap' event
+ if (d.lastTap && isDoubleTap(d.lastTap, d.start)) {
+ d.emitEvent('tap', d.start);
+ d.emitEvent('dbltap', d.start);
+ d.lastTap = null; // so we don't get another one
+ }
+ else {
+ // Emit a 'tap' event using the starting coordinates
+ // as the event details
+ d.emitEvent('tap', d.start);
+
+ // Remember the coordinates of this tap so we can detect double taps
+ d.lastTap = mouseCoordinates(e);
+ }
+
+ // In either case clear the timer and go back to the initial state
+ d.clearTimer('holdtimeout');
+ d.switchTo(initialState);
+ },
+
+ holdtimeout: function(d) {
+ d.switchTo(mouseHoldState);
+ },
+ };
+
+ // Like holdState, but for mouse events instead of touch events
+ var mouseHoldState = {
+ name: "mouseHoldState",
+ init: function(d) {
+ d.emitEvent('holdstart', d.start);
+ },
+
+ mousemove: function(d, e) {
+ var current = mouseCoordinates(e);
+ d.emitEvent('holdmove', {
+ absolute: {
+ dx: current.screenX - d.start.screenX,
+ dy: current.screenY - d.start.screenY
+ },
+ relative: {
+ dx: current.screenX - d.last.screenX,
+ dy: current.screenY - d.last.screenY
+ },
+ position: current
+ });
+
+ d.last = current;
+ },
+
+ mouseup: function(d, e) {
+ var current = mouseCoordinates(e);
+ d.emitEvent('holdend', {
+ start: d.start,
+ end: current,
+ dx: current.screenX - d.start.screenX,
+ dy: current.screenY - d.start.screenY,
+ });
+ d.switchTo(initialState);