fixed merge conflict
diff --git a/RELEASENOTES.md b/RELEASENOTES.md
index 548a6a2..5ad2e29 100644
--- a/RELEASENOTES.md
+++ b/RELEASENOTES.md
@@ -35,4 +35,17 @@
 
 ### 0.2.3 (Oct 9, 2013)
 * [CB-4915] Incremented plugin version on dev branch.
-* [CB-4926] Fixes inappbrowser plugin loading for windows8
\ No newline at end of file
+* [CB-4926] Fixes inappbrowser plugin loading for windows8
+
+### 0.2.4 (Oct 28, 2013)
+* CB-5128: added repo + issue tag to plugin.xml for inappbrowser plugin
+* CB-4995 Fix crash when WebView is quickly opened then closed.
+* CB-4930 - iOS - InAppBrowser should take into account the status bar
+* [CB-5010] Incremented plugin version on dev branch.
+* [CB-5010] Updated version and RELEASENOTES.md for release 0.2.3
+* CB-4858 - Run IAB methods on the UI thread.
+* CB-4858 Convert relative URLs to absolute URLs in JS
+* CB-3747 Fix back button having different dismiss logic from the close button.
+* CB-5021 Expose closeDialog() as a public function and make it safe to call multiple times.
+* CB-5021 Make it safe to call close() multiple times
+>>>>>>> dev
diff --git a/plugin.xml b/plugin.xml
index 03cb490..94cfb82 100644
--- a/plugin.xml
+++ b/plugin.xml
@@ -2,12 +2,18 @@
 
 <plugin xmlns="http://apache.org/cordova/ns/plugins/1.0"
            id="org.apache.cordova.inappbrowser"
-      version="0.2.3">
+      version="0.2.4">
+
     <name>InAppBrowser</name>
     <description>Cordova InAppBrowser Plugin</description>
     <license>Apache 2.0</license>
     <keywords>cordova,in,app,browser,inappbrowser</keywords>
+    <repo>https://git-wip-us.apache.org/repos/asf/cordova-plugin-inappbrowser.git</repo>
+    <issue>https://issues.apache.org/jira/browse/CB/component/12320641</issue>
 
+    <engines>
+      <engine name="cordova" version=">=3.1.0" /><!-- Needs cordova/urlutil -->
+    </engines>
 
     <js-module src="www/InAppBrowser.js" name="InAppBrowser">
         <clobbers target="window.open" />
diff --git a/src/android/InAppBrowser.java b/src/android/InAppBrowser.java
index 3be0316..da6b241 100644
--- a/src/android/InAppBrowser.java
+++ b/src/android/InAppBrowser.java
@@ -18,20 +18,6 @@
 */
 package org.apache.cordova.inappbrowser;
 
-import java.util.HashMap;
-import java.util.StringTokenizer;
-
-
-import org.apache.cordova.Config;
-import org.apache.cordova.CordovaWebView;
-import org.apache.cordova.CallbackContext;
-import org.apache.cordova.CordovaPlugin;
-import org.apache.cordova.LOG;
-import org.apache.cordova.PluginResult;
-import org.json.JSONArray;
-import org.json.JSONException;
-import org.json.JSONObject;
-
 import android.annotation.SuppressLint;
 import android.app.Dialog;
 import android.content.Context;
@@ -52,11 +38,7 @@
 import android.view.inputmethod.EditorInfo;
 import android.view.inputmethod.InputMethodManager;
 import android.webkit.CookieManager;
-import android.webkit.WebChromeClient;
-import android.webkit.GeolocationPermissions.Callback;
-import android.webkit.JsPromptResult;
 import android.webkit.WebSettings;
-import android.webkit.WebStorage;
 import android.webkit.WebView;
 import android.webkit.WebViewClient;
 import android.widget.Button;
@@ -64,6 +46,19 @@
 import android.widget.LinearLayout;
 import android.widget.RelativeLayout;
 
+import org.apache.cordova.CallbackContext;
+import org.apache.cordova.Config;
+import org.apache.cordova.CordovaArgs;
+import org.apache.cordova.CordovaPlugin;
+import org.apache.cordova.CordovaWebView;
+import org.apache.cordova.LOG;
+import org.apache.cordova.PluginResult;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.util.HashMap;
+import java.util.StringTokenizer;
+
 @SuppressLint("SetJavaScriptEnabled")
 public class InAppBrowser extends CordovaPlugin {
 
@@ -100,121 +95,135 @@
      * @param callbackId    The callback id used when calling back into JavaScript.
      * @return              A PluginResult object with a status and message.
      */
-    public boolean execute(String action, JSONArray args, CallbackContext callbackContext) throws JSONException {
-        try {
-            if (action.equals("open")) {
-                this.callbackContext = callbackContext;
-                String url = args.getString(0);
-                String target = args.optString(1);
-                if (target == null || target.equals("") || target.equals(NULL)) {
-                    target = SELF;
-                }
-                HashMap<String, Boolean> features = parseFeature(args.optString(2));
-                
-                Log.d(LOG_TAG, "target = " + target);
-
-                url = updateUrl(url);
-                String result = "";
-
-                // SELF
-                if (SELF.equals(target)) {
-                    Log.d(LOG_TAG, "in self");
-                    // load in webview
-                    if (url.startsWith("file://") || url.startsWith("javascript:") 
-                            || Config.isUrlWhiteListed(url)) {
-                        this.webView.loadUrl(url);
-                    }
-                    //Load the dialer
-                    else if (url.startsWith(WebView.SCHEME_TEL))
-                    {
-                        try {
-                            Intent intent = new Intent(Intent.ACTION_DIAL);
-                            intent.setData(Uri.parse(url));
-                            this.cordova.getActivity().startActivity(intent);
-                        } catch (android.content.ActivityNotFoundException e) {
-                            LOG.e(LOG_TAG, "Error dialing " + url + ": " + e.toString());
+    public boolean execute(String action, CordovaArgs args, final CallbackContext callbackContext) throws JSONException {
+        if (action.equals("open")) {
+            this.callbackContext = callbackContext;
+            final String url = args.getString(0);
+            String t = args.optString(1);
+            if (t == null || t.equals("") || t.equals(NULL)) {
+                t = SELF;
+            }
+            final String target = t;
+            final HashMap<String, Boolean> features = parseFeature(args.optString(2));
+            
+            Log.d(LOG_TAG, "target = " + target);
+            
+            this.cordova.getActivity().runOnUiThread(new Runnable() {
+                @Override
+                public void run() {
+                    String result = "";
+                    // SELF
+                    if (SELF.equals(target)) {
+                        Log.d(LOG_TAG, "in self");
+                        // load in webview
+                        if (url.startsWith("file://") || url.startsWith("javascript:") 
+                                || Config.isUrlWhiteListed(url)) {
+                            webView.loadUrl(url);
+                        }
+                        //Load the dialer
+                        else if (url.startsWith(WebView.SCHEME_TEL))
+                        {
+                            try {
+                                Intent intent = new Intent(Intent.ACTION_DIAL);
+                                intent.setData(Uri.parse(url));
+                               cordova.getActivity().startActivity(intent);
+                            } catch (android.content.ActivityNotFoundException e) {
+                                LOG.e(LOG_TAG, "Error dialing " + url + ": " + e.toString());
+                            }
+                        }
+                        // load in InAppBrowser
+                        else {
+                            result = showWebPage(url, features);
                         }
                     }
-                    // load in InAppBrowser
+                    // SYSTEM
+                    else if (SYSTEM.equals(target)) {
+                        Log.d(LOG_TAG, "in system");
+                        result = openExternal(url);
+                    }
+                    // BLANK - or anything else
                     else {
-                        result = this.showWebPage(url, features);
+                        Log.d(LOG_TAG, "in blank");
+                        result = showWebPage(url, features);
                     }
+    
+                    PluginResult pluginResult = new PluginResult(PluginResult.Status.OK, result);
+                    pluginResult.setKeepCallback(true);
+                    callbackContext.sendPluginResult(pluginResult);
                 }
-                // SYSTEM
-                else if (SYSTEM.equals(target)) {
-                    Log.d(LOG_TAG, "in system");
-                    result = this.openExternal(url);
+            });
+        }
+        else if (action.equals("close")) {
+            closeDialog();
+        }
+        else if (action.equals("injectScriptCode")) {
+            String jsWrapper = null;
+            if (args.getBoolean(1)) {
+                jsWrapper = String.format("prompt(JSON.stringify([eval(%%s)]), 'gap-iab://%s')", callbackContext.getCallbackId());
+            }
+            injectDeferredObject(args.getString(0), jsWrapper);
+        }
+        else if (action.equals("injectScriptFile")) {
+            String jsWrapper;
+            if (args.getBoolean(1)) {
+                jsWrapper = String.format("(function(d) { var c = d.createElement('script'); c.src = %%s; c.onload = function() { prompt('', 'gap-iab://%s'); }; d.body.appendChild(c); })(document)", callbackContext.getCallbackId());
+            } else {
+                jsWrapper = "(function(d) { var c = d.createElement('script'); c.src = %s; d.body.appendChild(c); })(document)";
+            }
+            injectDeferredObject(args.getString(0), jsWrapper);
+        }
+        else if (action.equals("injectStyleCode")) {
+            String jsWrapper;
+            if (args.getBoolean(1)) {
+                jsWrapper = String.format("(function(d) { var c = d.createElement('style'); c.innerHTML = %%s; d.body.appendChild(c); prompt('', 'gap-iab://%s');})(document)", callbackContext.getCallbackId());
+            } else {
+                jsWrapper = "(function(d) { var c = d.createElement('style'); c.innerHTML = %s; d.body.appendChild(c); })(document)";
+            }
+            injectDeferredObject(args.getString(0), jsWrapper);
+        }
+        else if (action.equals("injectStyleFile")) {
+            String jsWrapper;
+            if (args.getBoolean(1)) {
+                jsWrapper = String.format("(function(d) { var c = d.createElement('link'); c.rel='stylesheet'; c.type='text/css'; c.href = %%s; d.head.appendChild(c); prompt('', 'gap-iab://%s');})(document)", callbackContext.getCallbackId());
+            } else {
+                jsWrapper = "(function(d) { var c = d.createElement('link'); c.rel='stylesheet'; c.type='text/css'; c.href = %s; d.head.appendChild(c); })(document)";
+            }
+            injectDeferredObject(args.getString(0), jsWrapper);
+        }
+        else if (action.equals("show")) {
+            this.cordova.getActivity().runOnUiThread(new Runnable() {
+                @Override
+                public void run() {
+                    dialog.show();
                 }
-                // BLANK - or anything else
-                else {
-                    Log.d(LOG_TAG, "in blank");
-                    result = this.showWebPage(url, features);
-                }
-
-                PluginResult pluginResult = new PluginResult(PluginResult.Status.OK, result);
-                pluginResult.setKeepCallback(true);
-                this.callbackContext.sendPluginResult(pluginResult);
-            }
-            else if (action.equals("close")) {
-                closeDialog();
-                this.callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK));
-            }
-            else if (action.equals("injectScriptCode")) {
-                String jsWrapper = null;
-                if (args.getBoolean(1)) {
-                    jsWrapper = String.format("prompt(JSON.stringify([eval(%%s)]), 'gap-iab://%s')", callbackContext.getCallbackId());
-                }
-                injectDeferredObject(args.getString(0), jsWrapper);
-            }
-            else if (action.equals("injectScriptFile")) {
-                String jsWrapper;
-                if (args.getBoolean(1)) {
-                    jsWrapper = String.format("(function(d) { var c = d.createElement('script'); c.src = %%s; c.onload = function() { prompt('', 'gap-iab://%s'); }; d.body.appendChild(c); })(document)", callbackContext.getCallbackId());
-                } else {
-                    jsWrapper = "(function(d) { var c = d.createElement('script'); c.src = %s; d.body.appendChild(c); })(document)";
-                }
-                injectDeferredObject(args.getString(0), jsWrapper);
-            }
-            else if (action.equals("injectStyleCode")) {
-                String jsWrapper;
-                if (args.getBoolean(1)) {
-                    jsWrapper = String.format("(function(d) { var c = d.createElement('style'); c.innerHTML = %%s; d.body.appendChild(c); prompt('', 'gap-iab://%s');})(document)", callbackContext.getCallbackId());
-                } else {
-                    jsWrapper = "(function(d) { var c = d.createElement('style'); c.innerHTML = %s; d.body.appendChild(c); })(document)";
-                }
-                injectDeferredObject(args.getString(0), jsWrapper);
-            }
-            else if (action.equals("injectStyleFile")) {
-                String jsWrapper;
-                if (args.getBoolean(1)) {
-                    jsWrapper = String.format("(function(d) { var c = d.createElement('link'); c.rel='stylesheet'; c.type='text/css'; c.href = %%s; d.head.appendChild(c); prompt('', 'gap-iab://%s');})(document)", callbackContext.getCallbackId());
-                } else {
-                    jsWrapper = "(function(d) { var c = d.createElement('link'); c.rel='stylesheet'; c.type='text/css'; c.href = %s; d.head.appendChild(c); })(document)";
-                }
-                injectDeferredObject(args.getString(0), jsWrapper);
-            }
-            else if (action.equals("show")) {
-                Runnable runnable = new Runnable() {
-                    @Override
-                    public void run() {
-                        dialog.show();
-                    }
-                };
-                this.cordova.getActivity().runOnUiThread(runnable);
-                PluginResult pluginResult = new PluginResult(PluginResult.Status.OK);
-                pluginResult.setKeepCallback(true);
-                this.callbackContext.sendPluginResult(pluginResult);
-            }
-            else {
-                return false;
-            }
-        } catch (JSONException e) {
-            this.callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.JSON_EXCEPTION));
+            });
+            PluginResult pluginResult = new PluginResult(PluginResult.Status.OK);
+            pluginResult.setKeepCallback(true);
+            this.callbackContext.sendPluginResult(pluginResult);
+        }
+        else {
+            return false;
         }
         return true;
     }
 
     /**
+     * Called when the view navigates.
+     */
+    @Override
+    public void onReset() {
+        closeDialog();        
+    }
+    
+    /**
+     * Called by AccelBroker when listener is to be shut down.
+     * Stop listener.
+     */
+    public void onDestroy() {
+        closeDialog();
+    }
+    
+    /**
      * Inject an object (script or style) into the InAppBrowser WebView.
      *
      * This is a helper method for the inject{Script|Style}{Code|File} API calls, which
@@ -241,8 +250,14 @@
         } else {
             scriptToInject = source;
         }
+        final String finalScriptToInject = scriptToInject;
         // This action will have the side-effect of blurring the currently focused element
-        this.inAppWebView.loadUrl("javascript:" + scriptToInject);
+        this.cordova.getActivity().runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                inAppWebView.loadUrl("javascript:" + finalScriptToInject);
+            }
+        });
     }
 
     /**
@@ -275,20 +290,6 @@
     }
 
     /**
-     * Convert relative URL to full path
-     * 
-     * @param url
-     * @return 
-     */
-    private String updateUrl(String url) {
-        Uri newUrl = Uri.parse(url);
-        if (newUrl.isRelative()) {
-            url = this.webView.getUrl().substring(0, this.webView.getUrl().lastIndexOf("/")+1) + url;
-        }
-        return url;
-    }
-
-    /**
      * Display a new browser with the specified URL.
      *
      * @param url           The url to load.
@@ -311,30 +312,30 @@
     /**
      * Closes the dialog
      */
-    private void closeDialog() {
-        try {
-            final WebView childView = this.inAppWebView;
-            Runnable runnable = new Runnable() {
-
-                @Override
-                public void run() {
-                    childView.loadUrl("about:blank");
+    public void closeDialog() {
+        final WebView childView = this.inAppWebView;
+        // The JS protects against multiple calls, so this should happen only when
+        // closeDialog() is called by other native code.
+        if (childView == null) {
+            return;
+        }
+        this.cordova.getActivity().runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                childView.loadUrl("about:blank");
+                if (dialog != null) {
+                    dialog.dismiss();
                 }
-                
-            };
-            
-            this.cordova.getActivity().runOnUiThread(runnable);
+            }
+        });
+        try {
             JSONObject obj = new JSONObject();
             obj.put("type", EXIT_EVENT);
-
             sendUpdate(obj, false);
         } catch (JSONException ex) {
             Log.d(LOG_TAG, "Should never happen");
         }
         
-        if (dialog != null) {
-            dialog.dismiss();
-        }
     }
 
     /**
@@ -438,14 +439,7 @@
                 dialog.setCancelable(true);
                 dialog.setOnDismissListener(new DialogInterface.OnDismissListener() {
                         public void onDismiss(DialogInterface dialog) {
-                            try {
-                                JSONObject obj = new JSONObject();
-                                obj.put("type", EXIT_EVENT);
-
-                                sendUpdate(obj, false);
-                            } catch (JSONException e) {
-                                Log.d(LOG_TAG, "Should never happen");
-                            }
+                            closeDialog();
                         }
                 });
 
@@ -620,10 +614,16 @@
      *
      * @param obj a JSONObject contain event payload information
      * @param status the status code to return to the JavaScript environment
-     */    private void sendUpdate(JSONObject obj, boolean keepCallback, PluginResult.Status status) {
-        PluginResult result = new PluginResult(status, obj);
-        result.setKeepCallback(keepCallback);
-        this.callbackContext.sendPluginResult(result);
+     */    
+    private void sendUpdate(JSONObject obj, boolean keepCallback, PluginResult.Status status) {
+        if (callbackContext != null) {
+            PluginResult result = new PluginResult(status, obj);
+            result.setKeepCallback(keepCallback);
+            callbackContext.sendPluginResult(result);
+            if (!keepCallback) {
+                callbackContext = null;
+            }
+        }
     }
 
 
diff --git a/src/ios/CDVInAppBrowser.m b/src/ios/CDVInAppBrowser.m
index 4170dd2..4ef051f 100644
--- a/src/ios/CDVInAppBrowser.m
+++ b/src/ios/CDVInAppBrowser.m
@@ -32,6 +32,11 @@
 
 #pragma mark CDVInAppBrowser
 
+@interface CDVInAppBrowser () {
+    UIStatusBarStyle _previousStatusBarStyle;
+}
+@end
+
 @implementation CDVInAppBrowser
 
 - (CDVInAppBrowser*)initWithWebView:(UIWebView*)theWebView
@@ -51,12 +56,8 @@
 
 - (void)close:(CDVInvokedUrlCommand*)command
 {
-    if (self.inAppBrowserViewController != nil) {
-        [self.inAppBrowserViewController close];
-        self.inAppBrowserViewController = nil;
-    }
-
-    self.callbackId = nil;
+    // Things are cleaned up in browserExit.
+    [self.inAppBrowserViewController close];
 }
 
 - (BOOL) isSystemUrl:(NSURL*)url
@@ -115,6 +116,7 @@
         }
     }
 
+    _previousStatusBarStyle = [UIApplication sharedApplication].statusBarStyle;
 
     CDVInAppBrowserOptions* browserOptions = [CDVInAppBrowserOptions parseOptions:options];
     [self.inAppBrowserViewController showLocationBar:browserOptions.location];
@@ -155,8 +157,13 @@
     }
   
     if (! browserOptions.hidden) {
+        
+        UINavigationController* nav = [[UINavigationController alloc]
+                                       initWithRootViewController:self.inAppBrowserViewController];
+        nav.navigationBarHidden = YES;
+        
       if (self.viewController.modalViewController != self.inAppBrowserViewController) {
-        [self.viewController presentModalViewController:self.inAppBrowserViewController animated:YES];
+          [self.viewController presentModalViewController:nav animated:YES];
       }
     }
     [self.inAppBrowserViewController navigateTo:url];
@@ -166,7 +173,13 @@
 {
     if ([self.inAppBrowserViewController isViewLoaded] && self.inAppBrowserViewController.view.window)
         return;
-    [self.viewController presentModalViewController:self.inAppBrowserViewController animated:YES];
+    
+    _previousStatusBarStyle = [UIApplication sharedApplication].statusBarStyle;
+    
+    UINavigationController* nav = [[UINavigationController alloc]
+                                   initWithRootViewController:self.inAppBrowserViewController];
+    nav.navigationBarHidden = YES;
+    [self.viewController presentModalViewController:nav animated:YES];
 }
 
 - (void)openInCordovaWebView:(NSURL*)url withOptions:(NSString*)options
@@ -355,13 +368,18 @@
     if (self.callbackId != nil) {
         CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK
                                                       messageAsDictionary:@{@"type":@"exit"}];
-        [pluginResult setKeepCallback:[NSNumber numberWithBool:YES]];
-
         [self.commandDelegate sendPluginResult:pluginResult callbackId:self.callbackId];
+        self.callbackId = nil;
     }
+    // Set navigationDelegate to nil to ensure no callbacks are received from it.
+    self.inAppBrowserViewController.navigationDelegate = nil;
     // Don't recycle the ViewController since it may be consuming a lot of memory.
     // Also - this is required for the PDF/User-Agent bug work-around.
     self.inAppBrowserViewController = nil;
+
+    if (IsAtLeastiOSVersion(@"7.0")) {
+        [[UIApplication sharedApplication] setStatusBarStyle:_previousStatusBarStyle];
+    }
 }
 
 @end
@@ -632,6 +650,11 @@
     [CDVUserAgentUtil releaseLock:&_userAgentLockToken];
     [super viewDidUnload];
 }
+    
+- (UIStatusBarStyle)preferredStatusBarStyle
+{
+    return UIStatusBarStyleDefault;
+}
 
 - (void)close
 {
@@ -674,6 +697,15 @@
 {
     [self.webView goForward];
 }
+    
+- (void)viewWillAppear:(BOOL)animated
+{
+    if (IsAtLeastiOSVersion(@"7.0")) {
+        [[UIApplication sharedApplication] setStatusBarStyle:[self preferredStatusBarStyle]];
+    }
+    
+    [super viewWillAppear:animated];
+}
 
 #pragma mark UIWebViewDelegate
 
diff --git a/www/InAppBrowser.js b/www/InAppBrowser.js
index 5da53fd..3fe9261 100644
--- a/www/InAppBrowser.js
+++ b/www/InAppBrowser.js
@@ -22,6 +22,7 @@
 var exec = require('cordova/exec');
 var channel = require('cordova/channel');
 var modulemapper = require('cordova/modulemapper');
+var urlutil = require('cordova/urlutil');
 
 function InAppBrowser() {
    this.channels = {
@@ -30,6 +31,7 @@
         'loaderror' : channel.create('loaderror'),
         'exit' : channel.create('exit')
    };
+   this._alive = true;
 }
 
 InAppBrowser.prototype = {
@@ -39,7 +41,10 @@
         }
     },
     close: function (eventname) {
-        exec(null, null, "InAppBrowser", "close", []);
+        if (this._alive) {
+            this._alive = false;
+            exec(null, null, "InAppBrowser", "close", []);
+        }
     },
     show: function (eventname) {
       exec(null, null, "InAppBrowser", "show", []);
@@ -77,17 +82,18 @@
 };
 
 module.exports = function(strUrl, strWindowName, strWindowFeatures) {
-    var iab = new InAppBrowser();
-    var cb = function(eventname) {
-       iab._eventHandler(eventname);
-    };
-
     // Don't catch calls that write to existing frames (e.g. named iframes).
     if (window.frames && window.frames[strWindowName]) {
         var origOpenFunc = modulemapper.getOriginalSymbol(window, 'open');
         return origOpenFunc.apply(window, arguments);
     }
 
+    strUrl = urlutil.makeAbsolute(strUrl);
+    var iab = new InAppBrowser();
+    var cb = function(eventname) {
+       iab._eventHandler(eventname);
+    };
+
     exec(cb, cb, "InAppBrowser", "open", [strUrl, strWindowName, strWindowFeatures]);
     return iab;
 };