Mobile/Fennec/Android
Contents
- 1 Building Fennec
- 2 Hacking
- 3 Testing
- 4 Debugging
- 4.1 Using logcat
- 4.2 Using JimDB
- 4.3 Using Debug Intent
- 4.4 Getting dalvik java stack dumps using gdb
- 4.5 Debugging with jdb
- 4.6 Debugging with eclipse
- 4.7 Arguments and Environment Variables
- 4.8 PR Logging
- 4.9 Using legacy GDB (non-JimDB)
- 4.10 Reading back the framebuffer
- 4.11 Using Rendertrace (Maple)
- 4.12 Using apitrace
- 4.13 about:memory
- 4.14 Profiling
- 4.15 Debugging Java code with DDMS
- 5 Other useful tips and tricks
Building Fennec
First follow the platform-specific instructions below to set up a build environment on your machine. Once you have done that, follow the steps to get the source code, set up your mozconfig, and build Fennec.
Also, the OtherBuildEnvs page has some notes on a few other environment variations, so take a look at that as well if you have an environment or configuration that deviates from "normal".
Windows (using Linux VM)
It is not currently possible to compile Fennec directly on Windows.
A fresh install of Ubuntu 13.10 in a virtual machine can be configured to build Fennec following the Linux directions below, but ensure that you've assigned plenty of RAM and CPU to the VM. Beware that having insufficient RAM for the build, or assigning more RAM to the VM than the host system can support, may result in very very slow builds.
If your virtualization software supports connecting USB devices to a VM, it should be possible to install directly to device from the build VM.
Linux
Quick Script
Here is a quick script to get set up. It is explained in more detail below. Run it in the directory where you want the Android NDK installed.
# Set up this PPA to get access to Oracle Java 6 on most Ubuntu versions # https://launchpad.net/~webupd8team/+archive/java sudo apt-get update sudo apt-get install oracle-java6-installer mercurial ccache sudo apt-get build-dep firefox sudo apt-get install ant wget http://dl.google.com/android/ndk/android-ndk-r8e-linux-x86_64.tar.bz2 tar -xjf android-ndk-r8e-linux-x86_64.tar.bz2 wget http://dl.google.com/android/adt/adt-bundle-linux-x86_64-20131030.zip unzip adt-bundle-linux-x86_64-20131030.zip ./adt-bundle-linux-x86_64-20131030/sdk/tools/android update sdk -u ./adt-bundle-linux-x86_64-20131030/sdk/tools/android update adb
If you're using a 64-bit Linux, you'll need to install 32 bit libs to allow the toolchain binaries to run. Otherwise you may get a "bash: file not found" error when trying to use any of the SDK/NDK tools.
# http://stackoverflow.com/questions/13571145/android-adb-not-found sudo apt-get install libc6-i386 lib32stdc++6 lib32gcc1 lib32ncurses5 lib32z1
or for Fedora
yum install glibc-devel.i686 ncurses-libs-devel.i686 libstdc++-devel.i686 zlib-devel.i686
On older Debian/Ubuntu systems you can run
# On recent Debian or Ubuntu you may need to run "sudo dpkg --add-architecture i386" first. # For details see: http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=697924 sudo apt-get install ia32-libs
Explained
This section describes in greater detail all the dependencies and requirements of getting a working build environment on Linux.
Install Java
First install the Sun Java jdk6, which the Android SDK depends on. If you're on Ubuntu (pre-11.10), you'll need to enable the partners repo to get it. Java 7 should work, although occasionally breaks (java 6 is what the buildbots use, so that's the "officially supported" one).
# Ubuntu pre-11.10 sudo apt-get update sudo apt-get install sun-java6-jdk sudo update-java-alternatives -s java-6-sun
# Ubuntu 11.10 and after # Use this PPA, the instructions are on the page: # https://launchpad.net/~webupd8team/+archive/java
If you're on Ubuntu 64-bit, you'll also want to install ia32-libs at this point (things like adb won't work until you do):
apt-get install -y ia32-libs
On Ubuntu 13.10 and later ia32-libs is no longer in the standard repository. Add the following line to the end of your /etc/apt/sources.list file (or add it through synaptic)
deb http://archive.ubuntu.com/ubuntu/ raring main restricted universe multiverse
apt-get update apt-get install -y ia32-libs
Install ant
ant 1.8.0 or later is required to build GeckoViewExample
sudo apt-get install ant
Install Gecko Requirements
Then install the usual stuff needed for a firefox build.
sudo apt-get install mercurial ccache sudo apt-get build-dep firefox
Install Android NDK
Download and extract the Android NDK. The automated builders currently use version r8e. r8d does NOT work (see bug 825968).
For 64 Bit OS: wget http://dl.google.com/android/ndk/android-ndk-r8e-linux-x86_64.tar.bz2 tar -xjf android-ndk-r8e-linux-x86_64.tar.bz2
For 32 Bit OS; wget http://dl.google.com/android/ndk/android-ndk-r8e-linux-x86.tar.bz2 tar -xjf android-ndk-r8e-linux-x86.tar.bz2
Install Android SDK
You should install the latest Android SDK. The SDK download will take a while, make sure you have a decent internet connection and go get coffee, or maybe lunch.
After extracting the SDK, ensure it's up to date with these commands:
./adt-bundle-linux/sdk/tools/android update sdk -u ./adt-bundle-linux/sdk/tools/android update adb
Or to update manually, run the Android SDK GUI updater:
./adt-bundle-linux/sdk/tools/android
Make sure you check "Android SDK Tools", "Android SDK Platform-tools", "Android SDK Build-tools", "SDK Platform" from at least API version 16, and "Android Support Library" under "Extras".
You will probably want to add the SDK's "tools", "build-tools", and "platform-tools" directories to the PATH environment variable in your shell, so that you can run adb and other tools easily. For example, if you installed the SDK in $HOME/opt, you could add the following line to the end of your .bashrc:
export PATH=$PATH:$HOME/opt/adt-bundle-linux/sdk/tools:$HOME/opt/adt-bundle-linux/sdk/build-tools:$HOME/opt/adt-bundle-linux/sdk/platform-tools
You'll also have to ensure that the jarsigner executable from the JDK is in your path somewhere. Its probably easiest to just find it using
locate jarsigner
Possible Footguns
Don't set CC / CXX environmental variables
If you've set the environmental variables CC and CXX (e.g. via .bash_aliases or via your .mozconfig), then you probably need to unset them before building for Android, or else your build may fail with something like:
checking whether the C compiler (gcc -mandroid -fno-short-enums (etc etc)) works... no
followed by errors about "C compiler cannot create executables", "Relocations in generic ELF (EM: 40)", and "crtbegin_dynamic.o: error adding symbols: File in wrong format". This is a sign that you're compiling with your platform's native compiler (due to having CC / CXX set), instead of the android-specific GCC version that ships with the NDK. See bug 977817 for more details; as noted there, the build system may trust your custom CC & CXX variables, when you probably don't want it to.
Mac OS X
To set up a build environment on Mac OS X, you first need to install XCode and a few supplementary build tools via a package manager. To do this, follow steps 1 and 2 of the Mac OS X Build Prerequisites page. Once you have completed steps 1 and 2, come back here and continue with the instructions below.
After installing XCode make sure you also have wget installed. If wget is not a recognized command in terminal follow these steps to install it:
curl -O http://ftp.gnu.org/gnu/wget/wget-1.14.tar.gz tar -xzvf wget-1.14.tar.gz cd wget-1.14 ./configure --with-ssl=openssl make sudo make install which wget #Should output: /usr/local/bin/wget
Other packages
You'll need some additional packages, which you can install with HomeBrew.
brew install ant
You can optionally install ccache, which can make rebuilding Firefox faster after you have built it once and don't change (much) of the source code. (If you don't install ccache, you must remove the --with-ccache line from the sample mozconfig below.)
brew install ccache
Install Android NDK
Download and extract the Android NDK. The NDKs with 64-bit toolchains (x86_64) are recommended because linking Firefox requires a lot of memory. The supported NDK version right now is r8e. Installing newer versions will break your builds (see bug 951968 and bug 994859 for some examples).
wget http://dl.google.com/android/ndk/android-ndk-r8e-darwin-x86_64.tar.bz2 tar -xjf android-ndk-r8e-darwin-x86_64.tar.bz2
NOTE: there have been reports that r8c does not work on OS X before 10.7.0 (i.e. on Snow Leopard); you may need to try an older (or newer!) NDK version if you are running Snow Leopard. link and link. At time of updating this doc (17-may-2013), we have not tried r8e on OSX before 10.7.0.
Install Android SDK
You should just install the latest Android SDK, since we set the API level in our manifest files.
The SDK download will take a while, make sure you have a decent internet connection and go get coffee, or maybe lunch.
You will need to install support for Android API version 16. Run the Android SDK GUI updater:
./adt-bundle-mac/sdk/tools/android
You must uncheck version 18 or higher of "Android SDK Build-tools" and check version 17 instead. You will also need the "Android Support Library" under Extras. In addition, you should check the Android 4.1.2 (API 16).
You will probably want to add the SDK's "tools", "build-tools", and "platform-tools" directories to the PATH environment variable in your shell, so that you can run adb and other tools easily. For example, if you installed the SDK in $HOME, you could add the following line to the end of your .bashrc:
export PATH=$PATH:$HOME/adt-bundle-mac/sdk/tools:$HOME/adt-bundle-mac/sdk/build-tools:$HOME/adt-bundle-mac/sdk/platform-tools
NDK/SDK version notes
Note that in the event that the NDK and SDK versions listed here are out of date, you can always find the canonical version numbers here. Look for the following variables, which should be self-explanatory.
- ANDROID_NDK_VERSION
- ANDROID_NDK_VERSION_32BIT
- ANDROID_SDK_VERSION
Getting the source
Once you have build tools set up, you should configure Mercurial as described here. (It is not required, but highly recommended if you plan on touching the code in any way). Once you have done that, grab a clone of the repository:
hg clone http://hg.mozilla.org/mozilla-central/ src
Setup Fennec mozconfig
The mozconfig file is what tells the mozilla build scripts how your build environment is set up, and sets various build options. To build scripts will read the mozconfig file from the $MOZCONFIG environment variable, if one is set, or the .mozconfig file in your src directory, if there is one. So you could do either this:
cd src vim .mozconfig # put mozconfig here
or this:
cd some/random/folder vim my-fennec-mozconfig # put mozconfig here export MOZCONFIG=$PWD/my-fennec-mozconfig
Here is the minimal mozconfig file for building Fennec, assuming you have followed the instructions above and are using version r8e of the NDK.
# Add the correct paths here: ac_add_options --with-android-ndk="$HOME/android-ndk-r8e" ac_add_options --with-android-sdk="$HOME/adt-bundle-linux/sdk/platforms/android-16" # android options ac_add_options --enable-application=mobile/android ac_add_options --target=arm-linux-androideabi
You may optionally add support for ccache:
ac_add_options --with-ccache
Note that if you are using ccache, the default cache size of 1GB is not large enough for a Firefox build. To fully utilize ccache (full builds in <5 minutes after the initial build), you'll need to change the cache size to at least 4GB, so run:
$ ccache --max-size 4G
Or use the objdir of your liking:
mk_add_options MOZ_OBJDIR=./objdir-droid
Remember to use $HOME instead of ~ in the mozconfig, because ~ will not get expanded to the right path!
If you want to build for ARMv6 instead of ARMv7 (ONLY required for old/very low end phones!) add this:
ac_add_options --with-arch=armv6
If you want to build for x86, substitute:
ac_add_options --target=i386-linux-android
for the target specified above. You cannot use ac_add_options --disable-optimize when building for x86. See bug 965870.
If you want to create a debug build, you may add:
ac_add_options --enable-debug-symbols
Building and deploying the code
Before you start building connect your Android device and enable USB debugging.
The following steps will build (compile+link), package into apk, and deploy to the Android device.
cd src ./mach build ./mach package ./mach install
The APK file can be found in your objdir-droid/dist/ folder, and will be called something like fennec-28.0a1.en-US.android-arm.apk. You can install this APK to your device manually using "adb install" or "./mach install".
The name of the app that shows up on your phone will be "Fennec $USER" (where $USER is the username under which you built the code).
To speed up subsequent builds you should specify the android directory using "./mach build mobile/android" or "./mach build mobile/android/base" depending on where your changes are.
If you get a build error related to stale classes during ProGuard:
make -C objdir-droid/mobile/android/base clean
then try again.
Hacking
Finding relevant code
The Fennec-specific code can be found in the following locations in the mozilla-central source tree:
mobile/android/... widget/android/...
Of particular interest to most new contributors will be:
mobile/android/base/GeckoApp.java # the main android activity that starts up when you open Fennec mobile/android/chrome/content/browser.js # the main JS file that controls Gecko to make it do what we want
If you are looking for something specific, use the code-search tools at https://mxr.mozilla.org/mozilla-central/ and http://dxr.mozilla.org/ to search for relevant pieces of code.
Coding Style
Java
Follow the Mozilla Coding Style.
XML files (layout, resources, styles, etc)
- Each child tag should be indented with 4 spaces.
- The properties should be aligned with the first property of a tag.
- Each element should have "android:id" first, "style" (if exists) second, and the other properties follow. The root will have "xmlns:android" as first property.
- A Line break after every tag.
Common nits
Check for these nits before asking for review for a patch:
- Remove trailing whitespace
- Spacing
- Four spaces for Java indent (for main Fennec code - android-sync github project has different spacing)
- Spaces around operators (+, -, etc.)
- Space between comment "//" and text
- Braces for Java if statements, even if they are one line
- Comments should be full sentences (capitalization, punctuation)
Closing resources
When handling resources (like Cursors), a try/finally block should be used to ensure these are closed properly. For example:
final Cursor c = getCursor(); try { useCursorWhichMightThrowException(c); } catch (SomeSpecificException sse) { log(sse); } finally { c.close(); }
Once the try block is entered, the finally block will *always* get executed upon exit of the try block. The one exception is if there is a System.exit call inside the try block, which immediately exits the program and makes everything moot anyway. The finally block will get executed on caught and uncaught exceptions, as well as normal returns.
If you are casting the resource to something, make sure that you do the cast inside the try block, like so:
// GOOD! InputStream is = getInputStream(); try { FileInputStream fis = (FileInputStream) is; ... } finally { ... }
rather than doing this:
// BAD! FileInputStream fis = (FileInputStream) getInputStream(); try { ... } finally { ... }
This is so that in case of ClassCastExceptions you don't get a dangling open resource left behind.
Caveats for Timing
TLDR: Google recommends using SystemClock.uptimeMillis() for general purpose interval timing of user interface events or performance measurements. If you're adding stuff for timing, use SystemClock.uptimeMillis(), rather than something like new Date().getTime().
Normally in Java the default time-getter is System.currentTimeMillis() since it avoids the overhead of creating a new Date object. This is also what new Date() does under the hood. However, currentTimeMillis() and the Date object are both subject to change in unexpected ways if the user changes the time on their device, or if daylight savings comes into effect, or there's a network time update, or whatever. So Android has generously provided android.os.SystemClock which has various functions that you can use to get a better timestamp. Refer to the class javadoc and pick whichever function is most suitable for what you're trying to measure.
http://developer.android.com/reference/android/os/SystemClock.html
Partial Builds: Beware the startup cache!
Developers who frequently update .js files sometimes like to manually only re-build the module they have updated. Partial builds for updated Javascript files may not work correctly if installed with adb install -r. This section describes the issue. For instance:
./mach build mobile/android && ./mach package
This should result in the APK being properly updated with the updated Javascript, BUT the change may not be reflected on the device because of complications arising from the startup cache. If the startup cache from a previous run of Fennec exists on the device and contains an old version of the recently updated Javascript, Fennec will likely use the old version.
The startup cache is located in the profile, so deleting it ensures a new startup cache (see 976216 for details):
adb shell am force-stop org.mozilla.fennec adb shell "rm -r /data/data/org.mozilla.fennec/files/mozilla/*.default/startupCache"
Note that:
adb install -r dist/fennec*.apk
retains the profile (and startup cache) -- so that's still a problem.
None of this is a problem with full builds:
./mach build && ./mach package adb install -r dist/fennec*.apk
works just fine, because the startup cache respects the buildid: If the buildid found in an APK is different from the buildid used when the startup cache was last updated, the startup cache is automatically deleted. A full build correctly updates the buildid -- a partial build may not.
Multilocale builds
- Create a directory, clone mozharness, copy the config file for easy editing/usage:
mkdir multilocale cd multilocale hg clone http://hg.mozilla.org/build/mozharness cp mozharness/configs/multi_locale/standalone_mozilla-central.py myconfig.py
- Edit myconfig.py
- currently will check out m-c into a directory named 'mozilla-central' in this directory
- currently assumes your mozconfig is in this directory and named 'mozconfig'
- currently assumes your mozconfig sets your objdir name to 'objdir-droid'
- pull mozilla-central
mozharness/scripts/multil10n.py --cfg myconfig.py --pull-build-source # Alternately, you can hg clone http://hg.mozilla.org/mozilla-central
- Run the script, which will create a multilocale apk
mozharness/scripts/multil10n.py --cfg myconfig.py
And you're done.
- If you want to recompile or re-run the script, restore your objdir to en-US first!
mozharness/scripts/multil10n.py --cfg myconfig.py --restore-objdir
Also see this blog post for more information.
Single-locale language repacks
There is a script in mozharness for this (scripts/mobile_l10n.py) but it relies on buildbot information so it's not suitable for local repacks.
This assumes that $(AB_CD) is the locale you want to repack with; I tested with "ar" and "en-GB".
- clone l10n-central/$(AB_CD) so that it is a sibling of your mozilla-central directory
- I assume your object directory is "objdir-droid" and that you have built and packaged already
make -f client.mk && make -C objdir-droid package
- copy your .mozconfig to .mozconfig.l10n and add the following lines
# L10n ac_add_options --with-l10n-base=../../l10n-central # Global options ac_add_options --disable-tests mk_add_options MOZ_OBJDIR=./objdir-l10n
- cd to mozilla-central
- configure and prepare objdir-l10n
MOZCONFIG=.mozconfig.l10n make -f client.mk configure make -C objdir-l10n/config
- copy your built package into objdir-l10n
cp ./objdir-droid/dist/fennec-*en-US*.apk ./objdir-l10n/dist
- unpack. This files objdir-l10n/dist with the bits of the APK, ready for re-assembling.
make -C objdir-l10n/mobile/android/locales unpack
- compare locales (you may need to install the compare-locales tool first). This writes locale differences into objdir-l10n/merged.
compare-locales -m objdir-l10n/merged mobile/android/locales/l10n.ini ../l10n-central $(AB_CD)
- finally, re-assemble with the locale differences
LOCALE_MERGEDIR=objdir-l10n/merged make -C objdir-l10n/mobile/android/locales installers-$(AB_CD)
You should find an APK at "objdir-l10n/dist/fennec-*$(AB_CD)*.apk".
Testing
This section has instructions for running tests locally on a device of your choice. In theory, it is easy to run tests locally, but the test infrastructure is optimized and maintained primarily for the tegra boards and the panda boards used for continuous integration: It is not uncommon for device-specific bugs to creep in to the test infrastructure, causing problems when tests are run locally.
Running tests locally on non-rooted devices is particularly troublesome and sometimes impossible -- use a rooted device if at all possible.
Having trouble? Ping :gbrown on #mobile, or ask for help on #ateam.
Device Managers
Most test suites - mochitests, reftests, xpcshell tests, and others - use a "device manager" module to communicate with the remote device. There are two device manager implementations: ADB and SUT.
The ADB device manager uses the adb command from the Android SDK to communicate with the remote device. To use the ADB device manager:
- ensure the adb command is in your shell's PATH
- set environment variable DM_TRANS=adb
- do not set environment variable TEST_DEVICE, or set TEST_DEVICE= (unless you really need it)
The SUT device manager uses TCP to communicate with a remote agent, which must be installed on the device. To use the SUT device manager:
- ensure TCP connectivity between the local host and the remote device: check that they are on the same network and you can ping each from the other
- ensure the SUT agent is installed and started on the remote device
- the SUT agent APK is built alongside Fennec; just install <objdir-droid>/build/mobile/sutagent/android/sutAgentAndroid.apk
- The agent should be configured to start automatically with your phone when it boots. To start it immediately from an adb shell do: `am start -n com.mozilla.SUTAgentAndroid/.SUTAgentAndroid -a android.intent.action.MAIN`
- set environment variable DM_TRANS=sut
- set environment variable TEST_DEVICE=<ip address of remote device -- displayed by SUT agent>
Note: to run the tests you should ensure that your host machine and device are on the same network so that the device can contact the server that will run on the host.
Host Builds (MOZ_HOST_BIN)
Android mochitests and reftests require a parallel host build -- a build for the local host (desktop) environment. An environment variable, MOZ_HOST_BIN, must be set to point to that host build. MOZ_HOST_BIN is used to run xpcshell (for instance, to run a simple web server on the desktop), so the MOZ_HOST_BIN directory must contain xpcshell and all the shared libraries required by xpcshell. You can patch together these files from other sources, but the easiest way to get these is to download a xulrunner SDK build from here: http://ftp.mozilla.org/pub/mozilla.org/xulrunner/nightly/latest-trunk/
You can also build desktop Firefox with a mozconfig which might be as simple as:
ac_add_options --enable-application=browser mk_add_options MOZ_OBJDIR=./objdir-desktop
Then execute:
export MOZCONFIG=mozconfig.desktop ./mach build MOZ_HOST_BIN=objdir-desktop/dist/bin ls -l $MOZ_HOST_BIN/xpcshell
On Linux, the path to that build may also need to be in your LD_LIBRARY_PATH, unless your LD_LIBRARY_PATH contains ".":
LD_LIBRARY_PATH=$LD_LIBRARY_PATH:.
Test Directory
All Android tests require a remote test directory: A place to store pre-configured profiles, support binaries and libraries, test files, etc. Most tests use a directory in /mnt/sdcard by default; xpcshell and cppunittests use /data/local by default (because it is usually not possible to set execute permission on files on /mnt/sdcard).
The default remote test directory is usually correct and sufficient, but sometimes the default is not appropriate for a device:
- the device may not contain an SD card, or the SD card may not be mounted
- there may not be enough free space on the default location's partition
- the default location may not be writable by the ADB shell and/or SUT agent
(If you are using a Nexus S, the trick to making your device mountable is to not allow USB Storage between your computer and your device. When you plug in your device to your computer, simply don't click the button to allow this on your device and you should be able to run your tests.)
If necessary, the default remote test directory may be changed with:
export EXTRA_TEST_ARGS=--remoteTestRoot=<remote-directory>
Reftests
MOZ_HOST_BIN="<abspath-to-objdir-desktop>/dist/bin/" TEST_PATH=<path> make -C <objdir-droid> reftest-remote
Example TEST_PATH:
TEST_PATH=layout/reftests/reftest-sanity/reftest.list
Notes:
- If TEST_PATH is not specified, *all* reftests will be attempted: This usually fails/hangs and is not recommended. Specify a TEST_PATH or use the --total-chunks and --this-chunk arguments to reduce the number of tests executed.
- sut recommended. Test results displayed and saved to reftest.log and reftest-remote.log.
- adb works also. Test results displayed and saved to reftest.log (additional diagnostics in reftest-remote.log).
- Ensure that the device and host machine are on the same network.
Mochitests
Linux:
MOZ_HOST_BIN="<abspath-to-objdir-desktop>/dist/bin/" TEST_PATH=<path> make -C <objdir-droid> mochitest-remote
Mac:
MOZ_HOST_BIN="<abspath-to-objdir-desktop>/dist/Nightly.app/Contents/MacOS/" TEST_PATH=<path> make -C <objdir-droid> mochitest-remote
Note that as of this writing (September 2011) many mochitests will not complete successfully. Try setting your TEST_PATH to "dom/tests/mochitest/dom-level1-core" or "content/smil/test" if you want to restrict yourself to a subset of tests that are known to pass.
Some examples of possible TEST_PATH values are:
Harness_sanity MochiKit-1.4.2/tests content/smil/test content/xml/document/test content/xslt/tests/mochitest dom/src/json/test dom/src/jsurl/test dom/tests/mochitest/ajax/jquery dom/tests/mochitest/ajax/mochikit dom/tests/mochitest/ajax/scriptaculous dom/tests/mochitest/dom-level0 dom/tests/mochitest/dom-level1-core dom/tests/mochitest/dom-level2-core dom/tests/mochitest/dom-level2-html dom/tests/mochitest/orientation dom/tests/mochitest/storageevent editor/composer/test intl/uconv/tests js layout/inspector/tests layout/xul/test modules/libjar/test/mochitest parser/htmlparser/tests/mochitest toolkit/components/microformats/tests toolkit/xre/test
Notes:
- A rooted device is required to run the unit tests using the SUT Agent. Use ADB for unrooted devices (see [[1]]).
- Currently using ADB and using /data/local/tests on a non-rooted phone causes setting TEST_PATH to a directory rather than an individual HTML file to fail, and SpecialPowers tests to fail too - filed as bug 822652
- Ensure that the device and host machine are on the same network.
Robocop
The Robocop test suite verifies UI behavior in native Fennec. It is built on the Robotium testing framework. To run tests locally, a separate Robocop test apk also needs to be installed.
To get tests running locally, first build and install native Fennec, then execute the mochitest-robocop make target. The make target installs the Robocop APK to the device.
./mach build ./mach package ./mach install MOZ_HOST_BIN="<abspath-to-objdir-desktop>/dist/bin/" make -C $OBJDIR mochitest-robocop
More info at Robocop and http://mxr.mozilla.org/mozilla-central/source/mobile/android/base/tests/README.rst.
If you make changes to the tests and want to see them run on a device, you need to build the tests again and reinstall.
./mach build build/mobile/robocop make -C $OBJDIR mochitest-robocop
This builds the tests in mobile/android/base/tests/ and then installs the debug-signed Robocop apk onto the device. (It's unintuitive that building mobile/android doesn't build the tests within mobile/android/base/tests.)
(We also build a release-signed Robocp apk. To build and install this:
./mach build build/mobile/robocop ./mach package ./mach install
The only difference in this approach is that the package step signs the Robocop apk with the release key. This just takes longer.)
Notes:
- A rooted device is NOT required.
- SUT and ADB device managers are both supported.
- MOZ_HOST_BIN is used to launch xpcshell on the desktop to provide a web server.
- Use TEST_PATH=<test-name> to run just one test at a time. eg. TEST_PATH=testAwesomebar
Troubleshooting:
- Ensure the device's screen is on
- Ensure that the device and host machine are on the same network.
- Are the phone and the desktop both using wifi? (wifi vs ethernet??)
- Are the phone and the desktop both using the same wifi network? (Mozilla vs Mozilla Guest??)
- Is the desktop environment running in a VM? If so, you likely want a "Bridged" connection -- not NAT or Host-only.
- Ensure Robocop has the correct IP address for your machine
- Make sure _SERVER_ADDR in the Robocop output is the same as your machine's IP address.
- If not, set it to your IP by running |export DM_FLAGS="--remote-webserver=xxx.xxx.xxx.xxx"|
- Additional tips at Auto-tools/Projects/Robocop#Frequently_found_errors
xpcshell
Be sure you have successfully built Fennec and generated an APK, as described above.
To run all tests referenced by the master xpcshell manifest:
mach xpcshell-test
(Currently the master manifest is restricted: not all tests are run.)
To run a subset of tests in the specified directory:
mach xpcshell-test <test-directory>
Once either of the xpcshell-tests-remote commands has completed successfully, all test files have been copied to device, and it is then possible to run a single test quickly, without setup:
mach xpcshell-test <test.js> --no-setup
Notes:
- A rooted device IS required.
- Both ADB and SUT device managers are supported.
- Setup can take several minutes! When using ADB, setup is faster if unzip is available on the remote device; if your device does not have unzip, try installing busybox.
cppunittests
To run a single compiled code test:
cd <objdir-droid> export TEST_PATH=<test> make cppunittests-remote
For example, TEST_PATH=xpcom/tests/TestTimers, or TEST_PATH="xpcom/tests/TestTimers xpcom/tests/TestFile".
To run all the compiled code tests in a directory:
cd <objdir-droid> make -C <dir> cppunittests-remote
For example:
make -C xpcom/tests cppunittests-remote
Advanced features:
As with xpcshell tests, you can skip setup if needed:
export EXTRA_TEST_ARGS="--noSetup"
This will avoid copying any files to the device -- be sure that all necessary files are on the device before using this!
You can change the environment variables seen by the unit test:
export EXTRA_TEST_ARGS="--addEnv MYVAR=value" export EXTRA_TEST_ARGS="--addEnv MYVAR1=value1 --addEnv MYVAR2=value2" export EXTRA_TEST_ARGS="--addEnv HOME="
Notes:
- Either adb or sut device manager may be used
- All files are copied to /data/local/tests by default. On some devices, you may need to create /data/local/tests and make it world writable.
browser-chrome
Before you run tests, you will need to make sure you have packaged the tests in your object dir:
make -C <objdir-droid> package-tests
There is currently no special make command to build and run browser-chrome tests, but it should be possible to make them run by calling:
cd <objdir-droid>/_tests/testing/mochitest python runtestsremote.py --dm_trans=adb --test-path=mobile --browser-chrome --deviceIP=1.2.3.4 --app=org.mozilla.fennec_$USER --xre-path=<objdir_x86>/dist/bin/
talos
- NOTE: this requires python 2.5 or greater (tested up to 2.7)
See https://wiki.mozilla.org/Buildbot/Talos
This is 100% out of band from mozilla-central (same for desktop also).
hg clone http://hg.mozilla.org/build/talos talos cd talos python INSTALL.py . bin/activate cd talos
Run these commands to check out the talos dependencies (assuming you are already in the "talos/talos" directory):
cd page_load_test wget http://people.mozilla.org/~jmaher/mobile_tp4.zip unzip mobile_tp4.zip
Configure a talos profile. You can do this via adb or SUTAgent - if you want to use adb make sure you have rooted your device such that "adb shell" goes directly into a root shell. If you want to use adb to communicate with the device remotely, do something like this:
talos -v -e org.mozilla.fennec \ --develop \ --activeTests tprovider \ --output trobo.yml \ --remotePort -1 \ --remoteDevice 1.2.3.4 \ [--fennecIDs <abs-path-to-objdir>/dist/fennec_ids.txt]
If you want to use SUTAgent to communicate with the device, do something like this:
talos -v -e org.mozilla.fennec \ --develop \ --activeTests tsvg \ --results_url file://${PWD}/tsvg.txt \ --noChrome \ --remoteDevice <ip of your sutagent> \ [--fennecIDs <abs-path-to-objdir>/dist/fennec_ids.txt]
SUTAgent will not be able to read the application.ini on the device, so you will need to copy it. We highly recommend pulling the application.ini from the .apk and copying it to the talos/talos/remoteapp.ini file.
For Robocop based tests (tcheck, tcheck2, tcheck3, tprovider, etc...), we need to use the --fennecIDs flag to pass in the generated fennec_ids.txt file from the build you are testing. This file is generated during build time and has to match the fennec.apk and robocop.apk file. When this flag is used, we copy fennec_ids.txt and robotium.config (generated during configuration time) to the device and use those to run Robocop. If you are running ts, tp4, tsvg, or other traditional talos tests, there is no need for the --fennecIDs flag.
Unless two applications are signed with the same key they cannot read each others' /data/data directories, so SUTAgent cannot read org.mozilla.fennec's data directory. You can extract it from the fennec .apk or get it from adb from e.g. /data/data/org.mozilla.fennec/application.ini
shell@android:/ $ su shell@android:/ # cat /data/data/org.mozilla.fennec/application.ini [App] Vendor=Mozilla Name=Fennec Version=10.0a1 BuildID=20111031031100 SourceRepository=http://hg.mozilla.org/mozilla-central SourceStamp=04b4ea333800 ID={a23983c0-fd0e-11dc-95ff-0800200c9a66} [Gecko] MinVersion=1.9.2b5pre MaxVersion=10.0a1 [XRE] EnableExtensionManager=1 [Crash Reporter] Enabled=1 ServerURL=https://crash-reports.mozilla.com/submit
Note: As of this writing (Sept 27, 2011), the tgfx pageset does not currently work (it isn't run on the desktop either). Don't try it. :)
Note: When running robocop-based talos tests (tcheck, etc), be aware that some robocop-required files are not updated by Talos. For example, robocop.apk is not installed. Your best bet is to run mochitest-robocop first to make sure everything is up to date, or manually install robocop.apk from the dist folder.
Aside: For a quick-and-dirty hacky way to run robocop-talos tests locally, see Mobile/Fennec/Android/LocalRoboTalos
S1/S2 Automation
These tests start Fennec with a URL and measure the time to throbber start, time to throbber stop, and drawing end times.
S1/S2 graphs can be viewed at: http://phonedash.mozilla.org/
Manual run instructions can be found at: https://etherpad.mozilla.org/fennec-perf-ts-take2
See also: https://wiki.mozilla.org/Mobile/Performance/S1S2-Tests
Eideticker
Eideticker measures perceived Firefox performance by video capturing automated browser interactions.
Eideticker graphs can be viewed at: http://eideticker.mozilla.org/
See also:
Trouble-shooting testing problems
- Does your mozconfig contain "ac_add_options --disable-tests"?
- If so, you will see something like:
make: *** No rule to make target <your-test-target>. Stop.
- Is adb in your $PATH?
- Is your device connected? Does it appear in the output from "adb devices"?
- Can you run adb shell?
Debugging
Using logcat
logcat is a tool that is going to show you some logs prompted by the device. It might be a good help if you don't want to or can't run gdb. You can use it by running this command:
adb logcat -v time
You can make things appear in logcat using printf_stderr. With debug builds, NS_WARNING, NS_ERROR and NS_ASSERTIONS will show up in logcat. If you're trying to debug something, you may wish to pipe the logcat output through grep to filter out irrelevant things (most Fennec-related output will be viewable by
adb logcat -v time | grep Gecko
but remember that when attaching log output to a bug you should include unfiltered output as there may be relevant log entries under other tags.
Using aLogCat
If you don't have the Android SDK installed, you can still extract logs using an application called aLogCat. Install it from the Android Market. Use it to capture logs and attach the logs to bugs.
Note: aLogCat (and other logcat apps) do not support Jelly Bean (4.1) and above. It seems like the only way to get these apps to work on those versions is to root the device, which, although fulfilling, is probably more work than just using adb logcat
. If you are running Jelly Bean and above, you can instead install the about:logcat add-on at https://people.mozilla.com/~kgupta/aboutlogcat.xpi (works on Firefox 21 and up) to view the logcat.
Once you have alogcat installed, just use Fennec as you would normally. Upon encountering a bug or issue, start the aLogcat app (as soon as possible after seeing the Fennec issue) and select "Share" or "Save" from the menu to send it via email or save it to the SD card. The log can then be attached to a bug or sent to a developer. As with adb logcat, it is better to have a log with timestamps than without timestamps. To enable timestamps in the log, select "Preferences" from the aLogcat menu, and change the "Format?" option to "Time".
If you need to, you can search for some kinds of Fennec-related output by using the "Filter" menu item and entering "Gecko". However, when submitting logs for bug reports, please make sure you clear the filter and include all of the available log data.
JavaScript dump()
To use the dump() function in JavaScript to write to the log:
- Go to about:config and set browser.dom.window.dump.enabled to "true"
- Run the following ADB commands:
adb shell stop adb shell setprop log.redirect-stdio true adb shell start
Using JimDB
Using Debug Intent
Note: this is not useful with JimDB. If you want to use JimDB, just start it.
In order to attach before things get running, launch with:
adb shell am start -a org.mozilla.gecko.DEBUG -n org.mozilla.fennec_foobar/.App
(Replace foobar by your username)
and just click launch once gdb is attached. If you need to debug a crash that happens before XRE_Main is called, the patch on bug 572247 may be useful.
this script [2] will attach gdbserver for you
Getting dalvik java stack dumps using gdb
(gdb) call dvmDumpAllThreads(true)
this will dump a stack trace to logcat
Note: this will only work if you have symbols for dalvik.
Debugging with jdb
JimDB can now launch JDB integration
You can also use eclipse for debugging in a similar way by setting up for debugging "Remote Java application".
Debugging with eclipse
You need to find the PID of your fennec process. Forward it to a local TCP socket as in "Debugging with jdb."
In Eclipse switch to the debug perspective. Go to Run > Debug configurations... Remote Java Application. Change the port to the TCP port you specified in your adb command. Under Source, navigate to your checkout, then into mobile/android.
Eclipse looks for source code in a specific location. You need to create the directory hierarchy:
mobile /android /org mozilla/ gecko -> ../../base
That is, in mozilla-central/mobile/android, create org/mozilla, and put the symlink gecko pointing to mozilla-central/mobile/android/base.
You may also want to add more debugging information and can do that like this:
diff --git a/config/android-common.mk b/config/android-common.mk index 4591239..a47726a 100644 --- a/config/android-common.mk +++ b/config/android-common.mk @@ -70,6 +70,6 @@ JAVAC_FLAGS = \ -classpath $(JAVA_CLASSPATH) \ -bootclasspath $(JAVA_BOOTCLASSPATH) \ -encoding UTF8 \ - -g:source,lines \ + -g:source,lines,vars \ -Werror \ $(NULL)
Arguments and Environment Variables
If you need to set an environment variable at run time, append --es env# VAR=VAL to your activity manager command where # is the ordered number of variables for example:
adb shell am start -a android.activity.MAIN -n org.mozilla.fennec_$USER/.App --es env0 VAR=val --es env1 FOO=bar
If you need to pass arguments at run time, append --es args "<your-args>" to your activity manager command. For example, to launch with a specific profile:
adb shell am start -a android.activity.MAIN -n org.mozilla.fennec_$USER/.App --es args "--profile /mnt/sdcard/myprofile"
To launch with a specific URL, use the am -d option to set the intent's data URI:
adb shell am start -a android.activity.MAIN -n org.mozilla.fennec_$USER/.App -d 'http://www.mozilla.org'
PR Logging
You can use the env vars as described above to enable NSPR logging:
adb shell am start -a android.activity.MAIN -n org.mozilla.fennec_$USER/.App --es env0 NSPR_LOG_MODULES=all:5 --es env1 NSPR_LOG_FILE=/mnt/sdcard/log.txt
If no file is specified, logging is directed to the android logs:
adb shell am start -a android.activity.MAIN -n org.mozilla.fennec_$USER/.App --es env0 NSPR_LOG_MODULES=all:5
Look for lines marked "PRLog" in the adb logcat output.
Using legacy GDB (non-JimDB)
Reading back the framebuffer
If you need to verify what is in the back buffer at a particular time, you can cleverly call functions from gdb to allocate memory, read back, and write that to disk.
You need to know the size of your framebuffer a priori; in this case, it's 480x699.
You also need to know what the GL enum values are, because unless you compile with -ggdb, you don't have #defines available to you in the debugger. Very helpful information: GL_RGBA = 0x1908, GL_UNSIGNED_BYTE = 0x1401. The rest you can find in gfx/gl/GLDefs.h.
You should be able to call glReadPixels directly, but in my experience that causes Fennec to crash. However, if you have a GLContext* lying around, as you often do, you can work around that problem.
(gdb) set $m = (int*)malloc(480*699*4) (gdb) call aManager->mGLContext.mRawPtr->fReadPixels(0, 0, 480, 699, 0x1908, 0x1401, (void*)$m) (gdb) set $f = fopen("/sdcard/outputfile", "wb+") (gdb) call fwrite($m, 1, 480*699*4, $f) $7 = 1342080 (gdb) call fclose($f) $8 = 0
Now there is a file called /sdcard/outputfile that you can adb pull. But since it's just raw RGBA values, you need to be able to wrap that in PNG headers to display it. Jeff Muizelaar wrote a header called minpng.h that you can use to do so.
Get Jeff's minpng.h, and put it in a directory along with a driver c program:
#include "minpng.h" int main(int argc, char* argv[]) { FILE* f = fopen(argv[1], "rb"); int w = atoi(argv[2]); int h = atoi(argv[3]); char* d = (char*) malloc(w * h * 4); fread(d, w * h * 4, 1, f); fclose(f); write_png(argv[4], d, w, h); }
Compile and run:
$ gcc -o minpng minpng.c $ ./minpng outputfile 480 699 output.png
Using Rendertrace (Maple)
Rendertrace is a utility that will dump layer position and timing information (such as drawing, upload) to the console. This information can pasted into the rendertrace web front end to visualize the layer position and event timeline. This will let you understand where you're gecko is spending its time and why were checkerboarding.
To enable go in 'gfx/layers/RenderTrace.h' and uncomment '#define MOZ_RENDERTRACE'. Rebuild and run 'adb logcat | grep RENDERTRACE', paste the result in http://people.mozilla.org/~bgirard/rendertrace.html and hit 'reload'. For details talk to BenWa.
Using apitrace
Apitrace is a tool for tracing GL/EGL calls for debugging purposes. It basically uses an interim shared library called libapitrace that contains shadow gl* and egl* functions, which then get logged and then passed through to the real driver.
Use apitrace from https://github.com/apitrace/apitrace to build for desktop and android.
apt-get install libegl1-mesa-dev libgles1-mesa-dev libgles2-mesa-dev libqt4-dev cmake git clone https://github.com/gw280/apitrace.git cd apitrace # Build for Android cmake -DANDROID_NDK=/path/to/your/ndk -DCMAKE_TOOLCHAIN_FILE=android/android.toolchain.cmake -DANDROID_API_LEVEL=9 -Bbuild-android -H. make -C build-android -j8 # Build for desktop cmake -H. -Bbuild make -C build -j8 export EGL_SOFTWARE=true ./build/eglretrace -v /path/to/your/apitrace_log.trace
The Android build will create egltrace.so in build-android/wrappers, which you can then push to your device to /data/local:
adb push build-android/wrappers/egltrace.so /data/local/tmp
Restarting Fennec will cause it to load the apitrace library and the apitrace log will be saved to /data/data/org.mozilla.fennec_username/firefox.trace
You can then adb pull /data/data/org.mozilla.fennec_username/firefox.trace and analyse it on your desktop.
You can also use qapitrace as a GUI to inspect your trace files. (be sure to switch qapitrace to the EGL api using the options dialog)
These instructions provide a trace that does not include the Java GL code. To get traces including java code is more complicated. You need to use the patch from this bug https://bugzilla.mozilla.org/show_bug.cgi?id=749859 and this version of https://github.com/ideak/apitrace/tree/dev. Further, you'll need to build your own image/modify the current one to replace /init.rc. You also need to disable hardware acceleration of the UI (https://bug746703.bugzilla.mozilla.org/attachment.cgi?id=619009) Ask jrmuizel for more information if you want to do this.
about:memory
about:memory provides heaps (ha!) of useful memory information.
You can obtain a snapshot of memory info from a running Fennec instance using:
adb shell am broadcast -a org.mozilla.gecko.MEMORY_DUMP
This dumps a json file to the SD card and prints out the exact filename to logcat. You can pull the json file to desktop using
adb pull <absolute-path-to-file>
and view it in firefox's about:memory: use the "Read reports from a file" option at the bottom of the about:memory page.
Profiling
See https://wiki.mozilla.org/Mobile/Fennec/Android/Profiling.
Debugging Java code with DDMS
See http://developer.android.com/tools/debugging/ddms.html
Other useful tips and tricks
Addons
There are some addons which may be useful for development purposes. See Mobile/Fennec/Android/Development/Addons.
Tweaking UI prefs
By default, all of these prefs are set to "-1" in Fennec, meaning they take the values listed below, which are maintained in Axis.java.
Fractional values are specified in 1/1000th of a value; to specify a value of 0.3, write 300.
Note: You need to restart Fennec after changing these values.
Pref | Default value | Description | |
---|---|---|---|
ui.scrolling.friction_slow | 850 | This fraction in 1000ths of velocity remains after every animation frame when the velocity is low. | |
ui.scrolling.friction_fast | 970 | This fraction in 1000ths of velocity remains after every animation frame when the velocity is high. | |
ui.scrolling.velocity_threshold | 10 | Below this velocity (in pixels per frame), the friction changes from friction_fast to friction_slow. | |
ui.scrolling.max_event_acceleration | 12 | The maximum velocity change factor between events, per ms, in 1000ths. | |
ui.scrolling.overscroll_decel_rate | 40 | The rate of deceleration when the surface has overscrolled, in 1000ths. | |
ui.scrolling.overscroll_snap_limit | 300 | The fraction of the surface which can be overscrolled before it must snap back, in 1000ths. | |
ui.scrolling.min_scrollable_distance | 500 | The minimum amount of space that must be present for an axis to be considered scrollable, in 1/1000ths of pixels. | |
gfx.displayport.strategy | 1 | The strategy we use to determine how display ports are calculated. 0 = fixed margin, 1 = velocity bias, 2 = dynamic resolution, 3 = no margins | |
gfx.displayport.strategy_fm.multiplier | 1500 | When gfx.displayport.strategy = 0 (fixed margin), the 1000th of each dimension of the viewport the displayport is sized to. | |
gfx.displayport.strategy_fm.danger_x | 100 | When gfx.displayport.strategy = 0 (fixed margin), the 1000th of the width of the viewport the horizontal danger zone is set to.
| |
gfx.displayport.strategy_fm.danger_y | 200 | When gfx.displayport.strategy = 0 (fixed margin), the 1000th of the height of the viewport the vertical danger zone is set to. | |
gfx.displayport.strategy_vb.multiplier | 1500 | When gfx.displayport.strategy = 1 (velocity bias), the 1000th of each dimension of the viewport the displayport is sized to. | |
gfx.displayport.strategy_vb.threshold | 32 | When gfx.displayport.strategy = 1 (velocity bias), the threshold for velocity, in pixels/frame, when multiplied by the screen DPI. | |
gfx.displayport.strategy_vb.reverse_buffer | 200 | When gfx.displayport.strategy = 1 (velocity bias), the fraction of the buffer (in 1000ths) to be kept in the direction opposite the direction of the scroll. |
Refresh the JS cache
XUL and JavaScript files (like browser.js) are cached for fast startup. If you change one of these files in your development build, the new file might not be picked up unless you also touch toolkit/xre/nsAndroidStartup.cpp
and rebuild libxul. (See bug 695145 for details.)
killer script
#!/bin/sh if [ $# -ne 1 ] then echo "usage: $0 packagename.of.your.activity" echo "for example:" echo " $0 org.mozilla.fennec" exit fi p=`adb shell ps | grep $1 | awk '{print $2}'` if [ "$p" = "" ]; then echo "ERROR: That doesn't seem to be a running process. Please make sure your" echo "application has been started and that you are using the correct" echo "namespace argument." exit fi adb shell run-as $1 kill $p
.gdbinit
This is an example .gdbinit that uses the symbols from a locally built rom and automatically attaches to gdbserver. Note that putting a .gdbinit file inside a directory will make gdb load it thus you will not pollute your regular gdb init with those configurations.
set solib-search-path /home/blassey/android/system/out/target/product/passion/symbols/system/bin:/home/blassey/android/system/out/target/product/passion/symbols/system/lib/:/home/blassey/src/ndk5-m-c/objdir-droid-dbg/dist/bin set solib-absolute-prefix /home/blassey/android/system/out/target/product/passion/symbols/system/lib/ target remote localhost:12345
Developing Fennec with Eclipse
There is preliminary support for building Fennec using Eclipse in the tree (landed in bug 853045). Below is a brief recap of that data, but you can read more at nalexander's blog. There is also a series of video mini-tutorials.
You’ll need:
- Eclipse Kepler or above
- The Eclipse ADT plugin https://developer.android.com/tools/sdk/eclipse-adt.html
Build and package your tree:
mach build && mach package
Generate the Android Eclipse project files with
mach build-backend --backend=AndroidEclipse
In Eclipse, go to File > Import… and select General > Existing Projects into Workspace. Enter the path printed by mach build-backend
Using IntelliJ with Fennec
Getting IntelliJ hooked up to work with Fennec isn't too hard. A detailed blog post on it is at:
Rooting Android devices
Sign a Fennec build
Nightly builds are available unsigned, so that you can sign them with your local debug key and install them on top of your own debug builds (without uninstalling and losing your profile). To sign and install the unsigned nightly build:
wget http://ftp.mozilla.org/pub/mozilla.org/mobile/nightly/latest-mozilla-central-android-r7/gecko-unsigned-unaligned.apk jarsigner -sigalg SHA1withRSA -digestalg SHA1 -keystore ~/.android/debug.keystore -storepass android -keypass android gecko-unsigned-unaligned.apk androiddebugkey zipalign -f -v 4 gecko-unsigned-unaligned.apk gecko-signed-aligned.apk adb install -r gecko-signed-aligned.apk
Or you can also re-sign a signed build. If "fennec.apk" is signed already, this will remove the signature and replace it with your own. The result will be saved as "fennec-resigned.apk":
zip fennec.apk -d META-INF/* jarsigner -sigalg SHA1withRSA -digestalg SHA1 -keystore ~/.android/debug.keystore -storepass android -keypass android fennec.apk androiddebugkey zipalign -f -v 4 fennec.apk fennec-resigned.apk
If you get this error when you try to sign a package:
jarsigner: unable to sign jar: java.util.zip.ZipException: invalid entry compressed size (expected 16716 but got 16964 bytes)
You should to follow some steps to complete your task:
* rename the .apk to .zip * unzip the file in some folder * remove the METAINF folder * zip the remaining files * change the name .zip to .apk * sign again
To verify if everything is alright use the command
jarsigner -verbose -verify