summaryrefslogtreecommitdiff
path: root/b2g
diff options
context:
space:
mode:
authorMatt A. Tobin <mattatobin@localhost.localdomain>2018-02-02 04:16:08 -0500
committerMatt A. Tobin <mattatobin@localhost.localdomain>2018-02-02 04:16:08 -0500
commit5f8de423f190bbb79a62f804151bc24824fa32d8 (patch)
tree10027f336435511475e392454359edea8e25895d /b2g
parent49ee0794b5d912db1f95dce6eb52d781dc210db5 (diff)
downloaduxp-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.gz
Add m-esr52 at 52.6.0
Diffstat (limited to 'b2g')
-rw-r--r--b2g/LICENSE373
-rw-r--r--b2g/Makefile.in6
-rw-r--r--b2g/app.mozbuild15
-rw-r--r--b2g/app/Makefile.in66
-rw-r--r--b2g/app/b2g.js1018
-rw-r--r--b2g/app/macbuild/Contents/Info.plist.in67
-rw-r--r--b2g/app/macbuild/Contents/MacOS-files.in9
-rw-r--r--b2g/app/macbuild/Contents/Resources/English.lproj/InfoPlist.strings.in5
-rw-r--r--b2g/app/moz.build76
-rw-r--r--b2g/app/nsBrowserApp.cpp239
-rw-r--r--b2g/app/ua-update.json.in51
-rw-r--r--b2g/branding/branding-common.mozbuild23
-rw-r--r--b2g/branding/browserhtml/app.icnsbin0 -> 256255 bytes
-rw-r--r--b2g/branding/browserhtml/app.icobin0 -> 370070 bytes
-rw-r--r--b2g/branding/browserhtml/background.pngbin0 -> 129900 bytes
-rw-r--r--b2g/branding/browserhtml/configure.sh5
-rw-r--r--b2g/branding/browserhtml/content/about.pngbin0 -> 25842 bytes
-rw-r--r--b2g/branding/browserhtml/content/favicon32.pngbin0 -> 2670 bytes
-rw-r--r--b2g/branding/browserhtml/content/icon48.pngbin0 -> 5302 bytes
-rw-r--r--b2g/branding/browserhtml/content/icon64.pngbin0 -> 8267 bytes
-rw-r--r--b2g/branding/browserhtml/content/jar.mn10
-rw-r--r--b2g/branding/browserhtml/content/logo.pngbin0 -> 19488 bytes
-rw-r--r--b2g/branding/browserhtml/content/logoWordmark.pngbin0 -> 14021 bytes
-rw-r--r--b2g/branding/browserhtml/content/moz.build7
-rw-r--r--b2g/branding/browserhtml/content/splash.pngbin0 -> 19610 bytes
-rw-r--r--b2g/branding/browserhtml/default.pngbin0 -> 36395 bytes
-rw-r--r--b2g/branding/browserhtml/disk.icnsbin0 -> 891873 bytes
-rw-r--r--b2g/branding/browserhtml/dsstorebin0 -> 12292 bytes
-rw-r--r--b2g/branding/browserhtml/locales/en-US/brand.dtd7
-rw-r--r--b2g/branding/browserhtml/locales/en-US/brand.properties6
-rw-r--r--b2g/branding/browserhtml/locales/jar.mn11
-rw-r--r--b2g/branding/browserhtml/locales/moz.build7
-rw-r--r--b2g/branding/browserhtml/moz.build10
-rw-r--r--b2g/branding/horizon/app.icnsbin0 -> 53371 bytes
-rw-r--r--b2g/branding/horizon/app.icobin0 -> 15086 bytes
-rw-r--r--b2g/branding/horizon/background.pngbin0 -> 129900 bytes
-rw-r--r--b2g/branding/horizon/configure.sh5
-rw-r--r--b2g/branding/horizon/content/about.pngbin0 -> 25842 bytes
-rw-r--r--b2g/branding/horizon/content/favicon32.pngbin0 -> 2670 bytes
-rw-r--r--b2g/branding/horizon/content/icon48.pngbin0 -> 5302 bytes
-rw-r--r--b2g/branding/horizon/content/icon64.pngbin0 -> 8267 bytes
-rw-r--r--b2g/branding/horizon/content/jar.mn10
-rw-r--r--b2g/branding/horizon/content/logo.pngbin0 -> 19488 bytes
-rw-r--r--b2g/branding/horizon/content/logoWordmark.pngbin0 -> 14021 bytes
-rw-r--r--b2g/branding/horizon/content/moz.build7
-rw-r--r--b2g/branding/horizon/content/splash.pngbin0 -> 19610 bytes
-rw-r--r--b2g/branding/horizon/default.pngbin0 -> 51360 bytes
-rw-r--r--b2g/branding/horizon/disk.icnsbin0 -> 891873 bytes
-rw-r--r--b2g/branding/horizon/dsstorebin0 -> 12292 bytes
-rw-r--r--b2g/branding/horizon/locales/en-US/brand.dtd8
-rw-r--r--b2g/branding/horizon/locales/en-US/brand.properties6
-rw-r--r--b2g/branding/horizon/locales/jar.mn11
-rw-r--r--b2g/branding/horizon/locales/moz.build7
-rw-r--r--b2g/branding/horizon/moz.build10
-rw-r--r--b2g/branding/official/app.icnsbin0 -> 11953 bytes
-rw-r--r--b2g/branding/official/app.icobin0 -> 4286 bytes
-rw-r--r--b2g/branding/official/background.pngbin0 -> 129900 bytes
-rw-r--r--b2g/branding/official/configure.sh6
-rw-r--r--b2g/branding/official/content/about.pngbin0 -> 25842 bytes
-rw-r--r--b2g/branding/official/content/favicon32.pngbin0 -> 2670 bytes
-rw-r--r--b2g/branding/official/content/icon48.pngbin0 -> 5302 bytes
-rw-r--r--b2g/branding/official/content/icon64.pngbin0 -> 8267 bytes
-rw-r--r--b2g/branding/official/content/jar.mn10
-rw-r--r--b2g/branding/official/content/logo.pngbin0 -> 19488 bytes
-rw-r--r--b2g/branding/official/content/logoWordmark.pngbin0 -> 14021 bytes
-rw-r--r--b2g/branding/official/content/moz.build7
-rw-r--r--b2g/branding/official/content/splash.pngbin0 -> 19610 bytes
-rw-r--r--b2g/branding/official/default.pngbin0 -> 4762 bytes
-rw-r--r--b2g/branding/official/disk.icnsbin0 -> 891873 bytes
-rw-r--r--b2g/branding/official/dsstorebin0 -> 12292 bytes
-rw-r--r--b2g/branding/official/locales/en-US/brand.dtd8
-rw-r--r--b2g/branding/official/locales/en-US/brand.properties6
-rw-r--r--b2g/branding/official/locales/jar.mn11
-rw-r--r--b2g/branding/official/locales/moz.build7
-rw-r--r--b2g/branding/official/moz.build10
-rw-r--r--b2g/branding/unofficial/app.icnsbin0 -> 11953 bytes
-rw-r--r--b2g/branding/unofficial/app.icobin0 -> 4286 bytes
-rw-r--r--b2g/branding/unofficial/background.pngbin0 -> 129900 bytes
-rw-r--r--b2g/branding/unofficial/configure.sh6
-rw-r--r--b2g/branding/unofficial/content/about.pngbin0 -> 16858 bytes
-rw-r--r--b2g/branding/unofficial/content/favicon32.pngbin0 -> 1761 bytes
-rw-r--r--b2g/branding/unofficial/content/icon48.pngbin0 -> 4053 bytes
-rw-r--r--b2g/branding/unofficial/content/icon64.pngbin0 -> 5897 bytes
-rw-r--r--b2g/branding/unofficial/content/jar.mn10
-rw-r--r--b2g/branding/unofficial/content/logo.pngbin0 -> 19844 bytes
-rw-r--r--b2g/branding/unofficial/content/logoWordmark.pngbin0 -> 15088 bytes
-rw-r--r--b2g/branding/unofficial/content/moz.build7
-rw-r--r--b2g/branding/unofficial/content/splash.pngbin0 -> 19766 bytes
-rw-r--r--b2g/branding/unofficial/default.pngbin0 -> 4762 bytes
-rw-r--r--b2g/branding/unofficial/disk.icnsbin0 -> 891873 bytes
-rw-r--r--b2g/branding/unofficial/dsstorebin0 -> 12292 bytes
-rw-r--r--b2g/branding/unofficial/locales/en-US/brand.dtd8
-rw-r--r--b2g/branding/unofficial/locales/en-US/brand.properties6
-rw-r--r--b2g/branding/unofficial/locales/jar.mn11
-rw-r--r--b2g/branding/unofficial/locales/moz.build7
-rw-r--r--b2g/branding/unofficial/moz.build10
-rw-r--r--b2g/build.mk31
-rw-r--r--b2g/chrome/content/ErrorPage.js73
-rw-r--r--b2g/chrome/content/aboutCertError.xhtml233
-rw-r--r--b2g/chrome/content/arrow.svg5
-rw-r--r--b2g/chrome/content/blank.css7
-rw-r--r--b2g/chrome/content/blank.html10
-rw-r--r--b2g/chrome/content/content.css321
-rw-r--r--b2g/chrome/content/desktop.css59
-rw-r--r--b2g/chrome/content/desktop.js179
-rw-r--r--b2g/chrome/content/devtools/adb.js233
-rw-r--r--b2g/chrome/content/devtools/debugger.js397
-rw-r--r--b2g/chrome/content/devtools/hud.js1017
-rw-r--r--b2g/chrome/content/identity.js166
-rw-r--r--b2g/chrome/content/images/arrowdown-16.pngbin0 -> 246 bytes
-rw-r--r--b2g/chrome/content/images/arrowright-16.pngbin0 -> 235 bytes
-rw-r--r--b2g/chrome/content/images/desktop/home-black.pngbin0 -> 331 bytes
-rw-r--r--b2g/chrome/content/images/desktop/home-white.pngbin0 -> 276 bytes
-rw-r--r--b2g/chrome/content/images/desktop/rotate.pngbin0 -> 657 bytes
-rw-r--r--b2g/chrome/content/images/error.pngbin0 -> 433 bytes
-rw-r--r--b2g/chrome/content/images/errorpage-larry-black.pngbin0 -> 850 bytes
-rw-r--r--b2g/chrome/content/images/errorpage-larry-white.pngbin0 -> 886 bytes
-rw-r--r--b2g/chrome/content/images/errorpage-warning.pngbin0 -> 631 bytes
-rw-r--r--b2g/chrome/content/images/exitfullscreen-hdpi.pngbin0 -> 3409 bytes
-rw-r--r--b2g/chrome/content/images/fullscreen-hdpi.pngbin0 -> 3382 bytes
-rw-r--r--b2g/chrome/content/images/mute-hdpi.pngbin0 -> 3217 bytes
-rw-r--r--b2g/chrome/content/images/pause-hdpi.pngbin0 -> 3042 bytes
-rw-r--r--b2g/chrome/content/images/play-hdpi.pngbin0 -> 3318 bytes
-rw-r--r--b2g/chrome/content/images/scrubber-hdpi.pngbin0 -> 3967 bytes
-rw-r--r--b2g/chrome/content/images/throbber.pngbin0 -> 11862 bytes
-rw-r--r--b2g/chrome/content/images/unmute-hdpi.pngbin0 -> 3259 bytes
-rw-r--r--b2g/chrome/content/netError.css131
-rw-r--r--b2g/chrome/content/screen.js276
-rw-r--r--b2g/chrome/content/settings.js698
-rw-r--r--b2g/chrome/content/shell.css81
-rw-r--r--b2g/chrome/content/shell.html66
-rw-r--r--b2g/chrome/content/shell.js1308
-rw-r--r--b2g/chrome/content/shell_remote.html19
-rw-r--r--b2g/chrome/content/shell_remote.js139
-rw-r--r--b2g/chrome/content/test/mochitest/RecordingStatusChromeScript.js40
-rw-r--r--b2g/chrome/content/test/mochitest/RecordingStatusHelper.js82
-rw-r--r--b2g/chrome/content/test/mochitest/file_getusermedia_iframe.html36
-rw-r--r--b2g/chrome/content/test/mochitest/mochitest.ini11
-rw-r--r--b2g/chrome/content/test/mochitest/moz.build7
-rw-r--r--b2g/chrome/content/test/mochitest/test_recordingStatus_basic.html119
-rw-r--r--b2g/chrome/content/test/mochitest/test_recordingStatus_iframe.html71
-rw-r--r--b2g/chrome/content/test/mochitest/test_recordingStatus_kill_content_process.html72
-rw-r--r--b2g/chrome/content/test/mochitest/test_recordingStatus_multiple_requests.html108
-rw-r--r--b2g/chrome/content/touchcontrols.css233
-rw-r--r--b2g/chrome/jar.mn60
-rw-r--r--b2g/chrome/moz.build13
-rw-r--r--b2g/common.configure32
-rw-r--r--b2g/components/AboutServiceWorkers.jsm183
-rw-r--r--b2g/components/ActivityChannel.jsm64
-rw-r--r--b2g/components/AlertsHelper.jsm279
-rw-r--r--b2g/components/AlertsService.js153
-rw-r--r--b2g/components/B2GAboutRedirector.js78
-rw-r--r--b2g/components/B2GAppMigrator.js152
-rw-r--r--b2g/components/B2GComponents.manifest108
-rw-r--r--b2g/components/B2GPresentationDevicePrompt.js87
-rw-r--r--b2g/components/BootstrapCommandLine.js52
-rw-r--r--b2g/components/Bootstraper.jsm156
-rw-r--r--b2g/components/CommandLine.js29
-rw-r--r--b2g/components/ContentPermissionPrompt.js461
-rw-r--r--b2g/components/ContentRequestHelper.jsm68
-rw-r--r--b2g/components/DebuggerActors.js83
-rw-r--r--b2g/components/DirectoryProvider.js295
-rw-r--r--b2g/components/ErrorPage.jsm187
-rw-r--r--b2g/components/FilePicker.js223
-rw-r--r--b2g/components/Frames.jsm146
-rw-r--r--b2g/components/FxAccountsMgmtService.jsm173
-rw-r--r--b2g/components/FxAccountsUIGlue.js39
-rw-r--r--b2g/components/GaiaChrome.cpp188
-rw-r--r--b2g/components/GaiaChrome.h44
-rw-r--r--b2g/components/GlobalSimulatorScreen.jsm90
-rw-r--r--b2g/components/HelperAppDialog.js115
-rw-r--r--b2g/components/LogCapture.jsm221
-rw-r--r--b2g/components/LogParser.jsm257
-rw-r--r--b2g/components/LogShake.jsm588
-rw-r--r--b2g/components/MailtoProtocolHandler.js46
-rw-r--r--b2g/components/OMAContentHandler.js57
-rw-r--r--b2g/components/OopCommandLine.js46
-rw-r--r--b2g/components/OrientationChangeHandler.jsm70
-rw-r--r--b2g/components/PresentationRequestUIGlue.js116
-rw-r--r--b2g/components/ProcessGlobal.js202
-rw-r--r--b2g/components/RecoveryService.js160
-rw-r--r--b2g/components/SafeMode.jsm150
-rw-r--r--b2g/components/Screenshot.jsm43
-rw-r--r--b2g/components/SignInToWebsite.jsm444
-rw-r--r--b2g/components/SimulatorScreen.js117
-rw-r--r--b2g/components/SmsProtocolHandler.js74
-rw-r--r--b2g/components/SystemAppProxy.jsm377
-rw-r--r--b2g/components/SystemMessageInternal.js64
-rw-r--r--b2g/components/TelProtocolHandler.js60
-rw-r--r--b2g/components/TelURIParser.jsm120
-rw-r--r--b2g/components/UpdatePrompt.js783
-rw-r--r--b2g/components/moz.build91
-rw-r--r--b2g/components/nsIGaiaChrome.idl15
-rw-r--r--b2g/components/nsISystemMessagesInternal.idl51
-rw-r--r--b2g/components/test/mochitest/SandboxPromptTest.html57
-rw-r--r--b2g/components/test/mochitest/filepicker_path_handler_chrome.js31
-rw-r--r--b2g/components/test/mochitest/mochitest.ini28
-rw-r--r--b2g/components/test/mochitest/permission_handler_chrome.js36
-rw-r--r--b2g/components/test/mochitest/presentation_prompt_handler_chrome.js94
-rw-r--r--b2g/components/test/mochitest/presentation_ui_glue_handler_chrome.js32
-rw-r--r--b2g/components/test/mochitest/screenshot_helper.js40
-rw-r--r--b2g/components/test/mochitest/systemapp_helper.js173
-rw-r--r--b2g/components/test/mochitest/test_filepicker_path.html130
-rw-r--r--b2g/components/test/mochitest/test_permission_deny.html83
-rw-r--r--b2g/components/test/mochitest/test_permission_gum_remember.html170
-rw-r--r--b2g/components/test/mochitest/test_permission_visibilitychange.html57
-rw-r--r--b2g/components/test/mochitest/test_presentation_device_prompt.html145
-rw-r--r--b2g/components/test/mochitest/test_presentation_request_ui_glue.html105
-rw-r--r--b2g/components/test/mochitest/test_sandbox_permission.html104
-rw-r--r--b2g/components/test/mochitest/test_screenshot.html31
-rw-r--r--b2g/components/test/mochitest/test_systemapp.html31
-rw-r--r--b2g/components/test/moz.build8
-rw-r--r--b2g/components/test/unit/data/test_logger_filebin0 -> 4037 bytes
-rw-r--r--b2g/components/test/unit/head_identity.js159
-rw-r--r--b2g/components/test/unit/head_logshake_gonk.js58
-rw-r--r--b2g/components/test/unit/test_aboutserviceworkers.js142
-rw-r--r--b2g/components/test/unit/test_bug793310.js39
-rw-r--r--b2g/components/test/unit/test_bug832946.js18
-rw-r--r--b2g/components/test/unit/test_fxaccounts.js212
-rw-r--r--b2g/components/test/unit/test_logcapture.js13
-rw-r--r--b2g/components/test/unit/test_logcapture_gonk.js70
-rw-r--r--b2g/components/test/unit/test_logparser.js75
-rw-r--r--b2g/components/test/unit/test_logshake.js218
-rw-r--r--b2g/components/test/unit/test_logshake_gonk.js61
-rw-r--r--b2g/components/test/unit/test_logshake_gonk_compression.js76
-rw-r--r--b2g/components/test/unit/test_logshake_readLog_gonk.js65
-rw-r--r--b2g/components/test/unit/test_signintowebsite.js322
-rw-r--r--b2g/components/test/unit/xpcshell.ini49
-rw-r--r--b2g/config/aries/config.json52
-rw-r--r--b2g/config/aries/releng-aries.manifest27
-rw-r--r--b2g/config/aries/sources.xml162
-rw-r--r--b2g/config/desktop/config.json8
-rw-r--r--b2g/config/gaia.json7
-rw-r--r--b2g/config/mozconfigs/common24
-rw-r--r--b2g/config/mozconfigs/common.override8
-rw-r--r--b2g/config/mozconfigs/ics_armv7a_gecko/debug20
-rw-r--r--b2g/config/mozconfigs/ics_armv7a_gecko/nightly21
-rw-r--r--b2g/config/mozconfigs/linux32_gecko/debug34
-rw-r--r--b2g/config/mozconfigs/linux32_gecko/nightly36
-rw-r--r--b2g/config/mozconfigs/linux64_gecko/nightly36
-rw-r--r--b2g/config/mozconfigs/macosx64_gecko/debug33
-rw-r--r--b2g/config/mozconfigs/macosx64_gecko/nightly34
-rw-r--r--b2g/config/mozconfigs/win32_gecko/debug29
-rw-r--r--b2g/config/mozconfigs/win32_gecko/nightly30
-rw-r--r--b2g/config/nexus-5-l/config.json55
-rw-r--r--b2g/config/nexus-5-l/releng-nexus5.manifest19
-rw-r--r--b2g/config/nexus-5-l/sources.xml162
-rw-r--r--b2g/config/tooltool-manifests/linux32/releng.manifest32
-rw-r--r--b2g/config/tooltool-manifests/macosx64/releng.manifest24
-rw-r--r--b2g/config/tooltool-manifests/win32/releng.manifest22
-rw-r--r--b2g/confvars.sh45
-rw-r--r--b2g/dev/app.mozbuild21
-rw-r--r--b2g/dev/app/moz.build15
-rw-r--r--b2g/dev/app/mulet.js20
-rw-r--r--b2g/dev/build.mk6
-rw-r--r--b2g/dev/config/mozconfigs/linux64/mulet10
-rw-r--r--b2g/dev/config/mozconfigs/linux64/mulet-hazards13
-rw-r--r--b2g/dev/config/mozconfigs/linux64/mulet_dbg14
-rw-r--r--b2g/dev/config/mozconfigs/macosx64/mulet27
-rw-r--r--b2g/dev/config/mozconfigs/win32/mulet13
-rw-r--r--b2g/dev/config/tooltool-manifests/linux64/hazard.manifest48
-rw-r--r--b2g/dev/config/tooltool-manifests/linux64/releng.manifest48
-rw-r--r--b2g/dev/config/tooltool-manifests/macosx64/releng.manifest24
-rw-r--r--b2g/dev/config/tooltool-manifests/win32/releng.manifest22
-rw-r--r--b2g/dev/confvars.sh12
-rw-r--r--b2g/dev/moz.configure9
-rw-r--r--b2g/gaia/Makefile.in14
-rw-r--r--b2g/gaia/moz.build20
-rw-r--r--b2g/gaia/run-b2g.c50
-rw-r--r--b2g/gaia/run-b2g.cpp102
-rw-r--r--b2g/graphene/app.mozbuild17
-rw-r--r--b2g/graphene/build.mk5
-rw-r--r--b2g/graphene/config/horizon-mozconfigs/common24
-rw-r--r--b2g/graphene/config/horizon-mozconfigs/common.override5
-rw-r--r--b2g/graphene/config/horizon-mozconfigs/linux32/debug25
-rw-r--r--b2g/graphene/config/horizon-mozconfigs/linux32/nightly25
-rw-r--r--b2g/graphene/config/horizon-mozconfigs/linux64/debug25
-rw-r--r--b2g/graphene/config/horizon-mozconfigs/linux64/nightly25
-rw-r--r--b2g/graphene/config/horizon-mozconfigs/macosx64/debug23
-rw-r--r--b2g/graphene/config/horizon-mozconfigs/macosx64/nightly23
-rw-r--r--b2g/graphene/config/horizon-mozconfigs/win32/debug19
-rw-r--r--b2g/graphene/config/horizon-mozconfigs/win32/nightly18
-rw-r--r--b2g/graphene/config/horizon-mozconfigs/win64/debug23
-rw-r--r--b2g/graphene/config/horizon-mozconfigs/win64/nightly26
-rw-r--r--b2g/graphene/config/mozconfigs/common24
-rw-r--r--b2g/graphene/config/mozconfigs/common.override5
-rw-r--r--b2g/graphene/config/mozconfigs/linux32/debug25
-rw-r--r--b2g/graphene/config/mozconfigs/linux32/nightly25
-rw-r--r--b2g/graphene/config/mozconfigs/linux64/debug25
-rw-r--r--b2g/graphene/config/mozconfigs/linux64/nightly25
-rw-r--r--b2g/graphene/config/mozconfigs/macosx64/debug23
-rw-r--r--b2g/graphene/config/mozconfigs/macosx64/nightly23
-rw-r--r--b2g/graphene/config/mozconfigs/win32/debug19
-rw-r--r--b2g/graphene/config/mozconfigs/win32/nightly18
-rw-r--r--b2g/graphene/config/mozconfigs/win64/debug23
-rw-r--r--b2g/graphene/config/mozconfigs/win64/nightly26
-rw-r--r--b2g/graphene/confvars.sh53
-rw-r--r--b2g/graphene/graphene.js59
-rw-r--r--b2g/graphene/moz.configure7
-rw-r--r--b2g/graphene/settings.json3
-rw-r--r--b2g/installer/Makefile.in135
-rwxr-xr-xb2g/installer/flash.bat73
-rw-r--r--b2g/installer/moz.build6
-rw-r--r--b2g/installer/package-manifest.in845
-rw-r--r--b2g/installer/removed-files.in45
-rw-r--r--b2g/locales/Makefile.in149
-rw-r--r--b2g/locales/all-locales3
-rw-r--r--b2g/locales/en-US/b2g-l10n.js12
-rw-r--r--b2g/locales/en-US/chrome/graphene.properties5
-rw-r--r--b2g/locales/en-US/chrome/overrides/aboutCertError.dtd38
-rw-r--r--b2g/locales/en-US/chrome/overrides/appstrings.properties39
-rw-r--r--b2g/locales/en-US/defines.inc9
-rw-r--r--b2g/locales/filter.py15
-rw-r--r--b2g/locales/jar.mn75
-rw-r--r--b2g/locales/l10n.ini13
-rw-r--r--b2g/locales/moz.build7
-rw-r--r--b2g/moz.build14
-rw-r--r--b2g/moz.configure32
-rw-r--r--b2g/simulator/bootstrap.js4
-rw-r--r--b2g/simulator/build_xpi.py148
-rw-r--r--b2g/simulator/custom-prefs.js8
-rw-r--r--b2g/simulator/custom-settings.json6
-rw-r--r--b2g/simulator/icon.pngbin0 -> 4762 bytes
-rw-r--r--b2g/simulator/icon64.pngbin0 -> 7858 bytes
-rw-r--r--b2g/simulator/install.rdf.in52
-rw-r--r--b2g/test/b2g-unittest-requirements.txt8
-rw-r--r--b2g/test/emulator.manifest6
327 files changed, 22896 insertions, 0 deletions
diff --git a/b2g/LICENSE b/b2g/LICENSE
new file mode 100644
index 0000000000..14e2f777f6
--- /dev/null
+++ b/b2g/LICENSE
@@ -0,0 +1,373 @@
+Mozilla Public License Version 2.0
+==================================
+
+1. Definitions
+--------------
+
+1.1. "Contributor"
+ means each individual or legal entity that creates, contributes to
+ the creation of, or owns Covered Software.
+
+1.2. "Contributor Version"
+ means the combination of the Contributions of others (if any) used
+ by a Contributor and that particular Contributor's Contribution.
+
+1.3. "Contribution"
+ means Covered Software of a particular Contributor.
+
+1.4. "Covered Software"
+ means Source Code Form to which the initial Contributor has attached
+ the notice in Exhibit A, the Executable Form of such Source Code
+ Form, and Modifications of such Source Code Form, in each case
+ including portions thereof.
+
+1.5. "Incompatible With Secondary Licenses"
+ means
+
+ (a) that the initial Contributor has attached the notice described
+ in Exhibit B to the Covered Software; or
+
+ (b) that the Covered Software was made available under the terms of
+ version 1.1 or earlier of the License, but not also under the
+ terms of a Secondary License.
+
+1.6. "Executable Form"
+ means any form of the work other than Source Code Form.
+
+1.7. "Larger Work"
+ means a work that combines Covered Software with other material, in
+ a separate file or files, that is not Covered Software.
+
+1.8. "License"
+ means this document.
+
+1.9. "Licensable"
+ means having the right to grant, to the maximum extent possible,
+ whether at the time of the initial grant or subsequently, any and
+ all of the rights conveyed by this License.
+
+1.10. "Modifications"
+ means any of the following:
+
+ (a) any file in Source Code Form that results from an addition to,
+ deletion from, or modification of the contents of Covered
+ Software; or
+
+ (b) any new file in Source Code Form that contains any Covered
+ Software.
+
+1.11. "Patent Claims" of a Contributor
+ means any patent claim(s), including without limitation, method,
+ process, and apparatus claims, in any patent Licensable by such
+ Contributor that would be infringed, but for the grant of the
+ License, by the making, using, selling, offering for sale, having
+ made, import, or transfer of either its Contributions or its
+ Contributor Version.
+
+1.12. "Secondary License"
+ means either the GNU General Public License, Version 2.0, the GNU
+ Lesser General Public License, Version 2.1, the GNU Affero General
+ Public License, Version 3.0, or any later versions of those
+ licenses.
+
+1.13. "Source Code Form"
+ means the form of the work preferred for making modifications.
+
+1.14. "You" (or "Your")
+ means an individual or a legal entity exercising rights under this
+ License. For legal entities, "You" includes any entity that
+ controls, is controlled by, or is under common control with You. For
+ purposes of this definition, "control" means (a) the power, direct
+ or indirect, to cause the direction or management of such entity,
+ whether by contract or otherwise, or (b) ownership of more than
+ fifty percent (50%) of the outstanding shares or beneficial
+ ownership of such entity.
+
+2. License Grants and Conditions
+--------------------------------
+
+2.1. Grants
+
+Each Contributor hereby grants You a world-wide, royalty-free,
+non-exclusive license:
+
+(a) under intellectual property rights (other than patent or trademark)
+ Licensable by such Contributor to use, reproduce, make available,
+ modify, display, perform, distribute, and otherwise exploit its
+ Contributions, either on an unmodified basis, with Modifications, or
+ as part of a Larger Work; and
+
+(b) under Patent Claims of such Contributor to make, use, sell, offer
+ for sale, have made, import, and otherwise transfer either its
+ Contributions or its Contributor Version.
+
+2.2. Effective Date
+
+The licenses granted in Section 2.1 with respect to any Contribution
+become effective for each Contribution on the date the Contributor first
+distributes such Contribution.
+
+2.3. Limitations on Grant Scope
+
+The licenses granted in this Section 2 are the only rights granted under
+this License. No additional rights or licenses will be implied from the
+distribution or licensing of Covered Software under this License.
+Notwithstanding Section 2.1(b) above, no patent license is granted by a
+Contributor:
+
+(a) for any code that a Contributor has removed from Covered Software;
+ or
+
+(b) for infringements caused by: (i) Your and any other third party's
+ modifications of Covered Software, or (ii) the combination of its
+ Contributions with other software (except as part of its Contributor
+ Version); or
+
+(c) under Patent Claims infringed by Covered Software in the absence of
+ its Contributions.
+
+This License does not grant any rights in the trademarks, service marks,
+or logos of any Contributor (except as may be necessary to comply with
+the notice requirements in Section 3.4).
+
+2.4. Subsequent Licenses
+
+No Contributor makes additional grants as a result of Your choice to
+distribute the Covered Software under a subsequent version of this
+License (see Section 10.2) or under the terms of a Secondary License (if
+permitted under the terms of Section 3.3).
+
+2.5. Representation
+
+Each Contributor represents that the Contributor believes its
+Contributions are its original creation(s) or it has sufficient rights
+to grant the rights to its Contributions conveyed by this License.
+
+2.6. Fair Use
+
+This License is not intended to limit any rights You have under
+applicable copyright doctrines of fair use, fair dealing, or other
+equivalents.
+
+2.7. Conditions
+
+Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
+in Section 2.1.
+
+3. Responsibilities
+-------------------
+
+3.1. Distribution of Source Form
+
+All distribution of Covered Software in Source Code Form, including any
+Modifications that You create or to which You contribute, must be under
+the terms of this License. You must inform recipients that the Source
+Code Form of the Covered Software is governed by the terms of this
+License, and how they can obtain a copy of this License. You may not
+attempt to alter or restrict the recipients' rights in the Source Code
+Form.
+
+3.2. Distribution of Executable Form
+
+If You distribute Covered Software in Executable Form then:
+
+(a) such Covered Software must also be made available in Source Code
+ Form, as described in Section 3.1, and You must inform recipients of
+ the Executable Form how they can obtain a copy of such Source Code
+ Form by reasonable means in a timely manner, at a charge no more
+ than the cost of distribution to the recipient; and
+
+(b) You may distribute such Executable Form under the terms of this
+ License, or sublicense it under different terms, provided that the
+ license for the Executable Form does not attempt to limit or alter
+ the recipients' rights in the Source Code Form under this License.
+
+3.3. Distribution of a Larger Work
+
+You may create and distribute a Larger Work under terms of Your choice,
+provided that You also comply with the requirements of this License for
+the Covered Software. If the Larger Work is a combination of Covered
+Software with a work governed by one or more Secondary Licenses, and the
+Covered Software is not Incompatible With Secondary Licenses, this
+License permits You to additionally distribute such Covered Software
+under the terms of such Secondary License(s), so that the recipient of
+the Larger Work may, at their option, further distribute the Covered
+Software under the terms of either this License or such Secondary
+License(s).
+
+3.4. Notices
+
+You may not remove or alter the substance of any license notices
+(including copyright notices, patent notices, disclaimers of warranty,
+or limitations of liability) contained within the Source Code Form of
+the Covered Software, except that You may alter any license notices to
+the extent required to remedy known factual inaccuracies.
+
+3.5. Application of Additional Terms
+
+You may choose to offer, and to charge a fee for, warranty, support,
+indemnity or liability obligations to one or more recipients of Covered
+Software. However, You may do so only on Your own behalf, and not on
+behalf of any Contributor. You must make it absolutely clear that any
+such warranty, support, indemnity, or liability obligation is offered by
+You alone, and You hereby agree to indemnify every Contributor for any
+liability incurred by such Contributor as a result of warranty, support,
+indemnity or liability terms You offer. You may include additional
+disclaimers of warranty and limitations of liability specific to any
+jurisdiction.
+
+4. Inability to Comply Due to Statute or Regulation
+---------------------------------------------------
+
+If it is impossible for You to comply with any of the terms of this
+License with respect to some or all of the Covered Software due to
+statute, judicial order, or regulation then You must: (a) comply with
+the terms of this License to the maximum extent possible; and (b)
+describe the limitations and the code they affect. Such description must
+be placed in a text file included with all distributions of the Covered
+Software under this License. Except to the extent prohibited by statute
+or regulation, such description must be sufficiently detailed for a
+recipient of ordinary skill to be able to understand it.
+
+5. Termination
+--------------
+
+5.1. The rights granted under this License will terminate automatically
+if You fail to comply with any of its terms. However, if You become
+compliant, then the rights granted under this License from a particular
+Contributor are reinstated (a) provisionally, unless and until such
+Contributor explicitly and finally terminates Your grants, and (b) on an
+ongoing basis, if such Contributor fails to notify You of the
+non-compliance by some reasonable means prior to 60 days after You have
+come back into compliance. Moreover, Your grants from a particular
+Contributor are reinstated on an ongoing basis if such Contributor
+notifies You of the non-compliance by some reasonable means, this is the
+first time You have received notice of non-compliance with this License
+from such Contributor, and You become compliant prior to 30 days after
+Your receipt of the notice.
+
+5.2. If You initiate litigation against any entity by asserting a patent
+infringement claim (excluding declaratory judgment actions,
+counter-claims, and cross-claims) alleging that a Contributor Version
+directly or indirectly infringes any patent, then the rights granted to
+You by any and all Contributors for the Covered Software under Section
+2.1 of this License shall terminate.
+
+5.3. In the event of termination under Sections 5.1 or 5.2 above, all
+end user license agreements (excluding distributors and resellers) which
+have been validly granted by You or Your distributors under this License
+prior to termination shall survive termination.
+
+************************************************************************
+* *
+* 6. Disclaimer of Warranty *
+* ------------------------- *
+* *
+* Covered Software is provided under this License on an "as is" *
+* basis, without warranty of any kind, either expressed, implied, or *
+* statutory, including, without limitation, warranties that the *
+* Covered Software is free of defects, merchantable, fit for a *
+* particular purpose or non-infringing. The entire risk as to the *
+* quality and performance of the Covered Software is with You. *
+* Should any Covered Software prove defective in any respect, You *
+* (not any Contributor) assume the cost of any necessary servicing, *
+* repair, or correction. This disclaimer of warranty constitutes an *
+* essential part of this License. No use of any Covered Software is *
+* authorized under this License except under this disclaimer. *
+* *
+************************************************************************
+
+************************************************************************
+* *
+* 7. Limitation of Liability *
+* -------------------------- *
+* *
+* Under no circumstances and under no legal theory, whether tort *
+* (including negligence), contract, or otherwise, shall any *
+* Contributor, or anyone who distributes Covered Software as *
+* permitted above, be liable to You for any direct, indirect, *
+* special, incidental, or consequential damages of any character *
+* including, without limitation, damages for lost profits, loss of *
+* goodwill, work stoppage, computer failure or malfunction, or any *
+* and all other commercial damages or losses, even if such party *
+* shall have been informed of the possibility of such damages. This *
+* limitation of liability shall not apply to liability for death or *
+* personal injury resulting from such party's negligence to the *
+* extent applicable law prohibits such limitation. Some *
+* jurisdictions do not allow the exclusion or limitation of *
+* incidental or consequential damages, so this exclusion and *
+* limitation may not apply to You. *
+* *
+************************************************************************
+
+8. Litigation
+-------------
+
+Any litigation relating to this License may be brought only in the
+courts of a jurisdiction where the defendant maintains its principal
+place of business and such litigation shall be governed by laws of that
+jurisdiction, without reference to its conflict-of-law provisions.
+Nothing in this Section shall prevent a party's ability to bring
+cross-claims or counter-claims.
+
+9. Miscellaneous
+----------------
+
+This License represents the complete agreement concerning the subject
+matter hereof. If any provision of this License is held to be
+unenforceable, such provision shall be reformed only to the extent
+necessary to make it enforceable. Any law or regulation which provides
+that the language of a contract shall be construed against the drafter
+shall not be used to construe this License against a Contributor.
+
+10. Versions of the License
+---------------------------
+
+10.1. New Versions
+
+Mozilla Foundation is the license steward. Except as provided in Section
+10.3, no one other than the license steward has the right to modify or
+publish new versions of this License. Each version will be given a
+distinguishing version number.
+
+10.2. Effect of New Versions
+
+You may distribute the Covered Software under the terms of the version
+of the License under which You originally received the Covered Software,
+or under the terms of any subsequent version published by the license
+steward.
+
+10.3. Modified Versions
+
+If you create software not governed by this License, and you want to
+create a new license for such software, you may create and use a
+modified version of this License if you rename the license and remove
+any references to the name of the license steward (except to note that
+such modified license differs from this License).
+
+10.4. Distributing Source Code Form that is Incompatible With Secondary
+Licenses
+
+If You choose to distribute Source Code Form that is Incompatible With
+Secondary Licenses under the terms of this version of the License, the
+notice described in Exhibit B of this License must be attached.
+
+Exhibit A - Source Code Form License Notice
+-------------------------------------------
+
+ 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/.
+
+If it is not possible or desirable to put the notice in a particular
+file, then You may include the notice in a location (such as a LICENSE
+file in a relevant directory) where a recipient would be likely to look
+for such a notice.
+
+You may add additional accurate notices of copyright ownership.
+
+Exhibit B - "Incompatible With Secondary Licenses" Notice
+---------------------------------------------------------
+
+ This Source Code Form is "Incompatible With Secondary Licenses", as
+ defined by the Mozilla Public License, v. 2.0.
diff --git a/b2g/Makefile.in b/b2g/Makefile.in
new file mode 100644
index 0000000000..9b3e1e08ad
--- /dev/null
+++ b/b2g/Makefile.in
@@ -0,0 +1,6 @@
+# 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 $(topsrcdir)/config/rules.mk
+include $(topsrcdir)/testing/testsuite-targets.mk
diff --git a/b2g/app.mozbuild b/b2g/app.mozbuild
new file mode 100644
index 0000000000..4587438d5f
--- /dev/null
+++ b/b2g/app.mozbuild
@@ -0,0 +1,15 @@
+# vim: set filetype=python:
+# 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('/toolkit/toolkit.mozbuild')
+
+if CONFIG['MOZ_EXTENSIONS']:
+ DIRS += ['/extensions']
+
+DIRS += [
+ '/%s' % CONFIG['MOZ_BRANDING_DIRECTORY'],
+ '/b2g',
+]
diff --git a/b2g/app/Makefile.in b/b2g/app/Makefile.in
new file mode 100644
index 0000000000..6b50b87f72
--- /dev/null
+++ b/b2g/app/Makefile.in
@@ -0,0 +1,66 @@
+# 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/.
+
+# Make sure the standalone glue doesn't try to get libxpcom.so from b2g/app.
+NSDISTMODE = copy
+
+include $(topsrcdir)/config/rules.mk
+
+APP_ICON = app
+
+APP_BINARY = $(MOZ_APP_NAME)$(BIN_SUFFIX)
+
+ifeq (cocoa,$(MOZ_WIDGET_TOOLKIT))
+
+APP_NAME = $(MOZ_APP_DISPLAYNAME)
+APP_VERSION = $(MOZ_APP_VERSION)
+
+ifdef MOZ_DEBUG
+APP_NAME := $(APP_NAME)Debug
+endif
+
+AB_CD = $(MOZ_UI_LOCALE)
+
+ifeq (zh-TW,$(AB_CD))
+LPROJ_ROOT := $(subst -,_,$(AB_CD))
+else
+LPROJ_ROOT := $(firstword $(subst -, ,$(AB_CD)))
+endif
+LPROJ := Contents/Resources/$(LPROJ_ROOT).lproj
+
+clean clobber repackage::
+ rm -rf $(DIST)/$(APP_NAME).app
+
+libs-preqs = \
+ $(call mkdir_deps,$(DIST)/$(APP_NAME).app/Contents/MacOS) \
+ $(call mkdir_deps,$(DIST)/$(APP_NAME).app/$(LPROJ)) \
+ $(NULL)
+
+.PHONY: repackage
+tools repackage:: $(libs-preqs)
+ rsync -a --exclude '*.in' $(srcdir)/macbuild/Contents $(DIST)/$(APP_NAME).app --exclude English.lproj
+ rsync -a --exclude '*.in' $(srcdir)/macbuild/Contents/Resources/English.lproj/ $(DIST)/$(APP_NAME).app/$(LPROJ)
+ sed -e 's/%MOZ_APP_VERSION%/$(MOZ_APP_VERSION)/' -e 's/%MOZ_APP_NAME%/$(MOZ_APP_NAME)/' -e 's/%APP_VERSION%/$(APP_VERSION)/' -e 's/%APP_NAME%/$(APP_NAME)/' -e 's/%APP_BINARY%/$(APP_BINARY)/' $(srcdir)/macbuild/Contents/Info.plist.in > $(DIST)/$(APP_NAME).app/Contents/Info.plist
+ sed -e 's/%APP_VERSION%/$(APP_VERSION)/' -e 's/%APP_NAME%/$(APP_NAME)/' $(srcdir)/macbuild/Contents/Resources/English.lproj/InfoPlist.strings.in | iconv -f UTF-8 -t UTF-16 > $(DIST)/$(APP_NAME).app/$(LPROJ)/InfoPlist.strings
+ rsync -a --exclude 'mangle' --exclude 'shlibsign' --exclude-from='$(srcdir)/macbuild/Contents/MacOS-files.in' $(DIST)/bin/ $(DIST)/$(APP_NAME).app/Contents/Resources
+ rsync -a --include-from='$(srcdir)/macbuild/Contents/MacOS-files.in' --exclude '*' $(DIST)/bin/ $(DIST)/$(APP_NAME).app/Contents/MacOS
+ $(RM) $(DIST)/$(APP_NAME).app/Contents/MacOS/$(PROGRAM)
+ rsync -aL $(PROGRAM) $(DIST)/$(APP_NAME).app/Contents/MacOS
+ cp -RL $(DIST)/branding/app.icns $(DIST)/$(APP_NAME).app/Contents/Resources/$(MOZ_APP_NAME).icns
+ printf APPLMOZB > $(DIST)/$(APP_NAME).app/Contents/PkgInfo
+
+else # MOZ_WIDGET_TOOLKIT != cocoa
+
+libs::
+ $(NSINSTALL) -D $(DIST)/bin/chrome/icons/default
+
+# Copy the app icon for b2g-desktop
+ifeq ($(OS_ARCH),WINNT)
+ cp $(DIST)/branding/$(APP_ICON).ico $(DIST)/bin/chrome/icons/default/$(APP_ICON).ico
+ cp $(DIST)/branding/$(APP_ICON).ico $(DIST)/bin/chrome/icons/default/default.ico
+else ifneq (gonk,$(MOZ_WIDGET_TOOLKIT))
+ cp $(DIST)/branding/default.png $(DIST)/bin/chrome/icons/default/default.png
+endif
+
+endif
diff --git a/b2g/app/b2g.js b/b2g/app/b2g.js
new file mode 100644
index 0000000000..ec2f2a0f1e
--- /dev/null
+++ b/b2g/app/b2g.js
@@ -0,0 +1,1018 @@
+/* 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/. */
+
+#filter substitution
+
+// For the all MOZ_MULET ifdef conditions in this file: see bug 1174234
+
+#ifndef MOZ_MULET
+pref("toolkit.defaultChromeURI", "chrome://b2g/content/shell.html");
+pref("browser.chromeURL", "chrome://b2g/content/");
+#endif
+
+#ifdef MOZ_MULET
+// Set FxOS as the default homepage
+// bug 1000122: this pref is fetched as a complex value,
+// so that it can't be set a just a string.
+// data: url is a workaround this.
+pref("browser.startup.homepage", "data:text/plain,browser.startup.homepage=chrome://b2g/content/shell.html");
+pref("b2g.is_mulet", true);
+// Prevent having the firstrun page
+pref("startup.homepage_welcome_url", "");
+pref("browser.shell.checkDefaultBrowser", false);
+// Automatically open devtools on the firefox os panel
+pref("devtools.toolbox.host", "side");
+pref("devtools.toolbox.sidebar.width", 800);
+// Disable session store to ensure having only one tab opened
+pref("browser.sessionstore.max_tabs_undo", 0);
+pref("browser.sessionstore.max_windows_undo", 0);
+pref("browser.sessionstore.restore_on_demand", false);
+pref("browser.sessionstore.resume_from_crash", false);
+// No e10s on mulet
+pref("browser.tabs.remote.autostart.1", false);
+pref("browser.tabs.remote.autostart.2", false);
+#endif
+
+// Bug 945235: Prevent all bars to be considered visible:
+pref("toolkit.defaultChromeFeatures", "chrome,dialog=no,close,resizable,scrollbars,extrachrome");
+
+// Disable focus rings
+pref("browser.display.focus_ring_width", 0);
+
+// Device pixel to CSS px ratio, in percent. Set to -1 to calculate based on display density.
+pref("browser.viewport.scaleRatio", -1);
+
+/* disable text selection */
+pref("browser.ignoreNativeFrameTextSelection", true);
+
+/* cache prefs */
+#ifdef MOZ_WIDGET_GONK
+pref("browser.cache.disk.enable", true);
+pref("browser.cache.disk.capacity", 55000); // kilobytes
+pref("browser.cache.disk.parent_directory", "/cache");
+#endif
+pref("browser.cache.disk.smart_size.enabled", false);
+pref("browser.cache.disk.smart_size.first_run", false);
+
+pref("browser.cache.memory.enable", true);
+pref("browser.cache.memory.capacity", 1024); // kilobytes
+
+pref("browser.cache.memory_limit", 2048); // 2 MB
+
+/* image cache prefs */
+pref("image.cache.size", 1048576); // bytes
+pref("canvas.image.cache.limit", 20971520); // 20 MB
+
+/* offline cache prefs */
+pref("browser.offline-apps.notify", false);
+pref("browser.cache.offline.enable", true);
+pref("offline-apps.allow_by_default", true);
+
+/* protocol warning prefs */
+pref("network.protocol-handler.warn-external.tel", false);
+pref("network.protocol-handler.warn-external.mailto", false);
+pref("network.protocol-handler.warn-external.vnd.youtube", false);
+
+/* http prefs */
+pref("network.http.pipelining", true);
+pref("network.http.pipelining.ssl", true);
+pref("network.http.proxy.pipelining", true);
+pref("network.http.pipelining.maxrequests" , 6);
+pref("network.http.keep-alive.timeout", 109);
+pref("network.http.max-connections", 20);
+pref("network.http.max-persistent-connections-per-server", 6);
+pref("network.http.max-persistent-connections-per-proxy", 20);
+
+// Keep the old default of accepting all cookies,
+// no matter if you already visited the website or not
+pref("network.cookie.cookieBehavior", 0);
+
+// spdy
+pref("network.http.spdy.push-allowance", 32768);
+pref("network.http.spdy.default-hpack-buffer", 4096); // 4k
+
+// See bug 545869 for details on why these are set the way they are
+pref("network.buffer.cache.count", 24);
+pref("network.buffer.cache.size", 16384);
+
+// predictive actions
+pref("network.predictor.enabled", false); // disabled on b2g
+pref("network.predictor.max-db-size", 2097152); // bytes
+pref("network.predictor.preserve", 50); // percentage of predictor data to keep when cleaning up
+
+/* session history */
+pref("browser.sessionhistory.max_entries", 50);
+pref("browser.sessionhistory.contentViewerTimeout", 360);
+
+/* session store */
+pref("browser.sessionstore.resume_session_once", false);
+pref("browser.sessionstore.resume_from_crash", true);
+pref("browser.sessionstore.resume_from_crash_timeout", 60); // minutes
+pref("browser.sessionstore.interval", 10000); // milliseconds
+pref("browser.sessionstore.max_tabs_undo", 1);
+
+/* these should help performance */
+pref("mozilla.widget.force-24bpp", true);
+pref("mozilla.widget.use-buffer-pixmap", true);
+pref("mozilla.widget.disable-native-theme", true);
+pref("layout.reflow.synthMouseMove", false);
+#ifndef MOZ_X11
+pref("layers.enable-tiles", true);
+#endif
+pref("layers.low-precision-buffer", true);
+pref("layers.low-precision-opacity", "0.5");
+pref("layers.progressive-paint", true);
+
+/* download manager (don't show the window or alert) */
+pref("browser.download.useDownloadDir", true);
+pref("browser.download.folderList", 1); // Default to ~/Downloads
+pref("browser.download.manager.showAlertOnComplete", false);
+pref("browser.download.manager.showAlertInterval", 2000);
+pref("browser.download.manager.retention", 2);
+pref("browser.download.manager.showWhenStarting", false);
+pref("browser.download.manager.closeWhenDone", true);
+pref("browser.download.manager.openDelay", 0);
+pref("browser.download.manager.focusWhenStarting", false);
+pref("browser.download.manager.flashCount", 2);
+pref("browser.download.manager.displayedHistoryDays", 7);
+
+/* download helper */
+pref("browser.helperApps.deleteTempFileOnExit", false);
+
+/* password manager */
+pref("signon.rememberSignons", true);
+pref("signon.expireMasterPassword", false);
+
+/* autocomplete */
+pref("browser.formfill.enable", true);
+
+/* spellcheck */
+pref("layout.spellcheckDefault", 0);
+
+/* block popups by default, and notify the user about blocked popups */
+pref("dom.disable_open_during_load", true);
+pref("privacy.popups.showBrowserMessage", true);
+
+pref("keyword.enabled", true);
+pref("browser.fixup.domainwhitelist.localhost", true);
+
+pref("accessibility.typeaheadfind", false);
+pref("accessibility.typeaheadfind.timeout", 5000);
+pref("accessibility.typeaheadfind.flashBar", 1);
+pref("accessibility.typeaheadfind.linksonly", false);
+pref("accessibility.typeaheadfind.casesensitive", 0);
+
+// SSL error page behaviour
+pref("browser.ssl_override_behavior", 2);
+pref("browser.xul.error_pages.expert_bad_cert", false);
+
+// disable updating
+pref("browser.search.update", false);
+
+// tell the search service that we don't really expose the "current engine"
+pref("browser.search.noCurrentEngine", true);
+
+// enable xul error pages
+pref("browser.xul.error_pages.enabled", true);
+
+// disable color management
+pref("gfx.color_management.mode", 0);
+
+// don't allow JS to move and resize existing windows
+pref("dom.disable_window_move_resize", true);
+
+// prevent click image resizing for nsImageDocument
+pref("browser.enable_click_image_resizing", false);
+
+// controls which bits of private data to clear. by default we clear them all.
+pref("privacy.item.cache", true);
+pref("privacy.item.cookies", true);
+pref("privacy.item.offlineApps", true);
+pref("privacy.item.history", true);
+pref("privacy.item.formdata", true);
+pref("privacy.item.downloads", true);
+pref("privacy.item.passwords", true);
+pref("privacy.item.sessions", true);
+pref("privacy.item.geolocation", true);
+pref("privacy.item.siteSettings", true);
+pref("privacy.item.syncAccount", true);
+
+// base url for the wifi geolocation network provider
+pref("geo.provider.use_mls", false);
+pref("geo.wifi.uri", "https://location.services.mozilla.com/v1/geolocate?key=%MOZILLA_API_KEY%");
+
+// base url for the stumbler
+pref("geo.stumbler.url", "https://location.services.mozilla.com/v1/geosubmit?key=%MOZILLA_API_KEY%");
+
+// enable geo
+pref("geo.enabled", true);
+
+// content sink control -- controls responsiveness during page load
+// see https://bugzilla.mozilla.org/show_bug.cgi?id=481566#c9
+pref("content.sink.enable_perf_mode", 2); // 0 - switch, 1 - interactive, 2 - perf
+pref("content.sink.pending_event_mode", 0);
+pref("content.sink.perf_deflect_count", 1000000);
+pref("content.sink.perf_parse_time", 50000000);
+
+// Maximum scripts runtime before showing an alert
+// Disable the watchdog thread for B2G. See bug 870043 comment 31.
+pref("dom.use_watchdog", false);
+
+// The slow script dialog can be triggered from inside the JS engine as well,
+// ensure that those calls don't accidentally trigger the dialog.
+pref("dom.max_script_run_time", 0);
+pref("dom.max_chrome_script_run_time", 0);
+
+// plugins
+pref("plugin.disable", true);
+pref("dom.ipc.plugins.enabled", true);
+
+// product URLs
+// The breakpad report server to link to in about:crashes
+pref("breakpad.reportURL", "https://crash-stats.mozilla.com/report/index/");
+pref("app.releaseNotesURL", "https://www.mozilla.com/%LOCALE%/b2g/%VERSION%/releasenotes/");
+pref("app.support.baseURL", "https://support.mozilla.com/b2g");
+pref("app.privacyURL", "https://www.mozilla.com/%LOCALE%/m/privacy.html");
+pref("app.creditsURL", "https://www.mozilla.org/credits/");
+pref("app.featuresURL", "https://www.mozilla.com/%LOCALE%/b2g/features/");
+pref("app.faqURL", "https://www.mozilla.com/%LOCALE%/b2g/faq/");
+
+// Name of alternate about: page for certificate errors (when undefined, defaults to about:neterror)
+pref("security.alternate_certificate_error_page", "certerror");
+
+pref("security.warn_viewing_mixed", false); // Warning is disabled. See Bug 616712.
+
+// Block insecure active content on https pages
+pref("security.mixed_content.block_active_content", true);
+
+// 2 = strict certificate pinning checks.
+// This default preference is more strict than Firefox because B2G
+// currently does not have a way to install local root certificates.
+// Strict checking is effectively equivalent to non-strict checking as
+// long as that is true. If an ability to add local certificates is
+// added, there may be a need to change this pref.
+pref("security.cert_pinning.enforcement_level", 2);
+
+
+// Override some named colors to avoid inverse OS themes
+pref("ui.-moz-dialog", "#efebe7");
+pref("ui.-moz-dialogtext", "#101010");
+pref("ui.-moz-field", "#fff");
+pref("ui.-moz-fieldtext", "#1a1a1a");
+pref("ui.-moz-buttonhoverface", "#f3f0ed");
+pref("ui.-moz-buttonhovertext", "#101010");
+pref("ui.-moz-combobox", "#fff");
+pref("ui.-moz-comboboxtext", "#101010");
+pref("ui.buttonface", "#ece7e2");
+pref("ui.buttonhighlight", "#fff");
+pref("ui.buttonshadow", "#aea194");
+pref("ui.buttontext", "#101010");
+pref("ui.captiontext", "#101010");
+pref("ui.graytext", "#b1a598");
+pref("ui.highlighttext", "#1a1a1a");
+pref("ui.threeddarkshadow", "#000");
+pref("ui.threedface", "#ece7e2");
+pref("ui.threedhighlight", "#fff");
+pref("ui.threedlightshadow", "#ece7e2");
+pref("ui.threedshadow", "#aea194");
+pref("ui.windowframe", "#efebe7");
+
+// Themable via mozSettings
+pref("ui.menu", "#f97c17");
+pref("ui.menutext", "#ffffff");
+pref("ui.infobackground", "#343e40");
+pref("ui.infotext", "#686868");
+pref("ui.window", "#ffffff");
+pref("ui.windowtext", "#000000");
+pref("ui.highlight", "#b2f2ff");
+
+// replace newlines with spaces on paste into single-line text boxes
+pref("editor.singleLine.pasteNewlines", 2);
+
+// threshold where a tap becomes a drag, in 1/240" reference pixels
+// The names of the preferences are to be in sync with EventStateManager.cpp
+pref("ui.dragThresholdX", 25);
+pref("ui.dragThresholdY", 25);
+
+// Layers Acceleration. We can only have nice things on gonk, because
+// they're not maintained anywhere else.
+#ifndef MOZ_WIDGET_GONK
+pref("dom.ipc.tabs.disabled", true);
+pref("layers.async-pan-zoom.enabled", false);
+#else
+pref("dom.ipc.tabs.disabled", false);
+pref("layers.acceleration.disabled", false);
+pref("gfx.content.azure.backends", "cairo");
+#endif
+
+// Web Notifications
+pref("notification.feature.enabled", true);
+
+// prevent video elements from preloading too much data
+pref("media.preload.default", 1); // default to preload none
+pref("media.preload.auto", 2); // preload metadata if preload=auto
+pref("media.cache_size", 4096); // 4MB media cache
+// Try to save battery by not resuming reading from a connection until we fall
+// below 10s of buffered data.
+pref("media.cache_resume_threshold", 10);
+pref("media.cache_readahead_limit", 30);
+
+#ifdef MOZ_FMP4
+// Enable/Disable Gonk Decoder Module
+pref("media.gonk.enabled", true);
+#endif
+
+//Encrypted media extensions.
+pref("media.eme.enabled", true);
+pref("media.eme.apiVisible", true);
+// The default number of decoded video frames that are enqueued in
+// MediaDecoderReader's mVideoQueue.
+pref("media.video-queue.default-size", 3);
+
+// optimize images' memory usage
+pref("image.downscale-during-decode.enabled", true);
+pref("image.mem.allow_locking_in_content_processes", true);
+// Limit the surface cache to 1/8 of main memory or 128MB, whichever is smaller.
+// Almost everything that was factored into 'max_decoded_image_kb' is now stored
+// in the surface cache. 1/8 of main memory is 32MB on a 256MB device, which is
+// about the same as the old 'max_decoded_image_kb'.
+pref("image.mem.surfacecache.max_size_kb", 131072); // 128MB
+pref("image.mem.surfacecache.size_factor", 8); // 1/8 of main memory
+pref("image.mem.surfacecache.discard_factor", 2); // Discard 1/2 of the surface cache at a time.
+pref("image.mem.surfacecache.min_expiration_ms", 86400000); // 24h, we rely on the out of memory hook
+
+pref("dom.w3c_touch_events.safetyX", 0); // escape borders in units of 1/240"
+pref("dom.w3c_touch_events.safetyY", 120); // escape borders in units of 1/240"
+
+// True if this is the first time we are showing about:firstrun
+pref("browser.firstrun.show.uidiscovery", true);
+pref("browser.firstrun.show.localepicker", true);
+
+// initiated by a user
+pref("content.ime.strict_policy", true);
+
+// True if you always want dump() to work
+//
+// On Android, you also need to do the following for the output
+// to show up in logcat:
+//
+// $ adb shell stop
+// $ adb shell setprop log.redirect-stdio true
+// $ adb shell start
+pref("browser.dom.window.dump.enabled", false);
+
+// Default Content Security Policy to apply to certified apps.
+// If you change this CSP, make sure to update the fast path in nsCSPService.cpp
+pref("security.apps.certified.CSP.default", "default-src * data: blob:; script-src 'self'; object-src 'none'; style-src 'self' 'unsafe-inline' app://theme.gaiamobile.org");
+
+// handle links targeting new windows
+// 1=current window/tab, 2=new window, 3=new tab in most recent window
+pref("browser.link.open_newwindow", 3);
+
+// 0: no restrictions - divert everything
+// 1: don't divert window.open at all
+// 2: don't divert window.open with features
+pref("browser.link.open_newwindow.restriction", 0);
+
+// Enable browser frames (including OOP, except on Windows, where it doesn't
+// work), but make in-process browser frames the default.
+pref("dom.mozBrowserFramesEnabled", true);
+
+// Enable a (virtually) unlimited number of mozbrowser processes.
+// We'll run out of PIDs on UNIX-y systems before we hit this limit.
+pref("dom.ipc.processCount", 100000);
+
+pref("dom.ipc.browser_frames.oop_by_default", false);
+
+#if !defined(MOZ_MULET) && !defined(MOZ_GRAPHENE)
+pref("dom.meta-viewport.enabled", true);
+#endif
+
+//The waiting time in network manager.
+pref("network.gonk.ms-release-mms-connection", 30000);
+
+// Shortnumber matching needed for e.g. Brazil:
+// 03187654321 can be found with 87654321
+pref("dom.phonenumber.substringmatching.BR", 8);
+pref("dom.phonenumber.substringmatching.CO", 10);
+pref("dom.phonenumber.substringmatching.VE", 7);
+pref("dom.phonenumber.substringmatching.CL", 8);
+pref("dom.phonenumber.substringmatching.PE", 7);
+
+// NetworkStats
+#ifdef MOZ_WIDGET_GONK
+pref("dom.mozNetworkStats.enabled", true);
+pref("dom.webapps.firstRunWithSIM", true);
+#endif
+
+#ifdef MOZ_B2G_RIL
+// SingleVariant
+pref("dom.mozApps.single_variant_sourcedir", "/persist/svoperapps");
+#endif
+
+// WebSettings
+pref("dom.mozSettings.enabled", true);
+pref("dom.mozPermissionSettings.enabled", true);
+
+// controls if we want camera support
+pref("device.camera.enabled", true);
+pref("media.realtime_decoder.enabled", true);
+
+// TCPSocket
+pref("dom.mozTCPSocket.enabled", true);
+
+// "Preview" landing of bug 710563, which is bogged down in analysis
+// of talos regression. This is a needed change for higher-framerate
+// CSS animations, and incidentally works around an apparent bug in
+// our handling of requestAnimationFrame() listeners, which are
+// supposed to enable this REPEATING_PRECISE_CAN_SKIP behavior. The
+// secondary bug isn't really worth investigating since it's obseleted
+// by bug 710563.
+pref("layout.frame_rate.precise", true);
+
+// Handle hardware buttons in the b2g chrome package
+pref("b2g.keys.menu.enabled", true);
+
+// Display simulator software buttons
+pref("b2g.software-buttons", false);
+
+// Screen timeout in seconds
+pref("power.screen.timeout", 60);
+
+pref("full-screen-api.enabled", true);
+
+#ifndef MOZ_WIDGET_GONK
+// If we're not actually on physical hardware, don't make the top level widget
+// fullscreen when transitioning to fullscreen. This means in emulated
+// environments (like the b2g desktop client) we won't make the client window
+// fill the whole screen, we'll just make the content fill the client window,
+// i.e. it won't give the impression to content that the number of device
+// screen pixels changes!
+pref("full-screen-api.ignore-widgets", true);
+#endif
+
+pref("media.volume.steps", 10);
+
+#ifdef ENABLE_MARIONETTE
+//Enable/disable marionette server, set listening port
+pref("marionette.defaultPrefs.enabled", true);
+pref("marionette.defaultPrefs.port", 2828);
+#ifndef MOZ_WIDGET_GONK
+// On desktop builds, we need to force the socket to listen on localhost only
+pref("marionette.force-local", true);
+#endif
+#endif
+
+#ifdef MOZ_UPDATER
+// When we're applying updates, we can't let anything hang us on
+// quit+restart. The user has no recourse.
+pref("shutdown.watchdog.timeoutSecs", 10);
+// Timeout before the update prompt automatically installs the update
+pref("b2g.update.apply-prompt-timeout", 60000); // milliseconds
+// Amount of time to wait after the user is idle before prompting to apply an update
+pref("b2g.update.apply-idle-timeout", 600000); // milliseconds
+// Amount of time after which connection will be restarted if no progress
+pref("b2g.update.download-watchdog-timeout", 120000); // milliseconds
+pref("b2g.update.download-watchdog-max-retries", 5);
+
+pref("app.update.enabled", true);
+pref("app.update.auto", false);
+pref("app.update.silent", false);
+pref("app.update.staging.enabled", true);
+pref("app.update.service.enabled", true);
+
+pref("app.update.url", "https://aus5.mozilla.org/update/5/%PRODUCT%/%VERSION%/%BUILD_ID%/%PRODUCT_DEVICE%/%LOCALE%/%CHANNEL%/%OS_VERSION%/%DISTRIBUTION%/%DISTRIBUTION_VERSION%/%IMEI%/update.xml");
+pref("app.update.channel", "@MOZ_UPDATE_CHANNEL@");
+
+// Interval at which update manifest is fetched. In units of seconds.
+pref("app.update.interval", 86400); // 1 day
+// Don't throttle background updates.
+pref("app.update.download.backgroundInterval", 0);
+
+// Retry update socket connections every 30 seconds in the cases of certain kinds of errors
+pref("app.update.socket.retryTimeout", 30000);
+
+// Max of 20 consecutive retries (total 10 minutes) before giving up and marking
+// the update download as failed.
+// Note: Offline errors will always retry when the network comes online.
+pref("app.update.socket.maxErrors", 20);
+
+// Enable update logging for now, to diagnose growing pains in the
+// field.
+pref("app.update.log", true);
+
+// SystemUpdate API
+pref("dom.system_update.active", "@mozilla.org/updates/update-prompt;1");
+#else
+// Explicitly disable the shutdown watchdog. It's enabled by default.
+// When the updater is disabled, we want to know about shutdown hangs.
+pref("shutdown.watchdog.timeoutSecs", -1);
+#endif
+
+// Allow webapps update checking
+pref("webapps.update.enabled", true);
+
+// Check daily for apps updates.
+pref("webapps.update.interval", 86400);
+
+// Extensions preferences
+pref("extensions.update.enabled", false);
+pref("extensions.getAddons.cache.enabled", false);
+
+// Context Menu
+pref("ui.click_hold_context_menus", true);
+pref("ui.click_hold_context_menus.delay", 400);
+
+// Enable device storage
+pref("device.storage.enabled", true);
+
+// Enable pre-installed applications
+pref("dom.webapps.useCurrentProfile", true);
+
+// Enable system message
+pref("dom.sysmsg.enabled", true);
+pref("media.plugins.enabled", false);
+pref("media.rtsp.enabled", true);
+pref("media.rtsp.video.enabled", true);
+
+// Disable printing (particularly, window.print())
+pref("dom.disable_window_print", true);
+
+// Disable window.showModalDialog
+pref("dom.disable_window_showModalDialog", true);
+
+// Enable new experimental html forms
+pref("dom.experimental_forms", true);
+pref("dom.forms.number", true);
+
+// Don't enable <input type=color> yet as we don't have a color picker
+// implemented for b2g (bug 875751)
+pref("dom.forms.color", false);
+
+// This preference instructs the JS engine to discard the
+// source of any privileged JS after compilation. This saves
+// memory, but makes things like Function.prototype.toSource()
+// fail.
+pref("javascript.options.discardSystemSource", true);
+
+// XXXX REMOVE FOR PRODUCTION. Turns on GC and CC logging
+pref("javascript.options.mem.log", false);
+
+// Increase mark slice time from 10ms to 30ms
+pref("javascript.options.mem.gc_incremental_slice_ms", 30);
+
+// Increase time to get more high frequency GC on benchmarks from 1000ms to 1500ms
+pref("javascript.options.mem.gc_high_frequency_time_limit_ms", 1500);
+
+pref("javascript.options.mem.gc_high_frequency_heap_growth_max", 300);
+pref("javascript.options.mem.gc_high_frequency_heap_growth_min", 120);
+pref("javascript.options.mem.gc_high_frequency_high_limit_mb", 40);
+pref("javascript.options.mem.gc_high_frequency_low_limit_mb", 0);
+pref("javascript.options.mem.gc_low_frequency_heap_growth", 120);
+pref("javascript.options.mem.high_water_mark", 6);
+pref("javascript.options.mem.gc_allocation_threshold_mb", 1);
+pref("javascript.options.mem.gc_min_empty_chunk_count", 1);
+pref("javascript.options.mem.gc_max_empty_chunk_count", 2);
+
+// Show/Hide scrollbars when active/inactive
+pref("ui.showHideScrollbars", 1);
+pref("ui.useOverlayScrollbars", 1);
+pref("ui.scrollbarFadeBeginDelay", 450);
+pref("ui.scrollbarFadeDuration", 0);
+
+// Scrollbar position follows the document `dir` attribute
+pref("layout.scrollbar.side", 1);
+
+// CSS Scroll Snapping
+pref("layout.css.scroll-snap.enabled", true);
+
+// Enable the ProcessPriorityManager, and give processes with no visible
+// documents a 1s grace period before they're eligible to be marked as
+// background. Background processes that are perceivable due to playing
+// media are given a longer grace period to accomodate changing tracks, etc.
+pref("dom.ipc.processPriorityManager.enabled", true);
+pref("dom.ipc.processPriorityManager.backgroundGracePeriodMS", 1000);
+pref("dom.ipc.processPriorityManager.backgroundPerceivableGracePeriodMS", 5000);
+pref("dom.ipc.processPriorityManager.temporaryPriorityLockMS", 5000);
+
+// Number of different background/foreground levels for background/foreground
+// processes. We use these different levels to force the low-memory killer to
+// kill processes in a LRU order.
+pref("dom.ipc.processPriorityManager.BACKGROUND.LRUPoolLevels", 5);
+pref("dom.ipc.processPriorityManager.BACKGROUND_PERCEIVABLE.LRUPoolLevels", 4);
+
+// Kernel parameters for process priorities. These affect how processes are
+// killed on low-memory and their relative CPU priorities.
+//
+// The kernel can only accept 6 (OomScoreAdjust, KillUnderKB) pairs. But it is
+// okay, kernel will still kill processes with larger OomScoreAdjust first even
+// its OomScoreAdjust don't have a corresponding KillUnderKB.
+
+pref("hal.processPriorityManager.gonk.MASTER.OomScoreAdjust", 0);
+pref("hal.processPriorityManager.gonk.MASTER.KillUnderKB", 4096);
+pref("hal.processPriorityManager.gonk.MASTER.cgroup", "");
+
+pref("hal.processPriorityManager.gonk.PREALLOC.OomScoreAdjust", 67);
+pref("hal.processPriorityManager.gonk.PREALLOC.cgroup", "apps/bg_non_interactive");
+
+pref("hal.processPriorityManager.gonk.FOREGROUND_HIGH.OomScoreAdjust", 67);
+pref("hal.processPriorityManager.gonk.FOREGROUND_HIGH.KillUnderKB", 5120);
+pref("hal.processPriorityManager.gonk.FOREGROUND_HIGH.cgroup", "apps/critical");
+
+pref("hal.processPriorityManager.gonk.FOREGROUND.OomScoreAdjust", 134);
+pref("hal.processPriorityManager.gonk.FOREGROUND.KillUnderKB", 6144);
+pref("hal.processPriorityManager.gonk.FOREGROUND.cgroup", "apps");
+
+pref("hal.processPriorityManager.gonk.FOREGROUND_KEYBOARD.OomScoreAdjust", 200);
+pref("hal.processPriorityManager.gonk.FOREGROUND_KEYBOARD.cgroup", "apps");
+
+pref("hal.processPriorityManager.gonk.BACKGROUND_PERCEIVABLE.OomScoreAdjust", 400);
+pref("hal.processPriorityManager.gonk.BACKGROUND_PERCEIVABLE.KillUnderKB", 8192);
+pref("hal.processPriorityManager.gonk.BACKGROUND_PERCEIVABLE.cgroup", "apps/bg_perceivable");
+
+pref("hal.processPriorityManager.gonk.BACKGROUND.OomScoreAdjust", 667);
+pref("hal.processPriorityManager.gonk.BACKGROUND.KillUnderKB", 20480);
+pref("hal.processPriorityManager.gonk.BACKGROUND.cgroup", "apps/bg_non_interactive");
+
+// Control group definitions (i.e., CPU priority groups) for B2G processes.
+//
+// memory_swappiness - 0 - The kernel will swap only to avoid an out of memory condition
+// memory_swappiness - 60 - The default value.
+// memory_swappiness - 100 - The kernel will swap aggressively.
+
+// Foreground apps
+pref("hal.processPriorityManager.gonk.cgroups.apps.cpu_shares", 1024);
+pref("hal.processPriorityManager.gonk.cgroups.apps.cpu_notify_on_migrate", 0);
+pref("hal.processPriorityManager.gonk.cgroups.apps.memory_swappiness", 10);
+
+// Foreground apps with high priority, 16x more CPU than foreground ones
+pref("hal.processPriorityManager.gonk.cgroups.apps/critical.cpu_shares", 16384);
+pref("hal.processPriorityManager.gonk.cgroups.apps/critical.cpu_notify_on_migrate", 0);
+pref("hal.processPriorityManager.gonk.cgroups.apps/critical.memory_swappiness", 0);
+
+// Background perceivable apps, ~10x less CPU than foreground ones
+pref("hal.processPriorityManager.gonk.cgroups.apps/bg_perceivable.cpu_shares", 103);
+pref("hal.processPriorityManager.gonk.cgroups.apps/bg_perceivable.cpu_notify_on_migrate", 0);
+pref("hal.processPriorityManager.gonk.cgroups.apps/bg_perceivable.memory_swappiness", 60);
+
+// Background apps, ~20x less CPU than foreground ones and ~2x less than perceivable ones
+pref("hal.processPriorityManager.gonk.cgroups.apps/bg_non_interactive.cpu_shares", 52);
+pref("hal.processPriorityManager.gonk.cgroups.apps/bg_non_interactive.cpu_notify_on_migrate", 0);
+pref("hal.processPriorityManager.gonk.cgroups.apps/bg_non_interactive.memory_swappiness", 100);
+
+// By default the compositor thread on gonk runs without real-time priority. RT
+// priority can be enabled by setting this pref to a value between 1 and 99.
+// Note that audio processing currently runs at RT priority 2 or 3 at most.
+//
+// If RT priority is disabled, then the compositor nice value is used. We prefer
+// to use a nice value of -4, which matches Android's preferences. Setting a preference
+// of RT priority 1 would mean it is higher than audio, which is -16. The compositor
+// priority must be below the audio thread.
+//
+// Do not change these values without gfx team review.
+pref("hal.gonk.COMPOSITOR.rt_priority", 0);
+pref("hal.gonk.COMPOSITOR.nice", -4);
+
+// Fire a memory pressure event when the system has less than Xmb of memory
+// remaining. You should probably set this just above Y.KillUnderKB for
+// the highest priority class Y that you want to make an effort to keep alive.
+// (For example, we want BACKGROUND_PERCEIVABLE to stay alive.) If you set
+// this too high, then we'll send out a memory pressure event every Z seconds
+// (see below), even while we have processes that we would happily kill in
+// order to free up memory.
+pref("gonk.notifyHardLowMemUnderKB", 14336);
+
+// Fire a memory pressure event when the system has less than Xmb of memory
+// remaining and then switch to the hard trigger, see above. This should be
+// placed above the BACKGROUND priority class.
+pref("gonk.notifySoftLowMemUnderKB", 43008);
+
+// We wait this long before polling the memory-pressure fd after seeing one
+// memory pressure event. (When we're not under memory pressure, we sit
+// blocked on a poll(), and this pref has no effect.)
+pref("gonk.systemMemoryPressureRecoveryPollMS", 5000);
+
+// Enable pre-launching content processes for improved startup time
+// (hiding latency).
+pref("dom.ipc.processPrelaunch.enabled", true);
+// Wait this long before pre-launching a new subprocess.
+pref("dom.ipc.processPrelaunch.delayMs", 5000);
+
+pref("dom.ipc.reuse_parent_app", false);
+
+// When a process receives a system message, we hold a CPU wake lock on its
+// behalf for this many seconds, or until it handles the system message,
+// whichever comes first.
+pref("dom.ipc.systemMessageCPULockTimeoutSec", 30);
+
+// Ignore the "dialog=1" feature in window.open.
+pref("dom.disable_window_open_dialog_feature", true);
+
+// Enable before keyboard events and after keyboard events.
+pref("dom.beforeAfterKeyboardEvent.enabled", true);
+
+// Screen reader support
+pref("accessibility.accessfu.activate", 2);
+pref("accessibility.accessfu.quicknav_modes", "Link,Heading,FormElement,Landmark,ListItem");
+// Active quicknav mode, index value of list from quicknav_modes
+pref("accessibility.accessfu.quicknav_index", 0);
+// Setting for an utterance order (0 - description first, 1 - description last).
+pref("accessibility.accessfu.utterance", 1);
+// Whether to skip images with empty alt text
+pref("accessibility.accessfu.skip_empty_images", true);
+// Setting to change the verbosity of entered text (0 - none, 1 - characters,
+// 2 - words, 3 - both)
+pref("accessibility.accessfu.keyboard_echo", 3);
+
+// Enable hit-target fluffing
+pref("ui.touch.radius.enabled", true);
+pref("ui.touch.radius.leftmm", 3);
+pref("ui.touch.radius.topmm", 5);
+pref("ui.touch.radius.rightmm", 3);
+pref("ui.touch.radius.bottommm", 2);
+
+pref("ui.mouse.radius.enabled", true);
+pref("ui.mouse.radius.leftmm", 3);
+pref("ui.mouse.radius.topmm", 5);
+pref("ui.mouse.radius.rightmm", 3);
+pref("ui.mouse.radius.bottommm", 2);
+
+// Disable native prompt
+pref("browser.prompt.allowNative", false);
+
+// Minimum delay in milliseconds between network activity notifications (0 means
+// no notifications). The delay is the same for both download and upload, though
+// they are handled separately. This pref is only read once at startup:
+// a restart is required to enable a new value.
+pref("network.activity.blipIntervalMilliseconds", 250);
+
+// By default we want the NetworkManager service to manage Gecko's offline
+// status for us according to the state of Wifi/cellular data connections.
+// In some environments, such as the emulator or hardware with other network
+// connectivity, this is not desireable, however, in which case this pref
+// can be flipped to false.
+pref("network.gonk.manage-offline-status", true);
+
+// On Firefox Mulet, we can't enable shared JSM scope
+// as it breaks most Firefox JSMs (see bug 961777)
+#ifndef MOZ_MULET
+// Break any JSMs or JS components that rely on shared scope
+#ifndef DEBUG
+pref("jsloader.reuseGlobal", true);
+#endif
+#endif
+
+// Enable font inflation for browser tab content.
+pref("font.size.inflation.minTwips", 120);
+// And disable it for lingering master-process UI.
+pref("font.size.inflation.disabledInMasterProcess", true);
+
+// Enable freeing dirty pages when minimizing memory; this reduces memory
+// consumption when applications are sent to the background.
+pref("memory.free_dirty_pages", true);
+
+// Enable the Linux-specific, system-wide memory reporter.
+pref("memory.system_memory_reporter", true);
+
+// Don't dump memory reports on OOM, by default.
+pref("memory.dump_reports_on_oom", false);
+
+pref("layout.framevisibility.numscrollportwidths", 1);
+pref("layout.framevisibility.numscrollportheights", 1);
+
+// Wait up to this much milliseconds when orientation changed
+pref("layers.orientation.sync.timeout", 1000);
+
+// Animate the orientation change
+pref("b2g.orientation.animate", true);
+
+// Don't discard WebGL contexts for foreground apps on memory
+// pressure.
+pref("webgl.can-lose-context-in-foreground", false);
+
+// Allow nsMemoryInfoDumper to create a fifo in the temp directory. We use
+// this fifo to trigger about:memory dumps, among other things.
+pref("memory_info_dumper.watch_fifo.enabled", true);
+pref("memory_info_dumper.watch_fifo.directory", "/data/local");
+
+// See ua-update.json.in for the packaged UA override list
+pref("general.useragent.updates.enabled", true);
+pref("general.useragent.updates.url", "https://dynamicua.cdn.mozilla.net/0/%APP_ID%");
+pref("general.useragent.updates.interval", 604800); // 1 week
+pref("general.useragent.updates.retry", 86400); // 1 day
+// Device ID can be composed of letter, numbers, hyphen ("-") and dot (".")
+pref("general.useragent.device_id", "");
+
+// Add Mozilla AudioChannel APIs.
+pref("media.useAudioChannelAPI", true);
+
+pref("b2g.version", @MOZ_B2G_VERSION@);
+pref("b2g.osName", @MOZ_B2G_OS_NAME@);
+
+// Disable console buffering to save memory.
+pref("consoleservice.buffered", false);
+
+#ifdef MOZ_WIDGET_GONK
+// Performance testing suggests 2k is a better page size for SQLite.
+pref("toolkit.storage.pageSize", 2048);
+#endif
+
+// The url of the manifest we use for ADU pings.
+pref("ping.manifestURL", "https://marketplace.firefox.com/packaged.webapp");
+
+// Enable the disk space watcher
+pref("disk_space_watcher.enabled", true);
+
+// SNTP preferences.
+pref("network.sntp.maxRetryCount", 10);
+pref("network.sntp.refreshPeriod", 86400); // In seconds.
+pref("network.sntp.pools", // Servers separated by ';'.
+ "0.pool.ntp.org;1.pool.ntp.org;2.pool.ntp.org;3.pool.ntp.org");
+pref("network.sntp.port", 123);
+pref("network.sntp.timeout", 30); // In seconds.
+
+// Allow ADB to run for this many hours before disabling
+// (only applies when marionette is disabled)
+// 0 disables the timer.
+pref("b2g.adb.timeout-hours", 12);
+
+// InputMethod so we can do soft keyboards
+pref("dom.mozInputMethod.enabled", true);
+
+// Absolute path to the devtool unix domain socket file used
+// to communicate with a usb cable via adb forward
+pref("devtools.debugger.unix-domain-socket", "/data/local/debugger-socket");
+
+// enable Skia/GL (OpenGL-accelerated 2D drawing) for large enough 2d canvases,
+// falling back to Skia/software for smaller canvases
+#ifdef MOZ_WIDGET_GONK
+pref("gfx.canvas.azure.backends", "skia");
+pref("gfx.canvas.azure.accelerated", true);
+#endif
+
+// Turn on dynamic cache size for Skia
+pref("gfx.canvas.skiagl.dynamic-cache", true);
+
+// Limit skia to canvases the size of the device screen or smaller
+pref("gfx.canvas.max-size-for-skia-gl", -1);
+
+// enable fence with readpixels for SurfaceStream
+pref("gfx.gralloc.fence-with-readpixels", true);
+
+// enable screen mirroring to external display
+pref("gfx.screen-mirroring.enabled", true);
+
+// The url of the page used to display network error details.
+pref("b2g.neterror.url", "net_error.html");
+
+// Enable Web Speech synthesis API
+pref("media.webspeech.synth.enabled", true);
+
+// Enable Web Speech recognition API
+pref("media.webspeech.recognition.enable", true);
+
+// Downloads API
+pref("dom.mozDownloads.enabled", true);
+pref("dom.downloads.max_retention_days", 7);
+
+// External Helper Application Handling
+//
+// All external helper application handling can require the docshell to be
+// active before allowing the external helper app service to handle content.
+//
+// To prevent SD card DoS attacks via downloads we disable background handling.
+//
+pref("security.exthelperapp.disable_background_handling", true);
+
+// Inactivity time in milliseconds after which we shut down the OS.File worker.
+pref("osfile.reset_worker_delay", 5000);
+
+// APZ physics settings, tuned by UX designers
+pref("apz.axis_lock.mode", 2); // Use "sticky" axis locking
+pref("apz.fling_curve_function_x1", "0.41");
+pref("apz.fling_curve_function_y1", "0.0");
+pref("apz.fling_curve_function_x2", "0.80");
+pref("apz.fling_curve_function_y2", "1.0");
+pref("apz.fling_curve_threshold_inches_per_ms", "0.01");
+pref("apz.fling_friction", "0.0019");
+pref("apz.max_velocity_inches_per_ms", "0.07");
+pref("apz.overscroll.enabled", true);
+pref("apz.displayport_expiry_ms", 0); // causes issues on B2G, see bug 1250924
+
+// For event-regions based hit-testing
+pref("layout.event-regions.enabled", true);
+
+// This preference allows FirefoxOS apps (and content, I think) to force
+// the use of software (instead of hardware accelerated) 2D canvases by
+// creating a context like this:
+//
+// canvas.getContext('2d', { willReadFrequently: true })
+//
+// Using a software canvas can save memory when JS calls getImageData()
+// on the canvas frequently. See bug 884226.
+pref("gfx.canvas.willReadFrequently.enable", true);
+
+// Disable autofocus until we can have it not bring up the keyboard.
+// https://bugzilla.mozilla.org/show_bug.cgi?id=965763
+pref("browser.autofocus", false);
+
+// Enable wakelock
+pref("dom.wakelock.enabled", true);
+
+// Enable webapps add-ons
+pref("dom.apps.reviewer_paths", "/reviewers/,/extension/reviewers/");
+
+// New implementation to unify touch-caret and selection-carets.
+pref("layout.accessiblecaret.enabled", true);
+
+// Show the selection bars at the two ends of the selection highlight. Required
+// by the spec in bug 921965.
+pref("layout.accessiblecaret.bar.enabled", true);
+
+// Hide the caret in cursor mode after 3 seconds.
+pref("layout.accessiblecaret.timeout_ms", 3000);
+
+// Hide carets and text selection dialog during scrolling.
+pref("layout.accessiblecaret.always_show_when_scrolling", false);
+
+// Enable sync with Firefox Accounts.
+pref("services.sync.fxaccounts.enabled", true);
+pref("identity.fxaccounts.enabled", true);
+
+pref("identity.fxaccounts.remote.oauth.uri", "https://oauth.accounts.firefox.com/v1");
+pref("identity.fxaccounts.remote.profile.uri", "https://profile.accounts.firefox.com/v1");
+
+// Disable Firefox Accounts device registration until bug 1238895 is fixed.
+pref("identity.fxaccounts.skipDeviceRegistration", true);
+
+// Enable mapped array buffer.
+pref("dom.mapped_arraybuffer.enabled", true);
+
+// SystemUpdate API
+pref("dom.system_update.enabled", true);
+
+// UDPSocket API
+pref("dom.udpsocket.enabled", true);
+
+// Enable TV Manager API
+pref("dom.tv.enabled", true);
+
+// Enable Inputport Manager API
+pref("dom.inputport.enabled", true);
+
+pref("dom.mozSettings.SettingsDB.debug.enabled", true);
+pref("dom.mozSettings.SettingsManager.debug.enabled", true);
+pref("dom.mozSettings.SettingsRequestManager.debug.enabled", true);
+pref("dom.mozSettings.SettingsService.debug.enabled", true);
+
+pref("dom.mozSettings.SettingsDB.verbose.enabled", false);
+pref("dom.mozSettings.SettingsManager.verbose.enabled", false);
+pref("dom.mozSettings.SettingsRequestManager.verbose.enabled", false);
+pref("dom.mozSettings.SettingsService.verbose.enabled", false);
+
+// Controlling whether we want to allow forcing some Settings
+// IndexedDB transactions to be opened as readonly or keep everything as
+// readwrite.
+pref("dom.mozSettings.allowForceReadOnly", false);
+
+// Comma separated list of activity names that can only be provided by
+// the system app in dev mode.
+pref("dom.activities.developer_mode_only", "import-app");
+
+// mulet apparently loads firefox.js as well as b2g.js, so we have to explicitly
+// disable serviceworkers and push here to get them disabled in mulet.
+pref("dom.serviceWorkers.enabled", false);
+pref("dom.push.enabled", false);
+
+#if defined(RELEASE_OR_BETA)
+// Bug 1278848: Enable service worker notifications on release B2G once
+// they're ready.
+pref("dom.webnotifications.serviceworker.enabled", false);
+#else
+pref("dom.webnotifications.serviceworker.enabled", true);
+#endif
+
+// Retain at most 10 processes' layers buffers
+pref("layers.compositor-lru-size", 10);
+
+// In B2G by deafult any AudioChannelAgent is muted when created.
+pref("dom.audiochannel.mutedByDefault", true);
+
+// Default device name for Presentation API
+pref("dom.presentation.device.name", "Firefox OS");
+
+// Enable notification of performance timing
+pref("dom.performance.enable_notify_performance_timing", true);
+
+// Multi-screen
+pref("b2g.multiscreen.chrome_remote_url", "chrome://b2g/content/shell_remote.html");
+pref("b2g.multiscreen.system_remote_url", "index_remote.html");
+
+// Audio competing between tabs
+pref("dom.audiochannel.audioCompeting", false);
+
+// Because we can't have nice things.
+#ifdef MOZ_GRAPHENE
+#include ../graphene/graphene.js
+#endif
diff --git a/b2g/app/macbuild/Contents/Info.plist.in b/b2g/app/macbuild/Contents/Info.plist.in
new file mode 100644
index 0000000000..d5ea85f4d9
--- /dev/null
+++ b/b2g/app/macbuild/Contents/Info.plist.in
@@ -0,0 +1,67 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>CFBundleDevelopmentRegion</key>
+ <string>English</string>
+ <key>CFBundleExecutable</key>
+ <string>%APP_BINARY%</string>
+ <key>CFBundleGetInfoString</key>
+ <string>%APP_NAME% %APP_VERSION%</string>
+ <key>CFBundleIconFile</key>
+ <string>%MOZ_APP_NAME%.icns</string>
+ <key>CFBundleIdentifier</key>
+ <string>org.mozilla.b2g</string>
+ <key>CFBundleInfoDictionaryVersion</key>
+ <string>%MOZ_APP_VERSION%</string>
+ <key>CFBundleName</key>
+ <string>%APP_NAME%</string>
+ <key>CFBundlePackageType</key>
+ <string>APPL</string>
+ <key>CFBundleShortVersionString</key>
+ <string>%APP_VERSION%</string>
+ <key>CFBundleSignature</key>
+ <string>MOZB</string>
+ <key>CFBundleVersion</key>
+ <string>%APP_VERSION%</string>
+ <key>NSAppleScriptEnabled</key>
+ <true/>
+ <key>CGDisableCoalescedUpdates</key>
+ <true/>
+ <key>NSHighResolutionCapable</key>
+ <true/>
+ <key>NSPrincipalClass</key>
+ <string>GeckoNSApplication</string>
+ <key>CFBundleURLTypes</key>
+ <array>
+ <dict>
+ <key>CFBundleURLIconFile</key>
+ <string>document.icns</string>
+ <key>CFBundleURLName</key>
+ <string>http URL</string>
+ <key>CFBundleURLSchemes</key>
+ <array>
+ <string>http</string>
+ </array>
+ </dict>
+ <dict>
+ <key>CFBundleURLIconFile</key>
+ <string>document.icns</string>
+ <key>CFBundleURLName</key>
+ <string>https URL</string>
+ <key>CFBundleURLSchemes</key>
+ <array>
+ <string>https</string>
+ </array>
+ </dict>
+ <dict>
+ <key>CFBundleURLName</key>
+ <string>ftp URL</string>
+ <key>CFBundleURLSchemes</key>
+ <array>
+ <string>ftp</string>
+ </array>
+ </dict>
+ </array>
+</dict>
+</plist>
diff --git a/b2g/app/macbuild/Contents/MacOS-files.in b/b2g/app/macbuild/Contents/MacOS-files.in
new file mode 100644
index 0000000000..4cefd2ff96
--- /dev/null
+++ b/b2g/app/macbuild/Contents/MacOS-files.in
@@ -0,0 +1,9 @@
+/*.app/***
+/*.dylib
+/b2g
+/certutil
+/gtest/***
+/pk12util
+/ssltunnel
+/xpcshell
+/XUL
diff --git a/b2g/app/macbuild/Contents/Resources/English.lproj/InfoPlist.strings.in b/b2g/app/macbuild/Contents/Resources/English.lproj/InfoPlist.strings.in
new file mode 100644
index 0000000000..ddd3e0afa5
--- /dev/null
+++ b/b2g/app/macbuild/Contents/Resources/English.lproj/InfoPlist.strings.in
@@ -0,0 +1,5 @@
+/* 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/. */
+
+CFBundleName = "%APP_NAME%";
diff --git a/b2g/app/moz.build b/b2g/app/moz.build
new file mode 100644
index 0000000000..b28889065f
--- /dev/null
+++ b/b2g/app/moz.build
@@ -0,0 +1,76 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+if CONFIG['GAIADIR']:
+ GeckoProgram(CONFIG['MOZ_APP_NAME'] + "-bin")
+else:
+ GeckoProgram(CONFIG['MOZ_APP_NAME'])
+
+SOURCES += [
+ 'nsBrowserApp.cpp',
+]
+if CONFIG['_MSC_VER']:
+ # Always enter a Windows program through wmain, whether or not we're
+ # a console application.
+ WIN32_EXE_LDFLAGS += ['-ENTRY:wmainCRTStartup']
+
+USE_LIBS += [
+ 'zlib',
+]
+
+for var in ('MOZ_APP_NAME', 'MOZ_APP_VERSION', 'MOZ_UPDATER'):
+ DEFINES[var] = CONFIG[var]
+
+LOCAL_INCLUDES += [
+ '!/build',
+ '/toolkit/xre',
+ '/xpcom/base',
+ '/xpcom/build',
+]
+
+if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'gonk':
+ LOCAL_INCLUDES += [
+ '/widget/gonk/libdisplay',
+ ]
+
+ LDFLAGS += ['-Wl,--export-dynamic']
+
+ USE_LIBS += [
+ 'display',
+ 'mozpng',
+ ]
+ OS_LIBS += [
+ 'ui',
+ 'EGL',
+ 'hardware_legacy',
+ 'hardware',
+ 'cutils',
+ ]
+ OS_LIBS += CONFIG['MOZ_ZLIB_LIBS']
+ if CONFIG['ANDROID_VERSION'] in ('17', '18', '19', '21', '22'):
+ OS_LIBS += [
+ 'gui',
+ 'suspend',
+ ]
+ OS_LIBS += [
+ 'binder',
+ 'utils',
+ ]
+
+DISABLE_STL_WRAPPING = True
+
+if CONFIG['OS_ARCH'] == 'WINNT':
+ OS_LIBS += [
+ 'version',
+ ]
+
+JS_PREFERENCE_PP_FILES += [
+ 'b2g.js',
+]
+
+FINAL_TARGET_PP_FILES += [
+ 'ua-update.json.in',
+]
diff --git a/b2g/app/nsBrowserApp.cpp b/b2g/app/nsBrowserApp.cpp
new file mode 100644
index 0000000000..967b54b764
--- /dev/null
+++ b/b2g/app/nsBrowserApp.cpp
@@ -0,0 +1,239 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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 "nsXULAppAPI.h"
+#include "application.ini.h"
+#include "nsXPCOMGlue.h"
+#if defined(XP_WIN)
+#include <windows.h>
+#include <stdlib.h>
+#elif defined(XP_UNIX)
+#include <sys/time.h>
+#include <sys/resource.h>
+#include <unistd.h>
+#endif
+
+#include <stdio.h>
+#include <stdarg.h>
+#include <string.h>
+
+#include "nsCOMPtr.h"
+#include "nsIFile.h"
+#include "nsStringGlue.h"
+
+#ifdef XP_WIN
+// we want a wmain entry point
+#include "nsWindowsWMain.cpp"
+#define strcasecmp _stricmp
+#endif
+
+#ifdef MOZ_WIDGET_GONK
+#include "BootAnimation.h"
+#endif
+
+#include "BinaryPath.h"
+
+#include "nsXPCOMPrivate.h" // for MAXPATHLEN and XPCOM_DLL
+
+#ifdef MOZ_WIDGET_GONK
+# include <binder/ProcessState.h>
+#endif
+
+#include "mozilla/Sprintf.h"
+#include "mozilla/Telemetry.h"
+#include "mozilla/WindowsDllBlocklist.h"
+
+static void Output(const char *fmt, ... )
+{
+ va_list ap;
+ va_start(ap, fmt);
+
+#if defined(XP_WIN) && !MOZ_WINCONSOLE
+ wchar_t msg[2048];
+ _vsnwprintf(msg, sizeof(msg)/sizeof(msg[0]), NS_ConvertUTF8toUTF16(fmt).get(), ap);
+ MessageBoxW(nullptr, msg, L"XULRunner", MB_OK | MB_ICONERROR);
+#else
+ vfprintf(stderr, fmt, ap);
+#endif
+
+ va_end(ap);
+}
+
+/**
+ * Return true if |arg| matches the given argument name.
+ */
+static bool IsArg(const char* arg, const char* s)
+{
+ if (*arg == '-')
+ {
+ if (*++arg == '-')
+ ++arg;
+ return !strcasecmp(arg, s);
+ }
+
+#if defined(XP_WIN)
+ if (*arg == '/')
+ return !strcasecmp(++arg, s);
+#endif
+
+ return false;
+}
+
+XRE_GetFileFromPathType XRE_GetFileFromPath;
+XRE_CreateAppDataType XRE_CreateAppData;
+XRE_FreeAppDataType XRE_FreeAppData;
+XRE_TelemetryAccumulateType XRE_TelemetryAccumulate;
+XRE_mainType XRE_main;
+
+static const nsDynamicFunctionLoad kXULFuncs[] = {
+ { "XRE_GetFileFromPath", (NSFuncPtr*) &XRE_GetFileFromPath },
+ { "XRE_CreateAppData", (NSFuncPtr*) &XRE_CreateAppData },
+ { "XRE_FreeAppData", (NSFuncPtr*) &XRE_FreeAppData },
+ { "XRE_TelemetryAccumulate", (NSFuncPtr*) &XRE_TelemetryAccumulate },
+ { "XRE_main", (NSFuncPtr*) &XRE_main },
+ { nullptr, nullptr }
+};
+
+static int do_main(int argc, char* argv[])
+{
+ nsCOMPtr<nsIFile> appini;
+ nsresult rv;
+
+ // Allow firefox.exe to launch XULRunner apps via -app <application.ini>
+ // Note that -app must be the *first* argument.
+ const char *appDataFile = getenv("XUL_APP_FILE");
+ if (appDataFile && *appDataFile) {
+ rv = XRE_GetFileFromPath(appDataFile, getter_AddRefs(appini));
+ if (NS_FAILED(rv)) {
+ Output("Invalid path found: '%s'", appDataFile);
+ return 255;
+ }
+ }
+ else if (argc > 1 && IsArg(argv[1], "app")) {
+ if (argc == 2) {
+ Output("Incorrect number of arguments passed to -app");
+ return 255;
+ }
+
+ rv = XRE_GetFileFromPath(argv[2], getter_AddRefs(appini));
+ if (NS_FAILED(rv)) {
+ Output("application.ini path not recognized: '%s'", argv[2]);
+ return 255;
+ }
+
+ char appEnv[MAXPATHLEN];
+ SprintfLiteral(appEnv, "XUL_APP_FILE=%s", argv[2]);
+ if (putenv(strdup(appEnv))) {
+ Output("Couldn't set %s.\n", appEnv);
+ return 255;
+ }
+ argv[2] = argv[0];
+ argv += 2;
+ argc -= 2;
+ }
+
+#ifdef MOZ_WIDGET_GONK
+ /* Start boot animation */
+ mozilla::StartBootAnimation();
+#endif
+
+ if (appini) {
+ nsXREAppData *appData;
+ rv = XRE_CreateAppData(appini, &appData);
+ if (NS_FAILED(rv)) {
+ Output("Couldn't read application.ini");
+ return 255;
+ }
+ int result = XRE_main(argc, argv, appData, 0);
+ XRE_FreeAppData(appData);
+ return result;
+ }
+
+ return XRE_main(argc, argv, &sAppData, 0);
+}
+
+int main(int argc, char* argv[])
+{
+ char exePath[MAXPATHLEN];
+
+#ifdef MOZ_WIDGET_GONK
+ // This creates a ThreadPool for binder ipc. A ThreadPool is necessary to
+ // receive binder calls, though not necessary to send binder calls.
+ // ProcessState::Self() also needs to be called once on the main thread to
+ // register the main thread with the binder driver.
+ android::ProcessState::self()->startThreadPool();
+#endif
+
+ nsresult rv;
+ rv = mozilla::BinaryPath::Get(argv[0], exePath);
+ if (NS_FAILED(rv)) {
+ Output("Couldn't calculate the application directory.\n");
+ return 255;
+ }
+
+ char *lastSlash = strrchr(exePath, XPCOM_FILE_PATH_SEPARATOR[0]);
+ if (!lastSlash || ((lastSlash - exePath) + sizeof(XPCOM_DLL) + 1 > MAXPATHLEN))
+ return 255;
+
+ strcpy(++lastSlash, XPCOM_DLL);
+
+#if defined(XP_UNIX)
+ // If the b2g app is launched from adb shell, then the shell will wind
+ // up being the process group controller. This means that we can't send
+ // signals to the process group (useful for profiling).
+ // We ignore the return value since setsid() fails if we're already the
+ // process group controller (the normal situation).
+ (void)setsid();
+#endif
+
+#ifdef HAS_DLL_BLOCKLIST
+ DllBlocklist_Initialize();
+#endif
+
+ // We do this because of data in bug 771745
+ XPCOMGlueEnablePreload();
+
+ rv = XPCOMGlueStartup(exePath);
+ if (NS_FAILED(rv)) {
+ Output("Couldn't load XPCOM.\n");
+ return 255;
+ }
+ // Reset exePath so that it is the directory name and not the xpcom dll name
+ *lastSlash = 0;
+
+ rv = XPCOMGlueLoadXULFunctions(kXULFuncs);
+ if (NS_FAILED(rv)) {
+ Output("Couldn't load XRE functions.\n");
+ return 255;
+ }
+
+ int result;
+ {
+ ScopedLogging log;
+ char **_argv;
+
+ /*
+ * Duplicate argument vector to conform non-const argv of
+ * do_main() since XRE_main() is very stupid with non-const argv.
+ */
+ _argv = new char *[argc + 1];
+ for (int i = 0; i < argc; i++) {
+ size_t len = strlen(argv[i]) + 1;
+ _argv[i] = new char[len];
+ MOZ_ASSERT(_argv[i] != nullptr);
+ memcpy(_argv[i], argv[i], len);
+ }
+ _argv[argc] = nullptr;
+
+ result = do_main(argc, _argv);
+
+ for (int i = 0; i < argc; i++) {
+ delete[] _argv[i];
+ }
+ delete[] _argv;
+ }
+
+ return result;
+}
diff --git a/b2g/app/ua-update.json.in b/b2g/app/ua-update.json.in
new file mode 100644
index 0000000000..cb4c66d124
--- /dev/null
+++ b/b2g/app/ua-update.json.in
@@ -0,0 +1,51 @@
+#filter slashslash
+// Everything after the first // on a line will be removed by the preproccesor.
+// Send these sites a custom user-agent. Bugs should be included with an entry.
+{
+ // bug 826347, msn.com
+ "msn.com": "\\(Mobile#(Android 4.4.4; Mobile",
+ // bug 826353, itau.com.br
+ "itau.com.br": "\\(Mobile#(Android; Mobile",
+ // bug 826510, r7.com
+ "r7.com": "\\(Mobile#(Android; Mobile",
+ // bug 827622, bing.com
+ "bing.com": "\\(Mobile#(Android; Mobile",
+ // bug 827626, magazineluiza.com.br
+ "magazineluiza.com.br": "\\(Mobile#(Android; Mobile",
+ // bug 827670, elpais.com.co
+ "elpais.com.co": "\\(Mobile#(Android 4.4.4; Mobile",
+ // bug 828416, loteriasyapuestas.es
+ "loteriasyapuestas.es": "\\(Mobile#(Android; Mobile",
+ // bug 828418, bbva.es
+ "bbva.es": "\\(Mobile#(Android; Mobile",
+ // bug 828439, movistar.com.ve
+ "movistar.com.ve": "\\(Mobile#(Android; Mobile",
+ // bug 843132, comunio.es
+ "comunio.es": "\\(Mobile#(Android; Mobile",
+ // bug 843151, citibank.com
+ "citibank.com": "\\(Mobile#(Android; Mobile",
+ // bug 843153, games.com
+ "games.com": "\\(Mobile#(Android; Mobile",
+ // bug 843160, ehow.com
+ "ehow.com": "\\(Mobile#(Android; Mobile",
+ // bug 878228, blikk.hu
+ "blikk.hu": "\\(Mobile#(Android; Mobile",
+ // bug 878238, koponyeg.hu
+ "koponyeg.hu": "\\(Mobile#(Android; Mobile",
+ // bug 878240, kuruc.info
+ "kuruc.info": "\\(Mobile#(Android; Mobile",
+ // bug 878242, nemzetisport.hu
+ "nemzetisport.hu": "\\(Mobile#(Android; Mobile",
+ // bug 878246, port.hu
+ "port.hu": "\\(Mobile#(Android; Mobile",
+ // bug 878249, portfolio.hu
+ "portfolio.hu": "\\(Mobile#(Android; Mobile",
+ // bug 878260, cdm.me
+ "cdm.me": "\\(Mobile#(Android; Mobile",
+ // bug 878262, download.com
+ "download.com": "\\(Mobile#(Android; Mobile",
+ // bug 878273, livescore.com
+ "livescore.com": "\\(Mobile#(Android; Mobile",
+ // bug 878653, redstarbelgrade.info
+ "redstarbelgrade.info": "\\(Mobile#(Android; Mobile"
+}
diff --git a/b2g/branding/branding-common.mozbuild b/b2g/branding/branding-common.mozbuild
new file mode 100644
index 0000000000..ae4aeb285b
--- /dev/null
+++ b/b2g/branding/branding-common.mozbuild
@@ -0,0 +1,23 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+@template
+def B2GBranding():
+ if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'windows':
+ BRANDING_FILES += [
+ 'app.ico',
+ ]
+ elif CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa':
+ BRANDING_FILES += [
+ 'app.icns',
+ 'background.png',
+ 'disk.icns',
+ 'dsstore',
+ ]
+ elif 'gtk' in CONFIG['MOZ_WIDGET_TOOLKIT']:
+ BRANDING_FILES += [
+ 'default.png',
+ ]
diff --git a/b2g/branding/browserhtml/app.icns b/b2g/branding/browserhtml/app.icns
new file mode 100644
index 0000000000..3d680134e2
--- /dev/null
+++ b/b2g/branding/browserhtml/app.icns
Binary files differ
diff --git a/b2g/branding/browserhtml/app.ico b/b2g/branding/browserhtml/app.ico
new file mode 100644
index 0000000000..04596e4d06
--- /dev/null
+++ b/b2g/branding/browserhtml/app.ico
Binary files differ
diff --git a/b2g/branding/browserhtml/background.png b/b2g/branding/browserhtml/background.png
new file mode 100644
index 0000000000..db5576a336
--- /dev/null
+++ b/b2g/branding/browserhtml/background.png
Binary files differ
diff --git a/b2g/branding/browserhtml/configure.sh b/b2g/branding/browserhtml/configure.sh
new file mode 100644
index 0000000000..71c6129d27
--- /dev/null
+++ b/b2g/branding/browserhtml/configure.sh
@@ -0,0 +1,5 @@
+# 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/.
+
+MOZ_APP_DISPLAYNAME=Browser.html
diff --git a/b2g/branding/browserhtml/content/about.png b/b2g/branding/browserhtml/content/about.png
new file mode 100644
index 0000000000..3cc1444f64
--- /dev/null
+++ b/b2g/branding/browserhtml/content/about.png
Binary files differ
diff --git a/b2g/branding/browserhtml/content/favicon32.png b/b2g/branding/browserhtml/content/favicon32.png
new file mode 100644
index 0000000000..ac4a6968bf
--- /dev/null
+++ b/b2g/branding/browserhtml/content/favicon32.png
Binary files differ
diff --git a/b2g/branding/browserhtml/content/icon48.png b/b2g/branding/browserhtml/content/icon48.png
new file mode 100644
index 0000000000..b7513c2e46
--- /dev/null
+++ b/b2g/branding/browserhtml/content/icon48.png
Binary files differ
diff --git a/b2g/branding/browserhtml/content/icon64.png b/b2g/branding/browserhtml/content/icon64.png
new file mode 100644
index 0000000000..c8bee8fca9
--- /dev/null
+++ b/b2g/branding/browserhtml/content/icon64.png
Binary files differ
diff --git a/b2g/branding/browserhtml/content/jar.mn b/b2g/branding/browserhtml/content/jar.mn
new file mode 100644
index 0000000000..8a2c16964f
--- /dev/null
+++ b/b2g/branding/browserhtml/content/jar.mn
@@ -0,0 +1,10 @@
+# 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/.
+
+chrome.jar:
+% content branding %content/branding/ contentaccessible=yes
+ content/branding/about.png (about.png)
+ content/branding/logoWordmark.png (logoWordmark.png)
+ content/branding/logo.png (logo.png)
+ content/branding/favicon32.png (favicon32.png)
diff --git a/b2g/branding/browserhtml/content/logo.png b/b2g/branding/browserhtml/content/logo.png
new file mode 100644
index 0000000000..9d9d0c57e0
--- /dev/null
+++ b/b2g/branding/browserhtml/content/logo.png
Binary files differ
diff --git a/b2g/branding/browserhtml/content/logoWordmark.png b/b2g/branding/browserhtml/content/logoWordmark.png
new file mode 100644
index 0000000000..878363181a
--- /dev/null
+++ b/b2g/branding/browserhtml/content/logoWordmark.png
Binary files differ
diff --git a/b2g/branding/browserhtml/content/moz.build b/b2g/branding/browserhtml/content/moz.build
new file mode 100644
index 0000000000..eb4454d28f
--- /dev/null
+++ b/b2g/branding/browserhtml/content/moz.build
@@ -0,0 +1,7 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+JAR_MANIFESTS += ['jar.mn'] \ No newline at end of file
diff --git a/b2g/branding/browserhtml/content/splash.png b/b2g/branding/browserhtml/content/splash.png
new file mode 100644
index 0000000000..84ab581d20
--- /dev/null
+++ b/b2g/branding/browserhtml/content/splash.png
Binary files differ
diff --git a/b2g/branding/browserhtml/default.png b/b2g/branding/browserhtml/default.png
new file mode 100644
index 0000000000..ae4736223a
--- /dev/null
+++ b/b2g/branding/browserhtml/default.png
Binary files differ
diff --git a/b2g/branding/browserhtml/disk.icns b/b2g/branding/browserhtml/disk.icns
new file mode 100644
index 0000000000..c49b7b878e
--- /dev/null
+++ b/b2g/branding/browserhtml/disk.icns
Binary files differ
diff --git a/b2g/branding/browserhtml/dsstore b/b2g/branding/browserhtml/dsstore
new file mode 100644
index 0000000000..657101d6e2
--- /dev/null
+++ b/b2g/branding/browserhtml/dsstore
Binary files differ
diff --git a/b2g/branding/browserhtml/locales/en-US/brand.dtd b/b2g/branding/browserhtml/locales/en-US/brand.dtd
new file mode 100644
index 0000000000..6c3088d70c
--- /dev/null
+++ b/b2g/branding/browserhtml/locales/en-US/brand.dtd
@@ -0,0 +1,7 @@
+<!-- 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/. -->
+
+<!ENTITY brandShortName "Browser.html">
+<!ENTITY brandFullName "Mozilla Browser.html">
+<!ENTITY vendorShortName "Mozilla">
diff --git a/b2g/branding/browserhtml/locales/en-US/brand.properties b/b2g/branding/browserhtml/locales/en-US/brand.properties
new file mode 100644
index 0000000000..897d1a8007
--- /dev/null
+++ b/b2g/branding/browserhtml/locales/en-US/brand.properties
@@ -0,0 +1,6 @@
+# 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/.
+
+brandShortName=Browser.html
+brandFullName=Mozilla Browser.html
diff --git a/b2g/branding/browserhtml/locales/jar.mn b/b2g/branding/browserhtml/locales/jar.mn
new file mode 100644
index 0000000000..2ea47e1684
--- /dev/null
+++ b/b2g/branding/browserhtml/locales/jar.mn
@@ -0,0 +1,11 @@
+#filter substitution
+# 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/.
+
+
+@AB_CD@.jar:
+% locale branding @AB_CD@ %locale/branding/
+# Branding only exists in en-US
+ locale/branding/brand.dtd (en-US/brand.dtd)
+ locale/branding/brand.properties (en-US/brand.properties)
diff --git a/b2g/branding/browserhtml/locales/moz.build b/b2g/branding/browserhtml/locales/moz.build
new file mode 100644
index 0000000000..eb4454d28f
--- /dev/null
+++ b/b2g/branding/browserhtml/locales/moz.build
@@ -0,0 +1,7 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+JAR_MANIFESTS += ['jar.mn'] \ No newline at end of file
diff --git a/b2g/branding/browserhtml/moz.build b/b2g/branding/browserhtml/moz.build
new file mode 100644
index 0000000000..bf7aff4c04
--- /dev/null
+++ b/b2g/branding/browserhtml/moz.build
@@ -0,0 +1,10 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+DIRS += ['content', 'locales']
+
+include('../branding-common.mozbuild')
+B2GBranding()
diff --git a/b2g/branding/horizon/app.icns b/b2g/branding/horizon/app.icns
new file mode 100644
index 0000000000..6c7b9f5b50
--- /dev/null
+++ b/b2g/branding/horizon/app.icns
Binary files differ
diff --git a/b2g/branding/horizon/app.ico b/b2g/branding/horizon/app.ico
new file mode 100644
index 0000000000..49eb904192
--- /dev/null
+++ b/b2g/branding/horizon/app.ico
Binary files differ
diff --git a/b2g/branding/horizon/background.png b/b2g/branding/horizon/background.png
new file mode 100644
index 0000000000..db5576a336
--- /dev/null
+++ b/b2g/branding/horizon/background.png
Binary files differ
diff --git a/b2g/branding/horizon/configure.sh b/b2g/branding/horizon/configure.sh
new file mode 100644
index 0000000000..65774e3841
--- /dev/null
+++ b/b2g/branding/horizon/configure.sh
@@ -0,0 +1,5 @@
+# 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/.
+
+MOZ_APP_DISPLAYNAME=Horizon
diff --git a/b2g/branding/horizon/content/about.png b/b2g/branding/horizon/content/about.png
new file mode 100644
index 0000000000..3cc1444f64
--- /dev/null
+++ b/b2g/branding/horizon/content/about.png
Binary files differ
diff --git a/b2g/branding/horizon/content/favicon32.png b/b2g/branding/horizon/content/favicon32.png
new file mode 100644
index 0000000000..ac4a6968bf
--- /dev/null
+++ b/b2g/branding/horizon/content/favicon32.png
Binary files differ
diff --git a/b2g/branding/horizon/content/icon48.png b/b2g/branding/horizon/content/icon48.png
new file mode 100644
index 0000000000..b7513c2e46
--- /dev/null
+++ b/b2g/branding/horizon/content/icon48.png
Binary files differ
diff --git a/b2g/branding/horizon/content/icon64.png b/b2g/branding/horizon/content/icon64.png
new file mode 100644
index 0000000000..c8bee8fca9
--- /dev/null
+++ b/b2g/branding/horizon/content/icon64.png
Binary files differ
diff --git a/b2g/branding/horizon/content/jar.mn b/b2g/branding/horizon/content/jar.mn
new file mode 100644
index 0000000000..8a2c16964f
--- /dev/null
+++ b/b2g/branding/horizon/content/jar.mn
@@ -0,0 +1,10 @@
+# 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/.
+
+chrome.jar:
+% content branding %content/branding/ contentaccessible=yes
+ content/branding/about.png (about.png)
+ content/branding/logoWordmark.png (logoWordmark.png)
+ content/branding/logo.png (logo.png)
+ content/branding/favicon32.png (favicon32.png)
diff --git a/b2g/branding/horizon/content/logo.png b/b2g/branding/horizon/content/logo.png
new file mode 100644
index 0000000000..9d9d0c57e0
--- /dev/null
+++ b/b2g/branding/horizon/content/logo.png
Binary files differ
diff --git a/b2g/branding/horizon/content/logoWordmark.png b/b2g/branding/horizon/content/logoWordmark.png
new file mode 100644
index 0000000000..878363181a
--- /dev/null
+++ b/b2g/branding/horizon/content/logoWordmark.png
Binary files differ
diff --git a/b2g/branding/horizon/content/moz.build b/b2g/branding/horizon/content/moz.build
new file mode 100644
index 0000000000..eb4454d28f
--- /dev/null
+++ b/b2g/branding/horizon/content/moz.build
@@ -0,0 +1,7 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+JAR_MANIFESTS += ['jar.mn'] \ No newline at end of file
diff --git a/b2g/branding/horizon/content/splash.png b/b2g/branding/horizon/content/splash.png
new file mode 100644
index 0000000000..84ab581d20
--- /dev/null
+++ b/b2g/branding/horizon/content/splash.png
Binary files differ
diff --git a/b2g/branding/horizon/default.png b/b2g/branding/horizon/default.png
new file mode 100644
index 0000000000..0e6a4016ca
--- /dev/null
+++ b/b2g/branding/horizon/default.png
Binary files differ
diff --git a/b2g/branding/horizon/disk.icns b/b2g/branding/horizon/disk.icns
new file mode 100644
index 0000000000..c49b7b878e
--- /dev/null
+++ b/b2g/branding/horizon/disk.icns
Binary files differ
diff --git a/b2g/branding/horizon/dsstore b/b2g/branding/horizon/dsstore
new file mode 100644
index 0000000000..657101d6e2
--- /dev/null
+++ b/b2g/branding/horizon/dsstore
Binary files differ
diff --git a/b2g/branding/horizon/locales/en-US/brand.dtd b/b2g/branding/horizon/locales/en-US/brand.dtd
new file mode 100644
index 0000000000..ad7a938b56
--- /dev/null
+++ b/b2g/branding/horizon/locales/en-US/brand.dtd
@@ -0,0 +1,8 @@
+<!-- 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/. -->
+
+<!ENTITY brandShortName "Horizon">
+<!ENTITY brandFullName "Mozilla Horizon">
+<!ENTITY vendorShortName "Mozilla">
+<!ENTITY logoTrademark "Horizon and the Horizon logos are trademarks of the Mozilla Foundation.">
diff --git a/b2g/branding/horizon/locales/en-US/brand.properties b/b2g/branding/horizon/locales/en-US/brand.properties
new file mode 100644
index 0000000000..ce9e209cff
--- /dev/null
+++ b/b2g/branding/horizon/locales/en-US/brand.properties
@@ -0,0 +1,6 @@
+# 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/.
+
+brandShortName=Horizon
+brandFullName=Mozilla Horizon
diff --git a/b2g/branding/horizon/locales/jar.mn b/b2g/branding/horizon/locales/jar.mn
new file mode 100644
index 0000000000..2ea47e1684
--- /dev/null
+++ b/b2g/branding/horizon/locales/jar.mn
@@ -0,0 +1,11 @@
+#filter substitution
+# 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/.
+
+
+@AB_CD@.jar:
+% locale branding @AB_CD@ %locale/branding/
+# Branding only exists in en-US
+ locale/branding/brand.dtd (en-US/brand.dtd)
+ locale/branding/brand.properties (en-US/brand.properties)
diff --git a/b2g/branding/horizon/locales/moz.build b/b2g/branding/horizon/locales/moz.build
new file mode 100644
index 0000000000..eb4454d28f
--- /dev/null
+++ b/b2g/branding/horizon/locales/moz.build
@@ -0,0 +1,7 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+JAR_MANIFESTS += ['jar.mn'] \ No newline at end of file
diff --git a/b2g/branding/horizon/moz.build b/b2g/branding/horizon/moz.build
new file mode 100644
index 0000000000..bf7aff4c04
--- /dev/null
+++ b/b2g/branding/horizon/moz.build
@@ -0,0 +1,10 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+DIRS += ['content', 'locales']
+
+include('../branding-common.mozbuild')
+B2GBranding()
diff --git a/b2g/branding/official/app.icns b/b2g/branding/official/app.icns
new file mode 100644
index 0000000000..eba850aaee
--- /dev/null
+++ b/b2g/branding/official/app.icns
Binary files differ
diff --git a/b2g/branding/official/app.ico b/b2g/branding/official/app.ico
new file mode 100644
index 0000000000..5d4a61dc92
--- /dev/null
+++ b/b2g/branding/official/app.ico
Binary files differ
diff --git a/b2g/branding/official/background.png b/b2g/branding/official/background.png
new file mode 100644
index 0000000000..db5576a336
--- /dev/null
+++ b/b2g/branding/official/background.png
Binary files differ
diff --git a/b2g/branding/official/configure.sh b/b2g/branding/official/configure.sh
new file mode 100644
index 0000000000..127a0f1a17
--- /dev/null
+++ b/b2g/branding/official/configure.sh
@@ -0,0 +1,6 @@
+# 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/.
+
+MOZ_APP_DISPLAYNAME=B2G
+MOZ_UPDATER=
diff --git a/b2g/branding/official/content/about.png b/b2g/branding/official/content/about.png
new file mode 100644
index 0000000000..3cc1444f64
--- /dev/null
+++ b/b2g/branding/official/content/about.png
Binary files differ
diff --git a/b2g/branding/official/content/favicon32.png b/b2g/branding/official/content/favicon32.png
new file mode 100644
index 0000000000..ac4a6968bf
--- /dev/null
+++ b/b2g/branding/official/content/favicon32.png
Binary files differ
diff --git a/b2g/branding/official/content/icon48.png b/b2g/branding/official/content/icon48.png
new file mode 100644
index 0000000000..b7513c2e46
--- /dev/null
+++ b/b2g/branding/official/content/icon48.png
Binary files differ
diff --git a/b2g/branding/official/content/icon64.png b/b2g/branding/official/content/icon64.png
new file mode 100644
index 0000000000..c8bee8fca9
--- /dev/null
+++ b/b2g/branding/official/content/icon64.png
Binary files differ
diff --git a/b2g/branding/official/content/jar.mn b/b2g/branding/official/content/jar.mn
new file mode 100644
index 0000000000..8a2c16964f
--- /dev/null
+++ b/b2g/branding/official/content/jar.mn
@@ -0,0 +1,10 @@
+# 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/.
+
+chrome.jar:
+% content branding %content/branding/ contentaccessible=yes
+ content/branding/about.png (about.png)
+ content/branding/logoWordmark.png (logoWordmark.png)
+ content/branding/logo.png (logo.png)
+ content/branding/favicon32.png (favicon32.png)
diff --git a/b2g/branding/official/content/logo.png b/b2g/branding/official/content/logo.png
new file mode 100644
index 0000000000..9d9d0c57e0
--- /dev/null
+++ b/b2g/branding/official/content/logo.png
Binary files differ
diff --git a/b2g/branding/official/content/logoWordmark.png b/b2g/branding/official/content/logoWordmark.png
new file mode 100644
index 0000000000..878363181a
--- /dev/null
+++ b/b2g/branding/official/content/logoWordmark.png
Binary files differ
diff --git a/b2g/branding/official/content/moz.build b/b2g/branding/official/content/moz.build
new file mode 100644
index 0000000000..eb4454d28f
--- /dev/null
+++ b/b2g/branding/official/content/moz.build
@@ -0,0 +1,7 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+JAR_MANIFESTS += ['jar.mn'] \ No newline at end of file
diff --git a/b2g/branding/official/content/splash.png b/b2g/branding/official/content/splash.png
new file mode 100644
index 0000000000..84ab581d20
--- /dev/null
+++ b/b2g/branding/official/content/splash.png
Binary files differ
diff --git a/b2g/branding/official/default.png b/b2g/branding/official/default.png
new file mode 100644
index 0000000000..c4307fc841
--- /dev/null
+++ b/b2g/branding/official/default.png
Binary files differ
diff --git a/b2g/branding/official/disk.icns b/b2g/branding/official/disk.icns
new file mode 100644
index 0000000000..c49b7b878e
--- /dev/null
+++ b/b2g/branding/official/disk.icns
Binary files differ
diff --git a/b2g/branding/official/dsstore b/b2g/branding/official/dsstore
new file mode 100644
index 0000000000..657101d6e2
--- /dev/null
+++ b/b2g/branding/official/dsstore
Binary files differ
diff --git a/b2g/branding/official/locales/en-US/brand.dtd b/b2g/branding/official/locales/en-US/brand.dtd
new file mode 100644
index 0000000000..1a6f391481
--- /dev/null
+++ b/b2g/branding/official/locales/en-US/brand.dtd
@@ -0,0 +1,8 @@
+<!-- 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/. -->
+
+<!ENTITY brandShortName "Firefox">
+<!ENTITY brandFullName "Mozilla Firefox">
+<!ENTITY vendorShortName "Mozilla">
+<!ENTITY logoTrademark "Firefox and the Firefox logos are trademarks of the Mozilla Foundation.">
diff --git a/b2g/branding/official/locales/en-US/brand.properties b/b2g/branding/official/locales/en-US/brand.properties
new file mode 100644
index 0000000000..d0203e35a4
--- /dev/null
+++ b/b2g/branding/official/locales/en-US/brand.properties
@@ -0,0 +1,6 @@
+# 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/.
+
+brandShortName=Firefox
+brandFullName=Mozilla Firefox
diff --git a/b2g/branding/official/locales/jar.mn b/b2g/branding/official/locales/jar.mn
new file mode 100644
index 0000000000..2ea47e1684
--- /dev/null
+++ b/b2g/branding/official/locales/jar.mn
@@ -0,0 +1,11 @@
+#filter substitution
+# 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/.
+
+
+@AB_CD@.jar:
+% locale branding @AB_CD@ %locale/branding/
+# Branding only exists in en-US
+ locale/branding/brand.dtd (en-US/brand.dtd)
+ locale/branding/brand.properties (en-US/brand.properties)
diff --git a/b2g/branding/official/locales/moz.build b/b2g/branding/official/locales/moz.build
new file mode 100644
index 0000000000..eb4454d28f
--- /dev/null
+++ b/b2g/branding/official/locales/moz.build
@@ -0,0 +1,7 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+JAR_MANIFESTS += ['jar.mn'] \ No newline at end of file
diff --git a/b2g/branding/official/moz.build b/b2g/branding/official/moz.build
new file mode 100644
index 0000000000..bf7aff4c04
--- /dev/null
+++ b/b2g/branding/official/moz.build
@@ -0,0 +1,10 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+DIRS += ['content', 'locales']
+
+include('../branding-common.mozbuild')
+B2GBranding()
diff --git a/b2g/branding/unofficial/app.icns b/b2g/branding/unofficial/app.icns
new file mode 100644
index 0000000000..eba850aaee
--- /dev/null
+++ b/b2g/branding/unofficial/app.icns
Binary files differ
diff --git a/b2g/branding/unofficial/app.ico b/b2g/branding/unofficial/app.ico
new file mode 100644
index 0000000000..5d4a61dc92
--- /dev/null
+++ b/b2g/branding/unofficial/app.ico
Binary files differ
diff --git a/b2g/branding/unofficial/background.png b/b2g/branding/unofficial/background.png
new file mode 100644
index 0000000000..db5576a336
--- /dev/null
+++ b/b2g/branding/unofficial/background.png
Binary files differ
diff --git a/b2g/branding/unofficial/configure.sh b/b2g/branding/unofficial/configure.sh
new file mode 100644
index 0000000000..127a0f1a17
--- /dev/null
+++ b/b2g/branding/unofficial/configure.sh
@@ -0,0 +1,6 @@
+# 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/.
+
+MOZ_APP_DISPLAYNAME=B2G
+MOZ_UPDATER=
diff --git a/b2g/branding/unofficial/content/about.png b/b2g/branding/unofficial/content/about.png
new file mode 100644
index 0000000000..3819f6337a
--- /dev/null
+++ b/b2g/branding/unofficial/content/about.png
Binary files differ
diff --git a/b2g/branding/unofficial/content/favicon32.png b/b2g/branding/unofficial/content/favicon32.png
new file mode 100644
index 0000000000..3f04acd50d
--- /dev/null
+++ b/b2g/branding/unofficial/content/favicon32.png
Binary files differ
diff --git a/b2g/branding/unofficial/content/icon48.png b/b2g/branding/unofficial/content/icon48.png
new file mode 100644
index 0000000000..3ae248c857
--- /dev/null
+++ b/b2g/branding/unofficial/content/icon48.png
Binary files differ
diff --git a/b2g/branding/unofficial/content/icon64.png b/b2g/branding/unofficial/content/icon64.png
new file mode 100644
index 0000000000..fe980153be
--- /dev/null
+++ b/b2g/branding/unofficial/content/icon64.png
Binary files differ
diff --git a/b2g/branding/unofficial/content/jar.mn b/b2g/branding/unofficial/content/jar.mn
new file mode 100644
index 0000000000..8a2c16964f
--- /dev/null
+++ b/b2g/branding/unofficial/content/jar.mn
@@ -0,0 +1,10 @@
+# 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/.
+
+chrome.jar:
+% content branding %content/branding/ contentaccessible=yes
+ content/branding/about.png (about.png)
+ content/branding/logoWordmark.png (logoWordmark.png)
+ content/branding/logo.png (logo.png)
+ content/branding/favicon32.png (favicon32.png)
diff --git a/b2g/branding/unofficial/content/logo.png b/b2g/branding/unofficial/content/logo.png
new file mode 100644
index 0000000000..91377a3120
--- /dev/null
+++ b/b2g/branding/unofficial/content/logo.png
Binary files differ
diff --git a/b2g/branding/unofficial/content/logoWordmark.png b/b2g/branding/unofficial/content/logoWordmark.png
new file mode 100644
index 0000000000..a3017f59ec
--- /dev/null
+++ b/b2g/branding/unofficial/content/logoWordmark.png
Binary files differ
diff --git a/b2g/branding/unofficial/content/moz.build b/b2g/branding/unofficial/content/moz.build
new file mode 100644
index 0000000000..eb4454d28f
--- /dev/null
+++ b/b2g/branding/unofficial/content/moz.build
@@ -0,0 +1,7 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+JAR_MANIFESTS += ['jar.mn'] \ No newline at end of file
diff --git a/b2g/branding/unofficial/content/splash.png b/b2g/branding/unofficial/content/splash.png
new file mode 100644
index 0000000000..25a0830eac
--- /dev/null
+++ b/b2g/branding/unofficial/content/splash.png
Binary files differ
diff --git a/b2g/branding/unofficial/default.png b/b2g/branding/unofficial/default.png
new file mode 100644
index 0000000000..c4307fc841
--- /dev/null
+++ b/b2g/branding/unofficial/default.png
Binary files differ
diff --git a/b2g/branding/unofficial/disk.icns b/b2g/branding/unofficial/disk.icns
new file mode 100644
index 0000000000..c49b7b878e
--- /dev/null
+++ b/b2g/branding/unofficial/disk.icns
Binary files differ
diff --git a/b2g/branding/unofficial/dsstore b/b2g/branding/unofficial/dsstore
new file mode 100644
index 0000000000..657101d6e2
--- /dev/null
+++ b/b2g/branding/unofficial/dsstore
Binary files differ
diff --git a/b2g/branding/unofficial/locales/en-US/brand.dtd b/b2g/branding/unofficial/locales/en-US/brand.dtd
new file mode 100644
index 0000000000..1a6f391481
--- /dev/null
+++ b/b2g/branding/unofficial/locales/en-US/brand.dtd
@@ -0,0 +1,8 @@
+<!-- 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/. -->
+
+<!ENTITY brandShortName "Firefox">
+<!ENTITY brandFullName "Mozilla Firefox">
+<!ENTITY vendorShortName "Mozilla">
+<!ENTITY logoTrademark "Firefox and the Firefox logos are trademarks of the Mozilla Foundation.">
diff --git a/b2g/branding/unofficial/locales/en-US/brand.properties b/b2g/branding/unofficial/locales/en-US/brand.properties
new file mode 100644
index 0000000000..d0203e35a4
--- /dev/null
+++ b/b2g/branding/unofficial/locales/en-US/brand.properties
@@ -0,0 +1,6 @@
+# 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/.
+
+brandShortName=Firefox
+brandFullName=Mozilla Firefox
diff --git a/b2g/branding/unofficial/locales/jar.mn b/b2g/branding/unofficial/locales/jar.mn
new file mode 100644
index 0000000000..5a77695c91
--- /dev/null
+++ b/b2g/branding/unofficial/locales/jar.mn
@@ -0,0 +1,11 @@
+#filter substitution
+# 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/.
+
+
+@AB_CD@.jar:
+% locale branding @AB_CD@ %locale/branding/
+# Nightly branding only exists in en-US
+ locale/branding/brand.dtd (en-US/brand.dtd)
+ locale/branding/brand.properties (en-US/brand.properties)
diff --git a/b2g/branding/unofficial/locales/moz.build b/b2g/branding/unofficial/locales/moz.build
new file mode 100644
index 0000000000..eb4454d28f
--- /dev/null
+++ b/b2g/branding/unofficial/locales/moz.build
@@ -0,0 +1,7 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+JAR_MANIFESTS += ['jar.mn'] \ No newline at end of file
diff --git a/b2g/branding/unofficial/moz.build b/b2g/branding/unofficial/moz.build
new file mode 100644
index 0000000000..bf7aff4c04
--- /dev/null
+++ b/b2g/branding/unofficial/moz.build
@@ -0,0 +1,10 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+DIRS += ['content', 'locales']
+
+include('../branding-common.mozbuild')
+B2GBranding()
diff --git a/b2g/build.mk b/b2g/build.mk
new file mode 100644
index 0000000000..31e20b580e
--- /dev/null
+++ b/b2g/build.mk
@@ -0,0 +1,31 @@
+# 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 $(topsrcdir)/toolkit/mozapps/installer/package-name.mk
+
+installer:
+ @$(MAKE) -C b2g/installer installer
+
+package:
+ @$(MAKE) -C b2g/installer
+
+install::
+ @echo 'B2G can't be installed directly.'
+ @exit 1
+
+upload::
+ @$(MAKE) -C b2g/installer upload
+
+ifdef ENABLE_TESTS
+# Implemented in testing/testsuite-targets.mk
+
+mochitest-browser-chrome:
+ $(RUN_MOCHITEST) --flavor=browser
+ $(CHECK_TEST_ERROR)
+
+mochitest:: mochitest-browser-chrome
+
+.PHONY: mochitest-browser-chrome
+endif
+
diff --git a/b2g/chrome/content/ErrorPage.js b/b2g/chrome/content/ErrorPage.js
new file mode 100644
index 0000000000..d517824663
--- /dev/null
+++ b/b2g/chrome/content/ErrorPage.js
@@ -0,0 +1,73 @@
+/* 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';
+
+var Cu = Components.utils;
+var Cc = Components.classes;
+var Ci = Components.interfaces;
+
+dump("############ ErrorPage.js\n");
+
+var ErrorPageHandler = {
+ _reload: function() {
+ docShell.QueryInterface(Ci.nsIWebNavigation).reload(Ci.nsIWebNavigation.LOAD_FLAGS_NONE);
+ },
+
+ _certErrorPageEventHandler: function(e) {
+ let target = e.originalTarget;
+ let errorDoc = target.ownerDocument;
+
+ // If the event came from an ssl error page, it is one of the "Add
+ // Exception…" buttons.
+ if (/^about:certerror\?e=nssBadCert/.test(errorDoc.documentURI)) {
+ let permanent = errorDoc.getElementById("permanentExceptionButton");
+ let temp = errorDoc.getElementById("temporaryExceptionButton");
+ if (target == temp || target == permanent) {
+ sendAsyncMessage("ErrorPage:AddCertException", {
+ url: errorDoc.location.href,
+ isPermanent: target == permanent
+ });
+ }
+ }
+ },
+
+ _bindPageEvent: function(target) {
+ if (!target) {
+ return;
+ }
+
+ if (/^about:certerror/.test(target.documentURI)) {
+ let errorPageEventHandler = this._certErrorPageEventHandler.bind(this);
+ addEventListener("click", errorPageEventHandler, true, false);
+ let listener = function() {
+ removeEventListener("click", errorPageEventHandler, true);
+ removeEventListener("pagehide", listener, true);
+ }.bind(this);
+
+ addEventListener("pagehide", listener, true);
+ }
+ },
+
+ domContentLoadedHandler: function(e) {
+ let target = e.originalTarget;
+ let targetDocShell = target.defaultView
+ .QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIWebNavigation);
+ if (targetDocShell != docShell) {
+ return;
+ }
+ this._bindPageEvent(target);
+ },
+
+ init: function() {
+ addMessageListener("ErrorPage:ReloadPage", this._reload.bind(this));
+ addEventListener('DOMContentLoaded',
+ this.domContentLoadedHandler.bind(this),
+ true);
+ this._bindPageEvent(content.document);
+ }
+};
+
+ErrorPageHandler.init();
diff --git a/b2g/chrome/content/aboutCertError.xhtml b/b2g/chrome/content/aboutCertError.xhtml
new file mode 100644
index 0000000000..616657e540
--- /dev/null
+++ b/b2g/chrome/content/aboutCertError.xhtml
@@ -0,0 +1,233 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!DOCTYPE html [
+ <!ENTITY % htmlDTD
+ PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+ "DTD/xhtml1-strict.dtd">
+ %htmlDTD;
+ <!ENTITY % globalDTD
+ SYSTEM "chrome://global/locale/global.dtd">
+ %globalDTD;
+ <!ENTITY % certerrorDTD
+ SYSTEM "chrome://b2g-l10n/locale/aboutCertError.dtd">
+ %certerrorDTD;
+]>
+
+<!-- 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/. -->
+<html xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+ <title>&certerror.pagetitle;</title>
+ <meta name="viewport" content="width=device-width; user-scalable=false" />
+ <link rel="stylesheet" href="chrome://global/skin/netError.css" type="text/css" media="all" />
+ <!-- This page currently uses the same favicon as neterror.xhtml.
+ If the location of the favicon is changed for both pages, the
+ FAVICON_ERRORPAGE_URL symbol in toolkit/components/places/src/nsFaviconService.h
+ should be updated. If this page starts using a different favicon
+ than neterrorm nsFaviconService->SetAndLoadFaviconForPage
+ should be updated to ignore this one as well. -->
+ <link rel="icon" type="image/png" id="favicon" sizes="64x64" href="chrome://global/skin/icons/warning-64.png"/>
+
+ <script type="application/javascript"><![CDATA[
+ // Error url MUST be formatted like this:
+ // about:certerror?e=error&u=url&d=desc
+
+ // Note that this file uses document.documentURI to get
+ // the URL (with the format from above). This is because
+ // document.location.href gets the current URI off the docshell,
+ // which is the URL displayed in the location bar, i.e.
+ // the URI that the user attempted to load.
+
+ function getCSSClass()
+ {
+ var url = document.documentURI;
+ var matches = url.match(/s\=([^&]+)\&/);
+ // s is optional, if no match just return nothing
+ if (!matches || matches.length < 2)
+ return "";
+
+ // parenthetical match is the second entry
+ return decodeURIComponent(matches[1]);
+ }
+
+ function getDescription()
+ {
+ var url = document.documentURI;
+ var desc = url.search(/d\=/);
+
+ // desc == -1 if not found; if so, return an empty string
+ // instead of what would turn out to be portions of the URI
+ if (desc == -1)
+ return "";
+
+ return decodeURIComponent(url.slice(desc + 2));
+ }
+
+ function initPage()
+ {
+ // Replace the "#1" string in the intro with the hostname. Trickier
+ // than it might seem since we want to preserve the <b> tags, but
+ // not allow for any injection by just using innerHTML. Instead,
+ // just find the right target text node.
+ var intro = document.getElementById('introContentP1');
+ function replaceWithHost(node) {
+ if (node.textContent == "#1")
+ node.textContent = location.host;
+ else
+ for(var i = 0; i < node.childNodes.length; i++)
+ replaceWithHost(node.childNodes[i]);
+ };
+ replaceWithHost(intro);
+
+ if (getCSSClass() == "expertBadCert") {
+ toggle('technicalContent');
+ toggle('expertContent');
+ }
+
+ var tech = document.getElementById("technicalContentText");
+ if (tech)
+ tech.textContent = getDescription();
+
+ addDomainErrorLink();
+ }
+
+ /* In the case of SSL error pages about domain mismatch, see if
+ we can hyperlink the user to the correct site. We don't want
+ to do this generically since it allows MitM attacks to redirect
+ users to a site under attacker control, but in certain cases
+ it is safe (and helpful!) to do so. Bug 402210
+ */
+ function addDomainErrorLink() {
+ // Rather than textContent, we need to treat description as HTML
+ var sd = document.getElementById("technicalContentText");
+ if (sd) {
+ var desc = getDescription();
+
+ // sanitize description text - see bug 441169
+
+ // First, find the index of the <a> tag we care about, being careful not to
+ // use an over-greedy regex
+ var re = /<a id="cert_domain_link" title="([^"]+)">/;
+ var result = re.exec(desc);
+ if(!result)
+ return;
+
+ // Remove sd's existing children
+ sd.textContent = "";
+
+ // Everything up to the link should be text content
+ sd.appendChild(document.createTextNode(desc.slice(0, result.index)));
+
+ // Now create the link itself
+ var anchorEl = document.createElement("a");
+ anchorEl.setAttribute("id", "cert_domain_link");
+ anchorEl.setAttribute("title", result[1]);
+ anchorEl.appendChild(document.createTextNode(result[1]));
+ sd.appendChild(anchorEl);
+
+ // Finally, append text for anything after the closing </a>
+ sd.appendChild(document.createTextNode(desc.slice(desc.indexOf("</a>") + "</a>".length)));
+ }
+
+ var link = document.getElementById('cert_domain_link');
+ if (!link)
+ return;
+
+ var okHost = link.getAttribute("title");
+ var thisHost = document.location.hostname;
+ var proto = document.location.protocol;
+
+ // If okHost is a wildcard domain ("*.example.com") let's
+ // use "www" instead. "*.example.com" isn't going to
+ // get anyone anywhere useful. bug 432491
+ okHost = okHost.replace(/^\*\./, "www.");
+
+ /* case #1:
+ * example.com uses an invalid security certificate.
+ *
+ * The certificate is only valid for www.example.com
+ *
+ * Make sure to include the "." ahead of thisHost so that
+ * a MitM attack on paypal.com doesn't hyperlink to "notpaypal.com"
+ *
+ * We'd normally just use a RegExp here except that we lack a
+ * library function to escape them properly (bug 248062), and
+ * domain names are famous for having '.' characters in them,
+ * which would allow spurious and possibly hostile matches.
+ */
+ if (endsWith(okHost, "." + thisHost))
+ link.href = proto + okHost;
+
+ /* case #2:
+ * browser.garage.maemo.org uses an invalid security certificate.
+ *
+ * The certificate is only valid for garage.maemo.org
+ */
+ if (endsWith(thisHost, "." + okHost))
+ link.href = proto + okHost;
+
+ // If we set a link, meaning there's something helpful for
+ // the user here, expand the section by default
+ if (link.href && getCSSClass() != "expertBadCert")
+ toggle("technicalContent");
+ }
+
+ function endsWith(haystack, needle) {
+ return haystack.slice(-needle.length) == needle;
+ }
+
+ function toggle(id) {
+ var el = document.getElementById(id);
+ if (el.getAttribute("collapsed"))
+ el.setAttribute("collapsed", false);
+ else
+ el.setAttribute("collapsed", true);
+ }
+ ]]></script>
+ </head>
+
+ <body id="errorPage" class="certerror" dir="&locale.dir;">
+
+ <!-- Error Title -->
+ <div id="errorTitle">
+ <h1 class="errorTitleText">&certerror.longpagetitle;</h1>
+ </div>
+
+ <!-- PAGE CONTAINER (for styling purposes only) -->
+ <div id="errorPageContainer">
+
+ <!-- LONG CONTENT (the section most likely to require scrolling) -->
+ <div id="errorLongContent">
+ <div id="introContent">
+ <p id="introContentP1">&certerror.introPara1;</p>
+ </div>
+
+ <!-- The following sections can be unhidden by default by setting the
+ "browser.xul.error_pages.expert_bad_cert" pref to true -->
+ <div id="technicalContent" collapsed="true">
+ <h2 onclick="toggle('technicalContent');" id="technicalContentHeading">&certerror.technical.heading;</h2>
+ <p id="technicalContentText"/>
+ </div>
+
+ <div id="expertContent" collapsed="true">
+ <h2 onclick="toggle('expertContent');" id="expertContentHeading">&certerror.expert.heading;</h2>
+ <div>
+ <p>&certerror.expert.content;</p>
+ <p>&certerror.expert.contentPara2;</p>
+ <button id="temporaryExceptionButton">&certerror.addTemporaryException.label;</button>
+ <button id="permanentExceptionButton">&certerror.addPermanentException.label;</button>
+ </div>
+ </div>
+ </div>
+ </div>
+
+ <!--
+ - Note: It is important to run the script this way, instead of using
+ - an onload handler. This is because error pages are loaded as
+ - LOAD_BACKGROUND, which means that onload handlers will not be executed.
+ -->
+ <script type="application/javascript">initPage();</script>
+
+ </body>
+</html>
diff --git a/b2g/chrome/content/arrow.svg b/b2g/chrome/content/arrow.svg
new file mode 100644
index 0000000000..d3d9e8246a
--- /dev/null
+++ b/b2g/chrome/content/arrow.svg
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+
+<svg xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" version="1.1" width="11px" style="position: absolute; top: -moz-calc(50% - 2px);">
+ <polyline points="1 1 5 6 9 1" stroke="#414141" stroke-width="2" stroke-linecap="round" fill="transparent" stroke-linejoin="round"/>
+</svg>
diff --git a/b2g/chrome/content/blank.css b/b2g/chrome/content/blank.css
new file mode 100644
index 0000000000..71914be1f5
--- /dev/null
+++ b/b2g/chrome/content/blank.css
@@ -0,0 +1,7 @@
+/* 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/. */
+
+body {
+ background: black;
+}
diff --git a/b2g/chrome/content/blank.html b/b2g/chrome/content/blank.html
new file mode 100644
index 0000000000..b8b20e2c67
--- /dev/null
+++ b/b2g/chrome/content/blank.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<!-- 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/. -->
+
+<html>
+ <head>
+ <link rel="stylesheet" href="blank.css" type="text/css" media="all" />
+ </head>
+</html>
diff --git a/b2g/chrome/content/content.css b/b2g/chrome/content/content.css
new file mode 100644
index 0000000000..bb478087e8
--- /dev/null
+++ b/b2g/chrome/content/content.css
@@ -0,0 +1,321 @@
+/* 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/. */
+
+@namespace url("http://www.w3.org/1999/xhtml");
+@namespace xul url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");
+
+/* Style the scrollbars */
+xul|window xul|scrollbar {
+ display: none;
+}
+
+/* Bug 1041576 - Scrollable with scrollgrab should not have scrollbars */
+
+@-moz-document domain(system.gaiamobile.org) {
+ .browser-container > xul|scrollbar {
+ display: none;
+ }
+}
+
+%ifdef MOZ_GRAPHENE
+.moz-noscrollbars > xul|scrollbar {
+ display: none;
+}
+%endif
+
+xul|scrollbar[root="true"] {
+ position: relative;
+ z-index: 2147483647;
+}
+
+xul|scrollbar {
+ -moz-appearance: none !important;
+ background-color: transparent !important;
+ background-image: none !important;
+ border: 0px solid transparent !important;
+ pointer-events: none;
+}
+
+/* Scrollbar code will reset the margin to the correct side depending on
+ where layout actually puts the scrollbar */
+xul|scrollbar[orient="vertical"] {
+ margin-left: -8px;
+ min-width: 8px;
+ max-width: 8px;
+}
+
+xul|scrollbar[orient="vertical"] xul|thumb {
+ max-width: 6px !important;
+ min-width: 6px !important;
+}
+
+xul|scrollbar[orient="horizontal"] {
+ margin-top: -8px;
+ min-height: 8px;
+ max-height: 8px;
+}
+
+xul|scrollbar[orient="horizontal"] xul|thumb {
+ max-height: 6px !important;
+ min-height: 6px !important;
+}
+
+xul|scrollbar:not([active="true"]),
+xul|scrollbar[disabled] {
+ opacity: 0;
+}
+
+xul|scrollbarbutton {
+ min-height: 8px !important;
+ min-width: 8px !important;
+ -moz-appearance: none !important;
+ visibility: hidden;
+}
+
+xul|scrollbarbutton[sbattr="scrollbar-up-top"],
+xul|scrollbarbutton[sbattr="scrollbar-bottom-top"] {
+ display: none;
+}
+
+xul|thumb {
+ background-color: rgba(0, 0, 0, 0.4) !important;
+ -moz-border-top-colors: none !important;
+ -moz-border-bottom-colors: none !important;
+ -moz-border-right-colors: none !important;
+ -moz-border-left-colors: none !important;
+ border: 1px solid rgba(255, 255, 255, 0.4) !important;
+ border-radius: 3px;
+}
+
+xul|scrollbarbutton {
+ background-image: none !important;
+}
+
+%ifndef MOZ_GRAPHENE
+/* -moz-touch-enabled? media elements */
+:-moz-any(video, audio) > xul|videocontrols {
+ -moz-binding: url("chrome://global/content/bindings/videocontrols.xml#touchControlsGonk");
+}
+
+select:not([size]):not([multiple]) > xul|scrollbar,
+select[size="1"] > xul|scrollbar,
+select:not([size]):not([multiple]) xul|scrollbarbutton,
+select[size="1"] xul|scrollbarbutton {
+ display: block;
+ margin-left: 0;
+ min-width: 16px;
+}
+
+/* Override inverse OS themes */
+select,
+textarea,
+button,
+xul|button,
+* > input:not([type="image"]) {
+ -moz-appearance: none !important; /* See bug 598421 for fixing the platform */
+ border-radius: 3px;
+}
+
+select[size],
+select[multiple],
+select[size][multiple],
+textarea,
+* > input:not([type="image"]) {
+ border-style: solid;
+ border-color: #7d7d7d;
+ color: #414141;
+ background-color: white;
+}
+
+/* Selects are handled by the form helper, see bug 685197 */
+select option, select optgroup {
+ pointer-events: none;
+}
+
+select:not([size]):not([multiple]),
+select[size="0"],
+select[size="1"],
+* > input[type="button"],
+* > input[type="submit"],
+* > input[type="reset"],
+button {
+ border-style: solid;
+ border-color: #7d7d7d;
+ color: #414141;
+ background: white linear-gradient(rgba(255,255,255,0.2) 0, rgba(215,215,215,0.5) 18px, rgba(115,115,115,0.5) 100%);
+}
+
+input[type="checkbox"] {
+ background-color: white;
+}
+
+input[type="radio"] {
+ background-color: white;
+}
+
+select {
+ border-width: 1px;
+ padding: 1px;
+}
+
+select:not([size]):not([multiple]),
+select[size="0"],
+select[size="1"] {
+ padding: 0 1px 0 1px;
+}
+
+* > input:not([type="image"]) {
+ border-width: 1px;
+ padding: 1px;
+}
+
+textarea {
+ resize: none;
+ border-width: 1px;
+ padding-inline-start: 1px;
+ padding-inline-end: 1px;
+ padding-block-start: 2px;
+ padding-block-end: 2px;
+}
+
+input[type="button"],
+input[type="submit"],
+input[type="reset"],
+button {
+ border-width: 1px;
+ padding-inline-start: 7px;
+ padding-inline-end: 7px;
+ padding-block-start: 0;
+ padding-block-end: 0;
+}
+
+input[type="radio"],
+input[type="checkbox"] {
+ border: 1px solid #a7a7a7 !important;
+ padding-inline-start: 1px;
+ padding-inline-end: 1px;
+ padding-block-start: 2px;
+ padding-block-end: 2px;
+}
+
+select > button {
+ border-width: 0px !important;
+ margin: 0px !important;
+ padding: 0px !important;
+ border-radius: 0;
+ color: #414141;
+
+ background-image: radial-gradient(at bottom left, #bbbbbb 40%, #f5f5f5), url(arrow.svg) !important;
+ background-color: transparent;
+ background-position: -15px center, 4px center !important;
+ background-repeat: no-repeat, no-repeat !important;
+ background-size: 100% 90%, auto auto;
+
+ -moz-binding: none !important;
+ position: relative !important;
+ font-size: inherit;
+}
+
+select[size]:focus,
+select[multiple]:focus,
+select[size][multiple]:focus,
+textarea:focus,
+input[type="file"]:focus > input[type="text"],
+* > input:not([type="image"]):focus {
+ outline: 0px !important;
+ border-style: solid;
+ border-color: rgb(94,128,153);
+ background-color: white;
+}
+
+select:not([size]):not([multiple]):focus,
+select[size="0"]:focus,
+select[size="1"]:focus,
+input[type="button"]:focus,
+input[type="submit"]:focus,
+input[type="reset"]:focus,
+button:focus {
+ outline: 0px !important;
+ border-style: solid;
+ border-color: rgb(94,128,153);
+ background: white linear-gradient(rgba(255,255,255,0.2) 0, rgba(198,225,256,0.2) 18px, rgba(27,113,177,0.5) 100%);
+}
+
+input[type="checkbox"]:focus,
+input[type="radio"]:focus {
+ border-color: #99c6e0 !important;
+}
+
+/* we need to be specific for selects because the above rules are specific too */
+textarea[disabled],
+select[size][disabled],
+select[multiple][disabled],
+select[size][multiple][disabled],
+select:not([size]):not([multiple])[disabled],
+select[size="0"][disabled],
+select[size="1"][disabled],
+button[disabled],
+* > input:not([type="image"])[disabled] {
+ color: rgba(0,0,0,0.3);
+ border-color: rgba(125,125,125,0.4);
+ border-style: solid;
+ border-width: 1px;
+ background-color: #f5f5f5;
+}
+
+select:not([size]):not([multiple])[disabled],
+select[size="0"][disabled],
+select[size="1"][disabled] {
+ background-color: #f5f5f5;
+}
+
+input[type="button"][disabled],
+input[type="submit"][disabled],
+input[type="reset"][disabled],
+button[disabled] {
+ padding-inline-start: 7px;
+ padding-inline-end: 7px;
+ padding-block-start: 0;
+ padding-block-end: 0;
+ background-color: #f5f5f5;
+}
+
+input[type="radio"][disabled],
+input[type="radio"][disabled]:active,
+input[type="radio"][disabled]:hover,
+input[type="radio"][disabled]:hover:active,
+input[type="checkbox"][disabled],
+input[type="checkbox"][disabled]:active,
+input[type="checkbox"][disabled]:hover,
+input[type="checkbox"][disabled]:hover:active {
+ border:1px solid rgba(125,125,125,0.4) !important;
+}
+
+select[disabled] > button {
+ opacity: 0.6;
+ padding: 1px 7px 1px 7px;
+}
+
+*:any-link:active,
+*[role=button]:active,
+button:active,
+option:active,
+select:active,
+label:active {
+ background-color: rgba(141, 184, 216, 0.5);
+}
+
+input[type=number] > div > div, /* work around bug 946184 */
+input[type=number]::-moz-number-spin-box {
+ display: none;
+}
+%endif
+
+%ifdef MOZ_WIDGET_GONK
+/* This binding only provide key shortcuts that we can't use on devices */
+input,
+textarea {
+-moz-binding: none !important;
+}
+%endif
diff --git a/b2g/chrome/content/desktop.css b/b2g/chrome/content/desktop.css
new file mode 100644
index 0000000000..9612d732c3
--- /dev/null
+++ b/b2g/chrome/content/desktop.css
@@ -0,0 +1,59 @@
+#controls {
+ position: absolute;
+ left: 0;
+ bottom:0;
+ right: 0;
+ height: 30px;
+ background-color: -moz-dialog;
+}
+
+#home-button {
+ margin: auto;
+ margin-top: 3px;
+ width: 24px;
+ height: 24px;
+ background: #eee url("images/desktop/home-black.png") center no-repeat;
+ border: 1px solid #888;
+ border-radius: 12px;
+ display: block;
+}
+
+#home-button::-moz-focus-inner {
+ padding: 0;
+ border: 0;
+}
+
+#home-button:hover {
+ background-image: url("images/desktop/home-white.png");
+ background-color: #ccc;
+ border-color: #555;
+}
+
+#home-button.active {
+ background-image: url("images/desktop/home-white.png");
+ background-color: #888;
+ border-color: black;
+}
+
+#rotate-button {
+ position: absolute;
+ top: 3px;
+ bottom: 3px;
+ right: 3px;
+ width: 24px;
+ height: 24px;
+ background: #eee url("images/desktop/rotate.png") center no-repeat;
+ border: 1px solid #888;
+ border-radius: 12px;
+ display: block;
+}
+
+#rotate-button:hover {
+ background-color: #ccc;
+ border-color: #555;
+}
+
+#rotate-button.active {
+ background-color: #888;
+ border-color: black;
+}
diff --git a/b2g/chrome/content/desktop.js b/b2g/chrome/content/desktop.js
new file mode 100644
index 0000000000..5a1e7ff047
--- /dev/null
+++ b/b2g/chrome/content/desktop.js
@@ -0,0 +1,179 @@
+/* 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/. */
+
+var browserWindow = Services.wm.getMostRecentWindow("navigator:browser");
+var isMulet = "ResponsiveUI" in browserWindow;
+
+// Enable touch event shim on desktop that translates mouse events
+// into touch ones
+function enableTouch() {
+ let require = Cu.import('resource://devtools/shared/Loader.jsm', {})
+ .devtools.require;
+ let { TouchEventSimulator } = require('devtools/shared/touch/simulator');
+ let touchEventSimulator = new TouchEventSimulator(shell.contentBrowser);
+ touchEventSimulator.start();
+}
+
+// Some additional buttons are displayed on simulators to fake hardware buttons.
+function setupButtons() {
+ let link = document.createElement('link');
+ link.type = 'text/css';
+ link.rel = 'stylesheet';
+ link.href = 'chrome://b2g/content/desktop.css';
+ document.head.appendChild(link);
+
+ let footer = document.createElement('footer');
+ footer.id = 'controls';
+ document.body.appendChild(footer);
+ let homeButton = document.createElement('button');
+ homeButton.id = 'home-button';
+ footer.appendChild(homeButton);
+ let rotateButton = document.createElement('button');
+ rotateButton.id = 'rotate-button';
+ footer.appendChild(rotateButton);
+
+ homeButton.addEventListener('mousedown', function() {
+ let window = shell.contentBrowser.contentWindow;
+ let e = new window.KeyboardEvent('keydown', {key: 'Home'});
+ window.dispatchEvent(e);
+ homeButton.classList.add('active');
+ });
+ homeButton.addEventListener('mouseup', function() {
+ let window = shell.contentBrowser.contentWindow;
+ let e = new window.KeyboardEvent('keyup', {key: 'Home'});
+ window.dispatchEvent(e);
+ homeButton.classList.remove('active');
+ });
+
+ Cu.import("resource://gre/modules/GlobalSimulatorScreen.jsm");
+ rotateButton.addEventListener('mousedown', function() {
+ rotateButton.classList.add('active');
+ });
+ rotateButton.addEventListener('mouseup', function() {
+ GlobalSimulatorScreen.flipScreen();
+ rotateButton.classList.remove('active');
+ });
+}
+
+function setupStorage() {
+ let directory = null;
+
+ // Get the --storage-path argument from the command line.
+ try {
+ let service = Cc['@mozilla.org/commandlinehandler/general-startup;1?type=b2gcmds'].getService(Ci.nsISupports);
+ let args = service.wrappedJSObject.cmdLine;
+ if (args) {
+ let path = args.handleFlagWithParam('storage-path', false);
+ directory = Cc['@mozilla.org/file/local;1'].createInstance(Ci.nsIFile);
+ directory.initWithPath(path);
+ }
+ } catch(e) {
+ directory = null;
+ }
+
+ // Otherwise, default to 'storage' folder within current profile.
+ if (!directory) {
+ directory = Services.dirsvc.get('ProfD', Ci.nsIFile);
+ directory.append('storage');
+ if (!directory.exists()) {
+ directory.create(Ci.nsIFile.DIRECTORY_TYPE, parseInt("755", 8));
+ }
+ }
+ dump("Set storage path to: " + directory.path + "\n");
+
+ // This is the magic, where we override the default location for the storages.
+ Services.prefs.setCharPref('device.storage.overrideRootDir', directory.path);
+}
+
+function checkDebuggerPort() {
+ // XXX: To be removed once bug 942756 lands.
+ // We are hacking 'unix-domain-socket' pref by setting a tcp port (number).
+ // SocketListener.open detects that it isn't a file path (string), and starts
+ // listening on the tcp port given here as command line argument.
+
+ // Get the command line arguments that were passed to the b2g client
+ let args;
+ try {
+ let service = Cc["@mozilla.org/commandlinehandler/general-startup;1?type=b2gcmds"].getService(Ci.nsISupports);
+ args = service.wrappedJSObject.cmdLine;
+ } catch(e) {}
+
+ if (!args) {
+ return;
+ }
+
+ let dbgport;
+ try {
+ dbgport = args.handleFlagWithParam('start-debugger-server', false);
+ } catch(e) {}
+
+ if (dbgport) {
+ dump('Opening debugger server on ' + dbgport + '\n');
+ Services.prefs.setCharPref('devtools.debugger.unix-domain-socket', dbgport);
+ navigator.mozSettings.createLock().set(
+ {'debugger.remote-mode': 'adb-devtools'});
+ }
+}
+
+
+function initResponsiveDesign() {
+ Cu.import('resource://devtools/client/responsivedesign/responsivedesign.jsm');
+ ResponsiveUIManager.on('on', function(event, {tab:tab}) {
+ let responsive = ResponsiveUIManager.getResponsiveUIForTab(tab);
+ let document = tab.ownerDocument;
+
+ // Only tweak reponsive mode for shell.html tabs.
+ if (tab.linkedBrowser.contentWindow != window) {
+ return;
+ }
+
+ // Disable transition as they mess up with screen size handler
+ responsive.transitionsEnabled = false;
+
+ responsive.buildPhoneUI();
+
+ responsive.rotatebutton.addEventListener('command', function (evt) {
+ GlobalSimulatorScreen.flipScreen();
+ evt.stopImmediatePropagation();
+ evt.preventDefault();
+ }, true);
+
+ // Enable touch events
+ responsive.enableTouch();
+ });
+
+
+ let mgr = browserWindow.ResponsiveUI.ResponsiveUIManager;
+ mgr.toggle(browserWindow, browserWindow.gBrowser.selectedTab);
+
+}
+
+function openDevtools() {
+ // Open devtool panel while maximizing its size according to screen size
+ Services.prefs.setIntPref('devtools.toolbox.sidebar.width',
+ browserWindow.outerWidth - 550);
+ Services.prefs.setCharPref('devtools.toolbox.host', 'side');
+ let {gDevTools} = Cu.import('resource://devtools/client/framework/gDevTools.jsm', {});
+ let {devtools} = Cu.import("resource://devtools/shared/Loader.jsm", {});
+ let target = devtools.TargetFactory.forTab(browserWindow.gBrowser.selectedTab);
+ gDevTools.showToolbox(target);
+}
+
+window.addEventListener('ContentStart', function() {
+ // On Firefox Mulet, touch events are enabled within the responsive mode
+ if (!isMulet) {
+ enableTouch();
+ }
+ if (Services.prefs.getBoolPref('b2g.software-buttons')) {
+ setupButtons();
+ }
+ checkDebuggerPort();
+ setupStorage();
+ // On Firefox mulet, we automagically enable the responsive mode
+ // and show the devtools
+ if (isMulet) {
+ initResponsiveDesign(browserWindow);
+ openDevtools();
+ }
+});
diff --git a/b2g/chrome/content/devtools/adb.js b/b2g/chrome/content/devtools/adb.js
new file mode 100644
index 0000000000..cebc6696b9
--- /dev/null
+++ b/b2g/chrome/content/devtools/adb.js
@@ -0,0 +1,233 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- /
+/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
+/* 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";
+
+// This file is only loaded on Gonk to manage ADB state
+
+Components.utils.import("resource://gre/modules/FileUtils.jsm");
+
+const DEBUG = false;
+var debug = function(str) {
+ dump("AdbController: " + str + "\n");
+}
+
+var AdbController = {
+ locked: undefined,
+ remoteDebuggerEnabled: undefined,
+ lockEnabled: undefined,
+ disableAdbTimer: null,
+ disableAdbTimeoutHours: 12,
+ umsActive: false,
+
+ setLockscreenEnabled: function(value) {
+ this.lockEnabled = value;
+ DEBUG && debug("setLockscreenEnabled = " + this.lockEnabled);
+ this.updateState();
+ },
+
+ setLockscreenState: function(value) {
+ this.locked = value;
+ DEBUG && debug("setLockscreenState = " + this.locked);
+ this.updateState();
+ },
+
+ setRemoteDebuggerState: function(value) {
+ this.remoteDebuggerEnabled = value;
+ DEBUG && debug("setRemoteDebuggerState = " + this.remoteDebuggerEnabled);
+ this.updateState();
+ },
+
+ startDisableAdbTimer: function() {
+ if (this.disableAdbTimer) {
+ this.disableAdbTimer.cancel();
+ } else {
+ this.disableAdbTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
+ try {
+ this.disableAdbTimeoutHours =
+ Services.prefs.getIntPref("b2g.adb.timeout-hours");
+ } catch (e) {
+ // This happens if the pref doesn't exist, in which case
+ // disableAdbTimeoutHours will still be set to the default.
+ }
+ }
+ if (this.disableAdbTimeoutHours <= 0) {
+ DEBUG && debug("Timer to disable ADB not started due to zero timeout");
+ return;
+ }
+
+ DEBUG && debug("Starting timer to disable ADB in " +
+ this.disableAdbTimeoutHours + " hours");
+ let timeoutMilliseconds = this.disableAdbTimeoutHours * 60 * 60 * 1000;
+ this.disableAdbTimer.initWithCallback(this, timeoutMilliseconds,
+ Ci.nsITimer.TYPE_ONE_SHOT);
+ },
+
+ stopDisableAdbTimer: function() {
+ DEBUG && debug("Stopping timer to disable ADB");
+ if (this.disableAdbTimer) {
+ this.disableAdbTimer.cancel();
+ this.disableAdbTimer = null;
+ }
+ },
+
+ notify: function(aTimer) {
+ if (aTimer == this.disableAdbTimer) {
+ this.disableAdbTimer = null;
+ // The following dump will be the last thing that shows up in logcat,
+ // and will at least give the user a clue about why logcat was
+ // disconnected, if the user happens to be using logcat.
+ debug("ADB timer expired - disabling ADB\n");
+ navigator.mozSettings.createLock().set(
+ {'debugger.remote-mode': 'disabled'});
+ }
+ },
+
+ updateState: function() {
+ this.umsActive = false;
+ },
+
+ updateStateInternal: function() {
+ DEBUG && debug("updateStateInternal: called");
+
+ if (this.remoteDebuggerEnabled === undefined ||
+ this.lockEnabled === undefined ||
+ this.locked === undefined) {
+ // Part of initializing the settings database will cause the observers
+ // to trigger. We want to wait until both have been initialized before
+ // we start changing ther adb state. Without this then we can wind up
+ // toggling adb off and back on again (or on and back off again).
+ //
+ // For completeness, one scenario which toggles adb is using the unagi.
+ // The unagi has adb enabled by default (prior to b2g starting). If you
+ // have the phone lock disabled and remote debugging enabled, then we'll
+ // receive an unlock event and an rde event. However at the time we
+ // receive the unlock event we haven't yet received the rde event, so
+ // we turn adb off momentarily, which disconnects a logcat that might
+ // be running. Changing the defaults (in AdbController) just moves the
+ // problem to a different phone, which has adb disabled by default and
+ // we wind up turning on adb for a short period when we shouldn't.
+ //
+ // By waiting until both values are properly initialized, we avoid
+ // turning adb on or off accidentally.
+ DEBUG && debug("updateState: Waiting for all vars to be initialized");
+ return;
+ }
+
+ // Check if we have a remote debugging session going on. If so, we won't
+ // disable adb even if the screen is locked.
+ let isDebugging = USBRemoteDebugger.isDebugging;
+ DEBUG && debug("isDebugging=" + isDebugging);
+
+ // If USB Mass Storage, USB tethering, or a debug session is active,
+ // then we don't want to disable adb in an automatic fashion (i.e.
+ // when the screen locks or due to timeout).
+ let sysUsbConfig = libcutils.property_get("sys.usb.config").split(",");
+ let usbFuncActive = this.umsActive || isDebugging;
+ usbFuncActive |= (sysUsbConfig.indexOf("rndis") >= 0);
+ usbFuncActive |= (sysUsbConfig.indexOf("mtp") >= 0);
+
+ let enableAdb = this.remoteDebuggerEnabled &&
+ (!(this.lockEnabled && this.locked) || usbFuncActive);
+
+ let useDisableAdbTimer = true;
+ try {
+ if (Services.prefs.getBoolPref("marionette.defaultPrefs.enabled")) {
+ // Marionette is enabled. Marionette requires that adb be on (and also
+ // requires that remote debugging be off). The fact that marionette
+ // is enabled also implies that we're doing a non-production build, so
+ // we want adb enabled all of the time.
+ enableAdb = true;
+ useDisableAdbTimer = false;
+ }
+ } catch (e) {
+ // This means that the pref doesn't exist. Which is fine. We just leave
+ // enableAdb alone.
+ }
+
+ // Check wakelock to prevent adb from disconnecting when phone is locked
+ let lockFile = Cc['@mozilla.org/file/local;1'].createInstance(Ci.nsIFile);
+ lockFile.initWithPath('/sys/power/wake_lock');
+ if(lockFile.exists()) {
+ let foStream = Cc["@mozilla.org/network/file-input-stream;1"]
+ .createInstance(Ci.nsIFileInputStream);
+ let coStream = Cc["@mozilla.org/intl/converter-input-stream;1"]
+ .createInstance(Ci.nsIConverterInputStream);
+ let str = {};
+ foStream.init(lockFile, FileUtils.MODE_RDONLY, 0, 0);
+ coStream.init(foStream, "UTF-8", 0, 0);
+ coStream.readString(-1, str);
+ coStream.close();
+ foStream.close();
+ let wakeLockContents = str.value.replace(/\n/, "");
+ let wakeLockList = wakeLockContents.split(" ");
+ if (wakeLockList.indexOf("adb") >= 0) {
+ enableAdb = true;
+ useDisableAdbTimer = false;
+ DEBUG && debug("Keeping ADB enabled as ADB wakelock is present.");
+ } else {
+ DEBUG && debug("ADB wakelock not found.");
+ }
+ } else {
+ DEBUG && debug("Wake_lock file not found.");
+ }
+
+ DEBUG && debug("updateState: enableAdb = " + enableAdb +
+ " remoteDebuggerEnabled = " + this.remoteDebuggerEnabled +
+ " lockEnabled = " + this.lockEnabled +
+ " locked = " + this.locked +
+ " usbFuncActive = " + usbFuncActive);
+
+ // Configure adb.
+ let currentConfig = libcutils.property_get("persist.sys.usb.config");
+ let configFuncs = currentConfig.split(",");
+ if (currentConfig == "" || currentConfig == "none") {
+ // We want to treat none like the empty string.
+ // "".split(",") yields [""] and not []
+ configFuncs = [];
+ }
+ let adbIndex = configFuncs.indexOf("adb");
+
+ if (enableAdb) {
+ // Add adb to the list of functions, if not already present
+ if (adbIndex < 0) {
+ configFuncs.push("adb");
+ }
+ } else {
+ // Remove adb from the list of functions, if present
+ if (adbIndex >= 0) {
+ configFuncs.splice(adbIndex, 1);
+ }
+ }
+ let newConfig = configFuncs.join(",");
+ if (newConfig == "") {
+ // Convert the empty string back into none, since that's what init.rc
+ // needs.
+ newConfig = "none";
+ }
+ if (newConfig != currentConfig) {
+ DEBUG && debug("updateState: currentConfig = " + currentConfig);
+ DEBUG && debug("updateState: newConfig = " + newConfig);
+ try {
+ libcutils.property_set("persist.sys.usb.config", newConfig);
+ } catch(e) {
+ Cu.reportError("Error configuring adb: " + e);
+ }
+ }
+ if (useDisableAdbTimer) {
+ if (enableAdb && !usbFuncActive) {
+ this.startDisableAdbTimer();
+ } else {
+ this.stopDisableAdbTimer();
+ }
+ }
+ }
+};
+
+SettingsListener.observe("lockscreen.locked", false,
+ AdbController.setLockscreenState.bind(AdbController));
+SettingsListener.observe("lockscreen.enabled", false,
+ AdbController.setLockscreenEnabled.bind(AdbController));
diff --git a/b2g/chrome/content/devtools/debugger.js b/b2g/chrome/content/devtools/debugger.js
new file mode 100644
index 0000000000..11987a839b
--- /dev/null
+++ b/b2g/chrome/content/devtools/debugger.js
@@ -0,0 +1,397 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- /
+/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
+/* 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";
+
+XPCOMUtils.defineLazyGetter(this, "devtools", function() {
+ const { devtools } =
+ Cu.import("resource://devtools/shared/Loader.jsm", {});
+ return devtools;
+});
+
+XPCOMUtils.defineLazyGetter(this, "DebuggerServer", function() {
+ const { DebuggerServer } = devtools.require("devtools/server/main");
+ return DebuggerServer;
+});
+
+XPCOMUtils.defineLazyGetter(this, "B2GTabList", function() {
+ const { B2GTabList } =
+ devtools.require("resource://gre/modules/DebuggerActors.js");
+ return B2GTabList;
+});
+
+// Load the discovery module eagerly, so that it can set a device name at
+// startup. This does not cause discovery to start listening for packets, as
+// that only happens once DevTools is enabled.
+devtools.require("devtools/shared/discovery/discovery");
+
+var RemoteDebugger = {
+ _listening: false,
+
+ /**
+ * Prompt the user to accept or decline the incoming connection.
+ *
+ * @param session object
+ * The session object will contain at least the following fields:
+ * {
+ * authentication,
+ * client: {
+ * host,
+ * port
+ * },
+ * server: {
+ * host,
+ * port
+ * }
+ * }
+ * Specific authentication modes may include additional fields. Check
+ * the different |allowConnection| methods in
+ * devtools/shared/security/auth.js.
+ * @return An AuthenticationResult value.
+ * A promise that will be resolved to the above is also allowed.
+ */
+ allowConnection(session) {
+ if (this._promptingForAllow) {
+ // Don't stack connection prompts if one is already open
+ return DebuggerServer.AuthenticationResult.DENY;
+ }
+ this._listen();
+
+ this._promptingForAllow = new Promise(resolve => {
+ this._handleAllowResult = detail => {
+ this._handleAllowResult = null;
+ this._promptingForAllow = null;
+ // Newer Gaia supplies |authResult|, which is one of the
+ // AuthenticationResult values.
+ if (detail.authResult) {
+ resolve(detail.authResult);
+ } else if (detail.value) {
+ resolve(DebuggerServer.AuthenticationResult.ALLOW);
+ } else {
+ resolve(DebuggerServer.AuthenticationResult.DENY);
+ }
+ };
+
+ shell.sendChromeEvent({
+ type: "remote-debugger-prompt",
+ session
+ });
+ });
+
+ return this._promptingForAllow;
+ },
+
+ /**
+ * During OOB_CERT authentication, the user must transfer some data through some
+ * out of band mechanism from the client to the server to authenticate the
+ * devices.
+ *
+ * This implementation instructs Gaia to continually capture images which are
+ * passed back here and run through a QR decoder.
+ *
+ * @return An object containing:
+ * * sha256: hash(ClientCert)
+ * * k : K(random 128-bit number)
+ * A promise that will be resolved to the above is also allowed.
+ */
+ receiveOOB() {
+ if (this._receivingOOB) {
+ return this._receivingOOB;
+ }
+ this._listen();
+
+ const QR = devtools.require("devtools/shared/qrcode/index");
+ this._receivingOOB = new Promise((resolve, reject) => {
+ this._handleAuthEvent = detail => {
+ debug(detail.action);
+ if (detail.action === "abort") {
+ this._handleAuthEvent = null;
+ this._receivingOOB = null;
+ reject();
+ return;
+ }
+
+ if (detail.action !== "capture") {
+ return;
+ }
+
+ let url = detail.url;
+ QR.decodeFromURI(url).then(data => {
+ debug("Got auth data: " + data);
+ let oob = JSON.parse(data);
+
+ shell.sendChromeEvent({
+ type: "devtools-auth",
+ action: "stop"
+ });
+
+ this._handleAuthEvent = null;
+ this._receivingOOB = null;
+ resolve(oob);
+ }).catch(() => {
+ debug("No auth data, requesting new capture");
+ shell.sendChromeEvent({
+ type: "devtools-auth",
+ action: "capture"
+ });
+ });
+ };
+
+ // Show QR scanning dialog, get an initial capture
+ shell.sendChromeEvent({
+ type: "devtools-auth",
+ action: "start"
+ });
+ });
+
+ return this._receivingOOB;
+ },
+
+ _listen: function() {
+ if (this._listening) {
+ return;
+ }
+
+ this.handleEvent = this.handleEvent.bind(this);
+ let content = shell.contentBrowser.contentWindow;
+ content.addEventListener("mozContentEvent", this, false, true);
+ this._listening = true;
+ },
+
+ handleEvent: function(event) {
+ let detail = event.detail;
+ if (detail.type === "remote-debugger-prompt" && this._handleAllowResult) {
+ this._handleAllowResult(detail);
+ }
+ if (detail.type === "devtools-auth" && this._handleAuthEvent) {
+ this._handleAuthEvent(detail);
+ }
+ },
+
+ initServer: function() {
+ if (DebuggerServer.initialized) {
+ return;
+ }
+
+ // Ask for remote connections.
+ DebuggerServer.init();
+
+ // /!\ Be careful when adding a new actor, especially global actors.
+ // Any new global actor will be exposed and returned by the root actor.
+
+ // Add Firefox-specific actors, but prevent tab actors to be loaded in
+ // the parent process, unless we enable certified apps debugging.
+ let restrictPrivileges = Services.prefs.getBoolPref("devtools.debugger.forbid-certified-apps");
+ DebuggerServer.addBrowserActors("navigator:browser", restrictPrivileges);
+
+ // Allow debugging of chrome for any process
+ if (!restrictPrivileges) {
+ DebuggerServer.allowChromeProcess = true;
+ }
+
+ /**
+ * Construct a root actor appropriate for use in a server running in B2G.
+ * The returned root actor respects the factories registered with
+ * DebuggerServer.addGlobalActor only if certified apps debugging is on,
+ * otherwise we used an explicit limited list of global actors
+ *
+ * * @param connection DebuggerServerConnection
+ * The conection to the client.
+ */
+ DebuggerServer.createRootActor = function createRootActor(connection)
+ {
+ let parameters = {
+ tabList: new B2GTabList(connection),
+ // Use an explicit global actor list to prevent exposing
+ // unexpected actors
+ globalActorFactories: restrictPrivileges ? {
+ webappsActor: DebuggerServer.globalActorFactories.webappsActor,
+ deviceActor: DebuggerServer.globalActorFactories.deviceActor,
+ settingsActor: DebuggerServer.globalActorFactories.settingsActor
+ } : DebuggerServer.globalActorFactories
+ };
+ let { RootActor } = devtools.require("devtools/server/actors/root");
+ let root = new RootActor(connection, parameters);
+ root.applicationType = "operating-system";
+ return root;
+ };
+
+ if (isGonk) {
+ DebuggerServer.on("connectionchange", function() {
+ AdbController.updateState();
+ });
+ }
+ }
+};
+
+RemoteDebugger.allowConnection =
+ RemoteDebugger.allowConnection.bind(RemoteDebugger);
+RemoteDebugger.receiveOOB =
+ RemoteDebugger.receiveOOB.bind(RemoteDebugger);
+
+var USBRemoteDebugger = {
+
+ get isDebugging() {
+ if (!this._listener) {
+ return false;
+ }
+
+ return DebuggerServer._connections &&
+ Object.keys(DebuggerServer._connections).length > 0;
+ },
+
+ start: function() {
+ if (this._listener) {
+ return;
+ }
+
+ RemoteDebugger.initServer();
+
+ let portOrPath =
+ Services.prefs.getCharPref("devtools.debugger.unix-domain-socket") ||
+ "/data/local/debugger-socket";
+
+ try {
+ debug("Starting USB debugger on " + portOrPath);
+ let AuthenticatorType = DebuggerServer.Authenticators.get("PROMPT");
+ let authenticator = new AuthenticatorType.Server();
+ authenticator.allowConnection = RemoteDebugger.allowConnection;
+ this._listener = DebuggerServer.createListener();
+ this._listener.portOrPath = portOrPath;
+ this._listener.authenticator = authenticator;
+ this._listener.open();
+ // Temporary event, until bug 942756 lands and offers a way to know
+ // when the server is up and running.
+ Services.obs.notifyObservers(null, "debugger-server-started", null);
+ } catch (e) {
+ debug("Unable to start USB debugger server: " + e);
+ }
+ },
+
+ stop: function() {
+ if (!this._listener) {
+ return;
+ }
+
+ try {
+ this._listener.close();
+ this._listener = null;
+ } catch (e) {
+ debug("Unable to stop USB debugger server: " + e);
+ }
+ }
+
+};
+
+var WiFiRemoteDebugger = {
+
+ start: function() {
+ if (this._listener) {
+ return;
+ }
+
+ RemoteDebugger.initServer();
+
+ try {
+ debug("Starting WiFi debugger");
+ let AuthenticatorType = DebuggerServer.Authenticators.get("OOB_CERT");
+ let authenticator = new AuthenticatorType.Server();
+ authenticator.allowConnection = RemoteDebugger.allowConnection;
+ authenticator.receiveOOB = RemoteDebugger.receiveOOB;
+ this._listener = DebuggerServer.createListener();
+ this._listener.portOrPath = -1 /* any available port */;
+ this._listener.authenticator = authenticator;
+ this._listener.discoverable = true;
+ this._listener.encryption = true;
+ this._listener.open();
+ let port = this._listener.port;
+ debug("Started WiFi debugger on " + port);
+ } catch (e) {
+ debug("Unable to start WiFi debugger server: " + e);
+ }
+ },
+
+ stop: function() {
+ if (!this._listener) {
+ return;
+ }
+
+ try {
+ this._listener.close();
+ this._listener = null;
+ } catch (e) {
+ debug("Unable to stop WiFi debugger server: " + e);
+ }
+ }
+
+};
+
+(function() {
+ // Track these separately here so we can determine the correct value for the
+ // pref "devtools.debugger.remote-enabled", which is true when either mode of
+ // using DevTools is enabled.
+ let devtoolsUSB = false;
+ let devtoolsWiFi = false;
+
+ // Keep the old setting to not break people that won't have updated
+ // gaia and gecko.
+ SettingsListener.observe("devtools.debugger.remote-enabled", false,
+ function(value) {
+ devtoolsUSB = value;
+ Services.prefs.setBoolPref("devtools.debugger.remote-enabled",
+ devtoolsUSB || devtoolsWiFi);
+ // This preference is consulted during startup
+ Services.prefs.savePrefFile(null);
+ try {
+ value ? USBRemoteDebugger.start() : USBRemoteDebugger.stop();
+ } catch(e) {
+ dump("Error while initializing USB devtools: " +
+ e + "\n" + e.stack + "\n");
+ }
+ });
+
+ SettingsListener.observe("debugger.remote-mode", "disabled", function(value) {
+ if (["disabled", "adb-only", "adb-devtools"].indexOf(value) == -1) {
+ dump("Illegal value for debugger.remote-mode: " + value + "\n");
+ return;
+ }
+
+ devtoolsUSB = value == "adb-devtools";
+ Services.prefs.setBoolPref("devtools.debugger.remote-enabled",
+ devtoolsUSB || devtoolsWiFi);
+ // This preference is consulted during startup
+ Services.prefs.savePrefFile(null);
+
+ try {
+ (value == "adb-devtools") ? USBRemoteDebugger.start()
+ : USBRemoteDebugger.stop();
+ } catch(e) {
+ dump("Error while initializing USB devtools: " +
+ e + "\n" + e.stack + "\n");
+ }
+
+ isGonk && AdbController.setRemoteDebuggerState(value != "disabled");
+ });
+
+ SettingsListener.observe("devtools.remote.wifi.enabled", false,
+ function(value) {
+ devtoolsWiFi = value;
+ Services.prefs.setBoolPref("devtools.debugger.remote-enabled",
+ devtoolsUSB || devtoolsWiFi);
+ // Allow remote debugging on non-local interfaces when WiFi debug is enabled
+ // TODO: Bug 1034411: Lock down to WiFi interface, instead of all interfaces
+ Services.prefs.setBoolPref("devtools.debugger.force-local", !value);
+ // This preference is consulted during startup
+ Services.prefs.savePrefFile(null);
+
+ try {
+ value ? WiFiRemoteDebugger.start() : WiFiRemoteDebugger.stop();
+ } catch(e) {
+ dump("Error while initializing WiFi devtools: " +
+ e + "\n" + e.stack + "\n");
+ }
+ });
+})();
diff --git a/b2g/chrome/content/devtools/hud.js b/b2g/chrome/content/devtools/hud.js
new file mode 100644
index 0000000000..64e9d553d8
--- /dev/null
+++ b/b2g/chrome/content/devtools/hud.js
@@ -0,0 +1,1017 @@
+/* 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';
+
+// settings.js loads this file when the HUD setting is enabled.
+
+const DEVELOPER_HUD_LOG_PREFIX = 'DeveloperHUD';
+const CUSTOM_HISTOGRAM_PREFIX = 'DEVTOOLS_HUD_CUSTOM_';
+const APPNAME_IDX = 3;
+const HISTNAME_IDX = 4;
+
+XPCOMUtils.defineLazyGetter(this, 'devtools', function() {
+ const {devtools} = Cu.import('resource://devtools/shared/Loader.jsm', {});
+ return devtools;
+});
+
+XPCOMUtils.defineLazyGetter(this, 'DebuggerClient', function() {
+ return devtools.require('devtools/shared/client/main').DebuggerClient;
+});
+
+XPCOMUtils.defineLazyGetter(this, 'WebConsoleUtils', function() {
+ return devtools.require('devtools/shared/webconsole/utils').Utils;
+});
+
+XPCOMUtils.defineLazyGetter(this, 'EventLoopLagFront', function() {
+ return devtools.require('devtools/shared/fronts/eventlooplag').EventLoopLagFront;
+});
+
+XPCOMUtils.defineLazyGetter(this, 'PerformanceEntriesFront', function() {
+ return devtools.require('devtools/server/actors/performance-entries').PerformanceEntriesFront;
+});
+
+XPCOMUtils.defineLazyGetter(this, 'MemoryFront', function() {
+ return devtools.require('devtools/server/actors/memory').MemoryFront;
+});
+
+Cu.import('resource://gre/modules/Frames.jsm');
+
+var _telemetryDebug = false;
+
+function telemetryDebug(...args) {
+ if (_telemetryDebug) {
+ args.unshift('[AdvancedTelemetry]');
+ console.log(...args);
+ }
+}
+
+/**
+ * The Developer HUD is an on-device developer tool that displays widgets,
+ * showing visual debug information about apps. Each widget corresponds to a
+ * metric as tracked by a metric watcher (e.g. consoleWatcher).
+ */
+var developerHUD = {
+
+ _targets: new Map(),
+ _histograms: new Set(),
+ _customHistograms: new Set(),
+ _client: null,
+ _conn: null,
+ _watchers: [],
+ _logging: true,
+ _telemetry: false,
+
+ /**
+ * This method registers a metric watcher that will watch one or more metrics
+ * on app frames that are being tracked. A watcher must implement the
+ * `trackTarget(target)` and `untrackTarget(target)` methods, register
+ * observed metrics with `target.register(metric)`, and keep them up-to-date
+ * with `target.update(metric, message)` when necessary.
+ */
+ registerWatcher(watcher) {
+ this._watchers.unshift(watcher);
+ },
+
+ init() {
+ if (this._client) {
+ return;
+ }
+
+ if (!DebuggerServer.initialized) {
+ RemoteDebugger.initServer();
+ }
+
+ // We instantiate a local debugger connection so that watchers can use our
+ // DebuggerClient to send requests to tab actors (e.g. the consoleActor).
+ // Note the special usage of the private _serverConnection, which we need
+ // to call connectToChild and set up child process actors on a frame we
+ // intend to track. These actors will use the connection to communicate with
+ // our DebuggerServer in the parent process.
+ let transport = DebuggerServer.connectPipe();
+ this._conn = transport._serverConnection;
+ this._client = new DebuggerClient(transport);
+
+ for (let w of this._watchers) {
+ if (w.init) {
+ w.init(this._client);
+ }
+ }
+
+ Frames.addObserver(this);
+
+ let appFrames = Frames.list().filter(frame => frame.getAttribute('mozapp'));
+ for (let frame of appFrames) {
+ this.trackFrame(frame);
+ }
+
+ SettingsListener.observe('hud.logging', this._logging, enabled => {
+ this._logging = enabled;
+ });
+
+ SettingsListener.observe('hud.telemetry.logging', _telemetryDebug, enabled => {
+ _telemetryDebug = enabled;
+ });
+
+ SettingsListener.observe('metrics.selectedMetrics.level', "", level => {
+ this._telemetry = (level === 'Enhanced');
+ });
+ },
+
+ uninit() {
+ if (!this._client) {
+ return;
+ }
+
+ for (let frame of this._targets.keys()) {
+ this.untrackFrame(frame);
+ }
+
+ Frames.removeObserver(this);
+
+ this._client.close();
+ delete this._client;
+ },
+
+ /**
+ * This method will ask all registered watchers to track and update metrics
+ * on an app frame.
+ */
+ trackFrame(frame) {
+ if (this._targets.has(frame)) {
+ return;
+ }
+
+ DebuggerServer.connectToChild(this._conn, frame).then(actor => {
+ let target = new Target(frame, actor);
+ this._targets.set(frame, target);
+
+ for (let w of this._watchers) {
+ w.trackTarget(target);
+ }
+ });
+ },
+
+ untrackFrame(frame) {
+ let target = this._targets.get(frame);
+ if (target) {
+ for (let w of this._watchers) {
+ w.untrackTarget(target);
+ }
+
+ target.destroy();
+ this._targets.delete(frame);
+ }
+ },
+
+ onFrameCreated(frame, isFirstAppFrame) {
+ let mozapp = frame.getAttribute('mozapp');
+ if (!mozapp) {
+ return;
+ }
+ this.trackFrame(frame);
+ },
+
+ onFrameDestroyed(frame, isLastAppFrame) {
+ let mozapp = frame.getAttribute('mozapp');
+ if (!mozapp) {
+ return;
+ }
+ this.untrackFrame(frame);
+ },
+
+ log(message) {
+ if (this._logging) {
+ dump(DEVELOPER_HUD_LOG_PREFIX + ': ' + message + '\n');
+ }
+ }
+
+};
+
+
+/**
+ * A Target object represents all there is to know about a Firefox OS app frame
+ * that is being tracked, e.g. a pointer to the frame, current values of watched
+ * metrics, and how to notify the front-end when metrics have changed.
+ */
+function Target(frame, actor) {
+ this.frame = frame;
+ this.actor = actor;
+ this.metrics = new Map();
+ this._appName = null;
+}
+
+Target.prototype = {
+
+ get manifest() {
+ return this.frame.appManifestURL;
+ },
+
+ get appName() {
+
+ if (this._appName) {
+ return this._appName;
+ }
+
+ let manifest = this.manifest;
+ if (!manifest) {
+ let msg = DEVELOPER_HUD_LOG_PREFIX + ': Unable to determine app for telemetry metric. src: ' +
+ this.frame.src;
+ console.error(msg);
+ return null;
+ }
+
+ // "communications" apps are a special case
+ if (manifest.indexOf('communications') === -1) {
+ let start = manifest.indexOf('/') + 2;
+ let end = manifest.indexOf('.', start);
+ this._appName = manifest.substring(start, end).toLowerCase();
+ } else {
+ let src = this.frame.src;
+ if (src) {
+ // e.g., `app://communications.gaiamobile.org/contacts/index.html`
+ let parts = src.split('/');
+ let APP = 3;
+ let EXPECTED_PARTS_LENGTH = 5;
+ if (parts.length === EXPECTED_PARTS_LENGTH) {
+ this._appName = parts[APP];
+ }
+ }
+ }
+
+ return this._appName;
+ },
+
+ /**
+ * Register a metric that can later be updated. Does not update the front-end.
+ */
+ register(metric) {
+ this.metrics.set(metric, 0);
+ },
+
+ /**
+ * Modify one of a target's metrics, and send out an event to notify relevant
+ * parties (e.g. the developer HUD, automated tests, etc).
+ */
+ update(metric, message) {
+ if (!metric.name) {
+ throw new Error('Missing metric.name');
+ }
+
+ if (!metric.value) {
+ metric.value = 0;
+ }
+
+ let metrics = this.metrics;
+ if (metrics) {
+ metrics.set(metric.name, metric.value);
+ }
+
+ let data = {
+ metrics: [], // FIXME(Bug 982066) Remove this field.
+ manifest: this.manifest,
+ metric: metric,
+ message: message
+ };
+
+ // FIXME(Bug 982066) Remove this loop.
+ if (metrics && metrics.size > 0) {
+ for (let name of metrics.keys()) {
+ data.metrics.push({name: name, value: metrics.get(name)});
+ }
+ }
+
+ if (message) {
+ developerHUD.log('[' + data.manifest + '] ' + data.message);
+ }
+
+ this._send(data);
+ },
+
+ /**
+ * Nicer way to call update() when the metric value is a number that needs
+ * to be incremented.
+ */
+ bump(metric, message) {
+ metric.value = (this.metrics.get(metric.name) || 0) + 1;
+ this.update(metric, message);
+ },
+
+ /**
+ * Void a metric value and make sure it isn't displayed on the front-end
+ * anymore.
+ */
+ clear(metric) {
+ metric.value = 0;
+ this.update(metric);
+ },
+
+ /**
+ * Tear everything down, including the front-end by sending a message without
+ * widgets.
+ */
+ destroy() {
+ delete this.metrics;
+ this._send({metric: {skipTelemetry: true}});
+ },
+
+ _send(data) {
+ let frame = this.frame;
+
+ shell.sendEvent(frame, 'developer-hud-update', Cu.cloneInto(data, frame));
+ this._logHistogram(data.metric);
+ },
+
+ _getAddonHistogram(item) {
+ let appName = this._getAddonHistogramName(item, APPNAME_IDX);
+ let histName = this._getAddonHistogramName(item, HISTNAME_IDX);
+
+ return Services.telemetry.getAddonHistogram(appName, CUSTOM_HISTOGRAM_PREFIX
+ + histName);
+ },
+
+ _getAddonHistogramName(item, index) {
+ let array = item.split('_');
+ return array[index].toUpperCase();
+ },
+
+ _clearTelemetryData() {
+ developerHUD._histograms.forEach(function(item) {
+ Services.telemetry.getKeyedHistogramById(item).clear();
+ });
+
+ developerHUD._customHistograms.forEach(item => {
+ this._getAddonHistogram(item).clear();
+ });
+ },
+
+ _sendTelemetryData() {
+ if (!developerHUD._telemetry) {
+ return;
+ }
+ telemetryDebug('calling sendTelemetryData');
+ let frame = this.frame;
+ let payload = {
+ keyedHistograms: {},
+ addonHistograms: {}
+ };
+ // Package the hud histograms.
+ developerHUD._histograms.forEach(function(item) {
+ payload.keyedHistograms[item] =
+ Services.telemetry.getKeyedHistogramById(item).snapshot();
+ });
+
+ // Package the registered hud custom histograms
+ developerHUD._customHistograms.forEach(item => {
+ let appName = this._getAddonHistogramName(item, APPNAME_IDX);
+ let histName = CUSTOM_HISTOGRAM_PREFIX +
+ this._getAddonHistogramName(item, HISTNAME_IDX);
+ let addonHist = Services.telemetry.getAddonHistogram(appName, histName).snapshot();
+ if (!(appName in payload.addonHistograms)) {
+ payload.addonHistograms[appName] = {};
+ }
+ // Do not include histograms with sum of 0.
+ if (addonHist.sum > 0) {
+ payload.addonHistograms[appName][histName] = addonHist;
+ }
+ });
+ shell.sendEvent(frame, 'advanced-telemetry-update', Cu.cloneInto(payload, frame));
+ },
+
+ _logHistogram(metric) {
+ if (!developerHUD._telemetry || metric.skipTelemetry) {
+ return;
+ }
+
+ metric.appName = this.appName;
+ if (!metric.appName) {
+ return;
+ }
+
+ let metricName = metric.name.toUpperCase();
+ let metricAppName = metric.appName.toUpperCase();
+ if (!metric.custom) {
+ let keyedMetricName = 'DEVTOOLS_HUD_' + metricName;
+ try {
+ let keyed = Services.telemetry.getKeyedHistogramById(keyedMetricName);
+ if (keyed) {
+ keyed.add(metric.appName, parseInt(metric.value, 10));
+ developerHUD._histograms.add(keyedMetricName);
+ telemetryDebug(keyedMetricName, metric.value, metric.appName);
+ }
+ } catch(err) {
+ console.error('Histogram error is metricname added to histograms.json:'
+ + keyedMetricName);
+ }
+ } else {
+ let histogramName = CUSTOM_HISTOGRAM_PREFIX + metricAppName + '_'
+ + metricName;
+ // This is a call to add a value to an existing histogram.
+ if (typeof metric.value !== 'undefined') {
+ Services.telemetry.getAddonHistogram(metricAppName,
+ CUSTOM_HISTOGRAM_PREFIX + metricName).add(parseInt(metric.value, 10));
+ telemetryDebug(histogramName, metric.value);
+ return;
+ }
+
+ // The histogram already exists and are not adding data to it.
+ if (developerHUD._customHistograms.has(histogramName)) {
+ return;
+ }
+
+ // This is a call to create a new histogram.
+ try {
+ let metricType = parseInt(metric.type, 10);
+ if (metricType === Services.telemetry.HISTOGRAM_COUNT) {
+ Services.telemetry.registerAddonHistogram(metricAppName,
+ CUSTOM_HISTOGRAM_PREFIX + metricName, metricType);
+ } else {
+ Services.telemetry.registerAddonHistogram(metricAppName,
+ CUSTOM_HISTOGRAM_PREFIX + metricName, metricType, metric.min,
+ metric.max, metric.buckets);
+ }
+ developerHUD._customHistograms.add(histogramName);
+ } catch (err) {
+ console.error('Histogram error: ' + err);
+ }
+ }
+ }
+};
+
+
+/**
+ * The Console Watcher tracks the following metrics in apps: reflows, warnings,
+ * and errors, with security errors reported separately.
+ */
+var consoleWatcher = {
+
+ _client: null,
+ _targets: new Map(),
+ _watching: {
+ reflows: false,
+ warnings: false,
+ errors: false,
+ security: false
+ },
+ _security: [
+ 'Mixed Content Blocker',
+ 'Mixed Content Message',
+ 'CSP',
+ 'Invalid HSTS Headers',
+ 'Invalid HPKP Headers',
+ 'Insecure Password Field',
+ 'SSL',
+ 'CORS'
+ ],
+ _reflowThreshold: 0,
+
+ init(client) {
+ this._client = client;
+ this.consoleListener = this.consoleListener.bind(this);
+
+ let watching = this._watching;
+
+ for (let key in watching) {
+ let metric = key;
+ SettingsListener.observe('hud.' + metric, watching[metric], watch => {
+ // Watch or unwatch the metric.
+ if (watching[metric] = watch) {
+ return;
+ }
+
+ // If unwatched, remove any existing widgets for that metric.
+ for (let target of this._targets.values()) {
+ target.clear({name: metric});
+ }
+ });
+ }
+
+ SettingsListener.observe('hud.reflows.duration', this._reflowThreshold, threshold => {
+ this._reflowThreshold = threshold;
+ });
+
+ client.addListener('logMessage', this.consoleListener);
+ client.addListener('pageError', this.consoleListener);
+ client.addListener('consoleAPICall', this.consoleListener);
+ client.addListener('reflowActivity', this.consoleListener);
+ },
+
+ trackTarget(target) {
+ target.register('reflows');
+ target.register('warnings');
+ target.register('errors');
+ target.register('security');
+
+ this._client.request({
+ to: target.actor.consoleActor,
+ type: 'startListeners',
+ listeners: ['LogMessage', 'PageError', 'ConsoleAPI', 'ReflowActivity']
+ }, (res) => {
+ this._targets.set(target.actor.consoleActor, target);
+ });
+ },
+
+ untrackTarget(target) {
+ this._client.request({
+ to: target.actor.consoleActor,
+ type: 'stopListeners',
+ listeners: ['LogMessage', 'PageError', 'ConsoleAPI', 'ReflowActivity']
+ }, (res) => { });
+
+ this._targets.delete(target.actor.consoleActor);
+ },
+
+ consoleListener(type, packet) {
+ let target = this._targets.get(packet.from);
+ let metric = {};
+ let output = '';
+
+ switch (packet.type) {
+
+ case 'pageError':
+ let pageError = packet.pageError;
+
+ if (pageError.warning || pageError.strict) {
+ metric.name = 'warnings';
+ output += 'Warning (';
+ } else {
+ metric.name = 'errors';
+ output += 'Error (';
+ }
+
+ if (this._security.indexOf(pageError.category) > -1) {
+ metric.name = 'security';
+
+ // Telemetry sends the security error category not the
+ // count of security errors.
+ target._logHistogram({
+ name: 'security_category',
+ value: pageError.category
+ });
+
+ // Indicate that the 'hud' security metric (the count of security
+ // errors) should not be sent as a telemetry metric since the
+ // security error category is being sent instead.
+ metric.skipTelemetry = true;
+ }
+
+ let {errorMessage, sourceName, category, lineNumber, columnNumber} = pageError;
+ output += category + '): "' + (errorMessage.initial || errorMessage) +
+ '" in ' + sourceName + ':' + lineNumber + ':' + columnNumber;
+ break;
+
+ case 'consoleAPICall':
+ switch (packet.message.level) {
+
+ case 'error':
+ metric.name = 'errors';
+ output += 'Error (console)';
+ break;
+
+ case 'warn':
+ metric.name = 'warnings';
+ output += 'Warning (console)';
+ break;
+
+ case 'info':
+ this.handleTelemetryMessage(target, packet);
+
+ // Currently, informational log entries are tracked only by
+ // telemetry. Nonetheless, for consistency, we continue here
+ // and let the function return normally, when it concludes 'info'
+ // entries are not being watched.
+ metric.name = 'info';
+ break;
+
+ default:
+ return;
+ }
+ break;
+
+ case 'reflowActivity':
+ metric.name = 'reflows';
+
+ let {start, end, sourceURL, interruptible} = packet;
+ metric.interruptible = interruptible;
+ let duration = Math.round((end - start) * 100) / 100;
+
+ // Record the reflow if the duration exceeds the threshold.
+ if (duration < this._reflowThreshold) {
+ return;
+ }
+
+ output += 'Reflow: ' + duration + 'ms';
+ if (sourceURL) {
+ output += ' ' + this.formatSourceURL(packet);
+ }
+
+ // Telemetry also records reflow duration.
+ target._logHistogram({
+ name: 'reflow_duration',
+ value: Math.round(duration)
+ });
+ break;
+
+ default:
+ return;
+ }
+
+ if (developerHUD._telemetry) {
+ // Always record telemetry for these metrics.
+ if (metric.name === 'errors' || metric.name === 'warnings' || metric.name === 'reflows') {
+ let value = target.metrics.get(metric.name);
+ metric.value = (value || 0) + 1;
+ target._logHistogram(metric);
+
+ // Telemetry has already been recorded.
+ metric.skipTelemetry = true;
+
+ // If the metric is not being watched, persist the incremented value.
+ // If the metric is being watched, `target.bump` will increment the value
+ // of the metric and will persist the incremented value.
+ if (!this._watching[metric.name]) {
+ target.metrics.set(metric.name, metric.value);
+ }
+ }
+ }
+
+ if (!this._watching[metric.name]) {
+ return;
+ }
+
+ target.bump(metric, output);
+ },
+
+ formatSourceURL(packet) {
+ // Abbreviate source URL
+ let source = WebConsoleUtils.abbreviateSourceURL(packet.sourceURL);
+
+ // Add function name and line number
+ let {functionName, sourceLine} = packet;
+ source = 'in ' + (functionName || '<anonymousFunction>') +
+ ', ' + source + ':' + sourceLine;
+
+ return source;
+ },
+
+ handleTelemetryMessage(target, packet) {
+ if (!developerHUD._telemetry) {
+ return;
+ }
+
+ // If this is a 'telemetry' log entry, create a telemetry metric from
+ // the log content.
+ let separator = '|';
+ let logContent = packet.message.arguments.toString();
+
+ if (logContent.indexOf('telemetry') < 0) {
+ return;
+ }
+
+ let telemetryData = logContent.split(separator);
+
+ // Positions of the components of a telemetry log entry.
+ let TELEMETRY_IDENTIFIER_IDX = 0;
+ let NAME_IDX = 1;
+ let VALUE_IDX = 2;
+ let TYPE_IDX = 2;
+ let MIN_IDX = 3;
+ let MAX_IDX = 4;
+ let BUCKETS_IDX = 5;
+ let MAX_CUSTOM_ARGS = 6;
+ let MIN_CUSTOM_ARGS = 3;
+
+ if (telemetryData[TELEMETRY_IDENTIFIER_IDX] != 'telemetry' ||
+ telemetryData.length < MIN_CUSTOM_ARGS ||
+ telemetryData.length > MAX_CUSTOM_ARGS) {
+ return;
+ }
+
+ let metric = {
+ name: telemetryData[NAME_IDX]
+ };
+
+ if (metric.name === 'MGMT') {
+ metric.value = telemetryData[VALUE_IDX];
+ if (metric.value === 'TIMETOSHIP') {
+ telemetryDebug('Received a Ship event');
+ target._sendTelemetryData();
+ } else if (metric.value === 'CLEARMETRICS') {
+ target._clearTelemetryData();
+ }
+ } else {
+ if (telemetryData.length === MIN_CUSTOM_ARGS) {
+ metric.value = telemetryData[VALUE_IDX];
+ } else if (telemetryData.length === MAX_CUSTOM_ARGS) {
+ metric.type = telemetryData[TYPE_IDX];
+ metric.min = telemetryData[MIN_IDX];
+ metric.max = telemetryData[MAX_IDX];
+ metric.buckets = telemetryData[BUCKETS_IDX];
+ }
+ metric.custom = true;
+ target._logHistogram(metric);
+ }
+ }
+};
+developerHUD.registerWatcher(consoleWatcher);
+
+
+var eventLoopLagWatcher = {
+ _client: null,
+ _fronts: new Map(),
+ _active: false,
+
+ init(client) {
+ this._client = client;
+
+ SettingsListener.observe('hud.jank', false, this.settingsListener.bind(this));
+ },
+
+ settingsListener(value) {
+ if (this._active == value) {
+ return;
+ }
+
+ this._active = value;
+
+ // Toggle the state of existing fronts.
+ let fronts = this._fronts;
+ for (let target of fronts.keys()) {
+ if (value) {
+ fronts.get(target).start();
+ } else {
+ fronts.get(target).stop();
+ target.clear({name: 'jank'});
+ }
+ }
+ },
+
+ trackTarget(target) {
+ target.register('jank');
+
+ let front = new EventLoopLagFront(this._client, target.actor);
+ this._fronts.set(target, front);
+
+ front.on('event-loop-lag', time => {
+ target.update({name: 'jank', value: time}, 'Jank: ' + time + 'ms');
+ });
+
+ if (this._active) {
+ front.start();
+ }
+ },
+
+ untrackTarget(target) {
+ let fronts = this._fronts;
+ if (fronts.has(target)) {
+ fronts.get(target).destroy();
+ fronts.delete(target);
+ }
+ }
+};
+developerHUD.registerWatcher(eventLoopLagWatcher);
+
+/*
+ * The performanceEntriesWatcher determines the delta between the epoch
+ * of an app's launch time and the epoch of the app's performance entry marks.
+ * When it receives an "appLaunch" performance entry mark it records the
+ * name of the app being launched and the epoch of when the launch ocurred.
+ * When it receives subsequent performance entry events for the app being
+ * launched, it records the delta of the performance entry opoch compared
+ * to the app-launch epoch and emits an "app-start-time-<performance mark name>"
+ * event containing the delta.
+ *
+ * Additionally, while recording the "app-start-time" for a performance mark,
+ * USS memory at the time of the performance mark is also recorded.
+ */
+var performanceEntriesWatcher = {
+ _client: null,
+ _fronts: new Map(),
+ _appLaunch: new Map(),
+ _supported: [
+ 'contentInteractive',
+ 'navigationInteractive',
+ 'navigationLoaded',
+ 'visuallyLoaded',
+ 'fullyLoaded',
+ 'mediaEnumerated',
+ 'scanEnd'
+ ],
+
+ init(client) {
+ this._client = client;
+ let setting = 'devtools.telemetry.supported_performance_marks';
+ let defaultValue = this._supported.join(',');
+
+ SettingsListener.observe(setting, defaultValue, supported => {
+ this._supported = supported.split(',');
+ });
+ },
+
+ trackTarget(target) {
+ // The performanceEntries watcher doesn't register a metric because
+ // currently the metrics generated are not displayed in
+ // in the front-end.
+
+ let front = new PerformanceEntriesFront(this._client, target.actor);
+ this._fronts.set(target, front);
+
+ // User timings are always gathered; there is no setting to enable/
+ // disable.
+ front.start();
+
+ front.on('entry', detail => {
+
+ // Only process performance marks.
+ if (detail.type !== 'mark') {
+ return;
+ }
+
+ let name = detail.name;
+ let epoch = detail.epoch;
+
+ // If this is an "app launch" mark, record the app that was
+ // launched and the epoch of when it was launched.
+ if (name.indexOf('appLaunch') !== -1) {
+ let CHARS_UNTIL_APP_NAME = 7; // '@app://'
+ let startPos = name.indexOf('@app') + CHARS_UNTIL_APP_NAME;
+ let endPos = name.indexOf('.');
+ let appName = name.slice(startPos, endPos);
+ this._appLaunch.set(appName, epoch);
+ return;
+ }
+
+ // Only process supported performance marks
+ if (this._supported.indexOf(name) === -1) {
+ return;
+ }
+
+ let origin = detail.origin;
+ origin = origin.slice(0, origin.indexOf('.'));
+
+ let appLaunchTime = this._appLaunch.get(origin);
+
+ // Sanity check: ensure we have an app launch time for the app
+ // corresponding to this performance mark.
+ if (!appLaunchTime) {
+ return;
+ }
+
+ let time = epoch - appLaunchTime;
+ let eventName = 'app_startup_time_' + name;
+
+ // Events based on performance marks are for telemetry only, they are
+ // not displayed in the HUD front end.
+ target._logHistogram({name: eventName, value: time});
+
+ memoryWatcher.front(target).residentUnique().then(value => {
+ // bug 1215277, need 'v2' for app-memory histograms
+ eventName = 'app_memory_' + name + '_v2';
+ target._logHistogram({name: eventName, value: value});
+ }, err => {
+ console.error(err);
+ });
+ });
+ },
+
+ untrackTarget(target) {
+ let fronts = this._fronts;
+ if (fronts.has(target)) {
+ fronts.get(target).destroy();
+ fronts.delete(target);
+ }
+ }
+};
+developerHUD.registerWatcher(performanceEntriesWatcher);
+
+/**
+ * The Memory Watcher uses devtools actors to track memory usage.
+ */
+var memoryWatcher = {
+
+ _client: null,
+ _fronts: new Map(),
+ _timers: new Map(),
+ _watching: {
+ uss: false,
+ appmemory: false,
+ jsobjects: false,
+ jsstrings: false,
+ jsother: false,
+ dom: false,
+ style: false,
+ other: false
+ },
+ _active: false,
+
+ init(client) {
+ this._client = client;
+ let watching = this._watching;
+
+ for (let key in watching) {
+ let category = key;
+ SettingsListener.observe('hud.' + category, false, watch => {
+ watching[category] = watch;
+ this.update();
+ });
+ }
+ },
+
+ update() {
+ let watching = this._watching;
+ let active = watching.appmemory || watching.uss;
+
+ if (this._active) {
+ for (let target of this._fronts.keys()) {
+ if (!watching.appmemory) target.clear({name: 'memory'});
+ if (!watching.uss) target.clear({name: 'uss'});
+ if (!active) clearTimeout(this._timers.get(target));
+ }
+ } else if (active) {
+ for (let target of this._fronts.keys()) {
+ this.measure(target);
+ }
+ }
+ this._active = active;
+ },
+
+ measure(target) {
+ let watch = this._watching;
+ let format = this.formatMemory;
+
+ if (watch.uss) {
+ this.front(target).residentUnique().then(value => {
+ target.update({name: 'uss', value: value}, 'USS: ' + format(value));
+ }, err => {
+ console.error(err);
+ });
+ }
+
+ if (watch.appmemory) {
+ front.measure().then(data => {
+ let total = 0;
+ let details = [];
+
+ function item(name, condition, value) {
+ if (!condition) {
+ return;
+ }
+
+ let v = parseInt(value);
+ total += v;
+ details.push(name + ': ' + format(v));
+ }
+
+ item('JS objects', watch.jsobjects, data.jsObjectsSize);
+ item('JS strings', watch.jsstrings, data.jsStringsSize);
+ item('JS other', watch.jsother, data.jsOtherSize);
+ item('DOM', watch.dom, data.domSize);
+ item('Style', watch.style, data.styleSize);
+ item('Other', watch.other, data.otherSize);
+ // TODO Also count images size (bug #976007).
+
+ target.update({name: 'memory', value: total},
+ 'App Memory: ' + format(total) + ' (' + details.join(', ') + ')');
+ }, err => {
+ console.error(err);
+ });
+ }
+
+ let timer = setTimeout(() => this.measure(target), 2000);
+ this._timers.set(target, timer);
+ },
+
+ formatMemory(bytes) {
+ var prefix = ['','K','M','G','T','P','E','Z','Y'];
+ var i = 0;
+ for (; bytes > 1024 && i < prefix.length; ++i) {
+ bytes /= 1024;
+ }
+ return (Math.round(bytes * 100) / 100) + ' ' + prefix[i] + 'B';
+ },
+
+ trackTarget(target) {
+ target.register('uss');
+ target.register('memory');
+ this._fronts.set(target, MemoryFront(this._client, target.actor));
+ if (this._active) {
+ this.measure(target);
+ }
+ },
+
+ untrackTarget(target) {
+ let front = this._fronts.get(target);
+ if (front) {
+ front.destroy();
+ clearTimeout(this._timers.get(target));
+ this._fronts.delete(target);
+ this._timers.delete(target);
+ }
+ },
+
+ front(target) {
+ return this._fronts.get(target);
+ }
+};
+developerHUD.registerWatcher(memoryWatcher);
diff --git a/b2g/chrome/content/identity.js b/b2g/chrome/content/identity.js
new file mode 100644
index 0000000000..9c0ad50a2a
--- /dev/null
+++ b/b2g/chrome/content/identity.js
@@ -0,0 +1,166 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- /
+/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
+/* 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/. */
+
+// This JS shim contains the callbacks to fire DOMRequest events for
+// navigator.pay API within the payment processor's scope.
+
+"use strict";
+
+var { classes: Cc, interfaces: Ci, utils: Cu } = Components;
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+
+XPCOMUtils.defineLazyServiceGetter(this, "cpmm",
+ "@mozilla.org/childprocessmessagemanager;1",
+ "nsIMessageSender");
+
+XPCOMUtils.defineLazyServiceGetter(this, "uuidgen",
+ "@mozilla.org/uuid-generator;1",
+ "nsIUUIDGenerator");
+
+XPCOMUtils.defineLazyModuleGetter(this, "Logger",
+ "resource://gre/modules/identity/LogUtils.jsm");
+
+function log(...aMessageArgs) {
+ Logger.log.apply(Logger, ["injected identity.js"].concat(aMessageArgs));
+}
+
+log("\n\n======================= identity.js =======================\n\n");
+
+// This script may be injected more than once into an iframe.
+// It's hard to do this with |const| like we should, so use var instead.
+if (typeof kIdentityJSLoaded === 'undefined') {
+ var kIdentityDelegateWatch = "identity-delegate-watch";
+ var kIdentityDelegateRequest = "identity-delegate-request";
+ var kIdentityDelegateLogout = "identity-delegate-logout";
+ var kIdentityDelegateReady = "identity-delegate-ready";
+ var kIdentityDelegateFinished = "identity-delegate-finished";
+ var kIdentityControllerDoMethod = "identity-controller-doMethod";
+ var kIdentktyJSLoaded = true;
+}
+
+var showUI = false;
+var options = {};
+var isLoaded = false;
+var func = null;
+
+/*
+ * Message back to the SignInToWebsite pipe. Message should be an
+ * object with the following keys:
+ *
+ * method: one of 'login', 'logout', 'ready'
+ * assertion: optional assertion
+ */
+function identityCall(message) {
+ if (options._internal) {
+ message._internal = options._internal;
+ }
+ sendAsyncMessage(kIdentityControllerDoMethod, message);
+}
+
+/*
+ * To close the dialog, we first tell the gecko SignInToWebsite manager that it
+ * can clean up. Then we tell the gaia component that we are finished. It is
+ * necessary to notify gecko first, so that the message can be sent before gaia
+ * destroys our context.
+ */
+function closeIdentityDialog() {
+ // tell gecko we're done.
+ func = null; options = null;
+ sendAsyncMessage(kIdentityDelegateFinished);
+}
+
+/*
+ * doInternalWatch - call the internal.watch api and relay the results
+ * up to the controller.
+ */
+function doInternalWatch() {
+ log("doInternalWatch:", options, isLoaded);
+ if (options && isLoaded) {
+ let BrowserID = content.wrappedJSObject.BrowserID;
+ BrowserID.internal.watch(function(aParams, aInternalParams) {
+ identityCall(aParams);
+ if (aParams.method === "ready") {
+ closeIdentityDialog();
+ }
+ },
+ JSON.stringify(options),
+ function(...things) {
+ // internal watch log callback
+ log("(watch) internal: ", things);
+ }
+ );
+ }
+}
+
+function doInternalRequest() {
+ log("doInternalRequest:", options && isLoaded);
+ if (options && isLoaded) {
+ var stringifiedOptions = JSON.stringify(options);
+ content.wrappedJSObject.BrowserID.internal.get(
+ options.origin,
+ function(assertion, internalParams) {
+ internalParams = internalParams || {};
+ if (assertion) {
+ identityCall({
+ method: 'login',
+ assertion: assertion,
+ _internalParams: internalParams});
+ } else {
+ identityCall({
+ method: 'cancel'
+ });
+ }
+ closeIdentityDialog();
+ },
+ stringifiedOptions);
+ }
+}
+function doInternalLogout(aOptions) {
+ log("doInternalLogout:", (options && isLoaded));
+ if (options && isLoaded) {
+ let BrowserID = content.wrappedJSObject.BrowserID;
+ BrowserID.internal.logout(options.origin, function() {
+ identityCall({method:'logout'});
+ closeIdentityDialog();
+ });
+ }
+}
+
+addEventListener("DOMContentLoaded", function(e) {
+ content.addEventListener("load", function(e) {
+ isLoaded = true;
+ // bring da func
+ if (func) func();
+ });
+});
+
+// listen for request
+addMessageListener(kIdentityDelegateRequest, function(aMessage) {
+ log("injected identity.js received", kIdentityDelegateRequest);
+ options = aMessage.json;
+ showUI = true;
+ func = doInternalRequest;
+ func();
+});
+
+// listen for watch
+addMessageListener(kIdentityDelegateWatch, function(aMessage) {
+ log("injected identity.js received", kIdentityDelegateWatch);
+ options = aMessage.json;
+ showUI = false;
+ func = doInternalWatch;
+ func();
+});
+
+// listen for logout
+addMessageListener(kIdentityDelegateLogout, function(aMessage) {
+ log("injected identity.js received", kIdentityDelegateLogout);
+ options = aMessage.json;
+ showUI = false;
+ func = doInternalLogout;
+ func();
+});
diff --git a/b2g/chrome/content/images/arrowdown-16.png b/b2g/chrome/content/images/arrowdown-16.png
new file mode 100644
index 0000000000..c982426f2a
--- /dev/null
+++ b/b2g/chrome/content/images/arrowdown-16.png
Binary files differ
diff --git a/b2g/chrome/content/images/arrowright-16.png b/b2g/chrome/content/images/arrowright-16.png
new file mode 100644
index 0000000000..859e98ba64
--- /dev/null
+++ b/b2g/chrome/content/images/arrowright-16.png
Binary files differ
diff --git a/b2g/chrome/content/images/desktop/home-black.png b/b2g/chrome/content/images/desktop/home-black.png
new file mode 100644
index 0000000000..c51187ed49
--- /dev/null
+++ b/b2g/chrome/content/images/desktop/home-black.png
Binary files differ
diff --git a/b2g/chrome/content/images/desktop/home-white.png b/b2g/chrome/content/images/desktop/home-white.png
new file mode 100644
index 0000000000..43379d0e94
--- /dev/null
+++ b/b2g/chrome/content/images/desktop/home-white.png
Binary files differ
diff --git a/b2g/chrome/content/images/desktop/rotate.png b/b2g/chrome/content/images/desktop/rotate.png
new file mode 100644
index 0000000000..9da1b5674d
--- /dev/null
+++ b/b2g/chrome/content/images/desktop/rotate.png
Binary files differ
diff --git a/b2g/chrome/content/images/error.png b/b2g/chrome/content/images/error.png
new file mode 100644
index 0000000000..58e37283a7
--- /dev/null
+++ b/b2g/chrome/content/images/error.png
Binary files differ
diff --git a/b2g/chrome/content/images/errorpage-larry-black.png b/b2g/chrome/content/images/errorpage-larry-black.png
new file mode 100644
index 0000000000..9f2e4a6e73
--- /dev/null
+++ b/b2g/chrome/content/images/errorpage-larry-black.png
Binary files differ
diff --git a/b2g/chrome/content/images/errorpage-larry-white.png b/b2g/chrome/content/images/errorpage-larry-white.png
new file mode 100644
index 0000000000..fc153c7314
--- /dev/null
+++ b/b2g/chrome/content/images/errorpage-larry-white.png
Binary files differ
diff --git a/b2g/chrome/content/images/errorpage-warning.png b/b2g/chrome/content/images/errorpage-warning.png
new file mode 100644
index 0000000000..8bf9d8e7de
--- /dev/null
+++ b/b2g/chrome/content/images/errorpage-warning.png
Binary files differ
diff --git a/b2g/chrome/content/images/exitfullscreen-hdpi.png b/b2g/chrome/content/images/exitfullscreen-hdpi.png
new file mode 100644
index 0000000000..826e534084
--- /dev/null
+++ b/b2g/chrome/content/images/exitfullscreen-hdpi.png
Binary files differ
diff --git a/b2g/chrome/content/images/fullscreen-hdpi.png b/b2g/chrome/content/images/fullscreen-hdpi.png
new file mode 100644
index 0000000000..980e787310
--- /dev/null
+++ b/b2g/chrome/content/images/fullscreen-hdpi.png
Binary files differ
diff --git a/b2g/chrome/content/images/mute-hdpi.png b/b2g/chrome/content/images/mute-hdpi.png
new file mode 100644
index 0000000000..6daf7cf718
--- /dev/null
+++ b/b2g/chrome/content/images/mute-hdpi.png
Binary files differ
diff --git a/b2g/chrome/content/images/pause-hdpi.png b/b2g/chrome/content/images/pause-hdpi.png
new file mode 100644
index 0000000000..c7837f8229
--- /dev/null
+++ b/b2g/chrome/content/images/pause-hdpi.png
Binary files differ
diff --git a/b2g/chrome/content/images/play-hdpi.png b/b2g/chrome/content/images/play-hdpi.png
new file mode 100644
index 0000000000..fd64f96972
--- /dev/null
+++ b/b2g/chrome/content/images/play-hdpi.png
Binary files differ
diff --git a/b2g/chrome/content/images/scrubber-hdpi.png b/b2g/chrome/content/images/scrubber-hdpi.png
new file mode 100644
index 0000000000..b965b73d55
--- /dev/null
+++ b/b2g/chrome/content/images/scrubber-hdpi.png
Binary files differ
diff --git a/b2g/chrome/content/images/throbber.png b/b2g/chrome/content/images/throbber.png
new file mode 100644
index 0000000000..c601ec80ba
--- /dev/null
+++ b/b2g/chrome/content/images/throbber.png
Binary files differ
diff --git a/b2g/chrome/content/images/unmute-hdpi.png b/b2g/chrome/content/images/unmute-hdpi.png
new file mode 100644
index 0000000000..5de342bda5
--- /dev/null
+++ b/b2g/chrome/content/images/unmute-hdpi.png
Binary files differ
diff --git a/b2g/chrome/content/netError.css b/b2g/chrome/content/netError.css
new file mode 100644
index 0000000000..59d06a00c5
--- /dev/null
+++ b/b2g/chrome/content/netError.css
@@ -0,0 +1,131 @@
+/* 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/. */
+
+/*
+ * This defines the look-and-feel styling of the error pages.
+ * (see: netError.xhtml)
+ *
+ * Original styling by William Price <bugzilla@mob.rice.edu>
+ * Updated for mobile by: Wes Johnston <wjohnston@mozilla.com>
+ */
+
+body {
+ margin: 0;
+ padding: 0 8px 8px;
+ font-family: "Nokia Sans", Tahoma, sans-serif !important;
+}
+
+h1 {
+ font-size: 22px;
+}
+
+h2 {
+ font-size: 16px;
+}
+
+ul {
+ margin: 0px;
+ padding: 0px 0px 0px 1em;
+}
+
+li {
+ margin: 0px;
+ padding: 8px 0px;
+}
+
+#errorPage {
+ background-color: #CEE6F4;
+}
+
+#errorPage.certerror {
+ background-color: #EFD400;
+}
+
+#errorPage.blockedsite {
+ background-color: #BF0000;
+}
+
+#errorTitle {
+ background: url("chrome://b2g/content/images/errorpage-warning.png") left center no-repeat;
+ /* Scaled by .666 of their actual size */
+ background-size: 40px 40px;
+ background-origin: content-box;
+ min-height: 60px;
+ margin-left: auto;
+ margin-right: auto;
+ max-width: 500px;
+ margin-left: auto;
+ margin-right: auto;
+}
+
+#errorPage.certerror #errorTitle {
+ background-image: url("chrome://b2g/content/images/errorpage-larry-black.png");
+}
+
+#errorPage.blockedsite #errorTitle {
+ background-image: url("chrome://b2g/content/images/errorpage-larry-white.png");
+ color: white;
+}
+
+.errorTitleText {
+ padding: 0px 0px 0px 50px;
+ display: inline-block;
+ vertical-align: middle
+}
+
+#errorPageContainer {
+ background-color: white;
+ border: 1px solid #999999;
+ border-radius: 6px;
+ padding: 6px 20px 20px;
+ font-size: 14px;
+ max-width: 500px;
+ margin-left: auto;
+ margin-right: auto;
+}
+
+#errorShortDesc > p:empty {
+ display: none;
+}
+
+#errorShortDesc > p {
+ overflow: auto;
+ border-bottom: 1px solid #999999;
+ padding-bottom: 1em;
+}
+
+#errorPage.blockedsite #errorShortDesc > p {
+ font-weight: bold;
+ border-bottom: none;
+ padding-bottom: 0px;
+}
+
+#securityOverrideDiv {
+ padding-top: 10px;
+}
+
+div[collapsed] {
+ padding-left: 15px;
+ background-image: url("chrome://b2g/content/images/arrowright-16.png");
+ background-size: 11px 11px;
+ background-repeat: no-repeat;
+ background-position: left 0.3em;
+}
+
+div[collapsed="true"] {
+ background-image: url("chrome://b2g/content/images/arrowright-16.png");
+}
+
+div[collapsed="false"] {
+ background-image: url("chrome://b2g/content/images/arrowdown-16.png");
+}
+
+div[collapsed="true"] > p,
+div[collapsed="true"] > div {
+ display: none;
+}
+
+button {
+ padding: 0.3em !important;
+}
diff --git a/b2g/chrome/content/screen.js b/b2g/chrome/content/screen.js
new file mode 100644
index 0000000000..a893e88442
--- /dev/null
+++ b/b2g/chrome/content/screen.js
@@ -0,0 +1,276 @@
+// screen.js:
+// Set the screen size, pixel density and scaling of the b2g client screen
+// based on the --screen command-line option, if there is one.
+//
+// TODO: support multiple device pixels per CSS pixel
+//
+
+var browserWindow = Services.wm.getMostRecentWindow("navigator:browser");
+var isMulet = "ResponsiveUI" in browserWindow;
+Cu.import("resource://gre/modules/GlobalSimulatorScreen.jsm");
+
+window.addEventListener('ContentStart', onStart);
+window.addEventListener('SafeModeStart', onStart);
+
+// We do this on ContentStart and SafeModeStart because querying the
+// displayDPI fails otherwise.
+function onStart() {
+ // This is the toplevel <window> element
+ let shell = document.getElementById('shell');
+
+ // The <browser> element inside it
+ let browser = document.getElementById('systemapp');
+
+ // Figure out the native resolution of the screen
+ let windowUtils = window.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Components.interfaces.nsIDOMWindowUtils);
+ let hostDPI = windowUtils.displayDPI;
+
+ let DEFAULT_SCREEN = '320x480';
+
+ // This is a somewhat random selection of named screens.
+ // Add more to this list when we support more hardware.
+ // Data from: http://en.wikipedia.org/wiki/List_of_displays_by_pixel_density
+ let screens = {
+ iphone: {
+ name: 'Apple iPhone', width:320, height:480, dpi:163
+ },
+ ipad: {
+ name: 'Apple iPad', width:1024, height:768, dpi:132
+ },
+ nexus_s: {
+ name: 'Samsung Nexus S', width:480, height:800, dpi:235
+ },
+ galaxy_s2: {
+ name: 'Samsung Galaxy SII (I9100)', width:480, height:800, dpi:219
+ },
+ galaxy_nexus: {
+ name: 'Samsung Galaxy Nexus', width:720, height:1280, dpi:316
+ },
+ galaxy_tab: {
+ name: 'Samsung Galaxy Tab 10.1', width:800, height:1280, dpi:149
+ },
+ wildfire: {
+ name: 'HTC Wildfire', width:240, height:320, dpi:125
+ },
+ tattoo: {
+ name: 'HTC Tattoo', width:240, height:320, dpi:143
+ },
+ salsa: {
+ name: 'HTC Salsa', width:320, height:480, dpi:170
+ },
+ chacha: {
+ name: 'HTC ChaCha', width:320, height:480, dpi:222
+ },
+ };
+
+ // Get the command line arguments that were passed to the b2g client
+ let args;
+ try {
+ let service = Cc["@mozilla.org/commandlinehandler/general-startup;1?type=b2gcmds"].getService(Ci.nsISupports);
+ args = service.wrappedJSObject.cmdLine;
+ } catch(e) {}
+
+ let screenarg = null;
+
+ // Get the --screen argument from the command line
+ try {
+ if (args) {
+ screenarg = args.handleFlagWithParam('screen', false);
+ }
+
+ // Override default screen size with a pref
+ if (screenarg === null && Services.prefs.prefHasUserValue('b2g.screen.size')) {
+ screenarg = Services.prefs.getCharPref('b2g.screen.size');
+ }
+
+ // If there isn't one, use the default screen
+ if (screenarg === null)
+ screenarg = DEFAULT_SCREEN;
+
+ // With no value, tell the user how to use it
+ if (screenarg == '')
+ usage();
+ }
+ catch(e) {
+ // If getting the argument value fails, its an error
+ usage();
+ }
+
+ // Special case --screen=full goes into fullscreen mode
+ if (screenarg === 'full') {
+ shell.setAttribute('sizemode', 'fullscreen');
+ return;
+ }
+
+ let width, height, ratio = 1.0;
+ let lastResizedWidth;
+
+ if (screenarg in screens) {
+ // If this is a named screen, get its data
+ let screen = screens[screenarg];
+ width = screen.width;
+ height = screen.height;
+ ratio = screen.ratio;
+ } else {
+ // Otherwise, parse the resolution and density from the --screen value.
+ // The supported syntax is WIDTHxHEIGHT[@DPI]
+ let match = screenarg.match(/^(\d+)x(\d+)(@(\d+(\.\d+)?))?$/);
+
+ // Display usage information on syntax errors
+ if (match == null)
+ usage();
+
+ // Convert strings to integers
+ width = parseInt(match[1], 10);
+ height = parseInt(match[2], 10);
+ if (match[4])
+ ratio = parseFloat(match[4], 10);
+
+ // If any of the values came out 0 or NaN or undefined, display usage
+ if (!width || !height || !ratio) {
+ usage();
+ }
+ }
+
+ Services.prefs.setCharPref('layout.css.devPixelsPerPx',
+ ratio == 1 ? -1 : ratio);
+ let defaultOrientation = width < height ? 'portrait' : 'landscape';
+ GlobalSimulatorScreen.mozOrientation = GlobalSimulatorScreen.screenOrientation = defaultOrientation;
+
+ function resize() {
+ GlobalSimulatorScreen.width = width;
+ GlobalSimulatorScreen.height = height;
+
+ // Set the window width and height to desired size plus chrome
+ // Include the size of the toolbox displayed under the system app
+ let controls = document.getElementById('controls');
+ let controlsHeight = controls ? controls.getBoundingClientRect().height : 0;
+
+ if (isMulet) {
+ let tab = browserWindow.gBrowser.selectedTab;
+ let responsive = ResponsiveUIManager.getResponsiveUIForTab(tab);
+ responsive.setSize(width + 16*2,
+ height + controlsHeight + 61);
+ } else {
+ let chromewidth = window.outerWidth - window.innerWidth;
+ let chromeheight = window.outerHeight - window.innerHeight + controlsHeight;
+
+ if (lastResizedWidth == width) {
+ return;
+ }
+ lastResizedWidth = width;
+
+ window.resizeTo(width + chromewidth,
+ height + chromeheight);
+ }
+
+ let frameWidth = width, frameHeight = height;
+
+ // If the current app doesn't supports the current screen orientation
+ // still resize the window, but rotate its frame so that
+ // it is displayed rotated on the side
+ let shouldFlip = GlobalSimulatorScreen.mozOrientation != GlobalSimulatorScreen.screenOrientation;
+
+ if (shouldFlip) {
+ frameWidth = height;
+ frameHeight = width;
+ }
+
+ // Set the browser element to the full unscaled size of the screen
+ let style = browser.style;
+ style.transform = '';
+ style.height = 'calc(100% - ' + controlsHeight + 'px)';
+ style.bottom = controlsHeight;
+
+ style.width = frameWidth + "px";
+ style.height = frameHeight + "px";
+
+ if (shouldFlip) {
+ // Display the system app with a 90° clockwise rotation
+ let shift = Math.floor(Math.abs(frameWidth - frameHeight) / 2);
+ style.transform +=
+ ' rotate(0.25turn) translate(-' + shift + 'px, -' + shift + 'px)';
+ }
+ }
+
+ // Resize on startup
+ resize();
+
+ // Catch manual resizes to update the internal device size.
+ window.onresize = function() {
+ let controls = document.getElementById('controls');
+ let controlsHeight = controls ? controls.getBoundingClientRect().height : 0;
+
+ width = window.innerWidth;
+ height = window.innerHeight - controlsHeight;
+
+ queueResize();
+ };
+
+ // Then resize on each rotation button click,
+ // or when the system app lock/unlock the orientation
+ Services.obs.addObserver(function orientationChangeListener(subject) {
+ let screen = subject.wrappedJSObject;
+ let { mozOrientation, screenOrientation } = screen;
+
+ // If we have an orientation different than the current one,
+ // we switch the sizes
+ if (screenOrientation != defaultOrientation) {
+ let w = width;
+ width = height;
+ height = w;
+ }
+ defaultOrientation = screenOrientation;
+
+ queueResize();
+ }, 'simulator-adjust-window-size', false);
+
+ // Queue resize request in order to prevent race and slowdowns
+ // by requesting resize multiple times per loop
+ let resizeTimeout;
+ function queueResize() {
+ if (resizeTimeout) {
+ clearTimeout(resizeTimeout);
+ }
+ resizeTimeout = setTimeout(function () {
+ resizeTimeout = null;
+ resize();
+ }, 0);
+ }
+
+ // A utility function like console.log() for printing to the terminal window
+ // Uses dump(), but enables it first, if necessary
+ function print() {
+ let dump_enabled =
+ Services.prefs.getBoolPref('browser.dom.window.dump.enabled');
+
+ if (!dump_enabled)
+ Services.prefs.setBoolPref('browser.dom.window.dump.enabled', true);
+
+ dump(Array.prototype.join.call(arguments, ' ') + '\n');
+
+ if (!dump_enabled)
+ Services.prefs.setBoolPref('browser.dom.window.dump.enabled', false);
+ }
+
+ // Print usage info for --screen and exit
+ function usage() {
+ // Documentation for the --screen argument
+ let msg =
+ 'The --screen argument specifies the desired resolution and\n' +
+ 'pixel density of the simulated device screen. Use it like this:\n' +
+ '\t--screen=WIDTHxHEIGHT\t\t\t// E.g.: --screen=320x480\n' +
+ '\t--screen=WIDTHxHEIGHT@DOTS_PER_INCH\t// E.g.: --screen=480x800@250\n' +
+ '\t--screen=full\t\t\t\t// run in fullscreen mode\n' +
+ '\nYou can also specify certain device names:\n';
+ for(let p in screens)
+ msg += '\t--screen=' + p + '\t// ' + screens[p].name + '\n';
+
+ // Display the usage message
+ print(msg);
+
+ // Exit the b2g client
+ Services.startup.quit(Ci.nsIAppStartup.eAttemptQuit);
+ }
+}
diff --git a/b2g/chrome/content/settings.js b/b2g/chrome/content/settings.js
new file mode 100644
index 0000000000..95921da4c9
--- /dev/null
+++ b/b2g/chrome/content/settings.js
@@ -0,0 +1,698 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- /
+/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
+/* 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";
+
+window.performance.mark('gecko-settings-loadstart');
+
+var Cc = Components.classes;
+var Ci = Components.interfaces;
+var Cu = Components.utils;
+var Cr = Components.results;
+
+// The load order is important here SettingsRequestManager _must_ be loaded
+// prior to using SettingsListener otherwise there is a race in acquiring the
+// lock and fulfilling it. If we ever move SettingsListener or this file down in
+// the load order of shell.html things will likely break.
+Cu.import('resource://gre/modules/SettingsRequestManager.jsm');
+Cu.import('resource://gre/modules/XPCOMUtils.jsm');
+Cu.import('resource://gre/modules/Services.jsm');
+Cu.import('resource://gre/modules/AppConstants.jsm');
+
+const isGonk = AppConstants.platform === 'gonk';
+
+if (isGonk) {
+ XPCOMUtils.defineLazyGetter(this, "libcutils", function () {
+ Cu.import("resource://gre/modules/systemlibs.js");
+ return libcutils;
+ });
+}
+
+XPCOMUtils.defineLazyServiceGetter(this, "uuidgen",
+ "@mozilla.org/uuid-generator;1",
+ "nsIUUIDGenerator");
+
+// Once Bug 731746 - Allow chrome JS object to implement nsIDOMEventTarget
+// is resolved this helper could be removed.
+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;
+ }
+
+ 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();
+
+// =================== Mono Audio ======================
+
+SettingsListener.observe('accessibility.monoaudio.enable', false, function(value) {
+ Services.prefs.setBoolPref('accessibility.monoaudio.enable', value);
+});
+
+// =================== Console ======================
+
+SettingsListener.observe('debug.console.enabled', true, function(value) {
+ Services.prefs.setBoolPref('consoleservice.enabled', value);
+ Services.prefs.setBoolPref('layout.css.report_errors', value);
+});
+
+SettingsListener.observe('homescreen.manifestURL', 'Sentinel Value' , function(value) {
+ Services.prefs.setCharPref('dom.mozApps.homescreenURL', value);
+});
+
+// =================== Languages ====================
+SettingsListener.observe('language.current', 'en-US', function(value) {
+ Services.prefs.setCharPref('general.useragent.locale', value);
+
+ let prefName = 'intl.accept_languages';
+ let defaultBranch = Services.prefs.getDefaultBranch(null);
+
+ let intl = '';
+ try {
+ intl = defaultBranch.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);
+
+ if (shell.hasStarted() == false) {
+ shell.bootstrap();
+ }
+});
+
+// =================== RIL ====================
+(function RILSettingsToPrefs() {
+ // DSDS default service IDs
+ ['mms', 'sms', 'telephony'].forEach(function(key) {
+ SettingsListener.observe('ril.' + key + '.defaultServiceId', 0,
+ function(value) {
+ if (value != null) {
+ Services.prefs.setIntPref('dom.' + key + '.defaultServiceId', value);
+ }
+ });
+ });
+})();
+
+//=================== DeviceInfo ====================
+Components.utils.import('resource://gre/modules/XPCOMUtils.jsm');
+Components.utils.import('resource://gre/modules/ctypes.jsm');
+(function DeviceInfoToSettings() {
+ // MOZ_B2G_VERSION is set in b2g/confvars.sh, and is output as a #define value
+ // from configure.in, defaults to 1.0.0 if this value is not exist.
+ let os_version = AppConstants.MOZ_B2G_VERSION;
+ let os_name = AppConstants.MOZ_B2G_OS_NAME;
+
+ let appInfo = Cc["@mozilla.org/xre/app-info;1"]
+ .getService(Ci.nsIXULAppInfo);
+
+ // Get the hardware info and firmware revision from device properties.
+ let hardware_info = null;
+ let firmware_revision = null;
+ let product_manufacturer = null;
+ let product_model = null;
+ let product_device = null;
+ let build_number = null;
+ if (isGonk) {
+ hardware_info = libcutils.property_get('ro.hardware');
+ firmware_revision = libcutils.property_get('ro.firmware_revision');
+ product_manufacturer = libcutils.property_get('ro.product.manufacturer');
+ product_model = libcutils.property_get('ro.product.model');
+ product_device = libcutils.property_get('ro.product.device');
+ build_number = libcutils.property_get('ro.build.version.incremental');
+ }
+
+ // Populate deviceinfo settings,
+ // copying any existing deviceinfo.os into deviceinfo.previous_os
+ let lock = window.navigator.mozSettings.createLock();
+ let req = lock.get('deviceinfo.os');
+ req.onsuccess = req.onerror = () => {
+ let previous_os = req.result && req.result['deviceinfo.os'] || '';
+ let software = os_name + ' ' + os_version;
+ let setting = {
+ 'deviceinfo.build_number': build_number,
+ 'deviceinfo.os': os_version,
+ 'deviceinfo.previous_os': previous_os,
+ 'deviceinfo.software': software,
+ 'deviceinfo.platform_version': appInfo.platformVersion,
+ 'deviceinfo.platform_build_id': appInfo.platformBuildID,
+ 'deviceinfo.hardware': hardware_info,
+ 'deviceinfo.firmware_revision': firmware_revision,
+ 'deviceinfo.product_manufacturer': product_manufacturer,
+ 'deviceinfo.product_model': product_model,
+ 'deviceinfo.product_device': product_device
+ }
+ lock.set(setting);
+ }
+})();
+
+// =================== DevTools ====================
+
+var developerHUD;
+SettingsListener.observe('devtools.overlay', false, (value) => {
+ if (value) {
+ if (!developerHUD) {
+ let scope = {};
+ Services.scriptloader.loadSubScript('chrome://b2g/content/devtools/hud.js', scope);
+ developerHUD = scope.developerHUD;
+ }
+ developerHUD.init();
+ } else {
+ if (developerHUD) {
+ developerHUD.uninit();
+ }
+ }
+});
+
+if (isGonk) {
+ var LogShake;
+ (function() {
+ let scope = {};
+ Cu.import('resource://gre/modules/LogShake.jsm', scope);
+ LogShake = scope.LogShake;
+ LogShake.init();
+ })();
+
+ SettingsListener.observe('devtools.logshake.enabled', false, value => {
+ if (value) {
+ LogShake.enableDeviceMotionListener();
+ } else {
+ LogShake.disableDeviceMotionListener();
+ }
+ });
+
+ SettingsListener.observe('devtools.logshake.qa_enabled', false, value => {
+ if (value) {
+ LogShake.enableQAMode();
+ } else {
+ LogShake.disableQAMode();
+ }
+ });
+}
+
+// =================== Device Storage ====================
+SettingsListener.observe('device.storage.writable.name', 'sdcard', function(value) {
+ if (Services.prefs.getPrefType('device.storage.writable.name') != Ci.nsIPrefBranch.PREF_STRING) {
+ // We clear the pref because it used to be erroneously written as a bool
+ // and we need to clear it before we can change it to have the correct type.
+ Services.prefs.clearUserPref('device.storage.writable.name');
+ }
+ Services.prefs.setCharPref('device.storage.writable.name', value);
+});
+
+// =================== Privacy ====================
+SettingsListener.observe('privacy.donottrackheader.value', 1, function(value) {
+ Services.prefs.setIntPref('privacy.donottrackheader.value', value);
+ // If the user specifically disallows tracking, we set the value of
+ // app.update.custom (update tracking ID) to an empty string.
+ if (value == 1) {
+ Services.prefs.setCharPref('app.update.custom', '');
+ return;
+ }
+ // Otherwise, we assure that the update tracking ID exists.
+ setUpdateTrackingId();
+});
+
+// =================== Crash Reporting ====================
+SettingsListener.observe('app.reportCrashes', 'ask', function(value) {
+ if (value == 'always') {
+ Services.prefs.setBoolPref('app.reportCrashes', true);
+ } else if (value == 'never') {
+ Services.prefs.setBoolPref('app.reportCrashes', false);
+ } else {
+ Services.prefs.clearUserPref('app.reportCrashes');
+ }
+ // This preference is consulted during startup.
+ Services.prefs.savePrefFile(null);
+});
+
+// ================ Updates ================
+/**
+ * For tracking purposes some partners require us to add an UUID to the
+ * update URL. The update tracking ID will be an empty string if the
+ * do-not-track feature specifically disallows tracking and it is reseted
+ * to a different ID if the do-not-track value changes from disallow to allow.
+ */
+function setUpdateTrackingId() {
+ try {
+ let dntEnabled = Services.prefs.getBoolPref('privacy.donottrackheader.enabled');
+ let dntValue = Services.prefs.getIntPref('privacy.donottrackheader.value');
+ // If the user specifically decides to disallow tracking (1), we just bail out.
+ if (dntEnabled && (dntValue == 1)) {
+ return;
+ }
+
+ let trackingId =
+ Services.prefs.getPrefType('app.update.custom') ==
+ Ci.nsIPrefBranch.PREF_STRING &&
+ Services.prefs.getCharPref('app.update.custom');
+
+ // If there is no previous registered tracking ID, we generate a new one.
+ // This should only happen on first usage or after changing the
+ // do-not-track value from disallow to allow.
+ if (!trackingId) {
+ trackingId = uuidgen.generateUUID().toString().replace(/[{}]/g, "");
+ Services.prefs.setCharPref('app.update.custom', trackingId);
+ }
+ } catch(e) {
+ dump('Error getting tracking ID ' + e + '\n');
+ }
+}
+setUpdateTrackingId();
+
+(function syncUpdatePrefs() {
+ // The update service reads the prefs from the default branch. This is by
+ // design, as explained in bug 302721 comment 43. If we are to successfully
+ // modify them, that's where we need to make our changes.
+ let defaultBranch = Services.prefs.getDefaultBranch(null);
+
+ function syncPrefDefault(prefName) {
+ // The pref value at boot-time will serve as default for the setting.
+ let defaultValue = defaultBranch.getCharPref(prefName);
+ let defaultSetting = {};
+ defaultSetting[prefName] = defaultValue;
+
+ // We back up that value in order to detect pref changes across reboots.
+ // Such a change can happen e.g. when the user installs an OTA update that
+ // changes the update URL format.
+ let backupName = prefName + '.old';
+ try {
+ // Everything relies on the comparison below: When pushing a new Gecko
+ // that changes app.update.url or app.update.channel, we overwrite any
+ // existing setting with the new pref value.
+ let backupValue = Services.prefs.getCharPref(backupName);
+ if (defaultValue !== backupValue) {
+ // If the pref has changed since our last backup, overwrite the setting.
+ navigator.mozSettings.createLock().set(defaultSetting);
+ }
+ } catch(e) {
+ // There was no backup: Overwrite the setting and create a backup below.
+ navigator.mozSettings.createLock().set(defaultSetting);
+ }
+
+ // Initialize or update the backup value.
+ Services.prefs.setCharPref(backupName, defaultValue);
+
+ // Propagate setting changes to the pref.
+ SettingsListener.observe(prefName, defaultValue, value => {
+ if (!value) {
+ // If the setting value is invalid, reset it to its default.
+ navigator.mozSettings.createLock().set(defaultSetting);
+ return;
+ }
+ // Here we will overwrite the pref with the setting value.
+ defaultBranch.setCharPref(prefName, value);
+ });
+ }
+
+ syncPrefDefault('app.update.url');
+ syncPrefDefault('app.update.channel');
+})();
+
+// ================ Debug ================
+(function Composer2DSettingToPref() {
+ //layers.composer.enabled can be enabled in three ways
+ //In order of precedence they are:
+ //
+ //1. mozSettings "layers.composer.enabled"
+ //2. a gecko pref "layers.composer.enabled"
+ //3. presence of ro.display.colorfill at the Gonk level
+
+ var req = navigator.mozSettings.createLock().get('layers.composer2d.enabled');
+ req.onsuccess = function() {
+ if (typeof(req.result['layers.composer2d.enabled']) === 'undefined') {
+ var enabled = false;
+ if (Services.prefs.getPrefType('layers.composer2d.enabled') == Ci.nsIPrefBranch.PREF_BOOL) {
+ enabled = Services.prefs.getBoolPref('layers.composer2d.enabled');
+ } else if (isGonk) {
+ let androidVersion = libcutils.property_get("ro.build.version.sdk");
+ if (androidVersion >= 17 ) {
+ enabled = true;
+ } else {
+ enabled = (libcutils.property_get('ro.display.colorfill') === '1');
+ }
+ }
+ navigator.mozSettings.createLock().set({'layers.composer2d.enabled': enabled });
+ }
+
+ SettingsListener.observe("layers.composer2d.enabled", true, function(value) {
+ Services.prefs.setBoolPref("layers.composer2d.enabled", value);
+ });
+ };
+ req.onerror = function() {
+ dump("Error configuring layers.composer2d.enabled setting");
+ };
+
+})();
+
+// ================ Accessibility ============
+(function setupAccessibility() {
+ let accessibilityScope = {};
+ SettingsListener.observe("accessibility.screenreader", false, function(value) {
+ if (!value) {
+ return;
+ }
+ if (!('AccessFu' in accessibilityScope)) {
+ Cu.import('resource://gre/modules/accessibility/AccessFu.jsm',
+ accessibilityScope);
+ accessibilityScope.AccessFu.attach(window);
+ }
+ });
+})();
+
+// ================ Theming ============
+(function themingSettingsListener() {
+ let themingPrefs = ['ui.menu', 'ui.menutext', 'ui.infobackground', 'ui.infotext',
+ 'ui.window', 'ui.windowtext', 'ui.highlight'];
+
+ themingPrefs.forEach(function(pref) {
+ SettingsListener.observe('gaia.' + pref, null, function(value) {
+ if (value) {
+ Services.prefs.setCharPref(pref, value);
+ }
+ });
+ });
+})();
+
+// =================== Telemetry ======================
+(function setupTelemetrySettings() {
+ let gaiaSettingName = 'debug.performance_data.shared';
+ let geckoPrefName = 'toolkit.telemetry.enabled';
+ SettingsListener.observe(gaiaSettingName, null, function(value) {
+ if (value !== null) {
+ // Gaia setting has been set; update Gecko pref to that.
+ Services.prefs.setBoolPref(geckoPrefName, value);
+ return;
+ }
+ // Gaia setting has not been set; set the gaia setting to default.
+ let prefValue = AppConstants.MOZ_TELEMETRY_ON_BY_DEFAULT;
+ try {
+ prefValue = Services.prefs.getBoolPref(geckoPrefName);
+ } catch (e) {
+ // Pref not set; use default value.
+ }
+ let setting = {};
+ setting[gaiaSettingName] = prefValue;
+ window.navigator.mozSettings.createLock().set(setting);
+ });
+})();
+
+// =================== Low-precision buffer ======================
+(function setupLowPrecisionSettings() {
+ // The gaia setting layers.low-precision maps to two gecko prefs
+ SettingsListener.observe('layers.low-precision', null, function(value) {
+ if (value !== null) {
+ // Update gecko from the new Gaia setting
+ Services.prefs.setBoolPref('layers.low-precision-buffer', value);
+ Services.prefs.setBoolPref('layers.progressive-paint', value);
+ } else {
+ // Update gaia setting from gecko value
+ try {
+ let prefValue = Services.prefs.getBoolPref('layers.low-precision-buffer');
+ let setting = { 'layers.low-precision': prefValue };
+ window.navigator.mozSettings.createLock().set(setting);
+ } catch (e) {
+ console.log('Unable to read pref layers.low-precision-buffer: ' + e);
+ }
+ }
+ });
+
+ // The gaia setting layers.low-opacity maps to a string gecko pref (0.5/1.0)
+ SettingsListener.observe('layers.low-opacity', null, function(value) {
+ if (value !== null) {
+ // Update gecko from the new Gaia setting
+ Services.prefs.setCharPref('layers.low-precision-opacity', value ? '0.5' : '1.0');
+ } else {
+ // Update gaia setting from gecko value
+ try {
+ let prefValue = Services.prefs.getCharPref('layers.low-precision-opacity');
+ let setting = { 'layers.low-opacity': (prefValue == '0.5') };
+ window.navigator.mozSettings.createLock().set(setting);
+ } catch (e) {
+ console.log('Unable to read pref layers.low-precision-opacity: ' + e);
+ }
+ }
+ });
+})();
+
+// ======================= Dogfooders FOTA ==========================
+if (AppConstants.MOZ_B2G_RIL) {
+ XPCOMUtils.defineLazyModuleGetter(this, "AppsUtils",
+ "resource://gre/modules/AppsUtils.jsm");
+
+ SettingsListener.observe('debug.performance_data.dogfooding', false,
+ isDogfooder => {
+ if (!isDogfooder) {
+ dump('AUS:Settings: Not a dogfooder!\n');
+ return;
+ }
+
+ if (!('mozTelephony' in navigator)) {
+ dump('AUS:Settings: There is no mozTelephony!\n');
+ return;
+ }
+
+ if (!('mozMobileConnections' in navigator)) {
+ dump('AUS:Settings: There is no mozMobileConnections!\n');
+ return;
+ }
+
+ let conn = navigator.mozMobileConnections[0];
+ conn.addEventListener('radiostatechange', function onradiostatechange() {
+ if (conn.radioState !== 'enabled') {
+ return;
+ }
+
+ conn.removeEventListener('radiostatechange', onradiostatechange);
+ navigator.mozTelephony.dial('*#06#').then(call => {
+ return call.result.then(res => {
+ if (res.success && res.statusMessage
+ && (res.serviceCode === 'scImei')) {
+ Services.prefs.setCharPref("app.update.imei_hash",
+ AppsUtils.computeHash(res.statusMessage, "SHA512"));
+ }
+ });
+ });
+ });
+ });
+}
+
+// =================== Various simple mapping ======================
+var settingsToObserve = {
+ 'accessibility.screenreader_quicknav_modes': {
+ prefName: 'accessibility.accessfu.quicknav_modes',
+ resetToPref: true,
+ defaultValue: ''
+ },
+ 'accessibility.screenreader_quicknav_index': {
+ prefName: 'accessibility.accessfu.quicknav_index',
+ resetToPref: true,
+ defaultValue: 0
+ },
+ 'app.update.interval': 86400,
+ 'apz.overscroll.enabled': true,
+ 'browser.safebrowsing.phishing.enabled': true,
+ 'browser.safebrowsing.malware.enabled': true,
+ 'debug.fps.enabled': {
+ prefName: 'layers.acceleration.draw-fps',
+ defaultValue: false
+ },
+ 'debug.log-animations.enabled': {
+ prefName: 'layers.offmainthreadcomposition.log-animations',
+ defaultValue: false
+ },
+ 'debug.paint-flashing.enabled': {
+ prefName: 'nglayout.debug.paint_flashing',
+ defaultValue: false
+ },
+ // FIXME: Bug 1185806 - Provide a common device name setting.
+ // Borrow device name from developer's menu to avoid multiple name settings.
+ 'devtools.discovery.device': {
+ prefName: 'dom.presentation.device.name',
+ defaultValue: 'Firefox OS'
+ },
+ 'devtools.eventlooplag.threshold': 100,
+ 'devtools.remote.wifi.visible': {
+ resetToPref: true
+ },
+ 'devtools.telemetry.supported_performance_marks': {
+ resetToPref: true
+ },
+
+ 'dom.presentation.discovery.enabled': false,
+ 'dom.presentation.discoverable': false,
+ 'dom.serviceWorkers.testing.enabled': false,
+ 'gfx.layerscope.enabled': false,
+ 'layers.draw-borders': false,
+ 'layers.draw-tile-borders': false,
+ 'layers.dump': false,
+ 'layers.enable-tiles': AppConstants.platform !== "win",
+ 'layers.enable-tiles': true,
+ 'layers.effect.invert': false,
+ 'layers.effect.grayscale': false,
+ 'layers.effect.contrast': '0.0',
+ 'layout.display-list.dump': false,
+ 'mms.debugging.enabled': false,
+ 'network.debugging.enabled': false,
+ 'privacy.donottrackheader.enabled': false,
+ 'privacy.trackingprotection.enabled': false,
+ 'ril.debugging.enabled': false,
+ 'ril.radio.disabled': false,
+ 'ril.mms.requestReadReport.enabled': {
+ prefName: 'dom.mms.requestReadReport',
+ defaultValue: true
+ },
+ 'ril.mms.requestStatusReport.enabled': {
+ prefName: 'dom.mms.requestStatusReport',
+ defaultValue: false
+ },
+ 'ril.mms.retrieval_mode': {
+ prefName: 'dom.mms.retrieval_mode',
+ defaultValue: 'manual'
+ },
+ 'ril.sms.requestStatusReport.enabled': {
+ prefName: 'dom.sms.requestStatusReport',
+ defaultValue: false
+ },
+ 'ril.sms.strict7BitEncoding.enabled': {
+ prefName: 'dom.sms.strict7BitEncoding',
+ defaultValue: false
+ },
+ 'ril.sms.maxReadAheadEntries': {
+ prefName: 'dom.sms.maxReadAheadEntries',
+ defaultValue: 7
+ },
+ 'services.sync.enabled': {
+ defaultValue: false,
+ notifyChange: true
+ },
+ 'ui.touch.radius.leftmm': {
+ resetToPref: true
+ },
+ 'ui.touch.radius.topmm': {
+ resetToPref: true
+ },
+ 'ui.touch.radius.rightmm': {
+ resetToPref: true
+ },
+ 'ui.touch.radius.bottommm': {
+ resetToPref: true
+ },
+ 'ui.click_hold_context_menus.delay': {
+ resetToPref: true
+ },
+ 'wap.UAProf.tagname': 'x-wap-profile',
+ 'wap.UAProf.url': ''
+};
+
+if (AppConstants.MOZ_GRAPHENE) {
+ // Restart required
+ settingsToObserve['layers.async-pan-zoom.enabled'] = false;
+}
+
+function settingObserver(setPref, prefName, setting) {
+ return value => {
+ setPref(prefName, value);
+ if (setting.notifyChange) {
+ SystemAppProxy._sendCustomEvent('mozPrefChromeEvent', {
+ prefName: prefName,
+ value: value
+ });
+ }
+ };
+}
+
+for (let key in settingsToObserve) {
+ let setting = settingsToObserve[key];
+
+ // Allow setting to contain flags redefining prefName and defaultValue.
+ let prefName = setting.prefName || key;
+ let defaultValue = setting.defaultValue;
+ if (defaultValue === undefined) {
+ defaultValue = setting;
+ }
+
+ let prefs = Services.prefs;
+
+ // If requested, reset setting value and defaultValue to the pref value.
+ if (setting.resetToPref) {
+ switch (prefs.getPrefType(prefName)) {
+ case Ci.nsIPrefBranch.PREF_BOOL:
+ defaultValue = prefs.getBoolPref(prefName);
+ break;
+
+ case Ci.nsIPrefBranch.PREF_INT:
+ defaultValue = prefs.getIntPref(prefName);
+ break;
+
+ case Ci.nsIPrefBranch.PREF_STRING:
+ defaultValue = prefs.getCharPref(prefName);
+ break;
+ }
+
+ let setting = {};
+ setting[key] = defaultValue;
+ window.navigator.mozSettings.createLock().set(setting);
+ }
+
+ // Figure out the right setter function for this type of pref.
+ let setPref;
+ switch (typeof defaultValue) {
+ case 'boolean':
+ setPref = prefs.setBoolPref.bind(prefs);
+ break;
+
+ case 'number':
+ setPref = prefs.setIntPref.bind(prefs);
+ break;
+
+ case 'string':
+ setPref = prefs.setCharPref.bind(prefs);
+ break;
+ }
+
+ SettingsListener.observe(key, defaultValue,
+ settingObserver(setPref, prefName, setting));
+};
diff --git a/b2g/chrome/content/shell.css b/b2g/chrome/content/shell.css
new file mode 100644
index 0000000000..34daafd991
--- /dev/null
+++ b/b2g/chrome/content/shell.css
@@ -0,0 +1,81 @@
+/* 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/. */
+
+html {
+ background: black;
+ overflow: hidden;
+ width: 100%;
+ height: 100%;
+ padding: 0 !important;
+}
+body {
+ margin: 0;
+ width: 100%;
+ height: 100%;
+ overflow: hidden;
+}
+iframe {
+ overflow: hidden;
+ height: 100%;
+ width: 100%;
+ border: none;
+ position: absolute;
+ left: 0;
+ top: 0;
+ right: 0;
+ bottom: 0;
+ z-index: 1;
+ -moz-user-select: none;
+}
+
+%ifdef MOZ_GRAPHENE
+
+body.content-loaded > #installing {
+ display: none;
+}
+
+#installing {
+ z-index: 2;
+ position: absolute;
+ left: 0;
+ top: 0;
+ right: 0;
+ bottom: 0;
+ width: 100%;
+ height: 100%;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ background-color: #F1C40F;
+ color: #FFF;
+}
+
+.throbber {
+ width: 3px;
+ height: 3px;
+ border-radius: 100px;
+ background-color: #FFF;
+ animation-name: throbber;
+ animation-duration: 1500ms;
+ animation-iteration-count: infinite;
+ animation-timing-function: linear;
+}
+
+#titlebar-buttonbox {
+ margin: 6px 7px;
+ -moz-appearance: -moz-window-button-box;
+}
+
+@keyframes throbber{
+ from {
+ transform: scale(0);
+ opacity: 0.4;
+ }
+ to {
+ transform: scale(400);
+ opacity: 0;
+ }
+}
+
+%endif
diff --git a/b2g/chrome/content/shell.html b/b2g/chrome/content/shell.html
new file mode 100644
index 0000000000..5507a65aac
--- /dev/null
+++ b/b2g/chrome/content/shell.html
@@ -0,0 +1,66 @@
+<!DOCTYPE html>
+<!-- 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/. -->
+
+<html xmlns="http://www.w3.org/1999/xhtml"
+ id="shell"
+ windowtype="navigator:browser"
+#ifdef ANDROID
+ sizemode="fullscreen"
+#endif
+#ifdef MOZ_GRAPHENE
+ macanimationtype="document"
+ fullscreenbutton="true"
+ chromemargin="0,0,0,0"
+#endif
+ >
+
+<head>
+ <link rel="stylesheet" href="shell.css" type="text/css">
+ <script type="text/javascript">
+ <!-- Add raptor performance marker -->
+ window.performance.mark('gecko-shell-html-load');
+ </script>
+ <script type="application/javascript;version=1.8"
+ src="chrome://b2g/content/settings.js"> </script>
+ <script type="application/javascript;version=1.8"
+ src="chrome://b2g/content/shell.js"> </script>
+
+#ifndef ANDROID
+#ifndef MOZ_GRAPHENE
+ <!-- various task that has to happen only on desktop -->
+ <script type="application/javascript;version=1.8"
+ src="chrome://b2g/content/desktop.js"> </script>
+ <!-- this script handles the screen argument for desktop builds -->
+ <script type="application/javascript;version=1.8"
+ src="chrome://b2g/content/screen.js"> </script>
+#endif
+#else
+ <!-- this file is only loaded on Gonk to manage ADB state -->
+ <script type="application/javascript;version=1.8"
+ src="chrome://b2g/content/devtools/adb.js"> </script>
+#endif
+ <!-- manages DevTools server state -->
+ <script type="application/javascript;version=1.8"
+ src="chrome://b2g/content/devtools/debugger.js"> </script>
+</head>
+ <body id="container">
+#ifndef MOZ_GRAPHENE
+#ifdef MOZ_WIDGET_COCOA
+ <!--
+ If the document is empty at startup, we don't display the window
+ at all on Mac OS...
+ -->
+ <h1 id="placeholder">wtf mac os!</h1>
+#endif
+#else
+ <div id="titlebar-buttonbox"></div>
+ <div id="installing">
+ <div class="throbber"></div>
+ <div class="message"></div>
+ </div>
+#endif
+ <!-- The html:iframe containing the UI is created here. -->
+ </body>
+</html>
diff --git a/b2g/chrome/content/shell.js b/b2g/chrome/content/shell.js
new file mode 100644
index 0000000000..d483f9a64f
--- /dev/null
+++ b/b2g/chrome/content/shell.js
@@ -0,0 +1,1308 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- /
+/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
+/* 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/. */
+
+window.performance.mark('gecko-shell-loadstart');
+
+Cu.import('resource://gre/modules/NotificationDB.jsm');
+Cu.import("resource://gre/modules/AppsUtils.jsm");
+Cu.import('resource://gre/modules/UserAgentOverrides.jsm');
+Cu.import('resource://gre/modules/Keyboard.jsm');
+Cu.import('resource://gre/modules/ErrorPage.jsm');
+Cu.import('resource://gre/modules/AlertsHelper.jsm');
+Cu.import('resource://gre/modules/SystemUpdateService.jsm');
+
+if (isGonk) {
+ Cu.import('resource://gre/modules/NetworkStatsService.jsm');
+ Cu.import('resource://gre/modules/ResourceStatsService.jsm');
+}
+
+// Identity
+Cu.import('resource://gre/modules/SignInToWebsite.jsm');
+SignInToWebsiteController.init();
+
+Cu.import('resource://gre/modules/FxAccountsMgmtService.jsm');
+Cu.import('resource://gre/modules/DownloadsAPI.jsm');
+Cu.import('resource://gre/modules/PresentationDeviceInfoManager.jsm');
+Cu.import('resource://gre/modules/AboutServiceWorkers.jsm');
+
+XPCOMUtils.defineLazyModuleGetter(this, "SystemAppProxy",
+ "resource://gre/modules/SystemAppProxy.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "Screenshot",
+ "resource://gre/modules/Screenshot.jsm");
+
+XPCOMUtils.defineLazyServiceGetter(Services, 'env',
+ '@mozilla.org/process/environment;1',
+ 'nsIEnvironment');
+
+XPCOMUtils.defineLazyServiceGetter(Services, 'ss',
+ '@mozilla.org/content/style-sheet-service;1',
+ 'nsIStyleSheetService');
+
+XPCOMUtils.defineLazyServiceGetter(this, 'gSystemMessenger',
+ '@mozilla.org/system-message-internal;1',
+ 'nsISystemMessagesInternal');
+
+XPCOMUtils.defineLazyGetter(this, "ppmm", function() {
+ return Cc["@mozilla.org/parentprocessmessagemanager;1"]
+ .getService(Ci.nsIMessageListenerManager);
+});
+
+if (isGonk) {
+ XPCOMUtils.defineLazyGetter(this, "libcutils", function () {
+ Cu.import("resource://gre/modules/systemlibs.js");
+ return libcutils;
+ });
+}
+
+XPCOMUtils.defineLazyServiceGetter(Services, 'captivePortalDetector',
+ '@mozilla.org/toolkit/captive-detector;1',
+ 'nsICaptivePortalDetector');
+
+XPCOMUtils.defineLazyModuleGetter(this, "SafeBrowsing",
+ "resource://gre/modules/SafeBrowsing.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "SafeMode",
+ "resource://gre/modules/SafeMode.jsm");
+
+window.performance.measure('gecko-shell-jsm-loaded', 'gecko-shell-loadstart');
+
+function debug(str) {
+ dump(' -*- Shell.js: ' + str + '\n');
+}
+
+const once = event => {
+ let target = shell.contentBrowser;
+ return new Promise((resolve, reject) => {
+ target.addEventListener(event, function gotEvent(evt) {
+ target.removeEventListener(event, gotEvent, false);
+ resolve(evt);
+ }, false);
+ });
+}
+
+function clearCache() {
+ let cache = Cc["@mozilla.org/netwerk/cache-storage-service;1"]
+ .getService(Ci.nsICacheStorageService);
+ cache.clear();
+}
+
+function clearCacheAndReload() {
+ // Reload the main frame with a cleared cache.
+ debug('Reloading ' + shell.contentBrowser.contentWindow.location);
+ clearCache();
+ shell.contentBrowser.contentWindow.location.reload(true);
+ once('mozbrowserlocationchange').then(
+ evt => {
+ shell.sendEvent(window, "ContentStart");
+ });
+}
+
+function restart() {
+ let appStartup = Cc['@mozilla.org/toolkit/app-startup;1']
+ .getService(Ci.nsIAppStartup);
+ appStartup.quit(Ci.nsIAppStartup.eForceQuit | Ci.nsIAppStartup.eRestart);
+}
+
+function debugCrashReport(aStr) {
+ AppConstants.MOZ_CRASHREPORTER && dump('Crash reporter : ' + aStr);
+}
+
+var shell = {
+
+ get CrashSubmit() {
+ delete this.CrashSubmit;
+ if (AppConstants.MOZ_CRASHREPORTER) {
+ Cu.import("resource://gre/modules/CrashSubmit.jsm", this);
+ return this.CrashSubmit;
+ } else {
+ dump('Crash reporter : disabled at build time.');
+ return this.CrashSubmit = null;
+ }
+ },
+
+ onlineForCrashReport: function shell_onlineForCrashReport() {
+ let wifiManager = navigator.mozWifiManager;
+ let onWifi = (wifiManager &&
+ (wifiManager.connection.status == 'connected'));
+ return !Services.io.offline && onWifi;
+ },
+
+ reportCrash: function shell_reportCrash(isChrome, aCrashID) {
+ let crashID = aCrashID;
+ try {
+ // For chrome crashes, we want to report the lastRunCrashID.
+ if (isChrome) {
+ crashID = Cc["@mozilla.org/xre/app-info;1"]
+ .getService(Ci.nsIXULRuntime).lastRunCrashID;
+ }
+ } catch(e) {
+ debugCrashReport('Failed to fetch crash id. Crash ID is "' + crashID
+ + '" Exception: ' + e);
+ }
+
+ // Bail if there isn't a valid crashID.
+ if (!this.CrashSubmit || !crashID && !this.CrashSubmit.pendingIDs().length) {
+ return;
+ }
+
+ // purge the queue.
+ this.CrashSubmit.pruneSavedDumps();
+
+ // check for environment affecting crash reporting
+ let env = Cc["@mozilla.org/process/environment;1"]
+ .getService(Ci.nsIEnvironment);
+ let shutdown = env.get("MOZ_CRASHREPORTER_SHUTDOWN");
+ if (shutdown) {
+ let appStartup = Cc["@mozilla.org/toolkit/app-startup;1"]
+ .getService(Ci.nsIAppStartup);
+ appStartup.quit(Ci.nsIAppStartup.eForceQuit);
+ }
+
+ let noReport = env.get("MOZ_CRASHREPORTER_NO_REPORT");
+ if (noReport) {
+ return;
+ }
+
+ try {
+ // Check if we should automatically submit this crash.
+ if (Services.prefs.getBoolPref('app.reportCrashes')) {
+ this.submitCrash(crashID);
+ } else {
+ this.deleteCrash(crashID);
+ }
+ } catch (e) {
+ debugCrashReport('Can\'t fetch app.reportCrashes. Exception: ' + e);
+ }
+
+ // We can get here if we're just submitting old pending crashes.
+ // Check that there's a valid crashID so that we only notify the
+ // user if a crash just happened and not when we OOM. Bug 829477
+ if (crashID) {
+ this.sendChromeEvent({
+ type: "handle-crash",
+ crashID: crashID,
+ chrome: isChrome
+ });
+ }
+ },
+
+ deleteCrash: function shell_deleteCrash(aCrashID) {
+ if (aCrashID) {
+ debugCrashReport('Deleting pending crash: ' + aCrashID);
+ shell.CrashSubmit.delete(aCrashID);
+ }
+ },
+
+ // this function submit the pending crashes.
+ // make sure you are online.
+ submitQueuedCrashes: function shell_submitQueuedCrashes() {
+ // submit the pending queue.
+ let pending = shell.CrashSubmit.pendingIDs();
+ for (let crashid of pending) {
+ debugCrashReport('Submitting crash: ' + crashid);
+ shell.CrashSubmit.submit(crashid);
+ }
+ },
+
+ // This function submits a crash when we're online.
+ submitCrash: function shell_submitCrash(aCrashID) {
+ if (this.onlineForCrashReport()) {
+ this.submitQueuedCrashes();
+ return;
+ }
+
+ debugCrashReport('Not online, postponing.');
+
+ Services.obs.addObserver(function observer(subject, topic, state) {
+ let network = subject.QueryInterface(Ci.nsINetworkInfo);
+ if (network.state == Ci.nsINetworkInfo.NETWORK_STATE_CONNECTED
+ && network.type == Ci.nsINetworkInfo.NETWORK_TYPE_WIFI) {
+ shell.submitQueuedCrashes();
+
+ Services.obs.removeObserver(observer, topic);
+ }
+ }, "network-connection-state-changed", false);
+ },
+
+ get homeURL() {
+ try {
+ let homeSrc = Services.env.get('B2G_HOMESCREEN');
+ if (homeSrc)
+ return homeSrc;
+ } catch (e) {}
+
+ return Services.prefs.getCharPref('b2g.system_startup_url');
+ },
+
+ get manifestURL() {
+ return Services.prefs.getCharPref('b2g.system_manifest_url');
+ },
+
+ _started: false,
+ hasStarted: function shell_hasStarted() {
+ return this._started;
+ },
+
+ bootstrap: function() {
+ window.performance.mark('gecko-shell-bootstrap');
+
+ // Before anything, check if we want to start in safe mode.
+ SafeMode.check(window).then(() => {
+ let startManifestURL =
+ Cc['@mozilla.org/commandlinehandler/general-startup;1?type=b2gbootstrap']
+ .getService(Ci.nsISupports).wrappedJSObject.startManifestURL;
+
+ // If --start-manifest hasn't been specified, we re-use the latest specified manifest.
+ // If it's the first launch, we will fallback to b2g.default.start_manifest_url
+ if (AppConstants.MOZ_GRAPHENE && !startManifestURL) {
+ try {
+ startManifestURL = Services.prefs.getCharPref("b2g.system_manifest_url");
+ } catch(e) {}
+ }
+
+ if (!startManifestURL) {
+ try {
+ startManifestURL = Services.prefs.getCharPref("b2g.default.start_manifest_url");
+ } catch(e) {}
+ }
+
+ if (startManifestURL) {
+ Cu.import('resource://gre/modules/Bootstraper.jsm');
+
+ if (AppConstants.MOZ_GRAPHENE && Bootstraper.isInstallRequired(startManifestURL)) {
+ // Installing the app my take some time. We don't want to keep the
+ // native window hidden.
+ showInstallScreen();
+ }
+
+ Bootstraper.ensureSystemAppInstall(startManifestURL)
+ .then(this.start.bind(this))
+ .catch(Bootstraper.bailout);
+ } else {
+ this.start();
+ }
+ });
+ },
+
+ start: function shell_start() {
+ window.performance.mark('gecko-shell-start');
+ this._started = true;
+
+ // This forces the initialization of the cookie service before we hit the
+ // network.
+ // See bug 810209
+ let cookies = Cc["@mozilla.org/cookieService;1"];
+
+ try {
+ let cr = Cc["@mozilla.org/xre/app-info;1"]
+ .getService(Ci.nsICrashReporter);
+ // Dogfood id. We might want to remove it in the future.
+ // see bug 789466
+ try {
+ let dogfoodId = Services.prefs.getCharPref('prerelease.dogfood.id');
+ if (dogfoodId != "") {
+ cr.annotateCrashReport("Email", dogfoodId);
+ }
+ }
+ catch (e) { }
+
+ if (isGonk) {
+ // Annotate crash report
+ let annotations = [ [ "Android_Hardware", "ro.hardware" ],
+ [ "Android_Device", "ro.product.device" ],
+ [ "Android_CPU_ABI2", "ro.product.cpu.abi2" ],
+ [ "Android_CPU_ABI", "ro.product.cpu.abi" ],
+ [ "Android_Manufacturer", "ro.product.manufacturer" ],
+ [ "Android_Brand", "ro.product.brand" ],
+ [ "Android_Model", "ro.product.model" ],
+ [ "Android_Board", "ro.product.board" ],
+ ];
+
+ annotations.forEach(function (element) {
+ cr.annotateCrashReport(element[0], libcutils.property_get(element[1]));
+ });
+
+ let androidVersion = libcutils.property_get("ro.build.version.sdk") +
+ "(" + libcutils.property_get("ro.build.version.codename") + ")";
+ cr.annotateCrashReport("Android_Version", androidVersion);
+
+ SettingsListener.observe("deviceinfo.os", "", function(value) {
+ try {
+ let cr = Cc["@mozilla.org/xre/app-info;1"]
+ .getService(Ci.nsICrashReporter);
+ cr.annotateCrashReport("B2G_OS_Version", value);
+ } catch(e) { }
+ });
+ }
+ } catch(e) {
+ debugCrashReport('exception: ' + e);
+ }
+
+ let homeURL = this.homeURL;
+ if (!homeURL) {
+ let msg = 'Fatal error during startup: No homescreen found: try setting B2G_HOMESCREEN';
+ alert(msg);
+ return;
+ }
+
+ let manifestURL = this.manifestURL;
+ // <html:iframe id="systemapp"
+ // mozbrowser="true" allowfullscreen="true"
+ // style="overflow: hidden; height: 100%; width: 100%; border: none;"
+ // src="data:text/html;charset=utf-8,%3C!DOCTYPE html>%3Cbody style='background:black;'>"/>
+ let systemAppFrame =
+ document.createElementNS('http://www.w3.org/1999/xhtml', 'html:iframe');
+ systemAppFrame.setAttribute('id', 'systemapp');
+ systemAppFrame.setAttribute('mozbrowser', 'true');
+ systemAppFrame.setAttribute('mozapp', manifestURL);
+ systemAppFrame.setAttribute('allowfullscreen', 'true');
+ systemAppFrame.setAttribute('src', 'blank.html');
+ let container = document.getElementById('container');
+
+ if (AppConstants.platform == 'macosx') {
+ // See shell.html
+ let hotfix = document.getElementById('placeholder');
+ if (hotfix) {
+ container.removeChild(hotfix);
+ }
+ }
+
+ this.contentBrowser = container.appendChild(systemAppFrame);
+
+ let webNav = systemAppFrame.contentWindow
+ .QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIWebNavigation);
+ webNav.sessionHistory = Cc["@mozilla.org/browser/shistory;1"].createInstance(Ci.nsISHistory);
+
+ if (AppConstants.MOZ_GRAPHENE) {
+ webNav.QueryInterface(Ci.nsIDocShell).windowDraggingAllowed = true;
+ }
+
+ let audioChannels = systemAppFrame.allowedAudioChannels;
+ audioChannels && audioChannels.forEach(function(audioChannel) {
+ // Set all audio channels as unmuted by default
+ // because some audio in System app will be played
+ // before AudioChannelService[1] is Gaia is loaded.
+ // [1]: https://github.com/mozilla-b2g/gaia/blob/master/apps/system/js/audio_channel_service.js
+ audioChannel.setMuted(false);
+ });
+
+ // On firefox mulet, shell.html is loaded in a tab
+ // and we have to listen on the chrome event handler
+ // to catch key events
+ let chromeEventHandler = window.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIWebNavigation)
+ .QueryInterface(Ci.nsIDocShell)
+ .chromeEventHandler || window;
+ // Capture all key events so we can filter out hardware buttons
+ // And send them to Gaia via mozChromeEvents.
+ // Ideally, hardware buttons wouldn't generate key events at all, or
+ // if they did, they would use keycodes that conform to DOM 3 Events.
+ // See discussion in https://bugzilla.mozilla.org/show_bug.cgi?id=762362
+ chromeEventHandler.addEventListener('keydown', this, true);
+ chromeEventHandler.addEventListener('keyup', this, true);
+
+ window.addEventListener('MozApplicationManifest', this);
+ window.addEventListener('MozAfterPaint', this);
+ window.addEventListener('sizemodechange', this);
+ window.addEventListener('unload', this);
+ this.contentBrowser.addEventListener('mozbrowserloadstart', this, true);
+ this.contentBrowser.addEventListener('mozbrowserscrollviewchange', this, true);
+ this.contentBrowser.addEventListener('mozbrowsercaretstatechanged', this);
+
+ CustomEventManager.init();
+ UserAgentOverrides.init();
+ CaptivePortalLoginHelper.init();
+
+ this.contentBrowser.src = homeURL;
+
+ this._isEventListenerReady = false;
+
+ window.performance.mark('gecko-shell-system-frame-set');
+
+ ppmm.addMessageListener("content-handler", this);
+ ppmm.addMessageListener("dial-handler", this);
+ ppmm.addMessageListener("sms-handler", this);
+ ppmm.addMessageListener("mail-handler", this);
+ ppmm.addMessageListener("file-picker", this);
+
+ setTimeout(function() {
+ SafeBrowsing.init();
+ }, 5000);
+ },
+
+ stop: function shell_stop() {
+ window.removeEventListener('unload', this);
+ window.removeEventListener('keydown', this, true);
+ window.removeEventListener('keyup', this, true);
+ window.removeEventListener('MozApplicationManifest', this);
+ window.removeEventListener('sizemodechange', this);
+ this.contentBrowser.removeEventListener('mozbrowserloadstart', this, true);
+ this.contentBrowser.removeEventListener('mozbrowserscrollviewchange', this, true);
+ this.contentBrowser.removeEventListener('mozbrowsercaretstatechanged', this);
+ ppmm.removeMessageListener("content-handler", this);
+
+ UserAgentOverrides.uninit();
+ },
+
+ // If this key event represents a hardware button which needs to be send as
+ // a message, broadcasts it with the message set to 'xxx-button-press' or
+ // 'xxx-button-release'.
+ broadcastHardwareKeys: function shell_broadcastHardwareKeys(evt) {
+ let type;
+ let message;
+
+ let mediaKeys = {
+ 'MediaTrackNext': 'media-next-track-button',
+ 'MediaTrackPrevious': 'media-previous-track-button',
+ 'MediaPause': 'media-pause-button',
+ 'MediaPlay': 'media-play-button',
+ 'MediaPlayPause': 'media-play-pause-button',
+ 'MediaStop': 'media-stop-button',
+ 'MediaRewind': 'media-rewind-button',
+ 'MediaFastForward': 'media-fast-forward-button'
+ };
+
+ if (evt.keyCode == evt.DOM_VK_F1) {
+ type = 'headset-button';
+ message = 'headset-button';
+ } else if (mediaKeys[evt.key]) {
+ type = 'media-button';
+ message = mediaKeys[evt.key];
+ } else {
+ return;
+ }
+
+ switch (evt.type) {
+ case 'keydown':
+ message = message + '-press';
+ break;
+ case 'keyup':
+ message = message + '-release';
+ break;
+ }
+
+ // Let applications receive the headset button and media key press/release message.
+ if (message !== this.lastHardwareButtonMessage) {
+ this.lastHardwareButtonMessage = message;
+ gSystemMessenger.broadcastMessage(type, message);
+ }
+ },
+
+ lastHardwareButtonMessage: null, // property for the hack above
+ visibleNormalAudioActive: false,
+
+ handleEvent: function shell_handleEvent(evt) {
+ function checkReloadKey() {
+ if (evt.type !== 'keyup') {
+ return false;
+ }
+
+ try {
+ let key = JSON.parse(Services.prefs.getCharPref('b2g.reload_key'));
+ return (evt.keyCode == key.key &&
+ evt.ctrlKey == key.ctrl &&
+ evt.altKey == key.alt &&
+ evt.shiftKey == key.shift &&
+ evt.metaKey == key.meta);
+ } catch(e) {
+ debug('Failed to get key: ' + e);
+ }
+
+ return false;
+ }
+
+ let content = this.contentBrowser.contentWindow;
+ switch (evt.type) {
+ case 'keydown':
+ case 'keyup':
+ if (checkReloadKey()) {
+ clearCacheAndReload();
+ } else {
+ this.broadcastHardwareKeys(evt);
+ }
+ break;
+ case 'sizemodechange':
+ if (window.windowState == window.STATE_MINIMIZED && !this.visibleNormalAudioActive) {
+ this.contentBrowser.setVisible(false);
+ } else {
+ this.contentBrowser.setVisible(true);
+ }
+ break;
+ case 'load':
+ if (content.document.location == 'about:blank') {
+ return;
+ }
+ content.removeEventListener('load', this, true);
+ this.notifyContentWindowLoaded();
+ break;
+ case 'mozbrowserloadstart':
+ if (content.document.location == 'about:blank') {
+ this.contentBrowser.addEventListener('mozbrowserlocationchange', this, true);
+ return;
+ }
+
+ this.notifyContentStart();
+ break;
+ case 'mozbrowserlocationchange':
+ if (content.document.location == 'about:blank') {
+ return;
+ }
+
+ this.notifyContentStart();
+ break;
+ case 'mozbrowserscrollviewchange':
+ this.sendChromeEvent({
+ type: 'scrollviewchange',
+ detail: evt.detail,
+ });
+ break;
+ case 'mozbrowsercaretstatechanged':
+ {
+ let elt = evt.target;
+ let win = elt.ownerDocument.defaultView;
+ let offsetX = win.mozInnerScreenX - window.mozInnerScreenX;
+ let offsetY = win.mozInnerScreenY - window.mozInnerScreenY;
+
+ let rect = elt.getBoundingClientRect();
+ offsetX += rect.left;
+ offsetY += rect.top;
+
+ let data = evt.detail;
+ data.offsetX = offsetX;
+ data.offsetY = offsetY;
+ data.sendDoCommandMsg = null;
+
+ shell.sendChromeEvent({
+ type: 'caretstatechanged',
+ detail: data,
+ });
+ }
+ break;
+
+ case 'MozApplicationManifest':
+ try {
+ if (!Services.prefs.getBoolPref('browser.cache.offline.enable'))
+ return;
+
+ let contentWindow = evt.originalTarget.defaultView;
+ let documentElement = contentWindow.document.documentElement;
+ if (!documentElement)
+ return;
+
+ let manifest = documentElement.getAttribute('manifest');
+ if (!manifest)
+ return;
+
+ let principal = contentWindow.document.nodePrincipal;
+ if (Services.perms.testPermissionFromPrincipal(principal, 'offline-app') == Ci.nsIPermissionManager.UNKNOWN_ACTION) {
+ if (Services.prefs.getBoolPref('browser.offline-apps.notify')) {
+ // FIXME Bug 710729 - Add a UI for offline cache notifications
+ return;
+ }
+ return;
+ }
+
+ Services.perms.addFromPrincipal(principal, 'offline-app',
+ Ci.nsIPermissionManager.ALLOW_ACTION);
+
+ let documentURI = Services.io.newURI(contentWindow.document.documentURI,
+ null,
+ null);
+ let manifestURI = Services.io.newURI(manifest, null, documentURI);
+ let updateService = Cc['@mozilla.org/offlinecacheupdate-service;1']
+ .getService(Ci.nsIOfflineCacheUpdateService);
+ updateService.scheduleUpdate(manifestURI, documentURI, principal, window);
+ } catch (e) {
+ dump('Error while creating offline cache: ' + e + '\n');
+ }
+ break;
+ case 'MozAfterPaint':
+ window.removeEventListener('MozAfterPaint', this);
+ // This event should be sent before System app returns with
+ // system-message-listener-ready mozContentEvent, because it's on
+ // the critical launch path of the app.
+ SystemAppProxy._sendCustomEvent('mozChromeEvent', {
+ type: 'system-first-paint'
+ }, /* noPending */ true);
+ break;
+ case 'unload':
+ this.stop();
+ break;
+ }
+ },
+
+ // Send an event to a specific window, document or element.
+ sendEvent: function shell_sendEvent(target, type, details) {
+ if (target === this.contentBrowser) {
+ // We must ask SystemAppProxy to send the event in this case so
+ // that event would be dispatched from frame.contentWindow instead of
+ // on the System app frame.
+ SystemAppProxy._sendCustomEvent(type, details);
+ return;
+ }
+
+ let doc = target.document || target.ownerDocument || target;
+ let event = doc.createEvent('CustomEvent');
+ event.initCustomEvent(type, true, true, details ? details : {});
+ target.dispatchEvent(event);
+ },
+
+ sendCustomEvent: function shell_sendCustomEvent(type, details) {
+ SystemAppProxy._sendCustomEvent(type, details);
+ },
+
+ sendChromeEvent: function shell_sendChromeEvent(details) {
+ this.sendCustomEvent("mozChromeEvent", details);
+ },
+
+ receiveMessage: function shell_receiveMessage(message) {
+ var activities = { 'content-handler': { name: 'view', response: null },
+ 'dial-handler': { name: 'dial', response: null },
+ 'mail-handler': { name: 'new', response: null },
+ 'sms-handler': { name: 'new', response: null },
+ 'file-picker': { name: 'pick', response: 'file-picked' } };
+
+ if (!(message.name in activities))
+ return;
+
+ let data = message.data;
+ let activity = activities[message.name];
+
+ let a = new MozActivity({
+ name: activity.name,
+ data: data
+ });
+
+ if (activity.response) {
+ a.onsuccess = function() {
+ let sender = message.target.QueryInterface(Ci.nsIMessageSender);
+ sender.sendAsyncMessage(activity.response, { success: true,
+ result: a.result });
+ }
+ a.onerror = function() {
+ let sender = message.target.QueryInterface(Ci.nsIMessageSender);
+ sender.sendAsyncMessage(activity.response, { success: false });
+ }
+ }
+ },
+
+ notifyContentStart: function shell_notifyContentStart() {
+ window.performance.mark('gecko-shell-notify-content-start');
+ this.contentBrowser.removeEventListener('mozbrowserloadstart', this, true);
+ this.contentBrowser.removeEventListener('mozbrowserlocationchange', this, true);
+
+ let content = this.contentBrowser.contentWindow;
+ content.addEventListener('load', this, true);
+
+ this.reportCrash(true);
+
+ SystemAppProxy.registerFrame(shell.contentBrowser);
+
+ this.sendEvent(window, 'ContentStart');
+
+ Services.obs.notifyObservers(null, 'content-start', null);
+
+ if (AppConstants.MOZ_GRAPHENE &&
+ Services.prefs.getBoolPref("b2g.nativeWindowGeometry.fullscreen")) {
+ window.fullScreen = true;
+ }
+
+ shell.handleCmdLine();
+ },
+
+ handleCmdLine: function() {
+ // This isn't supported on devices.
+ if (!isGonk) {
+ let b2gcmds = Cc["@mozilla.org/commandlinehandler/general-startup;1?type=b2gcmds"]
+ .getService(Ci.nsISupports);
+ let args = b2gcmds.wrappedJSObject.cmdLine;
+ try {
+ // Returns null if -url is not present.
+ let url = args.handleFlagWithParam("url", false);
+ if (url) {
+ this.sendChromeEvent({type: "mozbrowseropenwindow", url});
+ args.preventDefault = true;
+ }
+ } catch(e) {
+ // Throws if -url is present with no params.
+ }
+ }
+ },
+
+ // This gets called when window.onload fires on the System app content window,
+ // which means things in <html> are parsed and statically referenced <script>s
+ // and <script defer>s are loaded and run.
+ notifyContentWindowLoaded: function shell_notifyContentWindowLoaded() {
+ isGonk && libcutils.property_set('sys.boot_completed', '1');
+
+ // This will cause Gonk Widget to remove boot animation from the screen
+ // and reveals the page.
+ Services.obs.notifyObservers(null, "browser-ui-startup-complete", "");
+
+ SystemAppProxy.setIsLoaded();
+ },
+
+ // This gets called when the content sends us system-message-listener-ready
+ // mozContentEvent, OR when an observer message tell us we should consider
+ // the content as ready.
+ notifyEventListenerReady: function shell_notifyEventListenerReady() {
+ if (this._isEventListenerReady) {
+ Cu.reportError('shell.js: SystemApp has already been declared as being ready.');
+ return;
+ }
+ this._isEventListenerReady = true;
+
+ if (Services.prefs.getBoolPref('b2g.orientation.animate')) {
+ Cu.import('resource://gre/modules/OrientationChangeHandler.jsm');
+ }
+
+ SystemAppProxy.setIsReady();
+ }
+};
+
+Services.obs.addObserver(function onFullscreenOriginChange(subject, topic, data) {
+ shell.sendChromeEvent({ type: "fullscreenoriginchange",
+ fullscreenorigin: data });
+}, "fullscreen-origin-change", false);
+
+Services.obs.addObserver(function onBluetoothVolumeChange(subject, topic, data) {
+ shell.sendChromeEvent({
+ type: "bluetooth-volumeset",
+ value: data
+ });
+}, 'bluetooth-volume-change', false);
+
+Services.obs.addObserver(function(subject, topic, data) {
+ shell.sendCustomEvent('mozmemorypressure');
+}, 'memory-pressure', false);
+
+Services.obs.addObserver(function(subject, topic, data) {
+ shell.notifyEventListenerReady();
+}, 'system-message-listener-ready', false);
+
+var permissionMap = new Map([
+ ['unknown', Services.perms.UNKNOWN_ACTION],
+ ['allow', Services.perms.ALLOW_ACTION],
+ ['deny', Services.perms.DENY_ACTION],
+ ['prompt', Services.perms.PROMPT_ACTION],
+]);
+var permissionMapRev = new Map(Array.from(permissionMap.entries()).reverse());
+
+var CustomEventManager = {
+ init: function custevt_init() {
+ window.addEventListener("ContentStart", (function(evt) {
+ let content = shell.contentBrowser.contentWindow;
+ content.addEventListener("mozContentEvent", this, false, true);
+ }).bind(this), false);
+ },
+
+ handleEvent: function custevt_handleEvent(evt) {
+ let detail = evt.detail;
+ dump('XXX FIXME : Got a mozContentEvent: ' + detail.type + "\n");
+
+ switch(detail.type) {
+ case 'system-message-listener-ready':
+ Services.obs.notifyObservers(null, 'system-message-listener-ready', null);
+ break;
+ case 'captive-portal-login-cancel':
+ CaptivePortalLoginHelper.handleEvent(detail);
+ break;
+ case 'inputmethod-update-layouts':
+ case 'inputregistry-add':
+ case 'inputregistry-remove':
+ KeyboardHelper.handleEvent(detail);
+ break;
+ case 'copypaste-do-command':
+ Services.obs.notifyObservers({ wrappedJSObject: shell.contentBrowser },
+ 'ask-children-to-execute-copypaste-command', detail.cmd);
+ break;
+ case 'add-permission':
+ Services.perms.add(Services.io.newURI(detail.uri, null, null),
+ detail.permissionType, permissionMap.get(detail.permission));
+ break;
+ case 'remove-permission':
+ Services.perms.remove(Services.io.newURI(detail.uri, null, null),
+ detail.permissionType);
+ break;
+ case 'test-permission':
+ let result = Services.perms.testExactPermission(
+ Services.io.newURI(detail.uri, null, null), detail.permissionType);
+ // Not equal check here because we want to prevent default only if it's not set
+ if (result !== permissionMapRev.get(detail.permission)) {
+ evt.preventDefault();
+ }
+ break;
+ case 'shutdown-application':
+ let appStartup = Cc['@mozilla.org/toolkit/app-startup;1']
+ .getService(Ci.nsIAppStartup);
+ appStartup.quit(appStartup.eAttemptQuit);
+ break;
+ case 'toggle-fullscreen-native-window':
+ window.fullScreen = !window.fullScreen;
+ Services.prefs.setBoolPref("b2g.nativeWindowGeometry.fullscreen",
+ window.fullScreen);
+ break;
+ case 'minimize-native-window':
+ window.minimize();
+ break;
+ case 'clear-cache-and-reload':
+ clearCacheAndReload();
+ break;
+ case 'clear-cache-and-restart':
+ clearCache();
+ restart();
+ break;
+ case 'restart':
+ restart();
+ break;
+ }
+ }
+}
+
+var KeyboardHelper = {
+ handleEvent: function keyboard_handleEvent(detail) {
+ switch (detail.type) {
+ case 'inputmethod-update-layouts':
+ Keyboard.setLayouts(detail.layouts);
+
+ break;
+ case 'inputregistry-add':
+ case 'inputregistry-remove':
+ Keyboard.inputRegistryGlue.returnMessage(detail);
+
+ break;
+ }
+ }
+};
+
+// This is the backend for Gaia's screenshot feature. Gaia requests a
+// screenshot by sending a mozContentEvent with detail.type set to
+// 'take-screenshot'. Then we take a screenshot and send a
+// mozChromeEvent with detail.type set to 'take-screenshot-success'
+// and detail.file set to the an image/png blob
+window.addEventListener('ContentStart', function ss_onContentStart() {
+ let content = shell.contentBrowser.contentWindow;
+ content.addEventListener('mozContentEvent', function ss_onMozContentEvent(e) {
+ if (e.detail.type !== 'take-screenshot')
+ return;
+
+ try {
+ shell.sendChromeEvent({
+ type: 'take-screenshot-success',
+ file: Screenshot.get()
+ });
+ } catch (e) {
+ dump('exception while creating screenshot: ' + e + '\n');
+ shell.sendChromeEvent({
+ type: 'take-screenshot-error',
+ error: String(e)
+ });
+ }
+ });
+});
+
+(function contentCrashTracker() {
+ Services.obs.addObserver(function(aSubject, aTopic, aData) {
+ let props = aSubject.QueryInterface(Ci.nsIPropertyBag2);
+ if (props.hasKey("abnormal") && props.hasKey("dumpID")) {
+ shell.reportCrash(false, props.getProperty("dumpID"));
+ }
+ },
+ "ipc:content-shutdown", false);
+})();
+
+var CaptivePortalLoginHelper = {
+ init: function init() {
+ Services.obs.addObserver(this, 'captive-portal-login', false);
+ Services.obs.addObserver(this, 'captive-portal-login-abort', false);
+ Services.obs.addObserver(this, 'captive-portal-login-success', false);
+ },
+ handleEvent: function handleEvent(detail) {
+ Services.captivePortalDetector.cancelLogin(detail.id);
+ },
+ observe: function observe(subject, topic, data) {
+ shell.sendChromeEvent(JSON.parse(data));
+ }
+}
+
+// Listen for crashes submitted through the crash reporter UI.
+window.addEventListener('ContentStart', function cr_onContentStart() {
+ let content = shell.contentBrowser.contentWindow;
+ content.addEventListener("mozContentEvent", function cr_onMozContentEvent(e) {
+ if (e.detail.type == "submit-crash" && e.detail.crashID) {
+ debugCrashReport("submitting crash at user request ", e.detail.crashID);
+ shell.submitCrash(e.detail.crashID);
+ } else if (e.detail.type == "delete-crash" && e.detail.crashID) {
+ debugCrashReport("deleting crash at user request ", e.detail.crashID);
+ shell.deleteCrash(e.detail.crashID);
+ }
+ });
+});
+
+window.addEventListener('ContentStart', function update_onContentStart() {
+ if (!AppConstants.MOZ_UPDATER) {
+ return;
+ }
+
+ let promptCc = Cc["@mozilla.org/updates/update-prompt;1"];
+ if (!promptCc) {
+ return;
+ }
+
+ let updatePrompt = promptCc.createInstance(Ci.nsIUpdatePrompt);
+ if (!updatePrompt) {
+ return;
+ }
+
+ updatePrompt.wrappedJSObject.handleContentStart(shell);
+});
+/* The "GPSChipOn" is to indicate that GPS engine is turned ON by the modem.
+ During this GPS engine is turned ON by the modem, we make the location tracking icon visible to user.
+ Once GPS engine is turned OFF, the location icon will disappear.
+ If GPS engine is not turned ON by the modem or GPS location service is triggered,
+ we let GPS service take over the control of showing the location tracking icon.
+ The regular sequence of the geolocation-device-events is: starting-> GPSStarting-> shutdown-> GPSShutdown
+*/
+
+
+(function geolocationStatusTracker() {
+ let gGeolocationActive = false;
+ let GPSChipOn = false;
+
+ Services.obs.addObserver(function(aSubject, aTopic, aData) {
+ let oldState = gGeolocationActive;
+ let promptWarning = false;
+ switch (aData) {
+ case "GPSStarting":
+ if (!gGeolocationActive) {
+ gGeolocationActive = true;
+ GPSChipOn = true;
+ promptWarning = true;
+ }
+ break;
+ case "GPSShutdown":
+ if (GPSChipOn) {
+ gGeolocationActive = false;
+ GPSChipOn = false;
+ }
+ break;
+ case "starting":
+ gGeolocationActive = true;
+ GPSChipOn = false;
+ break;
+ case "shutdown":
+ gGeolocationActive = false;
+ break;
+ }
+
+ if (gGeolocationActive != oldState) {
+ shell.sendChromeEvent({
+ type: 'geolocation-status',
+ active: gGeolocationActive,
+ prompt: promptWarning
+ });
+ }
+}, "geolocation-device-events", false);
+})();
+
+(function headphonesStatusTracker() {
+ Services.obs.addObserver(function(aSubject, aTopic, aData) {
+ shell.sendChromeEvent({
+ type: 'headphones-status-changed',
+ state: aData
+ });
+}, "headphones-status-changed", false);
+})();
+
+(function audioChannelChangedTracker() {
+ Services.obs.addObserver(function(aSubject, aTopic, aData) {
+ shell.sendChromeEvent({
+ type: 'audio-channel-changed',
+ channel: aData
+ });
+}, "audio-channel-changed", false);
+})();
+
+(function defaultVolumeChannelChangedTracker() {
+ Services.obs.addObserver(function(aSubject, aTopic, aData) {
+ shell.sendChromeEvent({
+ type: 'default-volume-channel-changed',
+ channel: aData
+ });
+}, "default-volume-channel-changed", false);
+})();
+
+(function visibleAudioChannelChangedTracker() {
+ Services.obs.addObserver(function(aSubject, aTopic, aData) {
+ shell.sendChromeEvent({
+ type: 'visible-audio-channel-changed',
+ channel: aData
+ });
+ shell.visibleNormalAudioActive = (aData == 'normal');
+}, "visible-audio-channel-changed", false);
+})();
+
+(function recordingStatusTracker() {
+ // Recording status is tracked per process with following data structure:
+ // {<processId>: {<requestURL>: {isApp: <isApp>,
+ // count: <N>,
+ // audioCount: <N>,
+ // videoCount: <N>}}
+ let gRecordingActiveProcesses = {};
+
+ let recordingHandler = function(aSubject, aTopic, aData) {
+ let props = aSubject.QueryInterface(Ci.nsIPropertyBag2);
+ let processId = (props.hasKey('childID')) ? props.get('childID')
+ : 'main';
+ if (processId && !gRecordingActiveProcesses.hasOwnProperty(processId)) {
+ gRecordingActiveProcesses[processId] = {};
+ }
+
+ let commandHandler = function (requestURL, command) {
+ let currentProcess = gRecordingActiveProcesses[processId];
+ let currentActive = currentProcess[requestURL];
+ let wasActive = (currentActive['count'] > 0);
+ let wasAudioActive = (currentActive['audioCount'] > 0);
+ let wasVideoActive = (currentActive['videoCount'] > 0);
+
+ switch (command.type) {
+ case 'starting':
+ currentActive['count']++;
+ currentActive['audioCount'] += (command.isAudio) ? 1 : 0;
+ currentActive['videoCount'] += (command.isVideo) ? 1 : 0;
+ break;
+ case 'shutdown':
+ currentActive['count']--;
+ currentActive['audioCount'] -= (command.isAudio) ? 1 : 0;
+ currentActive['videoCount'] -= (command.isVideo) ? 1 : 0;
+ break;
+ case 'content-shutdown':
+ currentActive['count'] = 0;
+ currentActive['audioCount'] = 0;
+ currentActive['videoCount'] = 0;
+ break;
+ }
+
+ if (currentActive['count'] > 0) {
+ currentProcess[requestURL] = currentActive;
+ } else {
+ delete currentProcess[requestURL];
+ }
+
+ // We need to track changes if any active state is changed.
+ let isActive = (currentActive['count'] > 0);
+ let isAudioActive = (currentActive['audioCount'] > 0);
+ let isVideoActive = (currentActive['videoCount'] > 0);
+ if ((isActive != wasActive) ||
+ (isAudioActive != wasAudioActive) ||
+ (isVideoActive != wasVideoActive)) {
+ shell.sendChromeEvent({
+ type: 'recording-status',
+ active: isActive,
+ requestURL: requestURL,
+ isApp: currentActive['isApp'],
+ isAudio: isAudioActive,
+ isVideo: isVideoActive
+ });
+ }
+ };
+
+ switch (aData) {
+ case 'starting':
+ case 'shutdown':
+ // create page record if it is not existed yet.
+ let requestURL = props.get('requestURL');
+ if (requestURL &&
+ !gRecordingActiveProcesses[processId].hasOwnProperty(requestURL)) {
+ gRecordingActiveProcesses[processId][requestURL] = {isApp: props.get('isApp'),
+ count: 0,
+ audioCount: 0,
+ videoCount: 0};
+ }
+ commandHandler(requestURL, { type: aData,
+ isAudio: props.get('isAudio'),
+ isVideo: props.get('isVideo')});
+ break;
+ case 'content-shutdown':
+ // iterate through all the existing active processes
+ Object.keys(gRecordingActiveProcesses[processId]).forEach(function(requestURL) {
+ commandHandler(requestURL, { type: aData,
+ isAudio: true,
+ isVideo: true});
+ });
+ break;
+ }
+
+ // clean up process record if no page record in it.
+ if (Object.keys(gRecordingActiveProcesses[processId]).length == 0) {
+ delete gRecordingActiveProcesses[processId];
+ }
+ };
+ Services.obs.addObserver(recordingHandler, 'recording-device-events', false);
+ Services.obs.addObserver(recordingHandler, 'recording-device-ipc-events', false);
+
+ Services.obs.addObserver(function(aSubject, aTopic, aData) {
+ // send additional recording events if content process is being killed
+ let processId = aSubject.QueryInterface(Ci.nsIPropertyBag2).get('childID');
+ if (gRecordingActiveProcesses.hasOwnProperty(processId)) {
+ Services.obs.notifyObservers(aSubject, 'recording-device-ipc-events', 'content-shutdown');
+ }
+ }, 'ipc:content-shutdown', false);
+})();
+
+(function volumeStateTracker() {
+ Services.obs.addObserver(function(aSubject, aTopic, aData) {
+ shell.sendChromeEvent({
+ type: 'volume-state-changed',
+ active: (aData == 'Shared')
+ });
+}, 'volume-state-changed', false);
+})();
+
+if (isGonk) {
+ // Devices don't have all the same partition size for /cache where we
+ // store the http cache.
+ (function setHTTPCacheSize() {
+ let path = Services.prefs.getCharPref("browser.cache.disk.parent_directory");
+ let volumeService = Cc["@mozilla.org/telephony/volume-service;1"]
+ .getService(Ci.nsIVolumeService);
+
+ let stats = volumeService.createOrGetVolumeByPath(path).getStats();
+
+ // We must set the size in KB, and keep a bit of free space.
+ let size = Math.floor(stats.totalBytes / 1024) - 1024;
+
+ // keep the default value if it is smaller than the physical partition size.
+ let oldSize = Services.prefs.getIntPref("browser.cache.disk.capacity");
+ if (size < oldSize) {
+ Services.prefs.setIntPref("browser.cache.disk.capacity", size);
+ }
+ })();
+
+ try {
+ let gmpService = Cc["@mozilla.org/gecko-media-plugin-service;1"]
+ .getService(Ci.mozIGeckoMediaPluginChromeService);
+ gmpService.addPluginDirectory("/system/b2g/gmp-clearkey/0.1");
+ } catch(e) {
+ dump("Failed to add clearkey path! " + e + "\n");
+ }
+}
+
+// Calling this observer will cause a shutdown an a profile reset.
+// Use eg. : Services.obs.notifyObservers(null, 'b2g-reset-profile', null);
+Services.obs.addObserver(function resetProfile(subject, topic, data) {
+ Services.obs.removeObserver(resetProfile, topic);
+
+ // Listening for 'profile-before-change-telemetry' which is late in the
+ // shutdown sequence, but still has xpcom access.
+ Services.obs.addObserver(function clearProfile(subject, topic, data) {
+ Services.obs.removeObserver(clearProfile, topic);
+ if (isGonk) {
+ let json = Cc['@mozilla.org/file/local;1'].createInstance(Ci.nsIFile);
+ json.initWithPath('/system/b2g/webapps/webapps.json');
+ let toRemove = json.exists()
+ // This is a user build, just rm -r /data/local /data/b2g/mozilla
+ ? ['/data/local', '/data/b2g/mozilla']
+ // This is an eng build. We clear the profile and a set of files
+ // under /data/local.
+ : ['/data/b2g/mozilla',
+ '/data/local/permissions.sqlite',
+ '/data/local/storage',
+ '/data/local/OfflineCache'];
+
+ toRemove.forEach(function(dir) {
+ try {
+ let file = Cc['@mozilla.org/file/local;1'].createInstance(Ci.nsIFile);
+ file.initWithPath(dir);
+ file.remove(true);
+ } catch(e) { dump(e); }
+ });
+ } else {
+ // Desktop builds.
+ let profile = Services.dirsvc.get('ProfD', Ci.nsIFile);
+
+ // We don't want to remove everything from the profile, since this
+ // would prevent us from starting up.
+ let whitelist = ['defaults', 'extensions', 'settings.json',
+ 'user.js', 'webapps'];
+ let enumerator = profile.directoryEntries;
+ while (enumerator.hasMoreElements()) {
+ let file = enumerator.getNext().QueryInterface(Ci.nsIFile);
+ if (whitelist.indexOf(file.leafName) == -1) {
+ file.remove(true);
+ }
+ }
+ }
+ },
+ 'profile-before-change-telemetry', false);
+
+ let appStartup = Cc['@mozilla.org/toolkit/app-startup;1']
+ .getService(Ci.nsIAppStartup);
+ appStartup.quit(Ci.nsIAppStartup.eForceQuit);
+}, 'b2g-reset-profile', false);
+
+var showInstallScreen;
+
+if (AppConstants.MOZ_GRAPHENE) {
+ const restoreWindowGeometry = () => {
+ let screenX = Services.prefs.getIntPref("b2g.nativeWindowGeometry.screenX");
+ let screenY = Services.prefs.getIntPref("b2g.nativeWindowGeometry.screenY");
+ let width = Services.prefs.getIntPref("b2g.nativeWindowGeometry.width");
+ let height = Services.prefs.getIntPref("b2g.nativeWindowGeometry.height");
+
+ if (screenX == -1) {
+ // Center
+ screenX = (screen.width - width) / 2;
+ screenY = (screen.height - height) / 2;
+ }
+
+ moveTo(screenX, screenY);
+ resizeTo(width, height);
+ }
+ restoreWindowGeometry();
+
+ const saveWindowGeometry = () => {
+ window.removeEventListener("unload", saveWindowGeometry);
+ Services.prefs.setIntPref("b2g.nativeWindowGeometry.screenX", screenX);
+ Services.prefs.setIntPref("b2g.nativeWindowGeometry.screenY", screenY);
+ Services.prefs.setIntPref("b2g.nativeWindowGeometry.width", outerWidth);
+ Services.prefs.setIntPref("b2g.nativeWindowGeometry.height", outerHeight);
+ }
+ window.addEventListener("unload", saveWindowGeometry);
+
+ var baseWindow = window.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIWebNavigation)
+ .QueryInterface(Ci.nsIDocShellTreeItem)
+ .treeOwner
+ .QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIBaseWindow);
+
+ const showNativeWindow = () => baseWindow.visibility = true;
+ const hideNativeWindow = () => baseWindow.visibility = false;
+
+ showInstallScreen = () => {
+ const grapheneStrings =
+ Services.strings.createBundle('chrome://b2g-l10n/locale/graphene.properties');
+ document.querySelector('#installing > .message').textContent =
+ grapheneStrings.GetStringFromName('installing');
+ showNativeWindow();
+ }
+
+ const hideInstallScreen = () => {
+ document.body.classList.add('content-loaded');
+ }
+
+ window.addEventListener('ContentStart', () => {
+ shell.contentBrowser.contentWindow.addEventListener('load', () => {
+ hideInstallScreen();
+ showNativeWindow();
+ });
+ });
+
+ hideNativeWindow();
+}
diff --git a/b2g/chrome/content/shell_remote.html b/b2g/chrome/content/shell_remote.html
new file mode 100644
index 0000000000..4f3f6efc8f
--- /dev/null
+++ b/b2g/chrome/content/shell_remote.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<!-- 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/. -->
+
+<html xmlns="http://www.w3.org/1999/xhtml"
+ id="shellRemote"
+ windowtype="navigator:remote-browser"
+ sizemode="fullscreen"
+ >
+
+<head>
+ <link rel="stylesheet" href="shell.css" type="text/css">
+ <script type="application/javascript;version=1.8"
+ src="chrome://b2g/content/shell_remote.js"> </script>
+</head>
+ <body id="container">
+ </body>
+</html>
diff --git a/b2g/chrome/content/shell_remote.js b/b2g/chrome/content/shell_remote.js
new file mode 100644
index 0000000000..1f1115ef0a
--- /dev/null
+++ b/b2g/chrome/content/shell_remote.js
@@ -0,0 +1,139 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- /
+/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
+/* 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";
+
+var {classes: Cc, interfaces: Ci, utils: Cu} = Components;
+
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/SystemAppProxy.jsm");
+
+function debug(aStr) {
+ // dump(" -*- ShellRemote.js: " + aStr + "\n");
+}
+
+var remoteShell = {
+
+ _started: false,
+
+ get homeURL() {
+ let systemAppManifestURL = Services.io.newURI(this.systemAppManifestURL, null, null);
+ let shellRemoteURL = Services.prefs.getCharPref("b2g.multiscreen.system_remote_url");
+ shellRemoteURL = Services.io.newURI(shellRemoteURL, null, systemAppManifestURL);
+ return shellRemoteURL.spec;
+ },
+
+ get systemAppManifestURL() {
+ return Services.prefs.getCharPref("b2g.system_manifest_url");
+ },
+
+ hasStarted: function () {
+ return this._started;
+ },
+
+ start: function () {
+ this._started = true;
+ this._isEventListenerReady = false;
+ this.id = window.location.hash.substring(1);
+
+ let homeURL = this.homeURL;
+ if (!homeURL) {
+ debug("ERROR! Remote home URL undefined.");
+ return;
+ }
+ let manifestURL = this.systemAppManifestURL;
+ // <html:iframe id="this.id"
+ // mozbrowser="true"
+ // allowfullscreen="true"
+ // src="blank.html"/>
+ let systemAppFrame =
+ document.createElementNS("http://www.w3.org/1999/xhtml", "html:iframe");
+ systemAppFrame.setAttribute("id", this.id);
+ systemAppFrame.setAttribute("mozbrowser", "true");
+ systemAppFrame.setAttribute("mozapp", manifestURL);
+ systemAppFrame.setAttribute("allowfullscreen", "true");
+ systemAppFrame.setAttribute("src", "blank.html");
+
+ let container = document.getElementById("container");
+ this.contentBrowser = container.appendChild(systemAppFrame);
+ this.contentBrowser.src = homeURL + window.location.hash;
+
+ window.addEventListener("unload", this);
+ this.contentBrowser.addEventListener("mozbrowserloadstart", this);
+ },
+
+ stop: function () {
+ window.removeEventListener("unload", this);
+ this.contentBrowser.removeEventListener("mozbrowserloadstart", this);
+ this.contentBrowser.removeEventListener("mozbrowserlocationchange", this, true);
+ SystemAppProxy.unregisterFrameWithId(this.id);
+ },
+
+ notifyContentStart: function(evt) {
+ this.contentBrowser.removeEventListener("mozbrowserloadstart", this);
+ this.contentBrowser.removeEventListener("mozbrowserlocationchange", this, true);
+
+ SystemAppProxy.registerFrameWithId(remoteShell.id, remoteShell.contentBrowser);
+ SystemAppProxy.addEventListenerWithId(this.id, "mozContentEvent", this);
+
+ let content = this.contentBrowser.contentWindow;
+ content.addEventListener("load", this, true);
+ },
+
+ notifyContentWindowLoaded: function () {
+ SystemAppProxy.setIsLoadedWithId(this.id);
+ },
+
+ notifyEventListenerReady: function () {
+ if (this._isEventListenerReady) {
+ Cu.reportError("shell_remote.js: SystemApp has already been declared as being ready.");
+ return;
+ }
+ this._isEventListenerReady = true;
+ SystemAppProxy.setIsReadyWithId(this.id);
+ },
+
+ handleEvent: function(evt) {
+ debug("Got an event: " + evt.type);
+ let content = this.contentBrowser.contentWindow;
+
+ switch(evt.type) {
+ case "mozContentEvent":
+ if (evt.detail.type === "system-message-listener-ready") {
+ this.notifyEventListenerReady();
+ }
+ break;
+ case "load":
+ if (content.document.location == "about:blank") {
+ return;
+ }
+ content.removeEventListener("load", this, true);
+ this.notifyContentWindowLoaded();
+ break;
+ case "mozbrowserloadstart":
+ if (content.document.location == "about:blank") {
+ this.contentBrowser.addEventListener("mozbrowserlocationchange", this, true);
+ return;
+ }
+ case "mozbrowserlocationchange":
+ if (content.document.location == "about:blank") {
+ return;
+ }
+ this.notifyContentStart();
+ break;
+ case "unload":
+ this.stop();
+ break;
+ }
+ }
+};
+
+window.onload = function() {
+ if (remoteShell.hasStarted() == false) {
+ remoteShell.start();
+ }
+};
+
diff --git a/b2g/chrome/content/test/mochitest/RecordingStatusChromeScript.js b/b2g/chrome/content/test/mochitest/RecordingStatusChromeScript.js
new file mode 100644
index 0000000000..1a5ed82743
--- /dev/null
+++ b/b2g/chrome/content/test/mochitest/RecordingStatusChromeScript.js
@@ -0,0 +1,40 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+var { classes: Cc, interfaces: Ci, results: Cr, utils: Cu } = Components;
+const { Services } = Cu.import('resource://gre/modules/Services.jsm');
+const { SystemAppProxy } = Cu.import('resource://gre/modules/SystemAppProxy.jsm');
+
+var processId;
+
+function peekChildId(aSubject, aTopic, aData) {
+ Services.obs.removeObserver(peekChildId, 'recording-device-events');
+ Services.obs.removeObserver(peekChildId, 'recording-device-ipc-events');
+ let props = aSubject.QueryInterface(Ci.nsIPropertyBag2);
+ if (props.hasKey('childID')) {
+ processId = props.get('childID');
+ }
+}
+
+addMessageListener('init-chrome-event', function(message) {
+ // listen mozChromeEvent and forward to content process.
+ let type = message.type;
+ SystemAppProxy.addEventListener('mozChromeEvent', function(event) {
+ let details = event.detail;
+ if (details.type === type) {
+ sendAsyncMessage('chrome-event', details);
+ }
+ }, true);
+
+ Services.obs.addObserver(peekChildId, 'recording-device-events', false);
+ Services.obs.addObserver(peekChildId, 'recording-device-ipc-events', false);
+});
+
+addMessageListener('fake-content-shutdown', function(message) {
+ let props = Cc["@mozilla.org/hash-property-bag;1"]
+ .createInstance(Ci.nsIWritablePropertyBag2);
+ if (processId) {
+ props.setPropertyAsUint64('childID', processId);
+ }
+ Services.obs.notifyObservers(props, 'recording-device-ipc-events', 'content-shutdown');
+});
diff --git a/b2g/chrome/content/test/mochitest/RecordingStatusHelper.js b/b2g/chrome/content/test/mochitest/RecordingStatusHelper.js
new file mode 100644
index 0000000000..5e3e6814e1
--- /dev/null
+++ b/b2g/chrome/content/test/mochitest/RecordingStatusHelper.js
@@ -0,0 +1,82 @@
+'use strict';
+
+// resolve multiple promise in parallel
+function expectAll(aValue) {
+ let deferred = new Promise(function(resolve, reject) {
+ let countdown = aValue.length;
+ let resolutionValues = new Array(countdown);
+
+ for (let i = 0; i < aValue.length; i++) {
+ let index = i;
+ aValue[i].then(function(val) {
+ resolutionValues[index] = val;
+ if (--countdown === 0) {
+ resolve(resolutionValues);
+ }
+ }, reject);
+ }
+ });
+
+ return deferred;
+}
+
+function TestInit() {
+ let url = SimpleTest.getTestFileURL("RecordingStatusChromeScript.js")
+ let script = SpecialPowers.loadChromeScript(url);
+
+ let helper = {
+ finish: function () {
+ script.destroy();
+ },
+ fakeShutdown: function () {
+ script.sendAsyncMessage('fake-content-shutdown', {});
+ }
+ };
+
+ script.addMessageListener('chrome-event', function (message) {
+ if (helper.hasOwnProperty('onEvent')) {
+ helper.onEvent(message);
+ } else {
+ ok(false, 'unexpected message: ' + JSON.stringify(message));
+ }
+ });
+
+ script.sendAsyncMessage("init-chrome-event", {
+ type: 'recording-status'
+ });
+
+ return Promise.resolve(helper);
+}
+
+function expectEvent(expected, eventHelper) {
+ return new Promise(function(resolve, reject) {
+ eventHelper.onEvent = function(message) {
+ delete eventHelper.onEvent;
+ ok(message, JSON.stringify(message));
+ is(message.type, 'recording-status', 'event type: ' + message.type);
+ is(message.active, expected.active, 'recording active: ' + message.active);
+ is(message.isAudio, expected.isAudio, 'audio recording active: ' + message.isAudio);
+ is(message.isVideo, expected.isVideo, 'video recording active: ' + message.isVideo);
+ resolve(eventHelper);
+ };
+ info('waiting for recording-status');
+ });
+}
+
+function expectStream(params, callback) {
+ return new Promise(function(resolve, reject) {
+ var req = navigator.mozGetUserMedia(
+ params,
+ function(stream) {
+ ok(true, 'create media stream');
+ callback(stream);
+ resolve();
+ },
+ function(err) {
+ ok(false, 'fail to create media stream');
+ reject(err);
+ }
+ );
+ info('waiting for gUM result');
+ });
+}
diff --git a/b2g/chrome/content/test/mochitest/file_getusermedia_iframe.html b/b2g/chrome/content/test/mochitest/file_getusermedia_iframe.html
new file mode 100644
index 0000000000..f2b18eab3f
--- /dev/null
+++ b/b2g/chrome/content/test/mochitest/file_getusermedia_iframe.html
@@ -0,0 +1,36 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Iframe for Recording Status</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript;version=1.7" src="RecordingStatusHelper.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+
+<pre id="test">
+<script class="testbody" type="text/javascript;version=1.7">
+
+var localStream;
+
+window.addEventListener('message', function(event) {
+ switch (event.data) {
+ case 'start':
+ let gumDeferred = expectStream({ audio: true,
+ fake: true
+ }, function(stream) {
+ localStream = stream;
+ event.source.postMessage('start-finished', window.location.origin);
+ });
+ break;
+ case 'stop':
+ localStream.stop();
+ localStream = null;
+ break;
+ }
+}, false);
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/b2g/chrome/content/test/mochitest/mochitest.ini b/b2g/chrome/content/test/mochitest/mochitest.ini
new file mode 100644
index 0000000000..d18a20401a
--- /dev/null
+++ b/b2g/chrome/content/test/mochitest/mochitest.ini
@@ -0,0 +1,11 @@
+[DEFAULT]
+skip-if = ((buildapp == 'mulet' || buildapp == 'b2g') && toolkit != 'gonk') #require OOP support for mochitest-b2g-desktop, Bug 957554
+support-files =
+ RecordingStatusChromeScript.js
+ RecordingStatusHelper.js
+ file_getusermedia_iframe.html
+
+[test_recordingStatus_basic.html]
+[test_recordingStatus_multiple_requests.html]
+[test_recordingStatus_iframe.html]
+[test_recordingStatus_kill_content_process.html]
diff --git a/b2g/chrome/content/test/mochitest/moz.build b/b2g/chrome/content/test/mochitest/moz.build
new file mode 100644
index 0000000000..3b13ba431a
--- /dev/null
+++ b/b2g/chrome/content/test/mochitest/moz.build
@@ -0,0 +1,7 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+MOCHITEST_MANIFESTS += ['mochitest.ini']
diff --git a/b2g/chrome/content/test/mochitest/test_recordingStatus_basic.html b/b2g/chrome/content/test/mochitest/test_recordingStatus_basic.html
new file mode 100644
index 0000000000..21f746d33c
--- /dev/null
+++ b/b2g/chrome/content/test/mochitest/test_recordingStatus_basic.html
@@ -0,0 +1,119 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for Recording Status</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript;version=1.7" src="RecordingStatusHelper.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+
+<pre id="test">
+<script class="testbody" type="text/javascript;version=1.7">
+'use strict';
+
+SimpleTest.waitForExplicitFinish();
+
+function test() {
+ let localStreams = [];
+ TestInit().then(function(eventHelper) {
+ /* step 1: create one audio stream
+ * expect: see one mozChromeEvent for audio recording start.
+ */
+ let eventDeferred = expectEvent({ active: true,
+ isAudio: true,
+ isVideo: false
+ }, eventHelper);
+
+ let gumDeferred = expectStream({ audio: true,
+ fake: true
+ }, function(stream) {
+ localStreams.push(stream);
+ });
+
+ return expectAll([eventDeferred, gumDeferred]);
+ }).then(function([eventHelper]) {
+ /* step 2: close the audio stream
+ * expect: see one mozChromeEvent for recording stop.
+ */
+ let eventDeferred = expectEvent({ active: false,
+ isAudio: false,
+ isVideo: false,
+ }, eventHelper);
+
+ localStreams.shift().stop();
+ info('stop audio stream');
+ return eventDeferred;
+ }).then(function(eventHelper) {
+ /* step 3: create one video stream
+ * expect: see one mozChromeEvent for video recording start
+ */
+ let eventDeferred = expectEvent({ active: true,
+ isAudio: false,
+ isVideo: true
+ }, eventHelper);
+
+ let gumDeferred = expectStream({ video: true,
+ fake: true
+ }, function(stream) {
+ localStreams.push(stream);
+ });
+
+ return expectAll([eventDeferred, gumDeferred]);
+ }).then(function([eventHelper]) {
+ /* step 4: close the audio stream
+ * expect: see one mozChromeEvent for recording stop.
+ */
+ let eventDeferred = expectEvent({ active: false,
+ isAudio: false,
+ isVideo: false,
+ }, eventHelper);
+
+ localStreams.shift().stop();
+ info('stop video stream');
+ return eventDeferred;
+ }).then(function(eventHelper) {
+ /* step 3: create one audio/video stream
+ * expect: see one mozChromeEvent for audio/video recording start
+ */
+ let eventDeferred = expectEvent({ active: true,
+ isAudio: true,
+ isVideo: true
+ }, eventHelper);
+
+ let gumDeferred = expectStream({ audio: true,
+ video: true,
+ fake: true
+ }, function(stream) {
+ localStreams.push(stream);
+ });
+
+ return expectAll([eventDeferred, gumDeferred]);
+ }).then(function([eventHelper]) {
+ /* step 4: close the audio stream
+ * expect: see one mozChromeEvent for recording stop.
+ */
+ let eventDeferred = expectEvent({ active: false,
+ isAudio: false,
+ isVideo: false,
+ }, eventHelper);
+
+ localStreams.shift().stop();
+ info('stop audio/video stream');
+ return eventDeferred;
+ }).then(function(eventHelper) {
+ eventHelper.finish();
+ SimpleTest.finish();
+ });
+}
+
+SpecialPowers.pushPrefEnv({
+ "set": [
+ ['media.navigator.permission.disabled', true]
+ ]
+}, test);
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/b2g/chrome/content/test/mochitest/test_recordingStatus_iframe.html b/b2g/chrome/content/test/mochitest/test_recordingStatus_iframe.html
new file mode 100644
index 0000000000..88c33c897b
--- /dev/null
+++ b/b2g/chrome/content/test/mochitest/test_recordingStatus_iframe.html
@@ -0,0 +1,71 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for Recording Status in iframe</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript;version=1.7" src="RecordingStatusHelper.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+
+<pre id="test">
+<iframe id="gum-iframe"></iframe>
+<script class="testbody" type="text/javascript;version=1.7">
+SimpleTest.waitForExplicitFinish();
+
+function test() {
+ TestInit().then(function(eventHelper) {
+ /* step 1: load iframe whilch creates audio stream
+ * expect: see one mozChromeEvent for audio recording start.
+ */
+ let eventDeferred = expectEvent({ active: true,
+ isAudio: true,
+ isVideo: false
+ }, eventHelper);
+
+ let loadDeferred = new Promise(function(resolve, reject) {
+ let gumIframe = document.getElementById('gum-iframe');
+ gumIframe.src = 'file_getusermedia_iframe.html';
+
+ window.addEventListener('message', function(event) {
+ if (event.data === 'start-finished') {
+ resolve();
+ }
+ }, false);
+
+ gumIframe.onload = function() {
+ info('start audio stream in iframe');
+ gumIframe.contentWindow.postMessage('start', window.location.origin);
+ };
+ });
+
+ return expectAll([eventDeferred, loadDeferred]);
+ }).then(function([eventHelper]) {
+ /* step 2: close the audio stream
+ * expect: see one mozChromeEvent for recording stop.
+ */
+ let eventDeferred = expectEvent({ active: false,
+ isAudio: false,
+ isVideo: false
+ }, eventHelper);
+
+ let win = document.getElementById('gum-iframe').contentWindow;
+ win.postMessage('stop', window.location.origin);
+ info('stop audio stream in iframe');
+ return eventDeferred;
+ }).then(function(eventHelper) {
+ eventHelper.finish();
+ SimpleTest.finish();
+ });
+}
+
+SpecialPowers.pushPrefEnv({
+ "set": [
+ ['media.navigator.permission.disabled', true]
+ ]
+}, test);
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/b2g/chrome/content/test/mochitest/test_recordingStatus_kill_content_process.html b/b2g/chrome/content/test/mochitest/test_recordingStatus_kill_content_process.html
new file mode 100644
index 0000000000..239c2c2d55
--- /dev/null
+++ b/b2g/chrome/content/test/mochitest/test_recordingStatus_kill_content_process.html
@@ -0,0 +1,72 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for Recording Status after process shutdown</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript;version=1.7" src="RecordingStatusHelper.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+
+<pre id="test">
+<script class="testbody" type="text/javascript;version=1.7">
+SimpleTest.waitForExplicitFinish();
+
+function test() {
+ let localStreams = [];
+ TestInit().then(function(eventHelper) {
+ /* step 1: load iframe whilch creates audio stream
+ * expect: see one mozChromeEvent for audio recording start.
+ */
+ let eventDeferred = expectEvent({ active: true,
+ isAudio: true,
+ isVideo: false
+ }, eventHelper);
+
+ let gumDeferred = expectStream({ audio: true,
+ fake: true
+ }, function(stream) { localStreams.push(stream); });
+
+ return expectAll([eventDeferred, gumDeferred]);
+ }).then(function([eventHelper]) {
+ /* step 2: create video stream
+ * expect: see one mozChromeEvent for audio recording start.
+ */
+ let eventDeferred = expectEvent({ active: true,
+ isAudio: true,
+ isVideo: true
+ }, eventHelper);
+
+ let gumDeferred = expectStream({ video: true,
+ fake: true
+ }, function(stream) { localStreams.push(stream); });
+
+ return expectAll([eventDeferred, gumDeferred]);
+ }).then(function([eventHelper]) {
+ /* step 3: close the audio stream
+ * expect: see one mozChromeEvent for recording stop.
+ */
+ let eventDeferred = expectEvent({ active: false,
+ isAudio: false,
+ isVideo: false
+ }, eventHelper);
+
+ eventHelper.fakeShutdown();
+ info('simulate content process been killed');
+ return eventDeferred;
+ }).then(function(eventHelper) {
+ eventHelper.finish();
+ SimpleTest.finish();
+ });
+}
+
+SpecialPowers.pushPrefEnv({
+ "set": [
+ ['media.navigator.permission.disabled', true]
+ ]
+}, test);
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/b2g/chrome/content/test/mochitest/test_recordingStatus_multiple_requests.html b/b2g/chrome/content/test/mochitest/test_recordingStatus_multiple_requests.html
new file mode 100644
index 0000000000..7d31a94f8f
--- /dev/null
+++ b/b2g/chrome/content/test/mochitest/test_recordingStatus_multiple_requests.html
@@ -0,0 +1,108 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for Recording Status with multiple gUM requests</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript;version=1.7" src="RecordingStatusHelper.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+
+<pre id="test">
+<script class="testbody" type="text/javascript;version=1.7">
+'use strict';
+
+SimpleTest.waitForExplicitFinish();
+
+function test() {
+ let localStreams = [];
+ TestInit().then(function(eventHelper) {
+ /* step 1: create one audio stream
+ * expect: see one mozChromeEvent for recording start.
+ */
+ let eventDeferred = expectEvent({ active: true,
+ isAudio: true,
+ isVideo: false
+ }, eventHelper);
+
+ let gumDeferred = expectStream({ audio: true,
+ fake: true
+ }, function(stream) {
+ localStreams.push(stream);
+ });
+
+ return expectAll([eventDeferred, gumDeferred]);
+ }).then(function([eventHelper]) {
+ /* step 2: create another audio stream
+ * expect: no mozChromeEvent after audio stream is created
+ */
+ let gumDeferred = expectStream({ audio: true,
+ fake: true
+ }, function(stream) {
+ localStreams.push(stream);
+ });
+
+ return expectAll([Promise.resolve(eventHelper), gumDeferred]);
+ }).then(function([eventHelper]) {
+ /* step 3: create video stream
+ * expect: see one mozChromeEvent for recording start
+ */
+ let eventDeferred = expectEvent({ active: true,
+ isAudio: true,
+ isVideo: true
+ }, eventHelper);
+
+ let gumDeferred = expectStream({ video: true,
+ fake: true
+ }, function(stream) {
+ localStreams.push(stream);
+ });
+
+ return expectAll([eventDeferred, gumDeferred]);
+ }).then(function([eventHelper]) {
+ /* step 4: stop first audio stream
+ * expect: no mozChromeEvent after first audio stream is stopped
+ */
+ localStreams.shift().stop();
+ info('stop the first audio stream');
+ return Promise.resolve(eventHelper);
+ }).then(function(eventHelper) {
+ /* step 5: stop the second audio stream
+ * expect: see one mozChromeEvent for audio recording stop.
+ */
+ let eventDeferred = expectEvent({ active: true,
+ isAudio: false,
+ isVideo: true
+ }, eventHelper);
+
+ localStreams.shift().stop();
+ info('stop the second audio stream');
+ return eventDeferred;
+ }).then(function(eventHelper) {
+ /* step 6: stop the video stream
+ * expect: see one mozChromeEvent for video recording stop.
+ */
+ let eventDeferred = expectEvent({ active: false,
+ isAudio: false,
+ isVideo: false
+ }, eventHelper);
+
+ localStreams.shift().stop();
+ info('stop the video stream');
+ return eventDeferred;
+ }).then(function(eventHelper) {
+ eventHelper.finish();
+ SimpleTest.finish();
+ });
+}
+
+SpecialPowers.pushPrefEnv({
+ "set": [
+ ['media.navigator.permission.disabled', true]
+ ]
+}, test);
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/b2g/chrome/content/touchcontrols.css b/b2g/chrome/content/touchcontrols.css
new file mode 100644
index 0000000000..7c407c331d
--- /dev/null
+++ b/b2g/chrome/content/touchcontrols.css
@@ -0,0 +1,233 @@
+/* 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/. */
+
+@namespace url(http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul);
+
+/* video controls */
+.controlsOverlay {
+ -moz-box-pack: center;
+ -moz-box-align: end;
+ -moz-box-flex: 1;
+ -moz-box-orient: horizontal;
+}
+
+.controlsOverlay[scaled] {
+ /* scaled attribute in videocontrols.css causes conflict
+ due to different -moz-box-orient values */
+ -moz-box-align: end;
+}
+
+.controlBar {
+ -moz-box-flex: 1;
+ background-color: rgba(50,50,50,0.8);
+ width: 100%;
+}
+
+.buttonsBar {
+ -moz-box-flex: 1;
+ -moz-box-align: center;
+}
+
+.controlsSpacer {
+ display: none;
+ -moz-box-flex: 0;
+}
+
+.fullscreenButton,
+.playButton,
+.castingButton,
+.muteButton {
+ -moz-appearance: none;
+ padding: 2px;
+ border: none !important;
+ min-height: 24px;
+ min-width: 24px;
+}
+
+.fullscreenButton {
+ background: url("chrome://b2g/content/images/fullscreen-hdpi.png") no-repeat center;
+ background-size: contain;
+ background-origin: content-box;
+}
+
+.fullscreenButton[fullscreened="true"] {
+ background: url("chrome://b2g/content/images/exitfullscreen-hdpi.png") no-repeat center;
+ background-size: contain;
+ background-origin: content-box;
+}
+
+.controlBar[fullscreen-unavailable] .fullscreenButton {
+ display: none;
+}
+
+.playButton {
+ background: url("chrome://b2g/content/images/pause-hdpi.png") no-repeat center;
+ background-size: contain;
+ background-origin: content-box;
+}
+
+/*
+ * Normally the button bar has fullscreen spacer play spacer mute, but if
+ * this is an audio control rather than a video control, the fullscreen button
+ * is hidden by videocontrols.xml, and that alters the position of the
+ * play button. This workaround moves it back to center.
+ */
+.controlBar[fullscreen-unavailable] .playButton {
+ transform: translateX(28px);
+}
+
+.playButton[paused="true"] {
+ background: url("chrome://b2g/content/images/play-hdpi.png") no-repeat center;
+ background-size: contain;
+ background-origin: content-box;
+}
+
+.castingButton {
+ display: none;
+}
+
+.muteButton {
+ background: url("chrome://b2g/content/images/mute-hdpi.png") no-repeat center;
+ background-size: contain;
+ background-origin: content-box;
+}
+
+.muteButton[muted="true"] {
+ background: url("chrome://b2g/content/images/unmute-hdpi.png") no-repeat center;
+ background-size: contain;
+ background-origin: content-box;
+}
+
+/* bars */
+.scrubberStack {
+ -moz-box-flex: 1;
+ padding: 0px 18px;
+}
+
+.flexibleBar,
+.flexibleBar .progress-bar,
+.bufferBar,
+.bufferBar .progress-bar,
+.progressBar,
+.progressBar .progress-bar,
+.scrubber,
+.scrubber .scale-slider,
+.scrubber .scale-thumb {
+ -moz-appearance: none;
+ border: none;
+ padding: 0px;
+ margin: 0px;
+ background-color: transparent;
+}
+
+.flexibleBar,
+.bufferBar,
+.progressBar {
+ height: 24px;
+ padding: 11px 0px;
+}
+
+.flexibleBar {
+ padding: 12px 0px;
+}
+
+.flexibleBar .progress-bar {
+ border: 1px #777777 solid;
+ border-radius: 1px;
+}
+
+.bufferBar .progress-bar {
+ border: 2px #AFB1B3 solid;
+ border-radius: 2px;
+}
+
+.progressBar .progress-bar {
+ border: 2px #FF9500 solid;
+ border-radius: 2px;
+}
+
+
+.scrubber {
+ margin-left: -8px;
+ margin-right: -8px;
+}
+
+.positionLabel, .durationLabel {
+ font-family: 'Roboto', Helvetica, Arial, sans-serif;
+ font-size: 12px;
+ color: white;
+}
+
+.scrubber .scale-thumb {
+ display: -moz-box;
+ margin: 0px !important;
+ padding: 0px !important;
+ background: url("chrome://b2g/content/images/scrubber-hdpi.png") no-repeat;
+ background-size: 12px 12px;
+ height: 12px;
+ width: 12px;
+}
+
+.statusOverlay {
+ -moz-box-align: center;
+ -moz-box-pack: center;
+ background-color: rgb(50,50,50);
+}
+
+.statusIcon {
+ margin-bottom: 28px;
+ width: 36px;
+ height: 36px;
+}
+
+.statusIcon[type="throbber"] {
+ background: url("chrome://b2g/content/images/throbber.png") no-repeat center;
+}
+
+.statusIcon[type="error"] {
+ background: url("chrome://b2g/content/images/error.png") no-repeat center;
+}
+
+/* CSS Transitions */
+.controlBar:not([immediate]) {
+ transition-property: opacity;
+ transition-duration: 200ms;
+}
+
+.controlBar[fadeout] {
+ opacity: 0;
+}
+
+.statusOverlay:not([immediate]) {
+ transition-property: opacity;
+ transition-duration: 300ms;
+ transition-delay: 750ms;
+}
+
+.statusOverlay[fadeout] {
+ opacity: 0;
+}
+
+.volumeStack,
+.controlBar[firstshow="true"] .fullscreenButton,
+.controlBar[firstshow="true"] .muteButton,
+.controlBar[firstshow="true"] .scrubberStack,
+.controlBar[firstshow="true"] .durationBox,
+.timeLabel {
+ display: none;
+}
+
+/* Error description formatting */
+.errorLabel {
+ font-family: Helvetica, Arial, sans-serif;
+ font-size: 11px;
+ color: #bbb;
+ text-shadow:
+ -1px -1px 0 #000,
+ 1px -1px 0 #000,
+ -1px 1px 0 #000,
+ 1px 1px 0 #000;
+ padding: 0 10px;
+ text-align: center;
+}
diff --git a/b2g/chrome/jar.mn b/b2g/chrome/jar.mn
new file mode 100644
index 0000000000..1fdea9a50f
--- /dev/null
+++ b/b2g/chrome/jar.mn
@@ -0,0 +1,60 @@
+#filter substitution
+# 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/.
+
+
+chrome.jar:
+% content branding %content/branding/ contentaccessible=yes
+% content b2g %content/
+
+ content/arrow.svg (content/arrow.svg)
+ content/settings.js (content/settings.js)
+* content/shell.html (content/shell.html)
+ content/shell.js (content/shell.js)
+ content/shell_remote.html (content/shell_remote.html)
+ content/shell_remote.js (content/shell_remote.js)
+* content/shell.css (content/shell.css)
+ content/blank.html (content/blank.html)
+ content/blank.css (content/blank.css)
+#ifdef MOZ_WIDGET_GONK
+ content/devtools/adb.js (content/devtools/adb.js)
+#endif
+ content/devtools/debugger.js (content/devtools/debugger.js)
+ content/devtools/hud.js (content/devtools/hud.js)
+#ifndef MOZ_WIDGET_GONK
+ content/desktop.css (content/desktop.css)
+ content/images/desktop/home-black.png (content/images/desktop/home-black.png)
+ content/images/desktop/home-white.png (content/images/desktop/home-white.png)
+ content/images/desktop/rotate.png (content/images/desktop/rotate.png)
+ content/desktop.js (content/desktop.js)
+ content/screen.js (content/screen.js)
+#endif
+* content/content.css (content/content.css)
+ content/touchcontrols.css (content/touchcontrols.css)
+
+ content/identity.js (content/identity.js)
+
+#ifndef MOZ_GRAPHENE
+% override chrome://global/skin/media/videocontrols.css chrome://b2g/content/touchcontrols.css
+#endif
+% override chrome://global/content/aboutCertError.xhtml chrome://b2g/content/aboutCertError.xhtml
+% override chrome://global/skin/netError.css chrome://b2g/content/netError.css
+
+ content/ErrorPage.js (content/ErrorPage.js)
+ content/aboutCertError.xhtml (content/aboutCertError.xhtml)
+ content/netError.css (content/netError.css)
+ content/images/errorpage-larry-black.png (content/images/errorpage-larry-black.png)
+ content/images/errorpage-larry-white.png (content/images/errorpage-larry-white.png)
+ content/images/errorpage-warning.png (content/images/errorpage-warning.png)
+ content/images/arrowdown-16.png (content/images/arrowdown-16.png)
+ content/images/arrowright-16.png (content/images/arrowright-16.png)
+ content/images/scrubber-hdpi.png (content/images/scrubber-hdpi.png)
+ content/images/unmute-hdpi.png (content/images/unmute-hdpi.png)
+ content/images/pause-hdpi.png (content/images/pause-hdpi.png)
+ content/images/play-hdpi.png (content/images/play-hdpi.png)
+ content/images/mute-hdpi.png (content/images/mute-hdpi.png)
+ content/images/fullscreen-hdpi.png (content/images/fullscreen-hdpi.png)
+ content/images/exitfullscreen-hdpi.png (content/images/exitfullscreen-hdpi.png)
+ content/images/throbber.png (content/images/throbber.png)
+ content/images/error.png (content/images/error.png)
diff --git a/b2g/chrome/moz.build b/b2g/chrome/moz.build
new file mode 100644
index 0000000000..99af44a5ca
--- /dev/null
+++ b/b2g/chrome/moz.build
@@ -0,0 +1,13 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+DEFINES['AB_CD'] = CONFIG['MOZ_UI_LOCALE']
+DEFINES['PACKAGE'] = 'b2g'
+DEFINES['MOZ_APP_VERSION'] = CONFIG['MOZ_APP_VERSION']
+
+JAR_MANIFESTS += ['jar.mn']
+
+TEST_DIRS += ['content/test/mochitest']
diff --git a/b2g/common.configure b/b2g/common.configure
new file mode 100644
index 0000000000..854de4b15d
--- /dev/null
+++ b/b2g/common.configure
@@ -0,0 +1,32 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+# Truetype fonts for B2G
+# ==============================================================
+option(env='MOZTTDIR', nargs=1, help='Path to truetype fonts for B2G')
+
+@depends('MOZTTDIR')
+@imports('os')
+def mozttdir(value):
+ if value:
+ path = value[0]
+ if not os.path.isdir(path):
+ die('MOZTTDIR "%s" is not a valid directory', path)
+ return path
+
+set_config('MOZTTDIR', mozttdir)
+
+@depends('MOZTTDIR')
+def package_moztt(value):
+ if value:
+ return True
+
+set_define('PACKAGE_MOZTT', package_moztt)
+
+imply_option('MOZ_ENABLE_WARNINGS_AS_ERRORS',
+ depends(target)(lambda t: t.os == 'Android'), reason='--target')
+
+include('../toolkit/moz.configure')
diff --git a/b2g/components/AboutServiceWorkers.jsm b/b2g/components/AboutServiceWorkers.jsm
new file mode 100644
index 0000000000..fe67e9c348
--- /dev/null
+++ b/b2g/components/AboutServiceWorkers.jsm
@@ -0,0 +1,183 @@
+/* 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"
+
+this.EXPORTED_SYMBOLS = ["AboutServiceWorkers"];
+
+const { interfaces: Ci, utils: Cu } = Components;
+
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "SystemAppProxy",
+ "resource://gre/modules/SystemAppProxy.jsm");
+
+XPCOMUtils.defineLazyServiceGetter(this, "gServiceWorkerManager",
+ "@mozilla.org/serviceworkers/manager;1",
+ "nsIServiceWorkerManager");
+
+function debug(aMsg) {
+ dump("AboutServiceWorkers - " + aMsg + "\n");
+}
+
+function serializeServiceWorkerInfo(aServiceWorkerInfo) {
+ if (!aServiceWorkerInfo) {
+ throw new Error("Invalid service worker information");
+ }
+
+ let result = {};
+
+ result.principal = {
+ origin: aServiceWorkerInfo.principal.originNoSuffix,
+ originAttributes: aServiceWorkerInfo.principal.originAttributes
+ };
+
+ ["scope", "scriptSpec"].forEach(property => {
+ result[property] = aServiceWorkerInfo[property];
+ });
+
+ return result;
+}
+
+
+this.AboutServiceWorkers = {
+ get enabled() {
+ if (this._enabled) {
+ return this._enabled;
+ }
+ this._enabled = false;
+ try {
+ this._enabled = Services.prefs.getBoolPref("dom.serviceWorkers.enabled");
+ } catch(e) {}
+ return this._enabled;
+ },
+
+ init: function() {
+ SystemAppProxy.addEventListener("mozAboutServiceWorkersContentEvent",
+ AboutServiceWorkers);
+ },
+
+ sendResult: function(aId, aResult) {
+ SystemAppProxy._sendCustomEvent("mozAboutServiceWorkersChromeEvent", {
+ id: aId,
+ result: aResult
+ });
+ },
+
+ sendError: function(aId, aError) {
+ SystemAppProxy._sendCustomEvent("mozAboutServiceWorkersChromeEvent", {
+ id: aId,
+ error: aError
+ });
+ },
+
+ handleEvent: function(aEvent) {
+ let message = aEvent.detail;
+
+ debug("Got content event " + JSON.stringify(message));
+
+ if (!message.id || !message.name) {
+ dump("Invalid event " + JSON.stringify(message) + "\n");
+ return;
+ }
+
+ let self = AboutServiceWorkers;
+
+ switch(message.name) {
+ case "init":
+ if (!self.enabled) {
+ self.sendResult(message.id, {
+ enabled: false,
+ registrations: []
+ });
+ return;
+ };
+
+ let data = gServiceWorkerManager.getAllRegistrations();
+ if (!data) {
+ self.sendError(message.id, "NoServiceWorkersRegistrations");
+ return;
+ }
+
+ let registrations = [];
+
+ for (let i = 0; i < data.length; i++) {
+ let info = data.queryElementAt(i, Ci.nsIServiceWorkerRegistrationInfo);
+ if (!info) {
+ dump("AboutServiceWorkers: Invalid nsIServiceWorkerRegistrationInfo " +
+ "interface.\n");
+ continue;
+ }
+ registrations.push(serializeServiceWorkerInfo(info));
+ }
+
+ self.sendResult(message.id, {
+ enabled: self.enabled,
+ registrations: registrations
+ });
+ break;
+
+ case "update":
+ if (!message.scope) {
+ self.sendError(message.id, "MissingScope");
+ return;
+ }
+
+ if (!message.principal ||
+ !message.principal.originAttributes) {
+ self.sendError(message.id, "MissingOriginAttributes");
+ return;
+ }
+
+ gServiceWorkerManager.propagateSoftUpdate(
+ message.principal.originAttributes,
+ message.scope
+ );
+
+ self.sendResult(message.id, true);
+ break;
+
+ case "unregister":
+ if (!message.principal ||
+ !message.principal.origin ||
+ !message.principal.originAttributes ||
+ !message.principal.originAttributes.appId ||
+ (message.principal.originAttributes.inIsolatedMozBrowser == null)) {
+ self.sendError(message.id, "MissingPrincipal");
+ return;
+ }
+
+ let principal = Services.scriptSecurityManager.createCodebasePrincipal(
+ // TODO: Bug 1196652. use originNoSuffix
+ Services.io.newURI(message.principal.origin, null, null),
+ message.principal.originAttributes);
+
+ if (!message.scope) {
+ self.sendError(message.id, "MissingScope");
+ return;
+ }
+
+ let serviceWorkerUnregisterCallback = {
+ unregisterSucceeded: function() {
+ self.sendResult(message.id, true);
+ },
+
+ unregisterFailed: function() {
+ self.sendError(message.id, "UnregisterError");
+ },
+
+ QueryInterface: XPCOMUtils.generateQI([
+ Ci.nsIServiceWorkerUnregisterCallback
+ ])
+ };
+ gServiceWorkerManager.propagateUnregister(principal,
+ serviceWorkerUnregisterCallback,
+ message.scope);
+ break;
+ }
+ }
+};
+
+AboutServiceWorkers.init();
diff --git a/b2g/components/ActivityChannel.jsm b/b2g/components/ActivityChannel.jsm
new file mode 100644
index 0000000000..9dfa13d67c
--- /dev/null
+++ b/b2g/components/ActivityChannel.jsm
@@ -0,0 +1,64 @@
+/* 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 {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
+
+Cu.import('resource://gre/modules/XPCOMUtils.jsm');
+
+XPCOMUtils.defineLazyServiceGetter(this, "cpmm",
+ "@mozilla.org/childprocessmessagemanager;1",
+ "nsIMessageSender");
+
+XPCOMUtils.defineLazyServiceGetter(this, "contentSecManager",
+ "@mozilla.org/contentsecuritymanager;1",
+ "nsIContentSecurityManager");
+
+this.EXPORTED_SYMBOLS = ["ActivityChannel"];
+
+this.ActivityChannel = function(aURI, aLoadInfo, aName, aDetails) {
+ this._activityName = aName;
+ this._activityDetails = aDetails;
+ this.originalURI = aURI;
+ this.URI = aURI;
+ this.loadInfo = aLoadInfo;
+}
+
+this.ActivityChannel.prototype = {
+ originalURI: null,
+ URI: null,
+ owner: null,
+ notificationCallbacks: null,
+ securityInfo: null,
+ contentType: null,
+ contentCharset: null,
+ contentLength: 0,
+ contentDisposition: Ci.nsIChannel.DISPOSITION_INLINE,
+ contentDispositionFilename: null,
+ contentDispositionHeader: null,
+ loadInfo: null,
+
+ open: function() {
+ throw Cr.NS_ERROR_NOT_IMPLEMENTED;
+ },
+
+ open2: function() {
+ throw Cr.NS_ERROR_NOT_IMPLEMENTED;
+ },
+
+ asyncOpen: function(aListener, aContext) {
+ cpmm.sendAsyncMessage(this._activityName, this._activityDetails);
+ // Let the listener cleanup.
+ aListener.onStopRequest(this, aContext, Cr.NS_OK);
+ },
+
+ asyncOpen2: function(aListener) {
+ // throws an error if security checks fail
+ var outListener = contentSecManager.performSecurityCheck(this, aListener);
+ this.asyncOpen(outListener, null);
+ },
+
+ QueryInterface2: XPCOMUtils.generateQI([Ci.nsIChannel])
+}
diff --git a/b2g/components/AlertsHelper.jsm b/b2g/components/AlertsHelper.jsm
new file mode 100644
index 0000000000..820f2406c5
--- /dev/null
+++ b/b2g/components/AlertsHelper.jsm
@@ -0,0 +1,279 @@
+/* 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";
+
+this.EXPORTED_SYMBOLS = [];
+
+const Ci = Components.interfaces;
+const Cu = Components.utils;
+const Cc = Components.classes;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/AppsUtils.jsm");
+
+XPCOMUtils.defineLazyServiceGetter(this, "gSystemMessenger",
+ "@mozilla.org/system-message-internal;1",
+ "nsISystemMessagesInternal");
+
+XPCOMUtils.defineLazyServiceGetter(this, "appsService",
+ "@mozilla.org/AppsService;1",
+ "nsIAppsService");
+
+XPCOMUtils.defineLazyModuleGetter(this, "SystemAppProxy",
+ "resource://gre/modules/SystemAppProxy.jsm");
+
+XPCOMUtils.defineLazyServiceGetter(this, "notificationStorage",
+ "@mozilla.org/notificationStorage;1",
+ "nsINotificationStorage");
+
+XPCOMUtils.defineLazyGetter(this, "ppmm", function() {
+ return Cc["@mozilla.org/parentprocessmessagemanager;1"]
+ .getService(Ci.nsIMessageListenerManager);
+});
+
+function debug(str) {
+ //dump("=*= AlertsHelper.jsm : " + str + "\n");
+}
+
+const kNotificationIconSize = 128;
+
+const kDesktopNotificationPerm = "desktop-notification";
+
+const kNotificationSystemMessageName = "notification";
+
+const kDesktopNotification = "desktop-notification";
+const kDesktopNotificationShow = "desktop-notification-show";
+const kDesktopNotificationClick = "desktop-notification-click";
+const kDesktopNotificationClose = "desktop-notification-close";
+
+const kTopicAlertClickCallback = "alertclickcallback";
+const kTopicAlertShow = "alertshow";
+const kTopicAlertFinished = "alertfinished";
+
+const kMozChromeNotificationEvent = "mozChromeNotificationEvent";
+const kMozContentNotificationEvent = "mozContentNotificationEvent";
+
+const kMessageAlertNotificationSend = "alert-notification-send";
+const kMessageAlertNotificationClose = "alert-notification-close";
+
+const kMessages = [
+ kMessageAlertNotificationSend,
+ kMessageAlertNotificationClose
+];
+
+var AlertsHelper = {
+
+ _listeners: {},
+
+ init: function() {
+ Services.obs.addObserver(this, "xpcom-shutdown", false);
+ for (let message of kMessages) {
+ ppmm.addMessageListener(message, this);
+ }
+ SystemAppProxy.addEventListener(kMozContentNotificationEvent, this);
+ },
+
+ observe: function(aSubject, aTopic, aData) {
+ switch (aTopic) {
+ case "xpcom-shutdown":
+ Services.obs.removeObserver(this, "xpcom-shutdown");
+ for (let message of kMessages) {
+ ppmm.removeMessageListener(message, this);
+ }
+ SystemAppProxy.removeEventListener(kMozContentNotificationEvent, this);
+ break;
+ }
+ },
+
+ handleEvent: function(evt) {
+ let detail = evt.detail;
+
+ switch(detail.type) {
+ case kDesktopNotificationShow:
+ case kDesktopNotificationClick:
+ case kDesktopNotificationClose:
+ this.handleNotificationEvent(detail);
+ break;
+ default:
+ debug("FIXME: Unhandled notification event: " + detail.type);
+ break;
+ }
+ },
+
+ handleNotificationEvent: function(detail) {
+ if (!detail || !detail.id) {
+ return;
+ }
+
+ let uid = detail.id;
+ let listener = this._listeners[uid];
+ if (!listener) {
+ return;
+ }
+
+ let topic;
+ if (detail.type === kDesktopNotificationClick) {
+ topic = kTopicAlertClickCallback;
+ } else if (detail.type === kDesktopNotificationShow) {
+ topic = kTopicAlertShow;
+ } else {
+ /* kDesktopNotificationClose */
+ topic = kTopicAlertFinished;
+ }
+
+ if (listener.cookie) {
+ try {
+ listener.observer.observe(null, topic, listener.cookie);
+ } catch (e) { }
+ } else {
+ if (detail.type === kDesktopNotificationClose && listener.dbId) {
+ notificationStorage.delete(listener.manifestURL, listener.dbId);
+ }
+ }
+
+ // we"re done with this notification
+ if (detail.type === kDesktopNotificationClose) {
+ delete this._listeners[uid];
+ }
+ },
+
+ registerListener: function(alertId, cookie, alertListener) {
+ this._listeners[alertId] = { observer: alertListener, cookie: cookie };
+ },
+
+ registerAppListener: function(uid, listener) {
+ this._listeners[uid] = listener;
+
+ appsService.getManifestFor(listener.manifestURL).then((manifest) => {
+ let app = appsService.getAppByManifestURL(listener.manifestURL);
+ let helper = new ManifestHelper(manifest, app.origin, app.manifestURL);
+ let getNotificationURLFor = function(messages) {
+ if (!messages) {
+ return null;
+ }
+
+ for (let i = 0; i < messages.length; i++) {
+ let message = messages[i];
+ if (message === kNotificationSystemMessageName) {
+ return helper.fullLaunchPath();
+ } else if (typeof message === "object" &&
+ kNotificationSystemMessageName in message) {
+ return helper.resolveURL(message[kNotificationSystemMessageName]);
+ }
+ }
+
+ // No message found...
+ return null;
+ }
+
+ listener.target = getNotificationURLFor(manifest.messages);
+
+ // Bug 816944 - Support notification messages for entry_points.
+ });
+ },
+
+ deserializeStructuredClone: function(dataString) {
+ if (!dataString) {
+ return null;
+ }
+ let scContainer = Cc["@mozilla.org/docshell/structured-clone-container;1"].
+ createInstance(Ci.nsIStructuredCloneContainer);
+
+ // The maximum supported structured-clone serialization format version
+ // as defined in "js/public/StructuredClone.h"
+ let JS_STRUCTURED_CLONE_VERSION = 4;
+ scContainer.initFromBase64(dataString, JS_STRUCTURED_CLONE_VERSION);
+ let dataObj = scContainer.deserializeToVariant();
+
+ // We have to check whether dataObj contains DOM objects (supported by
+ // nsIStructuredCloneContainer, but not by Cu.cloneInto), e.g. ImageData.
+ // After the structured clone callback systems will be unified, we'll not
+ // have to perform this check anymore.
+ try {
+ let data = Cu.cloneInto(dataObj, {});
+ } catch(e) { dataObj = null; }
+
+ return dataObj;
+ },
+
+ showNotification: function(imageURL, title, text, textClickable, cookie,
+ uid, dir, lang, dataObj, manifestURL, timestamp,
+ behavior) {
+ function send(appName, appIcon) {
+ SystemAppProxy._sendCustomEvent(kMozChromeNotificationEvent, {
+ type: kDesktopNotification,
+ id: uid,
+ icon: imageURL,
+ title: title,
+ text: text,
+ dir: dir,
+ lang: lang,
+ appName: appName,
+ appIcon: appIcon,
+ manifestURL: manifestURL,
+ timestamp: timestamp,
+ data: dataObj,
+ mozbehavior: behavior
+ });
+ }
+
+ if (!manifestURL || !manifestURL.length) {
+ send(null, null);
+ return;
+ }
+
+ // If we have a manifest URL, get the icon and title from the manifest
+ // to prevent spoofing.
+ appsService.getManifestFor(manifestURL).then((manifest) => {
+ let app = appsService.getAppByManifestURL(manifestURL);
+ let helper = new ManifestHelper(manifest, app.origin, manifestURL);
+ send(helper.name, helper.iconURLForSize(kNotificationIconSize));
+ });
+ },
+
+ showAlertNotification: function(aMessage) {
+ let data = aMessage.data;
+ let currentListener = this._listeners[data.name];
+ if (currentListener && currentListener.observer) {
+ currentListener.observer.observe(null, kTopicAlertFinished, currentListener.cookie);
+ }
+
+ let dataObj = this.deserializeStructuredClone(data.dataStr);
+ this.registerListener(data.name, data.cookie, data.alertListener);
+ this.showNotification(data.imageURL, data.title, data.text,
+ data.textClickable, data.cookie, data.name, data.dir,
+ data.lang, dataObj, null, data.inPrivateBrowsing);
+ },
+
+ closeAlert: function(name) {
+ SystemAppProxy._sendCustomEvent(kMozChromeNotificationEvent, {
+ type: kDesktopNotificationClose,
+ id: name
+ });
+ },
+
+ receiveMessage: function(aMessage) {
+ if (!aMessage.target.assertAppHasPermission(kDesktopNotificationPerm)) {
+ Cu.reportError("Desktop-notification message " + aMessage.name +
+ " from a content process with no " + kDesktopNotificationPerm +
+ " privileges.");
+ return;
+ }
+
+ switch(aMessage.name) {
+ case kMessageAlertNotificationSend:
+ this.showAlertNotification(aMessage);
+ break;
+
+ case kMessageAlertNotificationClose:
+ this.closeAlert(aMessage.data.name);
+ break;
+ }
+
+ },
+}
+
+AlertsHelper.init();
diff --git a/b2g/components/AlertsService.js b/b2g/components/AlertsService.js
new file mode 100644
index 0000000000..19a164f0ed
--- /dev/null
+++ b/b2g/components/AlertsService.js
@@ -0,0 +1,153 @@
+/* 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/. */
+
+const Ci = Components.interfaces;
+const Cu = Components.utils;
+const Cc = Components.classes;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+
+XPCOMUtils.defineLazyServiceGetter(this, "gSystemMessenger",
+ "@mozilla.org/system-message-internal;1",
+ "nsISystemMessagesInternal");
+
+XPCOMUtils.defineLazyServiceGetter(this, "uuidGenerator",
+ "@mozilla.org/uuid-generator;1",
+ "nsIUUIDGenerator");
+
+XPCOMUtils.defineLazyServiceGetter(this, "notificationStorage",
+ "@mozilla.org/notificationStorage;1",
+ "nsINotificationStorage");
+
+XPCOMUtils.defineLazyGetter(this, "cpmm", function() {
+ return Cc["@mozilla.org/childprocessmessagemanager;1"]
+ .getService(Ci.nsIMessageSender);
+});
+
+function debug(str) {
+ dump("=*= AlertsService.js : " + str + "\n");
+}
+
+// -----------------------------------------------------------------------
+// Alerts Service
+// -----------------------------------------------------------------------
+
+const kNotificationSystemMessageName = "notification";
+
+const kMessageAlertNotificationSend = "alert-notification-send";
+const kMessageAlertNotificationClose = "alert-notification-close";
+
+const kTopicAlertShow = "alertshow";
+const kTopicAlertFinished = "alertfinished";
+const kTopicAlertClickCallback = "alertclickcallback";
+
+function AlertsService() {
+ Services.obs.addObserver(this, "xpcom-shutdown", false);
+}
+
+AlertsService.prototype = {
+ classID: Components.ID("{fe33c107-82a4-41d6-8c64-5353267e04c9}"),
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIAlertsService,
+ Ci.nsIObserver]),
+
+ observe: function(aSubject, aTopic, aData) {
+ switch (aTopic) {
+ case "xpcom-shutdown":
+ Services.obs.removeObserver(this, "xpcom-shutdown");
+ break;
+ }
+ },
+
+ // nsIAlertsService
+ showAlert: function(aAlert, aAlertListener) {
+ if (!aAlert) {
+ return;
+ }
+ cpmm.sendAsyncMessage(kMessageAlertNotificationSend, {
+ imageURL: aAlert.imageURL,
+ title: aAlert.title,
+ text: aAlert.text,
+ clickable: aAlert.textClickable,
+ cookie: aAlert.cookie,
+ listener: aAlertListener,
+ id: aAlert.name,
+ dir: aAlert.dir,
+ lang: aAlert.lang,
+ dataStr: aAlert.data,
+ inPrivateBrowsing: aAlert.inPrivateBrowsing
+ });
+ },
+
+ showAlertNotification: function(aImageUrl, aTitle, aText, aTextClickable,
+ aCookie, aAlertListener, aName, aBidi,
+ aLang, aDataStr, aPrincipal,
+ aInPrivateBrowsing) {
+ let alert = Cc["@mozilla.org/alert-notification;1"].
+ createInstance(Ci.nsIAlertNotification);
+
+ alert.init(aName, aImageUrl, aTitle, aText, aTextClickable, aCookie,
+ aBidi, aLang, aDataStr, aPrincipal, aInPrivateBrowsing);
+
+ this.showAlert(alert, aAlertListener);
+ },
+
+ closeAlert: function(aName) {
+ cpmm.sendAsyncMessage(kMessageAlertNotificationClose, {
+ name: aName
+ });
+ },
+
+ // AlertsService.js custom implementation
+ _listeners: [],
+
+ receiveMessage: function(aMessage) {
+ let data = aMessage.data;
+ let listener = this._listeners[data.uid];
+ if (!listener) {
+ return;
+ }
+
+ let topic = data.topic;
+
+ try {
+ listener.observer.observe(null, topic, null);
+ } catch (e) {
+ if (topic === kTopicAlertFinished && listener.dbId) {
+ notificationStorage.delete(listener.manifestURL, listener.dbId);
+ }
+ }
+
+ // we're done with this notification
+ if (topic === kTopicAlertFinished) {
+ delete this._listeners[data.uid];
+ }
+ },
+
+ deserializeStructuredClone: function(dataString) {
+ if (!dataString) {
+ return null;
+ }
+ let scContainer = Cc["@mozilla.org/docshell/structured-clone-container;1"].
+ createInstance(Ci.nsIStructuredCloneContainer);
+
+ // The maximum supported structured-clone serialization format version
+ // as defined in "js/public/StructuredClone.h"
+ let JS_STRUCTURED_CLONE_VERSION = 4;
+ scContainer.initFromBase64(dataString, JS_STRUCTURED_CLONE_VERSION);
+ let dataObj = scContainer.deserializeToVariant();
+
+ // We have to check whether dataObj contains DOM objects (supported by
+ // nsIStructuredCloneContainer, but not by Cu.cloneInto), e.g. ImageData.
+ // After the structured clone callback systems will be unified, we'll not
+ // have to perform this check anymore.
+ try {
+ let data = Cu.cloneInto(dataObj, {});
+ } catch(e) { dataObj = null; }
+
+ return dataObj;
+ }
+};
+
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory([AlertsService]);
diff --git a/b2g/components/B2GAboutRedirector.js b/b2g/components/B2GAboutRedirector.js
new file mode 100644
index 0000000000..f4bcf47f48
--- /dev/null
+++ b/b2g/components/B2GAboutRedirector.js
@@ -0,0 +1,78 @@
+/* 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/. */
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+Components.utils.import("resource://gre/modules/Services.jsm");
+
+function debug(msg) {
+ //dump("B2GAboutRedirector: " + msg + "\n");
+}
+
+function netErrorURL() {
+ let systemManifestURL = Services.prefs.getCharPref("b2g.system_manifest_url");
+ systemManifestURL = Services.io.newURI(systemManifestURL, null, null);
+ let netErrorURL = Services.prefs.getCharPref("b2g.neterror.url");
+ netErrorURL = Services.io.newURI(netErrorURL, null, systemManifestURL);
+ return netErrorURL.spec;
+}
+
+var modules = {
+ certerror: {
+ uri: "chrome://b2g/content/aboutCertError.xhtml",
+ privileged: false,
+ hide: true
+ },
+ neterror: {
+ uri: netErrorURL(),
+ privileged: false,
+ hide: true
+ }
+};
+
+function B2GAboutRedirector() {}
+B2GAboutRedirector.prototype = {
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIAboutModule]),
+ classID: Components.ID("{920400b1-cf8f-4760-a9c4-441417b15134}"),
+
+ _getModuleInfo: function (aURI) {
+ let moduleName = aURI.path.replace(/[?#].*/, "").toLowerCase();
+ return modules[moduleName];
+ },
+
+ // nsIAboutModule
+ getURIFlags: function(aURI) {
+ let flags;
+ let moduleInfo = this._getModuleInfo(aURI);
+ if (moduleInfo.hide)
+ flags = Ci.nsIAboutModule.HIDE_FROM_ABOUTABOUT;
+
+ return flags | Ci.nsIAboutModule.ALLOW_SCRIPT;
+ },
+
+ newChannel: function(aURI, aLoadInfo) {
+ let moduleInfo = this._getModuleInfo(aURI);
+
+ var ios = Cc["@mozilla.org/network/io-service;1"].
+ getService(Ci.nsIIOService);
+
+ var newURI = ios.newURI(moduleInfo.uri, null, null);
+
+ var channel = ios.newChannelFromURIWithLoadInfo(newURI, aLoadInfo);
+
+ if (!moduleInfo.privileged) {
+ // Setting the owner to null means that we'll go through the normal
+ // path in GetChannelPrincipal and create a codebase principal based
+ // on the channel's originalURI
+ channel.owner = null;
+ }
+
+ channel.originalURI = aURI;
+
+ return channel;
+ }
+};
+
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory([B2GAboutRedirector]);
diff --git a/b2g/components/B2GAppMigrator.js b/b2g/components/B2GAppMigrator.js
new file mode 100644
index 0000000000..65671d1513
--- /dev/null
+++ b/b2g/components/B2GAppMigrator.js
@@ -0,0 +1,152 @@
+/* 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';
+
+function debug(s) {
+ dump("-*- B2GAppMigrator.js: " + s + "\n");
+}
+const DEBUG = false;
+
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+const Cu = Components.utils;
+
+const kMigrationMessageName = "webapps-before-update-merge";
+
+const kIDBDirType = "indexedDBPDir";
+const kProfileDirType = "ProfD";
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/FileUtils.jsm");
+
+XPCOMUtils.defineLazyServiceGetter(this, "appsService",
+ "@mozilla.org/AppsService;1",
+ "nsIAppsService");
+
+function B2GAppMigrator() {
+}
+
+B2GAppMigrator.prototype = {
+ classID: Components.ID('{7211ece0-b458-4635-9afc-f8d7f376ee95}'),
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver,
+ Ci.nsISupportsWeakReference]),
+ executeBrowserMigration: function() {
+ if (DEBUG) debug("Executing Browser Migration");
+ // The browser db file and directory names are hashed the same way
+ // everywhere, so it should be the same on all systems. We should
+ // be able to just hardcode it.
+ let browserDBDirName = "2959517650brreosw";
+ let browserDBFileName = browserDBDirName + ".sqlite";
+
+ // Storage directories need to be prefixed with the local id of
+ // the app
+ let browserLocalAppId = appsService.getAppLocalIdByManifestURL("app://browser.gaiamobile.org/manifest.webapp");
+ let browserAppStorageDirName = browserLocalAppId + "+f+app+++browser.gaiamobile.org";
+
+ // On the phone, the browser db will only be in the old IDB
+ // directory, since it only existed up until v2.0. On desktop, it
+ // will exist in the profile directory.
+ //
+ // Uses getDir with filename appending to make sure we don't
+ // create extra directories along the way if they don't already
+ // exist.
+ let browserDBFile = FileUtils.getDir(kIDBDirType,
+ ["storage",
+ "persistent",
+ browserAppStorageDirName,
+ "idb"], false, true);
+ browserDBFile.append(browserDBFileName);
+ let browserDBDir = FileUtils.getDir(kIDBDirType,
+ ["storage",
+ "persistent",
+ browserAppStorageDirName,
+ "idb",
+ browserDBDirName
+ ], false, true);
+
+ if (!browserDBFile.exists()) {
+ if (DEBUG) debug("Browser DB " + browserDBFile.path + " does not exist, trying profile location");
+ browserDBFile = FileUtils.getDir(kProfileDirType,
+ ["storage",
+ "persistent",
+ browserAppStorageDirName,
+ "idb"], false, true);
+ browserDBFile.append(browserDBFileName);
+ if (!browserDBFile.exists()) {
+ if (DEBUG) debug("Browser DB " + browserDBFile.path + " does not exist. Cannot copy browser db.");
+ return;
+ }
+ // If we have confirmed we have a DB file, we should also have a
+ // directory.
+ browserDBDir = FileUtils.getDir(kProfileDirType,
+ ["storage",
+ "persistent",
+ browserAppStorageDirName,
+ "idb",
+ browserDBDirName
+ ], false, true);
+ }
+
+ let systemLocalAppId = appsService.getAppLocalIdByManifestURL("app://system.gaiamobile.org/manifest.webapp");
+ let systemAppStorageDirName = systemLocalAppId + "+f+app+++system.gaiamobile.org";
+
+ // This check futureproofs the system DB storage directory. It
+ // currently exists outside of the profile but will most likely
+ // move into the profile at some point.
+ let systemDBDir = FileUtils.getDir(kIDBDirType,
+ ["storage",
+ "persistent",
+ systemAppStorageDirName,
+ "idb"], false, true);
+
+ if (!systemDBDir.exists()) {
+ if (DEBUG) debug("System DB directory " + systemDBDir.path + " does not exist, trying profile location");
+ systemDBDir = FileUtils.getDir(kProfileDirType,
+ ["storage",
+ "persistent",
+ systemAppStorageDirName,
+ "idb"], false, true);
+ if (!systemDBDir.exists()) {
+ if (DEBUG) debug("System DB directory " + systemDBDir.path + " does not exist. Cannot copy browser db.");
+ return;
+ }
+ }
+
+ if (DEBUG) {
+ debug("Browser DB file exists, copying");
+ debug("Browser local id: " + browserLocalAppId + "");
+ debug("System local id: " + systemLocalAppId + "");
+ debug("Browser DB file path: " + browserDBFile.path + "");
+ debug("Browser DB dir path: " + browserDBDir.path + "");
+ debug("System DB directory path: " + systemDBDir.path + "");
+ }
+
+ try {
+ browserDBFile.copyTo(systemDBDir, browserDBFileName);
+ } catch (e) {
+ debug("File copy caused error! " + e.name);
+ }
+ try {
+ browserDBDir.copyTo(systemDBDir, browserDBDirName);
+ } catch (e) {
+ debug("Dir copy caused error! " + e.name);
+ }
+ if (DEBUG) debug("Browser DB copied successfully");
+ },
+
+ observe: function(subject, topic, data) {
+ switch (topic) {
+ case kMigrationMessageName:
+ this.executeBrowserMigration();
+ break;
+ default:
+ debug("Unhandled topic: " + topic);
+ break;
+ }
+ }
+};
+
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory([B2GAppMigrator]);
diff --git a/b2g/components/B2GComponents.manifest b/b2g/components/B2GComponents.manifest
new file mode 100644
index 0000000000..53d0032f94
--- /dev/null
+++ b/b2g/components/B2GComponents.manifest
@@ -0,0 +1,108 @@
+# Scrollbars
+category agent-style-sheets browser-content-stylesheet chrome://b2g/content/content.css
+
+# AlertsService.js
+component {fe33c107-82a4-41d6-8c64-5353267e04c9} AlertsService.js
+contract @mozilla.org/system-alerts-service;1 {fe33c107-82a4-41d6-8c64-5353267e04c9}
+
+# ContentPermissionPrompt.js
+component {8c719f03-afe0-4aac-91ff-6c215895d467} ContentPermissionPrompt.js
+contract @mozilla.org/content-permission/prompt;1 {8c719f03-afe0-4aac-91ff-6c215895d467}
+
+#ifdef MOZ_UPDATER
+# UpdatePrompt.js
+component {88b3eb21-d072-4e3b-886d-f89d8c49fe59} UpdatePrompt.js
+contract @mozilla.org/updates/update-prompt;1 {88b3eb21-d072-4e3b-886d-f89d8c49fe59}
+category system-update-provider MozillaProvider @mozilla.org/updates/update-prompt;1,{88b3eb21-d072-4e3b-886d-f89d8c49fe59}
+#endif
+
+#ifdef MOZ_B2G
+# DirectoryProvider.js
+component {9181eb7c-6f87-11e1-90b1-4f59d80dd2e5} DirectoryProvider.js
+contract @mozilla.org/b2g/directory-provider;1 {9181eb7c-6f87-11e1-90b1-4f59d80dd2e5}
+category xpcom-directory-providers b2g-directory-provider @mozilla.org/b2g/directory-provider;1
+#endif
+
+# SystemMessageGlue.js
+component {2846f034-e614-11e3-93cd-74d02b97e723} SystemMessageGlue.js
+contract @mozilla.org/dom/messages/system-message-glue;1 {2846f034-e614-11e3-93cd-74d02b97e723}
+
+# ProcessGlobal.js
+component {1a94c87a-5ece-4d11-91e1-d29c29f21b28} ProcessGlobal.js
+contract @mozilla.org/b2g-process-global;1 {1a94c87a-5ece-4d11-91e1-d29c29f21b28}
+category app-startup ProcessGlobal service,@mozilla.org/b2g-process-global;1
+
+# OMAContentHandler.js
+component {a6b2ab13-9037-423a-9897-dde1081be323} OMAContentHandler.js
+contract @mozilla.org/uriloader/content-handler;1?type=application/vnd.oma.drm.message {a6b2ab13-9037-423a-9897-dde1081be323}
+contract @mozilla.org/uriloader/content-handler;1?type=application/vnd.oma.dd+xml {a6b2ab13-9037-423a-9897-dde1081be323}
+
+# TelProtocolHandler.js
+component {782775dd-7351-45ea-aff1-0ffa872cfdd2} TelProtocolHandler.js
+contract @mozilla.org/network/protocol;1?name=tel {782775dd-7351-45ea-aff1-0ffa872cfdd2}
+
+# SmsProtocolHandler.js
+component {81ca20cb-0dad-4e32-8566-979c8998bd73} SmsProtocolHandler.js
+contract @mozilla.org/network/protocol;1?name=sms {81ca20cb-0dad-4e32-8566-979c8998bd73}
+
+# MailtoProtocolHandler.js
+component {50777e53-0331-4366-a191-900999be386c} MailtoProtocolHandler.js
+contract @mozilla.org/network/protocol;1?name=mailto {50777e53-0331-4366-a191-900999be386c}
+
+# RecoveryService.js
+component {b3caca5d-0bb0-48c6-912b-6be6cbf08832} RecoveryService.js
+contract @mozilla.org/recovery-service;1 {b3caca5d-0bb0-48c6-912b-6be6cbf08832}
+
+# B2GAboutRedirector
+component {920400b1-cf8f-4760-a9c4-441417b15134} B2GAboutRedirector.js
+contract @mozilla.org/network/protocol/about;1?what=certerror {920400b1-cf8f-4760-a9c4-441417b15134}
+contract @mozilla.org/network/protocol/about;1?what=neterror {920400b1-cf8f-4760-a9c4-441417b15134}
+
+#ifndef MOZ_GRAPHENE
+# FilePicker.js
+component {436ff8f9-0acc-4b11-8ec7-e293efba3141} FilePicker.js
+contract @mozilla.org/filepicker;1 {436ff8f9-0acc-4b11-8ec7-e293efba3141}
+#endif
+
+# FxAccountsUIGlue.js
+component {51875c14-91d7-4b8c-b65d-3549e101228c} FxAccountsUIGlue.js
+contract @mozilla.org/fxaccounts/fxaccounts-ui-glue;1 {51875c14-91d7-4b8c-b65d-3549e101228c}
+
+# HelperAppDialog.js
+component {710322af-e6ae-4b0c-b2c9-1474a87b077e} HelperAppDialog.js
+contract @mozilla.org/helperapplauncherdialog;1 {710322af-e6ae-4b0c-b2c9-1474a87b077e}
+
+#ifndef MOZ_WIDGET_GONK
+component {c83c02c0-5d43-4e3e-987f-9173b313e880} SimulatorScreen.js
+contract @mozilla.org/simulator-screen;1 {c83c02c0-5d43-4e3e-987f-9173b313e880}
+category profile-after-change SimulatorScreen @mozilla.org/simulator-screen;1
+
+component {e30b0e13-2d12-4cb0-bc4c-4e617a1bf76e} OopCommandLine.js
+contract @mozilla.org/commandlinehandler/general-startup;1?type=b2goop {e30b0e13-2d12-4cb0-bc4c-4e617a1bf76e}
+category command-line-handler m-b2goop @mozilla.org/commandlinehandler/general-startup;1?type=b2goop
+
+component {385993fe-8710-4621-9fb1-00a09d8bec37} CommandLine.js
+contract @mozilla.org/commandlinehandler/general-startup;1?type=b2gcmds {385993fe-8710-4621-9fb1-00a09d8bec37}
+category command-line-handler m-b2gcmds @mozilla.org/commandlinehandler/general-startup;1?type=b2gcmds
+#endif
+
+# BootstrapCommandLine.js
+component {fd663ec8-cf3f-4c2b-aacb-17a6915ccb44} BootstrapCommandLine.js
+contract @mozilla.org/commandlinehandler/general-startup;1?type=b2gbootstrap {fd663ec8-cf3f-4c2b-aacb-17a6915ccb44}
+category command-line-handler m-b2gbootstrap @mozilla.org/commandlinehandler/general-startup;1?type=b2gbootstrap
+
+# B2GAppMigrator.js
+component {7211ece0-b458-4635-9afc-f8d7f376ee95} B2GAppMigrator.js
+contract @mozilla.org/app-migrator;1 {7211ece0-b458-4635-9afc-f8d7f376ee95}
+
+# B2GPresentationDevicePrompt.js
+component {4a300c26-e99b-4018-ab9b-c48cf9bc4de1} B2GPresentationDevicePrompt.js
+contract @mozilla.org/presentation-device/prompt;1 {4a300c26-e99b-4018-ab9b-c48cf9bc4de1}
+
+# PresentationRequestUIGlue.js
+component {ccc8a839-0b64-422b-8a60-fb2af0e376d0} PresentationRequestUIGlue.js
+contract @mozilla.org/presentation/requestuiglue;1 {ccc8a839-0b64-422b-8a60-fb2af0e376d0}
+
+# SystemMessageInternal.js
+component {70589ca5-91ac-4b9e-b839-d6a88167d714} SystemMessageInternal.js
+contract @mozilla.org/system-message-internal;1 {70589ca5-91ac-4b9e-b839-d6a88167d714}
diff --git a/b2g/components/B2GPresentationDevicePrompt.js b/b2g/components/B2GPresentationDevicePrompt.js
new file mode 100644
index 0000000000..998e0b7ac0
--- /dev/null
+++ b/b2g/components/B2GPresentationDevicePrompt.js
@@ -0,0 +1,87 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* 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";
+
+function debug(aMsg) {
+ //dump("-*- B2GPresentationDevicePrompt: " + aMsg + "\n");
+}
+
+const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
+
+const kB2GPRESENTATIONDEVICEPROMPT_CONTRACTID = "@mozilla.org/presentation-device/prompt;1";
+const kB2GPRESENTATIONDEVICEPROMPT_CID = Components.ID("{4a300c26-e99b-4018-ab9b-c48cf9bc4de1}");
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "SystemAppProxy",
+ "resource://gre/modules/SystemAppProxy.jsm");
+
+function B2GPresentationDevicePrompt() {}
+
+B2GPresentationDevicePrompt.prototype = {
+ classID: kB2GPRESENTATIONDEVICEPROMPT_CID,
+ contractID: kB2GPRESENTATIONDEVICEPROMPT_CONTRACTID,
+ classDescription: "B2G Presentation Device Prompt",
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIPresentationDevicePrompt]),
+
+ // nsIPresentationDevicePrompt
+ promptDeviceSelection: function(aRequest) {
+ let self = this;
+ let requestId = Cc["@mozilla.org/uuid-generator;1"]
+ .getService(Ci.nsIUUIDGenerator).generateUUID().toString();
+
+ SystemAppProxy.addEventListener("mozContentEvent", function contentEvent(aEvent) {
+ let detail = aEvent.detail;
+ if (detail.id !== requestId) {
+ return;
+ }
+
+ SystemAppProxy.removeEventListener("mozContentEvent", contentEvent);
+
+ switch (detail.type) {
+ case "presentation-select-result":
+ debug("device " + detail.deviceId + " is selected by user");
+ let device = self._getDeviceById(detail.deviceId);
+ if (!device) {
+ debug("cancel request because device is not found");
+ aRequest.cancel(Cr.NS_ERROR_DOM_NOT_FOUND_ERR);
+ }
+ aRequest.select(device);
+ break;
+ case "presentation-select-deny":
+ debug("request canceled by user");
+ aRequest.cancel(Cr.NS_ERROR_DOM_NOT_ALLOWED_ERR);
+ break;
+ }
+ });
+
+ let detail = {
+ type: "presentation-select-device",
+ origin: aRequest.origin,
+ requestURL: aRequest.requestURL,
+ id: requestId,
+ };
+
+ SystemAppProxy.dispatchEvent(detail);
+ },
+
+ _getDeviceById: function(aDeviceId) {
+ let deviceManager = Cc["@mozilla.org/presentation-device/manager;1"]
+ .getService(Ci.nsIPresentationDeviceManager);
+ let devices = deviceManager.getAvailableDevices().QueryInterface(Ci.nsIArray);
+
+ for (let i = 0; i < devices.length; i++) {
+ let device = devices.queryElementAt(i, Ci.nsIPresentationDevice);
+ if (device.id === aDeviceId) {
+ return device;
+ }
+ }
+
+ return null;
+ },
+};
+
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory([B2GPresentationDevicePrompt]);
diff --git a/b2g/components/BootstrapCommandLine.js b/b2g/components/BootstrapCommandLine.js
new file mode 100644
index 0000000000..24d9f5461e
--- /dev/null
+++ b/b2g/components/BootstrapCommandLine.js
@@ -0,0 +1,52 @@
+/* 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/. */
+
+const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/AppsUtils.jsm");
+
+function BootstrapCommandlineHandler() {
+ this.wrappedJSObject = this;
+ this.startManifestURL = null;
+}
+
+BootstrapCommandlineHandler.prototype = {
+ bailout: function(aMsg) {
+ dump("************************************************************\n");
+ dump("* /!\\ " + aMsg + "\n");
+ dump("************************************************************\n");
+ let appStartup = Cc["@mozilla.org/toolkit/app-startup;1"]
+ .getService(Ci.nsIAppStartup);
+ appStartup.quit(appStartup.eForceQuit);
+ },
+
+ handle: function(aCmdLine) {
+ this.startManifestURL = null;
+
+ try {
+ // Returns null if the argument was not specified. Throws
+ // NS_ERROR_INVALID_ARG if there is no parameter specified (because
+ // it was the last argument or the next argument starts with '-').
+ // However, someone could still explicitly pass an empty argument!
+ this.startManifestURL = aCmdLine.handleFlagWithParam("start-manifest", false);
+ } catch(e) {
+ return;
+ }
+
+ if (!this.startManifestURL) {
+ return;
+ }
+
+ if (!isAbsoluteURI(this.startManifestURL)) {
+ this.bailout("The start manifest url must be absolute.");
+ return;
+ }
+ },
+
+ helpInfo: "--start-manifest=manifest_url",
+ classID: Components.ID("{fd663ec8-cf3f-4c2b-aacb-17a6915ccb44}"),
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsICommandLineHandler])
+};
+
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory([BootstrapCommandlineHandler]);
diff --git a/b2g/components/Bootstraper.jsm b/b2g/components/Bootstraper.jsm
new file mode 100644
index 0000000000..3d3fb37d91
--- /dev/null
+++ b/b2g/components/Bootstraper.jsm
@@ -0,0 +1,156 @@
+/* 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";
+
+this.EXPORTED_SYMBOLS = ["Bootstraper"];
+
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+const Cu = Components.utils;
+const CC = Components.Constructor;
+
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/AppsUtils.jsm");
+
+function debug(aMsg) {
+ //dump("-*- Bootstraper: " + aMsg + "\n");
+}
+
+/**
+ * This module loads the manifest for app from the --start-url enpoint and
+ * ensures that it's installed as the system app.
+ */
+this.Bootstraper = {
+ _manifestURL: null,
+ _startupURL: null,
+
+ bailout: function(aMsg) {
+ dump("************************************************************\n");
+ dump("* /!\\ " + aMsg + "\n");
+ dump("************************************************************\n");
+ let appStartup = Cc["@mozilla.org/toolkit/app-startup;1"]
+ .getService(Ci.nsIAppStartup);
+ appStartup.quit(appStartup.eForceQuit);
+ },
+
+ installSystemApp: function(aManifest) {
+ // Get the appropriate startup url from the manifest launch_path.
+ let base = Services.io.newURI(this._manifestURL, null, null);
+ let origin = base.prePath;
+ let helper = new ManifestHelper(aManifest, origin, this._manifestURL);
+ this._startupURL = helper.fullLaunchPath();
+
+ return new Promise((aResolve, aReject) => {
+ debug("Origin is " + origin);
+ let appData = {
+ app: {
+ installOrigin: origin,
+ origin: origin,
+ manifest: aManifest,
+ manifestURL: this._manifestURL,
+ manifestHash: AppsUtils.computeHash(JSON.stringify(aManifest)),
+ appStatus: Ci.nsIPrincipal.APP_STATUS_CERTIFIED
+ },
+ appId: 1,
+ isBrowser: false,
+ isPackage: false
+ };
+
+ //DOMApplicationRegistry.confirmInstall(appData, null, aResolve);
+ });
+ },
+
+ /**
+ * Resolves to a json manifest.
+ */
+ loadManifest: function() {
+ return new Promise((aResolve, aReject) => {
+ debug("Loading manifest " + this._manifestURL);
+
+ let xhr = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"]
+ .createInstance(Ci.nsIXMLHttpRequest);
+ xhr.mozBackgroundRequest = true;
+ xhr.open("GET", this._manifestURL);
+ xhr.responseType = "json";
+ xhr.addEventListener("load", () => {
+ if (xhr.status >= 200 && xhr.status < 400) {
+ debug("Success loading " + this._manifestURL);
+ aResolve(xhr.response);
+ } else {
+ aReject("Error loading " + this._manifestURL);
+ }
+ });
+ xhr.addEventListener("error", () => {
+ aReject("Error loading " + this._manifestURL);
+ });
+ xhr.send(null);
+ });
+ },
+
+ configure: function() {
+ debug("Setting startup prefs... " + this._startupURL);
+ Services.prefs.setCharPref("b2g.system_manifest_url", this._manifestURL);
+ Services.prefs.setCharPref("b2g.system_startup_url", this._startupURL);
+ return Promise.resolve();
+ },
+
+ /**
+ * If a system app is already installed, uninstall it so that we can
+ * cleanly replace it by the current one.
+ */
+ uninstallPreviousSystemApp: function() {
+ // TODO: FIXME
+ return Promise.resolve();
+
+ let oldManifestURL;
+ try{
+ oldManifestURL = Services.prefs.getCharPref("b2g.system_manifest_url");
+ } catch(e) {
+ // No preference set, so nothing to uninstall.
+ return Promise.resolve();
+ }
+
+ let id = DOMApplicationRegistry.getAppLocalIdByManifestURL(oldManifestURL);
+ if (id == Ci.nsIScriptSecurityManager.NO_APP_ID) {
+ return Promise.resolve();
+ }
+ debug("Uninstalling " + oldManifestURL);
+ return DOMApplicationRegistry.uninstall(oldManifestURL);
+ },
+
+ /**
+ * Check if we are already configured to run from this manifest url.
+ */
+ isInstallRequired: function(aManifestURL) {
+ try {
+ if (Services.prefs.getCharPref("b2g.system_manifest_url") == aManifestURL) {
+ return false;
+ }
+ } catch(e) { }
+ return true;
+ },
+
+ /**
+ * Resolves once we have installed the app.
+ */
+ ensureSystemAppInstall: function(aManifestURL) {
+ this._manifestURL = aManifestURL;
+ debug("Installing app from " + this._manifestURL);
+
+ if (!this.isInstallRequired(this._manifestURL)) {
+ debug("Already configured for " + this._manifestURL);
+ return Promise.resolve();
+ }
+
+ return new Promise((aResolve, aReject) => {
+ this.uninstallPreviousSystemApp.bind(this)
+ .then(this.loadManifest.bind(this))
+ .then(this.installSystemApp.bind(this))
+ .then(this.configure.bind(this))
+ .then(aResolve)
+ .catch(aReject);
+ });
+ }
+};
diff --git a/b2g/components/CommandLine.js b/b2g/components/CommandLine.js
new file mode 100644
index 0000000000..6dc48bd338
--- /dev/null
+++ b/b2g/components/CommandLine.js
@@ -0,0 +1,29 @@
+/* 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/. */
+
+const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Services", "resource://gre/modules/Services.jsm");
+
+// Small helper to expose nsICommandLine object to chrome code
+
+function CommandlineHandler() {
+ this.wrappedJSObject = this;
+}
+
+CommandlineHandler.prototype = {
+ handle: function(cmdLine) {
+ this.cmdLine = cmdLine;
+ let win = Services.wm.getMostRecentWindow("navigator:browser");
+ if (win && win.shell) {
+ win.shell.handleCmdLine();
+ }
+ },
+
+ helpInfo: "",
+ classID: Components.ID("{385993fe-8710-4621-9fb1-00a09d8bec37}"),
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsICommandLineHandler]),
+};
+
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory([CommandlineHandler]);
diff --git a/b2g/components/ContentPermissionPrompt.js b/b2g/components/ContentPermissionPrompt.js
new file mode 100644
index 0000000000..e11b1b4585
--- /dev/null
+++ b/b2g/components/ContentPermissionPrompt.js
@@ -0,0 +1,461 @@
+/* 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"
+
+function debug(str) {
+ //dump("-*- ContentPermissionPrompt: " + str + "\n");
+}
+
+const Ci = Components.interfaces;
+const Cr = Components.results;
+const Cu = Components.utils;
+const Cc = Components.classes;
+
+const PROMPT_FOR_UNKNOWN = ["audio-capture",
+ "desktop-notification",
+ "geolocation",
+ "video-capture"];
+// Due to privary issue, permission requests like GetUserMedia should prompt
+// every time instead of providing session persistence.
+const PERMISSION_NO_SESSION = ["audio-capture", "video-capture"];
+const ALLOW_MULTIPLE_REQUESTS = ["audio-capture", "video-capture"];
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/AppsUtils.jsm");
+Cu.import("resource://gre/modules/PermissionsInstaller.jsm");
+Cu.import("resource://gre/modules/PermissionsTable.jsm");
+
+var permissionManager = Cc["@mozilla.org/permissionmanager;1"].getService(Ci.nsIPermissionManager);
+var secMan = Cc["@mozilla.org/scriptsecuritymanager;1"].getService(Ci.nsIScriptSecurityManager);
+
+var permissionSpecificChecker = {};
+
+XPCOMUtils.defineLazyModuleGetter(this, "SystemAppProxy",
+ "resource://gre/modules/SystemAppProxy.jsm");
+
+/**
+ * Determine if a permission should be prompt to user or not.
+ *
+ * @param aPerm requested permission
+ * @param aAction the action according to principal
+ * @return true if prompt is required
+ */
+function shouldPrompt(aPerm, aAction) {
+ return ((aAction == Ci.nsIPermissionManager.PROMPT_ACTION) ||
+ (aAction == Ci.nsIPermissionManager.UNKNOWN_ACTION &&
+ PROMPT_FOR_UNKNOWN.indexOf(aPerm) >= 0));
+}
+
+/**
+ * Create the default choices for the requested permissions
+ *
+ * @param aTypesInfo requested permissions
+ * @return the default choices for permissions with options, return
+ * undefined if no option in all requested permissions.
+ */
+function buildDefaultChoices(aTypesInfo) {
+ let choices;
+ for (let type of aTypesInfo) {
+ if (type.options.length > 0) {
+ if (!choices) {
+ choices = {};
+ }
+ choices[type.access] = type.options[0];
+ }
+ }
+ return choices;
+}
+
+/**
+ * aTypesInfo is an array of {permission, access, action, deny} which keeps
+ * the information of each permission. This arrary is initialized in
+ * ContentPermissionPrompt.prompt and used among functions.
+ *
+ * aTypesInfo[].permission : permission name
+ * aTypesInfo[].access : permission name + request.access
+ * aTypesInfo[].action : the default action of this permission
+ * aTypesInfo[].deny : true if security manager denied this app's origin
+ * principal.
+ * Note:
+ * aTypesInfo[].permission will be sent to prompt only when
+ * aTypesInfo[].action is PROMPT_ACTION and aTypesInfo[].deny is false.
+ */
+function rememberPermission(aTypesInfo, aPrincipal, aSession)
+{
+ function convertPermToAllow(aPerm, aPrincipal)
+ {
+ let type =
+ permissionManager.testExactPermissionFromPrincipal(aPrincipal, aPerm);
+ if (shouldPrompt(aPerm, type)) {
+ debug("add " + aPerm + " to permission manager with ALLOW_ACTION");
+ if (!aSession) {
+ permissionManager.addFromPrincipal(aPrincipal,
+ aPerm,
+ Ci.nsIPermissionManager.ALLOW_ACTION);
+ } else if (PERMISSION_NO_SESSION.indexOf(aPerm) < 0) {
+ permissionManager.addFromPrincipal(aPrincipal,
+ aPerm,
+ Ci.nsIPermissionManager.ALLOW_ACTION,
+ Ci.nsIPermissionManager.EXPIRE_SESSION, 0);
+ }
+ }
+ }
+
+ for (let i in aTypesInfo) {
+ // Expand the permission to see if we have multiple access properties
+ // to convert
+ let perm = aTypesInfo[i].permission;
+ let access = PermissionsTable[perm].access;
+ if (access) {
+ for (let idx in access) {
+ convertPermToAllow(perm + "-" + access[idx], aPrincipal);
+ }
+ } else {
+ convertPermToAllow(perm, aPrincipal);
+ }
+ }
+}
+
+function ContentPermissionPrompt() {}
+
+ContentPermissionPrompt.prototype = {
+
+ handleExistingPermission: function handleExistingPermission(request,
+ typesInfo) {
+ typesInfo.forEach(function(type) {
+ type.action =
+ Services.perms.testExactPermissionFromPrincipal(request.principal,
+ type.access);
+ if (shouldPrompt(type.access, type.action)) {
+ type.action = Ci.nsIPermissionManager.PROMPT_ACTION;
+ }
+ });
+
+ // If all permissions are allowed already and no more than one option,
+ // call allow() without prompting.
+ let checkAllowPermission = function(type) {
+ if (type.action == Ci.nsIPermissionManager.ALLOW_ACTION &&
+ type.options.length <= 1) {
+ return true;
+ }
+ return false;
+ }
+ if (typesInfo.every(checkAllowPermission)) {
+ debug("all permission requests are allowed");
+ request.allow(buildDefaultChoices(typesInfo));
+ return true;
+ }
+
+ // If all permissions are DENY_ACTION or UNKNOWN_ACTION, call cancel()
+ // without prompting.
+ let checkDenyPermission = function(type) {
+ if (type.action == Ci.nsIPermissionManager.DENY_ACTION ||
+ type.action == Ci.nsIPermissionManager.UNKNOWN_ACTION) {
+ return true;
+ }
+ return false;
+ }
+ if (typesInfo.every(checkDenyPermission)) {
+ debug("all permission requests are denied");
+ request.cancel();
+ return true;
+ }
+ return false;
+ },
+
+ // multiple requests should be audio and video
+ checkMultipleRequest: function checkMultipleRequest(typesInfo) {
+ if (typesInfo.length == 1) {
+ return true;
+ } else if (typesInfo.length > 1) {
+ let checkIfAllowMultiRequest = function(type) {
+ return (ALLOW_MULTIPLE_REQUESTS.indexOf(type.access) !== -1);
+ }
+ if (typesInfo.every(checkIfAllowMultiRequest)) {
+ debug("legal multiple requests");
+ return true;
+ }
+ }
+
+ return false;
+ },
+
+ handledByApp: function handledByApp(request, typesInfo) {
+ if (request.principal.appId == Ci.nsIScriptSecurityManager.NO_APP_ID ||
+ request.principal.appId == Ci.nsIScriptSecurityManager.UNKNOWN_APP_ID) {
+ // This should not really happen
+ request.cancel();
+ return true;
+ }
+
+ let appsService = Cc["@mozilla.org/AppsService;1"]
+ .getService(Ci.nsIAppsService);
+ let app = appsService.getAppByLocalId(request.principal.appId);
+
+ // Check each permission if it's denied by permission manager with app's
+ // URL.
+ let notDenyAppPrincipal = function(type) {
+ let url = Services.io.newURI(app.origin, null, null);
+ let principal =
+ secMan.createCodebasePrincipal(url,
+ {appId: request.principal.appId});
+ let result = Services.perms.testExactPermissionFromPrincipal(principal,
+ type.access);
+
+ if (result == Ci.nsIPermissionManager.ALLOW_ACTION ||
+ result == Ci.nsIPermissionManager.PROMPT_ACTION) {
+ type.deny = false;
+ }
+ return !type.deny;
+ }
+ // Cancel the entire request if one of the requested permissions is denied
+ if (!typesInfo.every(notDenyAppPrincipal)) {
+ request.cancel();
+ return true;
+ }
+
+ return false;
+ },
+
+ handledByPermissionType: function handledByPermissionType(request, typesInfo) {
+ for (let i in typesInfo) {
+ if (permissionSpecificChecker.hasOwnProperty(typesInfo[i].permission) &&
+ permissionSpecificChecker[typesInfo[i].permission](request)) {
+ return true;
+ }
+ }
+
+ return false;
+ },
+
+ prompt: function(request) {
+ // Initialize the typesInfo and set the default value.
+ let typesInfo = [];
+ let perms = request.types.QueryInterface(Ci.nsIArray);
+ for (let idx = 0; idx < perms.length; idx++) {
+ let perm = perms.queryElementAt(idx, Ci.nsIContentPermissionType);
+ let tmp = {
+ permission: perm.type,
+ access: (perm.access && perm.access !== "unused") ?
+ perm.type + "-" + perm.access : perm.type,
+ options: [],
+ deny: true,
+ action: Ci.nsIPermissionManager.UNKNOWN_ACTION
+ };
+
+ // Append available options, if any.
+ let options = perm.options.QueryInterface(Ci.nsIArray);
+ for (let i = 0; i < options.length; i++) {
+ let option = options.queryElementAt(i, Ci.nsISupportsString).data;
+ tmp.options.push(option);
+ }
+ typesInfo.push(tmp);
+ }
+
+ if (secMan.isSystemPrincipal(request.principal)) {
+ request.allow(buildDefaultChoices(typesInfo));
+ return;
+ }
+
+
+ if (typesInfo.length == 0) {
+ request.cancel();
+ return;
+ }
+
+ if(!this.checkMultipleRequest(typesInfo)) {
+ request.cancel();
+ return;
+ }
+
+ if (this.handledByApp(request, typesInfo) ||
+ this.handledByPermissionType(request, typesInfo)) {
+ return;
+ }
+
+ // returns true if the request was handled
+ if (this.handleExistingPermission(request, typesInfo)) {
+ return;
+ }
+
+ // prompt PROMPT_ACTION request or request with options.
+ typesInfo = typesInfo.filter(function(type) {
+ return !type.deny && (type.action == Ci.nsIPermissionManager.PROMPT_ACTION || type.options.length > 0) ;
+ });
+
+ if (!request.element) {
+ this.delegatePrompt(request, typesInfo);
+ return;
+ }
+
+ var cancelRequest = function() {
+ request.requester.onVisibilityChange = null;
+ request.cancel();
+ }
+
+ var self = this;
+
+ // If the request was initiated from a hidden iframe
+ // we don't forward it to content and cancel it right away
+ request.requester.getVisibility( {
+ notifyVisibility: function(isVisible) {
+ if (!isVisible) {
+ cancelRequest();
+ return;
+ }
+
+ // Monitor the frame visibility and cancel the request if the frame goes
+ // away but the request is still here.
+ request.requester.onVisibilityChange = {
+ notifyVisibility: function(isVisible) {
+ if (isVisible)
+ return;
+
+ self.cancelPrompt(request, typesInfo);
+ cancelRequest();
+ }
+ }
+
+ self.delegatePrompt(request, typesInfo, function onCallback() {
+ request.requester.onVisibilityChange = null;
+ });
+ }
+ });
+
+ },
+
+ cancelPrompt: function(request, typesInfo) {
+ this.sendToBrowserWindow("cancel-permission-prompt", request,
+ typesInfo);
+ },
+
+ delegatePrompt: function(request, typesInfo, callback) {
+ this.sendToBrowserWindow("permission-prompt", request, typesInfo,
+ function(type, remember, choices) {
+ if (type == "permission-allow") {
+ rememberPermission(typesInfo, request.principal, !remember);
+ if (callback) {
+ callback();
+ }
+ request.allow(choices);
+ return;
+ }
+
+ let addDenyPermission = function(type) {
+ debug("add " + type.permission +
+ " to permission manager with DENY_ACTION");
+ if (remember) {
+ Services.perms.addFromPrincipal(request.principal, type.access,
+ Ci.nsIPermissionManager.DENY_ACTION);
+ } else if (PERMISSION_NO_SESSION.indexOf(type.access) < 0) {
+ Services.perms.addFromPrincipal(request.principal, type.access,
+ Ci.nsIPermissionManager.DENY_ACTION,
+ Ci.nsIPermissionManager.EXPIRE_SESSION,
+ 0);
+ }
+ }
+ try {
+ // This will trow if we are canceling because the remote process died.
+ // Just eat the exception and call the callback that will cleanup the
+ // visibility event listener.
+ typesInfo.forEach(addDenyPermission);
+ } catch(e) { }
+
+ if (callback) {
+ callback();
+ }
+
+ try {
+ request.cancel();
+ } catch(e) { }
+ });
+ },
+
+ sendToBrowserWindow: function(type, request, typesInfo, callback) {
+ let requestId = Cc["@mozilla.org/uuid-generator;1"]
+ .getService(Ci.nsIUUIDGenerator).generateUUID().toString();
+ if (callback) {
+ SystemAppProxy.addEventListener("mozContentEvent", function contentEvent(evt) {
+ let detail = evt.detail;
+ if (detail.id != requestId)
+ return;
+ SystemAppProxy.removeEventListener("mozContentEvent", contentEvent);
+
+ callback(detail.type, detail.remember, detail.choices);
+ })
+ }
+
+ let principal = request.principal;
+ let isApp = principal.appStatus != Ci.nsIPrincipal.APP_STATUS_NOT_INSTALLED;
+ let remember = (principal.appStatus == Ci.nsIPrincipal.APP_STATUS_PRIVILEGED ||
+ principal.appStatus == Ci.nsIPrincipal.APP_STATUS_CERTIFIED)
+ ? true
+ : request.remember;
+ let isGranted = typesInfo.every(function(type) {
+ return type.action == Ci.nsIPermissionManager.ALLOW_ACTION;
+ });
+ let permissions = {};
+ for (let i in typesInfo) {
+ debug("prompt " + typesInfo[i].permission);
+ permissions[typesInfo[i].permission] = typesInfo[i].options;
+ }
+
+ let details = {
+ type: type,
+ permissions: permissions,
+ id: requestId,
+ // This system app uses the origin from permission events to
+ // compare against the mozApp.origin of app windows, so we
+ // are not concerned with origin suffixes here (appId, etc).
+ origin: principal.originNoSuffix,
+ isApp: isApp,
+ remember: remember,
+ isGranted: isGranted,
+ };
+
+ if (isApp) {
+ details.manifestURL = DOMApplicationRegistry.getManifestURLByLocalId(principal.appId);
+ }
+
+ // request.element is defined for OOP content, while request.window
+ // is defined for In-Process content.
+ // In both cases the message needs to be dispatched to the top-level
+ // <iframe mozbrowser> container in the system app.
+ // So the above code iterates over window.realFrameElement in order
+ // to crosss mozbrowser iframes boundaries and find the top-level
+ // one in the system app.
+ // window.realFrameElement will be |null| if the code try to cross
+ // content -> chrome boundaries.
+ let targetElement = request.element;
+ let targetWindow = request.window || targetElement.ownerDocument.defaultView;
+ while (targetWindow.realFrameElement) {
+ targetElement = targetWindow.realFrameElement;
+ targetWindow = targetElement.ownerDocument.defaultView;
+ }
+
+ SystemAppProxy.dispatchEvent(details, targetElement);
+ },
+
+ classID: Components.ID("{8c719f03-afe0-4aac-91ff-6c215895d467}"),
+
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIContentPermissionPrompt])
+};
+
+(function() {
+ // Do not allow GetUserMedia while in call.
+ permissionSpecificChecker["audio-capture"] = function(request) {
+ let forbid = false;
+
+ if (forbid) {
+ request.cancel();
+ }
+
+ return forbid;
+ };
+})();
+
+//module initialization
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory([ContentPermissionPrompt]);
diff --git a/b2g/components/ContentRequestHelper.jsm b/b2g/components/ContentRequestHelper.jsm
new file mode 100644
index 0000000000..14d8d250b7
--- /dev/null
+++ b/b2g/components/ContentRequestHelper.jsm
@@ -0,0 +1,68 @@
+/* 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";
+
+this.EXPORTED_SYMBOLS = ["ContentRequestHelper"];
+
+const { interfaces: Ci, utils: Cu } = Components;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Promise.jsm");
+
+XPCOMUtils.defineLazyServiceGetter(this, "uuidgen",
+ "@mozilla.org/uuid-generator;1",
+ "nsIUUIDGenerator");
+
+XPCOMUtils.defineLazyModuleGetter(this, "SystemAppProxy",
+ "resource://gre/modules/SystemAppProxy.jsm");
+
+function debug(msg) {
+ // dump("ContentRequestHelper ** " + msg + "\n");
+}
+
+this.ContentRequestHelper = function() {
+}
+
+ContentRequestHelper.prototype = {
+
+ contentRequest: function(aContentEventName, aChromeEventName,
+ aInternalEventName, aData) {
+ let deferred = Promise.defer();
+
+ let id = uuidgen.generateUUID().toString();
+
+ SystemAppProxy.addEventListener(aContentEventName,
+ function onContentEvent(result) {
+ SystemAppProxy.removeEventListener(aContentEventName,
+ onContentEvent);
+ let msg = result.detail;
+ if (!msg || !msg.id || msg.id != id) {
+ deferred.reject("InternalErrorWrongContentEvent " +
+ JSON.stringify(msg));
+ SystemAppProxy.removeEventListener(aContentEventName,
+ onContentEvent);
+ return;
+ }
+
+ debug("Got content event " + JSON.stringify(msg));
+
+ if (msg.error) {
+ deferred.reject(msg.error);
+ } else {
+ deferred.resolve(msg.result);
+ }
+ });
+
+ let detail = {
+ eventName: aInternalEventName,
+ id: id,
+ data: aData
+ };
+ debug("Send chrome event " + JSON.stringify(detail));
+ SystemAppProxy._sendCustomEvent(aChromeEventName, detail);
+
+ return deferred.promise;
+ }
+};
diff --git a/b2g/components/DebuggerActors.js b/b2g/components/DebuggerActors.js
new file mode 100644
index 0000000000..318c46e688
--- /dev/null
+++ b/b2g/components/DebuggerActors.js
@@ -0,0 +1,83 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- /
+/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
+/* 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 { Cu } = require("chrome");
+const DevToolsUtils = require("devtools/shared/DevToolsUtils");
+const promise = require("promise");
+const { XPCOMUtils } = require("resource://gre/modules/XPCOMUtils.jsm");
+const { BrowserTabList } = require("devtools/server/actors/webbrowser");
+
+XPCOMUtils.defineLazyGetter(this, "Frames", function() {
+ const { Frames } =
+ Cu.import("resource://gre/modules/Frames.jsm", {});
+ return Frames;
+});
+
+/**
+ * Unlike the original BrowserTabList which iterates over XUL windows, we
+ * override many portions to refer to Frames for the info needed here.
+ */
+function B2GTabList(connection) {
+ BrowserTabList.call(this, connection);
+ this._listening = false;
+}
+
+B2GTabList.prototype = Object.create(BrowserTabList.prototype);
+
+B2GTabList.prototype._getBrowsers = function() {
+ return Frames.list().filter(frame => {
+ // Ignore app frames
+ return !frame.getAttribute("mozapp");
+ });
+};
+
+B2GTabList.prototype._getSelectedBrowser = function() {
+ return this._getBrowsers().find(frame => {
+ // Find the one visible browser (if any)
+ return !frame.classList.contains("hidden");
+ });
+};
+
+B2GTabList.prototype._checkListening = function() {
+ // The conditions from BrowserTabList are merged here, since we must listen to
+ // all events with our observer design.
+ this._listenForEventsIf(this._onListChanged && this._mustNotify ||
+ this._actorByBrowser.size > 0);
+};
+
+B2GTabList.prototype._listenForEventsIf = function(shouldListen) {
+ if (this._listening != shouldListen) {
+ let op = shouldListen ? "addObserver" : "removeObserver";
+ Frames[op](this);
+ this._listening = shouldListen;
+ }
+};
+
+B2GTabList.prototype.onFrameCreated = function(frame) {
+ let mozapp = frame.getAttribute("mozapp");
+ if (mozapp) {
+ // Ignore app frames
+ return;
+ }
+ this._notifyListChanged();
+ this._checkListening();
+};
+
+B2GTabList.prototype.onFrameDestroyed = function(frame) {
+ let mozapp = frame.getAttribute("mozapp");
+ if (mozapp) {
+ // Ignore app frames
+ return;
+ }
+ let actor = this._actorByBrowser.get(frame);
+ if (actor) {
+ this._handleActorClose(actor, frame);
+ }
+};
+
+exports.B2GTabList = B2GTabList;
diff --git a/b2g/components/DirectoryProvider.js b/b2g/components/DirectoryProvider.js
new file mode 100644
index 0000000000..a7dccd0c9f
--- /dev/null
+++ b/b2g/components/DirectoryProvider.js
@@ -0,0 +1,295 @@
+/* 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/. */
+
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+const Cu = Components.utils;
+const Cr = Components.results;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/AppConstants.jsm");
+
+const XRE_OS_UPDATE_APPLY_TO_DIR = "OSUpdApplyToD"
+const UPDATE_ARCHIVE_DIR = "UpdArchD"
+const LOCAL_DIR = "/data/local";
+const UPDATES_DIR = "updates/0";
+const FOTA_DIR = "updates/fota";
+const COREAPPSDIR_PREF = "b2g.coreappsdir"
+
+XPCOMUtils.defineLazyServiceGetter(Services, "env",
+ "@mozilla.org/process/environment;1",
+ "nsIEnvironment");
+
+XPCOMUtils.defineLazyServiceGetter(Services, "um",
+ "@mozilla.org/updates/update-manager;1",
+ "nsIUpdateManager");
+
+XPCOMUtils.defineLazyServiceGetter(Services, "volumeService",
+ "@mozilla.org/telephony/volume-service;1",
+ "nsIVolumeService");
+
+XPCOMUtils.defineLazyServiceGetter(this, "cpmm",
+ "@mozilla.org/childprocessmessagemanager;1",
+ "nsISyncMessageSender");
+
+XPCOMUtils.defineLazyGetter(this, "gExtStorage", function dp_gExtStorage() {
+ return Services.env.get("EXTERNAL_STORAGE");
+});
+
+// This exists to mark the affected code for bug 828858.
+const gUseSDCard = true;
+
+const VERBOSE = 1;
+var log =
+ VERBOSE ?
+ function log_dump(msg) { dump("DirectoryProvider: " + msg + "\n"); } :
+ function log_noop(msg) { };
+
+function DirectoryProvider() {
+}
+
+DirectoryProvider.prototype = {
+ classID: Components.ID("{9181eb7c-6f87-11e1-90b1-4f59d80dd2e5}"),
+
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIDirectoryServiceProvider]),
+ _xpcom_factory: XPCOMUtils.generateSingletonFactory(DirectoryProvider),
+
+ _profD: null,
+
+ getFile: function(prop, persistent) {
+ if (AppConstants.platform === "gonk") {
+ return this.getFileOnGonk(prop, persistent);
+ }
+ return this.getFileNotGonk(prop, persistent);
+ },
+
+ getFileOnGonk: function(prop, persistent) {
+ let localProps = ["cachePDir", "webappsDir", "PrefD", "indexedDBPDir",
+ "permissionDBPDir", "UpdRootD"];
+ if (localProps.indexOf(prop) != -1) {
+ let file = Cc["@mozilla.org/file/local;1"]
+ .createInstance(Ci.nsILocalFile)
+ file.initWithPath(LOCAL_DIR);
+ persistent.value = true;
+ return file;
+ }
+ if (prop == "ProfD") {
+ let dir = Cc["@mozilla.org/file/local;1"]
+ .createInstance(Ci.nsILocalFile);
+ dir.initWithPath(LOCAL_DIR+"/tests/profile");
+ if (dir.exists()) {
+ persistent.value = true;
+ return dir;
+ }
+ }
+ if (prop == "coreAppsDir") {
+ let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile)
+ file.initWithPath("/system/b2g");
+ persistent.value = true;
+ return file;
+ }
+ if (prop == UPDATE_ARCHIVE_DIR) {
+ // getUpdateDir will set persistent to false since it may toggle between
+ // /data/local/ and /mnt/sdcard based on free space and/or availability
+ // of the sdcard.
+ // before download, check if free space is 2.1 times of update.mar
+ return this.getUpdateDir(persistent, UPDATES_DIR, 2.1);
+ }
+ if (prop == XRE_OS_UPDATE_APPLY_TO_DIR) {
+ // getUpdateDir will set persistent to false since it may toggle between
+ // /data/local/ and /mnt/sdcard based on free space and/or availability
+ // of the sdcard.
+ // before apply, check if free space is 1.1 times of update.mar
+ return this.getUpdateDir(persistent, FOTA_DIR, 1.1);
+ }
+ return null;
+ },
+
+ getFileNotGonk: function(prop, persistent) {
+ // In desktop builds, coreAppsDir is the same as the profile
+ // directory unless otherwise specified. We just need to get the
+ // path from the parent, and it is then used to build
+ // jar:remoteopenfile:// uris.
+ if (prop == "coreAppsDir") {
+ let coreAppsDirPref;
+ try {
+ coreAppsDirPref = Services.prefs.getCharPref(COREAPPSDIR_PREF);
+ } catch (e) {
+ // coreAppsDirPref may not exist if we're on an older version
+ // of gaia, so just fail silently.
+ }
+ let appsDir;
+ // If pref doesn't exist or isn't set, default to old value
+ if (!coreAppsDirPref || coreAppsDirPref == "") {
+ appsDir = Services.dirsvc.get("ProfD", Ci.nsIFile);
+ appsDir.append("webapps");
+ } else {
+ appsDir = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile)
+ appsDir.initWithPath(coreAppsDirPref);
+ }
+ persistent.value = true;
+ return appsDir;
+ } else if (prop == "ProfD") {
+ let inParent = Cc["@mozilla.org/xre/app-info;1"]
+ .getService(Ci.nsIXULRuntime)
+ .processType == Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT;
+ if (inParent) {
+ // Just bail out to use the default from toolkit.
+ return null;
+ }
+ if (!this._profD) {
+ this._profD = cpmm.sendSyncMessage("getProfD", {})[0];
+ }
+ let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
+ file.initWithPath(this._profD);
+ persistent.value = true;
+ return file;
+ }
+ return null;
+ },
+
+ // The VolumeService only exists on the device, and not on desktop
+ volumeHasFreeSpace: function dp_volumeHasFreeSpace(volumePath, requiredSpace) {
+ if (!volumePath) {
+ return false;
+ }
+ if (!Services.volumeService) {
+ return false;
+ }
+ let volume = Services.volumeService.createOrGetVolumeByPath(volumePath);
+ if (!volume || volume.state !== Ci.nsIVolume.STATE_MOUNTED) {
+ return false;
+ }
+ let stat = volume.getStats();
+ if (!stat) {
+ return false;
+ }
+ return requiredSpace <= stat.freeBytes;
+ },
+
+ findUpdateDirWithFreeSpace: function dp_findUpdateDirWithFreeSpace(requiredSpace, subdir) {
+ if (!Services.volumeService) {
+ return this.createUpdatesDir(LOCAL_DIR, subdir);
+ }
+
+ let activeUpdate = Services.um.activeUpdate;
+ if (gUseSDCard) {
+ if (this.volumeHasFreeSpace(gExtStorage, requiredSpace)) {
+ let extUpdateDir = this.createUpdatesDir(gExtStorage, subdir);
+ if (extUpdateDir !== null) {
+ return extUpdateDir;
+ }
+ log("Warning: " + gExtStorage + " has enough free space for update " +
+ activeUpdate.name + ", but is not writable");
+ }
+ }
+
+ if (this.volumeHasFreeSpace(LOCAL_DIR, requiredSpace)) {
+ let localUpdateDir = this.createUpdatesDir(LOCAL_DIR, subdir);
+ if (localUpdateDir !== null) {
+ return localUpdateDir;
+ }
+ log("Warning: " + LOCAL_DIR + " has enough free space for update " +
+ activeUpdate.name + ", but is not writable");
+ }
+
+ return null;
+ },
+
+ getUpdateDir: function dp_getUpdateDir(persistent, subdir, multiple) {
+ let defaultUpdateDir = this.getDefaultUpdateDir();
+ persistent.value = false;
+
+ let activeUpdate = Services.um.activeUpdate;
+ if (!activeUpdate) {
+ log("Warning: No active update found, using default update dir: " +
+ defaultUpdateDir);
+ return defaultUpdateDir;
+ }
+
+ let selectedPatch = activeUpdate.selectedPatch;
+ if (!selectedPatch) {
+ log("Warning: No selected patch, using default update dir: " +
+ defaultUpdateDir);
+ return defaultUpdateDir;
+ }
+
+ let requiredSpace = selectedPatch.size * multiple;
+ let updateDir = this.findUpdateDirWithFreeSpace(requiredSpace, subdir);
+ if (updateDir) {
+ return updateDir;
+ }
+
+ // If we've gotten this far, there isn't enough free space to download the patch
+ // on either external storage or /data/local. All we can do is report the
+ // error and let upstream code handle it more gracefully.
+ log("Error: No volume found with " + requiredSpace + " bytes for downloading"+
+ " update " + activeUpdate.name);
+ activeUpdate.errorCode = Cr.NS_ERROR_FILE_TOO_BIG;
+ return null;
+ },
+
+ createUpdatesDir: function dp_createUpdatesDir(root, subdir) {
+ let dir = Cc["@mozilla.org/file/local;1"]
+ .createInstance(Ci.nsILocalFile);
+ dir.initWithPath(root);
+ if (!dir.isWritable()) {
+ log("Error: " + dir.path + " isn't writable");
+ return null;
+ }
+ dir.appendRelativePath(subdir);
+ if (dir.exists()) {
+ if (dir.isDirectory() && dir.isWritable()) {
+ return dir;
+ }
+ // subdir is either a file or isn't writable. In either case we
+ // can't use it.
+ log("Error: " + dir.path + " is a file or isn't writable");
+ return null;
+ }
+ // subdir doesn't exist, and the parent is writable, so try to
+ // create it. This can fail if a file named updates exists.
+ try {
+ dir.create(Ci.nsIFile.DIRECTORY_TYPE, parseInt('0770', 8));
+ } catch (e) {
+ // The create failed for some reason. We can't use it.
+ log("Error: " + dir.path + " unable to create directory");
+ return null;
+ }
+ return dir;
+ },
+
+ getDefaultUpdateDir: function dp_getDefaultUpdateDir() {
+ let path = gExtStorage;
+ if (!path) {
+ path = LOCAL_DIR;
+ }
+
+ if (Services.volumeService) {
+ let extVolume = Services.volumeService.createOrGetVolumeByPath(path);
+ if (!extVolume) {
+ path = LOCAL_DIR;
+ }
+ }
+
+ let dir = Cc["@mozilla.org/file/local;1"]
+ .createInstance(Ci.nsILocalFile)
+ dir.initWithPath(path);
+
+ if (!dir.exists() && path != LOCAL_DIR) {
+ // Fallback to LOCAL_DIR if we didn't fallback earlier
+ dir.initWithPath(LOCAL_DIR);
+
+ if (!dir.exists()) {
+ throw Cr.NS_ERROR_FILE_NOT_FOUND;
+ }
+ }
+
+ dir.appendRelativePath("updates");
+ return dir;
+ }
+};
+
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory([DirectoryProvider]);
diff --git a/b2g/components/ErrorPage.jsm b/b2g/components/ErrorPage.jsm
new file mode 100644
index 0000000000..2c0c64c212
--- /dev/null
+++ b/b2g/components/ErrorPage.jsm
@@ -0,0 +1,187 @@
+/* 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';
+
+this.EXPORTED_SYMBOLS = ['ErrorPage'];
+
+const Cu = Components.utils;
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+const kErrorPageFrameScript = 'chrome://b2g/content/ErrorPage.js';
+
+Cu.import('resource://gre/modules/Services.jsm');
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+XPCOMUtils.defineLazyGetter(this, "CertOverrideService", function () {
+ return Cc["@mozilla.org/security/certoverride;1"]
+ .getService(Ci.nsICertOverrideService);
+});
+
+/**
+ * A class to add exceptions to override SSL certificate problems.
+ * The functionality itself is borrowed from exceptionDialog.js.
+ */
+function SSLExceptions(aCallback, aUri, aWindow) {
+ this._finishCallback = aCallback;
+ this._uri = aUri;
+ this._window = aWindow;
+};
+
+SSLExceptions.prototype = {
+ _finishCallback: null,
+ _window: null,
+ _uri: null,
+ _temporary: null,
+ _sslStatus: null,
+
+ getInterface: function SSLE_getInterface(aIID) {
+ return this.QueryInterface(aIID);
+ },
+
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIBadCertListener2]),
+
+ /**
+ * To collect the SSL status we intercept the certificate error here
+ * and store the status for later use.
+ */
+ notifyCertProblem: function SSLE_notifyCertProblem(aSocketInfo,
+ aSslStatus,
+ aTargetHost) {
+ this._sslStatus = aSslStatus.QueryInterface(Ci.nsISSLStatus);
+ Services.tm.currentThread.dispatch({
+ run: this._addOverride.bind(this)
+ }, Ci.nsIThread.DISPATCH_NORMAL);
+ return true; // suppress error UI
+ },
+
+ /**
+ * Attempt to download the certificate for the location specified to get
+ * the SSLState for the certificate and the errors.
+ */
+ _checkCert: function SSLE_checkCert() {
+ this._sslStatus = null;
+ if (!this._uri) {
+ return;
+ }
+ let req = new this._window.XMLHttpRequest();
+ try {
+ req.open("GET", this._uri.prePath, true);
+ req.channel.notificationCallbacks = this;
+ let xhrHandler = (function() {
+ req.removeEventListener("load", xhrHandler);
+ req.removeEventListener("error", xhrHandler);
+ if (!this._sslStatus) {
+ // Got response from server without an SSL error.
+ if (this._finishCallback) {
+ this._finishCallback();
+ }
+ }
+ }).bind(this);
+ req.addEventListener("load", xhrHandler);
+ req.addEventListener("error", xhrHandler);
+ req.send(null);
+ } catch (e) {
+ // We *expect* exceptions if there are problems with the certificate
+ // presented by the site. Log it, just in case, but we can proceed here,
+ // with appropriate sanity checks
+ Components.utils.reportError("Attempted to connect to a site with a bad certificate in the add exception dialog. " +
+ "This results in a (mostly harmless) exception being thrown. " +
+ "Logged for information purposes only: " + e);
+ }
+ },
+
+ /**
+ * Internal method to create an override.
+ */
+ _addOverride: function SSLE_addOverride() {
+ let SSLStatus = this._sslStatus;
+ let uri = this._uri;
+ let flags = 0;
+
+ if (SSLStatus.isUntrusted) {
+ flags |= Ci.nsICertOverrideService.ERROR_UNTRUSTED;
+ }
+ if (SSLStatus.isDomainMismatch) {
+ flags |= Ci.nsICertOverrideService.ERROR_MISMATCH;
+ }
+ if (SSLStatus.isNotValidAtThisTime) {
+ flags |= Ci.nsICertOverrideService.ERROR_TIME;
+ }
+
+ CertOverrideService.rememberValidityOverride(
+ uri.asciiHost,
+ uri.port,
+ SSLStatus.serverCert,
+ flags,
+ this._temporary);
+
+ if (this._finishCallback) {
+ this._finishCallback();
+ }
+ },
+
+ /**
+ * Creates a permanent exception to override all overridable errors for
+ * the given URL.
+ */
+ addException: function SSLE_addException(aTemporary) {
+ this._temporary = aTemporary;
+ this._checkCert();
+ }
+};
+
+var ErrorPage = {
+ _addCertException: function(aMessage) {
+ let frameLoaderOwner = aMessage.target.QueryInterface(Ci.nsIFrameLoaderOwner);
+ let win = frameLoaderOwner.ownerDocument.defaultView;
+ let mm = frameLoaderOwner.frameLoader.messageManager;
+
+ let uri = Services.io.newURI(aMessage.data.url, null, null);
+ let sslExceptions = new SSLExceptions((function() {
+ mm.sendAsyncMessage('ErrorPage:ReloadPage');
+ }).bind(this), uri, win);
+ try {
+ sslExceptions.addException(!aMessage.data.isPermanent);
+ } catch (e) {
+ dump("Failed to set cert exception: " + e + "\n");
+ }
+ },
+
+ _listenError: function(frameLoader) {
+ let self = this;
+ let frameElement = frameLoader.ownerElement;
+ let injectErrorPageScript = function() {
+ let mm = frameLoader.messageManager;
+ try {
+ mm.loadFrameScript(kErrorPageFrameScript, true, true);
+ } catch (e) {
+ dump('Error loading ' + kErrorPageFrameScript + ' as frame script: ' + e + '\n');
+ }
+ mm.addMessageListener('ErrorPage:AddCertException', self._addCertException.bind(self));
+ frameElement.removeEventListener('mozbrowsererror', injectErrorPageScript, true);
+ };
+
+ frameElement.addEventListener('mozbrowsererror',
+ injectErrorPageScript,
+ true // use capture
+ );
+ },
+
+ init: function errorPageInit() {
+ Services.obs.addObserver(this, 'inprocess-browser-shown', false);
+ Services.obs.addObserver(this, 'remote-browser-shown', false);
+ },
+
+ observe: function errorPageObserve(aSubject, aTopic, aData) {
+ let frameLoader = aSubject.QueryInterface(Ci.nsIFrameLoader);
+ // Ignore notifications that aren't from a BrowserOrApp
+ if (!frameLoader.ownerIsMozBrowserOrAppFrame) {
+ return;
+ }
+ this._listenError(frameLoader);
+ }
+};
+
+ErrorPage.init();
diff --git a/b2g/components/FilePicker.js b/b2g/components/FilePicker.js
new file mode 100644
index 0000000000..803eef6818
--- /dev/null
+++ b/b2g/components/FilePicker.js
@@ -0,0 +1,223 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* 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/. */
+
+/*
+ * No magic constructor behaviour, as is de rigeur for XPCOM.
+ * If you must perform some initialization, and it could possibly fail (even
+ * due to an out-of-memory condition), you should use an Init method, which
+ * can convey failure appropriately (thrown exception in JS,
+ * NS_FAILED(nsresult) return in C++).
+ *
+ * In JS, you can actually cheat, because a thrown exception will cause the
+ * CreateInstance call to fail in turn, but not all languages are so lucky.
+ * (Though ANSI C++ provides exceptions, they are verboten in Mozilla code
+ * for portability reasons -- and even when you're building completely
+ * platform-specific code, you can't throw across an XPCOM method boundary.)
+ */
+
+const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
+
+// FIXME: improve this list of filters.
+const IMAGE_FILTERS = ['image/gif', 'image/jpeg', 'image/pjpeg',
+ 'image/png', 'image/svg+xml', 'image/tiff',
+ 'image/vnd.microsoft.icon'];
+const VIDEO_FILTERS = ['video/mpeg', 'video/mp4', 'video/ogg',
+ 'video/quicktime', 'video/webm', 'video/x-matroska',
+ 'video/x-ms-wmv', 'video/x-flv'];
+const AUDIO_FILTERS = ['audio/basic', 'audio/L24', 'audio/mp4',
+ 'audio/mpeg', 'audio/ogg', 'audio/vorbis',
+ 'audio/vnd.rn-realaudio', 'audio/vnd.wave',
+ 'audio/webm'];
+
+Cu.import('resource://gre/modules/XPCOMUtils.jsm');
+Cu.import("resource://gre/modules/osfile.jsm");
+
+XPCOMUtils.defineLazyServiceGetter(this, 'cpmm',
+ '@mozilla.org/childprocessmessagemanager;1',
+ 'nsIMessageSender');
+
+function FilePicker() {
+}
+
+FilePicker.prototype = {
+ classID: Components.ID('{436ff8f9-0acc-4b11-8ec7-e293efba3141}'),
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIFilePicker]),
+
+ /* members */
+
+ mParent: undefined,
+ mExtraProps: undefined,
+ mFilterTypes: undefined,
+ mFileEnumerator: undefined,
+ mFilePickerShownCallback: undefined,
+
+ /* methods */
+
+ init: function(parent, title, mode) {
+ this.mParent = parent;
+ this.mExtraProps = {};
+ this.mFilterTypes = [];
+ this.mMode = mode;
+
+ if (mode != Ci.nsIFilePicker.modeOpen &&
+ mode != Ci.nsIFilePicker.modeOpenMultiple) {
+ throw Cr.NS_ERROR_NOT_IMPLEMENTED;
+ }
+ },
+
+ /* readonly attribute nsILocalFile file - not implemented; */
+ /* readonly attribute nsISimpleEnumerator files - not implemented; */
+ /* readonly attribute nsIURI fileURL - not implemented; */
+
+ get domFileOrDirectoryEnumerator() {
+ return this.mFilesEnumerator;
+ },
+
+ // We don't support directory selection yet.
+ get domFileOrDirectory() {
+ return this.mFilesEnumerator ? this.mFilesEnumerator.mFiles[0] : null;
+ },
+
+ get mode() {
+ return this.mMode;
+ },
+
+ appendFilters: function(filterMask) {
+ // Ci.nsIFilePicker.filterHTML is not supported
+ // Ci.nsIFilePicker.filterText is not supported
+
+ if (filterMask & Ci.nsIFilePicker.filterImages) {
+ this.mFilterTypes = this.mFilterTypes.concat(IMAGE_FILTERS);
+ // This property is needed for the gallery app pick activity.
+ this.mExtraProps['nocrop'] = true;
+ }
+
+ // Ci.nsIFilePicker.filterXML is not supported
+ // Ci.nsIFilePicker.filterXUL is not supported
+ // Ci.nsIFilePicker.filterApps is not supported
+ // Ci.nsIFilePicker.filterAllowURLs is not supported
+
+ if (filterMask & Ci.nsIFilePicker.filterVideo) {
+ this.mFilterTypes = this.mFilterTypes.concat(VIDEO_FILTERS);
+ }
+
+ if (filterMask & Ci.nsIFilePicker.filterAudio) {
+ this.mFilterTypes = this.mFilterTypes.concat(AUDIO_FILTERS);
+ }
+
+ if (filterMask & Ci.nsIFilePicker.filterAll) {
+ // This property is needed for the gallery app pick activity.
+ this.mExtraProps['nocrop'] = true;
+ }
+ },
+
+ appendFilter: function(title, extensions) {
+ // pick activity doesn't support extensions
+ },
+
+ open: function(aFilePickerShownCallback) {
+ this.mFilePickerShownCallback = aFilePickerShownCallback;
+
+ cpmm.addMessageListener('file-picked', this);
+
+ let detail = {};
+ if (this.mFilterTypes) {
+ detail.type = this.mFilterTypes;
+ }
+
+ for (let prop in this.mExtraProps) {
+ if (!(prop in detail)) {
+ detail[prop] = this.mExtraProps[prop];
+ }
+ }
+
+ cpmm.sendAsyncMessage('file-picker', detail);
+ },
+
+ fireSuccess: function(file) {
+ this.mFilesEnumerator = {
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsISimpleEnumerator]),
+
+ mFiles: [file],
+ mIndex: 0,
+
+ hasMoreElements: function() {
+ return (this.mIndex < this.mFiles.length);
+ },
+
+ getNext: function() {
+ if (this.mIndex >= this.mFiles.length) {
+ throw Components.results.NS_ERROR_FAILURE;
+ }
+ return this.mFiles[this.mIndex++];
+ }
+ };
+
+ if (this.mFilePickerShownCallback) {
+ this.mFilePickerShownCallback.done(Ci.nsIFilePicker.returnOK);
+ this.mFilePickerShownCallback = null;
+ }
+ },
+
+ fireError: function() {
+ if (this.mFilePickerShownCallback) {
+ this.mFilePickerShownCallback.done(Ci.nsIFilePicker.returnCancel);
+ this.mFilePickerShownCallback = null;
+ }
+ },
+
+ receiveMessage: function(message) {
+ if (message.name !== 'file-picked') {
+ return;
+ }
+
+ cpmm.removeMessageListener('file-picked', this);
+
+ let data = message.data;
+ if (!data.success || !data.result.blob) {
+ this.fireError();
+ return;
+ }
+
+ // The name to be shown can be part of the message, or can be taken from
+ // the File (if the blob is a File).
+ let name = data.result.name;
+ if (!name &&
+ (data.result.blob instanceof this.mParent.File) &&
+ data.result.blob.name) {
+ name = data.result.blob.name;
+ }
+
+ // Let's try to remove the full path and take just the filename.
+ if (name) {
+ let names = OS.Path.split(name);
+ name = names.components[names.components.length - 1];
+ }
+
+ // the fallback is a filename composed by 'blob' + extension.
+ if (!name) {
+ name = 'blob';
+ if (data.result.blob.type) {
+ let mimeSvc = Cc["@mozilla.org/mime;1"].getService(Ci.nsIMIMEService);
+ let mimeInfo = mimeSvc.getFromTypeAndExtension(data.result.blob.type, '');
+ if (mimeInfo) {
+ name += '.' + mimeInfo.primaryExtension;
+ }
+ }
+ }
+
+ let file = new this.mParent.File([data.result.blob],
+ name,
+ { type: data.result.blob.type });
+
+ if (file) {
+ this.fireSuccess(file);
+ } else {
+ this.fireError();
+ }
+ }
+};
+
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory([FilePicker]);
diff --git a/b2g/components/Frames.jsm b/b2g/components/Frames.jsm
new file mode 100644
index 0000000000..0eb00cb4cb
--- /dev/null
+++ b/b2g/components/Frames.jsm
@@ -0,0 +1,146 @@
+/* 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';
+
+this.EXPORTED_SYMBOLS = ['Frames'];
+
+const Cu = Components.utils;
+const Ci = Components.interfaces;
+
+Cu.import('resource://gre/modules/Services.jsm');
+Cu.import('resource://gre/modules/SystemAppProxy.jsm');
+
+const listeners = [];
+
+const Observer = {
+ // Save a map of (MessageManager => Frame) to be able to dispatch
+ // the FrameDestroyed event with a frame reference.
+ _frames: new Map(),
+
+ // Also save current number of iframes opened by app
+ _apps: new Map(),
+
+ start: function () {
+ Services.obs.addObserver(this, 'remote-browser-shown', false);
+ Services.obs.addObserver(this, 'inprocess-browser-shown', false);
+ Services.obs.addObserver(this, 'message-manager-close', false);
+
+ SystemAppProxy.getFrames().forEach(frame => {
+ let mm = frame.QueryInterface(Ci.nsIFrameLoaderOwner).frameLoader.messageManager;
+ this._frames.set(mm, frame);
+ let mozapp = frame.getAttribute('mozapp');
+ if (mozapp) {
+ this._apps.set(mozapp, (this._apps.get(mozapp) || 0) + 1);
+ }
+ });
+ },
+
+ stop: function () {
+ Services.obs.removeObserver(this, 'remote-browser-shown');
+ Services.obs.removeObserver(this, 'inprocess-browser-shown');
+ Services.obs.removeObserver(this, 'message-manager-close');
+ this._frames.clear();
+ this._apps.clear();
+ },
+
+ observe: function (subject, topic, data) {
+ switch(topic) {
+
+ // Listen for frame creation in OOP (device) as well as in parent process (b2g desktop)
+ case 'remote-browser-shown':
+ case 'inprocess-browser-shown':
+ let frameLoader = subject;
+
+ // get a ref to the app <iframe>
+ frameLoader.QueryInterface(Ci.nsIFrameLoader);
+ let frame = frameLoader.ownerElement;
+ let mm = frame.QueryInterface(Ci.nsIFrameLoaderOwner).frameLoader.messageManager;
+ this.onMessageManagerCreated(mm, frame);
+ break;
+
+ // Every time an iframe is destroyed, its message manager also is
+ case 'message-manager-close':
+ this.onMessageManagerDestroyed(subject);
+ break;
+ }
+ },
+
+ onMessageManagerCreated: function (mm, frame) {
+ this._frames.set(mm, frame);
+
+ let isFirstAppFrame = null;
+ let mozapp = frame.getAttribute('mozapp');
+ if (mozapp) {
+ let count = (this._apps.get(mozapp) || 0) + 1;
+ this._apps.set(mozapp, count);
+ isFirstAppFrame = (count === 1);
+ }
+
+ listeners.forEach(function (listener) {
+ try {
+ listener.onFrameCreated(frame, isFirstAppFrame);
+ } catch(e) {
+ dump('Exception while calling Frames.jsm listener:' + e + '\n' +
+ e.stack + '\n');
+ }
+ });
+ },
+
+ onMessageManagerDestroyed: function (mm) {
+ let frame = this._frames.get(mm);
+ if (!frame) {
+ // We received an event for an unknown message manager
+ return;
+ }
+
+ this._frames.delete(mm);
+
+ let isLastAppFrame = null;
+ let mozapp = frame.getAttribute('mozapp');
+ if (mozapp) {
+ let count = (this._apps.get(mozapp) || 0) - 1;
+ this._apps.set(mozapp, count);
+ isLastAppFrame = (count === 0);
+ }
+
+ listeners.forEach(function (listener) {
+ try {
+ listener.onFrameDestroyed(frame, isLastAppFrame);
+ } catch(e) {
+ dump('Exception while calling Frames.jsm listener:' + e + '\n' +
+ e.stack + '\n');
+ }
+ });
+ }
+
+};
+
+var Frames = this.Frames = {
+
+ list: () => SystemAppProxy.getFrames(),
+
+ addObserver: function (listener) {
+ if (listeners.indexOf(listener) !== -1) {
+ return;
+ }
+
+ listeners.push(listener);
+ if (listeners.length == 1) {
+ Observer.start();
+ }
+ },
+
+ removeObserver: function (listener) {
+ let idx = listeners.indexOf(listener);
+ if (idx !== -1) {
+ listeners.splice(idx, 1);
+ }
+ if (listeners.length === 0) {
+ Observer.stop();
+ }
+ }
+
+};
+
diff --git a/b2g/components/FxAccountsMgmtService.jsm b/b2g/components/FxAccountsMgmtService.jsm
new file mode 100644
index 0000000000..e51f46ed7f
--- /dev/null
+++ b/b2g/components/FxAccountsMgmtService.jsm
@@ -0,0 +1,173 @@
+/* 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/. */
+
+/**
+ * Some specific (certified) apps need to get access to certain Firefox Accounts
+ * functionality that allows them to manage accounts (this is mostly sign up,
+ * sign in, logout and delete) and get information about the currently existing
+ * ones.
+ *
+ * This service listens for requests coming from these apps, triggers the
+ * appropriate Fx Accounts flows and send reponses back to the UI.
+ *
+ * The communication mechanism is based in mozFxAccountsContentEvent (for
+ * messages coming from the UI) and mozFxAccountsChromeEvent (for messages
+ * sent from the chrome side) custom events.
+ */
+
+"use strict";
+
+this.EXPORTED_SYMBOLS = ["FxAccountsMgmtService"];
+
+const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
+
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/FxAccountsCommon.js");
+
+XPCOMUtils.defineLazyModuleGetter(this, "FxAccountsManager",
+ "resource://gre/modules/FxAccountsManager.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "SystemAppProxy",
+ "resource://gre/modules/SystemAppProxy.jsm");
+
+this.FxAccountsMgmtService = {
+ _onFulfill: function(aMsgId, aData) {
+ SystemAppProxy._sendCustomEvent("mozFxAccountsChromeEvent", {
+ id: aMsgId,
+ data: aData ? aData : null
+ });
+ },
+
+ _onReject: function(aMsgId, aReason) {
+ SystemAppProxy._sendCustomEvent("mozFxAccountsChromeEvent", {
+ id: aMsgId,
+ error: aReason ? aReason : null
+ });
+ },
+
+ init: function() {
+ Services.obs.addObserver(this, ONLOGIN_NOTIFICATION, false);
+ Services.obs.addObserver(this, ONVERIFIED_NOTIFICATION, false);
+ Services.obs.addObserver(this, ONLOGOUT_NOTIFICATION, false);
+ SystemAppProxy.addEventListener("mozFxAccountsContentEvent",
+ FxAccountsMgmtService);
+ },
+
+ observe: function(aSubject, aTopic, aData) {
+ log.debug("Observed " + aTopic);
+ switch (aTopic) {
+ case ONLOGIN_NOTIFICATION:
+ case ONVERIFIED_NOTIFICATION:
+ case ONLOGOUT_NOTIFICATION:
+ // FxAccounts notifications have the form of fxaccounts:*
+ SystemAppProxy._sendCustomEvent("mozFxAccountsUnsolChromeEvent", {
+ eventName: aTopic.substring(aTopic.indexOf(":") + 1)
+ });
+ break;
+ }
+ },
+
+ handleEvent: function(aEvent) {
+ let msg = aEvent.detail;
+ log.debug("MgmtService got content event: " + JSON.stringify(msg));
+ let self = FxAccountsMgmtService;
+
+ if (!msg.id) {
+ return;
+ }
+
+ if (msg.error) {
+ self._onReject(msg.id, msg.error);
+ return;
+ }
+
+ let data = msg.data;
+ if (!data) {
+ return;
+ }
+ // Backwards compatibility: handle accountId coming from Gaia
+ if (data.accountId && typeof(data.email === "undefined")) {
+ data.email = data.accountId;
+ delete data.accountId;
+ }
+
+ // Bug 1202450 dirty hack because Gaia is sending getAccounts.
+ if (data.method == "getAccounts") {
+ data.method = "getAccount";
+ }
+
+ switch(data.method) {
+ case "getAssertion":
+ let principal = Services.scriptSecurityManager.getSystemPrincipal();
+ let audience = data.audience || principal.originNoSuffix;
+ FxAccountsManager.getAssertion(audience, principal, {
+ silent: msg.silent || false
+ }).then(result => {
+ self._onFulfill(msg.id, result);
+ }, reason => {
+ self._onReject(msg.id, reason);
+ });
+ break;
+ case "getAccount":
+ case "getKeys":
+ FxAccountsManager[data.method]().then(
+ result => {
+ // For the getAccounts case, we only expose the email and
+ // verification status so far.
+ self._onFulfill(msg.id, result);
+ },
+ reason => {
+ self._onReject(msg.id, reason);
+ }
+ ).then(null, Components.utils.reportError);
+ break;
+ case "logout":
+ FxAccountsManager.signOut().then(
+ () => {
+ self._onFulfill(msg.id);
+ },
+ reason => {
+ self._onReject(msg.id, reason);
+ }
+ ).then(null, Components.utils.reportError);
+ break;
+ case "queryAccount":
+ FxAccountsManager.queryAccount(data.email).then(
+ result => {
+ self._onFulfill(msg.id, result);
+ },
+ reason => {
+ self._onReject(msg.id, reason);
+ }
+ ).then(null, Components.utils.reportError);
+ break;
+ case "resendVerificationEmail":
+ FxAccountsManager.resendVerificationEmail().then(
+ () => {
+ self._onFulfill(msg.id);
+ },
+ reason => {
+ self._onReject(msg.id, reason);
+ }
+ ).then(null, Components.utils.reportError);
+ break;
+ case "signIn":
+ case "signUp":
+ case "refreshAuthentication":
+ FxAccountsManager[data.method](data.email, data.password,
+ data.fetchKeys).then(
+ user => {
+ self._onFulfill(msg.id, user);
+ },
+ reason => {
+ self._onReject(msg.id, reason);
+ }
+ ).then(null, Components.utils.reportError);
+ break;
+ }
+ }
+};
+
+FxAccountsMgmtService.init();
diff --git a/b2g/components/FxAccountsUIGlue.js b/b2g/components/FxAccountsUIGlue.js
new file mode 100644
index 0000000000..d62a7d14fa
--- /dev/null
+++ b/b2g/components/FxAccountsUIGlue.js
@@ -0,0 +1,39 @@
+/* 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 { interfaces: Ci, utils: Cu } = Components;
+
+Cu.import("resource://gre/modules/FxAccountsCommon.js");
+Cu.import("resource://gre/modules/ContentRequestHelper.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+function FxAccountsUIGlue() {
+}
+
+FxAccountsUIGlue.prototype = {
+
+ __proto__: ContentRequestHelper.prototype,
+
+ signInFlow: function() {
+ return this.contentRequest("mozFxAccountsRPContentEvent",
+ "mozFxAccountsUnsolChromeEvent",
+ "openFlow");
+ },
+
+ refreshAuthentication: function(aEmail) {
+ return this.contentRequest("mozFxAccountsRPContentEvent",
+ "mozFxAccountsUnsolChromeEvent",
+ "refreshAuthentication", {
+ email: aEmail
+ });
+ },
+
+ classID: Components.ID("{51875c14-91d7-4b8c-b65d-3549e101228c}"),
+
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIFxAccountsUIGlue])
+};
+
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory([FxAccountsUIGlue]);
diff --git a/b2g/components/GaiaChrome.cpp b/b2g/components/GaiaChrome.cpp
new file mode 100644
index 0000000000..2b53750c2c
--- /dev/null
+++ b/b2g/components/GaiaChrome.cpp
@@ -0,0 +1,188 @@
+/* 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 "GaiaChrome.h"
+
+#include "nsAppDirectoryServiceDefs.h"
+#include "nsChromeRegistry.h"
+#include "nsDirectoryServiceDefs.h"
+#include "nsLocalFile.h"
+#include "nsXULAppAPI.h"
+
+#include "mozilla/ClearOnShutdown.h"
+#include "mozilla/ModuleUtils.h"
+#include "mozilla/Services.h"
+#include "mozilla/FileLocation.h"
+
+#define NS_GAIACHROME_CID \
+ { 0x83f8f999, 0x6b87, 0x4dd8, { 0xa0, 0x93, 0x72, 0x0b, 0xfb, 0x67, 0x4d, 0x38 } }
+
+using namespace mozilla;
+
+StaticRefPtr<GaiaChrome> gGaiaChrome;
+
+NS_IMPL_ISUPPORTS(GaiaChrome, nsIGaiaChrome)
+
+GaiaChrome::GaiaChrome()
+ : mPackageName(NS_LITERAL_CSTRING("gaia"))
+ , mAppsDir(NS_LITERAL_STRING("apps"))
+ , mDataRoot(NS_LITERAL_STRING("/data/local"))
+ , mSystemRoot(NS_LITERAL_STRING("/system/b2g"))
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ GetProfileDir();
+ Register();
+}
+
+//virtual
+GaiaChrome::~GaiaChrome()
+{
+}
+
+nsresult
+GaiaChrome::GetProfileDir()
+{
+ nsCOMPtr<nsIFile> profDir;
+ nsresult rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
+ getter_AddRefs(profDir));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = profDir->Clone(getter_AddRefs(mProfDir));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+nsresult
+GaiaChrome::ComputeAppsPath(nsIFile* aPath)
+{
+#if defined(MOZ_MULET)
+ aPath->InitWithFile(mProfDir);
+#elif defined(MOZ_WIDGET_GONK)
+ nsCOMPtr<nsIFile> locationDetection = new nsLocalFile();
+ locationDetection->InitWithPath(mSystemRoot);
+ locationDetection->Append(mAppsDir);
+ bool appsInSystem = EnsureIsDirectory(locationDetection);
+ locationDetection->InitWithPath(mDataRoot);
+ locationDetection->Append(mAppsDir);
+ bool appsInData = EnsureIsDirectory(locationDetection);
+
+ if (!appsInData && !appsInSystem) {
+ printf_stderr("!!! NO root directory with apps found\n");
+ MOZ_ASSERT(false);
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ aPath->InitWithPath(appsInData ? mDataRoot : mSystemRoot);
+#else
+ return NS_ERROR_UNEXPECTED;
+#endif
+
+ aPath->Append(mAppsDir);
+ aPath->Append(NS_LITERAL_STRING("."));
+
+ nsresult rv = EnsureValidPath(aPath);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+bool
+GaiaChrome::EnsureIsDirectory(nsIFile* aPath)
+{
+ bool isDir = false;
+ aPath->IsDirectory(&isDir);
+ return isDir;
+}
+
+nsresult
+GaiaChrome::EnsureValidPath(nsIFile* appsDir)
+{
+ // Ensure there is a valid "apps/system" directory
+ nsCOMPtr<nsIFile> systemAppDir = new nsLocalFile();
+ systemAppDir->InitWithFile(appsDir);
+ systemAppDir->Append(NS_LITERAL_STRING("system"));
+
+ bool hasSystemAppDir = EnsureIsDirectory(systemAppDir);
+ if (!hasSystemAppDir) {
+ nsCString path; appsDir->GetNativePath(path);
+ // We don't want to continue if the apps path does not exists ...
+ printf_stderr("!!! Gaia chrome package is not a directory: %s\n", path.get());
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+GaiaChrome::Register()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(nsChromeRegistry::gChromeRegistry != nullptr);
+
+ nsCOMPtr<nsIFile> aPath = new nsLocalFile();
+ nsresult rv = ComputeAppsPath(aPath);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ FileLocation appsLocation(aPath);
+ nsCString uri;
+ appsLocation.GetURIString(uri);
+
+ char* argv[2];
+ argv[0] = (char*)mPackageName.get();
+ argv[1] = (char*)uri.get();
+
+ nsChromeRegistry::ManifestProcessingContext cx(NS_APP_LOCATION, appsLocation);
+ nsChromeRegistry::gChromeRegistry->ManifestContent(cx, 0, argv, 0);
+
+ return NS_OK;
+}
+
+already_AddRefed<GaiaChrome>
+GaiaChrome::FactoryCreate()
+{
+ if (!XRE_IsParentProcess()) {
+ return nullptr;
+ }
+
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (!gGaiaChrome) {
+ gGaiaChrome = new GaiaChrome();
+ ClearOnShutdown(&gGaiaChrome);
+ }
+
+ RefPtr<GaiaChrome> service = gGaiaChrome.get();
+ return service.forget();
+}
+
+NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(GaiaChrome,
+ GaiaChrome::FactoryCreate)
+
+NS_DEFINE_NAMED_CID(NS_GAIACHROME_CID);
+
+static const mozilla::Module::CIDEntry kGaiaChromeCIDs[] = {
+ { &kNS_GAIACHROME_CID, false, nullptr, GaiaChromeConstructor },
+ { nullptr }
+};
+
+static const mozilla::Module::ContractIDEntry kGaiaChromeContracts[] = {
+ { "@mozilla.org/b2g/gaia-chrome;1", &kNS_GAIACHROME_CID },
+ { nullptr }
+};
+
+static const mozilla::Module::CategoryEntry kGaiaChromeCategories[] = {
+ { "profile-after-change", "Gaia Chrome Registration", GAIACHROME_CONTRACTID },
+ { nullptr }
+};
+
+static const mozilla::Module kGaiaChromeModule = {
+ mozilla::Module::kVersion,
+ kGaiaChromeCIDs,
+ kGaiaChromeContracts,
+ kGaiaChromeCategories
+};
+
+NSMODULE_DEFN(GaiaChromeModule) = &kGaiaChromeModule;
diff --git a/b2g/components/GaiaChrome.h b/b2g/components/GaiaChrome.h
new file mode 100644
index 0000000000..290613b817
--- /dev/null
+++ b/b2g/components/GaiaChrome.h
@@ -0,0 +1,44 @@
+/* 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/. */
+
+#ifndef __GAIACHROME_H__
+#define __GAIACHROME_H__
+
+#include "nsIGaiaChrome.h"
+
+#include "nsIFile.h"
+
+#include "nsCOMPtr.h"
+#include "nsString.h"
+
+using namespace mozilla;
+
+class GaiaChrome final : public nsIGaiaChrome
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIGAIACHROME
+
+ static already_AddRefed<GaiaChrome>
+ FactoryCreate();
+
+private:
+ nsCString mPackageName;
+
+ nsAutoString mAppsDir;
+ nsAutoString mDataRoot;
+ nsAutoString mSystemRoot;
+
+ nsCOMPtr<nsIFile> mProfDir;
+
+ GaiaChrome();
+ ~GaiaChrome();
+
+ nsresult ComputeAppsPath(nsIFile*);
+ bool EnsureIsDirectory(nsIFile*);
+ nsresult EnsureValidPath(nsIFile*);
+ nsresult GetProfileDir();
+};
+
+#endif // __GAIACHROME_H__
diff --git a/b2g/components/GlobalSimulatorScreen.jsm b/b2g/components/GlobalSimulatorScreen.jsm
new file mode 100644
index 0000000000..2895aef964
--- /dev/null
+++ b/b2g/components/GlobalSimulatorScreen.jsm
@@ -0,0 +1,90 @@
+/* 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/. */
+
+this.EXPORTED_SYMBOLS = [ 'GlobalSimulatorScreen' ];
+
+const Cu = Components.utils;
+
+Cu.import('resource://gre/modules/XPCOMUtils.jsm');
+Cu.import('resource://gre/modules/Services.jsm');
+
+this.GlobalSimulatorScreen = {
+ mozOrientationLocked: false,
+
+ // Actual orientation of apps
+ mozOrientation: 'portrait',
+
+ // The restricted list of actual orientation that can be used
+ // if mozOrientationLocked is true
+ lockedOrientation: [],
+
+ // The faked screen orientation
+ // if screenOrientation doesn't match mozOrientation due
+ // to lockedOrientation restriction, the app will be displayed
+ // on the side on desktop
+ screenOrientation: 'portrait',
+
+ // Updated by screen.js
+ width: 0, height: 0,
+
+ lock: function(orientation) {
+ this.mozOrientationLocked = true;
+
+ // Normalize to portrait or landscape,
+ // i.e. the possible values of screenOrientation
+ function normalize(str) {
+ if (str.match(/^portrait/)) {
+ return 'portrait';
+ } else if (str.match(/^landscape/)) {
+ return 'landscape';
+ } else {
+ return 'portrait';
+ }
+ }
+ this.lockedOrientation = orientation.map(normalize);
+
+ this.updateOrientation();
+ },
+
+ unlock: function() {
+ this.mozOrientationLocked = false;
+ this.updateOrientation();
+ },
+
+ updateOrientation: function () {
+ let orientation = this.screenOrientation;
+
+ // If the orientation is locked, we have to ensure ending up with a value
+ // of lockedOrientation. If none of lockedOrientation values matches
+ // the screen orientation we just choose the first locked orientation.
+ // This will be the precise scenario where the app is displayed on the
+ // side on desktop!
+ if (this.mozOrientationLocked &&
+ this.lockedOrientation.indexOf(this.screenOrientation) == -1) {
+ orientation = this.lockedOrientation[0];
+ }
+
+ // If the actual orientation changed,
+ // we have to fire mozorientation DOM events
+ if (this.mozOrientation != orientation) {
+ this.mozOrientation = orientation;
+
+ // Notify each app screen object to fire the event
+ Services.obs.notifyObservers(null, 'simulator-orientation-change', null);
+ }
+
+ // Finally, in any case, we update the window size and orientation
+ // (Use wrappedJSObject trick to be able to pass a raw JS object)
+ Services.obs.notifyObservers({wrappedJSObject:this}, 'simulator-adjust-window-size', null);
+ },
+
+ flipScreen: function() {
+ if (this.screenOrientation == 'portrait') {
+ this.screenOrientation = 'landscape';
+ } else if (this.screenOrientation == 'landscape') {
+ this.screenOrientation = 'portrait';
+ }
+ this.updateOrientation();
+ }
+}
diff --git a/b2g/components/HelperAppDialog.js b/b2g/components/HelperAppDialog.js
new file mode 100644
index 0000000000..3709833e16
--- /dev/null
+++ b/b2g/components/HelperAppDialog.js
@@ -0,0 +1,115 @@
+// -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
+/* 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/. */
+
+const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "Downloads",
+ "resource://gre/modules/Downloads.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "Task",
+ "resource://gre/modules/Task.jsm");
+
+// -----------------------------------------------------------------------
+// HelperApp Launcher Dialog
+//
+// For now on b2g we never prompt and just download to the default
+// location.
+//
+// -----------------------------------------------------------------------
+
+function HelperAppLauncherDialog() { }
+
+HelperAppLauncherDialog.prototype = {
+ classID: Components.ID("{710322af-e6ae-4b0c-b2c9-1474a87b077e}"),
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIHelperAppLauncherDialog]),
+
+ show: function(aLauncher, aContext, aReason) {
+ aLauncher.MIMEInfo.preferredAction = Ci.nsIMIMEInfo.saveToDisk;
+ aLauncher.saveToDisk(null, false);
+ },
+
+ promptForSaveToFileAsync: function(aLauncher,
+ aContext,
+ aDefaultFile,
+ aSuggestedFileExt,
+ aForcePrompt) {
+ // Retrieve the user's default download directory.
+ Task.spawn(function*() {
+ let file = null;
+ try {
+ let defaultFolder = yield Downloads.getPreferredDownloadsDirectory();
+ let dir = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
+ dir.initWithPath(defaultFolder);
+ file = this.validateLeafName(dir, aDefaultFile, aSuggestedFileExt);
+ } catch(e) { }
+ aLauncher.saveDestinationAvailable(file);
+ }.bind(this)).then(null, Cu.reportError);
+ },
+
+ validateLeafName: function(aLocalFile, aLeafName, aFileExt) {
+ if (!(aLocalFile && this.isUsableDirectory(aLocalFile)))
+ return null;
+
+ // Remove any leading periods, since we don't want to save hidden files
+ // automatically.
+ aLeafName = aLeafName.replace(/^\.+/, "");
+
+ if (aLeafName == "")
+ aLeafName = "unnamed" + (aFileExt ? "." + aFileExt : "");
+ aLocalFile.append(aLeafName);
+
+ this.makeFileUnique(aLocalFile);
+ return aLocalFile;
+ },
+
+ makeFileUnique: function(aLocalFile) {
+ try {
+ // Note - this code is identical to that in
+ // toolkit/content/contentAreaUtils.js.
+ // If you are updating this code, update that code too! We can't share code
+ // here since this is called in a js component.
+ let collisionCount = 0;
+ while (aLocalFile.exists()) {
+ collisionCount++;
+ if (collisionCount == 1) {
+ // Append "(2)" before the last dot in (or at the end of) the filename
+ // special case .ext.gz etc files so we don't wind up with .tar(2).gz
+ if (aLocalFile.leafName.match(/\.[^\.]{1,3}\.(gz|bz2|Z)$/i))
+ aLocalFile.leafName = aLocalFile.leafName.replace(/\.[^\.]{1,3}\.(gz|bz2|Z)$/i, "(2)$&");
+ else
+ aLocalFile.leafName = aLocalFile.leafName.replace(/(\.[^\.]*)?$/, "(2)$&");
+ }
+ else {
+ // replace the last (n) in the filename with (n+1)
+ aLocalFile.leafName = aLocalFile.leafName.replace(/^(.*\()\d+\)/, "$1" + (collisionCount+1) + ")");
+ }
+ }
+ aLocalFile.create(Ci.nsIFile.NORMAL_FILE_TYPE, 0o600);
+ }
+ catch (e) {
+ dump("*** exception in makeFileUnique: " + e + "\n");
+
+ if (e.result == Cr.NS_ERROR_FILE_ACCESS_DENIED)
+ throw e;
+
+ if (aLocalFile.leafName == "" || aLocalFile.isDirectory()) {
+ aLocalFile.append("unnamed");
+ if (aLocalFile.exists())
+ aLocalFile.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, 0o600);
+ }
+ }
+ },
+
+ isUsableDirectory: function(aDirectory) {
+ return aDirectory.exists() &&
+ aDirectory.isDirectory() &&
+ aDirectory.isWritable();
+ },
+};
+
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory([HelperAppLauncherDialog]);
diff --git a/b2g/components/LogCapture.jsm b/b2g/components/LogCapture.jsm
new file mode 100644
index 0000000000..803028d574
--- /dev/null
+++ b/b2g/components/LogCapture.jsm
@@ -0,0 +1,221 @@
+/* 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/. */
+/* jshint moz: true */
+/* global Uint8Array, Components, dump */
+
+"use strict";
+
+const Cu = Components.utils;
+const Ci = Components.interfaces;
+const Cc = Components.classes;
+
+Cu.importGlobalProperties(['FileReader']);
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "Promise", "resource://gre/modules/Promise.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Screenshot", "resource://gre/modules/Screenshot.jsm");
+
+this.EXPORTED_SYMBOLS = ["LogCapture"];
+
+const SYSTEM_PROPERTY_KEY_MAX = 32;
+const SYSTEM_PROPERTY_VALUE_MAX = 92;
+
+function debug(msg) {
+ dump("LogCapture.jsm: " + msg + "\n");
+}
+
+var LogCapture = {
+ ensureLoaded: function() {
+ if (!this.ctypes) {
+ this.load();
+ }
+ },
+
+ load: function() {
+ // load in everything on first use
+ Cu.import("resource://gre/modules/ctypes.jsm", this);
+
+ this.libc = this.ctypes.open(this.ctypes.libraryName("c"));
+
+ this.read = this.libc.declare("read",
+ this.ctypes.default_abi,
+ this.ctypes.int, // bytes read (out)
+ this.ctypes.int, // file descriptor (in)
+ this.ctypes.voidptr_t, // buffer to read into (in)
+ this.ctypes.size_t // size_t size of buffer (in)
+ );
+
+ this.open = this.libc.declare("open",
+ this.ctypes.default_abi,
+ this.ctypes.int, // file descriptor (returned)
+ this.ctypes.char.ptr, // path
+ this.ctypes.int // flags
+ );
+
+ this.close = this.libc.declare("close",
+ this.ctypes.default_abi,
+ this.ctypes.int, // error code (returned)
+ this.ctypes.int // file descriptor
+ );
+
+ this.getpid = this.libc.declare("getpid",
+ this.ctypes.default_abi,
+ this.ctypes.int // PID
+ );
+
+ this.property_find_nth =
+ this.libc.declare("__system_property_find_nth",
+ this.ctypes.default_abi,
+ this.ctypes.voidptr_t, // return value: nullable prop_info*
+ this.ctypes.unsigned_int); // n: the index of the property to return
+
+ this.property_read =
+ this.libc.declare("__system_property_read",
+ this.ctypes.default_abi,
+ this.ctypes.void_t, // return: none
+ this.ctypes.voidptr_t, // non-null prop_info*
+ this.ctypes.char.ptr, // key
+ this.ctypes.char.ptr); // value
+
+ this.key_buf = this.ctypes.char.array(SYSTEM_PROPERTY_KEY_MAX)();
+ this.value_buf = this.ctypes.char.array(SYSTEM_PROPERTY_VALUE_MAX)();
+ },
+
+ cleanup: function() {
+ this.libc.close();
+
+ this.read = null;
+ this.open = null;
+ this.close = null;
+ this.property_find_nth = null;
+ this.property_read = null;
+ this.key_buf = null;
+ this.value_buf = null;
+
+ this.libc = null;
+ this.ctypes = null;
+ },
+
+ /**
+ * readLogFile
+ * Read in /dev/log/{{log}} in nonblocking mode, which will return -1 if
+ * reading would block the thread.
+ *
+ * @param log {String} The log from which to read. Must be present in /dev/log
+ * @return {Uint8Array} Raw log data
+ */
+ readLogFile: function(logLocation) {
+ this.ensureLoaded();
+
+ const O_READONLY = 0;
+ const O_NONBLOCK = 1 << 11;
+
+ const BUF_SIZE = 2048;
+
+ let BufType = this.ctypes.ArrayType(this.ctypes.char);
+ let buf = new BufType(BUF_SIZE);
+ let logArray = [];
+
+ let logFd = this.open(logLocation, O_READONLY | O_NONBLOCK);
+ if (logFd === -1) {
+ return null;
+ }
+
+ let readStart = Date.now();
+ let readCount = 0;
+ while (true) {
+ let count = this.read(logFd, buf, BUF_SIZE);
+ readCount += 1;
+
+ if (count <= 0) {
+ // log has return due to being nonblocking or running out of things
+ break;
+ }
+ for(let i = 0; i < count; i++) {
+ logArray.push(buf[i]);
+ }
+ }
+
+ let logTypedArray = new Uint8Array(logArray);
+
+ this.close(logFd);
+
+ return logTypedArray;
+ },
+
+ /**
+ * Get all system properties as a dict with keys mapping to values
+ */
+ readProperties: function() {
+ this.ensureLoaded();
+ let n = 0;
+ let propertyDict = {};
+
+ while(true) {
+ let prop_info = this.property_find_nth(n);
+ if(prop_info.isNull()) {
+ break;
+ }
+
+ // read the prop_info into the key and value buffers
+ this.property_read(prop_info, this.key_buf, this.value_buf);
+ let key = this.key_buf.readString();;
+ let value = this.value_buf.readString()
+
+ propertyDict[key] = value;
+ n++;
+ }
+
+ return propertyDict;
+ },
+
+ /**
+ * Dumping about:memory to a file in /data/local/tmp/, returning a Promise.
+ * Will be resolved with the dumped file name.
+ */
+ readAboutMemory: function() {
+ this.ensureLoaded();
+ let deferred = Promise.defer();
+
+ // Perform the dump
+ let dumper = Cc["@mozilla.org/memory-info-dumper;1"]
+ .getService(Ci.nsIMemoryInfoDumper);
+
+ let file = "/data/local/tmp/logshake-about_memory-" + this.getpid() + ".json.gz";
+ dumper.dumpMemoryReportsToNamedFile(file, function() {
+ deferred.resolve(file);
+ }, null, false);
+
+ return deferred.promise;
+ },
+
+ /**
+ * Dumping screenshot, returning a Promise. Will be resolved with the content
+ * as an ArrayBuffer.
+ */
+ getScreenshot: function() {
+ let deferred = Promise.defer();
+ try {
+ this.ensureLoaded();
+
+ let fr = new FileReader();
+ fr.onload = function(evt) {
+ deferred.resolve(new Uint8Array(evt.target.result));
+ };
+
+ fr.onerror = function(evt) {
+ deferred.reject(evt);
+ };
+
+ fr.readAsArrayBuffer(Screenshot.get());
+ } catch(e) {
+ // We pass any errors through to the deferred Promise
+ deferred.reject(e);
+ }
+
+ return deferred.promise;
+ }
+};
+
+this.LogCapture = LogCapture;
diff --git a/b2g/components/LogParser.jsm b/b2g/components/LogParser.jsm
new file mode 100644
index 0000000000..c40db97672
--- /dev/null
+++ b/b2g/components/LogParser.jsm
@@ -0,0 +1,257 @@
+/* jshint esnext: true */
+/* global DataView */
+
+"use strict";
+
+this.EXPORTED_SYMBOLS = ["LogParser"];
+
+/**
+ * Parse an array read from a /dev/log/ file. Format taken from
+ * kernel/drivers/staging/android/logger.h and system/core/logcat/logcat.cpp
+ *
+ * @param array {Uint8Array} Array read from /dev/log/ file
+ * @return {Array} List of log messages
+ */
+function parseLogArray(array) {
+ let data = new DataView(array.buffer);
+ let byteString = String.fromCharCode.apply(null, array);
+
+ // Length of bytes that precede the payload of a log message
+ // From the 5 Uint32 and 1 Uint8 reads
+ const HEADER_LENGTH = 21;
+
+ let logMessages = [];
+ let pos = 0;
+
+ while (pos + HEADER_LENGTH < byteString.length) {
+ // Parse a single log entry
+
+ // Track current offset from global position
+ let offset = 0;
+
+ // Length of the entry, discarded
+ let length = data.getUint32(pos + offset, true);
+ offset += 4;
+ // Id of the process which generated the message
+ let processId = data.getUint32(pos + offset, true);
+ offset += 4;
+ // Id of the thread which generated the message
+ let threadId = data.getUint32(pos + offset, true);
+ offset += 4;
+ // Seconds since epoch when this message was logged
+ let seconds = data.getUint32(pos + offset, true);
+ offset += 4;
+ // Nanoseconds since the last second
+ let nanoseconds = data.getUint32(pos + offset, true);
+ offset += 4;
+
+ // Priority in terms of the ANDROID_LOG_* constants (see below)
+ // This is where the length field begins counting
+ let priority = data.getUint8(pos + offset);
+
+ // Reset pos and offset to count from here
+ pos += offset;
+ offset = 0;
+ offset += 1;
+
+ // Read the tag and message, represented as null-terminated c-style strings
+ let tag = "";
+ while (byteString[pos + offset] != "\0") {
+ tag += byteString[pos + offset];
+ offset ++;
+ }
+ offset ++;
+
+ let message = "";
+ // The kernel log driver may have cut off the null byte (logprint.c)
+ while (byteString[pos + offset] != "\0" && offset < length) {
+ message += byteString[pos + offset];
+ offset ++;
+ }
+
+ // Un-skip the missing null terminator
+ if (offset === length) {
+ offset --;
+ }
+
+ offset ++;
+
+ pos += offset;
+
+ // Log messages are occasionally delimited by newlines, but are also
+ // sometimes followed by newlines as well
+ if (message.charAt(message.length - 1) === "\n") {
+ message = message.substring(0, message.length - 1);
+ }
+
+ // Add an aditional time property to mimic the milliseconds since UTC
+ // expected by Date
+ let time = seconds * 1000.0 + nanoseconds/1000000.0;
+
+ // Log messages with interleaved newlines are considered to be separate log
+ // messages by logcat
+ for (let lineMessage of message.split("\n")) {
+ logMessages.push({
+ processId: processId,
+ threadId: threadId,
+ seconds: seconds,
+ nanoseconds: nanoseconds,
+ time: time,
+ priority: priority,
+ tag: tag,
+ message: lineMessage + "\n"
+ });
+ }
+ }
+
+ return logMessages;
+}
+
+/**
+ * Get a thread-time style formatted string from time
+ * @param time {Number} Milliseconds since epoch
+ * @return {String} Formatted time string
+ */
+function getTimeString(time) {
+ let date = new Date(time);
+ function pad(number) {
+ if ( number < 10 ) {
+ return "0" + number;
+ }
+ return number;
+ }
+ return pad( date.getMonth() + 1 ) +
+ "-" + pad( date.getDate() ) +
+ " " + pad( date.getHours() ) +
+ ":" + pad( date.getMinutes() ) +
+ ":" + pad( date.getSeconds() ) +
+ "." + (date.getMilliseconds() / 1000).toFixed(3).slice(2, 5);
+}
+
+/**
+ * Pad a string using spaces on the left
+ * @param str {String} String to pad
+ * @param width {Number} Desired string length
+ */
+function padLeft(str, width) {
+ while (str.length < width) {
+ str = " " + str;
+ }
+ return str;
+}
+
+/**
+ * Pad a string using spaces on the right
+ * @param str {String} String to pad
+ * @param width {Number} Desired string length
+ */
+function padRight(str, width) {
+ while (str.length < width) {
+ str = str + " ";
+ }
+ return str;
+}
+
+/** Constant values taken from system/core/liblog */
+const ANDROID_LOG_UNKNOWN = 0;
+const ANDROID_LOG_DEFAULT = 1;
+const ANDROID_LOG_VERBOSE = 2;
+const ANDROID_LOG_DEBUG = 3;
+const ANDROID_LOG_INFO = 4;
+const ANDROID_LOG_WARN = 5;
+const ANDROID_LOG_ERROR = 6;
+const ANDROID_LOG_FATAL = 7;
+const ANDROID_LOG_SILENT = 8;
+
+/**
+ * Map a priority number to its abbreviated string equivalent
+ * @param priorityNumber {Number} Log-provided priority number
+ * @return {String} Priority number's abbreviation
+ */
+function getPriorityString(priorityNumber) {
+ switch (priorityNumber) {
+ case ANDROID_LOG_VERBOSE:
+ return "V";
+ case ANDROID_LOG_DEBUG:
+ return "D";
+ case ANDROID_LOG_INFO:
+ return "I";
+ case ANDROID_LOG_WARN:
+ return "W";
+ case ANDROID_LOG_ERROR:
+ return "E";
+ case ANDROID_LOG_FATAL:
+ return "F";
+ case ANDROID_LOG_SILENT:
+ return "S";
+ default:
+ return "?";
+ }
+}
+
+
+/**
+ * Mimic the logcat "threadtime" format, generating a formatted string from a
+ * log message object.
+ * @param logMessage {Object} A log message from the list returned by parseLogArray
+ * @return {String} threadtime formatted summary of the message
+ */
+function formatLogMessage(logMessage) {
+ // MM-DD HH:MM:SS.ms pid tid priority tag: message
+ // from system/core/liblog/logprint.c:
+ return getTimeString(logMessage.time) +
+ " " + padLeft(""+logMessage.processId, 5) +
+ " " + padLeft(""+logMessage.threadId, 5) +
+ " " + getPriorityString(logMessage.priority) +
+ " " + padRight(logMessage.tag, 8) +
+ ": " + logMessage.message;
+}
+
+/**
+ * Convert a string to a utf-8 Uint8Array
+ * @param {String} str
+ * @return {Uint8Array}
+ */
+function textEncode(str) {
+ return new TextEncoder("utf-8").encode(str);
+}
+
+/**
+ * Pretty-print an array of bytes read from a log file by parsing then
+ * threadtime formatting its entries.
+ * @param array {Uint8Array} Array of a log file's bytes
+ * @return {Uint8Array} Pretty-printed log
+ */
+function prettyPrintLogArray(array) {
+ let logMessages = parseLogArray(array);
+ return textEncode(logMessages.map(formatLogMessage).join(""));
+}
+
+/**
+ * Pretty-print an array read from the list of propreties.
+ * @param {Object} Object representing the properties
+ * @return {Uint8Array} Human-readable string of property name: property value
+ */
+function prettyPrintPropertiesArray(properties) {
+ let propertiesString = "";
+ for(let propName in properties) {
+ propertiesString += "[" + propName + "]: [" + properties[propName] + "]\n";
+ }
+ return textEncode(propertiesString);
+}
+
+/**
+ * Pretty-print a normal array. Does nothing.
+ * @param array {Uint8Array} Input array
+ * @return {Uint8Array} The same array
+ */
+function prettyPrintArray(array) {
+ return array;
+}
+
+this.LogParser = {
+ parseLogArray: parseLogArray,
+ prettyPrintArray: prettyPrintArray,
+ prettyPrintLogArray: prettyPrintLogArray,
+ prettyPrintPropertiesArray: prettyPrintPropertiesArray
+};
diff --git a/b2g/components/LogShake.jsm b/b2g/components/LogShake.jsm
new file mode 100644
index 0000000000..6426c21de9
--- /dev/null
+++ b/b2g/components/LogShake.jsm
@@ -0,0 +1,588 @@
+/* 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/. */
+
+/**
+ * LogShake is a module which listens for log requests sent by Gaia. In
+ * response to a sufficiently large acceleration (a shake), it will save log
+ * files to an arbitrary directory which it will then return on a
+ * 'capture-logs-success' event with detail.logFilenames representing each log
+ * file's name and detail.logPaths representing the patch to each log file or
+ * the path to the archive.
+ * If an error occurs it will instead produce a 'capture-logs-error' event.
+ * We send a capture-logs-start events to notify the system app and the user,
+ * since dumping can be a bit long sometimes.
+ */
+
+/* enable Mozilla javascript extensions and global strictness declaration,
+ * disable valid this checking */
+/* jshint moz: true, esnext: true */
+/* jshint -W097 */
+/* jshint -W040 */
+/* global Services, Components, dump, LogCapture, LogParser,
+ OS, Promise, volumeService, XPCOMUtils, SystemAppProxy */
+
+"use strict";
+
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+const Cu = Components.utils;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+// Constants for creating zip file taken from toolkit/webapps/tests/head.js
+const PR_RDWR = 0x04;
+const PR_CREATE_FILE = 0x08;
+const PR_TRUNCATE = 0x20;
+
+XPCOMUtils.defineLazyModuleGetter(this, "LogCapture", "resource://gre/modules/LogCapture.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "LogParser", "resource://gre/modules/LogParser.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "OS", "resource://gre/modules/osfile.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Promise", "resource://gre/modules/Promise.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Services", "resource://gre/modules/Services.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "SystemAppProxy", "resource://gre/modules/SystemAppProxy.jsm");
+
+XPCOMUtils.defineLazyServiceGetter(this, "powerManagerService",
+ "@mozilla.org/power/powermanagerservice;1",
+ "nsIPowerManagerService");
+
+XPCOMUtils.defineLazyServiceGetter(this, "volumeService",
+ "@mozilla.org/telephony/volume-service;1",
+ "nsIVolumeService");
+
+this.EXPORTED_SYMBOLS = ["LogShake"];
+
+function debug(msg) {
+ dump("LogShake.jsm: "+msg+"\n");
+}
+
+/**
+ * An empirically determined amount of acceleration corresponding to a
+ * shake.
+ */
+const EXCITEMENT_THRESHOLD = 500;
+/**
+ * The maximum fraction to update the excitement value per frame. This
+ * corresponds to requiring shaking for approximately 10 motion events (1.6
+ * seconds)
+ */
+const EXCITEMENT_FILTER_ALPHA = 0.2;
+const DEVICE_MOTION_EVENT = "devicemotion";
+const SCREEN_CHANGE_EVENT = "screenchange";
+const CAPTURE_LOGS_CONTENT_EVENT = "requestSystemLogs";
+const CAPTURE_LOGS_START_EVENT = "capture-logs-start";
+const CAPTURE_LOGS_ERROR_EVENT = "capture-logs-error";
+const CAPTURE_LOGS_SUCCESS_EVENT = "capture-logs-success";
+
+var LogShake = {
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver]),
+
+ /**
+ * If LogShake is in QA Mode, which bundles all files into a compressed archive
+ */
+ qaModeEnabled: false,
+
+ /**
+ * If LogShake is listening for device motion events. Required due to lag
+ * between HAL layer of device motion events and listening for device motion
+ * events.
+ */
+ deviceMotionEnabled: false,
+
+ /**
+ * We only listen to motion events when the screen is enabled, keep track
+ * of its state.
+ */
+ screenEnabled: true,
+
+ /**
+ * Flag monitoring if the preference to enable shake to capture is
+ * enabled in gaia.
+ */
+ listenToDeviceMotion: true,
+
+ /**
+ * If a capture has been requested and is waiting for reads/parsing. Used for
+ * debouncing.
+ */
+ captureRequested: false,
+
+ /**
+ * The current excitement (movement) level
+ */
+ excitement: 0,
+
+ /**
+ * Map of files which have log-type information to their parsers
+ */
+ LOGS_WITH_PARSERS: {
+ "/dev/log/main": LogParser.prettyPrintLogArray,
+ "/dev/log/system": LogParser.prettyPrintLogArray,
+ "/dev/log/radio": LogParser.prettyPrintLogArray,
+ "/dev/log/events": LogParser.prettyPrintLogArray,
+ "/proc/cmdline": LogParser.prettyPrintArray,
+ "/proc/kmsg": LogParser.prettyPrintArray,
+ "/proc/last_kmsg": LogParser.prettyPrintArray,
+ "/proc/meminfo": LogParser.prettyPrintArray,
+ "/proc/uptime": LogParser.prettyPrintArray,
+ "/proc/version": LogParser.prettyPrintArray,
+ "/proc/vmallocinfo": LogParser.prettyPrintArray,
+ "/proc/vmstat": LogParser.prettyPrintArray,
+ "/system/b2g/application.ini": LogParser.prettyPrintArray,
+ "/cache/recovery/last_install": LogParser.prettyPrintArray,
+ "/cache/recovery/last_kmsg": LogParser.prettyPrintArray,
+ "/cache/recovery/last_log": LogParser.prettyPrintArray
+ },
+
+ /**
+ * Start existing, observing motion events if the screen is turned on.
+ */
+ init: function() {
+ // TODO: no way of querying screen state from power manager
+ // this.handleScreenChangeEvent({ detail: {
+ // screenEnabled: powerManagerService.screenEnabled
+ // }});
+
+ // However, the screen is always on when we are being enabled because it is
+ // either due to the phone starting up or a user enabling us directly.
+ this.handleScreenChangeEvent({ detail: {
+ screenEnabled: true
+ }});
+
+ // Reset excitement to clear residual motion
+ this.excitement = 0;
+
+ SystemAppProxy.addEventListener(CAPTURE_LOGS_CONTENT_EVENT, this, false);
+ SystemAppProxy.addEventListener(SCREEN_CHANGE_EVENT, this, false);
+
+ Services.obs.addObserver(this, "xpcom-shutdown", false);
+ },
+
+ /**
+ * Handle an arbitrary event, passing it along to the proper function
+ */
+ handleEvent: function(event) {
+ switch (event.type) {
+ case DEVICE_MOTION_EVENT:
+ if (!this.deviceMotionEnabled) {
+ return;
+ }
+ this.handleDeviceMotionEvent(event);
+ break;
+
+ case SCREEN_CHANGE_EVENT:
+ this.handleScreenChangeEvent(event);
+ break;
+
+ case CAPTURE_LOGS_CONTENT_EVENT:
+ this.startCapture();
+ break;
+ }
+ },
+
+ /**
+ * Handle an observation from Services.obs
+ */
+ observe: function(subject, topic) {
+ if (topic === "xpcom-shutdown") {
+ this.uninit();
+ }
+ },
+
+ enableQAMode: function() {
+ debug("Enabling QA Mode");
+ this.qaModeEnabled = true;
+ },
+
+ disableQAMode: function() {
+ debug("Disabling QA Mode");
+ this.qaModeEnabled = false;
+ },
+
+ enableDeviceMotionListener: function() {
+ this.listenToDeviceMotion = true;
+ this.startDeviceMotionListener();
+ },
+
+ disableDeviceMotionListener: function() {
+ this.listenToDeviceMotion = false;
+ this.stopDeviceMotionListener();
+ },
+
+ startDeviceMotionListener: function() {
+ if (!this.deviceMotionEnabled &&
+ this.listenToDeviceMotion &&
+ this.screenEnabled) {
+ SystemAppProxy.addEventListener(DEVICE_MOTION_EVENT, this, false);
+ this.deviceMotionEnabled = true;
+ }
+ },
+
+ stopDeviceMotionListener: function() {
+ SystemAppProxy.removeEventListener(DEVICE_MOTION_EVENT, this, false);
+ this.deviceMotionEnabled = false;
+ },
+
+ /**
+ * Handle a motion event, keeping track of "excitement", the magnitude
+ * of the device"s acceleration.
+ */
+ handleDeviceMotionEvent: function(event) {
+ // There is a lag between disabling the event listener and event arrival
+ // ceasing.
+ if (!this.deviceMotionEnabled) {
+ return;
+ }
+
+ let acc = event.accelerationIncludingGravity;
+
+ // Updates excitement by a factor of at most alpha, ignoring sudden device
+ // motion. See bug #1101994 for more information.
+ let newExcitement = acc.x * acc.x + acc.y * acc.y + acc.z * acc.z;
+ this.excitement += (newExcitement - this.excitement) * EXCITEMENT_FILTER_ALPHA;
+
+ if (this.excitement > EXCITEMENT_THRESHOLD) {
+ this.startCapture();
+ }
+ },
+
+ startCapture: function() {
+ if (this.captureRequested) {
+ return;
+ }
+ this.captureRequested = true;
+ SystemAppProxy._sendCustomEvent(CAPTURE_LOGS_START_EVENT, {});
+ this.captureLogs().then(logResults => {
+ // On resolution send the success event to the requester
+ SystemAppProxy._sendCustomEvent(CAPTURE_LOGS_SUCCESS_EVENT, {
+ logPaths: logResults.logPaths,
+ logFilenames: logResults.logFilenames
+ });
+ this.captureRequested = false;
+ }, error => {
+ // On an error send the error event
+ SystemAppProxy._sendCustomEvent(CAPTURE_LOGS_ERROR_EVENT, {error: error});
+ this.captureRequested = false;
+ });
+ },
+
+ handleScreenChangeEvent: function(event) {
+ this.screenEnabled = event.detail.screenEnabled;
+ if (this.screenEnabled) {
+ this.startDeviceMotionListener();
+ } else {
+ this.stopDeviceMotionListener();
+ }
+ },
+
+ /**
+ * Captures and saves the current device logs, returning a promise that will
+ * resolve to an array of log filenames.
+ */
+ captureLogs: function() {
+ return this.readLogs().then(logArrays => {
+ return this.saveLogs(logArrays);
+ });
+ },
+
+ /**
+ * Read in all log files, returning their formatted contents
+ * @return {Promise<Array>}
+ */
+ readLogs: function() {
+ let logArrays = {};
+ let readPromises = [];
+
+ try {
+ logArrays["properties"] =
+ LogParser.prettyPrintPropertiesArray(LogCapture.readProperties());
+ } catch (ex) {
+ Cu.reportError("Unable to get device properties: " + ex);
+ }
+
+ // Let Gecko perfom the dump to a file, and just collect it
+ let readAboutMemoryPromise = new Promise(resolve => {
+ // Wrap the readAboutMemory promise to make it infallible
+ LogCapture.readAboutMemory().then(aboutMemory => {
+ let file = OS.Path.basename(aboutMemory);
+ let logArray;
+ try {
+ logArray = LogCapture.readLogFile(aboutMemory);
+ if (!logArray) {
+ debug("LogCapture.readLogFile() returned nothing for about:memory");
+ }
+ // We need to remove the dumped file, now that we have it in memory
+ OS.File.remove(aboutMemory);
+ } catch (ex) {
+ Cu.reportError("Unable to handle about:memory dump: " + ex);
+ }
+ logArrays[file] = LogParser.prettyPrintArray(logArray);
+ resolve();
+ }, ex => {
+ Cu.reportError("Unable to get about:memory dump: " + ex);
+ resolve();
+ });
+ });
+ readPromises.push(readAboutMemoryPromise);
+
+ // Wrap the promise to make it infallible
+ let readScreenshotPromise = new Promise(resolve => {
+ LogCapture.getScreenshot().then(screenshot => {
+ logArrays["screenshot.png"] = screenshot;
+ resolve();
+ }, ex => {
+ Cu.reportError("Unable to get screenshot dump: " + ex);
+ resolve();
+ });
+ });
+ readPromises.push(readScreenshotPromise);
+
+ for (let loc in this.LOGS_WITH_PARSERS) {
+ let logArray;
+ try {
+ logArray = LogCapture.readLogFile(loc);
+ if (!logArray) {
+ debug("LogCapture.readLogFile() returned nothing for: " + loc);
+ continue;
+ }
+ } catch (ex) {
+ Cu.reportError("Unable to LogCapture.readLogFile('" + loc + "'): " + ex);
+ continue;
+ }
+
+ try {
+ logArrays[loc] = this.LOGS_WITH_PARSERS[loc](logArray);
+ } catch (ex) {
+ Cu.reportError("Unable to parse content of '" + loc + "': " + ex);
+ continue;
+ }
+ }
+
+ // Because the promises we depend upon can't fail this means that the
+ // blocking log reads will always be honored.
+ return Promise.all(readPromises).then(() => {
+ return logArrays;
+ });
+ },
+
+ /**
+ * Save the formatted arrays of log files to an sdcard if available
+ */
+ saveLogs: function(logArrays) {
+ if (!logArrays || Object.keys(logArrays).length === 0) {
+ return Promise.reject("Zero logs saved");
+ }
+
+ if (this.qaModeEnabled) {
+ return makeBaseLogsDirectory().then(writeLogArchive(logArrays),
+ rejectFunction("Error making base log directory"));
+ } else {
+ return makeBaseLogsDirectory().then(makeLogsDirectory,
+ rejectFunction("Error making base log directory"))
+ .then(writeLogFiles(logArrays),
+ rejectFunction("Error creating log directory"));
+ }
+ },
+
+ /**
+ * Stop logshake, removing all listeners
+ */
+ uninit: function() {
+ this.stopDeviceMotionListener();
+ SystemAppProxy.removeEventListener(SCREEN_CHANGE_EVENT, this, false);
+ Services.obs.removeObserver(this, "xpcom-shutdown");
+ }
+};
+
+function getLogFilename(logLocation) {
+ // sanitize the log location
+ let logName = logLocation.replace(/\//g, "-");
+ if (logName[0] === "-") {
+ logName = logName.substring(1);
+ }
+
+ // If no extension is provided, default to forcing .log
+ let extension = ".log";
+ let logLocationExt = logLocation.split(".");
+ if (logLocationExt.length > 1) {
+ // otherwise, just append nothing
+ extension = "";
+ }
+
+ return logName + extension;
+}
+
+function getSdcardPrefix() {
+ return volumeService.getVolumeByName("sdcard").mountPoint;
+}
+
+function getLogDirectoryRoot() {
+ return "logs";
+}
+
+function getLogIdentifier() {
+ let d = new Date();
+ d = new Date(d.getTime() - d.getTimezoneOffset() * 60000);
+ let timestamp = d.toISOString().slice(0, -5).replace(/[:T]/g, "-");
+ return timestamp;
+}
+
+function rejectFunction(message) {
+ return function(err) {
+ debug(message + ": " + err);
+ return Promise.reject(err);
+ };
+}
+
+function makeBaseLogsDirectory() {
+ let sdcardPrefix;
+ try {
+ sdcardPrefix = getSdcardPrefix();
+ } catch(e) {
+ // Handles missing sdcard
+ return Promise.reject(e);
+ }
+
+ let dirNameRoot = getLogDirectoryRoot();
+
+ let logsRoot = OS.Path.join(sdcardPrefix, dirNameRoot);
+
+ debug("Creating base log directory at root " + sdcardPrefix);
+
+ return OS.File.makeDir(logsRoot, {from: sdcardPrefix}).then(
+ function() {
+ return {
+ sdcardPrefix: sdcardPrefix,
+ basePrefix: dirNameRoot
+ };
+ }
+ );
+}
+
+function makeLogsDirectory({sdcardPrefix, basePrefix}) {
+ let dirName = getLogIdentifier();
+
+ let logsRoot = OS.Path.join(sdcardPrefix, basePrefix);
+ let logsDir = OS.Path.join(logsRoot, dirName);
+
+ debug("Creating base log directory at root " + sdcardPrefix);
+ debug("Final created directory will be " + logsDir);
+
+ return OS.File.makeDir(logsDir, {ignoreExisting: false}).then(
+ function() {
+ debug("Created: " + logsDir);
+ return {
+ logPrefix: OS.Path.join(basePrefix, dirName),
+ sdcardPrefix: sdcardPrefix
+ };
+ },
+ rejectFunction("Error at OS.File.makeDir for " + logsDir)
+ );
+}
+
+function getFile(filename) {
+ let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
+ file.initWithPath(filename);
+ return file;
+}
+
+/**
+ * Make a zip file
+ * @param {String} absoluteZipFilename - Fully qualified desired location of the zip file
+ * @param {Map<String, Uint8Array>} logArrays - Map from log location to log data
+ * @return {Array<String>} Paths of entries in the archive
+ */
+function makeZipFile(absoluteZipFilename, logArrays) {
+ let logFilenames = [];
+ let zipWriter = Cc["@mozilla.org/zipwriter;1"].createInstance(Ci.nsIZipWriter);
+ let zipFile = getFile(absoluteZipFilename);
+ zipWriter.open(zipFile, PR_RDWR | PR_CREATE_FILE | PR_TRUNCATE);
+
+ for (let logLocation in logArrays) {
+ let logArray = logArrays[logLocation];
+ let logFilename = getLogFilename(logLocation);
+ logFilenames.push(logFilename);
+
+ debug("Adding " + logFilename + " to the zip");
+ let logArrayStream = Cc["@mozilla.org/io/arraybuffer-input-stream;1"]
+ .createInstance(Ci.nsIArrayBufferInputStream);
+ // Set data to be copied, default offset to 0 because it is not present on
+ // ArrayBuffer objects
+ logArrayStream.setData(logArray.buffer, logArray.byteOffset || 0,
+ logArray.byteLength);
+
+ zipWriter.addEntryStream(logFilename, Date.now(),
+ Ci.nsIZipWriter.COMPRESSION_DEFAULT,
+ logArrayStream, false);
+ }
+ zipWriter.close();
+
+ return logFilenames;
+}
+
+function writeLogArchive(logArrays) {
+ return function({sdcardPrefix, basePrefix}) {
+ // Now the directory is guaranteed to exist, save the logs into their
+ // archive file
+
+ let zipFilename = getLogIdentifier() + "-logs.zip";
+ let zipPath = OS.Path.join(basePrefix, zipFilename);
+ let zipPrefix = OS.Path.dirname(zipPath);
+ let absoluteZipPath = OS.Path.join(sdcardPrefix, zipPath);
+
+ debug("Creating zip file at " + zipPath);
+ let logFilenames = [];
+ try {
+ logFilenames = makeZipFile(absoluteZipPath, logArrays);
+ } catch(e) {
+ return Promise.reject(e);
+ }
+ debug("Zip file created");
+
+ return {
+ logFilenames: logFilenames,
+ logPaths: [zipPath],
+ compressed: true
+ };
+ };
+}
+
+function writeLogFiles(logArrays) {
+ return function({sdcardPrefix, logPrefix}) {
+ // Now the directory is guaranteed to exist, save the logs
+ let logFilenames = [];
+ let logPaths = [];
+ let saveRequests = [];
+
+ for (let logLocation in logArrays) {
+ debug("Requesting save of " + logLocation);
+ let logArray = logArrays[logLocation];
+ let logFilename = getLogFilename(logLocation);
+ // The local pathrepresents the relative path within the SD card, not the
+ // absolute path because Gaia will refer to it using the DeviceStorage
+ // API
+ let localPath = OS.Path.join(logPrefix, logFilename);
+
+ logFilenames.push(logFilename);
+ logPaths.push(localPath);
+
+ let absolutePath = OS.Path.join(sdcardPrefix, localPath);
+ let saveRequest = OS.File.writeAtomic(absolutePath, logArray);
+ saveRequests.push(saveRequest);
+ }
+
+ return Promise.all(saveRequests).then(
+ function() {
+ return {
+ logFilenames: logFilenames,
+ logPaths: logPaths,
+ compressed: false
+ };
+ },
+ rejectFunction("Error at some save request")
+ );
+ };
+}
+
+LogShake.init();
+this.LogShake = LogShake;
diff --git a/b2g/components/MailtoProtocolHandler.js b/b2g/components/MailtoProtocolHandler.js
new file mode 100644
index 0000000000..500fb91122
--- /dev/null
+++ b/b2g/components/MailtoProtocolHandler.js
@@ -0,0 +1,46 @@
+/* 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 {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
+
+Cu.import('resource://gre/modules/XPCOMUtils.jsm');
+Cu.import('resource://gre/modules/ActivityChannel.jsm');
+
+function MailtoProtocolHandler() {
+}
+
+MailtoProtocolHandler.prototype = {
+
+ scheme: "mailto",
+ defaultPort: -1,
+ protocolFlags: Ci.nsIProtocolHandler.URI_NORELATIVE |
+ Ci.nsIProtocolHandler.URI_NOAUTH |
+ Ci.nsIProtocolHandler.URI_LOADABLE_BY_ANYONE |
+ Ci.nsIProtocolHandler.URI_DOES_NOT_RETURN_DATA,
+ allowPort: () => false,
+
+ newURI: function Proto_newURI(aSpec, aOriginCharset) {
+ let uri = Cc["@mozilla.org/network/simple-uri;1"].createInstance(Ci.nsIURI);
+ uri.spec = aSpec;
+ return uri;
+ },
+
+ newChannel2: function Proto_newChannel2(aURI, aLoadInfo) {
+ return new ActivityChannel(aURI, aLoadInfo,
+ "mail-handler",
+ { URI: aURI.spec,
+ type: "mail" });
+ },
+
+ newChannel: function Proto_newChannel(aURI) {
+ return this.newChannel2(aURI, null);
+ },
+
+ classID: Components.ID("{50777e53-0331-4366-a191-900999be386c}"),
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIProtocolHandler])
+};
+
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory([MailtoProtocolHandler]);
diff --git a/b2g/components/OMAContentHandler.js b/b2g/components/OMAContentHandler.js
new file mode 100644
index 0000000000..56c87a3b22
--- /dev/null
+++ b/b2g/components/OMAContentHandler.js
@@ -0,0 +1,57 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
+
+"use strict";
+
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+const Cr = Components.results;
+const Cu = Components.utils;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+
+XPCOMUtils.defineLazyGetter(this, "cpmm", function() {
+ return Cc["@mozilla.org/childprocessmessagemanager;1"]
+ .getService(Ci.nsIMessageSender);
+});
+
+function debug(aMsg) {
+ //dump("--*-- OMAContentHandler: " + aMsg + "\n");
+}
+
+const NS_ERROR_WONT_HANDLE_CONTENT = 0x805d0001;
+
+function OMAContentHandler() {
+}
+
+OMAContentHandler.prototype = {
+ classID: Components.ID("{a6b2ab13-9037-423a-9897-dde1081be323}"),
+
+ _xpcom_factory: {
+ createInstance: function createInstance(outer, iid) {
+ if (outer != null) {
+ throw Cr.NS_ERROR_NO_AGGREGATION;
+ }
+ return new OMAContentHandler().QueryInterface(iid);
+ }
+ },
+
+ handleContent: function handleContent(aMimetype, aContext, aRequest) {
+ if (!(aRequest instanceof Ci.nsIChannel)) {
+ throw NS_ERROR_WONT_HANDLE_CONTENT;
+ }
+
+ let detail = {
+ "type": aMimetype,
+ "url": aRequest.URI.spec
+ };
+ cpmm.sendAsyncMessage("content-handler", detail);
+
+ aRequest.cancel(Cr.NS_BINDING_ABORTED);
+ },
+
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIContentHandler])
+}
+
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory([OMAContentHandler]);
diff --git a/b2g/components/OopCommandLine.js b/b2g/components/OopCommandLine.js
new file mode 100644
index 0000000000..658bbdde52
--- /dev/null
+++ b/b2g/components/OopCommandLine.js
@@ -0,0 +1,46 @@
+/* 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/. */
+
+const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Services", "resource://gre/modules/Services.jsm");
+
+function oopCommandlineHandler() {
+}
+
+oopCommandlineHandler.prototype = {
+ handle: function(cmdLine) {
+ let oopFlag = cmdLine.handleFlag("oop", false);
+ if (oopFlag) {
+ /**
+ * Manipulate preferences by adding to the *default* branch. Adding
+ * to the default branch means the changes we make won"t get written
+ * back to user preferences.
+ */
+ let prefs = Services.prefs
+ let branch = prefs.getDefaultBranch("");
+
+ try {
+ // Turn on all OOP services, making desktop run similar to phone
+ // environment
+ branch.setBoolPref("dom.ipc.tabs.disabled", false);
+ branch.setBoolPref("layers.acceleration.disabled", false);
+ branch.setBoolPref("layers.offmainthreadcomposition.async-animations", true);
+ branch.setBoolPref("layers.async-video.enabled", true);
+ branch.setBoolPref("layers.async-pan-zoom.enabled", true);
+ branch.setCharPref("gfx.content.azure.backends", "cairo");
+ } catch (e) { }
+
+ }
+ if (cmdLine.state == Ci.nsICommandLine.STATE_REMOTE_AUTO) {
+ cmdLine.preventDefault = true;
+ }
+ },
+
+ helpInfo: " --oop Use out-of-process model in B2G\n",
+ classID: Components.ID("{e30b0e13-2d12-4cb0-bc4c-4e617a1bf76e}"),
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsICommandLineHandler]),
+};
+
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory([oopCommandlineHandler]);
diff --git a/b2g/components/OrientationChangeHandler.jsm b/b2g/components/OrientationChangeHandler.jsm
new file mode 100644
index 0000000000..5007b70e08
--- /dev/null
+++ b/b2g/components/OrientationChangeHandler.jsm
@@ -0,0 +1,70 @@
+/* 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";
+
+this.EXPORTED_SYMBOLS = [];
+
+const Cu = Components.utils;
+Cu.import("resource://gre/modules/Services.jsm");
+
+var window = Services.wm.getMostRecentWindow("navigator:browser");
+var system = window.document.getElementById("systemapp");
+
+var OrientationChangeHandler = {
+ // Clockwise orientations, looping
+ orientations: ["portrait-primary", "landscape-secondary",
+ "portrait-secondary", "landscape-primary",
+ "portrait-primary"],
+
+ lastOrientation: "portrait-primary",
+
+ init: function() {
+ window.screen.addEventListener("mozorientationchange", this, true);
+ },
+
+ handleEvent: function(evt) {
+ let newOrientation = window.screen.mozOrientation;
+ let orientationIndex = this.orientations.indexOf(this.lastOrientation);
+ let nextClockwiseOrientation = this.orientations[orientationIndex + 1];
+ let fullSwitch = (newOrientation.split("-")[0] ==
+ this.lastOrientation.split("-")[0]);
+
+ this.lastOrientation = newOrientation;
+
+ let angle, xFactor, yFactor;
+ if (fullSwitch) {
+ angle = 180;
+ xFactor = 1;
+ } else {
+ angle = (nextClockwiseOrientation == newOrientation) ? 90 : -90;
+ xFactor = window.innerWidth / window.innerHeight;
+ }
+ yFactor = 1 / xFactor;
+
+ system.style.transition = "";
+ system.style.transform = "rotate(" + angle + "deg)" +
+ "scale(" + xFactor + ", " + yFactor + ")";
+
+ function trigger() {
+ system.style.transition = "transform .25s cubic-bezier(.15, .7, .6, .9)";
+
+ system.style.opacity = "";
+ system.style.transform = "";
+ }
+
+ // 180deg rotation, no resize
+ if (fullSwitch) {
+ window.setTimeout(trigger);
+ return;
+ }
+
+ window.addEventListener("resize", function waitForResize(e) {
+ window.removeEventListener("resize", waitForResize);
+ trigger();
+ });
+ }
+};
+
+OrientationChangeHandler.init();
diff --git a/b2g/components/PresentationRequestUIGlue.js b/b2g/components/PresentationRequestUIGlue.js
new file mode 100644
index 0000000000..5c50401de1
--- /dev/null
+++ b/b2g/components/PresentationRequestUIGlue.js
@@ -0,0 +1,116 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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"
+
+function debug(aMsg) {
+ // dump("-*- PresentationRequestUIGlue: " + aMsg + "\n");
+}
+
+const { interfaces: Ci, utils: Cu, classes: Cc } = Components;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "SystemAppProxy",
+ "resource://gre/modules/SystemAppProxy.jsm");
+
+function PresentationRequestUIGlue() { }
+
+PresentationRequestUIGlue.prototype = {
+
+ sendRequest: function(aUrl, aSessionId, aDevice) {
+ let localDevice;
+ try {
+ localDevice = aDevice.QueryInterface(Ci.nsIPresentationLocalDevice);
+ } catch (e) {}
+
+ if (localDevice) {
+ return this.sendTo1UA(aUrl, aSessionId, localDevice.windowId);
+ } else {
+ return this.sendTo2UA(aUrl, aSessionId);
+ }
+ },
+
+ // For 1-UA scenario
+ sendTo1UA: function(aUrl, aSessionId, aWindowId) {
+ return new Promise((aResolve, aReject) => {
+ let handler = (evt) => {
+ if (evt.type === "unload") {
+ SystemAppProxy.removeEventListenerWithId(aWindowId,
+ "unload",
+ handler);
+ SystemAppProxy.removeEventListenerWithId(aWindowId,
+ "mozPresentationContentEvent",
+ handler);
+ aReject();
+ }
+ if (evt.type === "mozPresentationContentEvent" &&
+ evt.detail.id == aSessionId) {
+ SystemAppProxy.removeEventListenerWithId(aWindowId,
+ "unload",
+ handler);
+ SystemAppProxy.removeEventListenerWithId(aWindowId,
+ "mozPresentationContentEvent",
+ handler);
+ this.appLaunchCallback(evt.detail, aResolve, aReject);
+ }
+ };
+ // If system(-remote) app is closed.
+ SystemAppProxy.addEventListenerWithId(aWindowId,
+ "unload",
+ handler);
+ // Listen to the result for the opened iframe from front-end.
+ SystemAppProxy.addEventListenerWithId(aWindowId,
+ "mozPresentationContentEvent",
+ handler);
+ SystemAppProxy.sendCustomEventWithId(aWindowId,
+ "mozPresentationChromeEvent",
+ { type: "presentation-launch-receiver",
+ url: aUrl,
+ id: aSessionId });
+ });
+ },
+
+ // For 2-UA scenario
+ sendTo2UA: function(aUrl, aSessionId) {
+ return new Promise((aResolve, aReject) => {
+ let handler = (evt) => {
+ if (evt.type === "mozPresentationContentEvent" &&
+ evt.detail.id == aSessionId) {
+ SystemAppProxy.removeEventListener("mozPresentationContentEvent",
+ handler);
+ this.appLaunchCallback(evt.detail, aResolve, aReject);
+ }
+ };
+
+ // Listen to the result for the opened iframe from front-end.
+ SystemAppProxy.addEventListener("mozPresentationContentEvent",
+ handler);
+ SystemAppProxy._sendCustomEvent("mozPresentationChromeEvent",
+ { type: "presentation-launch-receiver",
+ url: aUrl,
+ id: aSessionId });
+ });
+ },
+
+ appLaunchCallback: function(aDetail, aResolve, aReject) {
+ switch(aDetail.type) {
+ case "presentation-receiver-launched":
+ aResolve(aDetail.frame);
+ break;
+ case "presentation-receiver-permission-denied":
+ aReject();
+ break;
+ }
+ },
+
+ classID: Components.ID("{ccc8a839-0b64-422b-8a60-fb2af0e376d0}"),
+
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIPresentationRequestUIGlue])
+};
+
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory([PresentationRequestUIGlue]);
diff --git a/b2g/components/ProcessGlobal.js b/b2g/components/ProcessGlobal.js
new file mode 100644
index 0000000000..94326ad504
--- /dev/null
+++ b/b2g/components/ProcessGlobal.js
@@ -0,0 +1,202 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- /
+/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
+/* 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';
+
+/**
+ * This code exists to be a "grab bag" of global code that needs to be
+ * loaded per B2G process, but doesn't need to directly interact with
+ * web content.
+ *
+ * (It's written as an XPCOM service because it needs to watch
+ * app-startup.)
+ */
+
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+const Cu = Components.utils;
+
+Cu.import('resource://gre/modules/Services.jsm');
+Cu.import('resource://gre/modules/XPCOMUtils.jsm');
+
+XPCOMUtils.defineLazyServiceGetter(this, "settings",
+ "@mozilla.org/settingsService;1",
+ "nsISettingsService");
+
+function debug(msg) {
+ log(msg);
+}
+function log(msg) {
+ // This file implements console.log(), so use dump().
+ //dump('ProcessGlobal: ' + msg + '\n');
+}
+
+function formatStackFrame(aFrame) {
+ let functionName = aFrame.functionName || '<anonymous>';
+ return ' at ' + functionName +
+ ' (' + aFrame.filename + ':' + aFrame.lineNumber +
+ ':' + aFrame.columnNumber + ')';
+}
+
+function ConsoleMessage(aMsg, aLevel) {
+ this.timeStamp = Date.now();
+ this.msg = aMsg;
+
+ switch (aLevel) {
+ case 'error':
+ case 'assert':
+ this.logLevel = Ci.nsIConsoleMessage.error;
+ break;
+ case 'warn':
+ this.logLevel = Ci.nsIConsoleMessage.warn;
+ break;
+ case 'log':
+ case 'info':
+ this.logLevel = Ci.nsIConsoleMessage.info;
+ break;
+ default:
+ this.logLevel = Ci.nsIConsoleMessage.debug;
+ break;
+ }
+}
+
+function toggleUnrestrictedDevtools(unrestricted) {
+ Services.prefs.setBoolPref("devtools.debugger.forbid-certified-apps",
+ !unrestricted);
+ Services.prefs.setBoolPref("dom.apps.developer_mode", unrestricted);
+ // TODO: Remove once bug 1125916 is fixed.
+ Services.prefs.setBoolPref("network.disable.ipc.security", unrestricted);
+ Services.prefs.setBoolPref("dom.webcomponents.enabled", unrestricted);
+ let lock = settings.createLock();
+ lock.set("developer.menu.enabled", unrestricted, null);
+ lock.set("devtools.unrestricted", unrestricted, null);
+}
+
+ConsoleMessage.prototype = {
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIConsoleMessage]),
+ toString: function() { return this.msg; }
+};
+
+const gFactoryResetFile = "__post_reset_cmd__";
+
+function ProcessGlobal() {}
+ProcessGlobal.prototype = {
+ classID: Components.ID('{1a94c87a-5ece-4d11-91e1-d29c29f21b28}'),
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver,
+ Ci.nsISupportsWeakReference]),
+
+ wipeDir: function(path) {
+ log("wipeDir " + path);
+ let dir = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
+ dir.initWithPath(path);
+ if (!dir.exists() || !dir.isDirectory()) {
+ return;
+ }
+ let entries = dir.directoryEntries;
+ while (entries.hasMoreElements()) {
+ let file = entries.getNext().QueryInterface(Ci.nsIFile);
+ log("Deleting " + file.path);
+ try {
+ file.remove(true);
+ } catch(e) {}
+ }
+ },
+
+ processCommandsFile: function(text) {
+ log("processCommandsFile " + text);
+ let lines = text.split("\n");
+ lines.forEach((line) => {
+ log(line);
+ let params = line.split(" ");
+ switch (params[0]) {
+ case "root":
+ log("unrestrict devtools");
+ toggleUnrestrictedDevtools(true);
+ break;
+ case "wipe":
+ this.wipeDir(params[1]);
+ case "normal":
+ log("restrict devtools");
+ toggleUnrestrictedDevtools(false);
+ break;
+ }
+ });
+ },
+
+ cleanupAfterFactoryReset: function() {
+ log("cleanupAfterWipe start");
+
+ Cu.import("resource://gre/modules/osfile.jsm");
+ let dir = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
+ dir.initWithPath("/persist");
+ var postResetFile = dir.exists() ?
+ OS.Path.join("/persist", gFactoryResetFile):
+ OS.Path.join("/cache", gFactoryResetFile);
+ let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
+ file.initWithPath(postResetFile);
+ if (!file.exists()) {
+ debug("No additional command.")
+ return;
+ }
+
+ let promise = OS.File.read(postResetFile);
+ promise.then(
+ (array) => {
+ file.remove(false);
+ let decoder = new TextDecoder();
+ this.processCommandsFile(decoder.decode(array));
+ },
+ function onError(error) {
+ debug("Error: " + error);
+ }
+ );
+
+ log("cleanupAfterWipe end.");
+ },
+
+ observe: function pg_observe(subject, topic, data) {
+ switch (topic) {
+ case 'app-startup': {
+ Services.obs.addObserver(this, 'console-api-log-event', false);
+ let inParent = Cc["@mozilla.org/xre/app-info;1"]
+ .getService(Ci.nsIXULRuntime)
+ .processType == Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT;
+ if (inParent) {
+ Services.ppmm.addMessageListener("getProfD", function(message) {
+ return Services.dirsvc.get("ProfD", Ci.nsIFile).path;
+ });
+
+ this.cleanupAfterFactoryReset();
+ }
+ break;
+ }
+ case 'console-api-log-event': {
+ // Pipe `console` log messages to the nsIConsoleService which
+ // writes them to logcat on Gonk.
+ let message = subject.wrappedJSObject;
+ let args = message.arguments;
+ let stackTrace = '';
+
+ if (message.stacktrace &&
+ (message.level == 'assert' || message.level == 'error' || message.level == 'trace')) {
+ stackTrace = Array.map(message.stacktrace, formatStackFrame).join('\n');
+ } else {
+ stackTrace = formatStackFrame(message);
+ }
+
+ if (stackTrace) {
+ args.push('\n' + stackTrace);
+ }
+
+ let msg = 'Content JS ' + message.level.toUpperCase() + ': ' + Array.join(args, ' ');
+ Services.console.logMessage(new ConsoleMessage(msg, message.level));
+ break;
+ }
+ }
+ },
+};
+
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory([ProcessGlobal]);
diff --git a/b2g/components/RecoveryService.js b/b2g/components/RecoveryService.js
new file mode 100644
index 0000000000..493763e6d2
--- /dev/null
+++ b/b2g/components/RecoveryService.js
@@ -0,0 +1,160 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
+/* 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 {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/ctypes.jsm");
+Cu.import("resource://gre/modules/AppConstants.jsm");
+
+const RECOVERYSERVICE_CID = Components.ID("{b3caca5d-0bb0-48c6-912b-6be6cbf08832}");
+const RECOVERYSERVICE_CONTRACTID = "@mozilla.org/recovery-service;1";
+
+function log(msg) {
+ dump("-*- RecoveryService: " + msg + "\n");
+}
+
+const isGonk = AppConstants.platform === 'gonk';
+
+if (isGonk) {
+ var librecovery = (function() {
+ let library;
+ try {
+ library = ctypes.open("librecovery.so");
+ } catch (e) {
+ log("Unable to open librecovery.so");
+ throw Cr.NS_ERROR_FAILURE;
+ }
+ // Bug 1163956, modify updatePath from ctyps.char.ptr to ctype.char.array(4096)
+ // align with librecovery.h. 4096 comes from PATH_MAX
+ let FotaUpdateStatus = new ctypes.StructType("FotaUpdateStatus", [
+ { result: ctypes.int },
+ { updatePath: ctypes.char.array(4096) }
+ ]);
+
+ return {
+ factoryReset: library.declare("factoryReset",
+ ctypes.default_abi,
+ ctypes.int),
+ installFotaUpdate: library.declare("installFotaUpdate",
+ ctypes.default_abi,
+ ctypes.int,
+ ctypes.char.ptr,
+ ctypes.int),
+
+ FotaUpdateStatus: FotaUpdateStatus,
+ getFotaUpdateStatus: library.declare("getFotaUpdateStatus",
+ ctypes.default_abi,
+ ctypes.int,
+ FotaUpdateStatus.ptr)
+ };
+ })();
+
+}
+
+const gFactoryResetFile = "__post_reset_cmd__";
+
+function RecoveryService() {}
+
+RecoveryService.prototype = {
+ classID: RECOVERYSERVICE_CID,
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIRecoveryService]),
+ classInfo: XPCOMUtils.generateCI({
+ classID: RECOVERYSERVICE_CID,
+ contractID: RECOVERYSERVICE_CONTRACTID,
+ interfaces: [Ci.nsIRecoveryService],
+ classDescription: "B2G Recovery Service"
+ }),
+
+ factoryReset: function RS_factoryReset(reason) {
+ if (!isGonk) {
+ Cr.NS_ERROR_FAILURE;
+ }
+
+ function doReset() {
+ // If this succeeds, then the device reboots and this never returns
+ if (librecovery.factoryReset() != 0) {
+ log("Error: Factory reset failed. Trying again after clearing cache.");
+ }
+ let cache = Cc["@mozilla.org/netwerk/cache-storage-service;1"]
+ .getService(Ci.nsICacheStorageService);
+ cache.clear();
+ if (librecovery.factoryReset() != 0) {
+ log("Error: Factory reset failed again");
+ }
+ }
+
+ log("factoryReset " + reason);
+ let commands = [];
+ if (reason == "wipe") {
+ let volumeService = Cc["@mozilla.org/telephony/volume-service;1"]
+ .getService(Ci.nsIVolumeService);
+ let volNames = volumeService.getVolumeNames();
+ log("Found " + volNames.length + " volumes");
+
+ for (let i = 0; i < volNames.length; i++) {
+ let name = volNames.queryElementAt(i, Ci.nsISupportsString);
+ let volume = volumeService.getVolumeByName(name.data);
+ log("Got volume: " + name.data + " at " + volume.mountPoint);
+ commands.push("wipe " + volume.mountPoint);
+ }
+ } else if (reason == "root") {
+ commands.push("root");
+ }
+
+ if (commands.length > 0) {
+ Cu.import("resource://gre/modules/osfile.jsm");
+ let dir = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
+ dir.initWithPath("/persist");
+ var postResetFile = dir.exists() ?
+ OS.Path.join("/persist", gFactoryResetFile):
+ OS.Path.join("/cache", gFactoryResetFile);
+ let encoder = new TextEncoder();
+ let text = commands.join("\n");
+ let array = encoder.encode(text);
+ let promise = OS.File.writeAtomic(postResetFile, array,
+ { tmpPath: postResetFile + ".tmp" });
+
+ promise.then(doReset, function onError(error) {
+ log("Error: " + error);
+ });
+ } else {
+ doReset();
+ }
+ },
+
+ installFotaUpdate: function RS_installFotaUpdate(updatePath) {
+ if (!isGonk) {
+ throw Cr.NS_ERROR_FAILURE;
+ }
+
+ // If this succeeds, then the device reboots and this never returns
+ if (librecovery.installFotaUpdate(updatePath, updatePath.length) != 0) {
+ log("Error: FOTA install failed. Trying again after clearing cache.");
+ }
+ var cache = Cc["@mozilla.org/netwerk/cache-storage-service;1"].getService(Ci.nsICacheStorageService);
+ cache.clear();
+ if (librecovery.installFotaUpdate(updatePath, updatePath.length) != 0) {
+ log("Error: FOTA install failed again");
+ }
+ },
+
+ getFotaUpdateStatus: function RS_getFotaUpdateStatus() {
+ let status = Ci.nsIRecoveryService.FOTA_UPDATE_UNKNOWN;
+
+ if (isGonk) {
+ let cStatus = librecovery.FotaUpdateStatus();
+
+ if (librecovery.getFotaUpdateStatus(cStatus.address()) == 0) {
+ status = cStatus.result;
+ }
+ }
+ return status;
+ }
+};
+
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory([RecoveryService]);
diff --git a/b2g/components/SafeMode.jsm b/b2g/components/SafeMode.jsm
new file mode 100644
index 0000000000..9f9342f679
--- /dev/null
+++ b/b2g/components/SafeMode.jsm
@@ -0,0 +1,150 @@
+/* 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";
+
+this.EXPORTED_SYMBOLS = ["SafeMode"];
+
+const Cu = Components.utils;
+
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/AppConstants.jsm");
+
+const kSafeModePref = "b2g.safe_mode";
+const kSafeModePage = "safe_mode.html";
+
+function debug(aStr) {
+ //dump("-*- SafeMode: " + aStr + "\n");
+}
+
+// This module is responsible for checking whether we want to start in safe
+// mode or not. The flow is as follow:
+// - wait for the `b2g.safe_mode` preference to be set to something different
+// than `unset` by nsAppShell
+// - If it's set to `no`, just start normally.
+// - If it's set to `yes`, we load a stripped down system app from safe_mode.html"
+// - This page is responsible to dispatch a mozContentEvent to us.
+// - If the user choose SafeMode, we disable all add-ons.
+// - We go on with startup.
+
+this.SafeMode = {
+ // Returns a promise that resolves when nsAppShell has set the
+ // b2g.safe_mode_state_ready preference to `true`.
+ _waitForPref: function() {
+ debug("waitForPref");
+ try {
+ let currentMode = Services.prefs.getCharPref(kSafeModePref);
+ debug("current mode: " + currentMode);
+ if (currentMode !== "unset") {
+ return Promise.resolve();
+ }
+ } catch(e) { debug("No current mode available!"); }
+
+ // Wait for the preference to toggle.
+ return new Promise((aResolve, aReject) => {
+ let observer = function(aSubject, aTopic, aData) {
+ if (Services.prefs.getCharPref(kSafeModePref)) {
+ Services.prefs.removeObserver(kSafeModePref, observer, false);
+ aResolve();
+ }
+ }
+
+ Services.prefs.addObserver(kSafeModePref, observer, false);
+ });
+ },
+
+ // Resolves once the user has decided how to start.
+ // Note that all the actions happen here, so there is no other action from
+ // consumers than to go on.
+ _waitForUser: function() {
+ debug("waitForUser");
+ let isSafeMode = Services.prefs.getCharPref(kSafeModePref) === "yes";
+ if (!isSafeMode) {
+ return Promise.resolve();
+ }
+ debug("Starting in Safe Mode!");
+
+ // Load $system_app/safe_mode.html as a full screen iframe, and wait for
+ // the user to make a choice.
+ let shell = SafeMode.window.shell;
+ let document = SafeMode.window.document;
+ SafeMode.window.screen.mozLockOrientation("portrait");
+
+ let url = Services.io.newURI(shell.homeURL, null, null)
+ .resolve(kSafeModePage);
+ debug("Registry is ready, loading " + url);
+ let frame = document.createElementNS("http://www.w3.org/1999/xhtml", "html:iframe");
+ frame.setAttribute("mozbrowser", "true");
+ frame.setAttribute("mozapp", shell.manifestURL);
+ frame.setAttribute("id", "systemapp"); // To keep screen.js happy.
+ let contentBrowser = document.body.appendChild(frame);
+
+ return new Promise((aResolve, aReject) => {
+ let content = contentBrowser.contentWindow;
+
+ // Stripped down version of the system app bootstrap.
+ function handleEvent(e) {
+ switch(e.type) {
+ case "mozbrowserloadstart":
+ if (content.document.location == "about:blank") {
+ contentBrowser.addEventListener("mozbrowserlocationchange", handleEvent, true);
+ contentBrowser.removeEventListener("mozbrowserloadstart", handleEvent, true);
+ return;
+ }
+
+ notifyContentStart();
+ break;
+ case "mozbrowserlocationchange":
+ if (content.document.location == "about:blank") {
+ return;
+ }
+
+ contentBrowser.removeEventListener("mozbrowserlocationchange", handleEvent, true);
+ notifyContentStart();
+ break;
+ case "mozContentEvent":
+ content.removeEventListener("mozContentEvent", handleEvent, true);
+ contentBrowser.parentNode.removeChild(contentBrowser);
+
+ if (e.detail == "safemode-yes") {
+ // Really starting in safe mode, let's disable add-ons first.
+ // TODO: disable add-ons
+ aResolve();
+ } else {
+ aResolve();
+ }
+ break;
+ }
+ }
+
+ function notifyContentStart() {
+ let window = SafeMode.window;
+ window.shell.sendEvent(window, "SafeModeStart");
+ contentBrowser.setVisible(true);
+
+ // browser-ui-startup-complete is used by the AppShell to stop the
+ // boot animation and start gecko rendering.
+ Services.obs.notifyObservers(null, "browser-ui-startup-complete", "");
+ content.addEventListener("mozContentEvent", handleEvent, true);
+ }
+
+ contentBrowser.addEventListener("mozbrowserloadstart", handleEvent, true);
+ contentBrowser.src = url;
+ });
+ },
+
+ // Returns a Promise that resolves once we have decided to run in safe mode
+ // or not. All the safe mode switching actions happen before resolving the
+ // promise.
+ check: function(aWindow) {
+ debug("check");
+ this.window = aWindow;
+ if (AppConstants.platform !== "gonk") {
+ // For now we only have gonk support.
+ return Promise.resolve();
+ }
+
+ return this._waitForPref().then(this._waitForUser);
+ }
+}
diff --git a/b2g/components/Screenshot.jsm b/b2g/components/Screenshot.jsm
new file mode 100644
index 0000000000..e6f8093759
--- /dev/null
+++ b/b2g/components/Screenshot.jsm
@@ -0,0 +1,43 @@
+/* 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 { classes: Cc, interfaces: Ci, utils: Cu } = Components;
+
+Cu.import('resource://gre/modules/XPCOMUtils.jsm');
+
+XPCOMUtils.defineLazyModuleGetter(this, "SystemAppProxy", "resource://gre/modules/SystemAppProxy.jsm");
+
+this.EXPORTED_SYMBOLS = ['Screenshot'];
+
+var Screenshot = {
+ get: function screenshot_get() {
+ let systemAppFrame = SystemAppProxy.getFrame();
+ let window = systemAppFrame.ownerDocument.defaultView;
+ let document = window.document;
+
+ var canvas = document.createElementNS('http://www.w3.org/1999/xhtml', 'canvas');
+ var docRect = document.body.getBoundingClientRect();
+ var width = docRect.width;
+ var height = docRect.height;
+
+ // Convert width and height from CSS pixels (potentially fractional)
+ // to device pixels (integer).
+ var scale = window.devicePixelRatio;
+ canvas.setAttribute('width', Math.round(width * scale));
+ canvas.setAttribute('height', Math.round(height * scale));
+
+ var context = canvas.getContext('2d');
+ var flags =
+ context.DRAWWINDOW_DRAW_CARET |
+ context.DRAWWINDOW_DRAW_VIEW |
+ context.DRAWWINDOW_USE_WIDGET_LAYERS;
+ context.scale(scale, scale);
+ context.drawWindow(window, 0, 0, width, height, 'rgb(255,255,255)', flags);
+
+ return canvas.mozGetAsFile('screenshot', 'image/png');
+ }
+};
+this.Screenshot = Screenshot;
diff --git a/b2g/components/SignInToWebsite.jsm b/b2g/components/SignInToWebsite.jsm
new file mode 100644
index 0000000000..fd1349d468
--- /dev/null
+++ b/b2g/components/SignInToWebsite.jsm
@@ -0,0 +1,444 @@
+/* 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/. */
+
+/*
+ * SignInToWebsite.jsm - UX Controller and means for accessing identity
+ * cookies on behalf of relying parties.
+ *
+ * Currently, the b2g security architecture isolates web applications
+ * so that each window has access only to a local cookie jar:
+ *
+ * To prevent Web apps from interfering with one another, each one is
+ * hosted on a separate domain, and therefore may only access the
+ * resources associated with its domain. These resources include
+ * things such as IndexedDB databases, cookies, offline storage,
+ * and so forth.
+ *
+ * -- https://developer.mozilla.org/en-US/docs/Mozilla/Firefox_OS/Security/Security_model
+ *
+ * As a result, an authentication system like Persona cannot share its
+ * cookie jar with multiple relying parties, and so would require a
+ * fresh login request in every window. This would not be a good
+ * experience.
+ *
+ *
+ * In order for navigator.id.request() to maintain state in a single
+ * cookie jar, we cause all Persona interactions to take place in a
+ * content context that is launched by the system application, with the
+ * result that Persona has a single cookie jar that all Relying
+ * Parties can use. Since of course those Relying Parties cannot
+ * reach into the system cookie jar, the Controller in this module
+ * provides a way to get messages and data to and fro between the
+ * Relying Party in its window context, and the Persona internal api
+ * in its context.
+ *
+ * On the Relying Party's side, say a web page invokes
+ * navigator.id.watch(), to register callbacks, and then
+ * navigator.id.request() to request an assertion. The navigator.id
+ * calls are provided by nsDOMIdentity. nsDOMIdentity messages down
+ * to the privileged DOMIdentity code (using cpmm and ppmm message
+ * managers). DOMIdentity stores the state of Relying Party flows
+ * using an Identity service (MinimalIdentity.jsm), and emits messages
+ * requesting Persona functions (doWatch, doReady, doLogout).
+ *
+ * The Identity service sends these observer messages to the
+ * Controller in this module, which in turn triggers content to open a
+ * window to host the Persona js. If user interaction is required,
+ * content will open the trusty UI. If user interaction is not required,
+ * and we only need to get to Persona functions, content will open a
+ * hidden iframe. In either case, a window is opened into which the
+ * controller causes the script identity.js to be injected. This
+ * script provides the glue between the in-page javascript and the
+ * pipe back down to the Controller, translating navigator.internal
+ * function callbacks into messages sent back to the Controller.
+ *
+ * As a result, a navigator.internal function in the hosted popup or
+ * iframe can call back to the injected identity.js (doReady, doLogin,
+ * or doLogout). identity.js callbacks send messages back through the
+ * pipe to the Controller. The controller invokes the corresponding
+ * function on the Identity Service (doReady, doLogin, or doLogout).
+ * The IdentityService calls the corresponding callback for the
+ * correct Relying Party, which causes DOMIdentity to send a message
+ * up to the Relying Party through nsDOMIdentity
+ * (Identity:RP:Watch:OnLogin etc.), and finally, nsDOMIdentity
+ * receives these messages and calls the original callback that the
+ * Relying Party registered (navigator.id.watch(),
+ * navigator.id.request(), or navigator.id.logout()).
+ */
+
+"use strict";
+
+this.EXPORTED_SYMBOLS = ["SignInToWebsiteController"];
+
+const Ci = Components.interfaces;
+const Cu = Components.utils;
+
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "getRandomId",
+ "resource://gre/modules/identity/IdentityUtils.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "IdentityService",
+ "resource://gre/modules/identity/MinimalIdentity.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "Logger",
+ "resource://gre/modules/identity/LogUtils.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "SystemAppProxy",
+ "resource://gre/modules/SystemAppProxy.jsm");
+
+// The default persona uri; can be overwritten with toolkit.identity.uri pref.
+// Do this if you want to repoint to a different service for testing.
+// There's no point in setting up an observer to monitor the pref, as b2g prefs
+// can only be overwritten when the profie is recreated. So just get the value
+// on start-up.
+var kPersonaUri = "https://firefoxos.persona.org";
+try {
+ kPersonaUri = Services.prefs.getCharPref("toolkit.identity.uri");
+} catch(noSuchPref) {
+ // stick with the default value
+}
+
+// JS shim that contains the callback functions that
+// live within the identity UI provisioning frame.
+const kIdentityShimFile = "chrome://b2g/content/identity.js";
+
+// Type of MozChromeEvents to handle id dialogs.
+const kOpenIdentityDialog = "id-dialog-open";
+const kDoneIdentityDialog = "id-dialog-done";
+const kCloseIdentityDialog = "id-dialog-close-iframe";
+
+// Observer messages to communicate to shim
+const kIdentityDelegateWatch = "identity-delegate-watch";
+const kIdentityDelegateRequest = "identity-delegate-request";
+const kIdentityDelegateLogout = "identity-delegate-logout";
+const kIdentityDelegateFinished = "identity-delegate-finished";
+const kIdentityDelegateReady = "identity-delegate-ready";
+
+const kIdentityControllerDoMethod = "identity-controller-doMethod";
+
+function log(...aMessageArgs) {
+ Logger.log.apply(Logger, ["SignInToWebsiteController"].concat(aMessageArgs));
+}
+
+log("persona uri =", kPersonaUri);
+
+function sendChromeEvent(details) {
+ details.uri = kPersonaUri;
+ SystemAppProxy.dispatchEvent(details);
+}
+
+function Pipe() {
+ this._watchers = [];
+}
+
+Pipe.prototype = {
+ init: function pipe_init() {
+ Services.obs.addObserver(this, "identity-child-process-shutdown", false);
+ Services.obs.addObserver(this, "identity-controller-unwatch", false);
+ },
+
+ uninit: function pipe_uninit() {
+ Services.obs.removeObserver(this, "identity-child-process-shutdown");
+ Services.obs.removeObserver(this, "identity-controller-unwatch");
+ },
+
+ observe: function Pipe_observe(aSubject, aTopic, aData) {
+ let options = {};
+ if (aSubject) {
+ options = aSubject.wrappedJSObject;
+ }
+ switch (aTopic) {
+ case "identity-child-process-shutdown":
+ log("pipe removing watchers by message manager");
+ this._removeWatchers(null, options.messageManager);
+ break;
+
+ case "identity-controller-unwatch":
+ log("unwatching", options.id);
+ this._removeWatchers(options.id, options.messageManager);
+ break;
+ }
+ },
+
+ _addWatcher: function Pipe__addWatcher(aId, aMm) {
+ log("Adding watcher with id", aId);
+ for (let i = 0; i < this._watchers.length; ++i) {
+ let watcher = this._watchers[i];
+ if (this._watcher.id === aId) {
+ watcher.count++;
+ return;
+ }
+ }
+ this._watchers.push({id: aId, count: 1, mm: aMm});
+ },
+
+ _removeWatchers: function Pipe__removeWatcher(aId, aMm) {
+ let checkId = aId !== null;
+ let index = -1;
+ for (let i = 0; i < this._watchers.length; ++i) {
+ let watcher = this._watchers[i];
+ if (watcher.mm === aMm &&
+ (!checkId || (checkId && watcher.id === aId))) {
+ index = i;
+ break;
+ }
+ }
+
+ if (index !== -1) {
+ if (checkId) {
+ if (--(this._watchers[index].count) === 0) {
+ this._watchers.splice(index, 1);
+ }
+ } else {
+ this._watchers.splice(index, 1);
+ }
+ }
+
+ if (this._watchers.length === 0) {
+ log("No more watchers; clean up persona host iframe");
+ let detail = {
+ type: kCloseIdentityDialog
+ };
+ log('telling content to close the dialog');
+ // tell content to close the dialog
+ sendChromeEvent(detail);
+ }
+ },
+
+ communicate: function(aRpOptions, aContentOptions, aMessageCallback) {
+ let rpID = aRpOptions.id;
+ let rpMM = aRpOptions.mm;
+ if (rpMM) {
+ this._addWatcher(rpID, rpMM);
+ }
+
+ log("RP options:", aRpOptions, "\n content options:", aContentOptions);
+
+ // This content variable is injected into the scope of
+ // kIdentityShimFile, where it is used to access the BrowserID object
+ // and its internal API.
+ let mm = null;
+ let uuid = getRandomId();
+ let self = this;
+
+ function removeMessageListeners() {
+ if (mm) {
+ mm.removeMessageListener(kIdentityDelegateFinished, identityDelegateFinished);
+ mm.removeMessageListener(kIdentityControllerDoMethod, aMessageCallback);
+ }
+ }
+
+ function identityDelegateFinished() {
+ removeMessageListeners();
+
+ let detail = {
+ type: kDoneIdentityDialog,
+ showUI: aContentOptions.showUI || false,
+ id: kDoneIdentityDialog + "-" + uuid,
+ requestId: aRpOptions.id
+ };
+ log('received delegate finished; telling content to close the dialog');
+ sendChromeEvent(detail);
+ self._removeWatchers(rpID, rpMM);
+ }
+
+ SystemAppProxy.addEventListener("mozContentEvent", function getAssertion(evt) {
+ let msg = evt.detail;
+ if (!msg.id.match(uuid)) {
+ return;
+ }
+
+ switch (msg.id) {
+ case kOpenIdentityDialog + '-' + uuid:
+ if (msg.type === 'cancel') {
+ // The user closed the dialog. Clean up and call cancel.
+ SystemAppProxy.removeEventListener("mozContentEvent", getAssertion);
+ removeMessageListeners();
+ aMessageCallback({json: {method: "cancel"}});
+ } else {
+ // The window has opened. Inject the identity shim file containing
+ // the callbacks in the content script. This could be either the
+ // visible popup that the user interacts with, or it could be an
+ // invisible frame.
+ let frame = evt.detail.frame;
+ let frameLoader = frame.QueryInterface(Ci.nsIFrameLoaderOwner).frameLoader;
+ mm = frameLoader.messageManager;
+ try {
+ mm.loadFrameScript(kIdentityShimFile, true, true);
+ log("Loaded shim", kIdentityShimFile);
+ } catch (e) {
+ log("Error loading", kIdentityShimFile, "as a frame script:", e);
+ }
+
+ // There are two messages that the delegate can send back: a "do
+ // method" event, and a "finished" event. We pass the do-method
+ // events straight to the caller for interpretation and handling.
+ // If we receive a "finished" event, then the delegate is done, so
+ // we shut down the pipe and clean up.
+ mm.addMessageListener(kIdentityControllerDoMethod, aMessageCallback);
+ mm.addMessageListener(kIdentityDelegateFinished, identityDelegateFinished);
+
+ mm.sendAsyncMessage(aContentOptions.message, aRpOptions);
+ }
+ break;
+
+ case kDoneIdentityDialog + '-' + uuid:
+ // Received our assertion. The message manager callbacks will handle
+ // communicating back to the IDService. All we have to do is remove
+ // this listener.
+ SystemAppProxy.removeEventListener("mozContentEvent", getAssertion);
+ break;
+
+ default:
+ log("ERROR - Unexpected message: id=" + msg.id + ", type=" + msg.type + ", errorMsg=" + msg.errorMsg);
+ break;
+ }
+
+ });
+
+ // Tell content to open the identity iframe or trusty popup. The parameter
+ // showUI signals whether user interaction is needed. If it is, content will
+ // open a dialog; if not, a hidden iframe. In each case, BrowserID is
+ // available in the context.
+ let detail = {
+ type: kOpenIdentityDialog,
+ showUI: aContentOptions.showUI || false,
+ id: kOpenIdentityDialog + "-" + uuid,
+ requestId: aRpOptions.id
+ };
+
+ sendChromeEvent(detail);
+ }
+
+};
+
+/*
+ * The controller sits between the IdentityService used by DOMIdentity
+ * and a content process launches an (invisible) iframe or (visible)
+ * trusty UI. Using an injected js script (identity.js), the
+ * controller enables the content window to access the persona identity
+ * storage in the system cookie jar and send events back via the
+ * controller into IdentityService and DOM, and ultimately up to the
+ * Relying Party, which is open in a different window context.
+ */
+this.SignInToWebsiteController = {
+
+ /*
+ * Initialize the controller. To use a different content communication pipe,
+ * such as when mocking it in tests, pass aOptions.pipe.
+ */
+ init: function SignInToWebsiteController_init(aOptions) {
+ aOptions = aOptions || {};
+ this.pipe = aOptions.pipe || new Pipe();
+ Services.obs.addObserver(this, "identity-controller-watch", false);
+ Services.obs.addObserver(this, "identity-controller-request", false);
+ Services.obs.addObserver(this, "identity-controller-logout", false);
+ },
+
+ uninit: function SignInToWebsiteController_uninit() {
+ Services.obs.removeObserver(this, "identity-controller-watch");
+ Services.obs.removeObserver(this, "identity-controller-request");
+ Services.obs.removeObserver(this, "identity-controller-logout");
+ },
+
+ observe: function SignInToWebsiteController_observe(aSubject, aTopic, aData) {
+ log("observe: received", aTopic, "with", aData, "for", aSubject);
+ let options = null;
+ if (aSubject) {
+ options = aSubject.wrappedJSObject;
+ }
+ switch (aTopic) {
+ case "identity-controller-watch":
+ this.doWatch(options);
+ break;
+ case "identity-controller-request":
+ this.doRequest(options);
+ break;
+ case "identity-controller-logout":
+ this.doLogout(options);
+ break;
+ default:
+ Logger.reportError("SignInToWebsiteController", "Unknown observer notification:", aTopic);
+ break;
+ }
+ },
+
+ /*
+ * options: method required - name of method to invoke
+ * assertion optional
+ */
+ _makeDoMethodCallback: function SignInToWebsiteController__makeDoMethodCallback(aRpId) {
+ return function SignInToWebsiteController_methodCallback(aOptions) {
+ let message = aOptions.json;
+ if (typeof message === 'string') {
+ message = JSON.parse(message);
+ }
+
+ switch (message.method) {
+ case "ready":
+ IdentityService.doReady(aRpId);
+ break;
+
+ case "login":
+ if (message._internalParams) {
+ IdentityService.doLogin(aRpId, message.assertion, message._internalParams);
+ } else {
+ IdentityService.doLogin(aRpId, message.assertion);
+ }
+ break;
+
+ case "logout":
+ IdentityService.doLogout(aRpId);
+ break;
+
+ case "cancel":
+ IdentityService.doCancel(aRpId);
+ break;
+
+ default:
+ log("WARNING: wonky method call:", message.method);
+ break;
+ }
+ };
+ },
+
+ doWatch: function SignInToWebsiteController_doWatch(aRpOptions) {
+ // dom prevents watch from being called twice
+ let contentOptions = {
+ message: kIdentityDelegateWatch,
+ showUI: false
+ };
+ this.pipe.communicate(aRpOptions, contentOptions,
+ this._makeDoMethodCallback(aRpOptions.id));
+ },
+
+ /**
+ * The website is requesting login so the user must choose an identity to use.
+ */
+ doRequest: function SignInToWebsiteController_doRequest(aRpOptions) {
+ log("doRequest", aRpOptions);
+ let contentOptions = {
+ message: kIdentityDelegateRequest,
+ showUI: true
+ };
+ this.pipe.communicate(aRpOptions, contentOptions,
+ this._makeDoMethodCallback(aRpOptions.id));
+ },
+
+ /*
+ *
+ */
+ doLogout: function SignInToWebsiteController_doLogout(aRpOptions) {
+ log("doLogout", aRpOptions);
+ let contentOptions = {
+ message: kIdentityDelegateLogout,
+ showUI: false
+ };
+ this.pipe.communicate(aRpOptions, contentOptions,
+ this._makeDoMethodCallback(aRpOptions.id));
+ }
+
+};
diff --git a/b2g/components/SimulatorScreen.js b/b2g/components/SimulatorScreen.js
new file mode 100644
index 0000000000..18d8f5cc46
--- /dev/null
+++ b/b2g/components/SimulatorScreen.js
@@ -0,0 +1,117 @@
+/* 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/. */
+
+var Ci = Components.interfaces;
+var Cu = Components.utils;
+
+Cu.import('resource://gre/modules/XPCOMUtils.jsm');
+Cu.import('resource://gre/modules/Services.jsm');
+Cu.import('resource://gre/modules/DOMRequestHelper.jsm');
+
+XPCOMUtils.defineLazyModuleGetter(this, 'GlobalSimulatorScreen',
+ 'resource://gre/modules/GlobalSimulatorScreen.jsm');
+
+var DEBUG_PREFIX = 'SimulatorScreen.js - ';
+function debug() {
+ //dump(DEBUG_PREFIX + Array.slice(arguments) + '\n');
+}
+
+function fireOrientationEvent(window) {
+ let e = new window.Event('mozorientationchange');
+ window.screen.dispatchEvent(e);
+}
+
+function hookScreen(window) {
+ let nodePrincipal = window.document.nodePrincipal;
+ let origin = nodePrincipal.origin;
+ if (nodePrincipal.appStatus == nodePrincipal.APP_STATUS_NOT_INSTALLED) {
+ // Only inject screen mock for apps
+ return;
+ }
+
+ let screen = window.wrappedJSObject.screen;
+
+ screen.mozLockOrientation = function (orientation) {
+ debug('mozLockOrientation:', orientation, 'from', origin);
+
+ // Normalize and do some checks against orientation input
+ if (typeof(orientation) == 'string') {
+ orientation = [orientation];
+ }
+
+ function isInvalidOrientationString(str) {
+ return typeof(str) != 'string' ||
+ !str.match(/^default$|^(portrait|landscape)(-(primary|secondary))?$/);
+ }
+ if (!Array.isArray(orientation) ||
+ orientation.some(isInvalidOrientationString)) {
+ Cu.reportError('Invalid orientation "' + orientation + '"');
+ return false;
+ }
+
+ GlobalSimulatorScreen.lock(orientation);
+
+ return true;
+ };
+
+ screen.mozUnlockOrientation = function() {
+ debug('mozOrientationUnlock from', origin);
+ GlobalSimulatorScreen.unlock();
+ return true;
+ };
+
+ Object.defineProperty(screen, 'width', {
+ get: () => GlobalSimulatorScreen.width
+ });
+ Object.defineProperty(screen, 'height', {
+ get: () => GlobalSimulatorScreen.height
+ });
+ Object.defineProperty(screen, 'mozOrientation', {
+ get: () => GlobalSimulatorScreen.mozOrientation
+ });
+}
+
+function SimulatorScreen() {}
+SimulatorScreen.prototype = {
+ classID: Components.ID('{c83c02c0-5d43-4e3e-987f-9173b313e880}'),
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver,
+ Ci.nsISupportsWeakReference]),
+ _windows: new Map(),
+
+ observe: function (subject, topic, data) {
+ let windows = this._windows;
+ switch (topic) {
+ case 'profile-after-change':
+ Services.obs.addObserver(this, 'document-element-inserted', false);
+ Services.obs.addObserver(this, 'simulator-orientation-change', false);
+ Services.obs.addObserver(this, 'inner-window-destroyed', false);
+ break;
+
+ case 'document-element-inserted':
+ let window = subject.defaultView;
+ if (!window) {
+ return;
+ }
+
+ hookScreen(window);
+
+ var id = window.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDOMWindowUtils)
+ .currentInnerWindowID;
+ windows.set(id, window);
+ break;
+
+ case 'inner-window-destroyed':
+ var id = subject.QueryInterface(Ci.nsISupportsPRUint64).data;
+ windows.delete(id);
+ break;
+
+ case 'simulator-orientation-change':
+ windows.forEach(fireOrientationEvent);
+ break;
+ }
+ }
+};
+
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory([SimulatorScreen]);
diff --git a/b2g/components/SmsProtocolHandler.js b/b2g/components/SmsProtocolHandler.js
new file mode 100644
index 0000000000..c54018fcf3
--- /dev/null
+++ b/b2g/components/SmsProtocolHandler.js
@@ -0,0 +1,74 @@
+/* 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/. */
+
+/**
+ * SmsProtocolHandle.js
+ *
+ * This file implements the URLs for SMS
+ * https://www.rfc-editor.org/rfc/rfc5724.txt
+ */
+
+"use strict";
+
+
+const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
+
+Cu.import('resource://gre/modules/XPCOMUtils.jsm');
+Cu.import("resource:///modules/TelURIParser.jsm");
+Cu.import('resource://gre/modules/ActivityChannel.jsm');
+
+function SmsProtocolHandler() {
+}
+
+SmsProtocolHandler.prototype = {
+
+ scheme: "sms",
+ defaultPort: -1,
+ protocolFlags: Ci.nsIProtocolHandler.URI_NORELATIVE |
+ Ci.nsIProtocolHandler.URI_NOAUTH |
+ Ci.nsIProtocolHandler.URI_LOADABLE_BY_ANYONE |
+ Ci.nsIProtocolHandler.URI_DOES_NOT_RETURN_DATA,
+ allowPort: () => false,
+
+ newURI: function Proto_newURI(aSpec, aOriginCharset) {
+ let uri = Cc["@mozilla.org/network/simple-uri;1"].createInstance(Ci.nsIURI);
+ uri.spec = aSpec;
+ return uri;
+ },
+
+ newChannel2: function Proto_newChannel2(aURI, aLoadInfo) {
+ let number = TelURIParser.parseURI('sms', aURI.spec);
+ let body = "";
+ let query = aURI.spec.split("?")[1];
+
+ if (query) {
+ let params = query.split("&");
+ params.forEach(function(aParam) {
+ let [name, value] = aParam.split("=");
+ if (name === "body") {
+ body = decodeURIComponent(value);
+ }
+ })
+ }
+
+ if (number || body) {
+ return new ActivityChannel(aURI, aLoadInfo,
+ "sms-handler",
+ { number: number || "",
+ type: "websms/sms",
+ body: body });
+ }
+
+ throw Components.results.NS_ERROR_ILLEGAL_VALUE;
+ },
+
+ newChannel: function Proto_newChannel(aURI) {
+ return this.newChannel2(aURI, null);
+ },
+
+ classID: Components.ID("{81ca20cb-0dad-4e32-8566-979c8998bd73}"),
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIProtocolHandler])
+};
+
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory([SmsProtocolHandler]);
diff --git a/b2g/components/SystemAppProxy.jsm b/b2g/components/SystemAppProxy.jsm
new file mode 100644
index 0000000000..b3d5843fc6
--- /dev/null
+++ b/b2g/components/SystemAppProxy.jsm
@@ -0,0 +1,377 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- /
+/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
+/* 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 { classes: Cc, interfaces: Ci, utils: Cu } = Components;
+
+Cu.import('resource://gre/modules/XPCOMUtils.jsm');
+Cu.import('resource://gre/modules/Services.jsm');
+
+this.EXPORTED_SYMBOLS = ['SystemAppProxy'];
+
+const kMainSystemAppId = 'main';
+
+var SystemAppProxy = {
+ _frameInfoMap: new Map(),
+ _pendingLoadedEvents: [],
+ _pendingReadyEvents: [],
+ _pendingListeners: [],
+
+ // To call when a main system app iframe is created
+ // Only used for main system app.
+ registerFrame: function systemApp_registerFrame(frame) {
+ this.registerFrameWithId(kMainSystemAppId, frame);
+ },
+
+ // To call when a new system(-remote) app iframe is created with ID
+ registerFrameWithId: function systemApp_registerFrameWithId(frameId,
+ frame) {
+ // - Frame ID of main system app is predefined as 'main'.
+ // - Frame ID of system-remote app is defined themselves.
+ //
+ // frameInfo = {
+ // isReady: ...,
+ // isLoaded: ...,
+ // frame: ...
+ // }
+
+ let frameInfo = { frameId: frameId,
+ isReady: false,
+ isLoaded: false,
+ frame: frame };
+
+ this._frameInfoMap.set(frameId, frameInfo);
+
+ // Register all DOM event listeners added before we got a ref to
+ // this system app iframe.
+ this._pendingListeners
+ .forEach(args => {
+ if (args[0] === frameInfo.frameId) {
+ this.addEventListenerWithId.apply(this, args);
+ }
+ });
+ // Removed registered event listeners.
+ this._pendingListeners =
+ this._pendingListeners
+ .filter(args => { return args[0] != frameInfo.frameId; });
+ },
+
+ unregisterFrameWithId: function systemApp_unregisterFrameWithId(frameId) {
+ this._frameInfoMap.delete(frameId);
+ // remove all pending event listener to the deleted system(-remote) app
+ this._pendingListeners = this._pendingListeners.filter(
+ args => { return args[0] != frameId; });
+ this._pendingReadyEvents = this._pendingReadyEvents.filter(
+ ([evtFrameId]) => { return evtFrameId != frameId });
+ this._pendingLoadedEvents = this._pendingLoadedEvents.filter(
+ ([evtFrameId]) => { return evtFrameId != frameId });
+ },
+
+ // Get the main system app frame
+ _getMainSystemAppInfo: function systemApp_getMainSystemAppInfo() {
+ return this._frameInfoMap.get(kMainSystemAppId);
+ },
+
+ // Get the main system app frame
+ // Only used for the main system app.
+ getFrame: function systemApp_getFrame() {
+ return this.getFrameWithId(kMainSystemAppId);
+ },
+
+ // Get the frame of the specific system app
+ getFrameWithId: function systemApp_getFrameWithId(frameId) {
+ let frameInfo = this._frameInfoMap.get(frameId);
+
+ if (!frameInfo) {
+ throw new Error('no frame ID is ' + frameId);
+ }
+ if (!frameInfo.frame) {
+ throw new Error('no content window');
+ }
+ return frameInfo.frame;
+ },
+
+ // To call when the load event of the main system app document is triggered.
+ // i.e. everything that is not lazily loaded are run and done.
+ // Only used for the main system app.
+ setIsLoaded: function systemApp_setIsLoaded() {
+ this.setIsLoadedWithId(kMainSystemAppId);
+ },
+
+ // To call when the load event of the specific system app document is
+ // triggered. i.e. everything that is not lazily loaded are run and done.
+ setIsLoadedWithId: function systemApp_setIsLoadedWithId(frameId) {
+ let frameInfo = this._frameInfoMap.get(frameId);
+ if (!frameInfo) {
+ throw new Error('no frame ID is ' + frameId);
+ }
+
+ if (frameInfo.isLoaded) {
+ if (frameInfo.frameId === kMainSystemAppId) {
+ Cu.reportError('SystemApp has already been declared as being loaded.');
+ }
+ else {
+ Cu.reportError('SystemRemoteApp (ID: ' + frameInfo.frameId + ') ' +
+ 'has already been declared as being loaded.');
+ }
+ }
+
+ frameInfo.isLoaded = true;
+
+ // Dispatch all events being queued while the system app was still loading
+ this._pendingLoadedEvents
+ .forEach(([evtFrameId, evtType, evtDetails]) => {
+ if (evtFrameId === frameInfo.frameId) {
+ this.sendCustomEventWithId(evtFrameId, evtType, evtDetails, true);
+ }
+ });
+ // Remove sent events.
+ this._pendingLoadedEvents =
+ this._pendingLoadedEvents
+ .filter(([evtFrameId]) => { return evtFrameId != frameInfo.frameId });
+ },
+
+ // To call when the main system app is ready to receive events
+ // i.e. when system-message-listener-ready mozContentEvent is sent.
+ // Only used for the main system app.
+ setIsReady: function systemApp_setIsReady() {
+ this.setIsReadyWithId(kMainSystemAppId);
+ },
+
+ // To call when the specific system(-remote) app is ready to receive events
+ // i.e. when system-message-listener-ready mozContentEvent is sent.
+ setIsReadyWithId: function systemApp_setIsReadyWithId(frameId) {
+ let frameInfo = this._frameInfoMap.get(frameId);
+ if (!frameInfo) {
+ throw new Error('no frame ID is ' + frameId);
+ }
+
+ if (!frameInfo.isLoaded) {
+ Cu.reportError('SystemApp.setIsLoaded() should be called before setIsReady().');
+ }
+
+ if (frameInfo.isReady) {
+ Cu.reportError('SystemApp has already been declared as being ready.');
+ }
+
+ frameInfo.isReady = true;
+
+ // Dispatch all events being queued while the system app was still not ready
+ this._pendingReadyEvents
+ .forEach(([evtFrameId, evtType, evtDetails]) => {
+ if (evtFrameId === frameInfo.frameId) {
+ this.sendCustomEventWithId(evtFrameId, evtType, evtDetails);
+ }
+ });
+
+ // Remove sent events.
+ this._pendingReadyEvents =
+ this._pendingReadyEvents
+ .filter(([evtFrameId]) => { return evtFrameId != frameInfo.frameId });
+ },
+
+ /*
+ * Common way to send an event to the main system app.
+ * Only used for the main system app.
+ *
+ * // In gecko code:
+ * SystemAppProxy.sendCustomEvent('foo', { data: 'bar' });
+ * // In system app:
+ * window.addEventListener('foo', function (event) {
+ * event.details == 'bar'
+ * });
+ *
+ * @param type The custom event type.
+ * @param details The event details.
+ * @param noPending Set to true to emit this event even before the system
+ * app is ready.
+ * Event is always pending if the app is not loaded yet.
+ * @param target The element who dispatch this event.
+ *
+ * @returns event? Dispatched event, or null if the event is pending.
+ */
+ _sendCustomEvent: function systemApp_sendCustomEvent(type,
+ details,
+ noPending,
+ target) {
+ let args = Array.prototype.slice.call(arguments);
+ return this.sendCustomEventWithId
+ .apply(this, [kMainSystemAppId].concat(args));
+ },
+
+ /*
+ * Common way to send an event to the specific system app.
+ *
+ * // In gecko code (send custom event from main system app):
+ * SystemAppProxy.sendCustomEventWithId('main', 'foo', { data: 'bar' });
+ * // In system app:
+ * window.addEventListener('foo', function (event) {
+ * event.details == 'bar'
+ * });
+ *
+ * @param frameId Specify the system(-remote) app who dispatch this event.
+ * @param type The custom event type.
+ * @param details The event details.
+ * @param noPending Set to true to emit this event even before the system
+ * app is ready.
+ * Event is always pending if the app is not loaded yet.
+ * @param target The element who dispatch this event.
+ *
+ * @returns event? Dispatched event, or null if the event is pending.
+ */
+ sendCustomEventWithId: function systemApp_sendCustomEventWithId(frameId,
+ type,
+ details,
+ noPending,
+ target) {
+ let frameInfo = this._frameInfoMap.get(frameId);
+ let content = (frameInfo && frameInfo.frame) ?
+ frameInfo.frame.contentWindow : null;
+ // If the system app isn't loaded yet,
+ // queue events until someone calls setIsLoaded
+ if (!content || !(frameInfo && frameInfo.isLoaded)) {
+ if (noPending) {
+ this._pendingLoadedEvents.push([frameId, type, details]);
+ } else {
+ this._pendingReadyEvents.push([frameId, type, details]);
+ }
+ return null;
+ }
+
+ // If the system app isn't ready yet,
+ // queue events until someone calls setIsReady
+ if (!(frameInfo && frameInfo.isReady) && !noPending) {
+ this._pendingReadyEvents.push([frameId, type, details]);
+ return null;
+ }
+
+ let event = content.document.createEvent('CustomEvent');
+
+ let payload;
+ // If the root object already has __exposedProps__,
+ // we consider the caller already wrapped (correctly) the object.
+ if ('__exposedProps__' in details) {
+ payload = details;
+ } else {
+ payload = details ? Cu.cloneInto(details, content) : {};
+ }
+
+ if ((target || content) === frameInfo.frame.contentWindow) {
+ dump('XXX FIXME : Dispatch a ' + type + ': ' + details.type + '\n');
+ }
+
+ event.initCustomEvent(type, true, false, payload);
+ (target || content).dispatchEvent(event);
+
+ return event;
+ },
+
+ // Now deprecated, use sendCustomEvent with a custom event name
+ dispatchEvent: function systemApp_dispatchEvent(details, target) {
+ return this._sendCustomEvent('mozChromeEvent', details, false, target);
+ },
+
+ dispatchKeyboardEvent: function systemApp_dispatchKeyboardEvent(type, details) {
+ try {
+ let frameInfo = this._getMainSystemAppInfo();
+ let content = (frameInfo && frameInfo.frame) ? frameInfo.frame.contentWindow
+ : null;
+ if (!content) {
+ throw new Error('no content window');
+ }
+ // If we don't already have a TextInputProcessor, create one now
+ if (!this.TIP) {
+ this.TIP = Cc['@mozilla.org/text-input-processor;1']
+ .createInstance(Ci.nsITextInputProcessor);
+ if (!this.TIP) {
+ throw new Error('failed to create textInputProcessor');
+ }
+ }
+
+ if (!this.TIP.beginInputTransactionForTests(content)) {
+ this.TIP = null;
+ throw new Error('beginInputTransaction failed');
+ }
+
+ let e = new content.KeyboardEvent('', { key: details.key, });
+
+ if (type === 'keydown') {
+ this.TIP.keydown(e);
+ }
+ else if (type === 'keyup') {
+ this.TIP.keyup(e);
+ }
+ else {
+ throw new Error('unexpected event type: ' + type);
+ }
+ }
+ catch(e) {
+ dump('dispatchKeyboardEvent: ' + e + '\n');
+ }
+ },
+
+ // Listen for dom events on the main system app
+ addEventListener: function systemApp_addEventListener() {
+ let args = Array.prototype.slice.call(arguments);
+ this.addEventListenerWithId.apply(this, [kMainSystemAppId].concat(args));
+ },
+
+ // Listen for dom events on the specific system app
+ addEventListenerWithId: function systemApp_addEventListenerWithId(frameId,
+ ...args) {
+ let frameInfo = this._frameInfoMap.get(frameId);
+
+ if (!frameInfo) {
+ this._pendingListeners.push(arguments);
+ return false;
+ }
+
+ let content = frameInfo.frame.contentWindow;
+ content.addEventListener.apply(content, args);
+ return true;
+ },
+
+ // remove the event listener from the main system app
+ removeEventListener: function systemApp_removeEventListener(name, listener) {
+ this.removeEventListenerWithId.apply(this, [kMainSystemAppId, name, listener]);
+ },
+
+ // remove the event listener from the specific system app
+ removeEventListenerWithId: function systemApp_removeEventListenerWithId(frameId,
+ name,
+ listener) {
+ let frameInfo = this._frameInfoMap.get(frameId);
+
+ if (frameInfo) {
+ let content = frameInfo.frame.contentWindow;
+ content.removeEventListener.apply(content, [name, listener]);
+ }
+ else {
+ this._pendingListeners = this._pendingListeners.filter(
+ args => {
+ return args[0] != frameId || args[1] != name || args[2] != listener;
+ });
+ }
+ },
+
+ // Get all frame in system app
+ getFrames: function systemApp_getFrames(frameId) {
+ let frameList = [];
+
+ for (let frameId of this._frameInfoMap.keys()) {
+ let frameInfo = this._frameInfoMap.get(frameId);
+ let systemAppFrame = frameInfo.frame;
+ let subFrames = systemAppFrame.contentDocument.querySelectorAll('iframe');
+ frameList.push(systemAppFrame);
+ for (let i = 0; i < subFrames.length; ++i) {
+ frameList.push(subFrames[i]);
+ }
+ }
+ return frameList;
+ }
+};
+this.SystemAppProxy = SystemAppProxy;
diff --git a/b2g/components/SystemMessageInternal.js b/b2g/components/SystemMessageInternal.js
new file mode 100644
index 0000000000..287496a50e
--- /dev/null
+++ b/b2g/components/SystemMessageInternal.js
@@ -0,0 +1,64 @@
+/* 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;
+const Cr = Components.results;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/SystemAppProxy.jsm");
+
+function debug(aMsg) {
+ dump("-- SystemMessageInternal " + Date.now() + " : " + aMsg + "\n");
+}
+
+// Implementation of the component used by internal users.
+
+function SystemMessageInternal() {
+}
+
+SystemMessageInternal.prototype = {
+
+ sendMessage: function(aType, aMessage, aPageURI, aManifestURI, aExtra) {
+ debug(`sendMessage ${aType} ${aMessage} ${aPageURI} ${aExtra}`);
+ SystemAppProxy._sendCustomEvent("mozSystemMessage", {
+ action: "send",
+ type: aType,
+ message: aMessage,
+ pageURI: aPageURI,
+ extra: aExtra
+ });
+ return Promise.resolve();
+ },
+
+ broadcastMessage: function(aType, aMessage, aExtra) {
+ debug(`broadcastMessage ${aType} ${aMessage} ${aExtra}`);
+ SystemAppProxy._sendCustomEvent("mozSystemMessage", {
+ action: "broadcast",
+ type: aType,
+ message: aMessage,
+ extra: aExtra
+ });
+ return Promise.resolve();
+ },
+
+ registerPage: function(aType, aPageURI, aManifestURI) {
+ SystemAppProxy._sendCustomEvent("mozSystemMessage", {
+ action: "register",
+ type: aType,
+ pageURI: aPageURI
+ });
+ debug(`registerPage ${aType} ${aPageURI} ${aManifestURI}`);
+ },
+
+ classID: Components.ID("{70589ca5-91ac-4b9e-b839-d6a88167d714}"),
+
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsISystemMessagesInternal])
+}
+
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory([SystemMessageInternal]);
diff --git a/b2g/components/TelProtocolHandler.js b/b2g/components/TelProtocolHandler.js
new file mode 100644
index 0000000000..021cbcd61b
--- /dev/null
+++ b/b2g/components/TelProtocolHandler.js
@@ -0,0 +1,60 @@
+/* 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/. */
+
+/**
+ * TelProtocolHandle.js
+ *
+ * This file implements the URLs for Telephone Calls
+ * https://www.ietf.org/rfc/rfc2806.txt
+ */
+
+"use strict";
+
+const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
+
+Cu.import('resource://gre/modules/XPCOMUtils.jsm');
+Cu.import("resource:///modules/TelURIParser.jsm");
+Cu.import('resource://gre/modules/ActivityChannel.jsm');
+
+function TelProtocolHandler() {
+}
+
+TelProtocolHandler.prototype = {
+
+ scheme: "tel",
+ defaultPort: -1,
+ protocolFlags: Ci.nsIProtocolHandler.URI_NORELATIVE |
+ Ci.nsIProtocolHandler.URI_NOAUTH |
+ Ci.nsIProtocolHandler.URI_LOADABLE_BY_ANYONE |
+ Ci.nsIProtocolHandler.URI_DOES_NOT_RETURN_DATA,
+ allowPort: () => false,
+
+ newURI: function Proto_newURI(aSpec, aOriginCharset) {
+ let uri = Cc["@mozilla.org/network/simple-uri;1"].createInstance(Ci.nsIURI);
+ uri.spec = aSpec;
+ return uri;
+ },
+
+ newChannel2: function Proto_newChannel(aURI, aLoadInfo) {
+ let number = TelURIParser.parseURI('tel', aURI.spec);
+
+ if (number) {
+ return new ActivityChannel(aURI, aLoadInfo,
+ "dial-handler",
+ { number: number,
+ type: "webtelephony/number" });
+ }
+
+ throw Components.results.NS_ERROR_ILLEGAL_VALUE;
+ },
+
+ newChannel: function Proto_newChannel(aURI) {
+ return this.newChannel2(aURI, null);
+ },
+
+ classID: Components.ID("{782775dd-7351-45ea-aff1-0ffa872cfdd2}"),
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIProtocolHandler])
+};
+
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory([TelProtocolHandler]);
diff --git a/b2g/components/TelURIParser.jsm b/b2g/components/TelURIParser.jsm
new file mode 100644
index 0000000000..46b0bb8fd3
--- /dev/null
+++ b/b2g/components/TelURIParser.jsm
@@ -0,0 +1,120 @@
+/* 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";
+
+this.EXPORTED_SYMBOLS = ["TelURIParser"];
+
+/**
+ * Singleton providing functionality for parsing tel: and sms: URIs
+ */
+this.TelURIParser = {
+ parseURI: function(scheme, uri) {
+ // https://www.ietf.org/rfc/rfc2806.txt
+ let subscriber = decodeURIComponent(uri.slice((scheme + ':').length));
+
+ if (!subscriber.length) {
+ return null;
+ }
+
+ let number = '';
+ let pos = 0;
+ let len = subscriber.length;
+
+ // visual-separator
+ let visualSeparator = [ ' ', '-', '.', '(', ')' ];
+ let digits = [ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' ];
+ let dtmfDigits = [ '*', '#', 'A', 'B', 'C', 'D' ];
+ let pauseCharacter = [ 'p', 'w' ];
+
+ // global-phone-number
+ if (subscriber[pos] == '+') {
+ number += '+';
+ for (++pos; pos < len; ++pos) {
+ if (visualSeparator.indexOf(subscriber[pos]) != -1) {
+ number += subscriber[pos];
+ } else if (digits.indexOf(subscriber[pos]) != -1) {
+ number += subscriber[pos];
+ } else {
+ break;
+ }
+ }
+ }
+ // local-phone-number
+ else {
+ for (; pos < len; ++pos) {
+ if (visualSeparator.indexOf(subscriber[pos]) != -1) {
+ number += subscriber[pos];
+ } else if (digits.indexOf(subscriber[pos]) != -1) {
+ number += subscriber[pos];
+ } else if (dtmfDigits.indexOf(subscriber[pos]) != -1) {
+ number += subscriber[pos];
+ } else if (pauseCharacter.indexOf(subscriber[pos]) != -1) {
+ number += subscriber[pos];
+ } else {
+ break;
+ }
+ }
+
+ // this means error
+ if (!number.length) {
+ return null;
+ }
+
+ // isdn-subaddress
+ if (subscriber.substring(pos, pos + 6) == ';isub=') {
+ let subaddress = '';
+
+ for (pos += 6; pos < len; ++pos) {
+ if (visualSeparator.indexOf(subscriber[pos]) != -1) {
+ subaddress += subscriber[pos];
+ } else if (digits.indexOf(subscriber[pos]) != -1) {
+ subaddress += subscriber[pos];
+ } else {
+ break;
+ }
+ }
+
+ // FIXME: ignore subaddress - Bug 795242
+ }
+
+ // post-dial
+ if (subscriber.substring(pos, pos + 7) == ';postd=') {
+ let subaddress = '';
+
+ for (pos += 7; pos < len; ++pos) {
+ if (visualSeparator.indexOf(subscriber[pos]) != -1) {
+ subaddress += subscriber[pos];
+ } else if (digits.indexOf(subscriber[pos]) != -1) {
+ subaddress += subscriber[pos];
+ } else if (dtmfDigits.indexOf(subscriber[pos]) != -1) {
+ subaddress += subscriber[pos];
+ } else if (pauseCharacter.indexOf(subscriber[pos]) != -1) {
+ subaddress += subscriber[pos];
+ } else {
+ break;
+ }
+ }
+
+ // FIXME: ignore subaddress - Bug 795242
+ }
+
+ // area-specific
+ if (subscriber.substring(pos, pos + 15) == ';phone-context=') {
+ pos += 15;
+
+ // global-network-prefix | local-network-prefix | private-prefi
+ number = subscriber.substring(pos, subscriber.length) + number;
+ }
+ }
+
+ // Ignore MWI and USSD codes. See 794034.
+ if (number.match(/[#\*]/) && !number.match(/^[#\*]\d+$/)) {
+ return null;
+ }
+
+ return number || null;
+ }
+};
+
diff --git a/b2g/components/UpdatePrompt.js b/b2g/components/UpdatePrompt.js
new file mode 100644
index 0000000000..1df07204c6
--- /dev/null
+++ b/b2g/components/UpdatePrompt.js
@@ -0,0 +1,783 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
+ * vim: sw=2 ts=8 et :
+ */
+/* 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/. */
+
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+const Cu = Components.utils;
+const Cr = Components.results;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/AppConstants.jsm");
+
+const VERBOSE = 1;
+var log =
+ VERBOSE ?
+ function log_dump(msg) { dump("UpdatePrompt: "+ msg +"\n"); } :
+ function log_noop(msg) { };
+
+const PREF_APPLY_PROMPT_TIMEOUT = "b2g.update.apply-prompt-timeout";
+const PREF_APPLY_IDLE_TIMEOUT = "b2g.update.apply-idle-timeout";
+const PREF_DOWNLOAD_WATCHDOG_TIMEOUT = "b2g.update.download-watchdog-timeout";
+const PREF_DOWNLOAD_WATCHDOG_MAX_RETRIES = "b2g.update.download-watchdog-max-retries";
+
+const NETWORK_ERROR_OFFLINE = 111;
+const HTTP_ERROR_OFFSET = 1000;
+
+const STATE_DOWNLOADING = 'downloading';
+
+XPCOMUtils.defineLazyServiceGetter(Services, "aus",
+ "@mozilla.org/updates/update-service;1",
+ "nsIApplicationUpdateService");
+
+XPCOMUtils.defineLazyServiceGetter(Services, "um",
+ "@mozilla.org/updates/update-manager;1",
+ "nsIUpdateManager");
+
+XPCOMUtils.defineLazyServiceGetter(Services, "idle",
+ "@mozilla.org/widget/idleservice;1",
+ "nsIIdleService");
+
+XPCOMUtils.defineLazyServiceGetter(Services, "settings",
+ "@mozilla.org/settingsService;1",
+ "nsISettingsService");
+
+XPCOMUtils.defineLazyServiceGetter(Services, 'env',
+ '@mozilla.org/process/environment;1',
+ 'nsIEnvironment');
+
+function useSettings() {
+ // When we're running in the real phone, then we can use settings.
+ // But when we're running as part of xpcshell, there is no settings database
+ // and trying to use settings in this scenario causes lots of weird
+ // assertions at shutdown time.
+ if (typeof useSettings.result === "undefined") {
+ useSettings.result = !Services.env.get("XPCSHELL_TEST_PROFILE_DIR");
+ }
+ return useSettings.result;
+}
+
+XPCOMUtils.defineLazyModuleGetter(this, "SystemAppProxy",
+ "resource://gre/modules/SystemAppProxy.jsm");
+
+function UpdateCheckListener(updatePrompt) {
+ this._updatePrompt = updatePrompt;
+}
+
+UpdateCheckListener.prototype = {
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIUpdateCheckListener]),
+
+ _updatePrompt: null,
+
+ onCheckComplete: function UCL_onCheckComplete(request, updates, updateCount) {
+ if (Services.um.activeUpdate) {
+ // We're actively downloading an update, that's the update the user should
+ // see, even if a newer update is available.
+ this._updatePrompt.setUpdateStatus("active-update");
+ this._updatePrompt.showUpdateAvailable(Services.um.activeUpdate);
+ return;
+ }
+
+ if (updateCount == 0) {
+ this._updatePrompt.setUpdateStatus("no-updates");
+
+ if (this._updatePrompt._systemUpdateListener) {
+ this._updatePrompt._systemUpdateListener.onError("no-updates");
+ }
+
+ return;
+ }
+
+ let update = Services.aus.selectUpdate(updates, updateCount);
+ if (!update) {
+ this._updatePrompt.setUpdateStatus("already-latest-version");
+
+ if (this._updatePrompt._systemUpdateListener) {
+ this._updatePrompt._systemUpdateListener.onError("already-latest-version");
+ }
+
+ return;
+ }
+
+ this._updatePrompt.setUpdateStatus("check-complete");
+ this._updatePrompt.showUpdateAvailable(update);
+ },
+
+ onError: function UCL_onError(request, update) {
+ // nsIUpdate uses a signed integer for errorCode while any platform errors
+ // require all 32 bits.
+ let errorCode = update.errorCode >>> 0;
+ let isNSError = (errorCode >>> 31) == 1;
+ let errorMsg = "check-error-";
+
+ if (errorCode == NETWORK_ERROR_OFFLINE) {
+ errorMsg = "retry-when-online";
+ this._updatePrompt.setUpdateStatus(errorMsg);
+ } else if (isNSError) {
+ errorMsg = "check-error-" + errorCode;
+ this._updatePrompt.setUpdateStatus(errorMsg);
+ } else if (errorCode > HTTP_ERROR_OFFSET) {
+ let httpErrorCode = errorCode - HTTP_ERROR_OFFSET;
+ errorMsg = "check-error-http-" + httpErrorCode;
+ this._updatePrompt.setUpdateStatus(errorMsg);
+ }
+
+ if (this._updatePrompt._systemUpdateListener) {
+ this._updatePrompt._systemUpdateListener.onError(errorMsg);
+ }
+
+ Services.aus.QueryInterface(Ci.nsIUpdateCheckListener);
+ Services.aus.onError(request, update);
+ }
+};
+
+function UpdatePrompt() {
+ this.wrappedJSObject = this;
+ this._updateCheckListener = new UpdateCheckListener(this);
+}
+
+UpdatePrompt.prototype = {
+ classID: Components.ID("{88b3eb21-d072-4e3b-886d-f89d8c49fe59}"),
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIUpdatePrompt,
+ Ci.nsIUpdateCheckListener,
+ Ci.nsIRequestObserver,
+ Ci.nsIProgressEventSink,
+ Ci.nsIObserver,
+ Ci.nsISystemUpdateProvider]),
+ _xpcom_factory: XPCOMUtils.generateSingletonFactory(UpdatePrompt),
+
+ _update: null,
+ _applyPromptTimer: null,
+ _waitingForIdle: false,
+ _updateCheckListner: null,
+ _systemUpdateListener: null,
+ _availableParameters: {
+ "deviceinfo.last_updated": null,
+ "gecko.updateStatus": null,
+ "app.update.channel": null,
+ "app.update.interval": null,
+ "app.update.url": null,
+ },
+ _pendingUpdateAvailablePackageInfo: null,
+ _isPendingUpdateReady: false,
+ _updateErrorQueue: [ ],
+ _receivedUpdatePromptReady: false,
+
+ // nsISystemUpdateProvider
+ checkForUpdate: function() {
+ this.forceUpdateCheck();
+ },
+
+ startDownload: function() {
+ this.downloadUpdate(this._update);
+ },
+
+ stopDownload: function() {
+ this.handleDownloadCancel();
+ },
+
+ applyUpdate: function() {
+ this.handleApplyPromptResult({result: "restart"});
+ },
+
+ setParameter: function(aName, aValue) {
+ if (!this._availableParameters.hasOwnProperty(aName)) {
+ return false;
+ }
+
+ this._availableParameters[aName] = aValue;
+
+ switch (aName) {
+ case "app.update.channel":
+ case "app.update.url":
+ Services.prefs.setCharPref(aName, aValue);
+ break;
+ case "app.update.interval":
+ Services.prefs.setIntPref(aName, parseInt(aValue, 10));
+ break;
+ }
+
+ return true;
+ },
+
+ getParameter: function(aName) {
+ if (!this._availableParameters.hasOwnProperty(aName)) {
+ return null;
+ }
+
+ return this._availableParameters[aName];
+ },
+
+ setListener: function(aListener) {
+ this._systemUpdateListener = aListener;
+
+ // If an update is available or ready, trigger the event right away at this point.
+ if (this._pendingUpdateAvailablePackageInfo) {
+ this._systemUpdateListener.onUpdateAvailable(this._pendingUpdateAvailablePackageInfo.type,
+ this._pendingUpdateAvailablePackageInfo.version,
+ this._pendingUpdateAvailablePackageInfo.description,
+ this._pendingUpdateAvailablePackageInfo.buildDate,
+ this._pendingUpdateAvailablePackageInfo.size);
+ // Set null when the listener is attached.
+ this._pendingUpdateAvailablePackageInfo = null;
+ }
+
+ if (this._isPendingUpdateReady) {
+ this._systemUpdateListener.onUpdateReady();
+ this._isPendingUpdateReady = false;
+ }
+ },
+
+ unsetListener: function(aListener) {
+ this._systemUpdateListener = null;
+ },
+
+ get applyPromptTimeout() {
+ return Services.prefs.getIntPref(PREF_APPLY_PROMPT_TIMEOUT);
+ },
+
+ get applyIdleTimeout() {
+ return Services.prefs.getIntPref(PREF_APPLY_IDLE_TIMEOUT);
+ },
+
+ handleContentStart: function UP_handleContentStart() {
+ SystemAppProxy.addEventListener("mozContentEvent", this);
+ },
+
+ // nsIUpdatePrompt
+
+ // FIXME/bug 737601: we should have users opt-in to downloading
+ // updates when on a billed pipe. Initially, opt-in for 3g, but
+ // that doesn't cover all cases.
+ checkForUpdates: function UP_checkForUpdates() { },
+
+ showUpdateAvailable: function UP_showUpdateAvailable(aUpdate) {
+ let packageInfo = {};
+ packageInfo.version = aUpdate.displayVersion;
+ packageInfo.description = aUpdate.statusText;
+ packageInfo.buildDate = aUpdate.buildID;
+
+ let patch = aUpdate.selectedPatch;
+ if (!patch && aUpdate.patchCount > 0) {
+ // For now we just check the first patch to get size information if a
+ // patch hasn't been selected yet.
+ patch = aUpdate.getPatchAt(0);
+ }
+
+ if (patch) {
+ packageInfo.size = patch.size;
+ packageInfo.type = patch.type;
+ } else {
+ log("Warning: no patches available in update");
+ }
+
+ this._pendingUpdateAvailablePackageInfo = packageInfo;
+
+ if (this._systemUpdateListener) {
+ this._systemUpdateListener.onUpdateAvailable(packageInfo.type,
+ packageInfo.version,
+ packageInfo.description,
+ packageInfo.buildDate,
+ packageInfo.size);
+ // Set null since the event is fired.
+ this._pendingUpdateAvailablePackageInfo = null;
+ }
+
+ if (!this.sendUpdateEvent("update-available", aUpdate)) {
+
+ log("Unable to prompt for available update, forcing download");
+ this.downloadUpdate(aUpdate);
+ }
+ },
+
+ showUpdateDownloaded: function UP_showUpdateDownloaded(aUpdate, aBackground) {
+ if (this._systemUpdateListener) {
+ this._systemUpdateListener.onUpdateReady();
+ } else {
+ this._isPendingUpdateReady = true;
+ }
+
+ // The update has been downloaded and staged. We send the update-downloaded
+ // event right away. After the user has been idle for a while, we send the
+ // update-prompt-restart event, increasing the chances that we can apply the
+ // update quietly without user intervention.
+ this.sendUpdateEvent("update-downloaded", aUpdate);
+
+ if (Services.idle.idleTime >= this.applyIdleTimeout) {
+ this.showApplyPrompt(aUpdate);
+ return;
+ }
+
+ let applyIdleTimeoutSeconds = this.applyIdleTimeout / 1000;
+ // We haven't been idle long enough, so register an observer
+ log("Update is ready to apply, registering idle timeout of " +
+ applyIdleTimeoutSeconds + " seconds before prompting.");
+
+ this._update = aUpdate;
+ this.waitForIdle();
+ },
+
+ storeUpdateError: function UP_storeUpdateError(aUpdate) {
+ log("Storing update error for later use");
+ this._updateErrorQueue.push(aUpdate);
+ },
+
+ sendStoredUpdateError: function UP_sendStoredUpdateError() {
+ log("Sending stored update error");
+ this._updateErrorQueue.forEach(aUpdate => {
+ this.sendUpdateEvent("update-error", aUpdate);
+ });
+ this._updateErrorQueue = [ ];
+ },
+
+ showUpdateError: function UP_showUpdateError(aUpdate) {
+ log("Update error, state: " + aUpdate.state + ", errorCode: " +
+ aUpdate.errorCode);
+ if (this._systemUpdateListener) {
+ this._systemUpdateListener.onError("update-error: " + aUpdate.errorCode + " " + aUpdate.statusText);
+ }
+
+ if (!this._receivedUpdatePromptReady) {
+ this.storeUpdateError(aUpdate);
+ } else {
+ this.sendUpdateEvent("update-error", aUpdate);
+ }
+
+ this.setUpdateStatus(aUpdate.statusText);
+ },
+
+ showUpdateHistory: function UP_showUpdateHistory(aParent) { },
+ showUpdateInstalled: function UP_showUpdateInstalled() {
+ this.setParameter("deviceinfo.last_updated", Date.now());
+
+ if (useSettings()) {
+ let lock = Services.settings.createLock();
+ lock.set("deviceinfo.last_updated", Date.now(), null, null);
+ }
+ },
+
+ // Custom functions
+
+ waitForIdle: function UP_waitForIdle() {
+ if (this._waitingForIdle) {
+ return;
+ }
+
+ this._waitingForIdle = true;
+ Services.idle.addIdleObserver(this, this.applyIdleTimeout / 1000);
+ Services.obs.addObserver(this, "quit-application", false);
+ },
+
+ setUpdateStatus: function UP_setUpdateStatus(aStatus) {
+ this.setParameter("gecko.updateStatus", aStatus);
+
+ if (useSettings()) {
+ log("Setting gecko.updateStatus: " + aStatus);
+
+ let lock = Services.settings.createLock();
+ lock.set("gecko.updateStatus", aStatus, null);
+ }
+ },
+
+ showApplyPrompt: function UP_showApplyPrompt(aUpdate) {
+ // Notify update package is ready to apply
+ if (this._systemUpdateListener) {
+ this._systemUpdateListener.onUpdateReady();
+ } else {
+ // Set the flag to true and fire the onUpdateReady event when the listener is attached.
+ this._isPendingUpdateReady = true;
+ }
+
+ if (!this.sendUpdateEvent("update-prompt-apply", aUpdate)) {
+ log("Unable to prompt, forcing restart");
+ this.restartProcess();
+ return;
+ }
+
+ if (AppConstants.MOZ_B2G_RIL) {
+ let window = Services.wm.getMostRecentWindow("navigator:browser");
+ let pinReq = window.navigator.mozIccManager.getCardLock("pin");
+ pinReq.onsuccess = function(e) {
+ if (e.target.result.enabled) {
+ // The SIM is pin locked. Don't use a fallback timer. This means that
+ // the user has to press Install to apply the update. If we use the
+ // timer, and the timer reboots the phone, then the phone will be
+ // unusable until the SIM is unlocked.
+ log("SIM is pin locked. Not starting fallback timer.");
+ } else {
+ // This means that no pin lock is enabled, so we go ahead and start
+ // the fallback timer.
+ this._applyPromptTimer = this.createTimer(this.applyPromptTimeout);
+ }
+ }.bind(this);
+ pinReq.onerror = function(e) {
+ this._applyPromptTimer = this.createTimer(this.applyPromptTimeout);
+ }.bind(this);
+ } else {
+ // Schedule a fallback timeout in case the UI is unable to respond or show
+ // a prompt for some reason.
+ this._applyPromptTimer = this.createTimer(this.applyPromptTimeout);
+ }
+ },
+
+ _copyProperties: ["appVersion", "buildID", "detailsURL", "displayVersion",
+ "errorCode", "isOSUpdate", "platformVersion",
+ "previousAppVersion", "state", "statusText"],
+
+ sendUpdateEvent: function UP_sendUpdateEvent(aType, aUpdate) {
+ let detail = {};
+ for (let property of this._copyProperties) {
+ detail[property] = aUpdate[property];
+ }
+
+ let patch = aUpdate.selectedPatch;
+ if (!patch && aUpdate.patchCount > 0) {
+ // For now we just check the first patch to get size information if a
+ // patch hasn't been selected yet.
+ patch = aUpdate.getPatchAt(0);
+ }
+
+ if (patch) {
+ detail.size = patch.size;
+ detail.updateType = patch.type;
+ } else {
+ log("Warning: no patches available in update");
+ }
+
+ this._update = aUpdate;
+ return this.sendChromeEvent(aType, detail);
+ },
+
+ sendChromeEvent: function UP_sendChromeEvent(aType, aDetail) {
+ let detail = aDetail || {};
+ detail.type = aType;
+
+ let sent = SystemAppProxy.dispatchEvent(detail);
+ if (!sent) {
+ log("Warning: Couldn't send update event " + aType +
+ ": no content browser. Will send again when content becomes available.");
+ return false;
+ }
+ return true;
+ },
+
+ handleAvailableResult: function UP_handleAvailableResult(aDetail) {
+ // If the user doesn't choose "download", the updater will implicitly call
+ // showUpdateAvailable again after a certain period of time
+ switch (aDetail.result) {
+ case "download":
+ this.downloadUpdate(this._update);
+ break;
+ }
+ },
+
+ handleApplyPromptResult: function UP_handleApplyPromptResult(aDetail) {
+ if (this._applyPromptTimer) {
+ this._applyPromptTimer.cancel();
+ this._applyPromptTimer = null;
+ }
+
+ switch (aDetail.result) {
+ // Battery not okay, do not wait for idle to re-prompt
+ case "low-battery":
+ break;
+ case "wait":
+ // Wait until the user is idle before prompting to apply the update
+ this.waitForIdle();
+ break;
+ case "restart":
+ this.finishUpdate();
+ this._update = null;
+ break;
+ }
+ },
+
+ downloadUpdate: function UP_downloadUpdate(aUpdate) {
+ if (!aUpdate) {
+ aUpdate = Services.um.activeUpdate;
+ if (!aUpdate) {
+ log("No active update found to download");
+ return;
+ }
+ }
+
+ let status = Services.aus.downloadUpdate(aUpdate, true);
+ if (status == STATE_DOWNLOADING) {
+ Services.aus.addDownloadListener(this);
+ return;
+ }
+
+ // If the update has already been downloaded and applied, then
+ // Services.aus.downloadUpdate will return immediately and not
+ // call showUpdateDownloaded, so we detect this.
+ if (aUpdate.state == "applied" && aUpdate.errorCode == 0) {
+ this.showUpdateDownloaded(aUpdate, true);
+ return;
+ }
+
+ log("Error downloading update " + aUpdate.name + ": " + aUpdate.errorCode);
+ let errorCode = aUpdate.errorCode >>> 0;
+ if (errorCode == Cr.NS_ERROR_FILE_TOO_BIG) {
+ aUpdate.statusText = "file-too-big";
+ }
+ this.showUpdateError(aUpdate);
+ },
+
+ handleDownloadCancel: function UP_handleDownloadCancel() {
+ log("Pausing download");
+ Services.aus.pauseDownload();
+ },
+
+ finishUpdate: function UP_finishUpdate() {
+ if (!this._update.isOSUpdate) {
+ // Standard gecko+gaia updates will just need to restart the process
+ this.restartProcess();
+ return;
+ }
+
+ try {
+ Services.aus.applyOsUpdate(this._update);
+ }
+ catch (e) {
+ this._update.errorCode = Cr.NS_ERROR_FAILURE;
+ this.showUpdateError(this._update);
+ }
+ },
+
+ restartProcess: function UP_restartProcess() {
+ log("Update downloaded, restarting to apply it");
+
+ let callbackAfterSet = function() {
+ if (AppConstants.platform !== "gonk") {
+ let appStartup = Cc["@mozilla.org/toolkit/app-startup;1"]
+ .getService(Ci.nsIAppStartup);
+ appStartup.quit(appStartup.eForceQuit | appStartup.eRestart);
+ } else {
+ // NB: on Gonk, we rely on the system process manager to restart us.
+ let pmService = Cc["@mozilla.org/power/powermanagerservice;1"]
+ .getService(Ci.nsIPowerManagerService);
+ pmService.restart();
+ }
+ }
+
+ if (useSettings()) {
+ // Save current os version in deviceinfo.previous_os
+ let lock = Services.settings.createLock({
+ handle: callbackAfterSet,
+ handleAbort: function(error) {
+ log("Abort callback when trying to set previous_os: " + error);
+ callbackAfterSet();
+ }
+ });
+ lock.get("deviceinfo.os", {
+ handle: function(name, value) {
+ log("Set previous_os to: " + value);
+ lock.set("deviceinfo.previous_os", value, null, null);
+ }
+ });
+ }
+ },
+
+ forceUpdateCheck: function UP_forceUpdateCheck() {
+ log("Forcing update check");
+
+ let checker = Cc["@mozilla.org/updates/update-checker;1"]
+ .createInstance(Ci.nsIUpdateChecker);
+ checker.checkForUpdates(this._updateCheckListener, true);
+ },
+
+ handleEvent: function UP_handleEvent(evt) {
+ if (evt.type !== "mozContentEvent") {
+ return;
+ }
+
+ let detail = evt.detail;
+ if (!detail) {
+ return;
+ }
+
+ switch (detail.type) {
+ case "force-update-check":
+ this.forceUpdateCheck();
+ break;
+ case "update-available-result":
+ this.handleAvailableResult(detail);
+ // If we started the apply prompt timer, this means that we're waiting
+ // for the user to press Later or Install Now. In this situation we
+ // don't want to clear this._update, becuase handleApplyPromptResult
+ // needs it.
+ if (this._applyPromptTimer == null && !this._waitingForIdle) {
+ this._update = null;
+ }
+ break;
+ case "update-download-cancel":
+ this.handleDownloadCancel();
+ break;
+ case "update-prompt-apply-result":
+ this.handleApplyPromptResult(detail);
+ break;
+ case "update-prompt-ready":
+ this._receivedUpdatePromptReady = true;
+ this.sendStoredUpdateError();
+ break;
+ }
+ },
+
+ // nsIObserver
+
+ observe: function UP_observe(aSubject, aTopic, aData) {
+ switch (aTopic) {
+ case "idle":
+ this._waitingForIdle = false;
+ this.showApplyPrompt(this._update);
+ // Fall through
+ case "quit-application":
+ Services.idle.removeIdleObserver(this, this.applyIdleTimeout / 1000);
+ Services.obs.removeObserver(this, "quit-application");
+ break;
+ }
+ },
+
+ // nsITimerCallback
+
+ notify: function UP_notify(aTimer) {
+ if (aTimer == this._applyPromptTimer) {
+ log("Timed out waiting for result, restarting");
+ this._applyPromptTimer = null;
+ this.finishUpdate();
+ this._update = null;
+ return;
+ }
+ if (aTimer == this._watchdogTimer) {
+ log("Download watchdog fired");
+ this._watchdogTimer = null;
+ this._autoRestartDownload = true;
+ Services.aus.pauseDownload();
+ return;
+ }
+ },
+
+ createTimer: function UP_createTimer(aTimeoutMs) {
+ let timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
+ timer.initWithCallback(this, aTimeoutMs, timer.TYPE_ONE_SHOT);
+ return timer;
+ },
+
+ // nsIRequestObserver
+
+ _startedSent: false,
+
+ _watchdogTimer: null,
+
+ _autoRestartDownload: false,
+ _autoRestartCount: 0,
+
+ startWatchdogTimer: function UP_startWatchdogTimer() {
+ let watchdogTimeout = 120000; // 120 seconds
+ try {
+ watchdogTimeout = Services.prefs.getIntPref(PREF_DOWNLOAD_WATCHDOG_TIMEOUT);
+ } catch (e) {
+ // This means that the preference doesn't exist. watchdogTimeout will
+ // retain its default assigned above.
+ }
+ if (watchdogTimeout <= 0) {
+ // 0 implies don't bother using the watchdog timer at all.
+ this._watchdogTimer = null;
+ return;
+ }
+ if (this._watchdogTimer) {
+ this._watchdogTimer.cancel();
+ } else {
+ this._watchdogTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
+ }
+ this._watchdogTimer.initWithCallback(this, watchdogTimeout,
+ Ci.nsITimer.TYPE_ONE_SHOT);
+ },
+
+ stopWatchdogTimer: function UP_stopWatchdogTimer() {
+ if (this._watchdogTimer) {
+ this._watchdogTimer.cancel();
+ this._watchdogTimer = null;
+ }
+ },
+
+ touchWatchdogTimer: function UP_touchWatchdogTimer() {
+ this.startWatchdogTimer();
+ },
+
+ onStartRequest: function UP_onStartRequest(aRequest, aContext) {
+ // Wait until onProgress to send the update-download-started event, in case
+ // this request turns out to fail for some reason
+ this._startedSent = false;
+ this.startWatchdogTimer();
+ },
+
+ onStopRequest: function UP_onStopRequest(aRequest, aContext, aStatusCode) {
+ this.stopWatchdogTimer();
+ Services.aus.removeDownloadListener(this);
+ let paused = !Components.isSuccessCode(aStatusCode);
+ if (!paused) {
+ // The download was successful, no need to restart
+ this._autoRestartDownload = false;
+ }
+ if (this._autoRestartDownload) {
+ this._autoRestartDownload = false;
+ let watchdogMaxRetries = Services.prefs.getIntPref(PREF_DOWNLOAD_WATCHDOG_MAX_RETRIES);
+ this._autoRestartCount++;
+ if (this._autoRestartCount > watchdogMaxRetries) {
+ log("Download - retry count exceeded - error");
+ // We exceeded the max retries. Treat the download like an error,
+ // which will give the user a chance to restart manually later.
+ this._autoRestartCount = 0;
+ if (Services.um.activeUpdate) {
+ this.showUpdateError(Services.um.activeUpdate);
+ }
+ return;
+ }
+ log("Download - restarting download - attempt " + this._autoRestartCount);
+ this.downloadUpdate(null);
+ return;
+ }
+ this._autoRestartCount = 0;
+ this.sendChromeEvent("update-download-stopped", {
+ paused: paused
+ });
+ },
+
+ // nsIProgressEventSink
+
+ onProgress: function UP_onProgress(aRequest, aContext, aProgress,
+ aProgressMax) {
+ if (this._systemUpdateListener) {
+ this._systemUpdateListener.onProgress(aProgress, aProgressMax);
+ }
+
+ if (aProgress == aProgressMax) {
+ // The update.mar validation done by onStopRequest may take
+ // a while before the onStopRequest callback is made, so stop
+ // the timer now.
+ this.stopWatchdogTimer();
+ } else {
+ this.touchWatchdogTimer();
+ }
+ if (!this._startedSent) {
+ this.sendChromeEvent("update-download-started", {
+ total: aProgressMax
+ });
+ this._startedSent = true;
+ }
+
+ this.sendChromeEvent("update-download-progress", {
+ progress: aProgress,
+ total: aProgressMax
+ });
+ },
+
+ onStatus: function UP_onStatus(aRequest, aUpdate, aStatus, aStatusArg) { }
+};
+
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory([UpdatePrompt]);
diff --git a/b2g/components/moz.build b/b2g/components/moz.build
new file mode 100644
index 0000000000..9290c5e2a0
--- /dev/null
+++ b/b2g/components/moz.build
@@ -0,0 +1,91 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+DIRS += ['test']
+
+EXTRA_COMPONENTS += [
+ 'AlertsService.js',
+ 'B2GAboutRedirector.js',
+ 'B2GAppMigrator.js',
+ 'B2GPresentationDevicePrompt.js',
+ 'BootstrapCommandLine.js',
+ 'ContentPermissionPrompt.js',
+ 'FilePicker.js',
+ 'FxAccountsUIGlue.js',
+ 'HelperAppDialog.js',
+ 'MailtoProtocolHandler.js',
+ 'OMAContentHandler.js',
+ 'PresentationRequestUIGlue.js',
+ 'ProcessGlobal.js',
+ 'SmsProtocolHandler.js',
+ 'SystemMessageInternal.js',
+ 'TelProtocolHandler.js',
+]
+
+if CONFIG['OS_TARGET'] != 'Android':
+ EXTRA_COMPONENTS += [
+ 'CommandLine.js',
+ 'OopCommandLine.js',
+ 'SimulatorScreen.js'
+ ]
+
+EXTRA_PP_COMPONENTS += [
+ 'B2GComponents.manifest',
+]
+
+if CONFIG['MOZ_B2G']:
+ EXTRA_COMPONENTS += [
+ 'DirectoryProvider.js',
+ 'RecoveryService.js',
+ ]
+
+if CONFIG['MOZ_UPDATER']:
+ EXTRA_COMPONENTS += [
+ 'UpdatePrompt.js',
+ ]
+
+EXTRA_JS_MODULES += [
+ 'AboutServiceWorkers.jsm',
+ 'ActivityChannel.jsm',
+ 'AlertsHelper.jsm',
+ 'Bootstraper.jsm',
+ 'ContentRequestHelper.jsm',
+ 'DebuggerActors.js',
+ 'ErrorPage.jsm',
+ 'Frames.jsm',
+ 'FxAccountsMgmtService.jsm',
+ 'LogCapture.jsm',
+ 'LogParser.jsm',
+ 'LogShake.jsm',
+ 'OrientationChangeHandler.jsm',
+ 'SafeMode.jsm',
+ 'Screenshot.jsm',
+ 'SignInToWebsite.jsm',
+ 'SystemAppProxy.jsm',
+ 'TelURIParser.jsm',
+]
+
+if CONFIG['MOZ_WIDGET_TOOLKIT'] != 'gonk':
+ EXTRA_JS_MODULES += [
+ 'GlobalSimulatorScreen.jsm'
+ ]
+
+XPIDL_SOURCES += [
+ 'nsIGaiaChrome.idl',
+ 'nsISystemMessagesInternal.idl'
+]
+
+XPIDL_MODULE = 'gaia_chrome'
+
+UNIFIED_SOURCES += [
+ 'GaiaChrome.cpp'
+]
+
+LOCAL_INCLUDES += [
+ '/chrome'
+]
+
+FINAL_LIBRARY = 'xul'
diff --git a/b2g/components/nsIGaiaChrome.idl b/b2g/components/nsIGaiaChrome.idl
new file mode 100644
index 0000000000..8e66d8456f
--- /dev/null
+++ b/b2g/components/nsIGaiaChrome.idl
@@ -0,0 +1,15 @@
+/* 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, uuid(92a18a98-ab5d-4d02-a024-bdbb3bc89ce1)]
+interface nsIGaiaChrome : nsISupports
+{
+ void register();
+};
+
+%{ C++
+#define GAIACHROME_CONTRACTID "@mozilla.org/b2g/gaia-chrome;1"
+%}
diff --git a/b2g/components/nsISystemMessagesInternal.idl b/b2g/components/nsISystemMessagesInternal.idl
new file mode 100644
index 0000000000..775ce0315d
--- /dev/null
+++ b/b2g/components/nsISystemMessagesInternal.idl
@@ -0,0 +1,51 @@
+/* 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"
+
+interface nsIURI;
+interface nsIDOMWindow;
+interface nsIMessageSender;
+
+// Implemented by the contract id @mozilla.org/system-message-internal;1
+
+[scriptable, uuid(59b6beda-f911-4d47-a296-8c81e6abcfb9)]
+interface nsISystemMessagesInternal : nsISupports
+{
+ /*
+ * Allow any internal user to send a message of a given type to a given page
+ * of an app. The message will be sent to all the registered pages of the app
+ * when |pageURI| is not specified.
+ * @param type The type of the message to be sent.
+ * @param message The message payload.
+ * @param pageURI The URI of the page that will be opened. Nullable.
+ * @param manifestURI The webapp's manifest URI.
+ * @param extra Extra opaque information that will be passed around in the observer
+ * notification to open the page.
+ * returns a Promise
+ */
+ nsISupports sendMessage(in DOMString type, in jsval message,
+ in nsIURI pageURI, in nsIURI manifestURI,
+ [optional] in jsval extra);
+
+ /*
+ * Allow any internal user to broadcast a message of a given type.
+ * The application that registers the message will be launched.
+ * @param type The type of the message to be sent.
+ * @param message The message payload.
+ * @param extra Extra opaque information that will be passed around in the observer
+ * notification to open the page.
+ * returns a Promise
+ */
+ nsISupports broadcastMessage(in DOMString type, in jsval message,
+ [optional] in jsval extra);
+
+ /*
+ * Registration of a page that wants to be notified of a message type.
+ * @param type The message type.
+ * @param pageURI The URI of the page that will be opened.
+ * @param manifestURI The webapp's manifest URI.
+ */
+ void registerPage(in DOMString type, in nsIURI pageURI, in nsIURI manifestURI);
+};
diff --git a/b2g/components/test/mochitest/SandboxPromptTest.html b/b2g/components/test/mochitest/SandboxPromptTest.html
new file mode 100644
index 0000000000..54f5fdd481
--- /dev/null
+++ b/b2g/components/test/mochitest/SandboxPromptTest.html
@@ -0,0 +1,57 @@
+<html>
+<body>
+<script>
+
+var actions = [
+ {
+ permissions: ["video-capture"],
+ action: function() {
+ // invoke video-capture permission prompt
+ navigator.mozGetUserMedia({video: true}, function () {}, function () {});
+ }
+ },
+ {
+ permissions: ["audio-capture", "video-capture"],
+ action: function() {
+ // invoke audio-capture + video-capture permission prompt
+ navigator.mozGetUserMedia({audio: true, video: true}, function () {}, function () {});
+ }
+ },
+ {
+ permissions: ["audio-capture"],
+ action: function() {
+ // invoke audio-capture permission prompt
+ navigator.mozGetUserMedia({audio: true}, function () {}, function () {});
+ }
+ },
+ {
+ permissions: ["geolocation"],
+ action: function() {
+ // invoke geolocation permission prompt
+ navigator.geolocation.getCurrentPosition(function (pos) {});
+ }
+ },
+ {
+ permissions: ["desktop-notification"],
+ action: function() {
+ // invoke desktop-notification prompt
+ Notification.requestPermission(function (perm) {});
+ }
+ },
+];
+
+// The requested permissions are specified in query string.
+var permissions = JSON.parse(decodeURIComponent(window.location.search.substring(1)));
+for (var i = 0; i < actions.length; i++) {
+ if(permissions.length === actions[i].permissions.length &&
+ permissions.every(function(permission) {
+ return actions[i].permissions.indexOf(permission) >= 0;
+ })) {
+ actions[i].action();
+ break;
+ }
+}
+
+</script>
+</body>
+</html>
diff --git a/b2g/components/test/mochitest/filepicker_path_handler_chrome.js b/b2g/components/test/mochitest/filepicker_path_handler_chrome.js
new file mode 100644
index 0000000000..a175746cb9
--- /dev/null
+++ b/b2g/components/test/mochitest/filepicker_path_handler_chrome.js
@@ -0,0 +1,31 @@
+/* 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';
+
+var Cc = Components.classes;
+var Ci = Components.interfaces;
+
+// use ppmm to handle file-picker message.
+var ppmm = Cc['@mozilla.org/parentprocessmessagemanager;1']
+ .getService(Ci.nsIMessageListenerManager);
+
+var pickResult = null;
+
+function processPickMessage(message) {
+ let sender = message.target.QueryInterface(Ci.nsIMessageSender);
+ // reply FilePicker's message
+ sender.sendAsyncMessage('file-picked', pickResult);
+ // notify caller
+ sendAsyncMessage('file-picked-posted', { type: 'file-picked-posted' });
+}
+
+function updatePickResult(result) {
+ pickResult = result;
+ sendAsyncMessage('pick-result-updated', { type: 'pick-result-updated' });
+}
+
+ppmm.addMessageListener('file-picker', processPickMessage);
+// use update-pick-result to change the expected pick result.
+addMessageListener('update-pick-result', updatePickResult);
diff --git a/b2g/components/test/mochitest/mochitest.ini b/b2g/components/test/mochitest/mochitest.ini
new file mode 100644
index 0000000000..97df32ea2b
--- /dev/null
+++ b/b2g/components/test/mochitest/mochitest.ini
@@ -0,0 +1,28 @@
+[DEFAULT]
+support-files =
+ permission_handler_chrome.js
+ SandboxPromptTest.html
+ filepicker_path_handler_chrome.js
+ screenshot_helper.js
+ systemapp_helper.js
+ presentation_prompt_handler_chrome.js
+ presentation_ui_glue_handler_chrome.js
+
+[test_filepicker_path.html]
+skip-if = toolkit != "gonk"
+[test_permission_deny.html]
+skip-if = toolkit != "gonk"
+[test_permission_gum_remember.html]
+skip-if = true # Bug 1019572 - frequent timeouts
+[test_sandbox_permission.html]
+skip-if = toolkit != "gonk"
+[test_screenshot.html]
+skip-if = toolkit != "gonk"
+[test_systemapp.html]
+skip-if = toolkit != "gonk"
+[test_presentation_device_prompt.html]
+skip-if = toolkit != "gonk"
+[test_permission_visibilitychange.html]
+skip-if = toolkit != "gonk"
+[test_presentation_request_ui_glue.html]
+skip-if = toolkit != "gonk"
diff --git a/b2g/components/test/mochitest/permission_handler_chrome.js b/b2g/components/test/mochitest/permission_handler_chrome.js
new file mode 100644
index 0000000000..9bf3e7819a
--- /dev/null
+++ b/b2g/components/test/mochitest/permission_handler_chrome.js
@@ -0,0 +1,36 @@
+/* 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";
+
+function debug(str) {
+ dump("CHROME PERMISSON HANDLER -- " + str + "\n");
+}
+
+var Cc = Components.classes;
+var Ci = Components.interfaces;
+var Cu = Components.utils;
+
+const { Services } = Cu.import("resource://gre/modules/Services.jsm");
+const { SystemAppProxy } = Cu.import("resource://gre/modules/SystemAppProxy.jsm");
+
+var eventHandler = function(evt) {
+ if (!evt.detail || evt.detail.type !== "permission-prompt") {
+ return;
+ }
+
+ sendAsyncMessage("permission-request", evt.detail);
+};
+
+SystemAppProxy.addEventListener("mozChromeEvent", eventHandler);
+
+// need to remove ChromeEvent listener after test finished.
+addMessageListener("teardown", function() {
+ SystemAppProxy.removeEventListener("mozChromeEvent", eventHandler);
+});
+
+addMessageListener("permission-response", function(detail) {
+ SystemAppProxy._sendCustomEvent('mozContentEvent', detail);
+});
+
diff --git a/b2g/components/test/mochitest/presentation_prompt_handler_chrome.js b/b2g/components/test/mochitest/presentation_prompt_handler_chrome.js
new file mode 100644
index 0000000000..4407e58d28
--- /dev/null
+++ b/b2g/components/test/mochitest/presentation_prompt_handler_chrome.js
@@ -0,0 +1,94 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+'use strict';
+
+ function debug(str) {
+ dump('presentation_prompt_handler_chrome: ' + str + '\n');
+ }
+
+var { classes: Cc, interfaces: Ci, utils: Cu } = Components;
+const { XPCOMUtils } = Cu.import('resource://gre/modules/XPCOMUtils.jsm');
+const { SystemAppProxy } = Cu.import('resource://gre/modules/SystemAppProxy.jsm');
+
+const manager = Cc["@mozilla.org/presentation-device/manager;1"]
+ .getService(Ci.nsIPresentationDeviceManager);
+
+const prompt = Cc['@mozilla.org/presentation-device/prompt;1']
+ .getService(Ci.nsIPresentationDevicePrompt);
+
+function TestPresentationDevice(options) {
+ this.id = options.id;
+ this.name = options.name;
+ this.type = options.type;
+}
+
+TestPresentationDevice.prototype = {
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIPresentationDevice]),
+ establishSessionTransport: function() {
+ return null;
+ },
+};
+
+function TestPresentationRequest(options) {
+ this.origin = options.origin;
+ this.requestURL = options.requestURL;
+}
+
+TestPresentationRequest.prototype = {
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIPresentationDeviceRequest]),
+ select: function(device) {
+ let result = {
+ type: 'select',
+ device: {
+ id: device.id,
+ name: device.name,
+ type: device.type,
+ },
+ };
+ sendAsyncMessage('presentation-select-result', result);
+ },
+ cancel: function() {
+ let result = {
+ type: 'cancel',
+ };
+ sendAsyncMessage('presentation-select-result', result);
+ },
+};
+
+var testDevice = null;
+
+addMessageListener('setup', function(device_options) {
+ testDevice = new TestPresentationDevice(device_options);
+ manager.QueryInterface(Ci.nsIPresentationDeviceListener).addDevice(testDevice);
+ sendAsyncMessage('setup-complete');
+});
+
+var eventHandler = function(evt) {
+ if (!evt.detail || evt.detail.type !== 'presentation-select-device') {
+ return;
+ }
+
+ sendAsyncMessage('presentation-select-device', evt.detail);
+};
+
+SystemAppProxy.addEventListener('mozChromeEvent', eventHandler);
+
+// need to remove ChromeEvent listener after test finished.
+addMessageListener('teardown', function() {
+ if (testDevice) {
+ manager.removeDevice(testDevice);
+ }
+ SystemAppProxy.removeEventListener('mozChromeEvent', eventHandler);
+});
+
+addMessageListener('trigger-device-prompt', function(request_options) {
+ let request = new TestPresentationRequest(request_options);
+ prompt.promptDeviceSelection(request);
+});
+
+addMessageListener('presentation-select-response', function(detail) {
+ SystemAppProxy._sendCustomEvent('mozContentEvent', detail);
+});
+
diff --git a/b2g/components/test/mochitest/presentation_ui_glue_handler_chrome.js b/b2g/components/test/mochitest/presentation_ui_glue_handler_chrome.js
new file mode 100644
index 0000000000..fac16db6cb
--- /dev/null
+++ b/b2g/components/test/mochitest/presentation_ui_glue_handler_chrome.js
@@ -0,0 +1,32 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+'use strict';
+
+var { classes: Cc, interfaces: Ci, utils: Cu } = Components;
+const { XPCOMUtils } = Cu.import('resource://gre/modules/XPCOMUtils.jsm');
+const { SystemAppProxy } = Cu.import('resource://gre/modules/SystemAppProxy.jsm');
+
+const glue = Cc["@mozilla.org/presentation/requestuiglue;1"]
+ .createInstance(Ci.nsIPresentationRequestUIGlue);
+
+SystemAppProxy.addEventListener('mozPresentationChromeEvent', function(aEvent) {
+ if (!aEvent.detail || aEvent.detail.type !== 'presentation-launch-receiver') {
+ return;
+ }
+ sendAsyncMessage('presentation-launch-receiver', aEvent.detail);
+});
+
+addMessageListener('trigger-ui-glue', function(aData) {
+ var promise = glue.sendRequest(aData.url, aData.sessionId);
+ promise.then(function(aFrame) {
+ sendAsyncMessage('iframe-resolved', aFrame);
+ }).catch(function() {
+ sendAsyncMessage('iframe-rejected');
+ });
+});
+
+addMessageListener('trigger-presentation-content-event', function(aDetail) {
+ SystemAppProxy._sendCustomEvent('mozPresentationContentEvent', aDetail);
+});
diff --git a/b2g/components/test/mochitest/screenshot_helper.js b/b2g/components/test/mochitest/screenshot_helper.js
new file mode 100644
index 0000000000..0320a14c16
--- /dev/null
+++ b/b2g/components/test/mochitest/screenshot_helper.js
@@ -0,0 +1,40 @@
+var Cu = Components.utils;
+var Ci = Components.interfaces;
+
+Cu.importGlobalProperties(['File']);
+
+const { Services } = Cu.import("resource://gre/modules/Services.jsm");
+
+// Load a duplicated copy of the jsm to prevent messing with the currently running one
+var scope = {};
+Services.scriptloader.loadSubScript("resource://gre/modules/Screenshot.jsm", scope);
+const { Screenshot } = scope;
+
+var index = -1;
+function next() {
+ index++;
+ if (index >= steps.length) {
+ assert.ok(false, "Shouldn't get here!");
+ return;
+ }
+ try {
+ steps[index]();
+ } catch(ex) {
+ assert.ok(false, "Caught exception: " + ex);
+ }
+}
+
+var steps = [
+ function getScreenshot() {
+ let screenshot = Screenshot.get();
+ assert.ok(screenshot instanceof File,
+ "Screenshot.get() returns a File");
+ next();
+ },
+
+ function endOfTest() {
+ sendAsyncMessage("finish");
+ }
+];
+
+next();
diff --git a/b2g/components/test/mochitest/systemapp_helper.js b/b2g/components/test/mochitest/systemapp_helper.js
new file mode 100644
index 0000000000..768b221fef
--- /dev/null
+++ b/b2g/components/test/mochitest/systemapp_helper.js
@@ -0,0 +1,173 @@
+var Cu = Components.utils;
+
+const { Services } = Cu.import("resource://gre/modules/Services.jsm");
+
+// Load a duplicated copy of the jsm to prevent messing with the currently running one
+var scope = {};
+Services.scriptloader.loadSubScript("resource://gre/modules/SystemAppProxy.jsm", scope);
+const { SystemAppProxy } = scope;
+
+var frame;
+var customEventTarget;
+
+var index = -1;
+function next() {
+ index++;
+ if (index >= steps.length) {
+ assert.ok(false, "Shouldn't get here!");
+ return;
+ }
+ try {
+ steps[index]();
+ } catch(ex) {
+ assert.ok(false, "Caught exception: " + ex);
+ }
+}
+
+// Listen for events received by the system app document
+// to ensure that we receive all of them, in an expected order and time
+var isLoaded = false;
+var isReady = false;
+var n = 0;
+function listener(event) {
+ if (!isLoaded) {
+ assert.ok(false, "Received event before the iframe is loaded");
+ return;
+ }
+ n++;
+ if (n == 1) {
+ assert.equal(event.type, "mozChromeEvent");
+ assert.equal(event.detail.name, "first");
+ } else if (n == 2) {
+ assert.equal(event.type, "custom");
+ assert.equal(event.detail.name, "second");
+
+ next(); // call checkEventPendingBeforeLoad
+ } else if (n == 3) {
+ if (!isReady) {
+ assert.ok(false, "Received event before the iframe is loaded");
+ return;
+ }
+
+ assert.equal(event.type, "custom");
+ assert.equal(event.detail.name, "third");
+ } else if (n == 4) {
+ if (!isReady) {
+ assert.ok(false, "Received event before the iframe is loaded");
+ return;
+ }
+
+ assert.equal(event.type, "mozChromeEvent");
+ assert.equal(event.detail.name, "fourth");
+
+ next(); // call checkEventDispatching
+ } else if (n == 5) {
+ assert.equal(event.type, "custom");
+ assert.equal(event.detail.name, "fifth");
+ } else if (n === 6) {
+ assert.equal(event.type, "mozChromeEvent");
+ assert.equal(event.detail.name, "sixth");
+ } else if (n === 7) {
+ assert.equal(event.type, "custom");
+ assert.equal(event.detail.name, "seventh");
+ assert.equal(event.target, customEventTarget);
+
+ next(); // call checkEventListening();
+ } else {
+ assert.ok(false, "Unexpected event of type " + event.type);
+ }
+}
+
+
+var steps = [
+ function earlyEvents() {
+ // Immediately try to send events
+ SystemAppProxy._sendCustomEvent("mozChromeEvent", { name: "first" }, true);
+ SystemAppProxy._sendCustomEvent("custom", { name: "second" }, true);
+ next();
+ },
+
+ function createFrame() {
+ // Create a fake system app frame
+ let win = Services.wm.getMostRecentWindow("navigator:browser");
+ let doc = win.document;
+ frame = doc.createElement("iframe");
+ doc.documentElement.appendChild(frame);
+
+ customEventTarget = frame.contentDocument.body;
+
+ // Ensure that events are correctly sent to the frame.
+ // `listener` is going to call next()
+ frame.contentWindow.addEventListener("mozChromeEvent", listener);
+ frame.contentWindow.addEventListener("custom", listener);
+
+ // Ensure that listener being registered before the system app is ready
+ // are correctly removed from the pending list
+ function removedListener() {
+ assert.ok(false, "Listener isn't correctly removed from the pending list");
+ }
+ SystemAppProxy.addEventListener("mozChromeEvent", removedListener);
+ SystemAppProxy.removeEventListener("mozChromeEvent", removedListener);
+
+ // Register it to the JSM
+ SystemAppProxy.registerFrame(frame);
+ assert.ok(true, "Frame created and registered");
+
+ frame.contentWindow.addEventListener("load", function onload() {
+ frame.contentWindow.removeEventListener("load", onload);
+ assert.ok(true, "Frame document loaded");
+
+ // Declare that the iframe is now loaded.
+ // That should dispatch early events
+ isLoaded = true;
+ SystemAppProxy.setIsLoaded();
+ assert.ok(true, "Frame declared as loaded");
+
+ let gotFrame = SystemAppProxy.getFrame();
+ assert.equal(gotFrame, frame, "getFrame returns the frame we passed");
+
+ // Once pending events are received,
+ // we will run checkEventDispatching from `listener` function
+ });
+
+ frame.setAttribute("src", "data:text/html,system app");
+ },
+
+ function checkEventPendingBeforeLoad() {
+ // Frame is loaded but not ready,
+ // these events should queue before the System app is ready.
+ SystemAppProxy._sendCustomEvent("custom", { name: "third" });
+ SystemAppProxy.dispatchEvent({ name: "fourth" });
+
+ isReady = true;
+ SystemAppProxy.setIsReady();
+ // Once this 4th event is received, we will run checkEventDispatching
+ },
+
+ function checkEventDispatching() {
+ // Send events after the iframe is ready,
+ // they should be dispatched right away
+ SystemAppProxy._sendCustomEvent("custom", { name: "fifth" });
+ SystemAppProxy.dispatchEvent({ name: "sixth" });
+ SystemAppProxy._sendCustomEvent("custom", { name: "seventh" }, false, customEventTarget);
+ // Once this 7th event is received, we will run checkEventListening
+ },
+
+ function checkEventListening() {
+ SystemAppProxy.addEventListener("mozContentEvent", function onContentEvent(event) {
+ assert.equal(event.detail.name, "first-content", "received a system app event");
+ SystemAppProxy.removeEventListener("mozContentEvent", onContentEvent);
+
+ next();
+ });
+ let win = frame.contentWindow;
+ win.dispatchEvent(new win.CustomEvent("mozContentEvent", { detail: {name: "first-content"} }));
+ },
+
+ function endOfTest() {
+ frame.remove();
+ sendAsyncMessage("finish");
+ }
+];
+
+next();
diff --git a/b2g/components/test/mochitest/test_filepicker_path.html b/b2g/components/test/mochitest/test_filepicker_path.html
new file mode 100644
index 0000000000..92c00dc682
--- /dev/null
+++ b/b2g/components/test/mochitest/test_filepicker_path.html
@@ -0,0 +1,130 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=949944
+-->
+<head>
+<meta charset="utf-8">
+<title>Permission Prompt Test</title>
+<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+</head>
+<body onload="processTestCase()">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=949944"> [B2G][Helix][Browser][Wallpaper] use new File([blob], filename) to return a blob with filename when picking</a>
+<script type="application/javascript">
+
+'use strict';
+
+var testCases = [
+ // case 1: returns blob with name
+ { pickedResult: { success: true,
+ result: {
+ type: 'text/plain',
+ blob: new Blob(['1234567890'],
+ { type: 'text/plain' }),
+ name: 'test1.txt'
+ }
+ },
+ fileName: 'test1.txt' },
+ // case 2: returns blob without name
+ { pickedResult: { success: true,
+ result: {
+ type: 'text/plain',
+ blob: new Blob(['1234567890'],
+ { type: 'text/plain' })
+ }
+ },
+ fileName: 'blob.txt' },
+ // case 3: returns blob with full path name
+ { pickedResult: { success: true,
+ result: {
+ type: 'text/plain',
+ blob: new Blob(['1234567890'],
+ { type: 'text/plain' }),
+ name: '/full/path/test3.txt'
+ }
+ },
+ fileName: 'test3.txt' },
+ // case 4: returns blob relative path name
+ { pickedResult: { success: true,
+ result: {
+ type: 'text/plain',
+ blob: new Blob(['1234567890'],
+ { type: 'text/plain' }),
+ name: 'relative/path/test4.txt'
+ }
+ },
+ fileName: 'test4.txt' },
+ // case 5: returns file with name
+ { pickedResult: { success: true,
+ result: {
+ type: 'text/plain',
+ blob: new File(['1234567890'],
+ 'useless-name.txt',
+ { type: 'text/plain' }),
+ name: 'test5.txt'
+ }
+ },
+ fileName: 'test5.txt'},
+ // case 6: returns file without name. This case may fail because we
+ // need to make sure the File can be sent through
+ // sendAsyncMessage API.
+ { pickedResult: { success: true,
+ result: {
+ type: 'text/plain',
+ blob: new File(['1234567890'],
+ 'test6.txt',
+ { type: 'text/plain' })
+ }
+ },
+ fileName: 'test6.txt'}
+];
+
+var chromeJS = SimpleTest.getTestFileURL('filepicker_path_handler_chrome.js');
+var chromeScript = SpecialPowers.loadChromeScript(chromeJS);
+var activeTestCase;
+
+chromeScript.addMessageListener('pick-result-updated', handleMessage);
+chromeScript.addMessageListener('file-picked-posted', handleMessage);
+
+// handle messages returned from chromeScript
+function handleMessage(data) {
+ var fileInput = document.getElementById('fileInput');
+ switch (data.type) {
+ case 'pick-result-updated':
+ fileInput.click();
+ break;
+ case 'file-picked-posted':
+ is(fileInput.value, activeTestCase.fileName,
+ 'File should be able to send through message.');
+ processTestCase();
+ break;
+ }
+}
+
+function processTestCase() {
+ if (!testCases.length) {
+ SimpleTest.finish();
+ return;
+ }
+ activeTestCase = testCases.shift();
+ var expectedResult = activeTestCase.pickedResult;
+ if (navigator.userAgent.indexOf('Windows') > -1 &&
+ expectedResult.result.name) {
+ // If we run at a window box, we need to translate the path from '/' to '\\'
+ var name = expectedResult.result.name;
+ name = name.replace('/', '\\');
+ // If the name is an absolute path, we need to prepend drive letter.
+ if (name.startsWith('\\')) {
+ name = 'C:' + name;
+ }
+ // update the expected name.
+ expectedResult.result.name = name
+ }
+ chromeScript.sendAsyncMessage('update-pick-result', expectedResult);
+}
+
+</script>
+<input type="file" id="fileInput">
+</body>
+</html>
diff --git a/b2g/components/test/mochitest/test_permission_deny.html b/b2g/components/test/mochitest/test_permission_deny.html
new file mode 100644
index 0000000000..29c35bdecd
--- /dev/null
+++ b/b2g/components/test/mochitest/test_permission_deny.html
@@ -0,0 +1,83 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=981113
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Permission Deny Test</title>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=981113">Test Permission Deny</a>
+<script type="application/javascript;version=1.8">
+
+'use strict';
+
+SimpleTest.waitForExplicitFinish();
+
+const PROMPT_ACTION = SpecialPowers.Ci.nsIPermissionManager.PROMPT_ACTION;
+
+var gUrl = SimpleTest.getTestFileURL('permission_handler_chrome.js');
+var gScript = SpecialPowers.loadChromeScript(gUrl);
+var gTests = [
+ {
+ 'video': true,
+ },
+ {
+ 'audio': true,
+ 'video': true,
+ },
+ {
+ 'audio': true,
+ },
+];
+
+function runNext() {
+ if (gTests.length > 0) {
+ // Put the requested permission in query string
+ let requestedType = gTests.shift();
+ info('getUserMedia for ' + JSON.stringify(requestedType));
+ navigator.mozGetUserMedia(requestedType, function success() {
+ ok(false, 'unexpected success, permission request should be denied');
+ runNext();
+ }, function failure(err) {
+ is(err.name, 'SecurityError', 'expected permission denied');
+ runNext();
+ });
+ } else {
+ info('test finished, teardown');
+ gScript.sendAsyncMessage('teardown', '');
+ gScript.destroy();
+ SimpleTest.finish();
+ }
+}
+
+gScript.addMessageListener('permission-request', function(detail) {
+ let response = {
+ id: detail.id,
+ type: 'permission-deny',
+ remember: false,
+ };
+ gScript.sendAsyncMessage('permission-response', response);
+});
+
+// Need to change camera permission from ALLOW to PROMPT, otherwise
+// MediaManager will automatically allow video-only gUM request.
+SpecialPowers.pushPermissions([
+ {type: 'video-capture', allow: PROMPT_ACTION, context: document},
+ {type: 'audio-capture', allow: PROMPT_ACTION, context: document},
+ {type: 'camera', allow: PROMPT_ACTION, context: document},
+ ], function() {
+ SpecialPowers.pushPrefEnv({
+ 'set': [
+ ['media.navigator.permission.disabled', false],
+ ]
+ }, runNext);
+ }
+);
+</script>
+</pre>
+</body>
+</html>
diff --git a/b2g/components/test/mochitest/test_permission_gum_remember.html b/b2g/components/test/mochitest/test_permission_gum_remember.html
new file mode 100644
index 0000000000..1ebfea1cac
--- /dev/null
+++ b/b2g/components/test/mochitest/test_permission_gum_remember.html
@@ -0,0 +1,170 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=978660
+-->
+<head>
+ <meta charset="utf-8">
+ <title>gUM Remember Permission Test</title>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=978660">Test remembering gUM Permission</a>
+<script type="application/javascript;version=1.8">
+
+'use strict';
+
+SimpleTest.waitForExplicitFinish();
+
+const PROMPT_ACTION = SpecialPowers.Ci.nsIPermissionManager.PROMPT_ACTION;
+
+var gUrl = SimpleTest.getTestFileURL('permission_handler_chrome.js');
+var gScript = SpecialPowers.loadChromeScript(gUrl);
+gScript.addMessageListener('permission-request', function(detail) {
+ ok(false, 'unexpected mozChromeEvent for permission prompt');
+ let response = {
+ id: detail.id,
+ type: 'permission-deny',
+ remember: false,
+ };
+ gScript.sendAsyncMessage('permission-response', response);
+});
+
+var gTests = [
+ {
+ 'audio': true,
+ 'video': {facingMode: 'environment', required: ['facingMode']},
+ },
+ {
+ 'video': {facingMode: 'environment', required: ['facingMode']},
+ },
+ {
+ 'audio': true,
+ },
+];
+
+function testGranted() {
+ info('test remember permission granted');
+ return new Promise(function(resolve, reject) {
+ let steps = [].concat(gTests);
+ function nextStep() {
+ if (steps.length > 0) {
+ let requestedType = steps.shift();
+ info('getUserMedia for ' + JSON.stringify(requestedType));
+ navigator.mozGetUserMedia(requestedType, function success(stream) {
+ ok(true, 'expected gUM success');
+ stream.stop();
+ nextStep();
+ }, function failure(err) {
+ ok(false, 'unexpected gUM fail: ' + err);
+ nextStep();
+ });
+ } else {
+ resolve();
+ }
+ }
+
+ SpecialPowers.pushPermissions([
+ {type: 'video-capture', allow: true, context: document},
+ {type: 'audio-capture', allow: true, context: document},
+ ], nextStep);
+ });
+}
+
+function testDenied() {
+ info('test remember permission denied');
+ return new Promise(function(resolve, reject) {
+ let steps = [].concat(gTests);
+ function nextStep() {
+ if (steps.length > 0) {
+ let requestedType = steps.shift();
+ info('getUserMedia for ' + JSON.stringify(requestedType));
+ navigator.mozGetUserMedia(requestedType, function success(stream) {
+ ok(false, 'unexpected gUM success');
+ stream.stop();
+ nextStep();
+ }, function failure(err) {
+ ok(true, 'expected gUM fail: ' + err);
+ nextStep();
+ });
+ } else {
+ resolve();
+ }
+ }
+
+ SpecialPowers.pushPermissions([
+ {type: 'video-capture', allow: false, context: document},
+ {type: 'audio-capture', allow: false, context: document},
+ ], nextStep);
+ });
+}
+
+function testPartialDeniedAudio() {
+ info('test remember permission partial denied: audio');
+ return new Promise(function(resolve, reject) {
+ info('getUserMedia for video and audio');
+ function nextStep() {
+ navigator.mozGetUserMedia({video: {facingMode: 'environment', required: ['facingMode']},
+ audio: true}, function success(stream) {
+ ok(false, 'unexpected gUM success');
+ stream.stop();
+ resolve();
+ }, function failure(err) {
+ ok(true, 'expected gUM fail: ' + err);
+ resolve();
+ });
+ }
+
+ SpecialPowers.pushPermissions([
+ {type: 'video-capture', allow: true, context: document},
+ {type: 'audio-capture', allow: false, context: document},
+ ], nextStep);
+ });
+}
+
+function testPartialDeniedVideo() {
+ info('test remember permission partial denied: video');
+ return new Promise(function(resolve, reject) {
+ info('getUserMedia for video and audio');
+ function nextStep() {
+ navigator.mozGetUserMedia({video: {facingMode: 'environment', required: ['facingMode']},
+ audio: true}, function success(stream) {
+ ok(false, 'unexpected gUM success');
+ stream.stop();
+ resolve();
+ }, function failure(err) {
+ ok(true, 'expected gUM fail: ' + err);
+ resolve();
+ });
+ }
+
+ SpecialPowers.pushPermissions([
+ {type: 'video-capture', allow: false, context: document},
+ {type: 'audio-capture', allow: true, context: document},
+ ], nextStep);
+ });
+}
+
+function runTests() {
+ testGranted()
+ .then(testDenied)
+ .then(testPartialDeniedAudio)
+ .then(testPartialDeniedVideo)
+ .then(function() {
+ info('test finished, teardown');
+ gScript.sendAsyncMessage('teardown', '');
+ gScript.destroy();
+ SimpleTest.finish();
+ });
+}
+
+SpecialPowers.pushPrefEnv({
+ 'set': [
+ ['media.navigator.permission.disabled', false],
+ ]
+}, runTests);
+</script>
+</pre>
+</body>
+</html>
diff --git a/b2g/components/test/mochitest/test_permission_visibilitychange.html b/b2g/components/test/mochitest/test_permission_visibilitychange.html
new file mode 100644
index 0000000000..cd5694b427
--- /dev/null
+++ b/b2g/components/test/mochitest/test_permission_visibilitychange.html
@@ -0,0 +1,57 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=951997
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Permission Prompt Test</title>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1020179">Permission prompt visibilitychange test</a>
+<script type="application/javascript;version=1.8">
+
+"use strict";
+
+var gUrl = SimpleTest.getTestFileURL("permission_handler_chrome.js");
+var gScript = SpecialPowers.loadChromeScript(gUrl);
+
+function testDone() {
+ gScript.sendAsyncMessage("teardown", "");
+ gScript.destroy();
+ SimpleTest.finish();
+ alert("setVisible::true");
+}
+
+function runTest() {
+ navigator.geolocation.getCurrentPosition(
+ function (pos) {
+ ok(false, "unexpected success, permission request should be canceled");
+ testDone();
+ }, function (err) {
+ ok(true, "success, permission request is canceled");
+ testDone();
+ });
+}
+
+gScript.addMessageListener("permission-request", function (detail) {
+ info("got permission-request!!!!\n");
+ alert("setVisible::false");
+});
+
+// Add permissions to this app. We use ALLOW_ACTION here. The ContentPermissionPrompt
+// should prompt for permission, not allow it without prompt.
+SpecialPowers.pushPrefEnv({"set": [["media.navigator.permission.disabled", false]]},
+ function() {
+ SpecialPowers.addPermission("geolocation",
+ SpecialPowers.Ci.nsIPermissionManager.PROMPT_ACTION, document);
+ runTest();
+ });
+
+SimpleTest.waitForExplicitFinish();
+</script>
+</pre>
+</body>
+</html>
diff --git a/b2g/components/test/mochitest/test_presentation_device_prompt.html b/b2g/components/test/mochitest/test_presentation_device_prompt.html
new file mode 100644
index 0000000000..9feeca7952
--- /dev/null
+++ b/b2g/components/test/mochitest/test_presentation_device_prompt.html
@@ -0,0 +1,145 @@
+<!DOCTYPE HTML>
+<html>
+<!-- Any copyright is dedicated to the Public Domain.
+ - http://creativecommons.org/publicdomain/zero/1.0/ -->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Presentation Device Selection</title>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=">Test for Presentation Device Selection</a>
+<script type="application/javascript;version=1.8">
+
+'use strict';
+
+SimpleTest.waitForExplicitFinish();
+
+var contentEventHandler = null;
+
+var gUrl = SimpleTest.getTestFileURL('presentation_prompt_handler_chrome.js');
+var gScript = SpecialPowers.loadChromeScript(gUrl);
+
+function testSetup() {
+ info('setup for device selection');
+ return new Promise(function(resolve, reject) {
+ let device = {
+ id: 'test-id',
+ name: 'test-name',
+ type: 'test-type',
+ };
+ gScript.addMessageListener('setup-complete', function() {
+ resolve(device);
+ });
+ gScript.sendAsyncMessage('setup', device);
+ });
+}
+
+function testSelected(device) {
+ info('test device selected by user');
+ return new Promise(function(resolve, reject) {
+ let request = {
+ origin: 'test-origin',
+ requestURL: 'test-requestURL',
+ };
+
+ gScript.addMessageListener('presentation-select-device', function contentEventHandler(detail) {
+ gScript.removeMessageListener('presentation-select-device', contentEventHandler);
+ ok(true, 'receive user prompt for device selection');
+ is(detail.origin, request.origin, 'expected origin');
+ is(detail.requestURL, request.requestURL, 'expected requestURL');
+ let response = {
+ id: detail.id,
+ type: 'presentation-select-result',
+ deviceId: device.id,
+ };
+ gScript.sendAsyncMessage('presentation-select-response', response);
+
+ gScript.addMessageListener('presentation-select-result', function resultHandler(result) {
+ gScript.removeMessageListener('presentation-select-result', resultHandler);
+ is(result.type, 'select', 'expect device selected');
+ is(result.device.id, device.id, 'expected device id');
+ is(result.device.name, device.name, 'expected device name');
+ is(result.device.type, device.type, 'expected devcie type');
+ resolve();
+ });
+ });
+
+ gScript.sendAsyncMessage('trigger-device-prompt', request);
+ });
+}
+
+function testSelectedNotExisted() {
+ info('test selected device doesn\'t exist');
+ return new Promise(function(resolve, reject) {
+ gScript.addMessageListener('presentation-select-device', function contentEventHandler(detail) {
+ gScript.removeMessageListener('presentation-select-device', contentEventHandler);
+ ok(true, 'receive user prompt for device selection');
+ let response = {
+ id: detail.id,
+ type: 'presentation-select-deny',
+ deviceId: undefined, // simulate device Id that doesn't exist
+ };
+ gScript.sendAsyncMessage('presentation-select-response', response);
+
+ gScript.addMessageListener('presentation-select-result', function resultHandler(result) {
+ gScript.removeMessageListener('presentation-select-result', resultHandler);
+ is(result.type, 'cancel', 'expect user cancel');
+ resolve();
+ });
+ });
+
+ let request = {
+ origin: 'test-origin',
+ requestURL: 'test-requestURL',
+ };
+ gScript.sendAsyncMessage('trigger-device-prompt', request);
+ });
+}
+
+function testDenied() {
+ info('test denial of device selection');
+ return new Promise(function(resolve, reject) {
+ gScript.addMessageListener('presentation-select-device', function contentEventHandler(detail) {
+ gScript.removeMessageListener('presentation-select-device', contentEventHandler);
+ ok(true, 'receive user prompt for device selection');
+ let response = {
+ id: detail.id,
+ type: 'presentation-select-deny',
+ };
+ gScript.sendAsyncMessage('presentation-select-response', response);
+
+ gScript.addMessageListener('presentation-select-result', function resultHandler(result) {
+ gScript.removeMessageListener('presentation-select-result', resultHandler);
+ is(result.type, 'cancel', 'expect user cancel');
+ resolve();
+ });
+ });
+
+ let request = {
+ origin: 'test-origin',
+ requestURL: 'test-requestURL',
+ };
+ gScript.sendAsyncMessage('trigger-device-prompt', request);
+ });
+}
+
+function runTests() {
+ testSetup()
+ .then(testSelected)
+ .then(testSelectedNotExisted)
+ .then(testDenied)
+ .then(function() {
+ info('test finished, teardown');
+ gScript.sendAsyncMessage('teardown');
+ gScript.destroy();
+ SimpleTest.finish();
+ });
+}
+
+window.addEventListener('load', runTests);
+</script>
+</pre>
+</body>
+</html>
diff --git a/b2g/components/test/mochitest/test_presentation_request_ui_glue.html b/b2g/components/test/mochitest/test_presentation_request_ui_glue.html
new file mode 100644
index 0000000000..29ac37221b
--- /dev/null
+++ b/b2g/components/test/mochitest/test_presentation_request_ui_glue.html
@@ -0,0 +1,105 @@
+<!DOCTYPE HTML>
+<html>
+<!-- Any copyright is dedicated to the Public Domain.
+ - http://creativecommons.org/publicdomain/zero/1.0/ -->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Presentation UI Glue</title>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=">Test for Presentation UI Glue</a>
+<script type="application/javascript;version=1.8">
+
+'use strict';
+
+SimpleTest.waitForExplicitFinish();
+
+var gScript = SpecialPowers.loadChromeScript(SimpleTest.getTestFileURL('presentation_ui_glue_handler_chrome.js'));
+
+var obs = SpecialPowers.Cc["@mozilla.org/observer-service;1"]
+ .getService(SpecialPowers.Ci.nsIObserverService);
+
+var url = 'http://example.com';
+var sessionId = 'sessionId';
+
+function testLaunchReceiver() {
+ return new Promise(function(aResolve, aReject) {
+ gScript.addMessageListener('presentation-launch-receiver', function launchReceiverHandler(aDetail) {
+ gScript.removeMessageListener('presentation-launch-receiver', launchReceiverHandler);
+ ok(true, "A presentation-launch-receiver mozPresentationChromeEvent should be received.");
+ is(aDetail.url, url, "Url should be the same.");
+ is(aDetail.id, sessionId, "Session ID should be the same.");
+
+ aResolve();
+ });
+
+ gScript.sendAsyncMessage('trigger-ui-glue',
+ { url: url,
+ sessionId : sessionId });
+ });
+}
+
+function testReceiverLaunched() {
+ return new Promise(function(aResolve, aReject) {
+ gScript.addMessageListener('iframe-resolved', function iframeResolvedHandler(aFrame) {
+ gScript.removeMessageListener('iframe-resolved', iframeResolvedHandler);
+ ok(true, "The promise should be resolved.");
+
+ aResolve();
+ });
+
+ var iframe = document.createElement('iframe');
+ iframe.setAttribute('remote', 'true');
+ iframe.setAttribute('mozbrowser', 'true');
+ iframe.setAttribute('src', 'http://example.com');
+ document.body.appendChild(iframe);
+
+ gScript.sendAsyncMessage('trigger-presentation-content-event',
+ { type: 'presentation-receiver-launched',
+ id: sessionId,
+ frame: iframe });
+ });
+}
+
+function testLaunchError() {
+ return new Promise(function(aResolve, aReject) {
+ gScript.addMessageListener('presentation-launch-receiver', function launchReceiverHandler(aDetail) {
+ gScript.removeMessageListener('presentation-launch-receiver', launchReceiverHandler);
+ ok(true, "A presentation-launch-receiver mozPresentationChromeEvent should be received.");
+ is(aDetail.url, url, "Url should be the same.");
+ is(aDetail.id, sessionId, "Session ID should be the same.");
+
+ gScript.addMessageListener('iframe-rejected', function iframeRejectedHandler() {
+ gScript.removeMessageListener('iframe-rejected', iframeRejectedHandler);
+ ok(true, "The promise should be rejected.");
+ aResolve();
+ });
+
+ gScript.sendAsyncMessage('trigger-presentation-content-event',
+ { type: 'presentation-receiver-permission-denied',
+ id: sessionId });
+ });
+
+ gScript.sendAsyncMessage('trigger-ui-glue',
+ { url: url,
+ sessionId : sessionId });
+ });
+}
+
+function runTests() {
+ testLaunchReceiver()
+ .then(testReceiverLaunched)
+ .then(testLaunchError)
+ .then(function() {
+ info('test finished, teardown');
+ gScript.destroy();
+ SimpleTest.finish();
+ });
+}
+
+window.addEventListener('load', runTests);
+</script>
+</body>
+</html>
diff --git a/b2g/components/test/mochitest/test_sandbox_permission.html b/b2g/components/test/mochitest/test_sandbox_permission.html
new file mode 100644
index 0000000000..cd13599a35
--- /dev/null
+++ b/b2g/components/test/mochitest/test_sandbox_permission.html
@@ -0,0 +1,104 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=951997
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Permission Prompt Test</title>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=951997">Permission prompt web content test</a>
+<script type="application/javascript;version=1.8">
+
+"use strict";
+
+const APP_URL = "SandboxPromptTest.html";
+
+var iframe;
+var gUrl = SimpleTest.getTestFileURL("permission_handler_chrome.js");
+var gScript = SpecialPowers.loadChromeScript(gUrl);
+var gResult = [
+ {
+ "video-capture": ["back"],
+ },
+ {
+ "audio-capture": [""],
+ "video-capture": ["back"],
+ },
+ {
+ "audio-capture": [""],
+ },
+ {
+ "geolocation": [],
+ },
+ {
+ "desktop-notification": [],
+ }
+];
+
+function runNext() {
+ if (gResult.length > 0) {
+ // Put the requested permission in query string
+ let requestedPermission = JSON.stringify(Object.keys(gResult[0]));
+ info('request permissions for ' + requestedPermission);
+ iframe.src = APP_URL + '?' + encodeURIComponent(requestedPermission);
+ } else {
+ info('test finished, teardown');
+ gScript.sendAsyncMessage("teardown", "");
+ gScript.destroy();
+ SimpleTest.finish();
+ }
+}
+
+// Create a sanbox iframe.
+function loadBrowser() {
+ iframe = document.createElement("iframe");
+ SpecialPowers.wrap(iframe).mozbrowser = true;
+ iframe.src = 'about:blank';
+ document.body.appendChild(iframe);
+
+ iframe.addEventListener("load", function onLoad() {
+ iframe.removeEventListener("load", onLoad);
+ runNext();
+ });
+}
+
+gScript.addMessageListener("permission-request", function (detail) {
+ let permissions = detail.permissions;
+ let expectedValue = gResult.shift();
+ let permissionTypes = Object.keys(permissions);
+
+ is(permissionTypes.length, Object.keys(expectedValue).length, "expected number of permissions");
+
+ for (let type of permissionTypes) {
+ ok(expectedValue.hasOwnProperty(type), "expected permission type");
+ for (let i in permissions[type]) {
+ is(permissions[type][i], expectedValue[type][i], "expected permission option");
+ }
+ }
+ runNext();
+});
+
+// Add permissions to this app. We use ALLOW_ACTION here. The ContentPermissionPrompt
+// should prompt for permission, not allow it without prompt.
+SpecialPowers.pushPrefEnv({"set": [["media.navigator.permission.disabled", false]]},
+ function() {
+ SpecialPowers.addPermission('video-capture',
+ SpecialPowers.Ci.nsIPermissionManager.ALLOW_ACTION, document);
+ SpecialPowers.addPermission('audio-capture',
+ SpecialPowers.Ci.nsIPermissionManager.ALLOW_ACTION, document);
+ SpecialPowers.addPermission('geolocation',
+ SpecialPowers.Ci.nsIPermissionManager.ALLOW_ACTION, document);
+ SpecialPowers.addPermission('desktop-notification',
+ SpecialPowers.Ci.nsIPermissionManager.ALLOW_ACTION, document);
+ loadBrowser();
+ });
+
+SimpleTest.waitForExplicitFinish();
+</script>
+</pre>
+</body>
+</html>
diff --git a/b2g/components/test/mochitest/test_screenshot.html b/b2g/components/test/mochitest/test_screenshot.html
new file mode 100644
index 0000000000..d2eeb8d483
--- /dev/null
+++ b/b2g/components/test/mochitest/test_screenshot.html
@@ -0,0 +1,31 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1136784
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Screenshot Test</title>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1136784">Screenshot.jsm</a>
+<script type="application/javascript">
+
+"use strict";
+
+var gUrl = SimpleTest.getTestFileURL("screenshot_helper.js");
+var gScript = SpecialPowers.loadChromeScript(gUrl);
+
+SimpleTest.waitForExplicitFinish();
+gScript.addMessageListener("finish", function () {
+ SimpleTest.ok(true, "chrome test script finished");
+ gScript.destroy();
+ SimpleTest.finish();
+});
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/b2g/components/test/mochitest/test_systemapp.html b/b2g/components/test/mochitest/test_systemapp.html
new file mode 100644
index 0000000000..450094a504
--- /dev/null
+++ b/b2g/components/test/mochitest/test_systemapp.html
@@ -0,0 +1,31 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=963239
+-->
+<head>
+ <meta charset="utf-8">
+ <title>SystemAppProxy Test</title>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=963239">SystemAppProxy.jsm</a>
+<script type="application/javascript">
+
+"use strict";
+
+var gUrl = SimpleTest.getTestFileURL("systemapp_helper.js");
+var gScript = SpecialPowers.loadChromeScript(gUrl);
+
+SimpleTest.waitForExplicitFinish();
+gScript.addMessageListener("finish", function () {
+ SimpleTest.ok(true, "chrome test script finished");
+ gScript.destroy();
+ SimpleTest.finish();
+});
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/b2g/components/test/moz.build b/b2g/components/test/moz.build
new file mode 100644
index 0000000000..387e3b8110
--- /dev/null
+++ b/b2g/components/test/moz.build
@@ -0,0 +1,8 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+XPCSHELL_TESTS_MANIFESTS += ['unit/xpcshell.ini']
+MOCHITEST_MANIFESTS += ['mochitest/mochitest.ini']
diff --git a/b2g/components/test/unit/data/test_logger_file b/b2g/components/test/unit/data/test_logger_file
new file mode 100644
index 0000000000..b1ed7f10ae
--- /dev/null
+++ b/b2g/components/test/unit/data/test_logger_file
Binary files differ
diff --git a/b2g/components/test/unit/head_identity.js b/b2g/components/test/unit/head_identity.js
new file mode 100644
index 0000000000..604a772848
--- /dev/null
+++ b/b2g/components/test/unit/head_identity.js
@@ -0,0 +1,159 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+var Ci = Components.interfaces;
+var Cu = Components.utils;
+
+// The following boilerplate makes sure that XPCOM calls
+// that use the profile directory work.
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "MinimalIDService",
+ "resource://gre/modules/identity/MinimalIdentity.jsm",
+ "IdentityService");
+
+XPCOMUtils.defineLazyModuleGetter(this,
+ "Logger",
+ "resource://gre/modules/identity/LogUtils.jsm");
+
+XPCOMUtils.defineLazyServiceGetter(this,
+ "uuidGenerator",
+ "@mozilla.org/uuid-generator;1",
+ "nsIUUIDGenerator");
+
+const TEST_URL = "https://myfavoriteflan.com";
+const TEST_USER = "uumellmahaye1969@hotmail.com";
+const TEST_PRIVKEY = "i-am-a-secret";
+const TEST_CERT = "i~like~pie";
+
+// The following are utility functions for Identity testing
+
+function log(...aMessageArgs) {
+ Logger.log.apply(Logger, ["test"].concat(aMessageArgs));
+}
+
+function partial(fn) {
+ let args = Array.prototype.slice.call(arguments, 1);
+ return function() {
+ return fn.apply(this, args.concat(Array.prototype.slice.call(arguments)));
+ };
+}
+
+function uuid() {
+ return uuidGenerator.generateUUID().toString();
+}
+
+// create a mock "doc" object, which the Identity Service
+// uses as a pointer back into the doc object
+function mockDoc(aParams, aDoFunc) {
+ let mockedDoc = {};
+ mockedDoc.id = uuid();
+
+ // Properties of aParams may include loggedInUser
+ Object.keys(aParams).forEach(function(param) {
+ mockedDoc[param] = aParams[param];
+ });
+
+ // the origin is set inside nsDOMIdentity by looking at the
+ // document.nodePrincipal.origin. Here we, we must satisfy
+ // ourselves with pretending.
+ mockedDoc.origin = "https://jedp.gov";
+
+ mockedDoc['do'] = aDoFunc;
+ mockedDoc.doReady = partial(aDoFunc, 'ready');
+ mockedDoc.doLogin = partial(aDoFunc, 'login');
+ mockedDoc.doLogout = partial(aDoFunc, 'logout');
+ mockedDoc.doError = partial(aDoFunc, 'error');
+ mockedDoc.doCancel = partial(aDoFunc, 'cancel');
+ mockedDoc.doCoffee = partial(aDoFunc, 'coffee');
+
+ return mockedDoc;
+}
+
+// create a mock "pipe" object that would normally communicate
+// messages up to gaia (either the trusty ui or the hidden iframe),
+// and convey messages back down from gaia to the controller through
+// the message callback.
+
+// The mock receiving pipe simulates gaia which, after receiving messages
+// through the pipe, will call back with instructions to invoke
+// certain methods. It mocks what comes back from the other end of
+// the pipe.
+function mockReceivingPipe() {
+ let MockedPipe = {
+ communicate: function(aRpOptions, aGaiaOptions, aMessageCallback) {
+ switch (aGaiaOptions.message) {
+ case "identity-delegate-watch":
+ aMessageCallback({json: {method: "ready"}});
+ break;
+ case "identity-delegate-request":
+ aMessageCallback({json: {method: "login", assertion: TEST_CERT}});
+ break;
+ case "identity-delegate-logout":
+ aMessageCallback({json: {method: "logout"}});
+ break;
+ default:
+ throw("what the what?? " + aGaiaOptions.message);
+ break;
+ }
+ }
+ };
+ return MockedPipe;
+}
+
+// The mock sending pipe lets us test what's actually getting put in the
+// pipe.
+function mockSendingPipe(aMessageCallback) {
+ let MockedPipe = {
+ communicate: function(aRpOptions, aGaiaOptions, aDummyCallback) {
+ aMessageCallback(aRpOptions, aGaiaOptions);
+ }
+ };
+ return MockedPipe;
+}
+
+// mimicking callback funtionality for ease of testing
+// this observer auto-removes itself after the observe function
+// is called, so this is meant to observe only ONE event.
+function makeObserver(aObserveTopic, aObserveFunc) {
+ let observer = {
+ // nsISupports provides type management in C++
+ // nsIObserver is to be an observer
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports, Ci.nsIObserver]),
+
+ observe: function (aSubject, aTopic, aData) {
+ if (aTopic == aObserveTopic) {
+ Services.obs.removeObserver(observer, aObserveTopic);
+ aObserveFunc(aSubject, aTopic, aData);
+ }
+ }
+ };
+
+ Services.obs.addObserver(observer, aObserveTopic, false);
+}
+
+// a hook to set up the ID service with an identity with keypair and all
+// when ready, invoke callback with the identity. It's there if we need it.
+function setup_test_identity(identity, cert, cb) {
+ cb();
+}
+
+// takes a list of functions and returns a function that
+// when called the first time, calls the first func,
+// then the next time the second, etc.
+function call_sequentially() {
+ let numCalls = 0;
+ let funcs = arguments;
+
+ return function() {
+ if (!funcs[numCalls]) {
+ let argString = Array.prototype.slice.call(arguments).join(",");
+ do_throw("Too many calls: " + argString);
+ return;
+ }
+ funcs[numCalls].apply(funcs[numCalls],arguments);
+ numCalls += 1;
+ };
+}
diff --git a/b2g/components/test/unit/head_logshake_gonk.js b/b2g/components/test/unit/head_logshake_gonk.js
new file mode 100644
index 0000000000..e94234f1f0
--- /dev/null
+++ b/b2g/components/test/unit/head_logshake_gonk.js
@@ -0,0 +1,58 @@
+/**
+ * Boostrap LogShake's tests that need gonk support.
+ * This is creating a fake sdcard for LogShake tests and importing LogShake and
+ * osfile
+ */
+
+/* jshint moz: true */
+/* global Components, LogCapture, LogShake, ok, add_test, run_next_test, dump,
+ do_get_profile, OS, volumeService, equal, XPCOMUtils */
+/* exported setup_logshake_mocks */
+
+/* disable use strict warning */
+/* jshint -W097 */
+
+"use strict";
+
+var Cu = Components.utils;
+var Ci = Components.interfaces;
+var Cc = Components.classes;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/osfile.jsm");
+
+XPCOMUtils.defineLazyServiceGetter(this, "volumeService",
+ "@mozilla.org/telephony/volume-service;1",
+ "nsIVolumeService");
+
+var sdcard;
+
+function setup_logshake_mocks() {
+ do_get_profile();
+ setup_fs();
+}
+
+function setup_fs() {
+ OS.File.makeDir("/data/local/tmp/sdcard/", {from: "/data"}).then(function() {
+ setup_sdcard();
+ });
+}
+
+function setup_sdcard() {
+ let volName = "sdcard";
+ let mountPoint = "/data/local/tmp/sdcard";
+ volumeService.createFakeVolume(volName, mountPoint);
+
+ let vol = volumeService.getVolumeByName(volName);
+ ok(vol, "volume shouldn't be null");
+ equal(volName, vol.name, "name");
+ equal(Ci.nsIVolume.STATE_MOUNTED, vol.state, "state");
+
+ ensure_sdcard();
+}
+
+function ensure_sdcard() {
+ sdcard = volumeService.getVolumeByName("sdcard").mountPoint;
+ ok(sdcard, "Should have a valid sdcard mountpoint");
+ run_next_test();
+}
diff --git a/b2g/components/test/unit/test_aboutserviceworkers.js b/b2g/components/test/unit/test_aboutserviceworkers.js
new file mode 100644
index 0000000000..d1a7d41aaa
--- /dev/null
+++ b/b2g/components/test/unit/test_aboutserviceworkers.js
@@ -0,0 +1,142 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+var {utils: Cu} = Components;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "AboutServiceWorkers",
+ "resource://gre/modules/AboutServiceWorkers.jsm");
+
+XPCOMUtils.defineLazyServiceGetter(this, "gServiceWorkerManager",
+ "@mozilla.org/serviceworkers/manager;1",
+ "nsIServiceWorkerManager");
+
+const CHROME_MSG = "mozAboutServiceWorkersChromeEvent";
+
+const ORIGINAL_SENDRESULT = AboutServiceWorkers.sendResult;
+const ORIGINAL_SENDERROR = AboutServiceWorkers.sendError;
+
+do_get_profile();
+
+var mockSendResult = (aId, aResult) => {
+ let msg = {
+ id: aId,
+ result: aResult
+ };
+ Services.obs.notifyObservers({wrappedJSObject: msg}, CHROME_MSG, null);
+};
+
+var mockSendError = (aId, aError) => {
+ let msg = {
+ id: aId,
+ result: aError
+ };
+ Services.obs.notifyObservers({wrappedJSObject: msg}, CHROME_MSG, null);
+};
+
+function attachMocks() {
+ AboutServiceWorkers.sendResult = mockSendResult;
+ AboutServiceWorkers.sendError = mockSendError;
+}
+
+function restoreMocks() {
+ AboutServiceWorkers.sendResult = ORIGINAL_SENDRESULT;
+ AboutServiceWorkers.sendError = ORIGINAL_SENDERROR;
+}
+
+do_register_cleanup(restoreMocks);
+
+function run_test() {
+ run_next_test();
+}
+
+/**
+ * "init" tests
+ */
+[
+// Pref disabled, no registrations
+{
+ prefEnabled: false,
+ expectedMessage: {
+ id: Date.now(),
+ result: {
+ enabled: false,
+ registrations: []
+ }
+ }
+},
+// Pref enabled, no registrations
+{
+ prefEnabled: true,
+ expectedMessage: {
+ id: Date.now(),
+ result: {
+ enabled: true,
+ registrations: []
+ }
+ }
+}].forEach(test => {
+ add_test(function() {
+ Services.prefs.setBoolPref("dom.serviceWorkers.enabled", test.prefEnabled);
+
+ let id = test.expectedMessage.id;
+
+ function onMessage(subject, topic, data) {
+ let message = subject.wrappedJSObject;
+ let expected = test.expectedMessage;
+
+ do_check_true(message.id, "Message should have id");
+ do_check_eq(message.id, test.expectedMessage.id,
+ "Id should be the expected one");
+ do_check_eq(message.result.enabled, expected.result.enabled,
+ "Pref should be disabled");
+ do_check_true(message.result.registrations, "Registrations should exist");
+ do_check_eq(message.result.registrations.length,
+ expected.result.registrations.length,
+ "Registrations length should be the expected one");
+
+ Services.obs.removeObserver(onMessage, CHROME_MSG);
+
+ run_next_test();
+ }
+
+ Services.obs.addObserver(onMessage, CHROME_MSG, false);
+
+ attachMocks();
+
+ AboutServiceWorkers.handleEvent({ detail: {
+ id: id,
+ name: "init"
+ }});
+ });
+});
+
+/**
+ * ServiceWorkerManager tests.
+ */
+
+// We cannot register a sw via ServiceWorkerManager cause chrome
+// registrations are not allowed.
+// All we can do for now is to test the interface of the swm.
+add_test(function test_swm() {
+ do_check_true(gServiceWorkerManager, "SWM exists");
+ do_check_true(gServiceWorkerManager.getAllRegistrations,
+ "SWM.getAllRegistrations exists");
+ do_check_true(typeof gServiceWorkerManager.getAllRegistrations == "function",
+ "SWM.getAllRegistrations is a function");
+ do_check_true(gServiceWorkerManager.propagateSoftUpdate,
+ "SWM.propagateSoftUpdate exists");
+ do_check_true(typeof gServiceWorkerManager.propagateSoftUpdate == "function",
+
+ "SWM.propagateSoftUpdate is a function");
+ do_check_true(gServiceWorkerManager.propagateUnregister,
+ "SWM.propagateUnregister exists");
+ do_check_true(typeof gServiceWorkerManager.propagateUnregister == "function",
+ "SWM.propagateUnregister exists");
+
+ run_next_test();
+});
diff --git a/b2g/components/test/unit/test_bug793310.js b/b2g/components/test/unit/test_bug793310.js
new file mode 100644
index 0000000000..2bdb8252e2
--- /dev/null
+++ b/b2g/components/test/unit/test_bug793310.js
@@ -0,0 +1,39 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+function run_test() {
+ Components.utils.import("resource:///modules/TelURIParser.jsm")
+
+ // global-phone-number
+ do_check_eq(TelURIParser.parseURI('tel', 'tel:+1234'), '+1234');
+
+ // global-phone-number => white space separator
+ do_check_eq(TelURIParser.parseURI('tel', 'tel:+123 456 789'), '+123 456 789');
+
+ // global-phone-number => ignored chars
+ do_check_eq(TelURIParser.parseURI('tel', 'tel:+1234_123'), '+1234');
+
+ // global-phone-number => visualSeparator + digits
+ do_check_eq(TelURIParser.parseURI('tel', 'tel:+-.()1234567890'), '+-.()1234567890');
+
+ // local-phone-number
+ do_check_eq(TelURIParser.parseURI('tel', 'tel:1234'), '1234');
+
+ // local-phone-number => visualSeparator + digits + dtmfDigits + pauseCharacter
+ do_check_eq(TelURIParser.parseURI('tel', 'tel:-.()1234567890ABCDpw'), '-.()1234567890ABCDpw');
+
+ // local-phone-number => visualSeparator + digits + dtmfDigits + pauseCharacter + ignored chars
+ do_check_eq(TelURIParser.parseURI('tel', 'tel:-.()1234567890ABCDpw_'), '-.()1234567890ABCDpw');
+
+ // local-phone-number => isdn-subaddress
+ do_check_eq(TelURIParser.parseURI('tel', 'tel:123;isub=123'), '123');
+
+ // local-phone-number => post-dial
+ do_check_eq(TelURIParser.parseURI('tel', 'tel:123;postd=123'), '123');
+
+ // local-phone-number => prefix
+ do_check_eq(TelURIParser.parseURI('tel', 'tel:123;phone-context=+0321'), '+0321123');
+
+ // local-phone-number => isdn-subaddress + post-dial + prefix
+ do_check_eq(TelURIParser.parseURI('tel', 'tel:123;isub=123;postd=123;phone-context=+0321'), '+0321123');
+}
diff --git a/b2g/components/test/unit/test_bug832946.js b/b2g/components/test/unit/test_bug832946.js
new file mode 100644
index 0000000000..4ddbd4280f
--- /dev/null
+++ b/b2g/components/test/unit/test_bug832946.js
@@ -0,0 +1,18 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+function run_test() {
+ Components.utils.import("resource:///modules/TelURIParser.jsm")
+
+ // blocked numbers
+ do_check_eq(TelURIParser.parseURI('tel', 'tel:#1234*'), null);
+ do_check_eq(TelURIParser.parseURI('tel', 'tel:*1234#'), null);
+ do_check_eq(TelURIParser.parseURI('tel', 'tel:*1234*'), null);
+ do_check_eq(TelURIParser.parseURI('tel', 'tel:#1234#'), null);
+ do_check_eq(TelURIParser.parseURI('tel', 'tel:*#*#7780#*#*'), null);
+ do_check_eq(TelURIParser.parseURI('tel', 'tel:*1234AB'), null);
+
+ // white list
+ do_check_eq(TelURIParser.parseURI('tel', 'tel:*1234'), '*1234');
+ do_check_eq(TelURIParser.parseURI('tel', 'tel:#1234'), '#1234');
+}
diff --git a/b2g/components/test/unit/test_fxaccounts.js b/b2g/components/test/unit/test_fxaccounts.js
new file mode 100644
index 0000000000..5de0d65653
--- /dev/null
+++ b/b2g/components/test/unit/test_fxaccounts.js
@@ -0,0 +1,212 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+var {utils: Cu} = Components;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://services-common/utils.js");
+Cu.import("resource://testing-common/httpd.js");
+
+XPCOMUtils.defineLazyModuleGetter(this, "FxAccountsMgmtService",
+ "resource://gre/modules/FxAccountsMgmtService.jsm",
+ "FxAccountsMgmtService");
+
+XPCOMUtils.defineLazyModuleGetter(this, "FxAccountsManager",
+ "resource://gre/modules/FxAccountsManager.jsm");
+
+// At end of test, restore original state
+const ORIGINAL_AUTH_URI = Services.prefs.getCharPref("identity.fxaccounts.auth.uri");
+var { SystemAppProxy } = Cu.import("resource://gre/modules/FxAccountsMgmtService.jsm");
+const ORIGINAL_SENDCUSTOM = SystemAppProxy._sendCustomEvent;
+do_register_cleanup(function() {
+ Services.prefs.setCharPref("identity.fxaccounts.auth.uri", ORIGINAL_AUTH_URI);
+ SystemAppProxy._sendCustomEvent = ORIGINAL_SENDCUSTOM;
+ Services.prefs.clearUserPref("identity.fxaccounts.skipDeviceRegistration");
+});
+
+// Make profile available so that fxaccounts can store user data
+do_get_profile();
+
+// Mock the system app proxy; make message passing possible
+var mockSendCustomEvent = function(aEventName, aMsg) {
+ Services.obs.notifyObservers({wrappedJSObject: aMsg}, aEventName, null);
+};
+
+function run_test() {
+ run_next_test();
+}
+
+add_task(function test_overall() {
+ // FxA device registration throws from this context
+ Services.prefs.setBoolPref("identity.fxaccounts.skipDeviceRegistration", true);
+
+ do_check_neq(FxAccountsMgmtService, null);
+});
+
+// Check that invalid email capitalization is corrected on signIn.
+// https://github.com/mozilla/fxa-auth-server/blob/master/docs/api.md#post-v1accountlogin
+add_test(function test_invalidEmailCase_signIn() {
+ do_test_pending();
+ let clientEmail = "greta.garbo@gmail.com";
+ let canonicalEmail = "Greta.Garbo@gmail.COM";
+ let attempts = 0;
+
+ function writeResp(response, msg) {
+ if (typeof msg === "object") {
+ msg = JSON.stringify(msg);
+ }
+ response.bodyOutputStream.write(msg, msg.length);
+ }
+
+ // Mock of the fxa accounts auth server, reproducing the behavior of
+ // /account/login when email capitalization is incorrect on signIn.
+ let server = httpd_setup({
+ "/account/login": function(request, response) {
+ response.setHeader("Content-Type", "application/json");
+ attempts += 1;
+
+ // Ensure we don't get in an endless loop
+ if (attempts > 2) {
+ response.setStatusLine(request.httpVersion, 429, "Sorry, you had your chance");
+ writeResp(response, {});
+ return;
+ }
+
+ let body = CommonUtils.readBytesFromInputStream(request.bodyInputStream);
+ let jsonBody = JSON.parse(body);
+ let email = jsonBody.email;
+
+ // The second time through, the accounts client will call the api with
+ // the correct email capitalization.
+ if (email == canonicalEmail) {
+ response.setStatusLine(request.httpVersion, 200, "Yay");
+ writeResp(response, {
+ uid: "your-uid",
+ sessionToken: "your-sessionToken",
+ keyFetchToken: "your-keyFetchToken",
+ verified: true,
+ authAt: 1392144866,
+ });
+ return;
+ }
+
+ // If the client has the wrong case on the email, we return a 400, with
+ // the capitalization of the email as saved in the accounts database.
+ response.setStatusLine(request.httpVersion, 400, "Incorrect email case");
+ writeResp(response, {
+ code: 400,
+ errno: 120,
+ error: "Incorrect email case",
+ email: canonicalEmail,
+ });
+ return;
+ },
+ });
+
+ // Point the FxAccountsClient's hawk rest request client to the mock server
+ Services.prefs.setCharPref("identity.fxaccounts.auth.uri", server.baseURI);
+
+ // FxA device registration throws from this context
+ Services.prefs.setBoolPref("identity.fxaccounts.skipDeviceRegistration", true);
+
+ // Receive a mozFxAccountsChromeEvent message
+ function onMessage(subject, topic, data) {
+ let message = subject.wrappedJSObject;
+
+ switch (message.id) {
+ // When we signed in as "Greta.Garbo", the server should have told us
+ // that the proper capitalization is really "greta.garbo". Call
+ // getAccounts to get the signed-in user and ensure that the
+ // capitalization is correct.
+ case "signIn":
+ FxAccountsMgmtService.handleEvent({
+ detail: {
+ id: "getAccounts",
+ data: {
+ method: "getAccounts",
+ }
+ }
+ });
+ break;
+
+ // Having initially signed in as "Greta.Garbo", getAccounts should show
+ // us that the signed-in user has the properly-capitalized email,
+ // "greta.garbo".
+ case "getAccounts":
+ Services.obs.removeObserver(onMessage, "mozFxAccountsChromeEvent");
+
+ do_check_eq(message.data.email, canonicalEmail);
+
+ do_test_finished();
+ server.stop(run_next_test);
+ break;
+
+ // We should not receive any other mozFxAccountsChromeEvent messages
+ default:
+ do_throw("wat!");
+ break;
+ }
+ }
+
+ Services.obs.addObserver(onMessage, "mozFxAccountsChromeEvent", false);
+
+ SystemAppProxy._sendCustomEvent = mockSendCustomEvent;
+
+ // Trigger signIn using an email with incorrect capitalization
+ FxAccountsMgmtService.handleEvent({
+ detail: {
+ id: "signIn",
+ data: {
+ method: "signIn",
+ email: clientEmail,
+ password: "123456",
+ },
+ },
+ });
+});
+
+add_test(function testHandleGetAssertionError_defaultCase() {
+ do_test_pending();
+
+ // FxA device registration throws from this context
+ Services.prefs.setBoolPref("identity.fxaccounts.skipDeviceRegistration", true);
+
+ FxAccountsManager.getAssertion(null).then(
+ success => {
+ // getAssertion should throw with invalid audience
+ ok(false);
+ },
+ reason => {
+ equal("INVALID_AUDIENCE", reason.error);
+ do_test_finished();
+ run_next_test();
+ }
+ )
+});
+
+// End of tests
+// Utility functions follow
+
+function httpd_setup (handlers, port=-1) {
+ let server = new HttpServer();
+ for (let path in handlers) {
+ server.registerPathHandler(path, handlers[path]);
+ }
+ try {
+ server.start(port);
+ } catch (ex) {
+ dump("ERROR starting server on port " + port + ". Already a process listening?");
+ do_throw(ex);
+ }
+
+ // Set the base URI for convenience.
+ let i = server.identity;
+ server.baseURI = i.primaryScheme + "://" + i.primaryHost + ":" + i.primaryPort;
+
+ return server;
+}
+
+
diff --git a/b2g/components/test/unit/test_logcapture.js b/b2g/components/test/unit/test_logcapture.js
new file mode 100644
index 0000000000..9dbe2bc77b
--- /dev/null
+++ b/b2g/components/test/unit/test_logcapture.js
@@ -0,0 +1,13 @@
+/**
+ * Testing non Gonk-specific code path
+ */
+function run_test() {
+ Components.utils.import("resource:///modules/LogCapture.jsm");
+ run_next_test();
+}
+
+// Trivial test just to make sure we have no syntax error
+add_test(function test_logCapture_loads() {
+ ok(LogCapture, "LogCapture object exists");
+ run_next_test();
+});
diff --git a/b2g/components/test/unit/test_logcapture_gonk.js b/b2g/components/test/unit/test_logcapture_gonk.js
new file mode 100644
index 0000000000..d80f33dd9a
--- /dev/null
+++ b/b2g/components/test/unit/test_logcapture_gonk.js
@@ -0,0 +1,70 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+
+/**
+ * Test that LogCapture successfully reads from the /dev/log devices, returning
+ * a Uint8Array of some length, including zero. This tests a few standard
+ * log devices
+ */
+function run_test() {
+ Components.utils.import("resource:///modules/LogCapture.jsm");
+ run_next_test();
+}
+
+function verifyLog(log) {
+ // log exists
+ notEqual(log, null);
+ // log has a length and it is non-negative (is probably array-like)
+ ok(log.length >= 0);
+}
+
+add_test(function test_readLogFile() {
+ let mainLog = LogCapture.readLogFile("/dev/log/main");
+ verifyLog(mainLog);
+
+ let meminfoLog = LogCapture.readLogFile("/proc/meminfo");
+ verifyLog(meminfoLog);
+
+ run_next_test();
+});
+
+add_test(function test_readProperties() {
+ let propertiesLog = LogCapture.readProperties();
+ notEqual(propertiesLog, null, "Properties should not be null");
+ notEqual(propertiesLog, undefined, "Properties should not be undefined");
+
+ for (let propertyName in propertiesLog) {
+ equal(typeof(propertiesLog[propertyName]), "string",
+ "Property " + propertyName + " should be a string");
+ }
+
+ equal(propertiesLog["ro.product.locale.language"], "en",
+ "Locale language should be read correctly. See bug 1171577.");
+
+ equal(propertiesLog["ro.product.locale.region"], "US",
+ "Locale region should be read correctly. See bug 1171577.");
+
+ run_next_test();
+});
+
+add_test(function test_readAppIni() {
+ let appIni = LogCapture.readLogFile("/system/b2g/application.ini");
+ verifyLog(appIni);
+
+ run_next_test();
+});
+
+add_test(function test_get_about_memory() {
+ let memLog = LogCapture.readAboutMemory();
+
+ ok(memLog, "Should have returned a valid Promise object");
+
+ memLog.then(file => {
+ ok(file, "Should have returned a filename");
+ run_next_test();
+ }, error => {
+ ok(false, "Dumping about:memory promise rejected: " + error);
+ run_next_test();
+ });
+});
diff --git a/b2g/components/test/unit/test_logparser.js b/b2g/components/test/unit/test_logparser.js
new file mode 100644
index 0000000000..624dcc6e28
--- /dev/null
+++ b/b2g/components/test/unit/test_logparser.js
@@ -0,0 +1,75 @@
+/* jshint moz: true */
+
+var {utils: Cu, classes: Cc, interfaces: Ci} = Components;
+
+function debug(msg) {
+ var timestamp = Date.now();
+ dump("LogParser: " + timestamp + ": " + msg + "\n");
+}
+
+function run_test() {
+ Cu.import("resource:///modules/LogParser.jsm");
+ debug("Starting");
+ run_next_test();
+}
+
+function makeStream(file) {
+ var fileStream = Cc["@mozilla.org/network/file-input-stream;1"]
+ .createInstance(Ci.nsIFileInputStream);
+ fileStream.init(file, -1, -1, 0);
+ var bis = Cc["@mozilla.org/binaryinputstream;1"]
+ .createInstance(Ci.nsIBinaryInputStream);
+ bis.setInputStream(fileStream);
+ return bis;
+}
+
+add_test(function test_parse_logfile() {
+ let loggerFile = do_get_file("data/test_logger_file");
+
+ let loggerStream = makeStream(loggerFile);
+
+ // Initialize arrays to hold the file contents (lengths are hardcoded)
+ let loggerArray = new Uint8Array(loggerStream.readByteArray(4037));
+
+ loggerStream.close();
+
+ let logMessages = LogParser.parseLogArray(loggerArray);
+
+ ok(logMessages.length === 58, "There should be 58 messages in the log");
+
+ let expectedLogEntry = {
+ processId: 271, threadId: 271,
+ seconds: 790796, nanoseconds: 620000001, time: 790796620.000001,
+ priority: 4, tag: "Vold",
+ message: "Vold 2.1 (the revenge) firing up\n"
+ };
+
+ deepEqual(expectedLogEntry, logMessages[0]);
+ run_next_test();
+});
+
+add_test(function test_print_properties() {
+ let properties = {
+ "ro.secure": "1",
+ "sys.usb.state": "diag,serial_smd,serial_tty,rmnet_bam,mass_storage,adb"
+ };
+
+ let logMessagesRaw = LogParser.prettyPrintPropertiesArray(properties);
+ let logMessages = new TextDecoder("utf-8").decode(logMessagesRaw);
+ let logMessagesArray = logMessages.split("\n");
+
+ ok(logMessagesArray.length === 3, "There should be 3 lines in the log.");
+ notEqual(logMessagesArray[0], "", "First line should not be empty");
+ notEqual(logMessagesArray[1], "", "Second line should not be empty");
+ equal(logMessagesArray[2], "", "Last line should be empty");
+
+ let expectedLog = [
+ "[ro.secure]: [1]",
+ "[sys.usb.state]: [diag,serial_smd,serial_tty,rmnet_bam,mass_storage,adb]",
+ ""
+ ].join("\n");
+
+ deepEqual(expectedLog, logMessages);
+
+ run_next_test();
+});
diff --git a/b2g/components/test/unit/test_logshake.js b/b2g/components/test/unit/test_logshake.js
new file mode 100644
index 0000000000..cfb81b8933
--- /dev/null
+++ b/b2g/components/test/unit/test_logshake.js
@@ -0,0 +1,218 @@
+/**
+ * Test the log capturing capabilities of LogShake.jsm
+ */
+
+/* jshint moz: true */
+/* global Components, LogCapture, LogShake, ok, add_test, run_next_test, dump */
+/* exported run_test */
+
+/* disable use strict warning */
+/* jshint -W097 */
+"use strict";
+
+var Cu = Components.utils;
+
+Cu.import("resource://gre/modules/LogCapture.jsm");
+Cu.import("resource://gre/modules/LogShake.jsm");
+
+const EVENTS_PER_SECOND = 6.25;
+const GRAVITY = 9.8;
+
+/**
+ * Force logshake to handle a device motion event with given components.
+ * Does not use SystemAppProxy because event needs special
+ * accelerationIncludingGravity property.
+ */
+function sendDeviceMotionEvent(x, y, z) {
+ let event = {
+ type: "devicemotion",
+ accelerationIncludingGravity: {
+ x: x,
+ y: y,
+ z: z
+ }
+ };
+ LogShake.handleEvent(event);
+}
+
+/**
+ * Send a screen change event directly, does not use SystemAppProxy due to race
+ * conditions.
+ */
+function sendScreenChangeEvent(screenEnabled) {
+ let event = {
+ type: "screenchange",
+ detail: {
+ screenEnabled: screenEnabled
+ }
+ };
+ LogShake.handleEvent(event);
+}
+
+/**
+ * Mock the readLogFile function of LogCapture.
+ * Used to detect whether LogShake activates.
+ * @return {Array<String>} Locations that LogShake tries to read
+ */
+function mockReadLogFile() {
+ let readLocations = [];
+
+ LogCapture.readLogFile = function(loc) {
+ readLocations.push(loc);
+ return null; // we don't want to provide invalid data to a parser
+ };
+
+ // Allow inspection of readLocations by caller
+ return readLocations;
+}
+
+/**
+ * Send a series of events that corresponds to a shake
+ */
+function sendSustainedShake() {
+ // Fire a series of devicemotion events that are of shake magnitude
+ for (let i = 0; i < 2 * EVENTS_PER_SECOND; i++) {
+ sendDeviceMotionEvent(0, 2 * GRAVITY, 2 * GRAVITY);
+ }
+
+}
+
+add_test(function test_do_log_capture_after_shaking() {
+ // Enable LogShake
+ LogShake.init();
+
+ let readLocations = mockReadLogFile();
+
+ sendSustainedShake();
+
+ ok(readLocations.length > 0,
+ "LogShake should attempt to read at least one log");
+
+ LogShake.uninit();
+ run_next_test();
+});
+
+add_test(function test_do_nothing_when_resting() {
+ // Enable LogShake
+ LogShake.init();
+
+ let readLocations = mockReadLogFile();
+
+ // Fire several devicemotion events that are relatively tiny
+ for (let i = 0; i < 2 * EVENTS_PER_SECOND; i++) {
+ sendDeviceMotionEvent(0, GRAVITY, GRAVITY);
+ }
+
+ ok(readLocations.length === 0,
+ "LogShake should not read any logs");
+
+ LogShake.uninit();
+ run_next_test();
+});
+
+add_test(function test_do_nothing_when_disabled() {
+ // Disable LogShake
+ LogShake.uninit();
+
+ let readLocations = mockReadLogFile();
+
+ // Fire a series of events that would normally be a shake
+ sendSustainedShake();
+
+ ok(readLocations.length === 0,
+ "LogShake should not read any logs");
+
+ run_next_test();
+});
+
+add_test(function test_do_nothing_when_screen_off() {
+ // Enable LogShake
+ LogShake.init();
+
+ // Send an event as if the screen has been turned off
+ sendScreenChangeEvent(false);
+
+ let readLocations = mockReadLogFile();
+
+ // Fire a series of events that would normally be a shake
+ sendSustainedShake();
+
+ ok(readLocations.length === 0,
+ "LogShake should not read any logs");
+
+ // Restore the screen
+ sendScreenChangeEvent(true);
+
+ LogShake.uninit();
+ run_next_test();
+});
+
+add_test(function test_do_log_capture_resilient_readLogFile() {
+ // Enable LogShake
+ LogShake.init();
+
+ let readLocations = [];
+ LogCapture.readLogFile = function(loc) {
+ readLocations.push(loc);
+ throw new Error("Exception during readLogFile for: " + loc);
+ };
+
+ // Fire a series of events that would normally be a shake
+ sendSustainedShake();
+
+ ok(readLocations.length > 0,
+ "LogShake should attempt to read at least one log");
+
+ LogShake.uninit();
+ run_next_test();
+});
+
+add_test(function test_do_log_capture_resilient_parseLog() {
+ // Enable LogShake
+ LogShake.init();
+
+ let readLocations = [];
+ LogCapture.readLogFile = function(loc) {
+ readLocations.push(loc);
+ LogShake.LOGS_WITH_PARSERS[loc] = function() {
+ throw new Error("Exception during LogParser for: " + loc);
+ };
+ return null;
+ };
+
+ // Fire a series of events that would normally be a shake
+ sendSustainedShake();
+
+ ok(readLocations.length > 0,
+ "LogShake should attempt to read at least one log");
+
+ LogShake.uninit();
+ run_next_test();
+});
+
+add_test(function test_do_nothing_when_dropped() {
+ // Enable LogShake
+ LogShake.init();
+
+ let readLocations = mockReadLogFile();
+
+ // We want a series of spikes to be ignored by LogShake. This roughly
+ // corresponds to the compare_stairs_sock graph on bug #1101994
+
+ for (let i = 0; i < 10 * EVENTS_PER_SECOND; i++) {
+ // Fire a devicemotion event that is at rest
+ sendDeviceMotionEvent(0, 0, GRAVITY);
+ // Fire a spike of motion
+ sendDeviceMotionEvent(0, 2 * GRAVITY, 2 * GRAVITY);
+ }
+
+ ok(readLocations.length === 0,
+ "LogShake should not read any logs");
+
+ LogShake.uninit();
+ run_next_test();
+});
+
+function run_test() {
+ run_next_test();
+}
diff --git a/b2g/components/test/unit/test_logshake_gonk.js b/b2g/components/test/unit/test_logshake_gonk.js
new file mode 100644
index 0000000000..28de0263f2
--- /dev/null
+++ b/b2g/components/test/unit/test_logshake_gonk.js
@@ -0,0 +1,61 @@
+/**
+ * Test the log capturing capabilities of LogShake.jsm, checking
+ * for Gonk-specific parts
+ */
+
+/* jshint moz: true, esnext: true */
+/* global Cu, LogCapture, LogShake, ok, add_test, run_next_test, dump,
+ setup_logshake_mocks, OS, sdcard */
+/* exported run_test */
+
+/* disable use strict warning */
+/* jshint -W097 */
+
+"use strict";
+
+Cu.import("resource://gre/modules/Promise.jsm");
+
+function run_test() {
+ Cu.import("resource://gre/modules/LogShake.jsm");
+ run_next_test();
+}
+
+add_test(setup_logshake_mocks);
+
+add_test(function test_logShake_captureLogs_writes() {
+ // Enable LogShake
+ LogShake.init();
+
+ let expectedFiles = [];
+
+ LogShake.captureLogs().then(logResults => {
+ LogShake.uninit();
+
+ ok(logResults.logFilenames.length > 0, "Should have filenames");
+ ok(logResults.logPaths.length > 0, "Should have paths");
+ ok(!logResults.compressed, "Should not be compressed");
+
+ logResults.logPaths.forEach(f => {
+ let p = OS.Path.join(sdcard, f);
+ ok(p, "Should have a valid result path: " + p);
+
+ let t = OS.File.exists(p).then(rv => {
+ ok(rv, "File exists: " + p);
+ });
+
+ expectedFiles.push(t);
+ });
+
+ Promise.all(expectedFiles).then(() => {
+ ok(true, "Completed all files checks");
+ run_next_test();
+ });
+ },
+ error => {
+ LogShake.uninit();
+
+ ok(false, "Should not have received error: " + error);
+
+ run_next_test();
+ });
+});
diff --git a/b2g/components/test/unit/test_logshake_gonk_compression.js b/b2g/components/test/unit/test_logshake_gonk_compression.js
new file mode 100644
index 0000000000..b5af460817
--- /dev/null
+++ b/b2g/components/test/unit/test_logshake_gonk_compression.js
@@ -0,0 +1,76 @@
+/**
+ * Test the log capturing capabilities of LogShake.jsm, checking
+ * for Gonk-specific parts
+ */
+
+/* jshint moz: true, esnext: true */
+/* global Cc, Ci, Cu, LogCapture, LogShake, ok, add_test, run_next_test, dump,
+ setup_logshake_mocks, OS, sdcard, FileUtils */
+/* exported run_test */
+
+/* disable use strict warning */
+/* jshint -W097 */
+
+"use strict";
+
+Cu.import("resource://gre/modules/Promise.jsm");
+Cu.import("resource://gre/modules/FileUtils.jsm");
+
+function run_test() {
+ Cu.import("resource://gre/modules/LogShake.jsm");
+ run_next_test();
+}
+
+add_test(setup_logshake_mocks);
+
+add_test(function test_logShake_captureLogs_writes_zip() {
+ // Enable LogShake
+ LogShake.init();
+
+ let expectedFiles = [];
+
+ LogShake.enableQAMode();
+
+ LogShake.captureLogs().then(logResults => {
+ LogShake.uninit();
+
+ ok(logResults.logPaths.length === 1, "Should have zip path");
+ ok(logResults.logFilenames.length >= 1, "Should have log filenames");
+ ok(logResults.compressed, "Log files should be compressed");
+
+ let zipPath = OS.Path.join(sdcard, logResults.logPaths[0]);
+ ok(zipPath, "Should have a valid archive path: " + zipPath);
+
+ let zipFile = new FileUtils.File(zipPath);
+ ok(zipFile, "Should have a valid archive file: " + zipFile);
+
+ let zipReader = Cc["@mozilla.org/libjar/zip-reader;1"]
+ .createInstance(Ci.nsIZipReader);
+ zipReader.open(zipFile);
+
+ let logFilenamesSeen = {};
+
+ let zipEntries = zipReader.findEntries(null); // Find all entries
+ while (zipEntries.hasMore()) {
+ let entryName = zipEntries.getNext();
+ let entry = zipReader.getEntry(entryName);
+ logFilenamesSeen[entryName] = true;
+ ok(!entry.isDirectory, "Archive entry " + entryName + " should be a file");
+ }
+ zipReader.close();
+
+ // TODO: Verify archive contents
+ logResults.logFilenames.forEach(filename => {
+ ok(logFilenamesSeen[filename], "File " + filename + " should be present in archive");
+ });
+ run_next_test();
+ },
+ error => {
+ LogShake.uninit();
+
+ ok(false, "Should not have received error: " + error);
+
+ run_next_test();
+ });
+});
+
diff --git a/b2g/components/test/unit/test_logshake_readLog_gonk.js b/b2g/components/test/unit/test_logshake_readLog_gonk.js
new file mode 100644
index 0000000000..003723ad53
--- /dev/null
+++ b/b2g/components/test/unit/test_logshake_readLog_gonk.js
@@ -0,0 +1,65 @@
+/**
+ * Test the log capturing capabilities of LogShake.jsm under conditions that
+ * could cause races
+ */
+
+/* jshint moz: true, esnext: true */
+/* global Cu, LogCapture, LogShake, ok, add_test, run_next_test, dump,
+ XPCOMUtils, do_get_profile, OS, volumeService, Promise, equal,
+ setup_logshake_mocks */
+/* exported run_test */
+
+/* disable use strict warning */
+/* jshint -W097 */
+
+"use strict";
+
+function run_test() {
+ Cu.import("resource://gre/modules/LogShake.jsm");
+ run_next_test();
+}
+
+add_test(setup_logshake_mocks);
+
+add_test(function test_logShake_captureLogs_waits_to_read() {
+ // Enable LogShake
+ LogShake.init();
+
+ // Save no logs synchronously (except properties)
+ LogShake.LOGS_WITH_PARSERS = {};
+
+ LogShake.captureLogs().then(logResults => {
+ LogShake.uninit();
+
+ ok(logResults.logFilenames.length > 0, "Should have filenames");
+ ok(logResults.logPaths.length > 0, "Should have paths");
+ ok(!logResults.compressed, "Should not be compressed");
+
+ // This assumes that the about:memory reading will only fail under abnormal
+ // circumstances. It does not check for screenshot.png because
+ // systemAppFrame is unavailable during xpcshell tests.
+ let hasAboutMemory = false;
+
+ logResults.logFilenames.forEach(filename => {
+ // Because the about:memory log's filename has the PID in it we can not
+ // use simple equality but instead search for the "about_memory" part of
+ // the filename which will look like logshake-about_memory-{PID}.json.gz
+ if (filename.indexOf("about_memory") < 0) {
+ return;
+ }
+ hasAboutMemory = true;
+ });
+
+ ok(hasAboutMemory,
+ "LogShake's asynchronous read of about:memory should have succeeded.");
+
+ run_next_test();
+ },
+ error => {
+ LogShake.uninit();
+
+ ok(false, "Should not have received error: " + error);
+
+ run_next_test();
+ });
+});
diff --git a/b2g/components/test/unit/test_signintowebsite.js b/b2g/components/test/unit/test_signintowebsite.js
new file mode 100644
index 0000000000..38d4fa79e2
--- /dev/null
+++ b/b2g/components/test/unit/test_signintowebsite.js
@@ -0,0 +1,322 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Tests for b2g/components/SignInToWebsite.jsm
+
+"use strict";
+
+XPCOMUtils.defineLazyModuleGetter(this, "MinimalIDService",
+ "resource://gre/modules/identity/MinimalIdentity.jsm",
+ "IdentityService");
+
+XPCOMUtils.defineLazyModuleGetter(this, "SignInToWebsiteController",
+ "resource://gre/modules/SignInToWebsite.jsm",
+ "SignInToWebsiteController");
+
+Cu.import("resource://gre/modules/identity/LogUtils.jsm");
+
+function log(...aMessageArgs) {
+ Logger.log.apply(Logger, ["test_signintowebsite"].concat(aMessageArgs));
+}
+
+function test_overall() {
+ do_check_neq(MinimalIDService, null);
+ run_next_test();
+}
+
+function objectContains(object, subset) {
+ let objectKeys = Object.keys(object);
+ let subsetKeys = Object.keys(subset);
+
+ // can't have fewer keys than the subset
+ if (objectKeys.length < subsetKeys.length) {
+ return false;
+ }
+
+ let key;
+ let success = true;
+ if (subsetKeys.length > 0) {
+ for (let i=0; i<subsetKeys.length; i++) {
+ key = subsetKeys[i];
+
+ // key exists in the source object
+ if (typeof object[key] === 'undefined') {
+ success = false;
+ break;
+ }
+
+ // recursively check object values
+ else if (typeof subset[key] === 'object') {
+ if (typeof object[key] !== 'object') {
+ success = false;
+ break;
+ }
+ if (! objectContains(object[key], subset[key])) {
+ success = false;
+ break;
+ }
+ }
+
+ else if (object[key] !== subset[key]) {
+ success = false;
+ break;
+ }
+ }
+ }
+
+ return success;
+}
+
+function test_object_contains() {
+ do_test_pending();
+
+ let someObj = {
+ pies: 42,
+ green: "spam",
+ flan: {yes: "please"}
+ };
+ let otherObj = {
+ pies: 42,
+ flan: {yes: "please"}
+ };
+ do_check_true(objectContains(someObj, otherObj));
+ do_test_finished();
+ run_next_test();
+}
+
+function test_mock_doc() {
+ do_test_pending();
+ let mockedDoc = mockDoc({loggedInUser: null}, function(action, params) {
+ do_check_eq(action, 'coffee');
+ do_test_finished();
+ run_next_test();
+ });
+
+ // A smoke test to ensure that mockedDoc is functioning correctly.
+ // There is presently no doCoffee method in Persona.
+ mockedDoc.doCoffee();
+}
+
+function test_watch() {
+ do_test_pending();
+
+ setup_test_identity("pie@food.gov", TEST_CERT, function() {
+ let controller = SignInToWebsiteController;
+
+ let mockedDoc = mockDoc({loggedInUser: null}, function(action, params) {
+ do_check_eq(action, 'ready');
+ controller.uninit();
+ MinimalIDService.RP.unwatch(mockedDoc.id);
+ do_test_finished();
+ run_next_test();
+ });
+
+ controller.init({pipe: mockReceivingPipe()});
+ MinimalIDService.RP.watch(mockedDoc, {});
+ });
+}
+
+function test_request_login() {
+ do_test_pending();
+
+ setup_test_identity("flan@food.gov", TEST_CERT, function() {
+ let controller = SignInToWebsiteController;
+
+ let mockedDoc = mockDoc({loggedInUser: null}, call_sequentially(
+ function(action, params) {
+ do_check_eq(action, 'ready');
+ do_check_eq(params, undefined);
+ },
+ function(action, params) {
+ do_check_eq(action, 'login');
+ do_check_eq(params, TEST_CERT);
+ controller.uninit();
+ MinimalIDService.RP.unwatch(mockedDoc.id);
+ do_test_finished();
+ run_next_test();
+ }
+ ));
+
+ controller.init({pipe: mockReceivingPipe()});
+ MinimalIDService.RP.watch(mockedDoc, {});
+ MinimalIDService.RP.request(mockedDoc.id, {});
+ });
+}
+
+function test_request_logout() {
+ do_test_pending();
+
+ setup_test_identity("flan@food.gov", TEST_CERT, function() {
+ let controller = SignInToWebsiteController;
+
+ let mockedDoc = mockDoc({loggedInUser: null}, call_sequentially(
+ function(action, params) {
+ do_check_eq(action, 'ready');
+ do_check_eq(params, undefined);
+ },
+ function(action, params) {
+ do_check_eq(action, 'logout');
+ do_check_eq(params, undefined);
+ controller.uninit();
+ MinimalIDService.RP.unwatch(mockedDoc.id);
+ do_test_finished();
+ run_next_test();
+ }
+ ));
+
+ controller.init({pipe: mockReceivingPipe()});
+ MinimalIDService.RP.watch(mockedDoc, {});
+ MinimalIDService.RP.logout(mockedDoc.id, {});
+ });
+}
+
+function test_request_login_logout() {
+ do_test_pending();
+
+ setup_test_identity("unagi@food.gov", TEST_CERT, function() {
+ let controller = SignInToWebsiteController;
+
+ let mockedDoc = mockDoc({loggedInUser: null}, call_sequentially(
+ function(action, params) {
+ do_check_eq(action, 'ready');
+ do_check_eq(params, undefined);
+ },
+ function(action, params) {
+ do_check_eq(action, 'login');
+ do_check_eq(params, TEST_CERT);
+ },
+ function(action, params) {
+ do_check_eq(action, 'logout');
+ do_check_eq(params, undefined);
+ controller.uninit();
+ MinimalIDService.RP.unwatch(mockedDoc.id);
+ do_test_finished();
+ run_next_test();
+ }
+ ));
+
+ controller.init({pipe: mockReceivingPipe()});
+ MinimalIDService.RP.watch(mockedDoc, {});
+ MinimalIDService.RP.request(mockedDoc.id, {});
+ MinimalIDService.RP.logout(mockedDoc.id, {});
+ });
+}
+
+function test_logout_everywhere() {
+ do_test_pending();
+ let logouts = 0;
+
+ setup_test_identity("fugu@food.gov", TEST_CERT, function() {
+ let controller = SignInToWebsiteController;
+
+ let mockedDoc1 = mockDoc({loggedInUser: null}, call_sequentially(
+ function(action, params) {
+ do_check_eq(action, 'ready');
+ },
+ function(action, params) {
+ do_check_eq(action, 'login');
+ },
+ function(action, params) {
+ // Result of logout from doc2.
+ // We don't know what order the logouts will occur in.
+ do_check_eq(action, 'logout');
+ if (++logouts === 2) {
+ do_test_finished();
+ run_next_test();
+ }
+ }
+ ));
+
+ let mockedDoc2 = mockDoc({loggedInUser: null}, call_sequentially(
+ function(action, params) {
+ do_check_eq(action, 'ready');
+ },
+ function(action, params) {
+ do_check_eq(action, 'login');
+ },
+ function(action, params) {
+ do_check_eq(action, 'logout');
+ if (++logouts === 2) {
+ do_test_finished();
+ run_next_test();
+ }
+ }
+ ));
+
+ controller.init({pipe: mockReceivingPipe()});
+ MinimalIDService.RP.watch(mockedDoc1, {});
+ MinimalIDService.RP.request(mockedDoc1.id, {});
+
+ MinimalIDService.RP.watch(mockedDoc2, {});
+ MinimalIDService.RP.request(mockedDoc2.id, {});
+
+ // Logs out of both docs because they share the
+ // same origin.
+ MinimalIDService.RP.logout(mockedDoc2.id, {});
+ });
+}
+
+function test_options_pass_through() {
+ do_test_pending();
+
+ // An meaningless structure for testing that RP messages preserve
+ // objects and their parameters as they are passed back and forth.
+ let randomMixedParams = {
+ loggedInUser: "juanita@mozilla.com",
+ forceAuthentication: true,
+ forceIssuer: "foo.com",
+ someThing: {
+ name: "Pertelote",
+ legs: 4,
+ nested: {bee: "Eric", remaining: "1/2"}
+ }
+ };
+
+ let mockedDoc = mockDoc(randomMixedParams, function(action, params) {});
+
+ function pipeOtherEnd(rpOptions, gaiaOptions) {
+ // Ensure that every time we receive a message, our mixed
+ // random params are contained in that message
+ do_check_true(objectContains(rpOptions, randomMixedParams));
+
+ switch (gaiaOptions.message) {
+ case "identity-delegate-watch":
+ MinimalIDService.RP.request(mockedDoc.id, {});
+ break;
+ case "identity-delegate-request":
+ MinimalIDService.RP.logout(mockedDoc.id, {});
+ break;
+ case "identity-delegate-logout":
+ do_test_finished();
+ controller.uninit();
+ MinimalIDService.RP.unwatch(mockedDoc.id);
+ run_next_test();
+ break;
+ }
+ }
+
+ let controller = SignInToWebsiteController;
+ controller.init({pipe: mockSendingPipe(pipeOtherEnd)});
+
+ MinimalIDService.RP.watch(mockedDoc, {});
+}
+
+var TESTS = [
+ test_overall,
+ test_mock_doc,
+ test_object_contains,
+
+ test_watch,
+ test_request_login,
+ test_request_logout,
+ test_request_login_logout,
+ test_logout_everywhere,
+
+ test_options_pass_through
+];
+
+TESTS.forEach(add_test);
+
+function run_test() {
+ run_next_test();
+}
diff --git a/b2g/components/test/unit/xpcshell.ini b/b2g/components/test/unit/xpcshell.ini
new file mode 100644
index 0000000000..ca3df5bf61
--- /dev/null
+++ b/b2g/components/test/unit/xpcshell.ini
@@ -0,0 +1,49 @@
+[DEFAULT]
+head =
+tail =
+
+support-files =
+ data/test_logger_file
+
+[test_bug793310.js]
+
+[test_bug832946.js]
+
+[test_fxaccounts.js]
+[test_signintowebsite.js]
+head = head_identity.js
+tail =
+
+# testing non gonk-specific stuff
+[test_logcapture.js]
+
+[test_logcapture_gonk.js]
+# can be slow because of what the test does, so let's give it some more time
+# to avoid intermittents: bug 1212395
+requesttimeoutfactor = 2
+# only run on b2g builds due to requiring b2g-specific log files to exist
+skip-if = toolkit != "gonk"
+
+[test_logparser.js]
+
+[test_logshake.js]
+
+[test_logshake_gonk.js]
+# can be slow because of what the test does, so let's give it some more time
+# to avoid intermittents: bug 1144499
+requesttimeoutfactor = 2
+head = head_logshake_gonk.js
+# only run on b2g builds due to requiring b2g-specific log files to exist
+skip-if = (toolkit != "gonk")
+
+[test_logshake_gonk_compression.js]
+head = head_logshake_gonk.js
+# only run on b2g builds due to requiring b2g-specific log files to exist
+skip-if = (toolkit != "gonk")
+
+[test_logshake_readLog_gonk.js]
+head = head_logshake_gonk.js
+# only run on b2g builds due to requiring b2g-specific log files to exist
+skip-if = (toolkit != "gonk")
+
+[test_aboutserviceworkers.js]
diff --git a/b2g/config/aries/config.json b/b2g/config/aries/config.json
new file mode 100644
index 0000000000..1902a30001
--- /dev/null
+++ b/b2g/config/aries/config.json
@@ -0,0 +1,52 @@
+{
+ "config_version": 2,
+ "tooltool_manifest": "releng-aries.manifest",
+ "mock_target": "mozilla-centos6-x86_64",
+ "mock_packages": ["ccache", "make", "bison", "flex", "gcc", "g++", "mpfr", "zlib-devel", "ncurses-devel", "zip", "autoconf213", "glibc-static", "perl-Digest-SHA", "wget", "alsa-lib", "atk", "cairo", "dbus-glib", "fontconfig", "freetype", "glib2", "gtk2", "libXRender", "libXt", "pango", "mozilla-python27-mercurial", "openssh-clients", "nss-devel", "glibc-devel.i686", "libstdc++.i686", "zlib-devel.i686", "ncurses-devel.i686", "libX11-devel.i686", "mesa-libGL-devel.i686", "mesa-libGL-devel", "libX11-devel", "git", "libxml2", "dosfstools"],
+ "mock_files": [
+ ["/home/cltbld/.ssh", "/home/mock_mozilla/.ssh"],
+ ["/builds/crash-stats-api.token", "/builds/crash-stats-api.token"]
+ ],
+ "build_targets": ["", "blobfree"],
+ "upload_files": [
+ "{objdir}/dist/b2g-*.crashreporter-symbols.zip",
+ "{objdir}/dist/b2g-*.tar.gz",
+ "{workdir}/sources.xml",
+ "{workdir}/out/target/product/aries/fota-*-update-*.mar"
+ ],
+ "public_upload_files": [
+ "{objdir}/dist/b2g-*.crashreporter-symbols.zip",
+ "{objdir}/dist/b2g-*.tar.gz",
+ "{workdir}/sources.xml",
+ "{objdir}/dist/b2g-update/*.mar",
+ "{workdir}/out/target/product/aries/fota-*-update.mar"
+ ],
+ "zip_files": [
+ ["{workdir}/out/target/product/aries/*.img", "out/target/product/aries/"],
+ "{workdir}/flash.sh",
+ "{workdir}/gecko/b2g/installer/flash.bat",
+ "{workdir}/prebuilts/misc/windows/win_flash_tools/*",
+ "{workdir}/load-config.sh",
+ "{workdir}/.config",
+ "{workdir}/sources.xml",
+ "{workdir}/profile.sh",
+ ["{workdir}/gecko/tools/profiler/merge-profiles.py", "gecko/tools/profiler/"],
+ ["{workdir}/scripts/profile-symbolicate.py", "scripts/"],
+ ["{workdir}/gecko/tools/rb/fix_stack_using_bpsyms.py", "gecko/tools/rb/"]
+ ],
+ "env": {
+ "VARIANT": "user",
+ "MOZILLA_OFFICIAL": "1",
+ "MOZ_TELEMETRY_REPORTING": "1",
+ "GAIA_KEYBOARD_LAYOUTS": "en,pt-BR,es,de,fr,pl,zh-Hans-Pinyin,zh-Hant-Zhuyin,en-Dvorak"
+ },
+ "b2g_manifest": "aries.xml",
+ "b2g_manifest_intree": true,
+ "gecko_l10n_root": "https://hg.mozilla.org/l10n-central",
+ "gaia": {
+ "l10n": {
+ "vcs": "hg",
+ "root": "https://hg.mozilla.org/gaia-l10n"
+ }
+ }
+}
diff --git a/b2g/config/aries/releng-aries.manifest b/b2g/config/aries/releng-aries.manifest
new file mode 100644
index 0000000000..a1276ffe34
--- /dev/null
+++ b/b2g/config/aries/releng-aries.manifest
@@ -0,0 +1,27 @@
+[
+{
+"version": "Android NDK r11b for B2G",
+"algorithm": "sha512",
+"visibility": "internal",
+"filename": "android-ndk-b2g.tar.xz",
+"unpack": true,
+"digest": "bc37c6b2e38f4ff19e3326786312d8f893600e155d35dfba45163bd909e022db852b9c6920863cb498bbe7da8b86a6a387fa024bc9444ce3a8d1715cf2c24b21",
+"size": 292442020
+},
+{
+"algorithm": "sha512",
+"visibility": "internal",
+"filename": "backup-aries_23.0.1.A.5.77.tar.xz",
+"unpack": true,
+"digest": "79c8e390e88cc4765ff7f5f29f3d5337c9037b7eb9414006947d38d34acefdbcf7090c18a366948c682b1c2c9d9ef51012e7be44daa28fdde7b837ade647c257",
+"size": 227555180
+},
+{
+"version": "gcc 4.8.5 + PR64905",
+"size": 80160264,
+"digest": "c1a9dc9da289b8528874d16300b9d13a997cec99195bb0bc46ff665216d8535d6d6cb5af6b4b1f2749af6815dab12e703fdb3849014e5c23a70eff351a0baf4e",
+"algorithm": "sha512",
+"filename": "gcc.tar.xz",
+"unpack": "True"
+}
+]
diff --git a/b2g/config/aries/sources.xml b/b2g/config/aries/sources.xml
new file mode 100644
index 0000000000..0ed0a67a60
--- /dev/null
+++ b/b2g/config/aries/sources.xml
@@ -0,0 +1,162 @@
+<?xml version="1.0" ?><manifest>
+ <!--
+ Remotes
+ -->
+ <remote fetch="git://github.com/apitrace/" name="apitrace"/>
+ <remote fetch="git://github.com/mozilla-b2g/" name="b2g"/>
+ <remote fetch="git://codeaurora.org/" name="caf"/>
+ <!--
+ B2G repositories for all targets
+ -->
+ <project name="gaia" path="gaia" remote="b2g" revision="f5d355dfeb559e11fad48901b0bea287ec33b874"/>
+ <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="99003a6e7ecee880330a3fb8b5e49fefdb762374"/>
+ <project name="moztt" path="external/moztt" remote="b2g" revision="99c333dab00ed79baff9e1cf76b320aee8e1c123"/>
+ <project name="platform_hardware_libhardware_moz" path="hardware/libhardware_moz" remote="b2g" revision="fdf3a143dc777e5f9d33a88373af7ea161d3b440"/>
+ <project name="platform_system_libfdio" path="system/libfdio" remote="b2g" revision="34adfb400e031f3dd3353d92413572db5e3a7376"/>
+ <project name="platform_system_libpdu" path="system/libpdu" remote="b2g" revision="f1a61fa8f97cc0a1ac4eca160acc222981b21d90"/>
+ <project name="platform_system_sensorsd" path="system/sensorsd" remote="b2g" revision="3618678c472320de386f5ddc27897992d0e148a8"/>
+ <!-- B2G specific things. -->
+ <project name="platform_build" path="build" remote="b2g" revision="964d9fa4eabe9eb473ef9101ca2a690880f28547">
+ <copyfile dest="Makefile" src="core/root.mk"/>
+ </project>
+ <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
+ <project name="fake-qemu-kernel" path="prebuilts/qemu-kernel" remote="b2g" revision="939b377d55a2f081d94029a30a75d05e5a20daf3"/>
+ <project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
+ <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
+ <project name="valgrind" path="external/valgrind" remote="b2g" revision="5f931350fbc87c3df9db8b0ceb37734b8b471593"/>
+ <project name="vex" path="external/VEX" remote="b2g" revision="48d8c7c950745f1b166b42125e6f0d3293d71636"/>
+ <project name="apitrace" path="external/apitrace" remote="apitrace" revision="7c5a77c651bcde37005e6b6e209747edcc6c9361"/>
+ <!-- Stock Android things -->
+ <project groups="linux" name="platform/prebuilts/gcc/linux-x86/host/i686-linux-glibc2.7-4.6" path="prebuilts/gcc/linux-x86/host/i686-linux-glibc2.7-4.6" revision="95bb5b66b3ec5769c3de8d3f25d681787418e7d2"/>
+ <project groups="linux" name="platform/prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.7-4.6" path="prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.7-4.6" revision="ebdad82e61c16772f6cd47e9f11936bf6ebe9aa0"/>
+ <project groups="linux,arm" name="platform/prebuilts/gcc/linux-x86/arm/arm-eabi-4.7" path="prebuilts/gcc/linux-x86/arm/arm-eabi-4.7" revision="8b880805d454664b3eed11d0f053cdeafa1ff06e"/>
+ <project groups="linux,arm" name="platform/prebuilts/gcc/linux-x86/arm/arm-linux-androideabi-4.7" path="prebuilts/gcc/linux-x86/arm/arm-linux-androideabi-4.7" revision="a1e239a0bb5cd1d69680bf1075883aa9a7bf2429"/>
+ <project groups="linux,arm" name="platform/prebuilts/gcc/linux-x86/arm/arm-linux-androideabi-4.9" path="prebuilts/gcc/linux-x86/arm/arm-linux-androideabi-4.9" remote="caf" revision="c09e1b3a55153d1ba142d5bf548c90487ea71f9e"/>
+ <project groups="linux,x86" name="platform/prebuilts/gcc/linux-x86/x86/i686-linux-android-4.7" path="prebuilts/gcc/linux-x86/x86/i686-linux-android-4.7" revision="c7931763d41be602407ed9d71e2c0292c6597e00"/>
+ <project groups="linux,x86" name="platform/prebuilts/gcc/linux-x86/x86/x86_64-linux-android-4.9" path="prebuilts/gcc/linux-x86/x86/x86_64-linux-android-4.9" remote="caf" revision="c33513f9de95fcdf4ec832db5e3ebd612382f541"/>
+ <project groups="linux,x86" name="platform/prebuilts/python/linux-x86/2.7.5" path="prebuilts/python/linux-x86/2.7.5" revision="a32003194f707f66a2d8cdb913ed1869f1926c5d"/>
+ <project name="device/common" path="device/common" revision="96d4d2006c4fcb2f19a3fa47ab10cb409faa017b"/>
+ <project name="device/sample" path="device/sample" revision="9c19bbbe0793ebdc427277dc37f9bb4ae20bccb2"/>
+ <project name="platform/abi/cpp" path="abi/cpp" revision="18f1b5e28734183ff8073fe86dc46bc4ebba8a59"/>
+ <project name="platform/bootable/recovery" path="bootable/recovery" revision="dc8da21282a0c68328d05268433d19b0f2f6a15c"/>
+ <project name="platform/external/aac" path="external/aac" revision="fa3eba16446cc8f2f5e2dfc20d86a49dbd37299e"/>
+ <project name="platform/external/bison" path="external/bison" revision="c2418b886165add7f5a31fc5609f0ce2d004a90e"/>
+ <project name="platform/external/bsdiff" path="external/bsdiff" revision="23e322ab19fb7d74c2c37e40ce364d9f709bdcee"/>
+ <project name="platform/external/bzip2" path="external/bzip2" revision="1cb636bd8e9e5cdfd5d5b2909a122f6e80db62de"/>
+ <project name="platform/external/checkpolicy" path="external/checkpolicy" revision="0d73ef7049feee794f14cf1af88d05dae8139914"/>
+ <project name="platform/external/dhcpcd" path="external/dhcpcd" revision="84b7252b0a9d0edc9a1db1e0c518771d26b23058"/>
+ <project name="platform/external/dnsmasq" path="external/dnsmasq" revision="a0f273e289166fa488bba261b2d431c9503aed0d"/>
+ <project name="platform/external/dropbear" path="external/dropbear" revision="a34ddbe3819bc465968f3676c734b405e655f8b7"/>
+ <project name="platform/external/e2fsprogs" path="external/e2fsprogs" revision="164ce36fe92b78b92575e56abd101209631b48fc"/>
+ <project name="platform/external/elfutils" path="external/elfutils" revision="b23b2dfb354b3ccf5d1c5d39815f02e7048cf516"/>
+ <project name="platform/external/expat" path="external/expat" revision="0af0cb3bc7519e567bd9daff3dcd315ab0134a99"/>
+ <project name="platform/external/fdlibm" path="external/fdlibm" revision="0da5f683c9ddc9442af3b389b4220e91ccffb320"/>
+ <project name="platform/external/flac" path="external/flac" revision="b38b3d2c53c22f542fd764f9d60ef19b49d42e1b"/>
+ <project name="platform/external/freetype" path="external/freetype" revision="899c67b6cfcd2010784fbf08c5415af16c526e0c"/>
+ <project name="platform/external/gcc-demangle" path="external/gcc-demangle" revision="9241386b62c353302c2f9eccda0672685b252b4d"/>
+ <project name="platform/external/genext2fs" path="external/genext2fs" revision="e11a9c7fe6f1cef99aad2f25afaea37b72fe9f93"/>
+ <project name="platform/external/giflib" path="external/giflib" revision="9aef3ea079a57c98a9207f8c3b95a5dc08ee74b5"/>
+ <project name="platform/external/gtest" path="external/gtest" revision="0f1ce3dd0b880b6ae0cf7f5413702b8ef542efb2"/>
+ <project name="platform/external/harfbuzz" path="external/harfbuzz" revision="858f2d28ac741ef139f74bdbdbcefa7560f17c91"/>
+ <project name="platform/external/harfbuzz_ng" path="external/harfbuzz_ng" revision="3309edccdbc2a92eb03a285abb27c1c1c4a88e43"/>
+ <project name="platform/external/iproute2" path="external/iproute2" revision="1778c5571f3b9ed213a03ecc80adf74f570b4916"/>
+ <project name="platform/external/ipsec-tools" path="external/ipsec-tools" revision="f4cb1ee4b00abbfb6f968dc25818c23b4b47e584"/>
+ <project name="platform/external/iptables" path="external/iptables" revision="ceedcd308d47976e31eda76a8852edd7f92837de"/>
+ <project name="platform/external/jack" path="external/jack" revision="5ceb2025ac5d25ed48183ac2d3dac4691fe761fb"/>
+ <project name="platform/external/jhead" path="external/jhead" revision="31b17e69a87e4caa50f9c6b1a47c84ef75f79d83"/>
+ <project name="platform/external/jpeg" path="external/jpeg" revision="1808bfd1060d2fde90478691a4da8ead0cb0a345"/>
+ <project name="platform/external/junit" path="external/junit" revision="01da89f7f8a8f9852e0ec1a490e7d2a0ee3785d5"/>
+ <project name="platform/external/libgsm" path="external/libgsm" revision="50761abed8f4734970874165b386cfd4d9599db4"/>
+ <project name="platform/external/liblzf" path="external/liblzf" revision="6946aa575b0949d045722794850896099d937cbb"/>
+ <project name="platform/external/libnfc-nxp" path="external/libnfc-nxp" revision="9e987ccb716624d658f98abc7db2097e11e3d8ed"/>
+ <project name="platform/external/libnl-headers" path="external/libnl-headers" revision="6ccf7349d61f73ac26a0675d735d903ab919c658"/>
+ <project name="platform/external/libogg" path="external/libogg" revision="ec0b24fb1468abe37be4164a6feb16568e036bde"/>
+ <project name="platform/external/libpcap" path="external/libpcap" revision="3a7bce5dda6a8db92c9248846d0255e68c3a5b2a"/>
+ <project name="platform/external/libpng" path="external/libpng" revision="cdf9c7fe4febaa4a7b3917d56d4180960e48800f"/>
+ <project name="platform/external/libselinux" path="external/libselinux" revision="1e2cf2c4a2d15a9b1ca2d353b99fb6884413ffe1"/>
+ <project name="platform/external/libsepol" path="external/libsepol" revision="edc447a138ec77236f1cbfd36c1211a38ba21418"/>
+ <project name="platform/external/libvpx" path="external/libvpx" revision="ca9281af0bfe816f1ae2fc3e8771524164a0a03c"/>
+ <project name="platform/external/mdnsresponder" path="external/mdnsresponder" remote="caf" revision="dd17df3f6775c4366a5c1d21865370d60a3b1295"/>
+ <project name="platform/external/mksh" path="external/mksh" revision="f8c396c4d446a038358106a301b329607a04633d"/>
+ <project name="platform/external/netcat" path="external/netcat" revision="444644cfa9a2f3002863caa168fb2d6b34dfd1e8"/>
+ <project name="platform/external/openssl" path="external/openssl" revision="bb8428f762b3632f493572c4f73957e1281ade79"/>
+ <project name="platform/external/protobuf" path="external/protobuf" revision="48ee66d295979372ed0234cefda42385daae8312"/>
+ <project name="platform/external/safe-iop" path="external/safe-iop" revision="aa0725fb1da35e47676b6da30009322eb5ed59be"/>
+ <project name="platform/external/scrypt" path="external/scrypt" revision="eb05b73c3bba21fff55529813109de4bad5ddbd1"/>
+ <project name="platform/external/sepolicy" path="external/sepolicy" revision="6f50b84072d4920ce331226837ba794be72ea2b1"/>
+ <project name="platform/external/sfntly" path="external/sfntly" revision="30d4e1f3d81ad9f7a1aa14ce6d2ceb5df56c15cd"/>
+ <project name="platform/external/skia" path="external/skia" revision="84a7058ba19c80ababd8c7c9eb379bf0babcc9ce"/>
+ <project name="platform/external/sonivox" path="external/sonivox" revision="9fb2c53165b1512aa57db0bf1c757e3215e28eb8"/>
+ <project name="platform/external/speex" path="external/speex" revision="fb7db5853ffb841a4d32fea8b5c3a43e6b875cae"/>
+ <project name="platform/external/sqlite" path="external/sqlite" revision="ac0e0d5f866fbce0ebf00d0ddd615464849aa83b"/>
+ <project name="platform/external/stlport" path="external/stlport" revision="628e14d37c5b239839a466e81c74bf66255b770b"/>
+ <project name="platform/external/strace" path="external/strace" revision="1a4e05d53dec658a061acb9869cb1eb1342cd09d"/>
+ <project name="platform/external/svox" path="external/svox" revision="b3c3bf3c1be35f3d455671de9f6d7b9bca3ce73a"/>
+ <project name="platform/external/tagsoup" path="external/tagsoup" revision="68c2ec9e0acdb3214b7fb91dbab8c9fab8736817"/>
+ <project name="platform/external/tcpdump" path="external/tcpdump" revision="841663c88f0692202d57def239267e28cf832cdc"/>
+ <project name="platform/external/tinyalsa" path="external/tinyalsa" revision="c1b682efcd3b3eac3101408231b58ea92c668756"/>
+ <project name="platform/external/tinycompress" path="external/tinycompress" revision="a85e245a09c028d36cbf04f233be10bc583691f5"/>
+ <project name="platform/external/tinyxml" path="external/tinyxml" revision="494e448824844d866e805831d1d5f5acb654065c"/>
+ <project name="platform/external/tinyxml2" path="external/tinyxml2" revision="ead7a211773b9366466c6512cf945bc9dd1490a5"/>
+ <project name="platform/external/tremolo" path="external/tremolo" revision="78772d5dde5a06eefae281b0dde224fcac46c4ff"/>
+ <project name="platform/external/webp" path="external/webp" revision="513e97bd307573e2adc776eb5368bd129aceaa4a"/>
+ <project name="platform/external/webrtc" path="external/webrtc" revision="446452f84e9cc4c75d8e80f6f05e24793397a19d"/>
+ <project name="platform/external/yaffs2" path="external/yaffs2" revision="a2cff2275e1b501ff478b03757d6e4f05fddc2db"/>
+ <project name="platform/external/zlib" path="external/zlib" revision="6eb3570ff8fa71bd83bb375b4bf09804c6089fed"/>
+ <project name="platform/frameworks/opt/emoji" path="frameworks/opt/emoji" revision="dbbe673145107e99883f62bafd70c5f43f11065c"/>
+ <project name="platform/frameworks/wilhelm" path="frameworks/wilhelm" revision="f0c3b4edf597c40aae4ea311575f39c8bcf203df"/>
+ <project name="platform/libcore" path="libcore" revision="baf7d8068dd501cfa338d3a8b1b87216d6ce0571"/>
+ <project name="platform/libnativehelper" path="libnativehelper" revision="50c4430e32849530ced32680fd6ee98963b3f7ac"/>
+ <project name="platform/ndk" path="ndk" revision="e58ef003be4306bb53a8c11331146f39e4eab31f"/>
+ <project name="platform_prebuilts_misc" path="prebuilts/misc" remote="b2g" revision="c739cffa6394c06e099ea48879a20341b6163338"/>
+ <project name="platform/prebuilts/ndk" path="prebuilts/ndk" revision="c792f0bd9fff7aea2887c60bbb3a9bbdb534ffa3"/>
+ <project name="platform/prebuilts/sdk" path="prebuilts/sdk" revision="69d524e80cdf3981006627c65ac85f3a871238a3"/>
+ <project name="platform/prebuilts/tools" path="prebuilts/tools" revision="5a48c04c4bb5f079bc757e29864a42427378e051"/>
+ <project name="platform_system_bluetoothd" path="system/bluetoothd" remote="b2g" revision="0a5aa7225129b792c19e91be88eac97a89c089b6"/>
+ <project name="platform/system/extras" path="system/extras" revision="576f57b6510de59c08568b53c0fb60588be8689e"/>
+ <project name="platform/system/netd" path="system/netd" revision="a6531f7befb49b1c81bc0de7e51c5482b308e1c5"/>
+ <project name="platform/system/security" path="system/security" revision="ee8068b9e7bfb2770635062fc9c2035be2142bd8"/>
+ <project name="platform/system/vold" path="system/vold" revision="42fa2a0f14f965970a4b629a176bbd2666edf017"/>
+ <project name="platform/external/curl" path="external/curl" revision="e68addd988448959ea8157c5de637346b4180c33"/>
+ <project name="platform/external/icu4c" path="external/icu4c" revision="d3ec7428eb276db43b7ed0544e09344a6014806c"/>
+ <project name="platform/hardware/libhardware_legacy" path="hardware/libhardware_legacy" revision="76c4bf4bc430a1b8317f2f21ef735867733e50cc"/>
+ <project name="platform/system/media" path="system/media" revision="c1332c21c608f4932a6d7e83450411cde53315ef"/>
+ <default remote="caf" revision="LNX.LA.3.5.2.1.1" sync-j="4"/>
+ <project groups="linux,arm" name="platform/prebuilts/gcc/linux-x86/arm/arm-eabi-4.8" path="prebuilts/gcc/linux-x86/arm/arm-eabi-4.8" revision="26e93f6af47f7bd3a9beb5c102a5f45e19bfa38a"/>
+ <project groups="linux,arm" name="platform/prebuilts/gcc/linux-x86/arm/arm-linux-androideabi-4.8" path="prebuilts/gcc/linux-x86/arm/arm-linux-androideabi-4.8" revision="d9735fc81434f2af2c44d86ca57740c673c8d9bc"/>
+ <!-- Platform common things -->
+ <project name="device-shinano-common" path="device/sony/shinano-common" remote="b2g" revision="53dcf88a4314c05dae3464f7647138ad420398e6"/>
+ <project name="device/generic/armv7-a-neon" path="device/generic/armv7-a-neon" revision="1bb28abbc215f45220620af5cd60a8ac1be93722"/>
+ <project name="device/qcom/common" path="device/qcom/common" revision="2501e5940ba69ece7654ff85611c76ae5bda299c"/>
+ <project name="codeaurora_kernel_msm" path="kernel" remote="b2g" revision="559bc18796a7b97b7619ebf30042a09c3ae0398f"/>
+ <project name="init_sh" path="external/init_sh" remote="b2g" revision="feb58d2b397e45ead9b904d5c4d9255df408db56"/>
+ <project name="platform_bionic" path="bionic" remote="b2g" revision="3e85c4683c121530c1c3a48c696a569bf5f587e2"/>
+ <project name="platform_external_bluetooth_bluedroid" path="external/bluetooth/bluedroid" remote="b2g" revision="70f536bd97d901b96b94669ae1aa2fd0fb54b258"/>
+ <project name="platform_external_libnfc-nci" path="external/libnfc-nci" remote="b2g" revision="2a52bd77d2ca0cefbf02acc6863492d16b6ccfec"/>
+ <project name="platform_external_libnfc-pn547" path="external/libnfc-pn547" remote="b2g" revision="5bb999b84b8adc14f6bea004d523ba258dea8188"/>
+ <project name="platform/external/wpa_supplicant_8" path="external/wpa_supplicant_8" revision="5b71e40213f650459e95d35b6f14af7e88d8ab62"/>
+ <project name="platform_frameworks_av" path="frameworks/av" remote="b2g" revision="5d5bcc83d6c32874701f0df78ed1119e006bd10a"/>
+ <project name="platform/frameworks/base" path="frameworks/base" revision="da8e6bc53c8bc669da0bb627904d08aa293f2497"/>
+ <project name="platform/frameworks/native" path="frameworks/native" revision="a46a9f1ac0ed5662d614c277cbb14eb3f332f365"/>
+ <project name="platform/hardware/libhardware" path="hardware/libhardware" revision="7196881a0e9dd7bfbbcf0af64c8064e70f0fa094"/>
+ <project name="platform/hardware/broadcom/wlan" path="hardware/broadcom/wlan" revision="15a9b66de9b7d84c7ea63df3a834f095bca9e493"/>
+ <project name="platform/hardware/qcom/audio" path="hardware/qcom/audio" revision="8d7676dfb68ee0cd069affedd5d1e97316a184ba"/>
+ <project name="platform/hardware/qcom/camera" path="hardware/qcom/camera" revision="2a1ded216a91bf62a72b1640cf01ab4998f37028"/>
+ <project name="hardware_qcom_display" path="hardware/qcom/display" remote="b2g" revision="4a83e04c3fecffbcab75cd59bad2ae5f342778b7"/>
+ <project name="platform/hardware/qcom/gps" path="hardware/qcom/gps" revision="9883ea57b0668d8f60dba025d4522dfa69a1fbfa"/>
+ <project name="platform/hardware/qcom/media" path="hardware/qcom/media" revision="a558dc844bf5144fc38603fd8f4df8d9557052a5"/>
+ <project name="platform/hardware/qcom/wlan" path="hardware/qcom/wlan" revision="57ee1320ed7b4a1a1274d8f3f6c177cd6b9becb2"/>
+ <project name="platform/hardware/ril" path="hardware/ril" revision="12b1977cc704b35f2e9db2bb423fa405348bc2f3"/>
+ <project name="timekeep" path="hardware/sony/timekeep" remote="b2g" revision="460869402e019b122c4e5ffce19bfbbad026c0fe"/>
+ <project name="platform/system/bluetooth" path="system/bluetooth" revision="985bf15264d865fe7b9c5b45f61c451cbaafa43d"/>
+ <project name="platform/system/core" path="system/core" revision="42839aedcf70bf6bc92a3b7ea4a5cc9bf9aef3f9"/>
+ <project name="platform_system_nfcd" path="system/nfcd" remote="b2g" revision="5f4b68c799927b6e078f987b12722c3a6ccd4a45"/>
+ <project name="platform/system/qcom" path="system/qcom" revision="63e3f6f176caad587d42bba4c16b66d953fb23c2"/>
+ <project name="platform/vendor/qcom/copper" path="device/qcom/msm8974" revision="ec7bc1a26610922156d7d412b4d3de6b4adb93da"/>
+ <project name="vendor_broadcom_wlan" path="vendor/broadcom/wlan" remote="b2g" revision="114b9491a8a919687da4e22fbd89fab511d6d8d7"/>
+ <project name="nginx" path="external/nginx" remote="b2g" revision="584bf310e21510e56af7e9dccd779eb7671118ef"/>
+ <!-- Shinano specific things -->
+ <project name="device-shinano" path="device/sony/leo" remote="b2g" revision="653f7e1f093b948e40262fcb3c665c2b4976df74"/>
+ <!-- Aries specific things -->
+ <project name="device-aries" path="device/sony/aries" remote="b2g" revision="791b8af16ec5f65366aa7a05aa0068c65018e882"/>
+</manifest>
diff --git a/b2g/config/desktop/config.json b/b2g/config/desktop/config.json
new file mode 100644
index 0000000000..ced2efa338
--- /dev/null
+++ b/b2g/config/desktop/config.json
@@ -0,0 +1,8 @@
+{
+ "gaia": {
+ "l10n": {
+ "vcs": "hg",
+ "root": "https://hg.mozilla.org/gaia-l10n"
+ }
+ }
+}
diff --git a/b2g/config/gaia.json b/b2g/config/gaia.json
new file mode 100644
index 0000000000..23934073b2
--- /dev/null
+++ b/b2g/config/gaia.json
@@ -0,0 +1,7 @@
+{
+ "git": {
+ "git_revision": "60655051c0ae65a93da194ccffa7bd7a66800a60",
+ "remote": "https://github.com/mozilla-b2g/gaia.git",
+ "branch": "master"
+ }
+}
diff --git a/b2g/config/mozconfigs/common b/b2g/config/mozconfigs/common
new file mode 100644
index 0000000000..0ef93ae119
--- /dev/null
+++ b/b2g/config/mozconfigs/common
@@ -0,0 +1,24 @@
+# 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/.
+
+# This file is included at the top of all b2g mozconfigs
+
+. "$topsrcdir/build/mozconfig.common"
+
+# Normally, we'd set this unconditionally, but this file is also used
+# for local builds and there is no other mozconfig in this tree that
+# is included on device builds.
+if test -d $topsrcdir/gcc/bin; then
+ HOST_CC="$topsrcdir/gcc/bin/gcc"
+ HOST_CXX="$topsrcdir/gcc/bin/g++"
+ . "$topsrcdir/build/unix/mozconfig.stdcxx"
+fi
+
+# Allow overriding this from the environment, and don't
+# try to set it if it doesn't exist. As per above, this file is also
+# used for local builds, and we may need to override this for builds in
+# other environments.
+if test -z "$SOCORRO_SYMBOL_UPLOAD_TOKEN_FILE" -a -f /builds/crash-stats-api.token; then
+ export SOCORRO_SYMBOL_UPLOAD_TOKEN_FILE=/builds/crash-stats-api.token
+fi
diff --git a/b2g/config/mozconfigs/common.override b/b2g/config/mozconfigs/common.override
new file mode 100644
index 0000000000..abe73b4605
--- /dev/null
+++ b/b2g/config/mozconfigs/common.override
@@ -0,0 +1,8 @@
+# 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/.
+
+# This file is included at the bottom of all b2g mozconfigs
+
+. "$topsrcdir/build/mozconfig.common.override"
+. "$topsrcdir/build/mozconfig.cache"
diff --git a/b2g/config/mozconfigs/ics_armv7a_gecko/debug b/b2g/config/mozconfigs/ics_armv7a_gecko/debug
new file mode 100644
index 0000000000..1cacb8a755
--- /dev/null
+++ b/b2g/config/mozconfigs/ics_armv7a_gecko/debug
@@ -0,0 +1,20 @@
+. "$topsrcdir/b2g/config/mozconfigs/common"
+
+mk_add_options MOZ_OBJDIR=@TOPSRCDIR@/obj-b2g
+
+ac_add_options --enable-application=b2g
+ac_add_options --enable-b2g-camera
+
+ac_add_options --target=arm-linux-androideabi
+ac_add_options --with-gonk="$topsrcdir/gonk-toolchain"
+export TOOLCHAIN_HOST=linux-x86
+export GONK_PRODUCT=generic
+ac_add_options --with-gonk-toolchain-prefix="$topsrcdir/gonk-toolchain/prebuilt/$TOOLCHAIN_HOST/toolchain/arm-linux-androideabi-4.4.x/bin/arm-linux-androideabi-"
+ac_add_options --enable-debug-symbols
+ac_add_options --enable-debug
+ENABLE_MARIONETTE=1
+
+# Enable dump() from JS.
+export CXXFLAGS="-DMOZ_ENABLE_JS_DUMP -include $topsrcdir/gonk-toolchain/gonk-misc/Unicode.h -include $topsrcdir/gonk-toolchain/system/vold/ResponseCode.h"
+
+. "$topsrcdir/b2g/config/mozconfigs/common.override"
diff --git a/b2g/config/mozconfigs/ics_armv7a_gecko/nightly b/b2g/config/mozconfigs/ics_armv7a_gecko/nightly
new file mode 100644
index 0000000000..c2f1ae0fa9
--- /dev/null
+++ b/b2g/config/mozconfigs/ics_armv7a_gecko/nightly
@@ -0,0 +1,21 @@
+. "$topsrcdir/b2g/config/mozconfigs/common"
+
+mk_add_options MOZ_OBJDIR=@TOPSRCDIR@/obj-b2g
+
+ac_add_options --enable-application=b2g
+ac_add_options --enable-b2g-camera
+ac_add_options --enable-updater
+
+ac_add_options --target=arm-linux-androideabi
+ac_add_options --with-gonk="$topsrcdir/gonk-toolchain"
+export TOOLCHAIN_HOST=linux-x86
+export GONK_PRODUCT=generic
+ac_add_options --with-gonk-toolchain-prefix="$topsrcdir/gonk-toolchain/prebuilt/$TOOLCHAIN_HOST/toolchain/arm-linux-androideabi-4.4.x/bin/arm-linux-androideabi-"
+ac_add_options --enable-debug-symbols
+# ac_add_options --enable-profiling
+ENABLE_MARIONETTE=1
+
+# Enable dump() from JS.
+export CXXFLAGS="-DMOZ_ENABLE_JS_DUMP -include $topsrcdir/gonk-toolchain/gonk-misc/Unicode.h -include $topsrcdir/gonk-toolchain/system/vold/ResponseCode.h"
+
+. "$topsrcdir/b2g/config/mozconfigs/common.override"
diff --git a/b2g/config/mozconfigs/linux32_gecko/debug b/b2g/config/mozconfigs/linux32_gecko/debug
new file mode 100644
index 0000000000..7ecbda3569
--- /dev/null
+++ b/b2g/config/mozconfigs/linux32_gecko/debug
@@ -0,0 +1,34 @@
+MOZ_AUTOMATION_L10N_CHECK=0
+MOZ_AUTOMATION_UPLOAD_SYMBOLS=0
+MOZ_AUTOMATION_UPDATE_PACKAGING=0
+. "$topsrcdir/b2g/config/mozconfigs/common"
+. "$topsrcdir/build/unix/mozconfig.linux32"
+
+ac_add_options --enable-update-channel=${MOZ_UPDATE_CHANNEL}
+ac_add_options --enable-signmar
+ac_add_options --enable-debug
+
+# This will overwrite the default of stripping everything and keep the symbol table.
+# This is useful for profiling and debugging and only increases the package size
+# by 2 MBs.
+STRIP_FLAGS="--strip-debug"
+
+# Needed to enable breakpad in application.ini
+export MOZILLA_OFFICIAL=1
+
+export MOZ_TELEMETRY_REPORTING=1
+
+# Use sccache
+no_sccache=
+
+#B2G options
+ac_add_options --enable-application=b2g
+ENABLE_MARIONETTE=1
+export CXXFLAGS=-DMOZ_ENABLE_JS_DUMP
+
+GAIADIR=$topsrcdir/gaia
+
+# Include Firefox OS fonts.
+MOZTTDIR=$topsrcdir/moz-tt
+
+. "$topsrcdir/b2g/config/mozconfigs/common.override"
diff --git a/b2g/config/mozconfigs/linux32_gecko/nightly b/b2g/config/mozconfigs/linux32_gecko/nightly
new file mode 100644
index 0000000000..4cc78ca0ed
--- /dev/null
+++ b/b2g/config/mozconfigs/linux32_gecko/nightly
@@ -0,0 +1,36 @@
+MOZ_AUTOMATION_L10N_CHECK=0
+MOZ_AUTOMATION_UPLOAD_SYMBOLS=0
+MOZ_AUTOMATION_UPDATE_PACKAGING=0
+MOZ_AUTOMATION_SDK=0
+. "$topsrcdir/b2g/config/mozconfigs/common"
+. "$topsrcdir/build/unix/mozconfig.linux32"
+
+ac_add_options --enable-update-channel=${MOZ_UPDATE_CHANNEL}
+ac_add_options --enable-signmar
+
+# This will overwrite the default of stripping everything and keep the symbol table.
+# This is useful for profiling and debugging and only increases the package size
+# by 2 MBs.
+STRIP_FLAGS="--strip-debug"
+
+# Needed to enable breakpad in application.ini
+export MOZILLA_OFFICIAL=1
+
+export MOZ_TELEMETRY_REPORTING=1
+
+# Use sccache
+no_sccache=
+
+#B2G options
+ac_add_options --enable-application=b2g
+export CXXFLAGS=-DMOZ_ENABLE_JS_DUMP
+
+GAIADIR=$topsrcdir/gaia
+
+# Include Firefox OS fonts.
+MOZTTDIR=$topsrcdir/moz-tt
+
+# Build simulator xpi and phone tweaks for b2g-desktop
+FXOS_SIMULATOR=1
+
+. "$topsrcdir/b2g/config/mozconfigs/common.override"
diff --git a/b2g/config/mozconfigs/linux64_gecko/nightly b/b2g/config/mozconfigs/linux64_gecko/nightly
new file mode 100644
index 0000000000..67e7cea4f4
--- /dev/null
+++ b/b2g/config/mozconfigs/linux64_gecko/nightly
@@ -0,0 +1,36 @@
+MOZ_AUTOMATION_L10N_CHECK=0
+MOZ_AUTOMATION_UPLOAD_SYMBOLS=0
+MOZ_AUTOMATION_UPDATE_PACKAGING=0
+MOZ_AUTOMATION_SDK=0
+. "$topsrcdir/b2g/config/mozconfigs/common"
+. "$topsrcdir/build/unix/mozconfig.linux"
+
+ac_add_options --enable-update-channel=${MOZ_UPDATE_CHANNEL}
+ac_add_options --enable-signmar
+
+# This will overwrite the default of stripping everything and keep the symbol table.
+# This is useful for profiling and debugging and only increases the package size
+# by 2 MBs.
+STRIP_FLAGS="--strip-debug"
+
+# Needed to enable breakpad in application.ini
+export MOZILLA_OFFICIAL=1
+
+export MOZ_TELEMETRY_REPORTING=1
+
+# Use sccache
+no_sccache=
+
+#B2G options
+ac_add_options --enable-application=b2g
+export CXXFLAGS=-DMOZ_ENABLE_JS_DUMP
+
+GAIADIR=$topsrcdir/gaia
+
+# Include Firefox OS fonts.
+MOZTTDIR=$topsrcdir/moz-tt
+
+# Build simulator xpi and phone tweaks for b2g-desktop
+FXOS_SIMULATOR=1
+
+. "$topsrcdir/b2g/config/mozconfigs/common.override"
diff --git a/b2g/config/mozconfigs/macosx64_gecko/debug b/b2g/config/mozconfigs/macosx64_gecko/debug
new file mode 100644
index 0000000000..1577e1f2ee
--- /dev/null
+++ b/b2g/config/mozconfigs/macosx64_gecko/debug
@@ -0,0 +1,33 @@
+MOZ_AUTOMATION_UPLOAD_SYMBOLS=0
+MOZ_AUTOMATION_UPDATE_PACKAGING=0
+. "$topsrcdir/b2g/config/mozconfigs/common"
+
+# Use sccache
+no_sccache=
+
+. $topsrcdir/build/macosx/mozconfig.common
+
+ac_add_options --enable-update-channel=${MOZ_UPDATE_CHANNEL}
+ac_add_options --enable-signmar
+
+# Needed to enable breakpad in application.ini
+export MOZILLA_OFFICIAL=1
+
+export MOZ_TELEMETRY_REPORTING=1
+
+#ac_add_options --with-macbundlename-prefix=Firefox
+
+# B2G Stuff
+ac_add_options --enable-application=b2g
+ac_add_options --enable-debug-symbols
+ac_add_options --enable-debug
+ENABLE_MARIONETTE=1
+
+export CXXFLAGS=-DMOZ_ENABLE_JS_DUMP
+
+GAIADIR=$topsrcdir/gaia
+
+# Include Firefox OS fonts.
+MOZTTDIR=$topsrcdir/moz-tt
+
+. "$topsrcdir/b2g/config/mozconfigs/common.override"
diff --git a/b2g/config/mozconfigs/macosx64_gecko/nightly b/b2g/config/mozconfigs/macosx64_gecko/nightly
new file mode 100644
index 0000000000..2c9c01d678
--- /dev/null
+++ b/b2g/config/mozconfigs/macosx64_gecko/nightly
@@ -0,0 +1,34 @@
+MOZ_AUTOMATION_UPLOAD_SYMBOLS=0
+MOZ_AUTOMATION_UPDATE_PACKAGING=0
+MOZ_AUTOMATION_SDK=0
+. "$topsrcdir/b2g/config/mozconfigs/common"
+
+# Use sccache
+no_sccache=
+
+. $topsrcdir/build/macosx/mozconfig.common
+
+ac_add_options --enable-update-channel=${MOZ_UPDATE_CHANNEL}
+ac_add_options --enable-signmar
+
+# Needed to enable breakpad in application.ini
+export MOZILLA_OFFICIAL=1
+
+export MOZ_TELEMETRY_REPORTING=1
+
+#ac_add_options --with-macbundlename-prefix=Firefox
+
+# B2G Stuff
+ac_add_options --enable-application=b2g
+ac_add_options --enable-debug-symbols
+export CXXFLAGS=-DMOZ_ENABLE_JS_DUMP
+
+GAIADIR=$topsrcdir/gaia
+
+# Include Firefox OS fonts.
+MOZTTDIR=$topsrcdir/moz-tt
+
+# Build simulator xpi and phone tweaks for b2g-desktop
+FXOS_SIMULATOR=1
+
+. "$topsrcdir/b2g/config/mozconfigs/common.override"
diff --git a/b2g/config/mozconfigs/win32_gecko/debug b/b2g/config/mozconfigs/win32_gecko/debug
new file mode 100644
index 0000000000..ab34aa1f72
--- /dev/null
+++ b/b2g/config/mozconfigs/win32_gecko/debug
@@ -0,0 +1,29 @@
+MOZ_AUTOMATION_L10N_CHECK=0
+MOZ_AUTOMATION_UPLOAD_SYMBOLS=0
+MOZ_AUTOMATION_UPDATE_PACKAGING=0
+. "$topsrcdir/b2g/config/mozconfigs/common"
+
+ac_add_options --enable-update-channel=${MOZ_UPDATE_CHANNEL}
+ac_add_options --enable-jemalloc
+ac_add_options --enable-signmar
+ac_add_options --enable-debug
+
+# Needed to enable breakpad in application.ini
+export MOZILLA_OFFICIAL=1
+
+export MOZ_TELEMETRY_REPORTING=1
+
+. $topsrcdir/build/win32/mozconfig.vs-latest
+
+# B2G Options
+ac_add_options --enable-application=b2g
+ENABLE_MARIONETTE=1
+
+export CXXFLAGS=-DMOZ_ENABLE_JS_DUMP
+
+GAIADIR=$topsrcdir/gaia
+
+# Include Firefox OS fonts.
+MOZTTDIR=$topsrcdir/moz-tt
+
+. "$topsrcdir/b2g/config/mozconfigs/common.override"
diff --git a/b2g/config/mozconfigs/win32_gecko/nightly b/b2g/config/mozconfigs/win32_gecko/nightly
new file mode 100644
index 0000000000..7e4486368e
--- /dev/null
+++ b/b2g/config/mozconfigs/win32_gecko/nightly
@@ -0,0 +1,30 @@
+MOZ_AUTOMATION_L10N_CHECK=0
+MOZ_AUTOMATION_UPLOAD_SYMBOLS=0
+MOZ_AUTOMATION_UPDATE_PACKAGING=0
+MOZ_AUTOMATION_SDK=0
+. "$topsrcdir/b2g/config/mozconfigs/common"
+
+ac_add_options --enable-update-channel=${MOZ_UPDATE_CHANNEL}
+ac_add_options --enable-jemalloc
+ac_add_options --enable-signmar
+
+# Needed to enable breakpad in application.ini
+export MOZILLA_OFFICIAL=1
+
+export MOZ_TELEMETRY_REPORTING=1
+
+. $topsrcdir/build/win32/mozconfig.vs-latest
+
+# B2G Options
+ac_add_options --enable-application=b2g
+export CXXFLAGS=-DMOZ_ENABLE_JS_DUMP
+
+GAIADIR=$topsrcdir/gaia
+
+# Include Firefox OS fonts.
+MOZTTDIR=$topsrcdir/moz-tt
+
+# Build simulator xpi and phone tweaks for b2g-desktop
+FXOS_SIMULATOR=1
+
+. "$topsrcdir/b2g/config/mozconfigs/common.override"
diff --git a/b2g/config/nexus-5-l/config.json b/b2g/config/nexus-5-l/config.json
new file mode 100644
index 0000000000..7b8bada402
--- /dev/null
+++ b/b2g/config/nexus-5-l/config.json
@@ -0,0 +1,55 @@
+{
+ "config_version": 2,
+ "tooltool_manifest": "releng-nexus5.manifest",
+ "mock_target": "mozilla-centos6-x86_64",
+ "mock_packages": ["ccache", "make", "bison", "flex", "gcc", "g++", "mpfr", "zlib-devel", "ncurses-devel", "zip", "autoconf213", "glibc-static", "perl-Digest-SHA", "wget", "alsa-lib", "atk", "cairo", "dbus-glib", "fontconfig", "freetype", "glib2", "gtk2", "libXRender", "libXt", "pango", "mozilla-python27-mercurial", "openssh-clients", "nss-devel", "glibc-devel.i686", "libstdc++.i686", "zlib-devel.i686", "ncurses-devel.i686", "libX11-devel.i686", "mesa-libGL-devel.i686", "mesa-libGL-devel", "libX11-devel", "git", "libxml2"],
+ "mock_files": [
+ ["/home/cltbld/.ssh", "/home/mock_mozilla/.ssh"],
+ ["/builds/crash-stats-api.token", "/builds/crash-stats-api.token"]
+ ],
+ "build_targets": ["", "blobfree"],
+ "upload_files": [
+ "{objdir}/dist/b2g-*.crashreporter-symbols.zip",
+ "{objdir}/dist/b2g-*.tar.gz",
+ "{workdir}/sources.xml",
+ "{workdir}/out/target/product/nexus-5-l/fota-*-update-*.mar"
+ ],
+ "public_upload_files": [
+ "{objdir}/dist/b2g-*.crashreporter-symbols.zip",
+ "{objdir}/dist/b2g-*.tar.gz",
+ "{workdir}/sources.xml",
+ "{objdir}/dist/b2g-update/*.mar",
+ "{workdir}/hammerhead.zip",
+ "{workdir}/out/target/product/nexus-5-l/fota-*-update.mar"
+ ],
+ "zip_files": [
+ ["{workdir}/out/target/product/hammerhead/*.img", "out/target/product/hammerhead/"],
+ ["{workdir}/boot.img", "out/target/product/hammerhead/"],
+ "{workdir}/flash.sh",
+ "{workdir}/gecko/b2g/installer/flash.bat",
+ "{workdir}/prebuilts/misc/windows/win_flash_tools/*",
+ "{workdir}/load-config.sh",
+ "{workdir}/.config",
+ "{workdir}/sources.xml",
+ "{workdir}/profile.sh",
+ ["{workdir}/gecko/tools/profiler/merge-profiles.py", "gecko/tools/profiler/"],
+ ["{workdir}/scripts/profile-symbolicate.py", "scripts/"],
+ ["{workdir}/gecko/tools/rb/fix_stack_using_bpsyms.py", "gecko/tools/rb/"]
+ ],
+ "env": {
+ "VARIANT": "userdebug",
+ "MOZILLA_OFFICIAL": "1",
+ "MOZ_TELEMETRY_REPORTING": "1",
+ "B2G_UPDATE_CHANNEL": "nightly"
+ },
+ "b2g_manifest": "nexus-5-l.xml",
+ "b2g_manifest_intree": true,
+ "additional_source_tarballs": [],
+ "gecko_l10n_root": "https://hg.mozilla.org/l10n-central",
+ "gaia": {
+ "l10n": {
+ "vcs": "hg",
+ "root": "https://hg.mozilla.org/gaia-l10n"
+ }
+ }
+}
diff --git a/b2g/config/nexus-5-l/releng-nexus5.manifest b/b2g/config/nexus-5-l/releng-nexus5.manifest
new file mode 100644
index 0000000000..f7851b4e37
--- /dev/null
+++ b/b2g/config/nexus-5-l/releng-nexus5.manifest
@@ -0,0 +1,19 @@
+[
+{
+"version": "Android NDK r11b for B2G",
+"algorithm": "sha512",
+"visibility": "internal",
+"filename": "android-ndk-b2g.tar.xz",
+"unpack": true,
+"digest": "bc37c6b2e38f4ff19e3326786312d8f893600e155d35dfba45163bd909e022db852b9c6920863cb498bbe7da8b86a6a387fa024bc9444ce3a8d1715cf2c24b21",
+"size": 292442020
+},
+{
+"version": "gcc 4.8.5 + PR64905",
+"size": 80160264,
+"digest": "c1a9dc9da289b8528874d16300b9d13a997cec99195bb0bc46ff665216d8535d6d6cb5af6b4b1f2749af6815dab12e703fdb3849014e5c23a70eff351a0baf4e",
+"algorithm": "sha512",
+"filename": "gcc.tar.xz",
+"unpack": "True"
+}
+]
diff --git a/b2g/config/nexus-5-l/sources.xml b/b2g/config/nexus-5-l/sources.xml
new file mode 100644
index 0000000000..0419408a3e
--- /dev/null
+++ b/b2g/config/nexus-5-l/sources.xml
@@ -0,0 +1,162 @@
+<?xml version="1.0" ?><manifest>
+ <!--
+ Remotes
+ -->
+ <remote fetch="git://github.com/apitrace/" name="apitrace"/>
+ <remote fetch="git://github.com/mozilla-b2g/" name="b2g"/>
+ <remote fetch="git://codeaurora.org/" name="caf"/>
+ <!--
+ B2G repositories for all targets
+ -->
+ <project name="gaia" path="gaia" remote="b2g" revision="f5d355dfeb559e11fad48901b0bea287ec33b874"/>
+ <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="99003a6e7ecee880330a3fb8b5e49fefdb762374"/>
+ <project name="moztt" path="external/moztt" remote="b2g" revision="99c333dab00ed79baff9e1cf76b320aee8e1c123"/>
+ <project name="platform_hardware_libhardware_moz" path="hardware/libhardware_moz" remote="b2g" revision="fdf3a143dc777e5f9d33a88373af7ea161d3b440"/>
+ <project name="platform_system_libfdio" path="system/libfdio" remote="b2g" revision="34adfb400e031f3dd3353d92413572db5e3a7376"/>
+ <project name="platform_system_libpdu" path="system/libpdu" remote="b2g" revision="f1a61fa8f97cc0a1ac4eca160acc222981b21d90"/>
+ <project name="platform_system_sensorsd" path="system/sensorsd" remote="b2g" revision="3618678c472320de386f5ddc27897992d0e148a8"/>
+ <!-- B2G specific things. -->
+ <project name="platform_build" path="build" remote="b2g" revision="63ae12cb8ca32cbbd7ef8292582f2ac2a1587a0b">
+ <copyfile dest="Makefile" src="core/root.mk"/>
+ </project>
+ <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
+ <project name="fake-qemu-kernel" path="prebuilts/qemu-kernel" remote="b2g" revision="939b377d55a2f081d94029a30a75d05e5a20daf3"/>
+ <project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
+ <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
+ <project name="valgrind" path="external/valgrind" remote="b2g" revision="5f931350fbc87c3df9db8b0ceb37734b8b471593"/>
+ <project name="vex" path="external/VEX" remote="b2g" revision="48d8c7c950745f1b166b42125e6f0d3293d71636"/>
+ <project name="apitrace" path="external/apitrace" remote="apitrace" revision="7c5a77c651bcde37005e6b6e209747edcc6c9361"/>
+ <!-- Stock Android things -->
+ <project groups="pdk,linux" name="platform/prebuilts/clang/linux-x86/host/3.5" path="prebuilts/clang/linux-x86/host/3.5" revision="ffc05a232799fe8fcb3e47b7440b52b1fb4244c0"/>
+ <project groups="pdk,linux,arm" name="platform/prebuilts/gcc/linux-x86/aarch64/aarch64-linux-android-4.8" path="prebuilts/gcc/linux-x86/aarch64/aarch64-linux-android-4.8" revision="337e0ef5e40f02a1ae59b90db0548976c70a7226"/>
+ <project groups="pdk,linux,arm" name="platform/prebuilts/gcc/linux-x86/arm/arm-eabi-4.8" path="prebuilts/gcc/linux-x86/arm/arm-eabi-4.8" revision="8af5ff6f5dced9eb5a8127459df6c75d24342204"/>
+ <project groups="pdk,linux,arm" name="platform/prebuilts/gcc/linux-x86/arm/arm-linux-androideabi-4.8" path="prebuilts/gcc/linux-x86/arm/arm-linux-androideabi-4.8" revision="30915518fa7ea07166efedc191a4f40aef516fe7"/>
+ <project depth="1" groups="pdk,linux,arm" name="platform/prebuilts/gcc/linux-x86/arm/arm-linux-androideabi-4.9" path="prebuilts/gcc/linux-x86/arm/arm-linux-androideabi-4.9" remote="caf" revision="c09e1b3a55153d1ba142d5bf548c90487ea71f9e"/>
+ <project groups="pdk,linux" name="platform/prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.11-4.6" path="prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.11-4.6" revision="96eee58e3389fb05a835310d6a06a6ba4486097a"/>
+ <project groups="pdk,linux" name="platform/prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.11-4.8" path="prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.11-4.8" revision="7c8a46698171aa2e0be09edb43d15a6acf832770"/>
+ <project groups="pdk,linux,x86" name="platform/prebuilts/gcc/linux-x86/x86/x86_64-linux-android-4.8" path="prebuilts/gcc/linux-x86/x86/x86_64-linux-android-4.8" revision="24b2038be8a636fd4a5d21f0abae1e466b07bcf7"/>
+ <project depth="1" groups="pdk,linux,x86" name="platform/prebuilts/gcc/linux-x86/x86/x86_64-linux-android-4.9" path="prebuilts/gcc/linux-x86/x86/x86_64-linux-android-4.9" remote="caf" revision="c33513f9de95fcdf4ec832db5e3ebd612382f541"/>
+ <project groups="linux" name="platform/prebuilts/python/linux-x86/2.7.5" path="prebuilts/python/linux-x86/2.7.5" revision="ab14644af0429dfb6744e9709f9ef46fb7bf73e4"/>
+ <project name="device/common" path="device/common" revision="05f67c8cf7b9669da6f37f74b3388b594e319b84"/>
+ <project name="device/sample" path="device/sample" revision="298675b60a633253434c4829339534c08292dc92"/>
+ <project name="platform/abi/cpp" path="abi/cpp" revision="39fd88f57cec1dd6e9c70f85ab0c76587f7ba766"/>
+ <project name="platform/bionic" path="bionic" revision="7741d30da4f0f0c15e6622ca75ad396e78eab7dd"/>
+ <project name="platform/bootable/recovery" path="bootable/recovery" revision="c9ef7996198ad29e706b352cbb773a7dad5bdc5c"/>
+ <project name="platform/external/aac" path="external/aac" revision="78fdf0627dd31f77fe71fde289512f749032a787"/>
+ <project name="platform/external/bison" path="external/bison" revision="4efa7909d921823fbfcf85f5c64ad3578803e2ee"/>
+ <project name="platform/external/bluetooth/bluedroid" path="external/bluetooth/bluedroid" revision="cdae70948d67a920e8847ad0323ff11f51dbcaf9"/>
+ <project name="platform/external/bsdiff" path="external/bsdiff" revision="d7a2c5578467c3b8375943bee09c20a692d8d2a0"/>
+ <project name="platform/external/bzip2" path="external/bzip2" revision="c41fb467156106c6274c12e1279fbd5340757667"/>
+ <project name="platform/external/checkpolicy" path="external/checkpolicy" revision="a1d60ce948816137de49ea0737d9a9d6b54adab7"/>
+ <project name="platform/external/clang" path="external/clang" revision="6c6bfc254506351c5753de7e2fe3eab6bca40d2b"/>
+ <project name="platform/external/compiler-rt" path="external/compiler-rt" revision="3b8d597882284a3694b9bca7500ee9d9a4f02683"/>
+ <project name="platform/external/dhcpcd" path="external/dhcpcd" revision="72519f06fc08fe450607e040544267cd0befdee3"/>
+ <project name="platform/external/dnsmasq" path="external/dnsmasq" revision="6e8e5151469e8da0eadb894813201f5e87e7ad66"/>
+ <project name="platform/external/e2fsprogs" path="external/e2fsprogs" revision="566faf84e682b1e8233ed9853c9a0a4dd8c3bc37"/>
+ <project name="platform/external/elfutils" path="external/elfutils" revision="5084a43adc8ae0de74888842a47d33d66f3fd2ca"/>
+ <project name="platform/external/expat" path="external/expat" revision="47e857874d4a893bc90288f98f392448151cf741"/>
+ <project name="platform/external/f2fs-tools" path="external/f2fs-tools" revision="fc24ac8347630a14d62ffafa93a27ec3b81cc44b"/>
+ <project name="platform/external/fdlibm" path="external/fdlibm" revision="f24510089e8b5cb533c0406fd2e9c5a8eb1b201b"/>
+ <project name="platform/external/flac" path="external/flac" revision="ae2004f637dd0eb68d763f441d3ff1cf285d42b4"/>
+ <project name="platform/external/freetype" path="external/freetype" revision="a177e10e69985dc9640b89f615e1c0b61fb4f0f4"/>
+ <project name="platform/external/gcc-demangle" path="external/gcc-demangle" revision="9d1a7f107acea987ed2440c0a310a6c42f667bd7"/>
+ <project name="platform/external/genext2fs" path="external/genext2fs" revision="c7e3aae061a272caa34e5a465edd9927cde42ac8"/>
+ <project name="platform/external/giflib" path="external/giflib" revision="f0f278e928db903456cbbc2dfff2a678c31a9d27"/>
+ <project name="platform/external/gtest" path="external/gtest" revision="5d13a30f9978eb09254ebc83ae51d6a730eec215"/>
+ <project name="platform/external/harfbuzz_ng" path="external/harfbuzz_ng" revision="647fa898029679d4fac6958332c88c5f3f169439"/>
+ <project name="platform/external/icu" path="external/icu" revision="a41b209dc1f9836733c59a30e862784e8895ac7c"/>
+ <project name="platform/external/iproute2" path="external/iproute2" revision="acf444b6a524b4bce5b7cfe81b29e2839ff6e508"/>
+ <project name="platform/external/ipsec-tools" path="external/ipsec-tools" revision="bf460d6fcb58d7f8e95f5d3ab9ffa2e7e16c2957"/>
+ <project name="platform/external/iptables" path="external/iptables" revision="11beff4f1ad782fd1d8e5c1857f2c2088abb42f6"/>
+ <project name="platform/external/jack" path="external/jack" revision="cd80e83b301ac9c5a845d8d01dc73084891cd19d"/>
+ <project name="platform/external/jemalloc" path="external/jemalloc" revision="a6a05e48b628346ec4342f8b6d1c3d0a5987236e"/>
+ <project name="platform/external/jhead" path="external/jhead" revision="3b6bb83af87698537d150ee004ba27720af50f54"/>
+ <project name="platform/external/jpeg" path="external/jpeg" revision="b5c22f7648e1b03241abb05733aec47e2bf66462"/>
+ <project name="platform/external/jsmn" path="external/jsmn" revision="646c4a36fd8cdb1ecd2080963bd26d9e245473c5"/>
+ <project name="platform/external/jsoncpp" path="external/jsoncpp" revision="d9d8c51470b0cd2b95219c6b596ef90ae7915b8e"/>
+ <project name="platform/external/junit" path="external/junit" revision="a9d933b7f8f42007de21bc674a61d491e14b48df"/>
+ <project name="platform/external/libcxxabi" path="external/libcxxabi" revision="31349d4fd0a2ee4744d1cade9fa774dd2213be80"/>
+ <project name="platform/external/libcxx" path="external/libcxx" revision="7cff60f9e680dc58c9a913eddc049946e3616265"/>
+ <project name="platform/external/libgsm" path="external/libgsm" revision="7f76ac798f682fed4662c789010050789eabee89"/>
+ <project name="platform/external/liblzf" path="external/liblzf" revision="0e6384fa203d8c7499fe64c9e93930df8e50cc99"/>
+ <project name="platform/external/libnfc-nxp" path="external/libnfc-nxp" revision="13c47f6dfcd78c59243e997b5df30c5155949d59"/>
+ <project name="platform/external/libnl" path="external/libnl" revision="b1e000e9a7b0048a5e8335a32125b405bd77c53b"/>
+ <project name="platform/external/libogg" path="external/libogg" revision="9a2354608fdae6e105b28d4456a94146e41616c7"/>
+ <project name="platform/external/libopus" path="external/libopus" revision="46be742257e102568f59c3ce18e322cf0b258799"/>
+ <project name="platform/external/libpcap" path="external/libpcap" revision="03c8b18ba58ac3347c18875779e162c75c652715"/>
+ <project name="platform/external/libpng" path="external/libpng" revision="8eac982ec2415fbc83e5a61899e0ca7eba4fe96d"/>
+ <project name="platform/external/libselinux" path="external/libselinux" revision="317bc4b75ea40dfb5988c12f607505a86c6c2823"/>
+ <project name="platform/external/libsepol" path="external/libsepol" revision="24b924ffd20dbf7359a9e15a10a8d1cfd6309597"/>
+ <project name="platform/external/libunwind" path="external/libunwind" revision="e3fa64fe20e4eaf13c2231727158bd5fac4e53ec"/>
+ <project name="platform/external/libvpx" path="external/libvpx" revision="652843c7218cdfcd42c98a839b72941d0e4696ba"/>
+ <project name="platform/external/llvm" path="external/llvm" revision="63e3c6329893697af239dae0ddab843a3e1623a3"/>
+ <project name="platform/external/mdnsresponder" path="external/mdnsresponder" revision="32a5f67c10e0b87cf95bbbaad489b8e3f098eb01"/>
+ <project name="platform/external/mksh" path="external/mksh" revision="c452c566cd6face953c9e33d01c4df652d39fdf6"/>
+ <project name="platform/external/netcat" path="external/netcat" revision="00d9d6b1aa7772c96878db17d556f29150705be8"/>
+ <project name="platform/external/openssl" path="external/openssl" revision="bf4112b16f0ede8128ba7ff3c17a0056834b7c75"/>
+ <project name="platform/external/pcre" path="external/pcre" revision="89b3b9780dbdd7c682b9bf2efd0f476a1ebc5d33"/>
+ <project name="platform/external/protobuf" path="external/protobuf" revision="cdb14929d7c934944079ce070f5eb2f9459f824c"/>
+ <project name="platform/external/safe-iop" path="external/safe-iop" revision="33d0429591d345687755c25f23ea2e46df5cd293"/>
+ <project name="platform/external/scrypt" path="external/scrypt" revision="d809e38af0d89747cde00970271fb64d42333302"/>
+ <project name="platform/external/sfntly" path="external/sfntly" revision="d2bab207acad60e098d0652b5ed7348c96a249f3"/>
+ <project name="platform/external/skia" path="external/skia" revision="b0fe7c355d4d95ff89b8e9e459285fe2b98e366d"/>
+ <project name="platform/external/sonivox" path="external/sonivox" revision="3d04f03d824d5ea8835e577785b51022820ae763"/>
+ <project name="platform/external/speex" path="external/speex" revision="9d73341cd881c415e6fce726ea4918654cd03145"/>
+ <project name="platform/external/sqlite" path="external/sqlite" revision="2394fd03445b9496025558d4e48a54c09180e132"/>
+ <project name="platform/external/stlport" path="external/stlport" revision="49f98215d1dea7f96f7560528bc8528570e2e05b"/>
+ <project name="platform/external/strace" path="external/strace" revision="a093fa84778892c96159ccee02dc8d6c4378a124"/>
+ <project name="platform/external/svox" path="external/svox" revision="7543f63ace85c9812dfc5263d63ed190eebb7f1c"/>
+ <project name="platform/external/tagsoup" path="external/tagsoup" revision="5ad81cc518df943dbff3a566f3db5a3aae4b3098"/>
+ <project name="platform/external/tcpdump" path="external/tcpdump" revision="f28770c517d9ca78bf25779d833da2d8a3aa1c61"/>
+ <project name="platform/external/tinyalsa" path="external/tinyalsa" revision="73444308ffa76f7df107c9b5c8eb657d2a212017"/>
+ <project name="platform/external/tinycompress" path="external/tinycompress" revision="d65aa7ba2e7548452906d3d61ee03c94cc64f25d"/>
+ <project name="platform/external/tinyxml2" path="external/tinyxml2" revision="ea87469657dd498dd76ea01b35daab9d48503a3c"/>
+ <project name="platform/external/tinyxml" path="external/tinyxml" revision="d09b587fe9bd7f1524d434c15941a5483c2d7046"/>
+ <project name="platform/external/tremolo" path="external/tremolo" revision="7954026e4cbeeaa4bb7d7e2c82a6556ea34c58ab"/>
+ <project name="platform/external/webp" path="external/webp" revision="5df3d5cb644f301e4a0c78b939e411b061a36558"/>
+ <project name="platform/external/webrtc" path="external/webrtc" revision="de40077759a01a02a3e21b30ea0755f2d6fc4e09"/>
+ <project name="platform/external/yaffs2" path="external/yaffs2" revision="b2aadfdf9482777530efac1fb13a25ff401e78ee"/>
+ <project name="platform/external/zlib" path="external/zlib" revision="a9dc8ffc4b43f0ff455d52fc5a889e92794962a4"/>
+ <project name="platform/external/zopfli" path="external/zopfli" revision="8b994159cf3fc74a58e42fca72bc6849e6027912"/>
+ <project name="platform_frameworks_native" path="frameworks/native" remote="b2g" revision="77c23f8067bca84476f96d663efaae636817edd5"/>
+ <project name="platform/frameworks/opt/emoji" path="frameworks/opt/emoji" revision="2293192ed15b88ebe962fb5377dd197200e6472b"/>
+ <project name="platform/hardware/libhardware" path="hardware/libhardware" revision="f5feb2aa2047fbaf13be448fe8d06bff3ccf7b84"/>
+ <project name="platform/hardware/libhardware_legacy" path="hardware/libhardware_legacy" revision="8d075b4d5e9e032b18fbc8b5def63827d1b4a30d"/>
+ <project name="platform/libcore" path="libcore" revision="bdec7d684c083760bef7bc4ba2429cceccaaf7d0"/>
+ <project name="platform/libnativehelper" path="libnativehelper" revision="27bcc086236cedd31c056303e255c6d0ea3d4a50"/>
+ <project name="platform/ndk" path="ndk" revision="42e85f81cc6c74af145056ee80b06e520cccb9a7"/>
+ <project name="platform_prebuilts_misc" path="prebuilts/misc" remote="b2g" revision="25b96077aeae7bd0e3a5e7c284fb636664337013"/>
+ <project name="platform/prebuilts/ndk" path="prebuilts/ndk" revision="1d080491f26dfdfd76d5bbc3e6b40c660e8565af"/>
+ <project name="platform/prebuilts/sdk" path="prebuilts/sdk" revision="61a10cbd19d6b7fc052a8cb92dfa1b37b93754f3"/>
+ <project name="platform/prebuilts/tools" path="prebuilts/tools" revision="9e892a67a01671f312c76b0880dedaa6ba478148"/>
+ <project name="platform_system_bluetoothd" path="system/bluetoothd" remote="b2g" revision="0a5aa7225129b792c19e91be88eac97a89c089b6"/>
+ <project name="platform/system/extras" path="system/extras" revision="47fa016e2248b80aebd5928402c7409f8e0ca64e"/>
+ <project name="platform/system/media" path="system/media" revision="70bfebc66d9c6a4c614a8c7efde90e8e7e1d8641"/>
+ <project name="platform/system/netd" path="system/netd" revision="d113f0ceefa9ce29eb3c86e2d23c7417a70b4048"/>
+ <project name="platform/system/security" path="system/security" revision="94e1617f6f2bc2286d005e79cffa6bf0721b06b3"/>
+ <project name="platform/system/vold" path="system/vold" revision="c065e301e38ea0c241164e2a373e1ecefbeaf2ec"/>
+ <project name="platform_frameworks_av" path="frameworks/av" remote="b2g" revision="479a404164986b3e95212eecdae7e67da4fba9ed"/>
+ <project name="platform_frameworks_base" path="frameworks/base" remote="b2g" revision="396b731dbccc62f272f1fdb8228109c3fbd83c25"/>
+ <project name="platform_frameworks_wilhelm" path="frameworks/wilhelm" remote="b2g" revision="174bb44bb9af7583e6337e1e1b6cc18d0217ae82"/>
+ <project name="platform_system_core" path="system/core" remote="b2g" revision="1b8322b228f717ff2a4d48fa8b44240d8e3f62bc"/>
+ <project name="platform_external_sepolicy" path="external/sepolicy" remote="b2g" revision="246c603d9fe181fa8893af7293dbc63e870fe5e0"/>
+ <default remote="caf" revision="refs/tags/android-5.1.0_r1" sync-j="4"/>
+ <!-- Nexus 5 specific things -->
+ <project name="device/generic/armv7-a-neon" path="device/generic/armv7-a-neon" revision="fe7df1bc8dd0fd71571505d7be1c31a4ad1e40fb"/>
+ <project name="device-hammerhead" path="device/lge/hammerhead" remote="b2g" revision="acbb7a8914059426180c9059fc9419e57668a478"/>
+ <project name="device_lge_hammerhead-kernel" path="device/lge/hammerhead-kernel" remote="b2g" revision="8b3ffcfdd3d3852eca5488628f8bb2a08acbffa7"/>
+ <project name="platform/external/libnfc-nci" path="external/libnfc-nci" revision="5d0ae53d9588c3d70c005aec9be94af9a534de16"/>
+ <project name="platform/external/wpa_supplicant_8" path="external/wpa_supplicant_8" revision="c15b6e266136cd0cdd9b94d0bbed1962d9dd6672"/>
+ <project name="platform/hardware/broadcom/libbt" path="hardware/broadcom/libbt" revision="399fe3d3c8f38c599a56becddc456133e62a5d70"/>
+ <project name="platform/hardware/broadcom/wlan" path="hardware/broadcom/wlan" revision="3f3134d5cb19d5ace48d36d0100467a545d430eb"/>
+ <project name="platform/hardware/qcom/audio" path="hardware/qcom/audio" revision="810c3dd29d009822a71eba9910e429a9ad114533"/>
+ <project name="hardware_qcom_display" path="hardware/qcom/display" remote="b2g" revision="7b5967bbd90cb193b489d8f8668bc945384bd9b1"/>
+ <project name="platform/hardware/qcom/keymaster" path="hardware/qcom/keymaster" revision="eaede9f8bc206736a889bc57817047c31e205589"/>
+ <project name="platform/hardware/qcom/media" path="hardware/qcom/media" revision="12364db20d6710f0003a4f00962c9790ad3c13e3"/>
+ <project name="platform/hardware/qcom/msm8x74" path="hardware/qcom/msm8x74" revision="f09920b2b488cf68bcbe9f7bbbde84a509a1d7a5"/>
+ <project name="platform/hardware/qcom/power" path="hardware/qcom/power" revision="29e9aeaf278b16fea4cb1ab4d05b8b8c9083c15b"/>
+ <project name="platform/hardware/qcom/sensors" path="hardware/qcom/sensors" revision="fde83fdf67e9b919f8a49008725bd595221bf33f"/>
+ <project name="platform/hardware/qcom/wlan" path="hardware/qcom/wlan" revision="6417804bea95f6e46094a01a06025a86e28c5b0d"/>
+ <project name="platform/hardware/ril" path="hardware/ril" revision="e00d716e7e3d31729f75399855b6921e90cb0b66"/>
+ <project name="platform_system_nfcd" path="system/nfcd" remote="b2g" revision="5f4b68c799927b6e078f987b12722c3a6ccd4a45"/>
+</manifest>
diff --git a/b2g/config/tooltool-manifests/linux32/releng.manifest b/b2g/config/tooltool-manifests/linux32/releng.manifest
new file mode 100644
index 0000000000..c1fd5eac11
--- /dev/null
+++ b/b2g/config/tooltool-manifests/linux32/releng.manifest
@@ -0,0 +1,32 @@
+[
+{
+"version": "gcc 4.8.5 + PR64905",
+"size": 80160264,
+"digest": "c1a9dc9da289b8528874d16300b9d13a997cec99195bb0bc46ff665216d8535d6d6cb5af6b4b1f2749af6815dab12e703fdb3849014e5c23a70eff351a0baf4e",
+"algorithm": "sha512",
+"filename": "gcc.tar.xz",
+"unpack": true
+},
+{
+"size": 11189216,
+"digest": "18bc52b0599b1308b667e282abb45f47597bfc98a5140cfcab8da71dacf89dd76d0dee22a04ce26fe7ad1f04e2d6596991f9e5b01fd2aaaab5542965f596b0e6",
+"algorithm": "sha512",
+"filename": "gtk3.tar.xz",
+"setup": "setup.sh",
+"unpack": true
+},
+{
+"size": 167175,
+"digest": "0b71a936edf5bd70cf274aaa5d7abc8f77fe8e7b5593a208f805cc9436fac646b9c4f0b43c2b10de63ff3da671497d35536077ecbc72dba7f8159a38b580f831",
+"algorithm": "sha512",
+"filename": "sccache.tar.bz2",
+"unpack": true
+},
+{
+"size": 31078810,
+"digest": "2dffe4e5419a0c0c9908dc52b01cc07379a42e2aa8481be7a26bb8750b586b95bbac3fe57e64f5d37b43e206516ea70ad938a2e45858fdcf1e28258e70ae8d8c",
+"algorithm": "sha512",
+"filename": "moz-tt.tar.bz2",
+"unpack": true
+}
+]
diff --git a/b2g/config/tooltool-manifests/macosx64/releng.manifest b/b2g/config/tooltool-manifests/macosx64/releng.manifest
new file mode 100644
index 0000000000..33158d8dcb
--- /dev/null
+++ b/b2g/config/tooltool-manifests/macosx64/releng.manifest
@@ -0,0 +1,24 @@
+[
+{
+"version": "clang 3.8.0",
+"size": 133060926,
+"digest": "aff5ad3ac2d41db19d1ba0df5f97b189a7d7e1b6af8c56e22c2b0cced84d75fa98394ded6a4ba5713652e6684a0a46f47aeccf87991f9e849bf8d7d82e564f6f",
+"algorithm": "sha512",
+"filename": "clang.tar.bz2",
+"unpack": true
+},
+{
+"size": 167175,
+"digest": "0b71a936edf5bd70cf274aaa5d7abc8f77fe8e7b5593a208f805cc9436fac646b9c4f0b43c2b10de63ff3da671497d35536077ecbc72dba7f8159a38b580f831",
+"algorithm": "sha512",
+"filename": "sccache.tar.bz2",
+"unpack": true
+},
+{
+"size": 31078810,
+"digest": "2dffe4e5419a0c0c9908dc52b01cc07379a42e2aa8481be7a26bb8750b586b95bbac3fe57e64f5d37b43e206516ea70ad938a2e45858fdcf1e28258e70ae8d8c",
+"algorithm": "sha512",
+"filename": "moz-tt.tar.bz2",
+"unpack": true
+}
+]
diff --git a/b2g/config/tooltool-manifests/win32/releng.manifest b/b2g/config/tooltool-manifests/win32/releng.manifest
new file mode 100644
index 0000000000..f0592ee141
--- /dev/null
+++ b/b2g/config/tooltool-manifests/win32/releng.manifest
@@ -0,0 +1,22 @@
+[
+{
+"size": 266240,
+"digest": "bb345b0e700ffab4d09436981f14b5de84da55a3f18a7f09ebc4364a4488acdeab8d46f447b12ac70f2da1444a68b8ce8b8675f0dae2ccf845e966d1df0f0869",
+"algorithm": "sha512",
+"filename": "mozmake.exe"
+},
+{
+"size": 31078810,
+"digest": "2dffe4e5419a0c0c9908dc52b01cc07379a42e2aa8481be7a26bb8750b586b95bbac3fe57e64f5d37b43e206516ea70ad938a2e45858fdcf1e28258e70ae8d8c",
+"algorithm": "sha512",
+"filename": "moz-tt.tar.bz2",
+"unpack": true
+},
+{
+"size": 167175,
+"digest": "0b71a936edf5bd70cf274aaa5d7abc8f77fe8e7b5593a208f805cc9436fac646b9c4f0b43c2b10de63ff3da671497d35536077ecbc72dba7f8159a38b580f831",
+"algorithm": "sha512",
+"filename": "sccache.tar.bz2",
+"unpack": true
+}
+]
diff --git a/b2g/confvars.sh b/b2g/confvars.sh
new file mode 100644
index 0000000000..64c42caf66
--- /dev/null
+++ b/b2g/confvars.sh
@@ -0,0 +1,45 @@
+# 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/.
+
+MOZ_APP_BASENAME=B2G
+MOZ_APP_VENDOR=Mozilla
+
+MOZ_APP_VERSION=$FIREFOX_VERSION
+MOZ_APP_UA_NAME=Firefox
+
+MOZ_UA_OS_AGNOSTIC=1
+
+MOZ_B2G_VERSION=2.6.0.0-prerelease
+MOZ_B2G_OS_NAME=Boot2Gecko
+
+MOZ_BRANDING_DIRECTORY=b2g/branding/unofficial
+MOZ_OFFICIAL_BRANDING_DIRECTORY=b2g/branding/official
+# MOZ_APP_DISPLAYNAME is set by branding/configure.sh
+
+MOZ_NO_SMART_CARDS=1
+MOZ_APP_STATIC_INI=1
+
+if test "$OS_TARGET" = "Android"; then
+MOZ_CAPTURE=1
+MOZ_RAW=1
+MOZ_AUDIO_CHANNEL_MANAGER=1
+fi
+
+# use custom widget for html:select
+MOZ_USE_NATIVE_POPUP_WINDOWS=1
+
+MOZ_XULRUNNER=
+
+MOZ_APP_ID={3c2e2abc-06d4-11e1-ac3b-374f68613e61}
+
+MOZ_TIME_MANAGER=1
+
+MOZ_TOOLKIT_SEARCH=
+MOZ_B2G=1
+
+MOZ_JSDOWNLOADS=1
+
+MOZ_BUNDLED_FONTS=1
+
+export JS_GC_SMALL_CHUNK_SIZE=1
diff --git a/b2g/dev/app.mozbuild b/b2g/dev/app.mozbuild
new file mode 100644
index 0000000000..0419915470
--- /dev/null
+++ b/b2g/dev/app.mozbuild
@@ -0,0 +1,21 @@
+# vim: set filetype=python:
+# 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('/toolkit/toolkit.mozbuild')
+
+if CONFIG['MOZ_EXTENSIONS']:
+ DIRS += ['/extensions']
+
+DIRS += ['/%s' % CONFIG['MOZ_BRANDING_DIRECTORY']]
+
+DIRS += [
+ '/b2g/chrome',
+ '/b2g/components',
+ '/b2g/dev/app',
+
+ # Never add dirs after browser because they apparently won't get
+ # packaged properly on Mac.
+ '/browser',
+]
diff --git a/b2g/dev/app/moz.build b/b2g/dev/app/moz.build
new file mode 100644
index 0000000000..2f162d7e93
--- /dev/null
+++ b/b2g/dev/app/moz.build
@@ -0,0 +1,15 @@
+# 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/.
+
+DIST_SUBDIR = 'browser'
+export('DIST_SUBDIR')
+
+JS_PREFERENCE_PP_FILES += [
+ '../../app/b2g.js',
+]
+
+JS_PREFERENCE_FILES += [
+ 'mulet.js',
+]
+
diff --git a/b2g/dev/app/mulet.js b/b2g/dev/app/mulet.js
new file mode 100644
index 0000000000..7be5198552
--- /dev/null
+++ b/b2g/dev/app/mulet.js
@@ -0,0 +1,20 @@
+// Automatically open b2g in a tab
+pref("browser.startup.homepage", "chrome://b2g/content/shell.html");
+
+// Disable some painful behavior of fx
+pref("startup.homepage_welcome_url", "");
+pref("browser.shell.checkDefaultBrowser", "");
+pref("browser.sessionstore.max_tabs_undo", 0);
+pref("browser.sessionstore.max_windows_undo", 0);
+pref("browser.sessionstore.restore_on_demand", false);
+pref("browser.sessionstore.resume_from_crash", false);
+
+// Display the devtools on the right of the phone
+pref("devtools.toolbox.host", "side");
+pref("devtools.toolbox.sidebar.width", 800);
+
+// Disable e10s as we don't want to run shell.html,
+// nor the system app OOP, but only inner apps
+pref("browser.tabs.remote.autostart", false);
+pref("browser.tabs.remote.autostart.1", false);
+pref("browser.tabs.remote.autostart.2", false);
diff --git a/b2g/dev/build.mk b/b2g/dev/build.mk
new file mode 100644
index 0000000000..daa4e7ac27
--- /dev/null
+++ b/b2g/dev/build.mk
@@ -0,0 +1,6 @@
+# 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 $(topsrcdir)/browser/build.mk
+
diff --git a/b2g/dev/config/mozconfigs/linux64/mulet b/b2g/dev/config/mozconfigs/linux64/mulet
new file mode 100644
index 0000000000..4aaa6b78dd
--- /dev/null
+++ b/b2g/dev/config/mozconfigs/linux64/mulet
@@ -0,0 +1,10 @@
+MOZ_AUTOMATION_L10N_CHECK=0
+MOZ_AUTOMATION_UPLOAD_SYMBOLS=0
+MOZ_AUTOMATION_UPDATE_PACKAGING=0
+MOZ_AUTOMATION_SDK=0
+. "$topsrcdir/browser/config/mozconfigs/linux64/nightly"
+
+ac_add_options --enable-application=b2g/dev
+
+# Include Firefox OS fonts.
+MOZTTDIR=$topsrcdir/moz-tt
diff --git a/b2g/dev/config/mozconfigs/linux64/mulet-hazards b/b2g/dev/config/mozconfigs/linux64/mulet-hazards
new file mode 100644
index 0000000000..2c3609c99f
--- /dev/null
+++ b/b2g/dev/config/mozconfigs/linux64/mulet-hazards
@@ -0,0 +1,13 @@
+MOZ_AUTOMATION_L10N_CHECK=0
+MOZ_AUTOMATION_UPLOAD_SYMBOLS=0
+MOZ_AUTOMATION_UPDATE_PACKAGING=0
+MOZ_AUTOMATION_SDK=0
+. "$topsrcdir/browser/config/mozconfigs/linux64/nightly"
+
+ac_add_options --enable-application=b2g/dev
+ac_add_options --with-compiler-wrapper=$TOOLTOOL_DIR/sixgill/usr/libexec/sixgill/scripts/wrap_gcc/basecc
+ac_add_options --without-ccache
+ac_add_options --disable-warnings-as-errors
+
+# Include Firefox OS fonts.
+MOZTTDIR=$topsrcdir/moz-tt
diff --git a/b2g/dev/config/mozconfigs/linux64/mulet_dbg b/b2g/dev/config/mozconfigs/linux64/mulet_dbg
new file mode 100644
index 0000000000..08baad0e95
--- /dev/null
+++ b/b2g/dev/config/mozconfigs/linux64/mulet_dbg
@@ -0,0 +1,14 @@
+MOZ_AUTOMATION_L10N_CHECK=0
+MOZ_AUTOMATION_UPLOAD_SYMBOLS=0
+MOZ_AUTOMATION_UPDATE_PACKAGING=0
+MOZ_AUTOMATION_SDK=0
+. "$topsrcdir/browser/config/mozconfigs/linux64/nightly"
+
+ac_add_options --enable-application=b2g/dev
+ac_add_options --enable-debug
+MOZ_DEMANGLE_SYMBOLS=1
+MOZ_DEBUG=1
+MOZ_DEBUG_SYMBOLS=1
+
+# Include Firefox OS fonts.
+MOZTTDIR=$topsrcdir/moz-tt
diff --git a/b2g/dev/config/mozconfigs/macosx64/mulet b/b2g/dev/config/mozconfigs/macosx64/mulet
new file mode 100644
index 0000000000..b6f1338ca1
--- /dev/null
+++ b/b2g/dev/config/mozconfigs/macosx64/mulet
@@ -0,0 +1,27 @@
+MOZ_AUTOMATION_BUILD_SYMBOLS=0
+MOZ_AUTOMATION_PACKAGE_TESTS=0
+MOZ_AUTOMATION_UPLOAD_SYMBOLS=0
+MOZ_AUTOMATION_UPDATE_PACKAGING=0
+MOZ_AUTOMATION_SDK=0
+. $topsrcdir/build/macosx/mozconfig.common
+
+ac_add_options --enable-application=b2g/dev
+ac_add_options --disable-install-strip
+ac_add_options --enable-signmar
+ac_add_options --enable-profiling
+ac_add_options --enable-instruments
+ac_add_options --enable-dtrace
+
+# Needed to enable breakpad in application.ini
+export MOZILLA_OFFICIAL=1
+
+ac_add_options --with-macbundlename-prefix=Firefox
+
+# Package js shell.
+export MOZ_PACKAGE_JSSHELL=1
+
+# Include Firefox OS fonts.
+MOZTTDIR=$topsrcdir/moz-tt
+
+. "$topsrcdir/build/mozconfig.common.override"
+. "$topsrcdir/build/mozconfig.cache"
diff --git a/b2g/dev/config/mozconfigs/win32/mulet b/b2g/dev/config/mozconfigs/win32/mulet
new file mode 100644
index 0000000000..f5a43bd6a3
--- /dev/null
+++ b/b2g/dev/config/mozconfigs/win32/mulet
@@ -0,0 +1,13 @@
+MOZ_AUTOMATION_BUILD_SYMBOLS=0
+MOZ_AUTOMATION_L10N_CHECK=0
+MOZ_AUTOMATION_PACKAGE_TESTS=0
+MOZ_AUTOMATION_INSTALLER=0
+MOZ_AUTOMATION_UPLOAD_SYMBOLS=0
+MOZ_AUTOMATION_UPDATE_PACKAGING=0
+MOZ_AUTOMATION_SDK=0
+. "$topsrcdir/browser/config/mozconfigs/win32/nightly"
+
+ac_add_options --enable-application=b2g/dev
+
+# Include Firefox OS fonts.
+MOZTTDIR=$topsrcdir/moz-tt
diff --git a/b2g/dev/config/tooltool-manifests/linux64/hazard.manifest b/b2g/dev/config/tooltool-manifests/linux64/hazard.manifest
new file mode 100644
index 0000000000..10a722c434
--- /dev/null
+++ b/b2g/dev/config/tooltool-manifests/linux64/hazard.manifest
@@ -0,0 +1,48 @@
+[
+{
+"size" : 102421980,
+"digest" : "f25292aa93dc449e0472eee511c0ac15b5f1a4272ab76cf53ce5d20dc57f29e83da49ae1a9d9e994192647f75e13ae60f75ba2ac3cb9d26d5f5d6cabf88de921",
+"version" : "gcc 4.9.3",
+"unpack" : true,
+"filename" : "gcc.tar.xz",
+"algorithm" : "sha512"
+},
+{
+"unpack" : true,
+"algorithm" : "sha512",
+"filename" : "sixgill.tar.xz",
+"hg_id" : "8cb9c3fb039a+ tip",
+"digest" : "36dc644e24c0aa824975ad8f5c15714445d5cb064d823000c3cb637e885199414d7df551e6b99233f0656dcf5760918192ef04113c486af37f3c489bb93ad029",
+"size" : 2631908
+},
+{
+"algorithm" : "sha512",
+"filename" : "gtk3.tar.xz",
+"setup" : "setup.sh",
+"unpack" : true,
+"digest" : "3915f8ec396c56a8a92e6f9695b70f09ce9d1582359d1258e37e3fd43a143bc974410e4cfc27f500e095f34a8956206e0ebf799b7287f0f38def0d5e34ed71c9",
+"size" : 12072532
+},
+{
+"version": "rustc 1.13.0 (2c6933acc 2016-11-07) repack",
+"size": 68921028,
+"digest": "9a9ceccc02d4be445ffa64617683419a4f47990b1f2689980ac8db13d6369435ef4af1a3714d77377fb7b3b0ec213856ab7144ff22cbe0881d49aed44d82c0fc",
+"algorithm": "sha512",
+"filename": "rustc.tar.xz",
+"unpack": true
+},
+{
+"algorithm" : "sha512",
+"filename" : "sccache.tar.bz2",
+"unpack" : true,
+"digest" : "0b71a936edf5bd70cf274aaa5d7abc8f77fe8e7b5593a208f805cc9436fac646b9c4f0b43c2b10de63ff3da671497d35536077ecbc72dba7f8159a38b580f831",
+"size" : 167175
+},
+{
+"filename" : "moz-tt.tar.bz2",
+"algorithm" : "sha512",
+"unpack" : true,
+"digest" : "2dffe4e5419a0c0c9908dc52b01cc07379a42e2aa8481be7a26bb8750b586b95bbac3fe57e64f5d37b43e206516ea70ad938a2e45858fdcf1e28258e70ae8d8c",
+"size" : 31078810
+}
+]
diff --git a/b2g/dev/config/tooltool-manifests/linux64/releng.manifest b/b2g/dev/config/tooltool-manifests/linux64/releng.manifest
new file mode 100644
index 0000000000..d356dbaa32
--- /dev/null
+++ b/b2g/dev/config/tooltool-manifests/linux64/releng.manifest
@@ -0,0 +1,48 @@
+[
+{
+"version": "gcc 4.9.3",
+"size": 102421980,
+"digest": "f25292aa93dc449e0472eee511c0ac15b5f1a4272ab76cf53ce5d20dc57f29e83da49ae1a9d9e994192647f75e13ae60f75ba2ac3cb9d26d5f5d6cabf88de921",
+"algorithm": "sha512",
+"filename": "gcc.tar.xz",
+"unpack": true
+},
+{
+"size": 12072532,
+"digest": "3915f8ec396c56a8a92e6f9695b70f09ce9d1582359d1258e37e3fd43a143bc974410e4cfc27f500e095f34a8956206e0ebf799b7287f0f38def0d5e34ed71c9",
+"algorithm": "sha512",
+"filename": "gtk3.tar.xz",
+"setup": "setup.sh",
+"unpack": true
+},
+{
+"version": "rustc 1.13.0 (2c6933acc 2016-11-07) repack",
+"size": 68921028,
+"digest": "9a9ceccc02d4be445ffa64617683419a4f47990b1f2689980ac8db13d6369435ef4af1a3714d77377fb7b3b0ec213856ab7144ff22cbe0881d49aed44d82c0fc",
+"algorithm": "sha512",
+"filename": "rustc.tar.xz",
+"unpack": true
+},
+{
+"version": "cargo 0.13.0-nightly (eca9e15 2016-11-01) repack",
+"size": 3027932,
+"digest": "a5c99eeb12b3b9b49632c259c762e34ec13cf72dadf90a0608b8ab1dc66b36cb114c5b45f71d326e12d31d9e88a41b029e6a728ca64cef392c0a8d211c2fe191",
+"algorithm": "sha512",
+"filename": "cargo.tar.xz",
+"unpack": true
+},
+{
+"size": 167175,
+"digest": "0b71a936edf5bd70cf274aaa5d7abc8f77fe8e7b5593a208f805cc9436fac646b9c4f0b43c2b10de63ff3da671497d35536077ecbc72dba7f8159a38b580f831",
+"algorithm": "sha512",
+"filename": "sccache.tar.bz2",
+"unpack": true
+},
+{
+"size": 31078810,
+"digest": "2dffe4e5419a0c0c9908dc52b01cc07379a42e2aa8481be7a26bb8750b586b95bbac3fe57e64f5d37b43e206516ea70ad938a2e45858fdcf1e28258e70ae8d8c",
+"algorithm": "sha512",
+"filename": "moz-tt.tar.bz2",
+"unpack": true
+}
+]
diff --git a/b2g/dev/config/tooltool-manifests/macosx64/releng.manifest b/b2g/dev/config/tooltool-manifests/macosx64/releng.manifest
new file mode 100644
index 0000000000..33158d8dcb
--- /dev/null
+++ b/b2g/dev/config/tooltool-manifests/macosx64/releng.manifest
@@ -0,0 +1,24 @@
+[
+{
+"version": "clang 3.8.0",
+"size": 133060926,
+"digest": "aff5ad3ac2d41db19d1ba0df5f97b189a7d7e1b6af8c56e22c2b0cced84d75fa98394ded6a4ba5713652e6684a0a46f47aeccf87991f9e849bf8d7d82e564f6f",
+"algorithm": "sha512",
+"filename": "clang.tar.bz2",
+"unpack": true
+},
+{
+"size": 167175,
+"digest": "0b71a936edf5bd70cf274aaa5d7abc8f77fe8e7b5593a208f805cc9436fac646b9c4f0b43c2b10de63ff3da671497d35536077ecbc72dba7f8159a38b580f831",
+"algorithm": "sha512",
+"filename": "sccache.tar.bz2",
+"unpack": true
+},
+{
+"size": 31078810,
+"digest": "2dffe4e5419a0c0c9908dc52b01cc07379a42e2aa8481be7a26bb8750b586b95bbac3fe57e64f5d37b43e206516ea70ad938a2e45858fdcf1e28258e70ae8d8c",
+"algorithm": "sha512",
+"filename": "moz-tt.tar.bz2",
+"unpack": true
+}
+]
diff --git a/b2g/dev/config/tooltool-manifests/win32/releng.manifest b/b2g/dev/config/tooltool-manifests/win32/releng.manifest
new file mode 100644
index 0000000000..a5f5e436a9
--- /dev/null
+++ b/b2g/dev/config/tooltool-manifests/win32/releng.manifest
@@ -0,0 +1,22 @@
+[
+{
+"size": 266240,
+"digest": "bb345b0e700ffab4d09436981f14b5de84da55a3f18a7f09ebc4364a4488acdeab8d46f447b12ac70f2da1444a68b8ce8b8675f0dae2ccf845e966d1df0f0869",
+"algorithm": "sha512",
+"filename": "mozmake.exe"
+},
+{
+"size": 167175,
+"digest": "0b71a936edf5bd70cf274aaa5d7abc8f77fe8e7b5593a208f805cc9436fac646b9c4f0b43c2b10de63ff3da671497d35536077ecbc72dba7f8159a38b580f831",
+"algorithm": "sha512",
+"filename": "sccache.tar.bz2",
+"unpack": true
+},
+{
+"size": 31078810,
+"digest": "2dffe4e5419a0c0c9908dc52b01cc07379a42e2aa8481be7a26bb8750b586b95bbac3fe57e64f5d37b43e206516ea70ad938a2e45858fdcf1e28258e70ae8d8c",
+"algorithm": "sha512",
+"filename": "moz-tt.tar.bz2",
+"unpack": true
+}
+]
diff --git a/b2g/dev/confvars.sh b/b2g/dev/confvars.sh
new file mode 100644
index 0000000000..4fb0b58a18
--- /dev/null
+++ b/b2g/dev/confvars.sh
@@ -0,0 +1,12 @@
+#! /bin/sh
+# 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/.
+
+MOZ_B2G=1
+MOZ_MULET=1
+
+. ${srcdir}/browser/confvars.sh
+
+MOZ_BUNDLED_FONTS=1
+MOZ_UA_OS_AGNOSTIC=1
diff --git a/b2g/dev/moz.configure b/b2g/dev/moz.configure
new file mode 100644
index 0000000000..d1b944eb55
--- /dev/null
+++ b/b2g/dev/moz.configure
@@ -0,0 +1,9 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+imply_option('MOZ_SERVICES_SYNC', True)
+
+include('../common.configure')
diff --git a/b2g/gaia/Makefile.in b/b2g/gaia/Makefile.in
new file mode 100644
index 0000000000..0820b16a4b
--- /dev/null
+++ b/b2g/gaia/Makefile.in
@@ -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/.
+
+GAIA_PATH := gaia/profile
+
+GENERATED_DIRS += $(DIST)/bin/$(GAIA_PATH)
+
+include $(topsrcdir)/config/rules.mk
+
+libs::
+ +$(MAKE) -j1 -C $(GAIADIR) clean
+ +$(MAKE) -j1 -C $(GAIADIR) profile
+ (cd $(GAIADIR)/profile && tar $(TAR_CREATE_FLAGS) - .) | (cd $(ABS_DIST)/bin/$(GAIA_PATH) && tar -xf -)
diff --git a/b2g/gaia/moz.build b/b2g/gaia/moz.build
new file mode 100644
index 0000000000..ab98fd151e
--- /dev/null
+++ b/b2g/gaia/moz.build
@@ -0,0 +1,20 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+Program(CONFIG['MOZ_APP_NAME'])
+
+if CONFIG['OS_ARCH'] == 'WINNT':
+ SOURCES += [
+ 'run-b2g.cpp',
+ ]
+ DEFINES['B2G_NAME'] = 'L"%s-bin%s"' % (PROGRAM, CONFIG['BIN_SUFFIX'])
+ DEFINES['GAIA_PATH'] = 'L"gaia\\\\profile"'
+else:
+ SOURCES += [
+ 'run-b2g.c',
+ ]
+ DEFINES['B2G_NAME'] = '"%s-bin%s"' % (PROGRAM, CONFIG['BIN_SUFFIX'])
+ DEFINES['GAIA_PATH'] = '"gaia/profile"'
diff --git a/b2g/gaia/run-b2g.c b/b2g/gaia/run-b2g.c
new file mode 100644
index 0000000000..184fa34000
--- /dev/null
+++ b/b2g/gaia/run-b2g.c
@@ -0,0 +1,50 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <libgen.h>
+
+#ifndef B2G_NAME
+#define B2G_NAME "b2g-bin"
+#endif
+#ifndef GAIA_PATH
+#define GAIA_PATH "gaia/profile"
+#endif
+#define NOMEM "Could not allocate enough memory"
+
+void error(char* msg){
+ fprintf(stderr, "ERROR: %s\n", msg);
+}
+
+int main(int argc, char* argv[], char* envp[]){
+ char* cwd = NULL;
+ char* full_path = NULL;
+ char* full_profile_path = NULL;
+ printf("Starting %s\n", B2G_NAME);
+ cwd = realpath(dirname(argv[0]), NULL);
+ full_path = (char*) malloc(strlen(cwd) + strlen(B2G_NAME) + 2);
+ if (!full_path) {
+ error(NOMEM);
+ return -2;
+ }
+ full_profile_path = (char*) malloc(strlen(cwd) + strlen(GAIA_PATH) + 2);
+ if (!full_profile_path) {
+ free(full_path);
+ error(NOMEM);
+ return -2;
+ }
+ sprintf(full_path, "%s/%s", cwd, B2G_NAME);
+ sprintf(full_profile_path, "%s/%s", cwd, GAIA_PATH);
+ free(cwd);
+ printf("Running: %s --profile %s\n", full_path, full_profile_path);
+ fflush(stdout);
+ fflush(stderr);
+ // XXX: yes, the printf above says --profile and this execle uses -profile.
+ // Bug 1088430 will change the execle to use --profile.
+ execle(full_path, full_path, "-profile", full_profile_path, NULL, envp);
+ error("unable to start");
+ perror(argv[0]);
+ free(full_path);
+ free(full_profile_path);
+ return -1;
+}
diff --git a/b2g/gaia/run-b2g.cpp b/b2g/gaia/run-b2g.cpp
new file mode 100644
index 0000000000..26fce08a2c
--- /dev/null
+++ b/b2g/gaia/run-b2g.cpp
@@ -0,0 +1,102 @@
+#include <Windows.h>
+#include <Shlwapi.h>
+#include <strsafe.h>
+
+// Linker options
+#pragma comment(lib, "User32.lib")
+#pragma comment(lib, "shlwapi.lib")
+#pragma comment(linker, "/SUBSYSTEM:windows /ENTRY:wmainCRTStartup")
+
+#define NUM_MAX_PATH_BYTES sizeof(wchar_t) * MAX_PATH + 1
+#define PROFILE_ARG L" -profile "
+
+// Options that can be overridden at build time with -D
+#ifndef B2G_NAME
+#define B2G_NAME L"b2g.exe"
+#endif
+#ifndef GAIA_PATH
+#define GAIA_PATH L"gaia\\profile"
+#endif
+
+void error(wchar_t* msg){
+ MessageBoxW(nullptr, msg, L"Error starting program", MB_OK | MB_ICONERROR);
+}
+
+/* This function takes a string which represents a windows path, orig.
+ * The file portion of the path is stripped off and replaced with the
+ * path component, file. This function returns a string which represents
+ * a windows path with the modifications requested and needs to be freed
+ * explicitly by the calling function.
+ */
+wchar_t* make_path_with_leaf_file_name(wchar_t* orig, wchar_t* file){
+ wchar_t* buffer = (wchar_t*) malloc(NUM_MAX_PATH_BYTES);
+ if (!buffer) {
+ return nullptr;
+ }
+ if (FAILED(StringCchCopyW(buffer, NUM_MAX_PATH_BYTES, orig))) {
+ error(L"Error copying string");
+ free(buffer);
+ buffer = nullptr;
+ }
+ PathRemoveFileSpecW(buffer);
+ if (!PathAppendW(buffer, file)) {
+ error(L"Unable to append file to directory");
+ free(buffer);
+ buffer = nullptr;
+ }
+ return buffer;
+}
+
+BOOL execute(wchar_t* binary_path, wchar_t* args, int cp_flags) {
+ STARTUPINFOW si;
+ PROCESS_INFORMATION pi;
+
+ ZeroMemory(&si, sizeof(si));
+ si.cb = sizeof(si);
+ ZeroMemory(&pi, sizeof(pi));
+
+ if (!CreateProcessW(
+ binary_path,
+ args,
+ nullptr,
+ nullptr,
+ FALSE,
+ cp_flags,
+ nullptr,
+ nullptr,
+ &si,
+ &pi)){
+ error(L"Could not execute program");
+ return FALSE;
+ }
+
+ WaitForInputIdle(pi.hProcess, 0);
+ CloseHandle(pi.hProcess);
+ CloseHandle(pi.hThread);
+ return true;
+}
+
+int wmain(int argc, wchar_t *argv[], wchar_t *envp[]){
+ int cp_flags;
+ wchar_t* b2g_path = make_path_with_leaf_file_name(argv[0], B2G_NAME);
+ wchar_t* profile_path = make_path_with_leaf_file_name(argv[0], GAIA_PATH);
+ // 10 chars for the ' -profile ' portion of the argument
+ wchar_t* args = (wchar_t*) malloc(2 * NUM_MAX_PATH_BYTES + wcslen(PROFILE_ARG));
+ if (FAILED(StringCchPrintfW(args, NUM_MAX_PATH_BYTES, L"\"%ws\"%ws\"%ws\"", b2g_path, PROFILE_ARG, profile_path))) {
+ error(L"Could not create argument string");
+ ExitProcess(1);
+ }
+#ifdef SHOW_CONSOLE
+ cp_flags = 0;
+#else
+ cp_flags = DETACHED_PROCESS;
+#endif
+ if (!execute(b2g_path, args, cp_flags)) {
+ error(L"Failed to launch program");
+ }
+ free(profile_path);
+ free(b2g_path);
+ free(args);
+ profile_path = b2g_path = args = nullptr;
+
+}
diff --git a/b2g/graphene/app.mozbuild b/b2g/graphene/app.mozbuild
new file mode 100644
index 0000000000..fc3037c068
--- /dev/null
+++ b/b2g/graphene/app.mozbuild
@@ -0,0 +1,17 @@
+# vim: set filetype=python:
+# 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('/toolkit/toolkit.mozbuild')
+
+if CONFIG['MOZ_EXTENSIONS']:
+ DIRS += ['/extensions']
+
+DIRS += [
+ '/%s' % CONFIG['MOZ_BRANDING_DIRECTORY'],
+ '/b2g',
+]
+
+# Add the defaults settings.
+FINAL_TARGET_FILES.defaults += [ 'settings.json' ]
diff --git a/b2g/graphene/build.mk b/b2g/graphene/build.mk
new file mode 100644
index 0000000000..5a3623e5af
--- /dev/null
+++ b/b2g/graphene/build.mk
@@ -0,0 +1,5 @@
+# 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 $(topsrcdir)/b2g/build.mk
diff --git a/b2g/graphene/config/horizon-mozconfigs/common b/b2g/graphene/config/horizon-mozconfigs/common
new file mode 100644
index 0000000000..9277d299f9
--- /dev/null
+++ b/b2g/graphene/config/horizon-mozconfigs/common
@@ -0,0 +1,24 @@
+# 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/.
+
+# Disable the l10n-check target, which isn't relevant to b2g builds at all.
+# This needs to be set prior to the next include for it to take effect.
+MOZ_AUTOMATION_PACKAGE_TESTS=0
+MOZ_AUTOMATION_L10N_CHECK=0
+MOZ_AUTOMATION_SDK=0
+
+. "$topsrcdir/build/mozconfig.common"
+
+# Normally, we'd set this unconditionally, but this file is also used
+# for local builds and there is no other mozconfig in this tree that
+# is included on device builds.
+if test -d $topsrcdir/../gcc/bin; then
+ HOST_CC="$topsrcdir/../gcc/bin/gcc"
+ HOST_CXX="$topsrcdir/../gcc/bin/g++"
+ . "$topsrcdir/build/unix/mozconfig.stdcxx"
+fi
+
+MOZ_HORIZON=1
+ac_add_options --with-branding=b2g/branding/horizon
+ac_add_options --enable-application=b2g/graphene
diff --git a/b2g/graphene/config/horizon-mozconfigs/common.override b/b2g/graphene/config/horizon-mozconfigs/common.override
new file mode 100644
index 0000000000..f17dadfe67
--- /dev/null
+++ b/b2g/graphene/config/horizon-mozconfigs/common.override
@@ -0,0 +1,5 @@
+# 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/.
+
+. "$topsrcdir/build/mozconfig.common.override"
diff --git a/b2g/graphene/config/horizon-mozconfigs/linux32/debug b/b2g/graphene/config/horizon-mozconfigs/linux32/debug
new file mode 100644
index 0000000000..9758e72e48
--- /dev/null
+++ b/b2g/graphene/config/horizon-mozconfigs/linux32/debug
@@ -0,0 +1,25 @@
+. "$topsrcdir/b2g/graphene/config/horizon-mozconfigs/common"
+. "$topsrcdir/build/unix/mozconfig.linux32"
+
+ac_add_options --enable-debug
+
+# Nightlies only since this has a cost in performance
+#ac_add_options --enable-js-diagnostics
+
+# This will overwrite the default of stripping everything and keep the symbol table.
+# This is useful for profiling and debugging and only increases the package size
+# by 2 MBs.
+STRIP_FLAGS="--strip-debug"
+
+# Needed to enable breakpad in application.ini
+export MOZILLA_OFFICIAL=1
+
+# Use sccache
+no_sccache=
+. "$topsrcdir/build/mozconfig.cache"
+
+# graphene options
+ENABLE_MARIONETTE=1
+export CXXFLAGS=-DMOZ_ENABLE_JS_DUMP
+
+. "$topsrcdir/b2g/graphene/config/horizon-mozconfigs/common.override"
diff --git a/b2g/graphene/config/horizon-mozconfigs/linux32/nightly b/b2g/graphene/config/horizon-mozconfigs/linux32/nightly
new file mode 100644
index 0000000000..ad36ce0380
--- /dev/null
+++ b/b2g/graphene/config/horizon-mozconfigs/linux32/nightly
@@ -0,0 +1,25 @@
+. "$topsrcdir/b2g/graphene/config/horizon-mozconfigs/common"
+. "$topsrcdir/build/unix/mozconfig.linux32"
+
+ac_add_options --enable-update-channel=${MOZ_UPDATE_CHANNEL}
+ac_add_options --enable-signmar
+
+# Nightlies only since this has a cost in performance
+#ac_add_options --enable-js-diagnostics
+
+# This will overwrite the default of stripping everything and keep the symbol table.
+# This is useful for profiling and debugging and only increases the package size
+# by 2 MBs.
+STRIP_FLAGS="--strip-debug"
+
+# Needed to enable breakpad in application.ini
+export MOZILLA_OFFICIAL=1
+
+# Use sccache
+no_sccache=
+. "$topsrcdir/build/mozconfig.cache"
+
+# graphene options
+export CXXFLAGS=-DMOZ_ENABLE_JS_DUMP
+
+. "$topsrcdir/b2g/graphene/config/horizon-mozconfigs/common.override"
diff --git a/b2g/graphene/config/horizon-mozconfigs/linux64/debug b/b2g/graphene/config/horizon-mozconfigs/linux64/debug
new file mode 100644
index 0000000000..c29fe6ebcd
--- /dev/null
+++ b/b2g/graphene/config/horizon-mozconfigs/linux64/debug
@@ -0,0 +1,25 @@
+. "$topsrcdir/b2g/graphene/config/horizon-mozconfigs/common"
+. "$topsrcdir/build/unix/mozconfig.linux"
+
+ac_add_options --enable-debug
+
+# Nightlies only since this has a cost in performance
+#ac_add_options --enable-js-diagnostics
+
+# This will overwrite the default of stripping everything and keep the symbol table.
+# This is useful for profiling and debugging and only increases the package size
+# by 2 MBs.
+STRIP_FLAGS="--strip-debug"
+
+# Needed to enable breakpad in application.ini
+export MOZILLA_OFFICIAL=1
+
+# Use sccache
+no_sccache=
+. "$topsrcdir/build/mozconfig.cache"
+
+# graphene options
+ENABLE_MARIONETTE=1
+export CXXFLAGS=-DMOZ_ENABLE_JS_DUMP
+
+. "$topsrcdir/b2g/graphene/config/horizon-mozconfigs/common.override"
diff --git a/b2g/graphene/config/horizon-mozconfigs/linux64/nightly b/b2g/graphene/config/horizon-mozconfigs/linux64/nightly
new file mode 100644
index 0000000000..0a695b081e
--- /dev/null
+++ b/b2g/graphene/config/horizon-mozconfigs/linux64/nightly
@@ -0,0 +1,25 @@
+. "$topsrcdir/b2g/graphene/config/horizon-mozconfigs/common"
+. "$topsrcdir/build/unix/mozconfig.linux"
+
+ac_add_options --enable-update-channel=${MOZ_UPDATE_CHANNEL}
+ac_add_options --enable-signmar
+
+# Nightlies only since this has a cost in performance
+#ac_add_options --enable-js-diagnostics
+
+# This will overwrite the default of stripping everything and keep the symbol table.
+# This is useful for profiling and debugging and only increases the package size
+# by 2 MBs.
+STRIP_FLAGS="--strip-debug"
+
+# Needed to enable breakpad in application.ini
+export MOZILLA_OFFICIAL=1
+
+# Use sccache
+no_sccache=
+. "$topsrcdir/build/mozconfig.cache"
+
+# graphene options
+export CXXFLAGS=-DMOZ_ENABLE_JS_DUMP
+
+. "$topsrcdir/b2g/graphene/config/horizon-mozconfigs/common.override"
diff --git a/b2g/graphene/config/horizon-mozconfigs/macosx64/debug b/b2g/graphene/config/horizon-mozconfigs/macosx64/debug
new file mode 100644
index 0000000000..b973fd24c9
--- /dev/null
+++ b/b2g/graphene/config/horizon-mozconfigs/macosx64/debug
@@ -0,0 +1,23 @@
+. "$topsrcdir/b2g/graphene/config/horizon-mozconfigs/common"
+
+# Use sccache
+no_sccache=
+
+. $topsrcdir/build/macosx/mozconfig.common
+
+# Nightlies only since this has a cost in performance
+ac_add_options --enable-js-diagnostics
+
+# Needed to enable breakpad in application.ini
+export MOZILLA_OFFICIAL=1
+
+#ac_add_options --with-macbundlename-prefix=Firefox
+
+# graphene Stuff
+ac_add_options --enable-debug-symbols
+ac_add_options --enable-debug
+ENABLE_MARIONETTE=1
+
+export CXXFLAGS=-DMOZ_ENABLE_JS_DUMP
+
+. "$topsrcdir/b2g/graphene/config/horizon-mozconfigs/common.override"
diff --git a/b2g/graphene/config/horizon-mozconfigs/macosx64/nightly b/b2g/graphene/config/horizon-mozconfigs/macosx64/nightly
new file mode 100644
index 0000000000..5125bb8f12
--- /dev/null
+++ b/b2g/graphene/config/horizon-mozconfigs/macosx64/nightly
@@ -0,0 +1,23 @@
+. "$topsrcdir/b2g/graphene/config/horizon-mozconfigs/common"
+
+# Use sccache
+no_sccache=
+
+. $topsrcdir/build/macosx/mozconfig.common
+
+ac_add_options --enable-update-channel=${MOZ_UPDATE_CHANNEL}
+ac_add_options --enable-signmar
+
+# Nightlies only since this has a cost in performance
+ac_add_options --enable-js-diagnostics
+
+# Needed to enable breakpad in application.ini
+export MOZILLA_OFFICIAL=1
+
+#ac_add_options --with-macbundlename-prefix=Firefox
+
+# graphene Stuff
+ac_add_options --enable-debug-symbols
+export CXXFLAGS=-DMOZ_ENABLE_JS_DUMP
+
+. "$topsrcdir/b2g/graphene/config/horizon-mozconfigs/common.override"
diff --git a/b2g/graphene/config/horizon-mozconfigs/win32/debug b/b2g/graphene/config/horizon-mozconfigs/win32/debug
new file mode 100644
index 0000000000..89bcb7a5fd
--- /dev/null
+++ b/b2g/graphene/config/horizon-mozconfigs/win32/debug
@@ -0,0 +1,19 @@
+. "$topsrcdir/b2g/graphene/config/horizon-mozconfigs/common"
+
+ac_add_options --enable-jemalloc
+ac_add_options --enable-debug
+
+# Nightlies only since this has a cost in performance
+ac_add_options --enable-js-diagnostics
+
+# Needed to enable breakpad in application.ini
+export MOZILLA_OFFICIAL=1
+
+. $topsrcdir/build/win32/mozconfig.vs-latest
+
+# graphene Options
+ENABLE_MARIONETTE=1
+
+export CXXFLAGS=-DMOZ_ENABLE_JS_DUMP
+
+. "$topsrcdir/b2g/graphene/config/mozconfigs/common.override"
diff --git a/b2g/graphene/config/horizon-mozconfigs/win32/nightly b/b2g/graphene/config/horizon-mozconfigs/win32/nightly
new file mode 100644
index 0000000000..b9ab618e5c
--- /dev/null
+++ b/b2g/graphene/config/horizon-mozconfigs/win32/nightly
@@ -0,0 +1,18 @@
+. "$topsrcdir/b2g/graphene/config/horizon-mozconfigs/common"
+
+ac_add_options --enable-update-channel=${MOZ_UPDATE_CHANNEL}
+ac_add_options --enable-jemalloc
+ac_add_options --enable-signmar
+
+# Nightlies only since this has a cost in performance
+ac_add_options --enable-js-diagnostics
+
+# Needed to enable breakpad in application.ini
+export MOZILLA_OFFICIAL=1
+
+. $topsrcdir/build/win32/mozconfig.vs-latest
+
+# graphene Options
+export CXXFLAGS=-DMOZ_ENABLE_JS_DUMP
+
+. "$topsrcdir/b2g/graphene/config/horizon-mozconfigs/common.override"
diff --git a/b2g/graphene/config/horizon-mozconfigs/win64/debug b/b2g/graphene/config/horizon-mozconfigs/win64/debug
new file mode 100644
index 0000000000..3ab8e7820d
--- /dev/null
+++ b/b2g/graphene/config/horizon-mozconfigs/win64/debug
@@ -0,0 +1,23 @@
+. "$topsrcdir/build/mozconfig.win-common"
+MOZ_AUTOMATION_L10N_CHECK=0
+. "$topsrcdir/b2g/graphene/config/horizon-mozconfigs/common"
+
+ac_add_options --target=x86_64-pc-mingw32
+ac_add_options --host=x86_64-pc-mingw32
+
+ac_add_options --enable-debug
+ac_add_options --enable-dmd
+ac_add_options --enable-profiling # needed for --enable-dmd to work on Windows
+ac_add_options --enable-signmar
+
+# Needed to enable breakpad in application.ini
+export MOZILLA_OFFICIAL=1
+
+# Package js shell.
+export MOZ_PACKAGE_JSSHELL=1
+
+. $topsrcdir/build/win64/mozconfig.vs-latest
+
+. "$topsrcdir/build/mozconfig.cache"
+
+. "$topsrcdir/b2g/graphene/config/horizon-mozconfigs/common.override"
diff --git a/b2g/graphene/config/horizon-mozconfigs/win64/nightly b/b2g/graphene/config/horizon-mozconfigs/win64/nightly
new file mode 100644
index 0000000000..f8cbb69e71
--- /dev/null
+++ b/b2g/graphene/config/horizon-mozconfigs/win64/nightly
@@ -0,0 +1,26 @@
+if [ "x$IS_NIGHTLY" = "xyes" ]; then
+ MOZ_AUTOMATION_UPLOAD_SYMBOLS=1
+ MOZ_AUTOMATION_UPDATE_PACKAGING=1
+fi
+
+. "$topsrcdir/b2g/graphene/config/horizon-mozconfigs/common"
+
+ac_add_options --target=x86_64-pc-mingw32
+ac_add_options --host=x86_64-pc-mingw32
+
+. $topsrcdir/build/win64/mozconfig.vs-latest
+
+ac_add_options --enable-update-channel=${MOZ_UPDATE_CHANNEL}
+ac_add_options --enable-jemalloc
+ac_add_options --enable-signmar
+
+# Nightlies only since this has a cost in performance
+ac_add_options --enable-js-diagnostics
+
+# Needed to enable breakpad in application.ini
+export MOZILLA_OFFICIAL=1
+
+# graphene Options
+export CXXFLAGS=-DMOZ_ENABLE_JS_DUMP
+
+. "$topsrcdir/b2g/graphene/config/horizon-mozconfigs/common.override"
diff --git a/b2g/graphene/config/mozconfigs/common b/b2g/graphene/config/mozconfigs/common
new file mode 100644
index 0000000000..807d5d9587
--- /dev/null
+++ b/b2g/graphene/config/mozconfigs/common
@@ -0,0 +1,24 @@
+# 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/.
+
+# Disable the l10n-check target, which isn't relevant to b2g builds at all.
+# This needs to be set prior to the next include for it to take effect.
+MOZ_AUTOMATION_PACKAGE_TESTS=0
+MOZ_AUTOMATION_L10N_CHECK=0
+MOZ_AUTOMATION_SDK=0
+
+. "$topsrcdir/build/mozconfig.common"
+
+# Normally, we'd set this unconditionally, but this file is also used
+# for local builds and there is no other mozconfig in this tree that
+# is included on device builds.
+if test -d $topsrcdir/../gcc/bin; then
+ HOST_CC="$topsrcdir/../gcc/bin/gcc"
+ HOST_CXX="$topsrcdir/../gcc/bin/g++"
+ . "$topsrcdir/build/unix/mozconfig.stdcxx"
+fi
+
+ac_add_options --with-branding=b2g/branding/browserhtml
+ac_add_options --enable-application=b2g/graphene
+
diff --git a/b2g/graphene/config/mozconfigs/common.override b/b2g/graphene/config/mozconfigs/common.override
new file mode 100644
index 0000000000..f17dadfe67
--- /dev/null
+++ b/b2g/graphene/config/mozconfigs/common.override
@@ -0,0 +1,5 @@
+# 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/.
+
+. "$topsrcdir/build/mozconfig.common.override"
diff --git a/b2g/graphene/config/mozconfigs/linux32/debug b/b2g/graphene/config/mozconfigs/linux32/debug
new file mode 100644
index 0000000000..4be8bc4789
--- /dev/null
+++ b/b2g/graphene/config/mozconfigs/linux32/debug
@@ -0,0 +1,25 @@
+. "$topsrcdir/b2g/graphene/config/mozconfigs/common"
+. "$topsrcdir/build/unix/mozconfig.linux32"
+
+ac_add_options --enable-debug
+
+# Nightlies only since this has a cost in performance
+#ac_add_options --enable-js-diagnostics
+
+# This will overwrite the default of stripping everything and keep the symbol table.
+# This is useful for profiling and debugging and only increases the package size
+# by 2 MBs.
+STRIP_FLAGS="--strip-debug"
+
+# Needed to enable breakpad in application.ini
+export MOZILLA_OFFICIAL=1
+
+# Use sccache
+no_sccache=
+. "$topsrcdir/build/mozconfig.cache"
+
+# graphene options
+ENABLE_MARIONETTE=1
+export CXXFLAGS=-DMOZ_ENABLE_JS_DUMP
+
+. "$topsrcdir/b2g/graphene/config/mozconfigs/common.override"
diff --git a/b2g/graphene/config/mozconfigs/linux32/nightly b/b2g/graphene/config/mozconfigs/linux32/nightly
new file mode 100644
index 0000000000..0bbd85ac23
--- /dev/null
+++ b/b2g/graphene/config/mozconfigs/linux32/nightly
@@ -0,0 +1,25 @@
+. "$topsrcdir/b2g/graphene/config/mozconfigs/common"
+. "$topsrcdir/build/unix/mozconfig.linux32"
+
+ac_add_options --enable-update-channel=${MOZ_UPDATE_CHANNEL}
+ac_add_options --enable-signmar
+
+# Nightlies only since this has a cost in performance
+#ac_add_options --enable-js-diagnostics
+
+# This will overwrite the default of stripping everything and keep the symbol table.
+# This is useful for profiling and debugging and only increases the package size
+# by 2 MBs.
+STRIP_FLAGS="--strip-debug"
+
+# Needed to enable breakpad in application.ini
+export MOZILLA_OFFICIAL=1
+
+# Use sccache
+no_sccache=
+. "$topsrcdir/build/mozconfig.cache"
+
+# graphene options
+export CXXFLAGS=-DMOZ_ENABLE_JS_DUMP
+
+. "$topsrcdir/b2g/graphene/config/mozconfigs/common.override"
diff --git a/b2g/graphene/config/mozconfigs/linux64/debug b/b2g/graphene/config/mozconfigs/linux64/debug
new file mode 100644
index 0000000000..ab1eece0b6
--- /dev/null
+++ b/b2g/graphene/config/mozconfigs/linux64/debug
@@ -0,0 +1,25 @@
+. "$topsrcdir/b2g/graphene/config/mozconfigs/common"
+. "$topsrcdir/build/unix/mozconfig.linux"
+
+ac_add_options --enable-debug
+
+# Nightlies only since this has a cost in performance
+#ac_add_options --enable-js-diagnostics
+
+# This will overwrite the default of stripping everything and keep the symbol table.
+# This is useful for profiling and debugging and only increases the package size
+# by 2 MBs.
+STRIP_FLAGS="--strip-debug"
+
+# Needed to enable breakpad in application.ini
+export MOZILLA_OFFICIAL=1
+
+# Use sccache
+no_sccache=
+. "$topsrcdir/build/mozconfig.cache"
+
+# graphene options
+ENABLE_MARIONETTE=1
+export CXXFLAGS=-DMOZ_ENABLE_JS_DUMP
+
+. "$topsrcdir/b2g/graphene/config/mozconfigs/common.override"
diff --git a/b2g/graphene/config/mozconfigs/linux64/nightly b/b2g/graphene/config/mozconfigs/linux64/nightly
new file mode 100644
index 0000000000..543ab8a4e1
--- /dev/null
+++ b/b2g/graphene/config/mozconfigs/linux64/nightly
@@ -0,0 +1,25 @@
+. "$topsrcdir/b2g/graphene/config/mozconfigs/common"
+. "$topsrcdir/build/unix/mozconfig.linux"
+
+ac_add_options --enable-update-channel=${MOZ_UPDATE_CHANNEL}
+ac_add_options --enable-signmar
+
+# Nightlies only since this has a cost in performance
+#ac_add_options --enable-js-diagnostics
+
+# This will overwrite the default of stripping everything and keep the symbol table.
+# This is useful for profiling and debugging and only increases the package size
+# by 2 MBs.
+STRIP_FLAGS="--strip-debug"
+
+# Needed to enable breakpad in application.ini
+export MOZILLA_OFFICIAL=1
+
+# Use sccache
+no_sccache=
+. "$topsrcdir/build/mozconfig.cache"
+
+# graphene options
+export CXXFLAGS=-DMOZ_ENABLE_JS_DUMP
+
+. "$topsrcdir/b2g/graphene/config/mozconfigs/common.override"
diff --git a/b2g/graphene/config/mozconfigs/macosx64/debug b/b2g/graphene/config/mozconfigs/macosx64/debug
new file mode 100644
index 0000000000..027632e1d4
--- /dev/null
+++ b/b2g/graphene/config/mozconfigs/macosx64/debug
@@ -0,0 +1,23 @@
+. "$topsrcdir/b2g/graphene/config/mozconfigs/common"
+
+# Use sccache
+no_sccache=
+
+. $topsrcdir/build/macosx/mozconfig.common
+
+# Nightlies only since this has a cost in performance
+ac_add_options --enable-js-diagnostics
+
+# Needed to enable breakpad in application.ini
+export MOZILLA_OFFICIAL=1
+
+#ac_add_options --with-macbundlename-prefix=Firefox
+
+# graphene Stuff
+ac_add_options --enable-debug-symbols
+ac_add_options --enable-debug
+ENABLE_MARIONETTE=1
+
+export CXXFLAGS=-DMOZ_ENABLE_JS_DUMP
+
+. "$topsrcdir/b2g/graphene/config/mozconfigs/common.override"
diff --git a/b2g/graphene/config/mozconfigs/macosx64/nightly b/b2g/graphene/config/mozconfigs/macosx64/nightly
new file mode 100644
index 0000000000..002fdeaf99
--- /dev/null
+++ b/b2g/graphene/config/mozconfigs/macosx64/nightly
@@ -0,0 +1,23 @@
+. "$topsrcdir/b2g/graphene/config/mozconfigs/common"
+
+# Use sccache
+no_sccache=
+
+. $topsrcdir/build/macosx/mozconfig.common
+
+ac_add_options --enable-update-channel=${MOZ_UPDATE_CHANNEL}
+ac_add_options --enable-signmar
+
+# Nightlies only since this has a cost in performance
+ac_add_options --enable-js-diagnostics
+
+# Needed to enable breakpad in application.ini
+export MOZILLA_OFFICIAL=1
+
+#ac_add_options --with-macbundlename-prefix=Firefox
+
+# graphene Stuff
+ac_add_options --enable-debug-symbols
+export CXXFLAGS=-DMOZ_ENABLE_JS_DUMP
+
+. "$topsrcdir/b2g/graphene/config/mozconfigs/common.override"
diff --git a/b2g/graphene/config/mozconfigs/win32/debug b/b2g/graphene/config/mozconfigs/win32/debug
new file mode 100644
index 0000000000..246b823999
--- /dev/null
+++ b/b2g/graphene/config/mozconfigs/win32/debug
@@ -0,0 +1,19 @@
+. "$topsrcdir/b2g/graphene/config/mozconfigs/common"
+
+ac_add_options --enable-jemalloc
+ac_add_options --enable-debug
+
+# Nightlies only since this has a cost in performance
+ac_add_options --enable-js-diagnostics
+
+# Needed to enable breakpad in application.ini
+export MOZILLA_OFFICIAL=1
+
+. $topsrcdir/build/win32/mozconfig.vs-latest
+
+# graphene Options
+ENABLE_MARIONETTE=1
+
+export CXXFLAGS=-DMOZ_ENABLE_JS_DUMP
+
+. "$topsrcdir/b2g/grapheneconfig/mozconfigs/common.override"
diff --git a/b2g/graphene/config/mozconfigs/win32/nightly b/b2g/graphene/config/mozconfigs/win32/nightly
new file mode 100644
index 0000000000..ed7668b645
--- /dev/null
+++ b/b2g/graphene/config/mozconfigs/win32/nightly
@@ -0,0 +1,18 @@
+. "$topsrcdir/b2g/graphene/config/mozconfigs/common"
+
+ac_add_options --enable-update-channel=${MOZ_UPDATE_CHANNEL}
+ac_add_options --enable-jemalloc
+ac_add_options --enable-signmar
+
+# Nightlies only since this has a cost in performance
+ac_add_options --enable-js-diagnostics
+
+# Needed to enable breakpad in application.ini
+export MOZILLA_OFFICIAL=1
+
+. $topsrcdir/build/win32/mozconfig.vs-latest
+
+# graphene Options
+export CXXFLAGS=-DMOZ_ENABLE_JS_DUMP
+
+. "$topsrcdir/b2g/graphene/config/mozconfigs/common.override"
diff --git a/b2g/graphene/config/mozconfigs/win64/debug b/b2g/graphene/config/mozconfigs/win64/debug
new file mode 100644
index 0000000000..455ec1762d
--- /dev/null
+++ b/b2g/graphene/config/mozconfigs/win64/debug
@@ -0,0 +1,23 @@
+. "$topsrcdir/build/mozconfig.win-common"
+MOZ_AUTOMATION_L10N_CHECK=0
+. "$topsrcdir/b2g/graphene/config/mozconfigs/common"
+
+ac_add_options --target=x86_64-pc-mingw32
+ac_add_options --host=x86_64-pc-mingw32
+
+ac_add_options --enable-debug
+ac_add_options --enable-dmd
+ac_add_options --enable-profiling # needed for --enable-dmd to work on Windows
+ac_add_options --enable-signmar
+
+# Needed to enable breakpad in application.ini
+export MOZILLA_OFFICIAL=1
+
+# Package js shell.
+export MOZ_PACKAGE_JSSHELL=1
+
+. $topsrcdir/build/win64/mozconfig.vs-latest
+
+. "$topsrcdir/build/mozconfig.cache"
+
+. "$topsrcdir/b2g/graphene/config/mozconfigs/common.override"
diff --git a/b2g/graphene/config/mozconfigs/win64/nightly b/b2g/graphene/config/mozconfigs/win64/nightly
new file mode 100644
index 0000000000..73db616cc7
--- /dev/null
+++ b/b2g/graphene/config/mozconfigs/win64/nightly
@@ -0,0 +1,26 @@
+if [ "x$IS_NIGHTLY" = "xyes" ]; then
+ MOZ_AUTOMATION_UPLOAD_SYMBOLS=1
+ MOZ_AUTOMATION_UPDATE_PACKAGING=1
+fi
+
+. "$topsrcdir/b2g/graphene/config/mozconfigs/common"
+
+ac_add_options --target=x86_64-pc-mingw32
+ac_add_options --host=x86_64-pc-mingw32
+
+. $topsrcdir/build/win64/mozconfig.vs-latest
+
+ac_add_options --enable-update-channel=${MOZ_UPDATE_CHANNEL}
+ac_add_options --enable-jemalloc
+ac_add_options --enable-signmar
+
+# Nightlies only since this has a cost in performance
+ac_add_options --enable-js-diagnostics
+
+# Needed to enable breakpad in application.ini
+export MOZILLA_OFFICIAL=1
+
+# graphene Options
+export CXXFLAGS=-DMOZ_ENABLE_JS_DUMP
+
+. "$topsrcdir/b2g/graphene/config/mozconfigs/common.override"
diff --git a/b2g/graphene/confvars.sh b/b2g/graphene/confvars.sh
new file mode 100644
index 0000000000..7db9a7ee61
--- /dev/null
+++ b/b2g/graphene/confvars.sh
@@ -0,0 +1,53 @@
+# 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/.
+
+if test "$MOZ_HORIZON"; then
+MOZ_APP_BASENAME=Horizon
+else
+MOZ_APP_BASENAME=Graphene
+fi
+
+MOZ_APP_VENDOR=Mozilla
+MOZ_UPDATER=1
+
+MOZ_B2G=1
+MOZ_GRAPHENE=1
+
+MOZ_APP_VERSION=$FIREFOX_VERSION
+MOZ_APP_UA_NAME=Firefox
+
+MOZ_B2G_VERSION=2.6.0.0-prerelease
+MOZ_B2G_OS_NAME=Boot2Gecko
+
+MOZ_BRANDING_DIRECTORY=b2g/branding/unofficial
+MOZ_OFFICIAL_BRANDING_DIRECTORY=b2g/branding/official
+# MOZ_APP_DISPLAYNAME is set by branding/configure.sh
+
+MOZ_CAPTIVEDETECT=1
+
+MOZ_NO_SMART_CARDS=1
+MOZ_APP_STATIC_INI=1
+NSS_NO_LIBPKIX=1
+
+if test "$OS_TARGET" = "Android"; then
+MOZ_CAPTURE=1
+MOZ_RAW=1
+MOZ_AUDIO_CHANNEL_MANAGER=1
+fi
+
+MOZ_APP_ID={d1bfe7d9-c01e-4237-998b-7b5f960a4314}
+MOZ_TIME_MANAGER=1
+
+MOZ_TOOLKIT_SEARCH=
+MOZ_PLACES=
+MOZ_B2G=1
+
+MOZ_JSDOWNLOADS=1
+
+MOZ_BUNDLED_FONTS=1
+
+export JS_GC_SMALL_CHUNK_SIZE=1
+
+# Include the DevTools client, not just the server (which is the default)
+MOZ_DEVTOOLS=all
diff --git a/b2g/graphene/graphene.js b/b2g/graphene/graphene.js
new file mode 100644
index 0000000000..0c8abf42fa
--- /dev/null
+++ b/b2g/graphene/graphene.js
@@ -0,0 +1,59 @@
+// See http://dxr.mozilla.org/mozilla-central/source/dom/webidl/KeyEvent.webidl
+// for keyCode values.
+// Default value is F5
+pref("b2g.reload_key", '{ "key": 116, "shift": false, "ctrl": false, "alt": false, "meta": false }');
+
+#ifdef MOZ_HORIZON
+pref("b2g.default.start_manifest_url", "https://mozvr.github.io/horizon/web/manifest.webapp");
+pref("dom.vr.enabled", true);
+pref("dom.ipc.tabs.disabled", true);
+#else
+pref("b2g.default.start_manifest_url", "https://mozilla.github.io/browser.html/manifest.webapp");
+pref("dom.ipc.tabs.disabled", false);
+#endif
+
+pref("javascript.options.discardSystemSource", false);
+pref("browser.dom.window.dump.enabled", true);
+pref("browser.ignoreNativeFrameTextSelection", false);
+pref("dom.meta-viewport.enabled", false);
+pref("full-screen-api.ignore-widgets", false);
+pref("image.high_quality_downscaling.enabled", true);
+pref("dom.w3c_touch_events.enabled", 0);
+pref("font.size.inflation.minTwips", 0);
+pref("browser.enable_click_image_resizing", true);
+pref("layout.css.scroll-snap.enabled", true);
+pref("dom.mozInputMethod.enabled", false);
+pref("browser.autofocus", true);
+pref("layers.async-pan-zoom.enabled", false);
+pref("network.predictor.enabled", true);
+
+// No AccessibleCaret
+pref("layout.accessiblecaret.enabled", false);
+
+// To be removed once bug 942756 is fixed.
+pref("devtools.debugger.unix-domain-socket", "6000");
+
+pref("devtools.debugger.forbid-certified-apps", false);
+pref("devtools.debugger.prompt-connection", false);
+
+// Update url.
+pref("app.update.url", "https://aus4.mozilla.org/update/3/%PRODUCT%/%VERSION%/%BUILD_ID%/%BUILD_TARGET%/%LOCALE%/%CHANNEL%/%OS_VERSION%/%DISTRIBUTION%/%DISTRIBUTION_VERSION%/update.xml");
+
+pref("b2g.nativeWindowGeometry.width", 700);
+pref("b2g.nativeWindowGeometry.height", 600);
+pref("b2g.nativeWindowGeometry.screenX", -1); // center
+pref("b2g.nativeWindowGeometry.screenY", -1); // center
+pref("b2g.nativeWindowGeometry.fullscreen", false);
+
+pref("media.useAudioChannelService", false);
+
+#ifdef ENABLE_MARIONETTE
+pref("b2g.is_mulet", true);
+#endif
+
+// Most DevTools prefs are set from the shared file
+// devtools/client/preferences/devtools.js, but this one is currently set
+// per-app or per-channel.
+// Number of usages of the web console or scratchpad. If this is less than 5,
+// then pasting code into the web console or scratchpad is disabled
+pref("devtools.selfxss.count", 5);
diff --git a/b2g/graphene/moz.configure b/b2g/graphene/moz.configure
new file mode 100644
index 0000000000..e9533bbda3
--- /dev/null
+++ b/b2g/graphene/moz.configure
@@ -0,0 +1,7 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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('../common.configure')
diff --git a/b2g/graphene/settings.json b/b2g/graphene/settings.json
new file mode 100644
index 0000000000..75bd243a43
--- /dev/null
+++ b/b2g/graphene/settings.json
@@ -0,0 +1,3 @@
+{
+ "apz.overscroll.enabled": false
+}
diff --git a/b2g/installer/Makefile.in b/b2g/installer/Makefile.in
new file mode 100644
index 0000000000..63d8f37e42
--- /dev/null
+++ b/b2g/installer/Makefile.in
@@ -0,0 +1,135 @@
+# 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/.
+
+STANDALONE_MAKEFILE := 1
+
+include $(topsrcdir)/config/rules.mk
+
+MOZ_PKG_REMOVALS = $(srcdir)/removed-files.in
+
+MOZ_PKG_MANIFEST = $(srcdir)/package-manifest.in
+
+ifdef MOZ_CHROME_MULTILOCALE
+MOZ_PKG_MANIFEST_DEPS = locale-manifest.in
+
+DEFINES += -DPKG_LOCALE_MANIFEST=$(CURDIR)/locale-manifest.in
+endif
+
+DEFINES += \
+ -DMOZ_APP_NAME=$(MOZ_APP_NAME) \
+ -DPREF_DIR=$(PREF_DIR) \
+ $(NULL)
+
+DEFINES += -DJAREXT=
+
+DEFINES += -DMOZ_CHILD_PROCESS_NAME=$(MOZ_CHILD_PROCESS_NAME)
+
+# Set MSVC dlls version to package, if any.
+ifdef MOZ_NO_DEBUG_RTL
+ifdef WIN32_REDIST_DIR
+DEFINES += -DMOZ_PACKAGE_MSVC_DLLS=1
+DEFINES += -DMSVC_C_RUNTIME_DLL=$(MSVC_C_RUNTIME_DLL)
+DEFINES += -DMSVC_CXX_RUNTIME_DLL=$(MSVC_CXX_RUNTIME_DLL)
+endif
+ifdef WIN_UCRT_REDIST_DIR
+DEFINES += -DMOZ_PACKAGE_WIN_UCRT_DLLS=1
+endif
+endif
+
+ifdef MOZ_DEBUG
+DEFINES += -DMOZ_DEBUG=1
+endif
+
+ifdef ENABLE_MARIONETTE
+DEFINES += -DENABLE_MARIONETTE=1
+endif
+
+MOZ_PACKAGER_MINIFY=1
+
+ifeq (cocoa,$(MOZ_WIDGET_TOOLKIT))
+ifndef _APPNAME
+_APPNAME = $(MOZ_MACBUNDLE_NAME)
+endif
+ifndef _BINPATH
+_BINPATH = /$(_APPNAME)/Contents/MacOS
+endif
+ifndef _RESPATH
+_RESPATH = /$(_APPNAME)/Contents/Resources
+endif
+endif
+
+include $(topsrcdir)/toolkit/mozapps/installer/packager.mk
+
+# Note that JS_BINARY can be defined in packager.mk, so this test must come after
+# including that file. MOZ_PACKAGER_MINIFY_JS is used in packager.mk, but since
+# recipe evaluation is deferred, we can set it here after the inclusion.
+ifneq (,$(JS_BINARY))
+ifndef MOZ_DEBUG
+MOZ_PACKAGER_MINIFY_JS=1
+endif
+endif
+
+ifeq (bundle, $(MOZ_FS_LAYOUT))
+BINPATH = $(_BINPATH)
+RESPATH = $(_RESPATH)
+DEFINES += -DAPPNAME=$(_APPNAME)
+else
+# Every other platform just winds up in dist/bin
+BINPATH = bin
+RESPATH = bin
+endif
+DEFINES += -DBINPATH=$(BINPATH)
+DEFINES += -DRESPATH=$(RESPATH)
+
+LPROJ_ROOT = $(firstword $(subst -, ,$(AB_CD)))
+ifeq (cocoa,$(MOZ_WIDGET_TOOLKIT))
+ifeq (zh-TW,$(AB_CD))
+LPROJ_ROOT := $(subst -,_,$(AB_CD))
+endif
+endif
+DEFINES += -DLPROJ_ROOT=$(LPROJ_ROOT)
+
+ifneq (,$(filter WINNT Darwin Android,$(OS_TARGET)))
+DEFINES += -DMOZ_SHARED_MOZGLUE=1
+endif
+
+DEFINES += -DMOZ_ICU_VERSION=$(MOZ_ICU_VERSION)
+ifdef MOZ_SYSTEM_ICU
+DEFINES += -DMOZ_SYSTEM_ICU
+endif
+DEFINES += -DMOZ_ICU_DBG_SUFFIX=$(MOZ_ICU_DBG_SUFFIX)
+DEFINES += -DICU_DATA_FILE=$(ICU_DATA_FILE)
+
+ifneq (,$(filter gtk%,$(MOZ_WIDGET_TOOLKIT)))
+DEFINES += -DMOZ_GTK=1
+ifeq ($(MOZ_WIDGET_TOOLKIT),gtk3)
+DEFINES += -DMOZ_GTK3=1
+endif
+endif
+
+ifdef MOZ_CHROME_MULTILOCALE
+locale-manifest.in: $(GLOBAL_DEPS) FORCE
+ printf '\n[multilocale]\n' > $@
+ for LOCALE in $(MOZ_CHROME_MULTILOCALE) ;\
+ do \
+ printf '$(BINPATH)/chrome/'"$$LOCALE"'$(JAREXT)\n' >> $@; \
+ printf '$(BINPATH)/chrome/'"$$LOCALE"'.manifest\n' >> $@; \
+ done
+
+GARBAGE += locale-manifest.in
+endif
+
+ifdef FXOS_SIMULATOR
+export MAKE
+
+.PHONY: simulator
+simulator: make-package
+ @echo 'Building simulator addon...'
+ $(PYTHON) $(topsrcdir)/b2g/simulator/build_xpi.py $(MOZ_PKG_PLATFORM)
+
+libs:: simulator
+
+# Ensure copying Simulator xpi to ftp
+UPLOAD_EXTRA_FILES += fxos-simulator-*-*.xpi
+endif
diff --git a/b2g/installer/flash.bat b/b2g/installer/flash.bat
new file mode 100755
index 0000000000..9b5093677d
--- /dev/null
+++ b/b2g/installer/flash.bat
@@ -0,0 +1,73 @@
+@ECHO OFF
+
+REM read config file
+setlocal ENABLEDELAYEDEXPANSION
+set loop=0
+for /F "tokens=*" %%A in (.config) do (
+ SET /A loop=!loop! + 1
+ set %%A
+)
+
+set DEVICE_FOUND=0
+
+REM nexus has device instead of product name
+IF [%PRODUCT_NAME%]==[] (
+ set PRODUCT_NAME=%DEVICE%
+)
+
+REM if nexus 4 assume you are in fastboot mode, can't seem to find drivers
+IF [%DEVICE%]==[mako] (
+call :flash
+)
+
+REM push device from adb to fastboot mode
+win_adb kill-server
+win_adb devices
+win_adb get-state > devicestate.txt
+set /p DEVICE_STATE= < devicestate.txt
+
+IF NOT "%DEVICE_STATE%"=="device" (
+ ECHO Please check :
+ ECHO 1. to make sure that only one device is connected to the computer
+ ECHO 2. the device is turned on with the screen showing
+ ECHO 3. the device is set to debugging via USB : ADB Only or ADB and Devtools
+ ECHO 4. the device drivers are installed on the computer.
+ Del devicestate.txt
+ PAUSE
+ EXIT /b
+)
+
+Del devicestate.txt
+win_adb reboot bootloader
+
+TIMEOUT 5
+
+:flash
+win_fastboot devices 2> fastboot_state.txt
+set /p FASTBOOT_STATE= < fastboot_state.txt
+
+IF NOT [%FASTBOOT_STATE%]==[] (
+ ECHO Please check :
+ ECHO 1. to make sure that only one device is connected to the computer
+ ECHO 2. the device is turned on with an indication that the device is in fastboot mode
+ ECHO 3. the fastboot drivers are installed on the computer.
+ Del fastboot_state.txt
+ PAUSE
+ EXIT /b
+)
+
+Del fastboot_state.txt
+
+ECHO "Flashing build. If nothing mentions that it flashed anything and it looks stuck, make sure you have the drivers installed."
+win_fastboot flash boot out/target/product/%PRODUCT_NAME%/boot.img
+win_fastboot flash system out/target/product/%PRODUCT_NAME%/system.img
+win_fastboot flash persist out/target/product/%PRODUCT_NAME%/persist.img
+win_fastboot flash recovery out/target/product/%PRODUCT_NAME%/recovery.img
+win_fastboot flash cache out/target/product/%PRODUCT_NAME%/cache.img
+win_fastboot flash userdata out/target/product/%PRODUCT_NAME%/userdata.img
+
+ECHO "Done..."
+
+win_fastboot reboot
+echo "Just close the windows as you wish."
+TIMEOUT 5
diff --git a/b2g/installer/moz.build b/b2g/installer/moz.build
new file mode 100644
index 0000000000..28919c271d
--- /dev/null
+++ b/b2g/installer/moz.build
@@ -0,0 +1,6 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
diff --git a/b2g/installer/package-manifest.in b/b2g/installer/package-manifest.in
new file mode 100644
index 0000000000..a5b886c421
--- /dev/null
+++ b/b2g/installer/package-manifest.in
@@ -0,0 +1,845 @@
+; 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/.
+
+; Package file for the B2G build.
+;
+; File format:
+;
+; [] designates a toplevel component. Example: [xpcom]
+; - in front of a file specifies it to be removed from the destination
+; * wildcard support to recursively copy the entire directory
+; ; file comment
+;
+
+#filter substitution
+
+#ifdef XP_MACOSX
+; Mac bundle stuff
+@APPNAME@/Contents/Info.plist
+@APPNAME@/Contents/PkgInfo
+@APPNAME@/Contents/Plug-Ins/
+@RESPATH@/@MOZ_APP_NAME@.icns
+@RESPATH@/@LPROJ_ROOT@.lproj/*
+#endif
+
+[@AB_CD@]
+@RESPATH@/chrome/@AB_CD@@JAREXT@
+@RESPATH@/chrome/@AB_CD@.manifest
+@RESPATH@/@PREF_DIR@/b2g-l10n.js
+@RESPATH@/searchplugins/*
+#ifdef MOZ_UPDATER
+@RESPATH@/update.locale
+@RESPATH@/updater.ini
+#endif
+@RESPATH@/dictionaries/*
+@RESPATH@/hyphenation/*
+#ifdef XP_WIN32
+@BINPATH@/uninstall/helper.exe
+#endif
+
+[xpcom]
+@RESPATH@/dependentlibs.list
+#ifndef MOZ_STATIC_JS
+@BINPATH@/@DLL_PREFIX@mozjs@DLL_SUFFIX@
+#endif
+#ifndef MOZ_FOLD_LIBS
+@BINPATH@/@DLL_PREFIX@plc4@DLL_SUFFIX@
+@BINPATH@/@DLL_PREFIX@plds4@DLL_SUFFIX@
+@BINPATH@/@DLL_PREFIX@nspr4@DLL_SUFFIX@
+#endif
+#ifdef MOZ_DMD
+@BINPATH@/@DLL_PREFIX@dmd@DLL_SUFFIX@
+#endif
+#ifdef XP_MACOSX
+@BINPATH@/XUL
+#else
+@BINPATH@/@DLL_PREFIX@xul@DLL_SUFFIX@
+#endif
+#ifdef XP_MACOSX
+@BINPATH@/@MOZ_CHILD_PROCESS_NAME@.app/
+#else
+@BINPATH@/@MOZ_CHILD_PROCESS_NAME@
+#endif
+#ifdef XP_WIN32
+#if MOZ_PACKAGE_MSVC_DLLS
+@BINPATH@/@MSVC_C_RUNTIME_DLL@
+@BINPATH@/@MSVC_CXX_RUNTIME_DLL@
+#endif
+#if MOZ_PACKAGE_WIN_UCRT_DLLS
+@BINPATH@/api-ms-win-*.dll
+@BINPATH@/ucrtbase.dll
+#endif
+#endif
+#ifndef MOZ_SYSTEM_ICU
+@RESPATH@/@ICU_DATA_FILE@
+#endif
+#ifdef MOZ_SHARED_MOZGLUE
+@BINPATH@/@DLL_PREFIX@mozglue@DLL_SUFFIX@
+#endif
+#ifdef ANDROID
+@RESPATH@/AndroidManifest.xml
+@RESPATH@/resources.arsc
+@RESPATH@/classes.dex
+@RESPATH@/res/drawable
+@RESPATH@/res/drawable-hdpi
+@RESPATH@/res/layout
+#endif
+#ifdef MOZ_GTK3
+@BINPATH@/@DLL_PREFIX@mozgtk@DLL_SUFFIX@
+@BINPATH@/gtk2/@DLL_PREFIX@mozgtk@DLL_SUFFIX@
+#endif
+
+[browser]
+; [Base Browser Files]
+#ifndef XP_UNIX
+@BINPATH@/@MOZ_APP_NAME@.exe
+#else
+@BINPATH@/@MOZ_APP_NAME@-bin
+@BINPATH@/@MOZ_APP_NAME@
+#endif
+@RESPATH@/application.ini
+@RESPATH@/platform.ini
+#ifndef MOZ_FOLD_LIBS
+@BINPATH@/@DLL_PREFIX@mozsqlite3@DLL_SUFFIX@
+#endif
+@BINPATH@/@DLL_PREFIX@lgpllibs@DLL_SUFFIX@
+@RESPATH@/blocklist.xml
+@RESPATH@/ua-update.json
+@RESPATH@/defaults/settings.json
+#ifdef XP_UNIX
+#ifndef XP_MACOSX
+@RESPATH@/run-mozilla.sh
+#endif
+#endif
+#ifdef MOZ_WEBSPEECH_MODELS
+@RESPATH@/models/
+#endif
+
+; [Components]
+@RESPATH@/components/components.manifest
+@RESPATH@/components/alerts.xpt
+#ifdef ACCESSIBILITY
+#ifdef XP_WIN32
+@BINPATH@/AccessibleMarshal.dll
+#endif
+@RESPATH@/components/accessibility.xpt
+#endif
+@RESPATH@/components/appshell.xpt
+@RESPATH@/components/appstartup.xpt
+@RESPATH@/components/autocomplete.xpt
+@RESPATH@/components/autoconfig.xpt
+@RESPATH@/components/browsercompsbase.xpt
+@RESPATH@/components/browser-element.xpt
+@RESPATH@/components/browser-feeds.xpt
+@RESPATH@/components/caps.xpt
+@RESPATH@/components/chardet.xpt
+@RESPATH@/components/chrome.xpt
+@RESPATH@/components/commandhandler.xpt
+@RESPATH@/components/commandlines.xpt
+@RESPATH@/components/composer.xpt
+@RESPATH@/components/content_events.xpt
+@RESPATH@/components/content_geckomediaplugins.xpt
+@RESPATH@/components/content_html.xpt
+@RESPATH@/components/content_xslt.xpt
+@RESPATH@/components/cookie.xpt
+@RESPATH@/components/directory.xpt
+@RESPATH@/components/diskspacewatcher.xpt
+@RESPATH@/components/docshell.xpt
+@RESPATH@/components/dom.xpt
+@RESPATH@/components/dom_activities.xpt
+@RESPATH@/components/dom_apps.xpt
+@RESPATH@/components/dom_audiochannel.xpt
+@RESPATH@/components/dom_base.xpt
+@RESPATH@/components/dom_system.xpt
+@RESPATH@/components/dom_workers.xpt
+#ifdef MOZ_WIDGET_GONK
+@RESPATH@/components/dom_wifi.xpt
+@RESPATH@/components/dom_system_gonk.xpt
+#endif
+@RESPATH@/components/dom_canvas.xpt
+@RESPATH@/components/dom_core.xpt
+@RESPATH@/components/dom_css.xpt
+@RESPATH@/components/dom_events.xpt
+@RESPATH@/components/dom_geolocation.xpt
+@RESPATH@/components/dom_media.xpt
+@RESPATH@/components/dom_network.xpt
+#ifdef MOZ_SECUREELEMENT
+@RESPATH@/components/dom_secureelement.xpt
+#endif
+@RESPATH@/components/dom_notification.xpt
+@RESPATH@/components/dom_html.xpt
+@RESPATH@/components/dom_offline.xpt
+@RESPATH@/components/dom_json.xpt
+@RESPATH@/components/dom_messages.xpt
+@RESPATH@/components/dom_power.xpt
+@RESPATH@/components/dom_push.xpt
+@RESPATH@/components/dom_quota.xpt
+@RESPATH@/components/dom_range.xpt
+@RESPATH@/components/dom_security.xpt
+@RESPATH@/components/dom_settings.xpt
+@RESPATH@/components/dom_permissionsettings.xpt
+@RESPATH@/components/dom_sidebar.xpt
+@RESPATH@/components/dom_cellbroadcast.xpt
+@RESPATH@/components/dom_icc.xpt
+@RESPATH@/components/dom_mobilemessage.xpt
+@RESPATH@/components/dom_storage.xpt
+@RESPATH@/components/dom_stylesheets.xpt
+@RESPATH@/components/dom_threads.xpt
+@RESPATH@/components/dom_traversal.xpt
+@RESPATH@/components/dom_tv.xpt
+@RESPATH@/components/dom_inputport.xpt
+@RESPATH@/components/dom_views.xpt
+#ifdef MOZ_WEBSPEECH
+@RESPATH@/components/dom_webspeechrecognition.xpt
+#endif
+@RESPATH@/components/dom_xbl.xpt
+@RESPATH@/components/dom_xhr.xpt
+@RESPATH@/components/dom_xpath.xpt
+@RESPATH@/components/dom_xul.xpt
+@RESPATH@/components/dom_time.xpt
+@RESPATH@/components/dom_presentation.xpt
+@RESPATH@/components/downloads.xpt
+@RESPATH@/components/editor.xpt
+@RESPATH@/components/embed_base.xpt
+@RESPATH@/components/extensions.xpt
+@RESPATH@/components/exthandler.xpt
+@RESPATH@/components/exthelper.xpt
+@RESPATH@/components/fastfind.xpt
+@RESPATH@/components/feeds.xpt
+#ifdef MOZ_GTK
+@RESPATH@/components/filepicker.xpt
+#endif
+@RESPATH@/components/find.xpt
+@RESPATH@/components/gfx.xpt
+@RESPATH@/components/gaia_chrome.xpt
+@RESPATH@/components/hal.xpt
+@RESPATH@/components/html5.xpt
+@RESPATH@/components/htmlparser.xpt
+@RESPATH@/components/identity.xpt
+@RESPATH@/components/imglib2.xpt
+@RESPATH@/components/inspector.xpt
+@RESPATH@/components/intl.xpt
+@RESPATH@/components/jar.xpt
+@RESPATH@/components/jsdebugger.xpt
+@RESPATH@/components/jsdownloads.xpt
+@RESPATH@/components/jsinspector.xpt
+@RESPATH@/components/layout_base.xpt
+#ifdef NS_PRINTING
+@RESPATH@/components/layout_printing.xpt
+#endif
+@RESPATH@/components/layout_xul_tree.xpt
+@RESPATH@/components/layout_xul.xpt
+@RESPATH@/components/locale.xpt
+@RESPATH@/components/lwbrk.xpt
+#ifdef MOZ_ENABLE_PROFILER_SPS
+@RESPATH@/components/memory_profiler.xpt
+#endif
+@RESPATH@/components/migration.xpt
+@RESPATH@/components/mimetype.xpt
+@RESPATH@/components/mozfind.xpt
+@RESPATH@/components/necko_about.xpt
+@RESPATH@/components/necko_cache.xpt
+@RESPATH@/components/necko_cache2.xpt
+@RESPATH@/components/necko_cookie.xpt
+@RESPATH@/components/necko_dns.xpt
+@RESPATH@/components/necko_file.xpt
+@RESPATH@/components/necko_ftp.xpt
+@RESPATH@/components/necko_http.xpt
+@RESPATH@/components/necko_mdns.xpt
+@RESPATH@/components/necko_res.xpt
+@RESPATH@/components/necko_socket.xpt
+@RESPATH@/components/necko_strconv.xpt
+@RESPATH@/components/necko_viewsource.xpt
+@RESPATH@/components/necko_websocket.xpt
+@RESPATH@/components/necko_wifi.xpt
+@RESPATH@/components/necko_wyciwyg.xpt
+@RESPATH@/components/necko.xpt
+@RESPATH@/components/loginmgr.xpt
+@RESPATH@/components/parentalcontrols.xpt
+#ifdef MOZ_WEBRTC
+@RESPATH@/components/peerconnection.xpt
+#endif
+@RESPATH@/components/places.xpt
+@RESPATH@/components/plugin.xpt
+@RESPATH@/components/pref.xpt
+@RESPATH@/components/prefetch.xpt
+#ifdef MOZ_ENABLE_PROFILER_SPS
+@RESPATH@/components/profiler.xpt
+#endif
+@RESPATH@/components/proxyObject.xpt
+@RESPATH@/components/rdf.xpt
+@RESPATH@/components/satchel.xpt
+@RESPATH@/components/saxparser.xpt
+@RESPATH@/components/sessionstore.xpt
+@RESPATH@/components/services-crypto-component.xpt
+@RESPATH@/components/captivedetect.xpt
+@RESPATH@/components/shellservice.xpt
+@RESPATH@/components/shistory.xpt
+@RESPATH@/components/spellchecker.xpt
+@RESPATH@/components/storage.xpt
+@RESPATH@/components/telemetry.xpt
+@RESPATH@/components/toolkit_asyncshutdown.xpt
+@RESPATH@/components/toolkit_filewatcher.xpt
+@RESPATH@/components/toolkit_finalizationwitness.xpt
+@RESPATH@/components/toolkit_formautofill.xpt
+@RESPATH@/components/toolkit_osfile.xpt
+@RESPATH@/components/toolkit_securityreporter.xpt
+@RESPATH@/components/toolkit_perfmonitoring.xpt
+@RESPATH@/components/toolkit_xulstore.xpt
+@RESPATH@/components/toolkitprofile.xpt
+#ifdef MOZ_ENABLE_XREMOTE
+@RESPATH@/components/toolkitremote.xpt
+#endif
+@RESPATH@/components/txtsvc.xpt
+@RESPATH@/components/txmgr.xpt
+#ifdef MOZ_USE_NATIVE_UCONV
+@RESPATH@/components/ucnative.xpt
+#endif
+@RESPATH@/components/uconv.xpt
+@RESPATH@/components/unicharutil.xpt
+@RESPATH@/components/update.xpt
+@RESPATH@/components/uriloader.xpt
+@RESPATH@/components/urlformatter.xpt
+@RESPATH@/components/webBrowser_core.xpt
+@RESPATH@/components/webbrowserpersist.xpt
+@RESPATH@/components/webshell_idls.xpt
+@RESPATH@/components/widget.xpt
+#ifdef XP_MACOSX
+@RESPATH@/components/widget_cocoa.xpt
+#endif
+#ifdef ANDROID
+@RESPATH@/components/widget_android.xpt
+#endif
+@RESPATH@/components/windowds.xpt
+@RESPATH@/components/windowwatcher.xpt
+@RESPATH@/components/xpcom_base.xpt
+@RESPATH@/components/xpcom_system.xpt
+@RESPATH@/components/xpcom_components.xpt
+@RESPATH@/components/xpcom_ds.xpt
+@RESPATH@/components/xpcom_io.xpt
+@RESPATH@/components/xpcom_threads.xpt
+@RESPATH@/components/xpcom_xpti.xpt
+@RESPATH@/components/xpconnect.xpt
+@RESPATH@/components/xulapp.xpt
+@RESPATH@/components/xul.xpt
+@RESPATH@/components/xultmpl.xpt
+@RESPATH@/components/zipwriter.xpt
+
+; JavaScript components
+@RESPATH@/components/ConsoleAPI.manifest
+@RESPATH@/components/ConsoleAPIStorage.js
+@RESPATH@/components/BrowserElementParent.manifest
+@RESPATH@/components/BrowserElementParent.js
+@RESPATH@/components/BrowserElementProxy.manifest
+@RESPATH@/components/BrowserElementProxy.js
+@RESPATH@/components/PhoneNumberService.js
+@RESPATH@/components/PhoneNumberService.manifest
+@RESPATH@/components/NotificationStorage.js
+@RESPATH@/components/NotificationStorage.manifest
+@RESPATH@/components/PermissionSettings.js
+@RESPATH@/components/PermissionSettings.manifest
+@RESPATH@/components/PermissionPromptService.js
+@RESPATH@/components/PermissionPromptService.manifest
+@RESPATH@/components/FeedProcessor.manifest
+@RESPATH@/components/FeedProcessor.js
+@RESPATH@/components/BrowserFeeds.manifest
+@RESPATH@/components/FeedConverter.js
+@RESPATH@/components/FeedWriter.js
+@RESPATH@/components/WebContentConverter.js
+@RESPATH@/components/BrowserComponents.manifest
+@RESPATH@/components/nsBrowserContentHandler.js
+@RESPATH@/components/nsBrowserGlue.js
+@RESPATH@/components/nsSetDefaultBrowser.manifest
+@RESPATH@/components/nsSetDefaultBrowser.js
+@RESPATH@/components/toolkitsearch.manifest
+@RESPATH@/components/nsTryToClose.manifest
+@RESPATH@/components/nsTryToClose.js
+@RESPATH@/components/passwordmgr.manifest
+@RESPATH@/components/nsLoginInfo.js
+@RESPATH@/components/nsLoginManager.js
+@RESPATH@/components/nsLoginManagerPrompter.js
+@RESPATH@/components/TooltipTextProvider.js
+@RESPATH@/components/TooltipTextProvider.manifest
+@RESPATH@/components/NetworkGeolocationProvider.manifest
+@RESPATH@/components/NetworkGeolocationProvider.js
+@RESPATH@/components/TVSimulatorService.js
+@RESPATH@/components/TVSimulatorService.manifest
+#ifdef MOZ_WEBRTC
+@RESPATH@/components/PeerConnection.js
+@RESPATH@/components/PeerConnection.manifest
+#endif
+@RESPATH@/components/SiteSpecificUserAgent.js
+@RESPATH@/components/SiteSpecificUserAgent.manifest
+@RESPATH@/components/storage-json.js
+@RESPATH@/components/crypto-SDR.js
+@RESPATH@/components/Downloads.manifest
+@RESPATH@/components/DownloadLegacy.js
+@RESPATH@/components/nsSidebar.manifest
+@RESPATH@/components/nsSidebar.js
+@RESPATH@/components/nsAsyncShutdown.manifest
+@RESPATH@/components/nsAsyncShutdown.js
+@RESPATH@/components/htmlMenuBuilder.js
+@RESPATH@/components/htmlMenuBuilder.manifest
+@RESPATH@/components/PresentationDeviceInfoManager.manifest
+@RESPATH@/components/PresentationDeviceInfoManager.js
+@RESPATH@/components/BuiltinProviders.manifest
+@RESPATH@/components/PresentationControlService.js
+@RESPATH@/components/PresentationDataChannelSessionTransport.js
+@RESPATH@/components/PresentationDataChannelSessionTransport.manifest
+
+#ifdef MOZ_SECUREELEMENT
+@RESPATH@/components/ACEService.js
+@RESPATH@/components/ACEService.manifest
+@RESPATH@/components/GPAccessRulesManager.js
+@RESPATH@/components/GPAccessRulesManager.manifest
+@RESPATH@/components/SecureElement.js
+@RESPATH@/components/SecureElement.manifest
+@RESPATH@/components/UiccConnector.js
+@RESPATH@/components/UiccConnector.manifest
+#endif
+
+; WiFi, NetworkManager, NetworkStats
+#ifdef MOZ_WIDGET_GONK
+@RESPATH@/components/DOMWifiManager.js
+@RESPATH@/components/DOMWifiManager.manifest
+@RESPATH@/components/DOMWifiP2pManager.js
+@RESPATH@/components/DOMWifiP2pManager.manifest
+@RESPATH@/components/EthernetManager.js
+@RESPATH@/components/EthernetManager.manifest
+@RESPATH@/components/NetworkInterfaceListService.js
+@RESPATH@/components/NetworkInterfaceListService.manifest
+@RESPATH@/components/NetworkManager.js
+@RESPATH@/components/NetworkManager.manifest
+@RESPATH@/components/NetworkService.js
+@RESPATH@/components/NetworkService.manifest
+@RESPATH@/components/NetworkStatsManager.js
+@RESPATH@/components/NetworkStatsManager.manifest
+@RESPATH@/components/NetworkStatsServiceProxy.js
+@RESPATH@/components/NetworkStatsServiceProxy.manifest
+@RESPATH@/components/TetheringService.js
+@RESPATH@/components/TetheringService.manifest
+@RESPATH@/components/WifiWorker.js
+@RESPATH@/components/WifiWorker.manifest
+#endif // MOZ_WIDGET_GONK
+
+; Tethering
+#ifdef MOZ_WIDGET_GONK
+@RESPATH@/components/TetheringManager.js
+@RESPATH@/components/TetheringManager.manifest
+#endif
+
+; RIL
+#if defined(MOZ_WIDGET_GONK) && defined(MOZ_B2G_RIL)
+@RESPATH@/components/CellBroadcastService.js
+@RESPATH@/components/CellBroadcastService.manifest
+@RESPATH@/components/DataCallManager.js
+@RESPATH@/components/DataCallManager.manifest
+@RESPATH@/components/IccService.js
+@RESPATH@/components/IccService.manifest
+@RESPATH@/components/MmsService.js
+@RESPATH@/components/MmsService.manifest
+@RESPATH@/components/MobileMessageDatabaseService.js
+@RESPATH@/components/MobileMessageDatabaseService.manifest
+#ifndef DISABLE_MOZ_RIL_GEOLOC
+@RESPATH@/components/DataCallInterfaceService.js
+@RESPATH@/components/DataCallInterfaceService.manifest
+@RESPATH@/components/RadioInterfaceLayer.js
+@RESPATH@/components/RadioInterfaceLayer.manifest
+@RESPATH@/components/SmsService.js
+@RESPATH@/components/SmsService.manifest
+#endif
+@RESPATH@/components/StkCmdFactory.js
+@RESPATH@/components/StkCmdFactory.manifest
+@RESPATH@/components/RILSystemMessengerHelper.js
+@RESPATH@/components/RILSystemMessengerHelper.manifest
+#ifndef DISABLE_MOZ_RIL_GEOLOC
+#endif
+#endif // MOZ_WIDGET_GONK && MOZ_B2G_RIL
+
+#ifndef MOZ_WIDGET_GONK
+@RESPATH@/components/addonManager.js
+@RESPATH@/components/amContentHandler.js
+@RESPATH@/components/amInstallTrigger.js
+@RESPATH@/components/amWebInstallListener.js
+
+@RESPATH@/components/OopCommandLine.js
+@RESPATH@/components/CommandLine.js
+#endif
+@RESPATH@/components/extensions.manifest
+@RESPATH@/components/nsBlocklistService.js
+@RESPATH@/components/BootstrapCommandLine.js
+
+#ifdef MOZ_UPDATER
+@RESPATH@/components/nsUpdateService.manifest
+@RESPATH@/components/nsUpdateService.js
+@RESPATH@/components/nsUpdateServiceStub.js
+#endif
+@RESPATH@/components/nsUpdateTimerManager.manifest
+@RESPATH@/components/nsUpdateTimerManager.js
+@RESPATH@/components/pluginGlue.manifest
+@RESPATH@/components/nsSessionStore.manifest
+@RESPATH@/components/nsSessionStartup.js
+@RESPATH@/components/nsSessionStore.js
+@RESPATH@/components/nsURLFormatter.manifest
+@RESPATH@/components/nsURLFormatter.js
+@RESPATH@/components/@DLL_PREFIX@browsercomps@DLL_SUFFIX@
+@RESPATH@/components/txEXSLTRegExFunctions.manifest
+@RESPATH@/components/txEXSLTRegExFunctions.js
+@RESPATH@/components/toolkitplaces.manifest
+@RESPATH@/components/nsLivemarkService.js
+@RESPATH@/components/nsTaggingService.js
+@RESPATH@/components/nsPlacesDBFlush.js
+@RESPATH@/components/UnifiedComplete.js
+@RESPATH@/components/nsPlacesExpiration.js
+@RESPATH@/components/PlacesCategoriesStarter.js
+@RESPATH@/components/nsDefaultCLH.manifest
+@RESPATH@/components/nsDefaultCLH.js
+@RESPATH@/components/nsContentPrefService.manifest
+@RESPATH@/components/nsContentPrefService.js
+@RESPATH@/components/nsContentDispatchChooser.manifest
+@RESPATH@/components/nsContentDispatchChooser.js
+@RESPATH@/components/nsHandlerService.manifest
+@RESPATH@/components/nsHandlerService.js
+@RESPATH@/components/nsWebHandlerApp.manifest
+@RESPATH@/components/nsWebHandlerApp.js
+@RESPATH@/components/satchel.manifest
+@RESPATH@/components/nsFormAutoComplete.js
+@RESPATH@/components/nsFormHistory.js
+@RESPATH@/components/FormHistoryStartup.js
+@RESPATH@/components/nsInputListAutoComplete.js
+@RESPATH@/components/formautofill.manifest
+@RESPATH@/components/FormAutofillContentService.js
+@RESPATH@/components/FormAutofillStartup.js
+@RESPATH@/components/CSSUnprefixingService.js
+@RESPATH@/components/CSSUnprefixingService.manifest
+@RESPATH@/components/contentAreaDropListener.manifest
+@RESPATH@/components/contentAreaDropListener.js
+@RESPATH@/components/messageWakeupService.js
+@RESPATH@/components/messageWakeupService.manifest
+@RESPATH@/components/SettingsManager.js
+@RESPATH@/components/SettingsManager.manifest
+@RESPATH@/components/SettingsService.js
+@RESPATH@/components/SettingsService.manifest
+@RESPATH@/components/webvtt.xpt
+@RESPATH@/components/WebVTT.manifest
+@RESPATH@/components/WebVTTParserWrapper.js
+#ifdef MOZ_SECUREELEMENT
+@RESPATH@/components/DOMSecureElement.manifest
+@RESPATH@/components/DOMSecureElement.js
+#endif
+@RESPATH@/components/nsINIProcessor.manifest
+@RESPATH@/components/nsINIProcessor.js
+@RESPATH@/components/nsPrompter.manifest
+@RESPATH@/components/nsPrompter.js
+@RESPATH@/components/servicesComponents.manifest
+@RESPATH@/components/cryptoComponents.manifest
+@RESPATH@/components/CaptivePortalDetectComponents.manifest
+@RESPATH@/components/captivedetect.js
+@RESPATH@/components/TelemetryStartup.js
+@RESPATH@/components/TelemetryStartup.manifest
+@RESPATH@/components/XULStore.js
+@RESPATH@/components/XULStore.manifest
+@RESPATH@/components/AppsService.js
+@RESPATH@/components/AppsService.manifest
+@RESPATH@/components/Push.js
+@RESPATH@/components/Push.manifest
+@RESPATH@/components/PushComponents.js
+
+@RESPATH@/components/nsDOMIdentity.js
+@RESPATH@/components/nsIDService.js
+@RESPATH@/components/Identity.manifest
+
+@RESPATH@/components/SystemMessageInternal.js
+@RESPATH@/components/SystemMessageManager.js
+@RESPATH@/components/SystemMessageCache.js
+@RESPATH@/components/SystemMessageManager.manifest
+@RESPATH@/components/HCIEventTransactionSystemMessage.manifest
+@RESPATH@/components/HCIEventTransactionSystemMessageConfigurator.js
+
+@RESPATH@/components/ActivityProxy.js
+@RESPATH@/components/ActivityRequestHandler.js
+@RESPATH@/components/ActivityWrapper.js
+@RESPATH@/components/ActivityMessageConfigurator.js
+
+@RESPATH@/components/DownloadsAPI.js
+@RESPATH@/components/DownloadsAPI.manifest
+
+; InputMethod API
+@RESPATH@/components/MozKeyboard.js
+@RESPATH@/components/InputMethod.manifest
+#ifdef MOZ_B2G
+@RESPATH@/components/inputmethod.xpt
+#endif
+
+@RESPATH@/components/SystemUpdate.manifest
+@RESPATH@/components/SystemUpdateManager.js
+
+#if defined(ENABLE_TESTS) && defined(MOZ_DEBUG)
+@RESPATH@/components/TestInterfaceJS.js
+@RESPATH@/components/TestInterfaceJS.manifest
+@RESPATH@/components/TestInterfaceJSMaplike.js
+#endif
+
+; Modules
+@RESPATH@/modules/*
+
+; Safe Browsing
+@RESPATH@/components/nsURLClassifier.manifest
+@RESPATH@/components/nsUrlClassifierHashCompleter.js
+@RESPATH@/components/nsUrlClassifierListManager.js
+@RESPATH@/components/nsUrlClassifierLib.js
+@RESPATH@/components/url-classifier.xpt
+
+; Private Browsing
+@RESPATH@/components/privatebrowsing.xpt
+@RESPATH@/components/PrivateBrowsing.manifest
+@RESPATH@/components/PrivateBrowsingTrackingProtectionWhitelist.js
+
+; Security Reports
+@RESPATH@/components/SecurityReporter.manifest
+@RESPATH@/components/SecurityReporter.js
+
+; ANGLE on Win32
+#ifdef XP_WIN32
+#ifndef HAVE_64BIT_BUILD
+@BINPATH@/libEGL.dll
+@BINPATH@/libGLESv2.dll
+#endif
+#endif
+
+; [Browser Chrome Files]
+@RESPATH@/chrome/browser@JAREXT@
+@RESPATH@/chrome/browser.manifest
+@RESPATH@/chrome/toolkit@JAREXT@
+@RESPATH@/chrome/toolkit.manifest
+#ifdef XP_UNIX
+#ifndef XP_MACOSX
+@RESPATH@/chrome/icons/default/default16.png
+@RESPATH@/chrome/icons/default/default32.png
+@RESPATH@/chrome/icons/default/default48.png
+#endif
+#endif
+
+; DevTools
+@RESPATH@/chrome/devtools@JAREXT@
+@RESPATH@/chrome/devtools.manifest
+#ifdef MOZ_GRAPHENE
+@RESPATH@/@PREF_DIR@/devtools.js
+#endif
+
+; shell icons
+#ifdef XP_UNIX
+#ifndef XP_MACOSX
+@RESPATH@/icons/*.xpm
+@RESPATH@/icons/*.png
+#endif
+#endif
+
+; [Default Preferences]
+; All the pref files must be part of base to prevent migration bugs
+#ifdef MOZ_MULET
+@RESPATH@/browser/@PREF_DIR@/b2g.js
+#else
+@RESPATH@/@PREF_DIR@/b2g.js
+#endif
+@RESPATH@/@PREF_DIR@/channel-prefs.js
+@RESPATH@/greprefs.js
+@RESPATH@/defaults/autoconfig/prefcalls.js
+
+; [Layout Engine Resources]
+; Style Sheets, Graphics and other Resources used by the layout engine.
+@RESPATH@/res/EditorOverride.css
+@RESPATH@/res/contenteditable.css
+@RESPATH@/res/designmode.css
+@RESPATH@/res/ImageDocument.css
+@RESPATH@/res/TopLevelImageDocument.css
+@RESPATH@/res/TopLevelVideoDocument.css
+@RESPATH@/res/table-add-column-after-active.gif
+@RESPATH@/res/table-add-column-after-hover.gif
+@RESPATH@/res/table-add-column-after.gif
+@RESPATH@/res/table-add-column-before-active.gif
+@RESPATH@/res/table-add-column-before-hover.gif
+@RESPATH@/res/table-add-column-before.gif
+@RESPATH@/res/table-add-row-after-active.gif
+@RESPATH@/res/table-add-row-after-hover.gif
+@RESPATH@/res/table-add-row-after.gif
+@RESPATH@/res/table-add-row-before-active.gif
+@RESPATH@/res/table-add-row-before-hover.gif
+@RESPATH@/res/table-add-row-before.gif
+@RESPATH@/res/table-remove-column-active.gif
+@RESPATH@/res/table-remove-column-hover.gif
+@RESPATH@/res/table-remove-column.gif
+@RESPATH@/res/table-remove-row-active.gif
+@RESPATH@/res/table-remove-row-hover.gif
+@RESPATH@/res/table-remove-row.gif
+@RESPATH@/res/grabber.gif
+#ifdef XP_MACOSX
+@RESPATH@/res/cursors/*
+#endif
+@RESPATH@/res/fonts/*
+@RESPATH@/res/dtd/*
+@RESPATH@/res/html/*
+@RESPATH@/res/language.properties
+@RESPATH@/res/entityTables/*
+#ifdef XP_MACOSX
+@RESPATH@/res/MainMenu.nib/
+#endif
+
+; svg
+@RESPATH@/res/svg.css
+@RESPATH@/components/dom_svg.xpt
+@RESPATH@/components/dom_smil.xpt
+
+; [Personal Security Manager]
+;
+@BINPATH@/@DLL_PREFIX@nssckbi@DLL_SUFFIX@
+@RESPATH@/components/pipnss.xpt
+@RESPATH@/components/pippki.xpt
+@BINPATH@/@DLL_PREFIX@nss3@DLL_SUFFIX@
+#ifndef MOZ_FOLD_LIBS
+@BINPATH@/@DLL_PREFIX@nssutil3@DLL_SUFFIX@
+@BINPATH@/@DLL_PREFIX@smime3@DLL_SUFFIX@
+@BINPATH@/@DLL_PREFIX@ssl3@DLL_SUFFIX@
+#endif
+@BINPATH@/@DLL_PREFIX@softokn3@DLL_SUFFIX@
+#if defined(XP_LINUX) && !defined(ANDROID)
+@BINPATH@/@DLL_PREFIX@freeblpriv3@DLL_SUFFIX@
+#else
+@BINPATH@/@DLL_PREFIX@freebl3@DLL_SUFFIX@
+#endif
+#ifndef CROSS_COMPILE
+@BINPATH@/@DLL_PREFIX@freebl3.chk
+@BINPATH@/@DLL_PREFIX@softokn3.chk
+#endif
+#ifndef NSS_DISABLE_DBM
+@BINPATH@/@DLL_PREFIX@nssdbm3@DLL_SUFFIX@
+#ifndef CROSS_COMPILE
+@BINPATH@/@DLL_PREFIX@nssdbm3.chk
+#endif
+#endif
+@RESPATH@/chrome/pippki@JAREXT@
+@RESPATH@/chrome/pippki.manifest
+
+; For process sandboxing
+#if defined(MOZ_SANDBOX)
+#if defined(XP_WIN)
+@BINPATH@/@DLL_PREFIX@sandboxbroker@DLL_SUFFIX@
+#elif defined(XP_LINUX)
+@BINPATH@/@DLL_PREFIX@mozsandbox@DLL_SUFFIX@
+#endif
+#endif
+
+; for Solaris SPARC
+#ifdef SOLARIS
+bin/libfreebl_32fpu_3.chk
+bin/libfreebl_32fpu_3.so
+bin/libfreebl_32int_3.chk
+bin/libfreebl_32int_3.so
+bin/libfreebl_32int64_3.chk
+bin/libfreebl_32int64_3.so
+#endif
+
+; [Updater]
+;
+#ifdef MOZ_UPDATER
+#ifdef XP_MACOSX
+@BINPATH@/updater.app/
+#else
+@BINPATH@/updater@BIN_SUFFIX@
+#endif
+#endif
+
+; [Crash Reporter]
+;
+#ifdef MOZ_CRASHREPORTER
+#ifdef XP_MACOSX
+@BINPATH@/crashreporter.app/
+#else
+@BINPATH@/crashreporter@BIN_SUFFIX@
+@RESPATH@/crashreporter.crt
+@RESPATH@/crashreporter.ini
+#ifdef XP_UNIX
+@RESPATH@/Throbber-small.gif
+#endif
+#endif
+@RESPATH@/crashreporter-override.ini
+#endif
+
+[b2g]
+#ifndef MOZ_WIDGET_GONK
+#ifdef XP_WIN32
+@BINPATH@/xpcshell.exe
+@BINPATH@/ssltunnel.exe
+#else
+@BINPATH@/xpcshell
+@BINPATH@/ssltunnel
+#endif
+#endif
+@RESPATH@/chrome/icons/
+@RESPATH@/chrome/chrome@JAREXT@
+@RESPATH@/chrome/chrome.manifest
+@RESPATH@/components/B2GComponents.manifest
+@BINPATH@/@DLL_PREFIX@omxplugin@DLL_SUFFIX@
+#if defined(ENABLE_MARIONETTE) || !defined(MOZ_WIDGET_GONK)
+@RESPATH@/chrome/marionette@JAREXT@
+@RESPATH@/chrome/marionette.manifest
+@RESPATH@/components/marionette.manifest
+@RESPATH@/components/marionette.js
+#endif
+@RESPATH@/components/AlertsService.js
+@RESPATH@/components/ContentPermissionPrompt.js
+#ifdef MOZ_UPDATER
+@RESPATH@/components/UpdatePrompt.js
+#endif
+@RESPATH@/components/DirectoryProvider.js
+@RESPATH@/components/ProcessGlobal.js
+@RESPATH@/components/OMAContentHandler.js
+@RESPATH@/components/RecoveryService.js
+@RESPATH@/components/MailtoProtocolHandler.js
+@RESPATH@/components/SmsProtocolHandler.js
+@RESPATH@/components/TelProtocolHandler.js
+@RESPATH@/components/B2GAboutRedirector.js
+@RESPATH@/components/FilePicker.js
+@RESPATH@/components/HelperAppDialog.js
+@RESPATH@/components/DownloadsUI.js
+@RESPATH@/components/SystemMessageGlue.js
+@RESPATH@/components/B2GAppMigrator.js
+@RESPATH@/components/B2GPresentationDevicePrompt.js
+@RESPATH@/components/PresentationRequestUIGlue.js
+
+#ifndef MOZ_WIDGET_GONK
+@RESPATH@/components/SimulatorScreen.js
+#endif
+
+@RESPATH@/components/FxAccountsUIGlue.js
+@RESPATH@/components/services_fxaccounts.xpt
+
+#ifdef MOZ_WEBSPEECH
+@RESPATH@/components/dom_webspeechsynth.xpt
+#endif
+
+#ifdef XP_MACOSX
+@BINPATH@/@DLL_PREFIX@plugin_child_interpose@DLL_SUFFIX@
+#endif
+
+#ifdef PACKAGE_GAIA
+[gaia]
+@RESPATH@/gaia/*
+@BINPATH@/b2g-bin@BIN_SUFFIX@
+#endif
+
+#ifdef PACKAGE_MOZTT
+@RESPATH@/fonts/*
+#endif
+
+; media
+@RESPATH@/gmp-clearkey/0.1/@DLL_PREFIX@clearkey@DLL_SUFFIX@
+@RESPATH@/gmp-clearkey/0.1/clearkey.info
+
+#ifdef PKG_LOCALE_MANIFEST
+#include @PKG_LOCALE_MANIFEST@
+#endif
+
+@RESPATH@/components/simpleServices.js
+@RESPATH@/components/utils.manifest
diff --git a/b2g/installer/removed-files.in b/b2g/installer/removed-files.in
new file mode 100644
index 0000000000..557a379dad
--- /dev/null
+++ b/b2g/installer/removed-files.in
@@ -0,0 +1,45 @@
+# Due to Apple Mac OS X packaging requirements files that are in the same
+# directory on other platforms must be located in different directories on
+# Mac OS X. The following defines allow specifying the Mac OS X bundle
+# location which also work on other platforms.
+#
+# @DIR_MACOS@
+# Equals Contents/MacOS/ on Mac OS X and is an empty string on other platforms.
+#
+# @DIR_RESOURCES@
+# Equals Contents/Resources/ on Mac OS X and is an empty string on other
+# platforms.
+
+# Mac OS X v2 signing removals
+#ifdef XP_MACOSX
+ @DIR_MACOS@active-update.xml
+ @DIR_MACOS@update-settings.ini
+ @DIR_MACOS@updates.xml
+ @DIR_MACOS@defaults/*
+ @DIR_MACOS@updates/*
+#endif
+
+@DIR_MACOS@README.txt
+@DIR_MACOS@@DLL_PREFIX@mozutils@DLL_SUFFIX@
+@DIR_MACOS@jssubloader/
+#ifdef XP_MACOSX
+@DIR_MACOS@run-mozilla.sh
+#endif
+#ifdef XP_WIN
+ mozcrt19.dll
+ mozcpp19.dll
+#endif
+@DIR_MACOS@defaults/preferences/services-sync.js
+@DIR_MACOS@defaults/preferences/healthreport-prefs.js
+@DIR_MACOS@components/dom_sms.xpt
+@DIR_MACOS@components/dom_webspeech.xpt
+#ifdef MOZ_FOLD_LIBS
+@DIR_MACOS@@DLL_PREFIX@nspr4@DLL_SUFFIX@
+@DIR_MACOS@@DLL_PREFIX@plds4@DLL_SUFFIX@
+@DIR_MACOS@@DLL_PREFIX@plc4@DLL_SUFFIX@
+@DIR_MACOS@@DLL_PREFIX@ssl3@DLL_SUFFIX@
+@DIR_MACOS@@DLL_PREFIX@smime3@DLL_SUFFIX@
+@DIR_MACOS@@DLL_PREFIX@nssutil3@DLL_SUFFIX@
+@DIR_MACOS@@DLL_PREFIX@mozsqlite3@DLL_SUFFIX@
+#endif
+@DIR_MACOS@@DLL_PREFIX@xpcom@DLL_SUFFIX@
diff --git a/b2g/locales/Makefile.in b/b2g/locales/Makefile.in
new file mode 100644
index 0000000000..c626e557cc
--- /dev/null
+++ b/b2g/locales/Makefile.in
@@ -0,0 +1,149 @@
+# vim:set ts=8 sw=8 sts=8 noet:
+# 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 $(topsrcdir)/config/config.mk
+
+SUBMAKEFILES += \
+ $(DEPTH)/$(MOZ_BRANDING_DIRECTORY)/Makefile \
+ $(DEPTH)/$(MOZ_BRANDING_DIRECTORY)/locales/Makefile \
+ $(NULL)
+
+# This makefile uses variable overrides from the libs-% target to
+# build non-default locales to non-default dist/ locations. Be aware!
+
+PWD := $(CURDIR)
+
+# These are defaulted to be compatible with the files the wget-en-US target
+# pulls. You may override them if you provide your own files. You _must_
+# override them when MOZ_PKG_PRETTYNAMES is defined - the defaults will not
+# work in that case.
+ZIP_IN ?= $(ABS_DIST)/$(PACKAGE)
+WIN32_INSTALLER_IN ?= $(ABS_DIST)/$(PKG_INST_PATH)$(PKG_INST_BASENAME).exe
+RETRIEVE_WINDOWS_INSTALLER = 1
+
+MOZ_LANGPACK_EID=langpack-$(AB_CD)@b2g.mozilla.org
+
+L10N_PREF_JS_EXPORTS = $(call MERGE_FILE,b2g-l10n.js)
+L10N_PREF_JS_EXPORTS_PATH = $(FINAL_TARGET)/$(PREF_DIR)
+L10N_PREF_JS_EXPORTS_FLAGS = $(PREF_PPFLAGS) --silence-missing-directive-warnings
+PP_TARGETS += L10N_PREF_JS_EXPORTS
+
+ifneq (,$(filter cocoa,$(MOZ_WIDGET_TOOLKIT)))
+MOZ_PKG_MAC_DSSTORE=$(ABS_DIST)/branding/dsstore
+MOZ_PKG_MAC_BACKGROUND=$(ABS_DIST)/branding/background.png
+MOZ_PKG_MAC_ICON=$(ABS_DIST)/branding/disk.icns
+MOZ_PKG_MAC_EXTRA=--symlink '/Applications:/ '
+endif
+
+ifeq (WINNT,$(OS_ARCH))
+UNINSTALLER_PACKAGE_HOOK = $(RM) -r $(STAGEDIST)/uninstall; \
+ $(NSINSTALL) -D $(STAGEDIST)/uninstall; \
+ cp ../installer/windows/l10ngen/helper.exe $(STAGEDIST)/uninstall; \
+ $(RM) $(ABS_DIST)/l10n-stage/setup.exe; \
+ cp ../installer/windows/l10ngen/setup.exe $(ABS_DIST)/l10n-stage; \
+ $(NULL)
+endif
+
+include $(topsrcdir)/config/rules.mk
+
+include $(topsrcdir)/toolkit/locales/l10n.mk
+
+$(STAGEDIST): $(DIST)/branding
+
+$(DIST)/branding:
+ $(NSINSTALL) -D $@
+
+libs::
+ @if test -f '$(LOCALE_SRCDIR)/existing-profile-defaults.js'; then \
+ $(PYTHON) -m mozbuild.action.preprocessor $(PREF_PPFLAGS) $(DEFINES) $(ACDEFINES) \
+ $(LOCALE_SRCDIR)/existing-profile-defaults.js -o $(FINAL_TARGET)/defaults/existing-profile-defaults.js; \
+ fi
+
+NO_JA_JP_MAC_AB_CD := $(if $(filter ja-JP-mac, $(AB_CD)),ja,$(AB_CD))
+
+libs-%:
+ $(NSINSTALL) -D $(DIST)/install
+ @$(MAKE) -C ../../toolkit/locales libs-$*
+ @$(MAKE) -C ../../intl/locales AB_CD=$* XPI_NAME=locale-$*
+ @$(MAKE) libs AB_CD=$* XPI_NAME=locale-$* PREF_DIR=$(PREF_DIR)
+ @$(MAKE) -C $(DEPTH)/$(MOZ_BRANDING_DIRECTORY)/locales AB_CD=$* XPI_NAME=locale-$*
+
+# Tailored target to just add the chrome processing for multi-locale builds
+chrome-%:
+ @$(MAKE) chrome AB_CD=$*
+ @$(MAKE) -C $(DEPTH)/$(MOZ_BRANDING_DIRECTORY)/locales chrome AB_CD=$*
+
+repackage-win32-installer: WIN32_INSTALLER_OUT=$(ABS_DIST)/$(PKG_INST_PATH)$(PKG_INST_BASENAME).exe
+repackage-win32-installer: $(call ESCAPE_SPACE,$(WIN32_INSTALLER_IN)) $(SUBMAKEFILES) libs-$(AB_CD)
+ @echo 'Repackaging $(WIN32_INSTALLER_IN) into $(WIN32_INSTALLER_OUT).'
+ $(MAKE) -C $(DEPTH)/$(MOZ_BRANDING_DIRECTORY) export
+ $(MAKE) -C ../installer/windows CONFIG_DIR=l10ngen l10ngen/setup.exe l10ngen/7zSD.sfx
+ $(MAKE) repackage-zip \
+ AB_CD=$(AB_CD) \
+ MOZ_PKG_FORMAT=SFX7Z \
+ ZIP_IN='$(WIN32_INSTALLER_IN)' \
+ ZIP_OUT='$(WIN32_INSTALLER_OUT)' \
+ SFX_HEADER='$(PWD)/../installer/windows/l10ngen/7zSD.sfx \
+ $(topsrcdir)/b2g/installer/windows/app.tag'
+
+ifeq (WINNT,$(OS_ARCH))
+repackage-win32-installer-%:
+ @$(MAKE) repackage-win32-installer AB_CD=$* WIN32_INSTALLER_IN='$(WIN32_INSTALLER_IN)'
+else
+repackage-win32-installer-%: ;
+endif
+
+
+clobber-zip:
+ $(RM) $(STAGEDIST)/chrome/$(AB_CD).jar \
+ $(STAGEDIST)/chrome/$(AB_CD).manifest \
+ $(STAGEDIST)/defaults/pref/b2g-l10n.js
+ $(STAGEDIST)/dictionaries \
+ $(STAGEDIST)/defaults/profile \
+ $(STAGEDIST)/chrome/$(AB_CD)
+
+
+langpack: langpack-$(AB_CD)
+
+# This is a generic target that will make a langpack, repack ZIP (+tarball)
+# builds, and repack an installer if applicable. It is called from the
+# tinderbox scripts. Alter it with caution.
+
+installers-%: clobber-% langpack-% repackage-win32-installer-% repackage-zip-%
+ @echo 'repackaging done'
+
+# When we unpack b2g on MacOS X the platform.ini and application.ini are in slightly
+# different locations that on all other platforms
+ifeq (Darwin, $(OS_ARCH))
+GECKO_PLATFORM_INI_PATH='$(STAGEDIST)/platform.ini'
+B2G_APPLICATION_INI_PATH='$(STAGEDIST)/application.ini'
+else
+GECKO_PLATFORM_INI_PATH='$(STAGEDIST)/platform.ini'
+B2G_APPLICATION_INI_PATH='$(STAGEDIST)/application.ini'
+endif
+
+
+ident:
+ @printf 'gecko_revision '
+ @$(PYTHON) $(topsrcdir)/config/printconfigsetting.py $(GECKO_PLATFORM_INI_PATH) Build SourceStamp
+ @printf 'b2g_revision '
+ @$(PYTHON) $(topsrcdir)/config/printconfigsetting.py $(B2G_APPLICATION_INI_PATH) App SourceStamp
+ @printf 'buildid '
+ @$(PYTHON) $(topsrcdir)/config/printconfigsetting.py $(B2G_APPLICATION_INI_PATH) App BuildID
+
+merge-%:
+ifdef LOCALE_MERGEDIR
+ $(RM) -rf $(LOCALE_MERGEDIR)
+ $(topsrcdir)/mach compare-locales --merge-dir $(LOCALE_MERGEDIR) $*
+endif
+ @echo
+
+# test target, depends on make package
+# try to repack x-test, with just toolkit/defines.inc being there
+l10n-check::
+ $(RM) -rf x-test
+ $(NSINSTALL) -D x-test/toolkit
+ echo '#define MOZ_LANG_TITLE Just testing' > x-test/toolkit/defines.inc
+ $(MAKE) installers-x-test L10NBASEDIR='$(PWD)' LOCALE_MERGEDIR='$(PWD)/mergedir'
diff --git a/b2g/locales/all-locales b/b2g/locales/all-locales
new file mode 100644
index 0000000000..44317e1ee1
--- /dev/null
+++ b/b2g/locales/all-locales
@@ -0,0 +1,3 @@
+es-ES
+pl
+pt-BR
diff --git a/b2g/locales/en-US/b2g-l10n.js b/b2g/locales/en-US/b2g-l10n.js
new file mode 100644
index 0000000000..d502f0ae54
--- /dev/null
+++ b/b2g/locales/en-US/b2g-l10n.js
@@ -0,0 +1,12 @@
+# 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/.
+
+#filter substitution
+
+pref("general.useragent.locale", "@AB_CD@");
+
+// Enable sparse localization by setting a few package locale overrides
+pref("chrome.override_package.global", "b2g-l10n");
+pref("chrome.override_package.mozapps", "b2g-l10n");
+pref("chrome.override_package.passwordmgr", "b2g-l10n");
diff --git a/b2g/locales/en-US/chrome/graphene.properties b/b2g/locales/en-US/chrome/graphene.properties
new file mode 100644
index 0000000000..be5f0b5c59
--- /dev/null
+++ b/b2g/locales/en-US/chrome/graphene.properties
@@ -0,0 +1,5 @@
+# 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/.
+
+installing=Installing…
diff --git a/b2g/locales/en-US/chrome/overrides/aboutCertError.dtd b/b2g/locales/en-US/chrome/overrides/aboutCertError.dtd
new file mode 100644
index 0000000000..cd6c6ba632
--- /dev/null
+++ b/b2g/locales/en-US/chrome/overrides/aboutCertError.dtd
@@ -0,0 +1,38 @@
+<!-- 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/. -->
+
+<!ENTITY % brandDTD
+ SYSTEM "chrome://branding/locale/brand.dtd">
+ %brandDTD;
+
+<!-- These strings are used by Firefox's custom about:certerror page,
+a replacement for the standard security certificate errors produced
+by NSS/PSM via netError.xhtml. -->
+
+<!ENTITY certerror.pagetitle "Untrusted Connection">
+<!ENTITY certerror.longpagetitle "This Connection is Untrusted">
+
+<!-- Localization note (certerror.introPara1) - The string "#1" will
+be replaced at runtime with the name of the server to which the user
+was trying to connect. -->
+<!ENTITY certerror.introPara1 "You have asked &brandShortName; to connect
+securely to <b>#1</b>, but we can't confirm that your connection is secure.">
+
+<!ENTITY certerror.whatShouldIDo.heading "What Should I Do?">
+<!ENTITY certerror.whatShouldIDo.content "If you usually connect to
+this site without problems, this error could mean that someone is
+trying to impersonate the site, and you shouldn't continue.">
+<!ENTITY certerror.getMeOutOfHere.label "Get me out of here!">
+
+<!ENTITY certerror.expert.heading "I Understand the Risks">
+<!ENTITY certerror.expert.content "If you understand what's going on, you
+can tell &brandShortName; to start trusting this site's identification.
+<b>Even if you trust the site, this error could mean that someone is
+tampering with your connection.</b>">
+<!ENTITY certerror.expert.contentPara2 "Don't add an exception unless
+you know there's a good reason why this site doesn't use trusted identification.">
+<!ENTITY certerror.addTemporaryException.label "Visit site">
+<!ENTITY certerror.addPermanentException.label "Add permanent exception">
+
+<!ENTITY certerror.technical.heading "Technical Details">
diff --git a/b2g/locales/en-US/chrome/overrides/appstrings.properties b/b2g/locales/en-US/chrome/overrides/appstrings.properties
new file mode 100644
index 0000000000..ecc9e0a04e
--- /dev/null
+++ b/b2g/locales/en-US/chrome/overrides/appstrings.properties
@@ -0,0 +1,39 @@
+# 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/.
+
+malformedURI=The URL is not valid and cannot be loaded.
+fileNotFound=Firefox can't find the file at %S.
+dnsNotFound=Firefox can't find the server at %S.
+unknownProtocolFound=Firefox doesn't know how to open this address, because one of the following protocols (%S) isn't associated with any program or is not allowed in this context.
+connectionFailure=Firefox can't establish a connection to the server at %S.
+netInterrupt=The connection to %S was interrupted while the page was loading.
+netTimeout=The server at %S is taking too long to respond.
+redirectLoop=Firefox has detected that the server is redirecting the request for this address in a way that will never complete.
+## LOCALIZATION NOTE (confirmRepostPrompt): In this item, don't translate "%S"
+confirmRepostPrompt=To display this page, %S must send information that will repeat any action (such as a search or order confirmation) that was performed earlier.
+resendButton.label=Resend
+unknownSocketType=Firefox doesn't know how to communicate with the server.
+netReset=The connection to the server was reset while the page was loading.
+notCached=This document is no longer available.
+netOffline=Firefox is currently in offline mode and can't browse the Web.
+isprinting=The document cannot change while Printing or in Print Preview.
+deniedPortAccess=This address uses a network port which is normally used for purposes other than Web browsing. Firefox has canceled the request for your protection.
+proxyResolveFailure=Firefox is configured to use a proxy server that can't be found.
+proxyConnectFailure=Firefox is configured to use a proxy server that is refusing connections.
+contentEncodingError=The page you are trying to view cannot be shown because it uses an invalid or unsupported form of compression.
+unsafeContentType=The page you are trying to view cannot be shown because it is contained in a file type that may not be safe to open. Please contact the website owners to inform them of this problem.
+externalProtocolTitle=External Protocol Request
+externalProtocolPrompt=An external application must be launched to handle %1$S: links.\n\n\nRequested link:\n\n%2$S\n\nApplication: %3$S\n\n\nIf you were not expecting this request it may be an attempt to exploit a weakness in that other program. Cancel this request unless you are sure it is not malicious.\n
+#LOCALIZATION NOTE (externalProtocolUnknown): The following string is shown if the application name can't be determined
+externalProtocolUnknown=<Unknown>
+externalProtocolChkMsg=Remember my choice for all links of this type.
+externalProtocolLaunchBtn=Launch application
+malwareBlocked=The site at %S has been reported as an attack site and has been blocked based on your security preferences.
+unwantedBlocked=The site at %S has been reported as serving unwanted software and has been blocked based on your security preferences.
+deceptiveBlocked=This web page at %S has been reported as a deceptive site and has been blocked based on your security preferences.
+cspBlocked=This page has a content security policy that prevents it from being loaded in this way.
+corruptedContentErrorv2=The site at %S has experienced a network protocol violation that cannot be repaired.
+remoteXUL=This page uses an unsupported technology that is no longer available by default in Firefox.
+sslv3Used=Firefox cannot guarantee the safety of your data on %S because it uses SSLv3, a broken security protocol.
+weakCryptoUsed=The owner of %S has configured their website improperly. To protect your information from being stolen, Firefox has not connected to this website.
diff --git a/b2g/locales/en-US/defines.inc b/b2g/locales/en-US/defines.inc
new file mode 100644
index 0000000000..24f45813a2
--- /dev/null
+++ b/b2g/locales/en-US/defines.inc
@@ -0,0 +1,9 @@
+#filter emptyLines
+
+#define MOZ_LANGPACK_CREATOR mozilla.org
+
+# If non-English locales wish to credit multiple contributors, uncomment this
+# variable definition and use the format specified.
+# #define MOZ_LANGPACK_CONTRIBUTORS <em:contributor>Joe Solon</em:contributor> <em:contributor>Suzy Solon</em:contributor>
+
+#unfilter emptyLines
diff --git a/b2g/locales/filter.py b/b2g/locales/filter.py
new file mode 100644
index 0000000000..d49507adca
--- /dev/null
+++ b/b2g/locales/filter.py
@@ -0,0 +1,15 @@
+# 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/.
+
+
+def test(mod, path, entity = None):
+ import re
+ # ignore anything but b2g and specific overloads from dom and toolkit
+ if mod not in ("netwerk", "dom", "toolkit", "security/manager",
+ "devtools/shared",
+ "mobile",
+ "b2g"):
+ return "ignore"
+
+ return "error"
diff --git a/b2g/locales/jar.mn b/b2g/locales/jar.mn
new file mode 100644
index 0000000000..970261dbe3
--- /dev/null
+++ b/b2g/locales/jar.mn
@@ -0,0 +1,75 @@
+#filter substitution
+# 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/.
+
+
+@AB_CD@.jar:
+% locale b2g-l10n @AB_CD@ %locale/@AB_CD@/b2g-l10n/
+
+% override chrome://global/locale/aboutCertError.dtd chrome://b2g-l10n/locale/aboutCertError.dtd
+% override chrome://global/locale/appstrings.properties chrome://b2g-l10n/locale/appstrings.properties
+ locale/@AB_CD@/b2g-l10n/aboutCertError.dtd (%chrome/overrides/aboutCertError.dtd)
+ locale/@AB_CD@/b2g-l10n/appstrings.properties (%chrome/overrides/appstrings.properties)
+#ifdef MOZ_GRAPHENE
+ locale/@AB_CD@/b2g-l10n/graphene.properties (%chrome/graphene.properties)
+#endif
+
+
+# overrides for toolkit l10n, also for en-US
+relativesrcdir toolkit/locales:
+ locale/@AB_CD@/b2g-l10n/overrides/about.dtd (%chrome/global/about.dtd)
+ locale/@AB_CD@/b2g-l10n/overrides/aboutAbout.dtd (%chrome/global/aboutAbout.dtd)
+ locale/@AB_CD@/b2g-l10n/overrides/aboutRights.dtd (%chrome/global/aboutRights.dtd)
+ locale/@AB_CD@/b2g-l10n/overrides/commonDialogs.properties (%chrome/global/commonDialogs.properties)
+ locale/@AB_CD@/b2g-l10n/overrides/handling/handling.properties (%chrome/mozapps/handling/handling.properties)
+ locale/@AB_CD@/b2g-l10n/overrides/intl.properties (%chrome/global/intl.properties)
+ locale/@AB_CD@/b2g-l10n/overrides/intl.css (%chrome/global/intl.css)
+ locale/@AB_CD@/b2g-l10n/overrides/passwordmgr.properties (%chrome/passwordmgr/passwordmgr.properties)
+ locale/@AB_CD@/b2g-l10n/overrides/search/search.properties (%chrome/search/search.properties)
+ locale/@AB_CD@/b2g-l10n/overrides/update/updates.properties (%chrome/mozapps/update/updates.properties)
+# about:support
+ locale/@AB_CD@/b2g-l10n/overrides/global/aboutSupport.dtd (%chrome/global/aboutSupport.dtd)
+ locale/@AB_CD@/b2g-l10n/overrides/global/aboutSupport.properties (%chrome/global/aboutSupport.properties)
+#about:crashes
+ locale/@AB_CD@/b2g-l10n/overrides/crashreporter/crashes.dtd (%crashreporter/crashes.dtd)
+ locale/@AB_CD@/b2g-l10n/overrides/crashreporter/crashes.properties (%crashreporter/crashes.properties)
+#about:mozilla
+ locale/@AB_CD@/b2g-l10n/overrides/global/mozilla.dtd (%chrome/global/mozilla.dtd)
+#about:telemetry
+ locale/@AB_CD@/b2g-l10n/overrides/global/aboutTelemetry.dtd (%chrome/global/aboutTelemetry.dtd)
+ locale/@AB_CD@/b2g-l10n/overrides/global/aboutTelemetry.properties (%chrome/global/aboutTelemetry.properties)
+#about:webrtc
+ locale/@AB_CD@/b2g-l10n/overrides/global/aboutWebrtc.properties (%chrome/global/aboutWebrtc.properties)
+
+% override chrome://global/locale/about.dtd chrome://b2g-l10n/locale/overrides/about.dtd
+% override chrome://global/locale/aboutAbout.dtd chrome://b2g-l10n/locale/overrides/aboutAbout.dtd
+% override chrome://global/locale/aboutRights.dtd chrome://b2g-l10n/locale/overrides/aboutRights.dtd
+% override chrome://global/locale/commonDialogs.properties chrome://b2g-l10n/locale/overrides/commonDialogs.properties
+% override chrome://mozapps/locale/handling/handling.properties chrome://b2g-l10n/locale/overrides/handling/handling.properties
+% override chrome://global/locale/intl.properties chrome://b2g-l10n/locale/overrides/intl.properties
+% override chrome://global/locale/intl.css chrome://b2g-l10n/locale/overrides/intl.css
+% override chrome://passwordmgr/locale/passwordmgr.properties chrome://b2g-l10n/locale/overrides/passwordmgr/passwordmgr.properties
+% override chrome://global/locale/search/search.properties chrome://b2g-l10n/locale/overrides/search/search.properties
+% override chrome://mozapps/locale/update/updates.properties chrome://b2g-l10n/locale/overrides/update/updates.properties
+% override chrome://global/locale/aboutSupport.dtd chrome://b2g-l10n/locale/overrides/global/aboutSupport.dtd
+% override chrome://global/locale/aboutSupport.properties chrome://b2g-l10n/locale/overrides/global/aboutSupport.properties
+% override chrome://global/locale/crashes.dtd chrome://b2g-l10n/locale/overrides/crashreporter/crashes.dtd
+% override chrome://global/locale/crashes.properties chrome://b2g-l10n/locale/overrides/crashreporter/crashes.properties
+% override chrome://global/locale/mozilla.dtd chrome://b2g-l10n/locale/overrides/global/mozilla.dtd
+% override chrome://global/locale/aboutTelemetry.dtd chrome://b2g-l10n/locale/overrides/global/aboutTelemetry.dtd
+% override chrome://global/locale/aboutTelemetry.properties chrome://b2g-l10n/locale/overrides/global/aboutTelemetry.properties
+% override chrome://global/locale/aboutWebrtc.properties chrome://b2g-l10n/locale/overrides/global/aboutWebrtc.properties
+
+# overrides for dom l10n, also for en-US
+relativesrcdir dom/locales:
+ locale/@AB_CD@/b2g-l10n/overrides/global.dtd (%chrome/global.dtd)
+ locale/@AB_CD@/b2g-l10n/overrides/AccessFu.properties (%chrome/accessibility/AccessFu.properties)
+ locale/@AB_CD@/b2g-l10n/overrides/dom/dom.properties (%chrome/dom/dom.properties)
+#about:plugins
+ locale/@AB_CD@/b2g-l10n/overrides/plugins.properties (%chrome/plugins.properties)
+
+% override chrome://global/locale/global.dtd chrome://b2g-l10n/locale/overrides/global.dtd
+% override chrome://global/locale/AccessFu.properties chrome://b2g-l10n/locale/overrides/AccessFu.properties
+% override chrome://global/locale/dom/dom.properties chrome://b2g-l10n/locale/overrides/dom/dom.properties
+% override chrome://global/locale/plugins.properties chrome://b2g-l10n/locale/overrides/plugins.properties
diff --git a/b2g/locales/l10n.ini b/b2g/locales/l10n.ini
new file mode 100644
index 0000000000..b40159dfdc
--- /dev/null
+++ b/b2g/locales/l10n.ini
@@ -0,0 +1,13 @@
+; 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/.
+
+[general]
+depth = ../..
+all = b2g/locales/all-locales
+
+[compare]
+dirs = b2g
+
+[includes]
+toolkit = toolkit/locales/l10n.ini
diff --git a/b2g/locales/moz.build b/b2g/locales/moz.build
new file mode 100644
index 0000000000..eb4454d28f
--- /dev/null
+++ b/b2g/locales/moz.build
@@ -0,0 +1,7 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+JAR_MANIFESTS += ['jar.mn'] \ No newline at end of file
diff --git a/b2g/moz.build b/b2g/moz.build
new file mode 100644
index 0000000000..c5aec07c91
--- /dev/null
+++ b/b2g/moz.build
@@ -0,0 +1,14 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+CONFIGURE_SUBST_FILES += ['installer/Makefile']
+
+DIRS += ['chrome', 'components', 'locales']
+
+if CONFIG['GAIADIR']:
+ DIRS += ['gaia']
+
+DIRS += ['app']
diff --git a/b2g/moz.configure b/b2g/moz.configure
new file mode 100644
index 0000000000..b98e5e61fe
--- /dev/null
+++ b/b2g/moz.configure
@@ -0,0 +1,32 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+option('--with-gonk', nargs=1, help='Path to the gonk base directory')
+
+@depends_if('--with-gonk', '--help')
+def gonkdir(value, _):
+ return value[0]
+
+add_old_configure_assignment('gonkdir', gonkdir)
+
+@depends_if('--with-gonk')
+def gonk_toolkit(_):
+ return 'cairo-gonk'
+
+imply_option('--enable-default-toolkit', gonk_toolkit)
+
+
+option('--with-gonk-toolchain-prefix', nargs=1,
+ help='Prefix to gonk toolchain commands')
+
+@depends_if('--with-gonk-toolchain-prefix')
+def gonk_toolchain_prefix(value):
+ return value
+
+imply_option('--with-toolchain-prefix', gonk_toolchain_prefix)
+
+
+include('common.configure')
diff --git a/b2g/simulator/bootstrap.js b/b2g/simulator/bootstrap.js
new file mode 100644
index 0000000000..b728caa3dd
--- /dev/null
+++ b/b2g/simulator/bootstrap.js
@@ -0,0 +1,4 @@
+function startup(data, reason) {}
+function shutdown(data, reason) {}
+function install(data, reason) {}
+function uninstall(data, reason) {}
diff --git a/b2g/simulator/build_xpi.py b/b2g/simulator/build_xpi.py
new file mode 100644
index 0000000000..392be09b27
--- /dev/null
+++ b/b2g/simulator/build_xpi.py
@@ -0,0 +1,148 @@
+# 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/.
+
+# Generate xpi for the simulator addon by:
+# - building a special gaia profile for it, as we need:
+# * more languages, and,
+# * less apps
+# than b2g desktop's one
+# - retrieve usefull app version metadata from the build system
+# - finally, use addon sdk's cfx tool to build the addon xpi
+# that ships:
+# * b2g desktop runtime
+# * gaia profile
+
+import sys, os, re, subprocess
+from mozbuild.preprocessor import Preprocessor
+from mozbuild.base import MozbuildObject
+from mozbuild.util import ensureParentDir
+from mozpack.mozjar import JarWriter
+from zipfile import ZipFile
+from distutils.version import LooseVersion
+
+ftp_root_path = "/pub/mozilla.org/labs/fxos-simulator"
+UPDATE_URL = "https://ftp.mozilla.org" + ftp_root_path + "/%(update_path)s/update.rdf"
+XPI_NAME = "fxos-simulator-%(version)s-%(platform)s.xpi"
+
+class GaiaBuilder(object):
+ def __init__(self, build, gaia_path):
+ self.build = build
+ self.gaia_path = gaia_path
+
+ def clean(self):
+ self.build._run_make(target="clean", directory=self.gaia_path)
+
+ def profile(self, env):
+ self.build._run_make(target="profile", directory=self.gaia_path, num_jobs=1, silent=False, append_env=env)
+
+ def override_prefs(self, srcfile):
+ # Note that each time we call `make profile` in gaia, a fresh new pref file is created
+ # cat srcfile >> profile/user.js
+ with open(os.path.join(self.gaia_path, "profile", "user.js"), "a") as userJs:
+ userJs.write(open(srcfile).read())
+
+def preprocess_file(src, dst, version, app_buildid, update_url):
+ ensureParentDir(dst)
+
+ defines = {
+ "ADDON_ID": "fxos_" + version.replace(".", "_") + "_simulator@mozilla.org",
+ # (reduce the app build id to only the build date
+ # as addon manager doesn't handle big ints in addon versions)
+ "ADDON_VERSION": ("%s.%s" % (version, app_buildid[:8])),
+ "ADDON_NAME": "Firefox OS " + version + " Simulator",
+ "ADDON_DESCRIPTION": "a Firefox OS " + version + " simulator",
+ "ADDON_UPDATE_URL": update_url
+ }
+ pp = Preprocessor(defines=defines)
+ pp.do_filter("substitution")
+ with open(dst, "w") as output:
+ with open(src, "r") as input:
+ pp.processFile(input=input, output=output)
+
+def add_dir_to_zip(jar, top, pathInZip, blacklist=()):
+ for dirpath, subdirs, files in os.walk(top):
+ dir_relpath = os.path.relpath(dirpath, top)
+ if dir_relpath.startswith(blacklist):
+ continue
+ for filename in files:
+ relpath = os.path.join(dir_relpath, filename)
+ if relpath in blacklist:
+ continue
+ path = os.path.normpath(os.path.join(pathInZip, relpath))
+ file = open(os.path.join(dirpath, filename), "rb")
+ mode = os.stat(os.path.join(dirpath, filename)).st_mode
+ jar.add(path.encode("ascii"), file, mode=mode)
+
+def add_file_to_zip(jar, path, pathInZip):
+ file = open(path, "rb")
+ mode = os.stat(path).st_mode
+ jar.add(pathInZip.encode("ascii"), file, mode=mode)
+
+def main(platform):
+ build = MozbuildObject.from_environment()
+ topsrcdir = build.topsrcdir
+ distdir = build.distdir
+
+ srcdir = os.path.join(topsrcdir, "b2g", "simulator")
+
+ app_buildid = open(os.path.join(build.topobjdir, "config", "buildid")).read().strip()
+
+ # The simulator uses a shorter version string,
+ # it only keeps the major version digits A.B
+ # whereas MOZ_B2G_VERSION is A.B.C.D
+ b2g_version = build.config_environment.defines["MOZ_B2G_VERSION"].replace('"', '')
+ version = ".".join(str(n) for n in LooseVersion(b2g_version).version[0:2])
+
+ # Build a gaia profile specific to the simulator in order to:
+ # - disable the FTU
+ # - set custom prefs to enable devtools debugger server
+ # - set custom settings to disable lockscreen and screen timeout
+ # - only ship production apps
+ gaia_path = build.config_environment.substs["GAIADIR"]
+ builder = GaiaBuilder(build, gaia_path)
+ builder.clean()
+ env = {
+ "NOFTU": "1",
+ "GAIA_APP_TARGET": "production",
+ "SETTINGS_PATH": os.path.join(srcdir, "custom-settings.json")
+ }
+ builder.profile(env)
+ builder.override_prefs(os.path.join(srcdir, "custom-prefs.js"))
+
+ # Build the simulator addon xpi
+ xpi_name = XPI_NAME % {"version": version, "platform": platform}
+ xpi_path = os.path.join(distdir, xpi_name)
+
+ update_path = "%s/%s" % (version, platform)
+ update_url = UPDATE_URL % {"update_path": update_path}
+
+ # Preprocess some files...
+ manifest = os.path.join(build.topobjdir, "b2g", "simulator", "install.rdf")
+ preprocess_file(os.path.join(srcdir, "install.rdf.in"),
+ manifest,
+ version,
+ app_buildid,
+ update_url)
+
+ with JarWriter(xpi_path, optimize=False) as zip:
+ # Ship addon files into the .xpi
+ add_file_to_zip(zip, manifest, "install.rdf")
+ add_file_to_zip(zip, os.path.join(srcdir, "bootstrap.js"), "bootstrap.js")
+ add_file_to_zip(zip, os.path.join(srcdir, "icon.png"), "icon.png")
+ add_file_to_zip(zip, os.path.join(srcdir, "icon64.png"), "icon64.png")
+
+ # Ship b2g-desktop, but prevent its gaia profile to be shipped in the xpi
+ add_dir_to_zip(zip, os.path.join(distdir, "b2g"), "b2g",
+ ("gaia", "B2G.app/Contents/MacOS/gaia",
+ "B2G.app/Contents/Resources/gaia"))
+ # Then ship our own gaia profile
+ add_dir_to_zip(zip, os.path.join(gaia_path, "profile"), "profile")
+
+if __name__ == '__main__':
+ if 2 != len(sys.argv):
+ print("""Usage:
+ python {0} MOZ_PKG_PLATFORM
+""".format(sys.argv[0]))
+ sys.exit(1)
+ main(*sys.argv[1:])
diff --git a/b2g/simulator/custom-prefs.js b/b2g/simulator/custom-prefs.js
new file mode 100644
index 0000000000..634a9cebea
--- /dev/null
+++ b/b2g/simulator/custom-prefs.js
@@ -0,0 +1,8 @@
+user_pref("devtools.debugger.prompt-connection", false);
+user_pref("devtools.debugger.forbid-certified-apps", false);
+user_pref("devtools.apps.forbidden-permissions", "");
+user_pref("b2g.software-buttons", true);
+
+// Required for Mulet in order to run the debugger server from the command line
+user_pref("devtools.debugger.remote-enabled", true);
+user_pref("devtools.chrome.enabled", true);
diff --git a/b2g/simulator/custom-settings.json b/b2g/simulator/custom-settings.json
new file mode 100644
index 0000000000..ea0264d9a5
--- /dev/null
+++ b/b2g/simulator/custom-settings.json
@@ -0,0 +1,6 @@
+{
+ "debugger.remote-mode": "adb-devtools",
+ "screen.timeout": 0,
+ "lockscreen.enabled": false,
+ "lockscreen.locked": false
+}
diff --git a/b2g/simulator/icon.png b/b2g/simulator/icon.png
new file mode 100644
index 0000000000..c4307fc841
--- /dev/null
+++ b/b2g/simulator/icon.png
Binary files differ
diff --git a/b2g/simulator/icon64.png b/b2g/simulator/icon64.png
new file mode 100644
index 0000000000..275cd04a5e
--- /dev/null
+++ b/b2g/simulator/icon64.png
Binary files differ
diff --git a/b2g/simulator/install.rdf.in b/b2g/simulator/install.rdf.in
new file mode 100644
index 0000000000..e9827d0845
--- /dev/null
+++ b/b2g/simulator/install.rdf.in
@@ -0,0 +1,52 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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/. -->
+<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>@ADDON_ID@</em:id>
+ <em:version>@ADDON_VERSION@</em:version>
+ <em:type>2</em:type>
+ <em:bootstrap>true</em:bootstrap>
+ <em:unpack>true</em:unpack>
+
+ <!-- Firefox -->
+ <em:targetApplication>
+ <Description>
+ <em:id>{ec8030f7-c20a-464f-9b0e-13a3a9e97384}</em:id>
+ <em:minVersion>44.0a1</em:minVersion>
+ <em:maxVersion>45.0</em:maxVersion>
+ </Description>
+ </em:targetApplication>
+
+ <!-- Front End MetaData -->
+ <em:name>@ADDON_NAME@</em:name>
+ <em:description>@ADDON_DESCRIPTION@</em:description>
+ <em:creator>Myk Melez (https://github.com/mykmelez)</em:creator>
+
+ <em:optionsType>2</em:optionsType>
+
+ <em:updateURL>@ADDON_UPDATE_URL@</em:updateURL>
+
+ <em:contributor>Alexandre Poirot (https://github.com/ochameau)</em:contributor>
+ <em:contributor>Anant Narayanan (https://github.com/anantn)</em:contributor>
+ <em:contributor>Brandon Kase (https://github.com/bkase)</em:contributor>
+ <em:contributor>Breck Yunits (https://github.com/breck7)</em:contributor>
+ <em:contributor>César Carruitero (https://github.com/ccarruitero)</em:contributor>
+ <em:contributor>David Gomes (https://github.com/davidgomes)</em:contributor>
+ <em:contributor>Fabrice Desré (https://github.com/fabricedesre)</em:contributor>
+ <em:contributor>Fraser Tweedale (https://github.com/frasertweedale)</em:contributor>
+ <em:contributor>Harald Kirschner (https://github.com/digitarald)</em:contributor>
+ <em:contributor>Jérémie Patonnier (https://github.com/JeremiePat)</em:contributor>
+ <em:contributor>J. Ryan Stinnett (https://github.com/jryans)</em:contributor>
+ <em:contributor>Kan-Ru Chen (陳侃如) (https://github.com/kanru)</em:contributor>
+ <em:contributor>Louis Stowasser (https://github.com/louisstow)</em:contributor>
+ <em:contributor>Luca Greco (https://github.com/rpl)</em:contributor>
+ <em:contributor>Matthew Claypotch (https://github.com/potch)</em:contributor>
+ <em:contributor>Matthew Riley MacPherson (https://github.com/tofumatt)</em:contributor>
+ <em:contributor>Nick Desaulniers (https://github.com/nickdesaulniers)</em:contributor>
+ <em:contributor>Soumen Ganguly (https://github.com/SoumenG)</em:contributor>
+ <em:contributor>Sudheesh Singanamalla (https://github.com/sudheesh001)</em:contributor>
+ <em:contributor>Victor Bjelkholm (https://github.com/VictorBjelkholm)</em:contributor>
+ </Description>
+</RDF>
diff --git a/b2g/test/b2g-unittest-requirements.txt b/b2g/test/b2g-unittest-requirements.txt
new file mode 100644
index 0000000000..46bbccc7b4
--- /dev/null
+++ b/b2g/test/b2g-unittest-requirements.txt
@@ -0,0 +1,8 @@
+manifestparser==0.5.7
+mozprocess==0.9
+mozprofile==0.6
+mozrunner==5.15
+mozdevice==0.21
+mozcrash==0.5
+mozfile==0.3
+mozlog==1.1
diff --git a/b2g/test/emulator.manifest b/b2g/test/emulator.manifest
new file mode 100644
index 0000000000..5d9ed8a8da
--- /dev/null
+++ b/b2g/test/emulator.manifest
@@ -0,0 +1,6 @@
+[{
+"size": 746441603,
+"digest": "199236aefecc1657cdc1b791ec38c8184557ab9249aff9c63a74abf73edc1dc0ea36b19b558f34ca3b14f8a511b10bcf37408b19701929522b4dc22dbaddcbe9",
+"algorithm": "sha512",
+"filename": "emulator.zip"
+}]