--- a/config/recurse.mk
+++ b/config/recurse.mk
@@ -166,20 +166,16 @@ js/xpconnect/src/export: dom/bindings/ex
accessible/xpcom/export: xpcom/xpidl/export
# The widget binding generator code is part of the annotationProcessors.
widget/android/bindings/export: build/annotationProcessors/export
# .xpt generation needs the xpidl lex/yacc files
xpcom/xpidl/export: xpcom/idl-parser/xpidl/export
-# The roboextender addon includes a classes.dex containing a test Java addon.
-# The test addon must be built first.
-mobile/android/tests/browser/robocop/roboextender/tools: mobile/android/tests/javaaddons/tools
-
ifdef ENABLE_CLANG_PLUGIN
$(filter-out config/host build/unix/stdc++compat/% build/clang-plugin/%,$(compile_targets)): build/clang-plugin/target build/clang-plugin/tests/target
build/clang-plugin/tests/target: build/clang-plugin/target
endif
# Interdependencies that moz.build world don't know about yet for compilation.
# Note some others are hardcoded or "guessed" in recursivemake.py and emitter.py
ifeq ($(MOZ_WIDGET_TOOLKIT),gtk3)
--- a/mobile/android/app/build.gradle
+++ b/mobile/android/app/build.gradle
@@ -101,17 +101,16 @@ android {
aidl {
srcDir "${topsrcdir}/mobile/android/base/aidl"
}
java {
srcDir "${topsrcdir}/mobile/android/base/java"
srcDir "${topsrcdir}/mobile/android/search/java"
- srcDir "${topsrcdir}/mobile/android/javaaddons/java"
srcDir "${topsrcdir}/mobile/android/services/src/main/java"
if (mozconfig.substs.MOZ_ANDROID_MLS_STUMBLER) {
srcDir "${topsrcdir}/mobile/android/stumbler/java"
}
if (!mozconfig.substs.MOZ_CRASHREPORTER) {
exclude 'org/mozilla/gecko/CrashReporter.java'
--- a/mobile/android/base/Makefile.in
+++ b/mobile/android/base/Makefile.in
@@ -141,17 +141,16 @@ GECKOVIEW_JARS += gecko-thirdparty-adjus
endif
geckoview_jars_classpath := $(subst $(NULL) ,:,$(strip $(GECKOVIEW_JARS)))
FENNEC_JARS = \
gecko-browser.jar \
gecko-thirdparty.jar \
services.jar \
- ../javaaddons/javaaddons-1.0.jar \
$(NULL)
ifdef MOZ_WEBRTC
FENNEC_JARS += webrtc.jar
endif
ifdef MOZ_ANDROID_SEARCH_ACTIVITY
FENNEC_JARS += search-activity.jar
--- a/mobile/android/base/java/org/mozilla/gecko/BrowserApp.java
+++ b/mobile/android/base/java/org/mozilla/gecko/BrowserApp.java
@@ -53,17 +53,16 @@ import org.mozilla.gecko.home.HomeConfig
import org.mozilla.gecko.home.HomeConfigPrefsBackend;
import org.mozilla.gecko.home.HomeFragment;
import org.mozilla.gecko.home.HomePager.OnUrlOpenInBackgroundListener;
import org.mozilla.gecko.home.HomePager.OnUrlOpenListener;
import org.mozilla.gecko.home.HomePanelsManager;
import org.mozilla.gecko.home.HomeScreen;
import org.mozilla.gecko.home.SearchEngine;
import org.mozilla.gecko.icons.Icons;
-import org.mozilla.gecko.javaaddons.JavaAddonManager;
import org.mozilla.gecko.media.VideoPlayer;
import org.mozilla.gecko.menu.GeckoMenu;
import org.mozilla.gecko.menu.GeckoMenuItem;
import org.mozilla.gecko.mozglue.GeckoLoader;
import org.mozilla.gecko.mozglue.SafeIntent;
import org.mozilla.gecko.notifications.NotificationHelper;
import org.mozilla.gecko.overlays.ui.ShareDialog;
import org.mozilla.gecko.permissions.Permissions;
@@ -802,17 +801,16 @@ public class BrowserApp extends GeckoApp
mSearchEngineManager = new SearchEngineManager(this, distribution);
// Init suggested sites engine in BrowserDB.
final SuggestedSites suggestedSites = new SuggestedSites(appContext, distribution);
final BrowserDB db = BrowserDB.from(profile);
db.setSuggestedSites(suggestedSites);
- JavaAddonManager.getInstance().init(appContext);
mSharedPreferencesHelper = new SharedPreferencesHelper(appContext);
mReadingListHelper = new ReadingListHelper(appContext, profile);
mAccountsHelper = new AccountsHelper(appContext, profile);
if (AppConstants.MOZ_ANDROID_BEAM) {
NfcAdapter nfc = NfcAdapter.getDefaultAdapter(this);
if (nfc != null) {
nfc.setNdefPushMessageCallback(new NfcAdapter.CreateNdefMessageCallback() {
deleted file mode 100644
--- a/mobile/android/base/java/org/mozilla/gecko/javaaddons/JavaAddonManager.java
+++ /dev/null
@@ -1,170 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.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 org.mozilla.gecko.javaaddons;
-
-import android.content.Context;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.Message;
-import android.util.Log;
-import dalvik.system.DexClassLoader;
-import org.json.JSONException;
-import org.json.JSONObject;
-import org.mozilla.gecko.EventDispatcher;
-import org.mozilla.gecko.util.BundleEventListener;
-import org.mozilla.gecko.util.EventCallback;
-import org.mozilla.gecko.util.GeckoBundle;
-
-import java.io.File;
-import java.lang.reflect.Constructor;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.Map;
-
-/**
- * The manager for addon-provided Java code.
- *
- * Java code in addons can be loaded using the Dex:Load message, and unloaded
- * via the Dex:Unload message. Addon classes loaded are checked for a constructor
- * that takes a Map<String, Handler.Callback>. If such a constructor
- * exists, it is called and the objects populated into the map by the constructor
- * are registered as event listeners. If no such constructor exists, the default
- * constructor is invoked instead.
- *
- * Note: The Map and Handler.Callback classes were used in this API definition
- * rather than defining a custom class. This was done explicitly so that the
- * addon code can be compiled against the android.jar provided in the Android
- * SDK, rather than having to be compiled against Fennec source code.
- *
- * The Handler.Callback instances provided (as described above) are invoked with
- * Message objects when the corresponding events are dispatched. The Bundle
- * object attached to the Message will contain the "primitive" values from the
- * JSON of the event. ("primitive" includes bool/int/long/double/String). If
- * the addon callback wishes to synchronously return a value back to the event
- * dispatcher, they can do so by inserting the response string into the bundle
- * under the key "response".
- */
-public class JavaAddonManager implements BundleEventListener {
- private static final String LOGTAG = "GeckoJavaAddonManager";
-
- private static JavaAddonManager sInstance;
-
- private final EventDispatcher mDispatcher;
- private final Map<String, Map<String, BundleEventListener>> mAddonCallbacks;
-
- private Context mApplicationContext;
-
- public static JavaAddonManager getInstance() {
- if (sInstance == null) {
- sInstance = new JavaAddonManager();
- }
- return sInstance;
- }
-
- private JavaAddonManager() {
- mDispatcher = EventDispatcher.getInstance();
- mAddonCallbacks = new HashMap<>();
- }
-
- public void init(Context applicationContext) {
- if (mApplicationContext != null) {
- // we've already done this registration. don't do it again
- return;
- }
- mApplicationContext = applicationContext;
- mDispatcher.registerGeckoThreadListener(this,
- "Dex:Load",
- "Dex:Unload");
- JavaAddonManagerV1.getInstance().init(applicationContext);
- }
-
- @Override // BundleEventListener
- public void handleMessage(final String event, final GeckoBundle message,
- final EventCallback callback) {
- if ("Dex:Load".equals(event)) {
- final String zipFile = message.getString("zipfile");
- final String implClass = message.getString("impl");
- Log.d(LOGTAG, "Attempting to load classes.dex file from " + zipFile +
- " and instantiate " + implClass);
- try {
- final File tmpDir = mApplicationContext.getDir("dex", 0);
- final DexClassLoader loader = new DexClassLoader(
- zipFile, tmpDir.getAbsolutePath(),
- null, mApplicationContext.getClassLoader());
- final Class<?> c = loader.loadClass(implClass);
- try {
- final Constructor<?> constructor = c.getDeclaredConstructor(Map.class);
- final Map<String, Handler.Callback> callbacks =
- new HashMap<String, Handler.Callback>();
- constructor.newInstance(callbacks);
- registerCallbacks(zipFile, callbacks);
- } catch (final NoSuchMethodException nsme) {
- Log.d(LOGTAG, "Did not find constructor with parameters " +
- "Map<String, Handler.Callback>. Falling back " +
- "to default constructor...");
- // fallback for instances with no constructor that takes a Map<String,
- // Handler.Callback>
- c.newInstance();
- }
- } catch (final Exception e) {
- Log.e(LOGTAG, "Unable to load dex successfully", e);
- }
-
- } else if ("Dex:Unload".equals(event)) {
- final String zipFile = message.getString("zipfile");
- unregisterCallbacks(zipFile);
- }
- }
-
- private void registerCallbacks(String zipFile, Map<String, Handler.Callback> callbacks) {
- Map<String, BundleEventListener> addonCallbacks = mAddonCallbacks.get(zipFile);
- if (addonCallbacks != null) {
- Log.w(LOGTAG, "Found pre-existing callbacks for zipfile [" + zipFile + "]; aborting re-registration!");
- return;
- }
- addonCallbacks = new HashMap<>();
- for (String event : callbacks.keySet()) {
- CallbackWrapper wrapper = new CallbackWrapper(callbacks.get(event));
- mDispatcher.registerGeckoThreadListener(wrapper, event);
- addonCallbacks.put(event, wrapper);
- }
- mAddonCallbacks.put(zipFile, addonCallbacks);
- }
-
- private void unregisterCallbacks(String zipFile) {
- Map<String, BundleEventListener> callbacks = mAddonCallbacks.remove(zipFile);
- if (callbacks == null) {
- Log.w(LOGTAG, "Attempting to unregister callbacks from zipfile [" + zipFile +
- "] which has no callbacks registered.");
- return;
- }
- for (String event : callbacks.keySet()) {
- mDispatcher.unregisterGeckoThreadListener(callbacks.get(event), event);
- }
- }
-
- private static class CallbackWrapper implements BundleEventListener {
- private final Handler.Callback mDelegate;
-
- CallbackWrapper(Handler.Callback delegate) {
- mDelegate = delegate;
- }
-
- @Override // BundleEventListener
- public void handleMessage(final String event, final GeckoBundle message,
- final EventCallback callback) {
- final Message msg = new Message();
- final Bundle data = message.toBundle();
- data.putString("type", event);
- msg.setData(data);
- mDelegate.handleMessage(msg);
-
- final GeckoBundle response = new GeckoBundle(1);
- response.putString("response", data.getString("response"));
- callback.sendSuccess(response);
- }
- }
-}
deleted file mode 100644
--- a/mobile/android/base/java/org/mozilla/gecko/javaaddons/JavaAddonManagerV1.java
+++ /dev/null
@@ -1,308 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.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 org.mozilla.gecko.javaaddons;
-
-import android.content.Context;
-import android.util.Log;
-import android.util.Pair;
-import dalvik.system.DexClassLoader;
-import org.json.JSONException;
-import org.json.JSONObject;
-import org.mozilla.gecko.EventDispatcher;
-import org.mozilla.gecko.GeckoAppShell;
-import org.mozilla.gecko.sync.Utils;
-import org.mozilla.gecko.util.BundleEventListener;
-import org.mozilla.gecko.util.EventCallback;
-import org.mozilla.gecko.util.GeckoBundle;
-import org.mozilla.gecko.util.GeckoJarReader;
-import org.mozilla.javaaddons.JavaAddonInterfaceV1;
-
-import java.io.File;
-import java.io.IOException;
-import java.lang.reflect.Constructor;
-import java.lang.reflect.InvocationTargetException;
-import java.util.HashMap;
-import java.util.IdentityHashMap;
-import java.util.Map;
-
-public class JavaAddonManagerV1 implements BundleEventListener {
- private static final String LOGTAG = "GeckoJavaAddonMgrV1";
- public static final String MESSAGE_LOAD = "JavaAddonManagerV1:Load";
- public static final String MESSAGE_UNLOAD = "JavaAddonManagerV1:Unload";
-
- private static JavaAddonManagerV1 sInstance;
-
- // Protected by static synchronized.
- private Context mApplicationContext;
-
- private final org.mozilla.gecko.EventDispatcher mDispatcher;
-
- // Protected by synchronized (this).
- private final Map<String, EventDispatcherImpl> mGUIDToDispatcherMap = new HashMap<>();
-
- public static synchronized JavaAddonManagerV1 getInstance() {
- if (sInstance == null) {
- sInstance = new JavaAddonManagerV1();
- }
- return sInstance;
- }
-
- private JavaAddonManagerV1() {
- mDispatcher = org.mozilla.gecko.EventDispatcher.getInstance();
- }
-
- public synchronized void init(Context applicationContext) {
- if (mApplicationContext != null) {
- // We've already registered; don't register again.
- return;
- }
- mApplicationContext = applicationContext;
- mDispatcher.registerGeckoThreadListener(this,
- MESSAGE_LOAD,
- MESSAGE_UNLOAD);
- }
-
- protected String getExtension(String filename) {
- if (filename == null) {
- return "";
- }
- final int last = filename.lastIndexOf(".");
- if (last < 0) {
- return "";
- }
- return filename.substring(last);
- }
-
- protected synchronized EventDispatcherImpl registerNewInstance(String classname, String filename)
- throws ClassNotFoundException, InstantiationException, IllegalAccessException, InvocationTargetException, NoSuchMethodException, IOException {
- Log.d(LOGTAG, "Attempting to instantiate " + classname + " from filename " + filename);
-
- // It's important to maintain the extension, either .dex, .apk, .jar.
- final String extension = getExtension(filename);
- final File dexFile = GeckoJarReader.extractStream(mApplicationContext, filename, mApplicationContext.getCacheDir(), "." + extension);
- try {
- if (dexFile == null) {
- throw new IOException("Could not find file " + filename);
- }
- final File tmpDir = mApplicationContext.getDir("dex", 0); // We'd prefer getCodeCacheDir but it's API 21+.
- final DexClassLoader loader = new DexClassLoader(dexFile.getAbsolutePath(), tmpDir.getAbsolutePath(), null, mApplicationContext.getClassLoader());
- final Class<?> c = loader.loadClass(classname);
- final Constructor<?> constructor = c.getDeclaredConstructor(Context.class, JavaAddonInterfaceV1.EventDispatcher.class);
- final String guid = Utils.generateGuid();
- final EventDispatcherImpl dispatcher = new EventDispatcherImpl(guid, filename);
- final Object instance = constructor.newInstance(mApplicationContext, dispatcher);
- mGUIDToDispatcherMap.put(guid, dispatcher);
- return dispatcher;
- } finally {
- // DexClassLoader writes an optimized version, so we can get rid of our temporary extracted version.
- if (dexFile != null) {
- dexFile.delete();
- }
- }
- }
-
- @Override // BundleEventListener
- public synchronized void handleMessage(final String event, final GeckoBundle message,
- final EventCallback callback) {
- switch (event) {
- case MESSAGE_LOAD: {
- if (callback == null) {
- throw new IllegalArgumentException("callback must not be null");
- }
- final String classname = message.getString("classname");
- final String filename = message.getString("filename");
- final EventDispatcherImpl dispatcher;
- try {
- dispatcher = registerNewInstance(classname, filename);
- callback.sendSuccess(dispatcher.guid);
- } catch (final Exception e) {
- Log.e(LOGTAG, "Unable to load dex successfully", e);
- callback.sendError(e.toString());
- }
- }
- break;
-
- case MESSAGE_UNLOAD: {
- if (callback == null) {
- throw new IllegalArgumentException("callback must not be null");
- }
- final String guid = message.getString("guid");
- final EventDispatcherImpl dispatcher = mGUIDToDispatcherMap.remove(guid);
- if (dispatcher == null) {
- Log.w(LOGTAG, "Attempting to unload addon with unknown " +
- "associated dispatcher; ignoring.");
- callback.sendSuccess(false);
- } else {
- dispatcher.unregisterAllEventListeners();
- callback.sendSuccess(true);
- }
- }
- break;
- }
- }
-
- /**
- * An event dispatcher is tied to a single Java Addon instance. It serves to prefix all
- * messages with its unique GUID.
- * <p/>
- * Curiously, the dispatcher does not hold a direct reference to its add-on instance. It will
- * likely hold indirect instances through its wrapping map, since the instance will probably
- * register event listeners that hold a reference to itself. When these listeners are
- * unregistered, any link will be broken, allowing the instances to be garbage collected.
- */
- private class EventDispatcherImpl implements JavaAddonInterfaceV1.EventDispatcher {
- private final String guid;
- private final String dexFileName;
-
- // Protected by synchronized (this).
- private final Map<JavaAddonInterfaceV1.EventListener, Pair<BundleEventListener, String[]>>
- mListenerToWrapperMap = new IdentityHashMap<>();
-
- public EventDispatcherImpl(String guid, String dexFileName) {
- this.guid = guid;
- this.dexFileName = dexFileName;
- }
-
- protected class ListenerWrapper implements BundleEventListener {
- private final JavaAddonInterfaceV1.EventListener listener;
-
- public ListenerWrapper(JavaAddonInterfaceV1.EventListener listener) {
- this.listener = listener;
- }
-
- @Override // BundleEventListener
- public void handleMessage(final String prefixedEvent, final GeckoBundle message,
- final EventCallback callback) {
- if (!prefixedEvent.startsWith(guid + ":")) {
- return;
- }
- final String event = prefixedEvent.substring(guid.length() + 1); // Skip "guid:".
- final JavaAddonInterfaceV1.EventCallback callbackAdapter;
- if (callback == null) {
- callbackAdapter = null;
- } else {
- callbackAdapter = new JavaAddonInterfaceV1.EventCallback() {
- private void invokeCallback(final boolean success, final Object response) {
- final GeckoBundle bundle;
- try {
- final JSONObject wrapper = new JSONObject();
- wrapper.put("response", response);
- bundle = GeckoBundle.fromJSONObject(wrapper);
- } catch (final JSONException e) {
- Log.e(LOGTAG, "Unsupported response for message [" + event + "]", e);
- return;
- }
- if (success) {
- callback.sendSuccess(bundle);
- } else {
- callback.sendError(bundle);
- }
- }
-
- @Override
- public void sendSuccess(Object response) {
- invokeCallback(/* success */ true, response);
- }
-
- @Override
- public void sendError(Object response) {
- invokeCallback(/* success */ false, response);
- }
- };
- }
- final JSONObject json;
- try {
- json = message.toJSONObject();
- } catch (final JSONException e) {
- Log.e(LOGTAG, "Exception handling message [" + prefixedEvent + "]", e);
- return;
- }
- listener.handleMessage(mApplicationContext, event, json, callbackAdapter);
- }
- }
-
- @Override
- public synchronized void registerEventListener(
- final JavaAddonInterfaceV1.EventListener listener, String... events) {
- if (mListenerToWrapperMap.containsKey(listener)) {
- Log.e(LOGTAG, "Attempting to register listener which is already registered; ignoring.");
- return;
- }
-
- final BundleEventListener listenerWrapper = new ListenerWrapper(listener);
-
- final String[] prefixedEvents = new String[events.length];
- for (int i = 0; i < events.length; i++) {
- prefixedEvents[i] = this.guid + ":" + events[i];
- }
- mDispatcher.registerGeckoThreadListener(listenerWrapper, prefixedEvents);
- mListenerToWrapperMap.put(listener, new Pair<>(listenerWrapper, prefixedEvents));
- }
-
- @Override
- public synchronized void unregisterEventListener(
- final JavaAddonInterfaceV1.EventListener listener) {
- final Pair<BundleEventListener, String[]> pair = mListenerToWrapperMap.remove(listener);
- if (pair == null) {
- Log.e(LOGTAG, "Attempting to unregister listener which is not registered; ignoring.");
- return;
- }
- mDispatcher.unregisterGeckoThreadListener(pair.first, pair.second);
- }
-
- protected synchronized void unregisterAllEventListeners() {
- // Unregister everything, then forget everything.
- for (Pair<BundleEventListener, String[]> pair : mListenerToWrapperMap.values()) {
- mDispatcher.unregisterGeckoThreadListener(pair.first, pair.second);
- }
- mListenerToWrapperMap.clear();
- }
-
- @Override
- public void sendRequestToGecko(final String event, final JSONObject message,
- final JavaAddonInterfaceV1.RequestCallback callback) {
- final String prefixedEvent = guid + ":" + event;
- final GeckoBundle data;
- try {
- data = GeckoBundle.fromJSONObject(message);
- } catch (final JSONException e) {
- Log.e(LOGTAG, "Cannot convert message", e);
- return;
- }
-
- final EventCallback cb;
- if (callback == null) {
- cb = null;
- } else {
- cb = new EventCallback() {
- @Override
- public void sendSuccess(final Object response) {
- if (!(response instanceof GeckoBundle)) {
- Log.e(LOGTAG, "Response to request [" + event + "] must be an object");
- return;
- }
- try {
- final JSONObject json = ((GeckoBundle) response).toJSONObject();
- callback.onResponse(GeckoAppShell.getApplicationContext(), json);
- } catch (final Exception e) {
- // No way to report failure.
- Log.e(LOGTAG, "Exception handling response to request [" +
- event + "]", e);
- }
- }
-
- @Override
- public void sendError(final Object response) {
- Log.e(LOGTAG, "Exception handling response to request [" +
- event + "]: " + response);
- }
- };
- }
-
- mDispatcher.dispatch(prefixedEvent, data, cb);
- }
- }
-}
--- a/mobile/android/base/moz.build
+++ b/mobile/android/base/moz.build
@@ -41,19 +41,16 @@ with Files('java/org/mozilla/gecko/first
BUG_COMPONENT = ('Firefox for Android', 'First Run')
with Files('java/org/mozilla/gecko/home/**'):
BUG_COMPONENT = ('Firefox for Android', 'Awesomescreen')
with Files('java/org/mozilla/gecko/icons/**'):
BUG_COMPONENT = ('Firefox for Android', 'Favicon Handling')
-with Files('java/org/mozilla/gecko/javaaddons/**'):
- BUG_COMPONENT = ('Firefox for Android', 'General')
-
with Files('java/org/mozilla/gecko/mdns/**'):
BUG_COMPONENT = ('Firefox for Android', 'General')
with Files('java/org/mozilla/gecko/media/**'):
BUG_COMPONENT = ('Firefox for Android', 'Audio/Video')
with Files('java/org/mozilla/gecko/mdns/**'):
BUG_COMPONENT = ('Firefox for Android', 'Settings and Preferences')
@@ -719,18 +716,16 @@ gbjar.sources += ['java/org/mozilla/geck
'icons/processing/DiskProcessor.java',
'icons/processing/MemoryProcessor.java',
'icons/processing/Processor.java',
'icons/processing/ResizingProcessor.java',
'icons/storage/DiskStorage.java',
'icons/storage/FailureCache.java',
'icons/storage/MemoryStorage.java',
'IntentHelper.java',
- 'javaaddons/JavaAddonManager.java',
- 'javaaddons/JavaAddonManagerV1.java',
'LauncherActivity.java',
'lwt/LightweightTheme.java',
'lwt/LightweightThemeDrawable.java',
'mdns/MulticastDNSManager.java',
'media/AudioFocusAgent.java',
'media/MediaControlService.java',
'media/VideoPlayer.java',
'MediaCastingBar.java',
@@ -1005,17 +1000,16 @@ if max_sdk_version >= 11:
'tabs/TabStripAdapter.java',
'tabs/TabStripDividerItem.java',
'tabs/TabStripItemAnimator.java',
'tabs/TabStripItemView.java',
'tabs/TabStripView.java'
]]
gbjar.extra_jars += [
- OBJDIR + '/../javaaddons/javaaddons-1.0.jar',
'gecko-R.jar',
'gecko-mozglue.jar',
'gecko-thirdparty.jar',
'gecko-util.jar',
'gecko-view.jar',
'sync-thirdparty.jar',
'services.jar',
]
--- a/mobile/android/config/proguard/proguard.cfg
+++ b/mobile/android/config/proguard/proguard.cfg
@@ -148,25 +148,16 @@
@org.mozilla.gecko.annotation.RobocopTarget <methods>;
}
-keepclasseswithmembers class * {
@org.mozilla.gecko.annotation.RobocopTarget <fields>;
}
-keep class **.R$*
-# Keep all interfaces that might be dynamically required by Java Addons.
--keep class org.mozilla.javaaddons.* {
- *;
-}
-
--keep class org.mozilla.javaaddons.*$* {
- *;
-}
-
# Disable obfuscation because it makes exception stack traces more difficult to read.
-dontobfuscate
# Suppress warnings about missing descriptor classes.
#-dontnote **,!ch.boye.**,!org.mozilla.gecko.sync.**
-include "play-services-keeps.cfg"
deleted file mode 100644
--- a/mobile/android/javaaddons/Makefile.in
+++ /dev/null
@@ -1,9 +0,0 @@
-# This Source Code Form is subject to the terms of the Mozilla Public
-# License, v. 2.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)/config/android-common.mk
-
-libs:: javaaddons-1.0.jar
deleted file mode 100644
--- a/mobile/android/javaaddons/java/org/mozilla/javaaddons/JavaAddonInterfaceV1.java
+++ /dev/null
@@ -1,51 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.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 org.mozilla.javaaddons;
-
-import android.content.Context;
-import org.json.JSONObject;
-
-public interface JavaAddonInterfaceV1 {
- /**
- * Callback interface for Gecko requests.
- * <p/>
- * For each instance of EventCallback, exactly one of sendResponse, sendError, must be called to prevent observer leaks.
- * If more than one send* method is called, or if a single send method is called multiple times, an
- * {@link IllegalStateException} will be thrown.
- */
- interface EventCallback {
- /**
- * Sends a success response with the given data.
- *
- * @param response The response data to send to Gecko. Can be any of the types accepted by
- * JSONObject#put(String, Object).
- */
- public void sendSuccess(Object response);
-
- /**
- * Sends an error response with the given data.
- *
- * @param response The response data to send to Gecko. Can be any of the types accepted by
- * JSONObject#put(String, Object).
- */
- public void sendError(Object response);
- }
-
- interface EventDispatcher {
- void registerEventListener(EventListener listener, String... events);
- void unregisterEventListener(EventListener listener);
-
- void sendRequestToGecko(String event, JSONObject message, RequestCallback callback);
- }
-
- interface EventListener {
- public void handleMessage(final Context context, final String event, final JSONObject message, final EventCallback callback);
- }
-
- interface RequestCallback {
- void onResponse(final Context context, JSONObject jsonObject);
- }
-}
deleted file mode 100644
--- a/mobile/android/javaaddons/moz.build
+++ /dev/null
@@ -1,14 +0,0 @@
-# -*- 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/.
-
-with Files('**'):
- BUG_COMPONENT = ('Firefox for Android', 'General')
-
-jar = add_java_jar('javaaddons-1.0')
-jar.sources = [
- 'java/org/mozilla/javaaddons/JavaAddonInterfaceV1.java',
-]
-jar.javac_flags += ['-Xlint:all']
deleted file mode 100644
--- a/mobile/android/modules/JavaAddonManager.jsm
+++ /dev/null
@@ -1,128 +0,0 @@
-// -*- Mode: js2; tab-width: 2; indent-tabs-mode: nil; js2-basic-offset: 2; js2-skip-preprocessor-directives: t; -*-
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.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 = ["JavaAddonManager"];
-
-const { classes: Cc, interfaces: Ci, utils: Cu } = Components; /*global Components */
-
-Cu.import("resource://gre/modules/Messaging.jsm"); /*global Messaging */
-Cu.import("resource://gre/modules/Services.jsm"); /*global Services */
-
-function resolveGeckoURI(uri) {
- if (!uri) {
- throw new Error("Can't resolve an empty uri");
- }
- if (uri.startsWith("chrome://")) {
- let registry = Cc['@mozilla.org/chrome/chrome-registry;1'].getService(Ci["nsIChromeRegistry"]);
- return registry.convertChromeURL(Services.io.newURI(uri)).spec;
- } else if (uri.startsWith("resource://")) {
- let handler = Services.io.getProtocolHandler("resource").QueryInterface(Ci.nsIResProtocolHandler);
- return handler.resolveURI(Services.io.newURI(uri));
- }
- return uri;
-}
-
-/**
- * A promise-based API
- */
-var JavaAddonManager = Object.freeze({
- classInstanceFromFile: function(classname, filename) {
- if (!classname) {
- throw new Error("classname cannot be null");
- }
- if (!filename) {
- throw new Error("filename cannot be null");
- }
- return EventDispatcher.instance.sendRequestForResult({
- type: "JavaAddonManagerV1:Load",
- classname: classname,
- filename: resolveGeckoURI(filename)
- })
- .then((guid) => {
- if (!guid) {
- throw new Error("Internal error: guid should not be null");
- }
- return new JavaAddonV1({classname: classname, guid: guid});
- });
- }
-});
-
-function JavaAddonV1(options = {}) {
- if (!(this instanceof JavaAddonV1)) {
- return new JavaAddonV1(options);
- }
- if (!options.classname) {
- throw new Error("options.classname cannot be null");
- }
- if (!options.guid) {
- throw new Error("options.guid cannot be null");
- }
- this._classname = options.classname;
- this._guid = options.guid;
- this._loaded = true;
-
- this._listeners = {
- list: {},
- onEvent: function(event, data, callback) {
- let listener = this.list[event];
- if (listener) {
- let ret = listener(data);
- callback && callback.onSuccess(ret);
- } else {
- callback && callback.onError("No listener");
- }
- },
- };
-}
-
-JavaAddonV1.prototype = Object.freeze({
- unload: function() {
- if (!this._loaded) {
- return;
- }
-
- EventDispatcher.instance.sendRequestForResult({
- type: "JavaAddonManagerV1:Unload",
- guid: this._guid
- })
- .then(() => {
- this._loaded = false;
- for (let event in this._listeners.list) {
- // If we use this.removeListener, we prefix twice.
- EventDispatcher.instance.unregisterListener(this._listeners, event);
- }
- this._listeners.list = {};
- });
- },
-
- _prefix: function(message) {
- let newMessage = Cu.cloneInto(message, {}, { cloneFunctions: false });
- newMessage.type = this._guid + ":" + message.type;
- return newMessage;
- },
-
- sendRequest: function(message) {
- return EventDispatcher.instance.sendRequest(this._prefix(message));
- },
-
- sendRequestForResult: function(message) {
- return EventDispatcher.instance.sendRequestForResult(this._prefix(message)).then(
- wrapper => wrapper.response, wrapper => { throw wrapper && wrapper.response; });
- },
-
- addListener: function(listener, message) {
- let prefixedMessage = this._guid + ":" + message;
- this._listeners.list[prefixedMessage] = listener;
- return EventDispatcher.instance.registerListener(this._listeners, prefixedMessage);
- },
-
- removeListener: function(message) {
- let prefixedMessage = this._guid + ":" + message;
- delete this._listeners.list[prefixedMessage];
- return EventDispatcher.instance.unregisterListener(this._listeners, prefixedMessage);
- }
-});
--- a/mobile/android/modules/moz.build
+++ b/mobile/android/modules/moz.build
@@ -23,17 +23,16 @@ EXTRA_JS_MODULES += [
'Accounts.jsm',
'dbg-browser-actors.js',
'DelayedInit.jsm',
'DownloadNotifications.jsm',
'FxAccountsWebChannel.jsm',
'HelperApps.jsm',
'Home.jsm',
'HomeProvider.jsm',
- 'JavaAddonManager.jsm',
'JNI.jsm',
'LightweightThemeConsumer.jsm',
'MediaPlayerApp.jsm',
'NetErrorHelper.jsm',
'Notifications.jsm',
'PageActions.jsm',
'Prompt.jsm',
'RuntimePermissions.jsm',
--- a/mobile/android/moz.build
+++ b/mobile/android/moz.build
@@ -55,17 +55,16 @@ DIRS += [
'../locales',
'locales',
]
if CONFIG['MOZ_ANDROID_MLS_STUMBLER']:
DIRS += ['stumbler']
DIRS += [
- 'javaaddons', # Must be built before base.
'base',
'chrome',
'components',
'extensions',
'modules',
'themes/core',
'app',
'fonts',
--- a/mobile/android/tests/browser/chrome/chrome.ini
+++ b/mobile/android/tests/browser/chrome/chrome.ini
@@ -27,17 +27,16 @@ support-files =
skip-if = debug
[test_debugger_server.html]
[test_desktop_useragent.html]
[test_device_search_engine.html]
[test_get_last_visited.html]
[test_home_provider.html]
[test_hidden_select_option.html]
[test_identity_mode.html]
-[test_java_addons.html]
[test_jni.html]
[test_migrate_ui.html]
[test_network_manager.html]
[test_offline_page.html]
skip-if = true # Bug 1241478
[test_reader_view.html]
[test_resource_substitutions.html]
[test_restricted_profiles.html]
deleted file mode 100644
--- a/mobile/android/tests/browser/chrome/test_java_addons.html
+++ /dev/null
@@ -1,119 +0,0 @@
-<!DOCTYPE HTML>
-<html>
-<!--
-https://bugzilla.mozilla.org/show_bug.cgi?id=1168407
-Migrated from Robocop https://bugzilla.mozilla.org/show_bug.cgi?id=1184186
--->
-<head>
- <meta charset="utf-8">
- <title>Test for Bug 1168407</title>
- <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
- <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SpawnTask.js"></script>
- <link rel="stylesheet" type="text/css" href="chrome://global/skin"/>
- <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/>
- <script type="application/javascript">
-
- const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
-
- Cu.import("resource://gre/modules/JavaAddonManager.jsm"); /*global JavaAddonManager */
- Cu.import("resource://gre/modules/Promise.jsm"); /*global Promise */
- Cu.import("resource://gre/modules/Task.jsm"); /*global Task */
-
- const DEX_FILE = "chrome://roboextender/content/javaaddons-test.apk";
- const CLASS = "org.mozilla.javaaddons.test.JavaAddonV1";
-
- const MESSAGE = "JavaAddon:V1";
-
- add_task(function* testFailureCases() {
- info("Loading Java Addon from non-existent class.");
- let gotError1 = yield JavaAddonManager.classInstanceFromFile(CLASS + "GARBAGE", DEX_FILE)
- .then((result) => false)
- .catch((error) => true);
- is(gotError1, true, "got expected error for non-existent class");
-
- info("Loading Java Addon from non-existent DEX file.");
- let gotError2 = yield JavaAddonManager.classInstanceFromFile(CLASS, DEX_FILE + "GARBAGE")
- .then((result) => false)
- .catch((error) => true);
- is(gotError2, true, "got expected error for non-existent DEX file");
- });
-
- // Make a request to a dynamically loaded Java Addon; wait for a response.
- // Then expect the add-on to make a request; respond.
- // Then expect the add-on to make a second request; use it to verify the response to the first request.
- add_task(function* testJavaAddonV1() {
- info("Loading Java Addon from: " + DEX_FILE);
-
- let javaAddon = yield JavaAddonManager.classInstanceFromFile(CLASS, DEX_FILE);
- isnot(javaAddon, null, "addon is not null");
- isnot(javaAddon._guid, null, "guid is not null");
- is(javaAddon._classname, CLASS, "got expected class");
- is(javaAddon._loaded, true, "addon is loaded");
-
- let messagePromise = Promise.defer();
- var count = 0;
- function listener(data) {
- info("Got request initiated from Java Addon: " + data + ", " + typeof(data) + ", " + JSON.stringify(data));
- count += 1;
- messagePromise.resolve(); // It's okay to resolve before returning: we'll wait on the verification promise no matter what.
- return {
- outputStringKey: "inputStringKey=" + data.inputStringKey,
- outputIntKey: data.inputIntKey - 1
- };
- }
- javaAddon.addListener(listener, "JavaAddon:V1:Request");
-
- let verifierPromise = Promise.defer();
- function verifier(data) {
- info("Got verification request initiated from Java Addon: " + data + ", " + typeof(data) + ", " + JSON.stringify(data));
- // These values are from the test Java Addon, after being processed by the :Request listener above.
- is(data.outputStringKey, "inputStringKey=raw", "got expected outputStringKey");
- is(data.outputIntKey, 2, "got expected outputIntKey");
- verifierPromise.resolve();
- return {};
- }
- javaAddon.addListener(verifier, "JavaAddon:V1:VerificationRequest");
-
- let message = {type: MESSAGE, inputStringKey: "test", inputIntKey: 5};
- info("Sending request to Java Addon: " + JSON.stringify(message));
- let output = yield javaAddon.sendRequestForResult(message);
-
- info("Got response from Java Addon: " + output + ", " + typeof(output) + ", " + JSON.stringify(output));
- is(output.outputStringKey, "inputStringKey=test", "got expected outputStringKey");
- is(output.outputIntKey, 6, "got expected outputIntKey");
-
- // We don't worry about timing out: the harness will (very much later)
- // kill us if we don't see the expected messages.
-
- info("Waiting for request initiated from Java Addon.");
- yield messagePromise.promise;
- is(count, 1, "count is 1");
-
- info("Waiting for verification request initiated from Java Addon.");
- yield verifierPromise.promise;
-
- info("Sending unregistered request to Java Addon: " + JSON.stringify(message));
- javaAddon.sendRequest(message);
-
- // Wait for the above request.
- info("Waiting for unregistered request to finish processing.");
- yield javaAddon.sendRequestForResult({type: "JavaAddon:V1:Finish"});
-
- // The JavaAddon should have removed its listener, so we shouldn't get a response and count should stay the same.
- is(count, 1, "count is still 1");
- });
-
- </script>
-</head>
-<body>
-<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1168407">Mozilla Bug 1168407</a>
-<br>
-<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1184186">Migrated from Robocop testJavaAddons</a>
-<p id="display"></p>
-<div id="content" style="display: none">
-
-</div>
-<pre id="test">
-</pre>
-</body>
-</html>
--- a/mobile/android/tests/browser/robocop/roboextender/Makefile.in
+++ b/mobile/android/tests/browser/robocop/roboextender/Makefile.in
@@ -1,9 +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/.
TEST_EXTENSIONS_DIR = $(DEPTH)/_tests/testing/mochitest/extensions
-
-tools::
- -cp $(DEPTH)/mobile/android/tests/javaaddons/javaaddons-test.apk $(TEST_EXTENSIONS_DIR)/roboextender@mozilla.org/base
deleted file mode 100644
--- a/mobile/android/tests/javaaddons/AndroidManifest.xml.in
+++ /dev/null
@@ -1,14 +0,0 @@
-#filter substitution
-<?xml version="1.0" encoding="utf-8"?>
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="org.mozilla.javaaddons.test"
- android:versionCode="1"
- android:versionName="1.0" >
-
- <uses-sdk android:minSdkVersion="@MOZ_ANDROID_MIN_SDK_VERSION@"
-#ifdef MOZ_ANDROID_MAX_SDK_VERSION
- android:maxSdkVersion="@MOZ_ANDROID_MAX_SDK_VERSION@"
-#endif
- android:targetSdkVersion="@ANDROID_TARGET_SDK@"/>
-
-</manifest>
deleted file mode 100644
--- a/mobile/android/tests/javaaddons/Makefile.in
+++ /dev/null
@@ -1,11 +0,0 @@
-# This Source Code Form is subject to the terms of the Mozilla Public
-# License, v. 2.0. If a copy of the MPL was not distributed with this
-# file, You can obtain one at http://mozilla.org/MPL/2.0/.
-
-ANDROID_MANIFEST_FILE := $(CURDIR)/AndroidManifest.xml
-
-ANDROID_EXTRA_JARS := javaaddons-test.jar
-
-include $(topsrcdir)/config/rules.mk
-
-tools libs:: $(ANDROID_APK_NAME).apk
deleted file mode 100644
--- a/mobile/android/tests/javaaddons/moz.build
+++ /dev/null
@@ -1,26 +0,0 @@
-# -*- 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/.
-
-with Files('**'):
- BUG_COMPONENT = ('Firefox for Android', 'Testing')
-
-ANDROID_APK_NAME = 'javaaddons-test'
-ANDROID_APK_PACKAGE = 'org.mozilla.javaaddons.test'
-
-jar = add_java_jar('javaaddons-test')
-jar.extra_jars += [
- TOPOBJDIR + '/mobile/android/javaaddons/javaaddons-1.0.jar',
-]
-jar.javac_flags += ['-Xlint:all']
-jar.sources += [
- 'src/org/mozilla/javaaddons/test/ClassWithNoRecognizedConstructors.java',
- 'src/org/mozilla/javaaddons/test/JavaAddonV0.java',
- 'src/org/mozilla/javaaddons/test/JavaAddonV1.java',
-]
-
-OBJDIR_PP_FILES.mobile.android.tests.javaaddons += [
- 'AndroidManifest.xml.in',
-]
deleted file mode 100644
--- a/mobile/android/tests/javaaddons/res/values/strings.xml
+++ /dev/null
@@ -1,3 +0,0 @@
-<resources>
- <string name="app_name">org.mozilla.javaaddons.test</string>
-</resources>
deleted file mode 100644
--- a/mobile/android/tests/javaaddons/src/org/mozilla/javaaddons/test/ClassWithNoRecognizedConstructors.java
+++ /dev/null
@@ -1,11 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.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 org.mozilla.javaaddons.test;
-
-public class ClassWithNoRecognizedConstructors {
- public ClassWithNoRecognizedConstructors(int a, String b, boolean c) {
- }
-}
deleted file mode 100644
--- a/mobile/android/tests/javaaddons/src/org/mozilla/javaaddons/test/JavaAddonV0.java
+++ /dev/null
@@ -1,24 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.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 org.mozilla.javaaddons.test;
-
-import android.os.Handler;
-import android.os.Message;
-import android.util.Log;
-
-import java.util.Map;
-
-public class JavaAddonV0 implements Handler.Callback {
- public JavaAddonV0(Map<String, Handler.Callback> callbacks) {
- callbacks.put("JavaAddon:V0", this);
- }
-
- @Override
- public boolean handleMessage(Message message) {
- Log.i("JavaAddon", "handleMessage " + message.toString());
- return true;
- }
-}
deleted file mode 100644
--- a/mobile/android/tests/javaaddons/src/org/mozilla/javaaddons/test/JavaAddonV1.java
+++ /dev/null
@@ -1,67 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.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 org.mozilla.javaaddons.test;
-
-import android.content.Context;
-import android.util.Log;
-import org.json.JSONException;
-import org.json.JSONObject;
-import org.mozilla.javaaddons.JavaAddonInterfaceV1.EventCallback;
-import org.mozilla.javaaddons.JavaAddonInterfaceV1.EventDispatcher;
-import org.mozilla.javaaddons.JavaAddonInterfaceV1.EventListener;
-import org.mozilla.javaaddons.JavaAddonInterfaceV1.RequestCallback;
-
-public class JavaAddonV1 implements EventListener, RequestCallback {
- protected final EventDispatcher mDispatcher;
-
- public JavaAddonV1(Context context, EventDispatcher dispatcher) {
- mDispatcher = dispatcher;
- mDispatcher.registerEventListener(this, "JavaAddon:V1");
- }
-
- @Override
- public void handleMessage(Context context, String event, JSONObject message, EventCallback callback) {
- Log.i("JavaAddon", "handleMessage: " + event + ", " + message.toString());
- final JSONObject output = new JSONObject();
- try {
- output.put("outputStringKey", "inputStringKey=" + message.getString("inputStringKey"));
- output.put("outputIntKey", 1 + message.getInt("inputIntKey"));
- } catch (JSONException e) {
- // Should never happen; ignore.
- }
- // Respond.
- if (callback != null) {
- callback.sendSuccess(output);
- }
-
- // And send an independent Gecko event.
- final JSONObject input = new JSONObject();
- try {
- input.put("inputStringKey", "raw");
- input.put("inputIntKey", 3);
- } catch (JSONException e) {
- // Should never happen; ignore.
- }
- mDispatcher.sendRequestToGecko("JavaAddon:V1:Request", input, this);
- }
-
- @Override
- public void onResponse(Context context, JSONObject jsonObject) {
- Log.i("JavaAddon", "onResponse: " + jsonObject.toString());
- // Unregister event listener, so that the JavaScript side can send a test message and
- // check it is not handled.
- mDispatcher.unregisterEventListener(this);
- mDispatcher.sendRequestToGecko("JavaAddon:V1:VerificationRequest", jsonObject, null);
-
- mDispatcher.registerEventListener(new EventListener() {
- @Override
- public void handleMessage(Context context, String event, JSONObject message, EventCallback callback) {
- mDispatcher.unregisterEventListener(this);
- callback.sendSuccess(null);
- }
- }, "JavaAddon:V1:Finish");
- }
-}
--- a/mobile/android/tests/moz.build
+++ b/mobile/android/tests/moz.build
@@ -10,13 +10,11 @@ with Files('**'):
if not CONFIG['MOZ_BUILD_MOBILE_ANDROID_WITH_GRADLE']:
TEST_DIRS += [
'background',
]
TEST_DIRS += [
'browser',
- 'javaaddons', # Must be built before browser/robocop/roboextender.
- # This is enforced in config/recurse.mk.
]
ANDROID_INSTRUMENTATION_MANIFESTS += ['browser/robocop/robocop.ini']