diff options
Diffstat (limited to 'b2g')
327 files changed, 22896 insertions, 0 deletions
diff --git a/b2g/LICENSE b/b2g/LICENSE new file mode 100644 index 000000000..14e2f777f --- /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 000000000..9b3e1e08a --- /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 000000000..4587438d5 --- /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 000000000..6b50b87f7 --- /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 000000000..ec2f2a0f1 --- /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 000000000..d5ea85f4d --- /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 000000000..4cefd2ff9 --- /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 000000000..ddd3e0afa --- /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 000000000..b28889065 --- /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 000000000..967b54b76 --- /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 000000000..cb4c66d12 --- /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 000000000..ae4aeb285 --- /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 Binary files differnew file mode 100644 index 000000000..3d680134e --- /dev/null +++ b/b2g/branding/browserhtml/app.icns diff --git a/b2g/branding/browserhtml/app.ico b/b2g/branding/browserhtml/app.ico Binary files differnew file mode 100644 index 000000000..04596e4d0 --- /dev/null +++ b/b2g/branding/browserhtml/app.ico diff --git a/b2g/branding/browserhtml/background.png b/b2g/branding/browserhtml/background.png Binary files differnew file mode 100644 index 000000000..db5576a33 --- /dev/null +++ b/b2g/branding/browserhtml/background.png diff --git a/b2g/branding/browserhtml/configure.sh b/b2g/branding/browserhtml/configure.sh new file mode 100644 index 000000000..71c6129d2 --- /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 Binary files differnew file mode 100644 index 000000000..3cc1444f6 --- /dev/null +++ b/b2g/branding/browserhtml/content/about.png diff --git a/b2g/branding/browserhtml/content/favicon32.png b/b2g/branding/browserhtml/content/favicon32.png Binary files differnew file mode 100644 index 000000000..ac4a6968b --- /dev/null +++ b/b2g/branding/browserhtml/content/favicon32.png diff --git a/b2g/branding/browserhtml/content/icon48.png b/b2g/branding/browserhtml/content/icon48.png Binary files differnew file mode 100644 index 000000000..b7513c2e4 --- /dev/null +++ b/b2g/branding/browserhtml/content/icon48.png diff --git a/b2g/branding/browserhtml/content/icon64.png b/b2g/branding/browserhtml/content/icon64.png Binary files differnew file mode 100644 index 000000000..c8bee8fca --- /dev/null +++ b/b2g/branding/browserhtml/content/icon64.png diff --git a/b2g/branding/browserhtml/content/jar.mn b/b2g/branding/browserhtml/content/jar.mn new file mode 100644 index 000000000..8a2c16964 --- /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 Binary files differnew file mode 100644 index 000000000..9d9d0c57e --- /dev/null +++ b/b2g/branding/browserhtml/content/logo.png diff --git a/b2g/branding/browserhtml/content/logoWordmark.png b/b2g/branding/browserhtml/content/logoWordmark.png Binary files differnew file mode 100644 index 000000000..878363181 --- /dev/null +++ b/b2g/branding/browserhtml/content/logoWordmark.png diff --git a/b2g/branding/browserhtml/content/moz.build b/b2g/branding/browserhtml/content/moz.build new file mode 100644 index 000000000..eb4454d28 --- /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 Binary files differnew file mode 100644 index 000000000..84ab581d2 --- /dev/null +++ b/b2g/branding/browserhtml/content/splash.png diff --git a/b2g/branding/browserhtml/default.png b/b2g/branding/browserhtml/default.png Binary files differnew file mode 100644 index 000000000..ae4736223 --- /dev/null +++ b/b2g/branding/browserhtml/default.png diff --git a/b2g/branding/browserhtml/disk.icns b/b2g/branding/browserhtml/disk.icns Binary files differnew file mode 100644 index 000000000..c49b7b878 --- /dev/null +++ b/b2g/branding/browserhtml/disk.icns diff --git a/b2g/branding/browserhtml/dsstore b/b2g/branding/browserhtml/dsstore Binary files differnew file mode 100644 index 000000000..657101d6e --- /dev/null +++ b/b2g/branding/browserhtml/dsstore 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 000000000..6c3088d70 --- /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 000000000..897d1a800 --- /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 000000000..2ea47e168 --- /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 000000000..eb4454d28 --- /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 000000000..bf7aff4c0 --- /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 Binary files differnew file mode 100644 index 000000000..6c7b9f5b5 --- /dev/null +++ b/b2g/branding/horizon/app.icns diff --git a/b2g/branding/horizon/app.ico b/b2g/branding/horizon/app.ico Binary files differnew file mode 100644 index 000000000..49eb90419 --- /dev/null +++ b/b2g/branding/horizon/app.ico diff --git a/b2g/branding/horizon/background.png b/b2g/branding/horizon/background.png Binary files differnew file mode 100644 index 000000000..db5576a33 --- /dev/null +++ b/b2g/branding/horizon/background.png diff --git a/b2g/branding/horizon/configure.sh b/b2g/branding/horizon/configure.sh new file mode 100644 index 000000000..65774e384 --- /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 Binary files differnew file mode 100644 index 000000000..3cc1444f6 --- /dev/null +++ b/b2g/branding/horizon/content/about.png diff --git a/b2g/branding/horizon/content/favicon32.png b/b2g/branding/horizon/content/favicon32.png Binary files differnew file mode 100644 index 000000000..ac4a6968b --- /dev/null +++ b/b2g/branding/horizon/content/favicon32.png diff --git a/b2g/branding/horizon/content/icon48.png b/b2g/branding/horizon/content/icon48.png Binary files differnew file mode 100644 index 000000000..b7513c2e4 --- /dev/null +++ b/b2g/branding/horizon/content/icon48.png diff --git a/b2g/branding/horizon/content/icon64.png b/b2g/branding/horizon/content/icon64.png Binary files differnew file mode 100644 index 000000000..c8bee8fca --- /dev/null +++ b/b2g/branding/horizon/content/icon64.png diff --git a/b2g/branding/horizon/content/jar.mn b/b2g/branding/horizon/content/jar.mn new file mode 100644 index 000000000..8a2c16964 --- /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 Binary files differnew file mode 100644 index 000000000..9d9d0c57e --- /dev/null +++ b/b2g/branding/horizon/content/logo.png diff --git a/b2g/branding/horizon/content/logoWordmark.png b/b2g/branding/horizon/content/logoWordmark.png Binary files differnew file mode 100644 index 000000000..878363181 --- /dev/null +++ b/b2g/branding/horizon/content/logoWordmark.png diff --git a/b2g/branding/horizon/content/moz.build b/b2g/branding/horizon/content/moz.build new file mode 100644 index 000000000..eb4454d28 --- /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 Binary files differnew file mode 100644 index 000000000..84ab581d2 --- /dev/null +++ b/b2g/branding/horizon/content/splash.png diff --git a/b2g/branding/horizon/default.png b/b2g/branding/horizon/default.png Binary files differnew file mode 100644 index 000000000..0e6a4016c --- /dev/null +++ b/b2g/branding/horizon/default.png diff --git a/b2g/branding/horizon/disk.icns b/b2g/branding/horizon/disk.icns Binary files differnew file mode 100644 index 000000000..c49b7b878 --- /dev/null +++ b/b2g/branding/horizon/disk.icns diff --git a/b2g/branding/horizon/dsstore b/b2g/branding/horizon/dsstore Binary files differnew file mode 100644 index 000000000..657101d6e --- /dev/null +++ b/b2g/branding/horizon/dsstore 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 000000000..ad7a938b5 --- /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 000000000..ce9e209cf --- /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 000000000..2ea47e168 --- /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 000000000..eb4454d28 --- /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 000000000..bf7aff4c0 --- /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 Binary files differnew file mode 100644 index 000000000..eba850aae --- /dev/null +++ b/b2g/branding/official/app.icns diff --git a/b2g/branding/official/app.ico b/b2g/branding/official/app.ico Binary files differnew file mode 100644 index 000000000..5d4a61dc9 --- /dev/null +++ b/b2g/branding/official/app.ico diff --git a/b2g/branding/official/background.png b/b2g/branding/official/background.png Binary files differnew file mode 100644 index 000000000..db5576a33 --- /dev/null +++ b/b2g/branding/official/background.png diff --git a/b2g/branding/official/configure.sh b/b2g/branding/official/configure.sh new file mode 100644 index 000000000..127a0f1a1 --- /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 Binary files differnew file mode 100644 index 000000000..3cc1444f6 --- /dev/null +++ b/b2g/branding/official/content/about.png diff --git a/b2g/branding/official/content/favicon32.png b/b2g/branding/official/content/favicon32.png Binary files differnew file mode 100644 index 000000000..ac4a6968b --- /dev/null +++ b/b2g/branding/official/content/favicon32.png diff --git a/b2g/branding/official/content/icon48.png b/b2g/branding/official/content/icon48.png Binary files differnew file mode 100644 index 000000000..b7513c2e4 --- /dev/null +++ b/b2g/branding/official/content/icon48.png diff --git a/b2g/branding/official/content/icon64.png b/b2g/branding/official/content/icon64.png Binary files differnew file mode 100644 index 000000000..c8bee8fca --- /dev/null +++ b/b2g/branding/official/content/icon64.png diff --git a/b2g/branding/official/content/jar.mn b/b2g/branding/official/content/jar.mn new file mode 100644 index 000000000..8a2c16964 --- /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 Binary files differnew file mode 100644 index 000000000..9d9d0c57e --- /dev/null +++ b/b2g/branding/official/content/logo.png diff --git a/b2g/branding/official/content/logoWordmark.png b/b2g/branding/official/content/logoWordmark.png Binary files differnew file mode 100644 index 000000000..878363181 --- /dev/null +++ b/b2g/branding/official/content/logoWordmark.png diff --git a/b2g/branding/official/content/moz.build b/b2g/branding/official/content/moz.build new file mode 100644 index 000000000..eb4454d28 --- /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 Binary files differnew file mode 100644 index 000000000..84ab581d2 --- /dev/null +++ b/b2g/branding/official/content/splash.png diff --git a/b2g/branding/official/default.png b/b2g/branding/official/default.png Binary files differnew file mode 100644 index 000000000..c4307fc84 --- /dev/null +++ b/b2g/branding/official/default.png diff --git a/b2g/branding/official/disk.icns b/b2g/branding/official/disk.icns Binary files differnew file mode 100644 index 000000000..c49b7b878 --- /dev/null +++ b/b2g/branding/official/disk.icns diff --git a/b2g/branding/official/dsstore b/b2g/branding/official/dsstore Binary files differnew file mode 100644 index 000000000..657101d6e --- /dev/null +++ b/b2g/branding/official/dsstore 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 000000000..1a6f39148 --- /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 000000000..d0203e35a --- /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 000000000..2ea47e168 --- /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 000000000..eb4454d28 --- /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 000000000..bf7aff4c0 --- /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 Binary files differnew file mode 100644 index 000000000..eba850aae --- /dev/null +++ b/b2g/branding/unofficial/app.icns diff --git a/b2g/branding/unofficial/app.ico b/b2g/branding/unofficial/app.ico Binary files differnew file mode 100644 index 000000000..5d4a61dc9 --- /dev/null +++ b/b2g/branding/unofficial/app.ico diff --git a/b2g/branding/unofficial/background.png b/b2g/branding/unofficial/background.png Binary files differnew file mode 100644 index 000000000..db5576a33 --- /dev/null +++ b/b2g/branding/unofficial/background.png diff --git a/b2g/branding/unofficial/configure.sh b/b2g/branding/unofficial/configure.sh new file mode 100644 index 000000000..127a0f1a1 --- /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 Binary files differnew file mode 100644 index 000000000..3819f6337 --- /dev/null +++ b/b2g/branding/unofficial/content/about.png diff --git a/b2g/branding/unofficial/content/favicon32.png b/b2g/branding/unofficial/content/favicon32.png Binary files differnew file mode 100644 index 000000000..3f04acd50 --- /dev/null +++ b/b2g/branding/unofficial/content/favicon32.png diff --git a/b2g/branding/unofficial/content/icon48.png b/b2g/branding/unofficial/content/icon48.png Binary files differnew file mode 100644 index 000000000..3ae248c85 --- /dev/null +++ b/b2g/branding/unofficial/content/icon48.png diff --git a/b2g/branding/unofficial/content/icon64.png b/b2g/branding/unofficial/content/icon64.png Binary files differnew file mode 100644 index 000000000..fe980153b --- /dev/null +++ b/b2g/branding/unofficial/content/icon64.png diff --git a/b2g/branding/unofficial/content/jar.mn b/b2g/branding/unofficial/content/jar.mn new file mode 100644 index 000000000..8a2c16964 --- /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 Binary files differnew file mode 100644 index 000000000..91377a312 --- /dev/null +++ b/b2g/branding/unofficial/content/logo.png diff --git a/b2g/branding/unofficial/content/logoWordmark.png b/b2g/branding/unofficial/content/logoWordmark.png Binary files differnew file mode 100644 index 000000000..a3017f59e --- /dev/null +++ b/b2g/branding/unofficial/content/logoWordmark.png diff --git a/b2g/branding/unofficial/content/moz.build b/b2g/branding/unofficial/content/moz.build new file mode 100644 index 000000000..eb4454d28 --- /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 Binary files differnew file mode 100644 index 000000000..25a0830ea --- /dev/null +++ b/b2g/branding/unofficial/content/splash.png diff --git a/b2g/branding/unofficial/default.png b/b2g/branding/unofficial/default.png Binary files differnew file mode 100644 index 000000000..c4307fc84 --- /dev/null +++ b/b2g/branding/unofficial/default.png diff --git a/b2g/branding/unofficial/disk.icns b/b2g/branding/unofficial/disk.icns Binary files differnew file mode 100644 index 000000000..c49b7b878 --- /dev/null +++ b/b2g/branding/unofficial/disk.icns diff --git a/b2g/branding/unofficial/dsstore b/b2g/branding/unofficial/dsstore Binary files differnew file mode 100644 index 000000000..657101d6e --- /dev/null +++ b/b2g/branding/unofficial/dsstore 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 000000000..1a6f39148 --- /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 000000000..d0203e35a --- /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 000000000..5a77695c9 --- /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 000000000..eb4454d28 --- /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 000000000..bf7aff4c0 --- /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 000000000..31e20b580 --- /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 000000000..d51782466 --- /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 000000000..616657e54 --- /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 000000000..d3d9e8246 --- /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 000000000..71914be1f --- /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 000000000..b8b20e2c6 --- /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 000000000..bb478087e --- /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 000000000..9612d732c --- /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 000000000..5a1e7ff04 --- /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 000000000..cebc6696b --- /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 000000000..11987a839 --- /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 000000000..64e9d553d --- /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 000000000..9c0ad50a2 --- /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 Binary files differnew file mode 100644 index 000000000..c982426f2 --- /dev/null +++ b/b2g/chrome/content/images/arrowdown-16.png diff --git a/b2g/chrome/content/images/arrowright-16.png b/b2g/chrome/content/images/arrowright-16.png Binary files differnew file mode 100644 index 000000000..859e98ba6 --- /dev/null +++ b/b2g/chrome/content/images/arrowright-16.png diff --git a/b2g/chrome/content/images/desktop/home-black.png b/b2g/chrome/content/images/desktop/home-black.png Binary files differnew file mode 100644 index 000000000..c51187ed4 --- /dev/null +++ b/b2g/chrome/content/images/desktop/home-black.png diff --git a/b2g/chrome/content/images/desktop/home-white.png b/b2g/chrome/content/images/desktop/home-white.png Binary files differnew file mode 100644 index 000000000..43379d0e9 --- /dev/null +++ b/b2g/chrome/content/images/desktop/home-white.png diff --git a/b2g/chrome/content/images/desktop/rotate.png b/b2g/chrome/content/images/desktop/rotate.png Binary files differnew file mode 100644 index 000000000..9da1b5674 --- /dev/null +++ b/b2g/chrome/content/images/desktop/rotate.png diff --git a/b2g/chrome/content/images/error.png b/b2g/chrome/content/images/error.png Binary files differnew file mode 100644 index 000000000..58e37283a --- /dev/null +++ b/b2g/chrome/content/images/error.png diff --git a/b2g/chrome/content/images/errorpage-larry-black.png b/b2g/chrome/content/images/errorpage-larry-black.png Binary files differnew file mode 100644 index 000000000..9f2e4a6e7 --- /dev/null +++ b/b2g/chrome/content/images/errorpage-larry-black.png diff --git a/b2g/chrome/content/images/errorpage-larry-white.png b/b2g/chrome/content/images/errorpage-larry-white.png Binary files differnew file mode 100644 index 000000000..fc153c731 --- /dev/null +++ b/b2g/chrome/content/images/errorpage-larry-white.png diff --git a/b2g/chrome/content/images/errorpage-warning.png b/b2g/chrome/content/images/errorpage-warning.png Binary files differnew file mode 100644 index 000000000..8bf9d8e7d --- /dev/null +++ b/b2g/chrome/content/images/errorpage-warning.png diff --git a/b2g/chrome/content/images/exitfullscreen-hdpi.png b/b2g/chrome/content/images/exitfullscreen-hdpi.png Binary files differnew file mode 100644 index 000000000..826e53408 --- /dev/null +++ b/b2g/chrome/content/images/exitfullscreen-hdpi.png diff --git a/b2g/chrome/content/images/fullscreen-hdpi.png b/b2g/chrome/content/images/fullscreen-hdpi.png Binary files differnew file mode 100644 index 000000000..980e78731 --- /dev/null +++ b/b2g/chrome/content/images/fullscreen-hdpi.png diff --git a/b2g/chrome/content/images/mute-hdpi.png b/b2g/chrome/content/images/mute-hdpi.png Binary files differnew file mode 100644 index 000000000..6daf7cf71 --- /dev/null +++ b/b2g/chrome/content/images/mute-hdpi.png diff --git a/b2g/chrome/content/images/pause-hdpi.png b/b2g/chrome/content/images/pause-hdpi.png Binary files differnew file mode 100644 index 000000000..c7837f822 --- /dev/null +++ b/b2g/chrome/content/images/pause-hdpi.png diff --git a/b2g/chrome/content/images/play-hdpi.png b/b2g/chrome/content/images/play-hdpi.png Binary files differnew file mode 100644 index 000000000..fd64f9697 --- /dev/null +++ b/b2g/chrome/content/images/play-hdpi.png diff --git a/b2g/chrome/content/images/scrubber-hdpi.png b/b2g/chrome/content/images/scrubber-hdpi.png Binary files differnew file mode 100644 index 000000000..b965b73d5 --- /dev/null +++ b/b2g/chrome/content/images/scrubber-hdpi.png diff --git a/b2g/chrome/content/images/throbber.png b/b2g/chrome/content/images/throbber.png Binary files differnew file mode 100644 index 000000000..c601ec80b --- /dev/null +++ b/b2g/chrome/content/images/throbber.png diff --git a/b2g/chrome/content/images/unmute-hdpi.png b/b2g/chrome/content/images/unmute-hdpi.png Binary files differnew file mode 100644 index 000000000..5de342bda --- /dev/null +++ b/b2g/chrome/content/images/unmute-hdpi.png diff --git a/b2g/chrome/content/netError.css b/b2g/chrome/content/netError.css new file mode 100644 index 000000000..59d06a00c --- /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 000000000..a893e8844 --- /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 000000000..95921da4c --- /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 000000000..34daafd99 --- /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 000000000..5507a65aa --- /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 000000000..d483f9a64 --- /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 000000000..4f3f6efc8 --- /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 000000000..1f1115ef0 --- /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 000000000..1a5ed8274 --- /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 000000000..5e3e6814e --- /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 000000000..f2b18eab3 --- /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 000000000..d18a20401 --- /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 000000000..3b13ba431 --- /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 000000000..21f746d33 --- /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 000000000..88c33c897 --- /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 000000000..239c2c2d5 --- /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 000000000..7d31a94f8 --- /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 000000000..7c407c331 --- /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 000000000..1fdea9a50 --- /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 000000000..99af44a5c --- /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 000000000..854de4b15 --- /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 000000000..fe67e9c34 --- /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 000000000..9dfa13d67 --- /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 000000000..820f2406c --- /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 000000000..19a164f0e --- /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 000000000..f4bcf47f4 --- /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 000000000..65671d151 --- /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 000000000..53d0032f9 --- /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 000000000..998e0b7ac --- /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 000000000..24d9f5461 --- /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 000000000..3d3fb37d9 --- /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 000000000..6dc48bd33 --- /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 000000000..e11b1b458 --- /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 000000000..14d8d250b --- /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 000000000..318c46e68 --- /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 000000000..a7dccd0c9 --- /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 000000000..2c0c64c21 --- /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 000000000..803eef681 --- /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 000000000..0eb00cb4c --- /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 000000000..e51f46ed7 --- /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 000000000..d62a7d14f --- /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 000000000..2b53750c2 --- /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 000000000..290613b81 --- /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 000000000..2895aef96 --- /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 000000000..3709833e1 --- /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 000000000..803028d57 --- /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 000000000..c40db9767 --- /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 000000000..6426c21de --- /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 000000000..500fb9112 --- /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 000000000..56c87a3b2 --- /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 000000000..658bbdde5 --- /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 000000000..5007b70e0 --- /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 000000000..5c50401de --- /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 000000000..94326ad50 --- /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 000000000..493763e6d --- /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 000000000..9f9342f67 --- /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 000000000..e6f809375 --- /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 000000000..fd1349d46 --- /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 000000000..18d8f5cc4 --- /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 000000000..c54018fcf --- /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 000000000..b3d5843fc --- /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 000000000..287496a50 --- /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 000000000..021cbcd61 --- /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 000000000..46b0bb8fd --- /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 000000000..1df07204c --- /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 000000000..9290c5e2a --- /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 000000000..8e66d8456 --- /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 000000000..775ce0315 --- /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 000000000..54f5fdd48 --- /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 000000000..a175746cb --- /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 000000000..97df32ea2 --- /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 000000000..9bf3e7819 --- /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 000000000..4407e58d2 --- /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 000000000..fac16db6c --- /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 000000000..0320a14c1 --- /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 000000000..768b221fe --- /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 000000000..92c00dc68 --- /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 000000000..29c35bdec --- /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 000000000..1ebfea1ca --- /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 000000000..cd5694b42 --- /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 000000000..9feeca795 --- /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 000000000..29ac37221 --- /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 000000000..cd13599a3 --- /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 000000000..d2eeb8d48 --- /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 000000000..450094a50 --- /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 000000000..387e3b811 --- /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 Binary files differnew file mode 100644 index 000000000..b1ed7f10a --- /dev/null +++ b/b2g/components/test/unit/data/test_logger_file diff --git a/b2g/components/test/unit/head_identity.js b/b2g/components/test/unit/head_identity.js new file mode 100644 index 000000000..604a77284 --- /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 000000000..e94234f1f --- /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 000000000..d1a7d41aa --- /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 000000000..2bdb8252e --- /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 000000000..4ddbd4280 --- /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 000000000..5de0d6565 --- /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 000000000..9dbe2bc77 --- /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 000000000..d80f33dd9 --- /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 000000000..624dcc6e2 --- /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 000000000..cfb81b893 --- /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 000000000..28de0263f --- /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 000000000..b5af46081 --- /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 000000000..003723ad5 --- /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 000000000..38d4fa79e --- /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 000000000..ca3df5bf6 --- /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 000000000..1902a3000 --- /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 000000000..a1276ffe3 --- /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 000000000..0ed0a67a6 --- /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 000000000..ced2efa33 --- /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 000000000..23934073b --- /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 000000000..0ef93ae11 --- /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 000000000..abe73b460 --- /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 000000000..1cacb8a75 --- /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 000000000..c2f1ae0fa --- /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 000000000..7ecbda356 --- /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 000000000..4cc78ca0e --- /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 000000000..67e7cea4f --- /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 000000000..1577e1f2e --- /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 000000000..2c9c01d67 --- /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 000000000..ab34aa1f7 --- /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 000000000..7e4486368 --- /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 000000000..7b8bada40 --- /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 000000000..f7851b4e3 --- /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 000000000..0419408a3 --- /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 000000000..c1fd5eac1 --- /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 000000000..33158d8dc --- /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 000000000..f0592ee14 --- /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 000000000..64c42caf6 --- /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 000000000..041991547 --- /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 000000000..2f162d7e9 --- /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 000000000..7be519855 --- /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 000000000..daa4e7ac2 --- /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 000000000..4aaa6b78d --- /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 000000000..2c3609c99 --- /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 000000000..08baad0e9 --- /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 000000000..b6f1338ca --- /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 000000000..f5a43bd6a --- /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 000000000..10a722c43 --- /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 000000000..d356dbaa3 --- /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 000000000..33158d8dc --- /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 000000000..a5f5e436a --- /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 000000000..4fb0b58a1 --- /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 000000000..d1b944eb5 --- /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 000000000..0820b16a4 --- /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 000000000..ab98fd151 --- /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 000000000..184fa3400 --- /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 000000000..26fce08a2 --- /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 000000000..fc3037c06 --- /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 000000000..5a3623e5a --- /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 000000000..9277d299f --- /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 000000000..f17dadfe6 --- /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 000000000..9758e72e4 --- /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 000000000..ad36ce038 --- /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 000000000..c29fe6ebc --- /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 000000000..0a695b081 --- /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 000000000..b973fd24c --- /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 000000000..5125bb8f1 --- /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 000000000..89bcb7a5f --- /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 000000000..b9ab618e5 --- /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 000000000..3ab8e7820 --- /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 000000000..f8cbb69e7 --- /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 000000000..807d5d958 --- /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 000000000..f17dadfe6 --- /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 000000000..4be8bc478 --- /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 000000000..0bbd85ac2 --- /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 000000000..ab1eece0b --- /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 000000000..543ab8a4e --- /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 000000000..027632e1d --- /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 000000000..002fdeaf9 --- /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 000000000..246b82399 --- /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 000000000..ed7668b64 --- /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 000000000..455ec1762 --- /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 000000000..73db616cc --- /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 000000000..7db9a7ee6 --- /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 000000000..0c8abf42f --- /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 000000000..e9533bbda --- /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 000000000..75bd243a4 --- /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 000000000..63d8f37e4 --- /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 000000000..9b5093677 --- /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 000000000..28919c271 --- /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 000000000..a5b886c42 --- /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 000000000..557a379da --- /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 000000000..c626e557c --- /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 000000000..44317e1ee --- /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 000000000..d502f0ae5 --- /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 000000000..be5f0b5c5 --- /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 000000000..cd6c6ba63 --- /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 000000000..ecc9e0a04 --- /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 000000000..24f45813a --- /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 000000000..d49507adc --- /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 000000000..970261dbe --- /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 000000000..b40159dfd --- /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 000000000..eb4454d28 --- /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 000000000..c5aec07c9 --- /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 000000000..b98e5e61f --- /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 000000000..b728caa3d --- /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 000000000..392be09b2 --- /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 000000000..634a9cebe --- /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 000000000..ea0264d9a --- /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 Binary files differnew file mode 100644 index 000000000..c4307fc84 --- /dev/null +++ b/b2g/simulator/icon.png diff --git a/b2g/simulator/icon64.png b/b2g/simulator/icon64.png Binary files differnew file mode 100644 index 000000000..275cd04a5 --- /dev/null +++ b/b2g/simulator/icon64.png diff --git a/b2g/simulator/install.rdf.in b/b2g/simulator/install.rdf.in new file mode 100644 index 000000000..e9827d084 --- /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 000000000..46bbccc7b --- /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 000000000..5d9ed8a8d --- /dev/null +++ b/b2g/test/emulator.manifest @@ -0,0 +1,6 @@ +[{ +"size": 746441603, +"digest": "199236aefecc1657cdc1b791ec38c8184557ab9249aff9c63a74abf73edc1dc0ea36b19b558f34ca3b14f8a511b10bcf37408b19701929522b4dc22dbaddcbe9", +"algorithm": "sha512", +"filename": "emulator.zip" +}] |