summaryrefslogtreecommitdiff
path: root/plugins/cordova-plugin-inappbrowser/src
diff options
context:
space:
mode:
Diffstat (limited to 'plugins/cordova-plugin-inappbrowser/src')
-rw-r--r--plugins/cordova-plugin-inappbrowser/src/amazon/InAppBrowser.java846
-rw-r--r--plugins/cordova-plugin-inappbrowser/src/amazon/InAppChromeClient.java146
-rw-r--r--plugins/cordova-plugin-inappbrowser/src/android/InAppBrowser.java879
-rw-r--r--plugins/cordova-plugin-inappbrowser/src/android/InAppBrowserDialog.java58
-rw-r--r--plugins/cordova-plugin-inappbrowser/src/android/InAppChromeClient.java133
-rw-r--r--plugins/cordova-plugin-inappbrowser/src/android/res/drawable-hdpi/ic_action_next_item.pngbin0 -> 593 bytes
-rw-r--r--plugins/cordova-plugin-inappbrowser/src/android/res/drawable-hdpi/ic_action_previous_item.pngbin0 -> 599 bytes
-rw-r--r--plugins/cordova-plugin-inappbrowser/src/android/res/drawable-hdpi/ic_action_remove.pngbin0 -> 438 bytes
-rw-r--r--plugins/cordova-plugin-inappbrowser/src/android/res/drawable-mdpi/ic_action_next_item.pngbin0 -> 427 bytes
-rw-r--r--plugins/cordova-plugin-inappbrowser/src/android/res/drawable-mdpi/ic_action_previous_item.pngbin0 -> 438 bytes
-rw-r--r--plugins/cordova-plugin-inappbrowser/src/android/res/drawable-mdpi/ic_action_remove.pngbin0 -> 328 bytes
-rw-r--r--plugins/cordova-plugin-inappbrowser/src/android/res/drawable-xhdpi/ic_action_next_item.pngbin0 -> 727 bytes
-rw-r--r--plugins/cordova-plugin-inappbrowser/src/android/res/drawable-xhdpi/ic_action_previous_item.pngbin0 -> 744 bytes
-rw-r--r--plugins/cordova-plugin-inappbrowser/src/android/res/drawable-xhdpi/ic_action_remove.pngbin0 -> 536 bytes
-rw-r--r--plugins/cordova-plugin-inappbrowser/src/android/res/drawable-xxhdpi/ic_action_next_item.pngbin0 -> 1021 bytes
-rw-r--r--plugins/cordova-plugin-inappbrowser/src/android/res/drawable-xxhdpi/ic_action_previous_item.pngbin0 -> 1038 bytes
-rw-r--r--plugins/cordova-plugin-inappbrowser/src/android/res/drawable-xxhdpi/ic_action_remove.pngbin0 -> 681 bytes
-rw-r--r--plugins/cordova-plugin-inappbrowser/src/blackberry10/README.md43
-rw-r--r--plugins/cordova-plugin-inappbrowser/src/browser/InAppBrowserProxy.js221
-rw-r--r--plugins/cordova-plugin-inappbrowser/src/firefoxos/InAppBrowserProxy.js191
-rw-r--r--plugins/cordova-plugin-inappbrowser/src/ios/CDVInAppBrowser.h113
-rw-r--r--plugins/cordova-plugin-inappbrowser/src/ios/CDVInAppBrowser.m1022
-rw-r--r--plugins/cordova-plugin-inappbrowser/src/ubuntu/InAppBrowser.qml92
-rw-r--r--plugins/cordova-plugin-inappbrowser/src/ubuntu/InAppBrowser_escapeScript.js29
-rw-r--r--plugins/cordova-plugin-inappbrowser/src/ubuntu/close.pngbin0 -> 461 bytes
-rw-r--r--plugins/cordova-plugin-inappbrowser/src/ubuntu/inappbrowser.cpp105
-rw-r--r--plugins/cordova-plugin-inappbrowser/src/ubuntu/inappbrowser.h61
-rw-r--r--plugins/cordova-plugin-inappbrowser/src/windows/InAppBrowserProxy.js326
-rw-r--r--plugins/cordova-plugin-inappbrowser/src/wp/InAppBrowser.cs515
29 files changed, 4780 insertions, 0 deletions
diff --git a/plugins/cordova-plugin-inappbrowser/src/amazon/InAppBrowser.java b/plugins/cordova-plugin-inappbrowser/src/amazon/InAppBrowser.java
new file mode 100644
index 00000000..0263ea2c
--- /dev/null
+++ b/plugins/cordova-plugin-inappbrowser/src/amazon/InAppBrowser.java
@@ -0,0 +1,846 @@
+/*
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied. See the License for the
+ specific language governing permissions and limitations
+ under the License.
+*/
+package org.apache.cordova.inappbrowser;
+
+import android.annotation.SuppressLint;
+import org.apache.cordova.inappbrowser.InAppBrowserDialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.drawable.Drawable;
+import android.net.Uri;
+import android.os.Build;
+import android.os.Bundle;
+import android.text.InputType;
+import android.util.Log;
+import android.util.TypedValue;
+import android.view.Gravity;
+import android.view.KeyEvent;
+import android.view.View;
+import android.view.Window;
+import android.view.WindowManager;
+import android.view.WindowManager.LayoutParams;
+import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.InputMethodManager;
+import com.amazon.android.webkit.AmazonWebChromeClient;
+import com.amazon.android.webkit.AmazonGeolocationPermissions.Callback;
+import com.amazon.android.webkit.AmazonJsPromptResult;
+import com.amazon.android.webkit.AmazonWebSettings;
+import com.amazon.android.webkit.AmazonWebStorage;
+import com.amazon.android.webkit.AmazonWebView;
+import com.amazon.android.webkit.AmazonWebViewClient;
+import com.amazon.android.webkit.AmazonCookieManager;
+import android.widget.Button;
+import android.widget.EditText;
+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.apache.cordova.CordovaActivity;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.util.HashMap;
+import java.util.StringTokenizer;
+
+@SuppressLint("SetJavaScriptEnabled")
+public class InAppBrowser extends CordovaPlugin {
+
+ private static final String NULL = "null";
+ protected static final String LOG_TAG = "InAppBrowser";
+ private static final String SELF = "_self";
+ private static final String SYSTEM = "_system";
+ // private static final String BLANK = "_blank";
+ private static final String EXIT_EVENT = "exit";
+ private static final String LOCATION = "location";
+ private static final String HIDDEN = "hidden";
+ private static final String ZOOM = "zoom";
+ private static final String LOAD_START_EVENT = "loadstart";
+ private static final String LOAD_STOP_EVENT = "loadstop";
+ private static final String LOAD_ERROR_EVENT = "loaderror";
+ private static final String CLEAR_ALL_CACHE = "clearcache";
+ private static final String CLEAR_SESSION_CACHE = "clearsessioncache";
+
+ private InAppBrowserDialog dialog;
+ private AmazonWebView inAppWebView;
+ private EditText edittext;
+ private CallbackContext callbackContext;
+ private boolean showLocationBar = true;
+ private boolean showZoomControls = true;
+ private boolean openWindowHidden = false;
+ private boolean clearAllCache= false;
+ private boolean clearSessionCache=false;
+
+ /**
+ * Executes the request and returns PluginResult.
+ *
+ * @param action The action to execute.
+ * @param args JSONArry of arguments for the plugin.
+ * @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, 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)) {
+ Log.d(LOG_TAG, "loading in webview");
+ webView.loadUrl(url);
+ }
+ //Load the dialer
+ else if (url.startsWith(AmazonWebView.SCHEME_TEL))
+ {
+ try {
+ Log.d(LOG_TAG, "loading in dialer");
+ 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 {
+ Log.d(LOG_TAG, "loading in InAppBrowser");
+ result = showWebPage(url, features);
+ }
+ }
+ // SYSTEM
+ else if (SYSTEM.equals(target)) {
+ Log.d(LOG_TAG, "in system");
+ result = openExternal(url);
+ }
+ // BLANK - or anything else
+ else {
+ Log.d(LOG_TAG, "in blank");
+ result = showWebPage(url, features);
+ }
+
+ PluginResult pluginResult = new PluginResult(PluginResult.Status.OK, result);
+ pluginResult.setKeepCallback(true);
+ callbackContext.sendPluginResult(pluginResult);
+ }
+ });
+ }
+ 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();
+ }
+ });
+ 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 AmazonWebView.
+ *
+ * This is a helper method for the inject{Script|Style}{Code|File} API calls, which
+ * provides a consistent method for injecting JavaScript code into the document.
+ *
+ * If a wrapper string is supplied, then the source string will be JSON-encoded (adding
+ * quotes) and wrapped using string formatting. (The wrapper string should have a single
+ * '%s' marker)
+ *
+ * @param source The source object (filename or script/style text) to inject into
+ * the document.
+ * @param jsWrapper A JavaScript string to wrap the source string in, so that the object
+ * is properly injected, or null if the source string is JavaScript text
+ * which should be executed directly.
+ */
+ private void injectDeferredObject(String source, String jsWrapper) {
+ final String scriptToInject;
+ if (jsWrapper != null) {
+ org.json.JSONArray jsonEsc = new org.json.JSONArray();
+ jsonEsc.put(source);
+ String jsonRepr = jsonEsc.toString();
+ String jsonSourceString = jsonRepr.substring(1, jsonRepr.length()-1);
+ scriptToInject = String.format(jsWrapper, jsonSourceString);
+ } else {
+ scriptToInject = source;
+ }
+ final String finalScriptToInject = scriptToInject;
+ this.cordova.getActivity().runOnUiThread(new Runnable() {
+ @SuppressLint("NewApi")
+ @Override
+ public void run() {
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
+ // This action will have the side-effect of blurring the currently focused element
+ inAppWebView.loadUrl("javascript:" + finalScriptToInject);
+ } /*else {
+ inAppWebView.evaluateJavascript(finalScriptToInject, null);
+ }*/
+ }
+ });
+ }
+
+ /**
+ * Put the list of features into a hash map
+ *
+ * @param optString
+ * @return
+ */
+ private HashMap<String, Boolean> parseFeature(String optString) {
+ if (optString.equals(NULL)) {
+ return null;
+ } else {
+ HashMap<String, Boolean> map = new HashMap<String, Boolean>();
+ StringTokenizer features = new StringTokenizer(optString, ",");
+ StringTokenizer option;
+ while(features.hasMoreElements()) {
+ option = new StringTokenizer(features.nextToken(), "=");
+ if (option.hasMoreElements()) {
+ String key = option.nextToken();
+ Boolean value = option.nextToken().equals("no") ? Boolean.FALSE : Boolean.TRUE;
+ map.put(key, value);
+ }
+ }
+ return map;
+ }
+ }
+
+ /**
+ * Display a new browser with the specified URL.
+ *
+ * @param url The url to load.
+ * @param usePhoneGap Load url in PhoneGap webview
+ * @return "" if ok, or error message.
+ */
+ public String openExternal(String url) {
+ try {
+ Intent intent = null;
+ intent = new Intent(Intent.ACTION_VIEW);
+ // Omitting the MIME type for file: URLs causes "No Activity found to handle Intent".
+ // Adding the MIME type to http: URLs causes them to not be handled by the downloader.
+ Uri uri = Uri.parse(url);
+ if ("file".equals(uri.getScheme())) {
+ intent.setDataAndType(uri, webView.getResourceApi().getMimeType(uri));
+ } else {
+ intent.setData(uri);
+ }
+ this.cordova.getActivity().startActivity(intent);
+ return "";
+ } catch (android.content.ActivityNotFoundException e) {
+ Log.d(LOG_TAG, "InAppBrowser: Error loading url "+url+":"+ e.toString());
+ return e.toString();
+ }
+ }
+
+ /**
+ * Closes the dialog
+ */
+ public void closeDialog() {
+ final AmazonWebView 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.setWebViewClient(new AmazonWebViewClient() {
+ // NB: wait for about:blank before dismissing
+ public void onPageFinished(AmazonWebView view, String url) {
+ if (dialog != null) {
+ dialog.dismiss();
+ }
+ }
+ });
+ // NB: From SDK 19: "If you call methods on WebView from any thread
+ // other than your app's UI thread, it can cause unexpected results."
+ // http://developer.android.com/guide/webapps/migrating.html#Threads
+ childView.loadUrl("about:blank");
+ }
+ });
+
+ try {
+ JSONObject obj = new JSONObject();
+ obj.put("type", EXIT_EVENT);
+ sendUpdate(obj, false);
+ } catch (JSONException ex) {
+ Log.d(LOG_TAG, "Should never happen");
+ }
+ }
+ /**
+ * Checks to see if it is possible to go back one page in history, then does so.
+ */
+ private void goBack() {
+ this.cordova.getActivity().runOnUiThread(new Runnable() {
+ public void run() {
+ if (InAppBrowser.this.inAppWebView.canGoBack()) {
+ InAppBrowser.this.inAppWebView.goBack();
+ }
+ }
+ });
+ }
+
+ /**
+ * Checks to see if it is possible to go forward one page in history, then does so.
+ */
+ private void goForward() {
+ this.cordova.getActivity().runOnUiThread(new Runnable() {
+ public void run() {
+ if (InAppBrowser.this.inAppWebView.canGoForward()) {
+ InAppBrowser.this.inAppWebView.goForward();
+ }
+ }
+ });
+ }
+
+ /**
+ * Navigate to the new page
+ *
+ * @param url to load
+ */
+ private void navigate(final String url) {
+ InputMethodManager imm = (InputMethodManager)this.cordova.getActivity().getSystemService(Context.INPUT_METHOD_SERVICE);
+ imm.hideSoftInputFromWindow(edittext.getWindowToken(), 0);
+
+ this.cordova.getActivity().runOnUiThread(new Runnable() {
+ public void run() {
+ if (!url.startsWith("http") && !url.startsWith("file:")) {
+ InAppBrowser.this.inAppWebView.loadUrl("http://" + url);
+ } else {
+ InAppBrowser.this.inAppWebView.loadUrl(url);
+ }
+ InAppBrowser.this.inAppWebView.requestFocus();
+ }
+ });
+ }
+
+
+ /**
+ * Should we show the location bar?
+ *
+ * @return boolean
+ */
+ private boolean getShowLocationBar() {
+ return this.showLocationBar;
+ }
+
+ /**
+ * Should we show the zoom controls?
+ *
+ * @return boolean
+ */
+ private boolean getShowZoomControls() {
+ return this.showZoomControls;
+ }
+
+ private InAppBrowser getInAppBrowser(){
+ return this;
+ }
+
+ /**
+ * Display a new browser with the specified URL.
+ *
+ * @param url The url to load.
+ * @param jsonObject
+ */
+ public String showWebPage(final String url, HashMap<String, Boolean> features) {
+ // Determine if we should hide the location bar.
+ showLocationBar = true;
+ showZoomControls = true;
+ openWindowHidden = false;
+ if (features != null) {
+ Boolean show = features.get(LOCATION);
+ if (show != null) {
+ showLocationBar = show.booleanValue();
+ }
+ Boolean zoom = features.get(ZOOM);
+ if (zoom != null) {
+ showZoomControls = zoom.booleanValue();
+ }
+ Boolean hidden = features.get(HIDDEN);
+ if (hidden != null) {
+ openWindowHidden = hidden.booleanValue();
+ }
+ Boolean cache = features.get(CLEAR_ALL_CACHE);
+ if (cache != null) {
+ clearAllCache = cache.booleanValue();
+ } else {
+ cache = features.get(CLEAR_SESSION_CACHE);
+ if (cache != null) {
+ clearSessionCache = cache.booleanValue();
+ }
+ }
+ }
+
+ final CordovaWebView thatWebView = this.webView;
+
+ // Create dialog in new thread
+ Runnable runnable = new Runnable() {
+ /**
+ * Convert our DIP units to Pixels
+ *
+ * @return int
+ */
+ private int dpToPixels(int dipValue) {
+ int value = (int) TypedValue.applyDimension( TypedValue.COMPLEX_UNIT_DIP,
+ (float) dipValue,
+ cordova.getActivity().getResources().getDisplayMetrics()
+ );
+
+ return value;
+ }
+
+ @SuppressLint("NewApi")
+ public void run() {
+ // Let's create the main dialog
+ dialog = new InAppBrowserDialog(cordova.getActivity(), android.R.style.Theme_NoTitleBar);
+ dialog.getWindow().getAttributes().windowAnimations = android.R.style.Animation_Dialog;
+ dialog.requestWindowFeature(Window.FEATURE_NO_TITLE);
+ dialog.setCancelable(true);
+ dialog.setInAppBroswer(getInAppBrowser());
+
+ // Main container layout
+ LinearLayout main = new LinearLayout(cordova.getActivity());
+ main.setOrientation(LinearLayout.VERTICAL);
+
+ // Toolbar layout
+ RelativeLayout toolbar = new RelativeLayout(cordova.getActivity());
+ //Please, no more black!
+ toolbar.setBackgroundColor(android.graphics.Color.LTGRAY);
+ toolbar.setLayoutParams(new RelativeLayout.LayoutParams(LayoutParams.MATCH_PARENT, this.dpToPixels(44)));
+ toolbar.setPadding(this.dpToPixels(2), this.dpToPixels(2), this.dpToPixels(2), this.dpToPixels(2));
+ toolbar.setHorizontalGravity(Gravity.LEFT);
+ toolbar.setVerticalGravity(Gravity.TOP);
+
+ // Action Button Container layout
+ RelativeLayout actionButtonContainer = new RelativeLayout(cordova.getActivity());
+ actionButtonContainer.setLayoutParams(new RelativeLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
+ actionButtonContainer.setHorizontalGravity(Gravity.LEFT);
+ actionButtonContainer.setVerticalGravity(Gravity.CENTER_VERTICAL);
+ actionButtonContainer.setId(1);
+
+ // Back button
+ Button back = new Button(cordova.getActivity());
+ RelativeLayout.LayoutParams backLayoutParams = new RelativeLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT);
+ backLayoutParams.addRule(RelativeLayout.ALIGN_LEFT);
+ back.setLayoutParams(backLayoutParams);
+ back.setContentDescription("Back Button");
+ back.setId(2);
+ Resources activityRes = cordova.getActivity().getResources();
+ int backResId = activityRes.getIdentifier("ic_action_previous_item", "drawable", cordova.getActivity().getPackageName());
+ Drawable backIcon = activityRes.getDrawable(backResId);
+ if(android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.JELLY_BEAN)
+ {
+ back.setBackgroundDrawable(backIcon);
+ }
+ else
+ {
+ back.setBackground(backIcon);
+ }
+
+ back.setOnClickListener(new View.OnClickListener() {
+ public void onClick(View v) {
+ goBack();
+ }
+ });
+
+ // Forward button
+ Button forward = new Button(cordova.getActivity());
+ RelativeLayout.LayoutParams forwardLayoutParams = new RelativeLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT);
+ forwardLayoutParams.addRule(RelativeLayout.RIGHT_OF, 2);
+ forward.setLayoutParams(forwardLayoutParams);
+ forward.setContentDescription("Forward Button");
+ forward.setId(3);
+ int fwdResId = activityRes.getIdentifier("ic_action_next_item", "drawable", cordova.getActivity().getPackageName());
+ Drawable fwdIcon = activityRes.getDrawable(fwdResId);
+ if(android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.JELLY_BEAN)
+ {
+ forward.setBackgroundDrawable(fwdIcon);
+ }
+ else
+ {
+ forward.setBackground(fwdIcon);
+ }
+ forward.setOnClickListener(new View.OnClickListener() {
+ public void onClick(View v) {
+ goForward();
+ }
+ });
+
+ // Edit Text Box
+ edittext = new EditText(cordova.getActivity());
+ RelativeLayout.LayoutParams textLayoutParams = new RelativeLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
+ textLayoutParams.addRule(RelativeLayout.RIGHT_OF, 1);
+ textLayoutParams.addRule(RelativeLayout.LEFT_OF, 5);
+ edittext.setLayoutParams(textLayoutParams);
+ edittext.setId(4);
+ edittext.setSingleLine(true);
+ edittext.setText(url);
+ edittext.setInputType(InputType.TYPE_TEXT_VARIATION_URI);
+ edittext.setImeOptions(EditorInfo.IME_ACTION_GO);
+ edittext.setInputType(InputType.TYPE_NULL); // Will not except input... Makes the text NON-EDITABLE
+ edittext.setOnKeyListener(new View.OnKeyListener() {
+ public boolean onKey(View v, int keyCode, KeyEvent event) {
+ // If the event is a key-down event on the "enter" button
+ if ((event.getAction() == KeyEvent.ACTION_DOWN) && (keyCode == KeyEvent.KEYCODE_ENTER)) {
+ navigate(edittext.getText().toString());
+ return true;
+ }
+ return false;
+ }
+ });
+
+ // Close/Done button
+ Button close = new Button(cordova.getActivity());
+ RelativeLayout.LayoutParams closeLayoutParams = new RelativeLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT);
+ closeLayoutParams.addRule(RelativeLayout.ALIGN_PARENT_RIGHT);
+ close.setLayoutParams(closeLayoutParams);
+ forward.setContentDescription("Close Button");
+ close.setId(5);
+ int closeResId = activityRes.getIdentifier("ic_action_remove", "drawable", cordova.getActivity().getPackageName());
+ Drawable closeIcon = activityRes.getDrawable(closeResId);
+ if(android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.JELLY_BEAN)
+ {
+ close.setBackgroundDrawable(closeIcon);
+ }
+ else
+ {
+ close.setBackground(closeIcon);
+ }
+ close.setOnClickListener(new View.OnClickListener() {
+ public void onClick(View v) {
+ closeDialog();
+ }
+ });
+
+ // WebView
+ inAppWebView = new AmazonWebView(cordova.getActivity());
+
+ CordovaActivity app = (CordovaActivity) cordova.getActivity();
+ cordova.getFactory().initializeWebView(inAppWebView, 0x00FF00, false, null);
+
+ inAppWebView.setLayoutParams(new LinearLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
+ inAppWebView.setWebChromeClient(new InAppChromeClient(thatWebView));
+ AmazonWebViewClient client = new InAppBrowserClient(thatWebView, edittext);
+ inAppWebView.setWebViewClient(client);
+ AmazonWebSettings settings = inAppWebView.getSettings();
+ settings.setJavaScriptEnabled(true);
+ settings.setJavaScriptCanOpenWindowsAutomatically(true);
+ settings.setBuiltInZoomControls(getShowZoomControls());
+ settings.setPluginState(com.amazon.android.webkit.AmazonWebSettings.PluginState.ON);
+
+ //Toggle whether this is enabled or not!
+ Bundle appSettings = cordova.getActivity().getIntent().getExtras();
+ boolean enableDatabase = appSettings == null ? true : appSettings.getBoolean("InAppBrowserStorageEnabled", true);
+ if (enableDatabase) {
+ String databasePath = cordova.getActivity().getApplicationContext().getDir("inAppBrowserDB", Context.MODE_PRIVATE).getPath();
+ settings.setDatabasePath(databasePath);
+ settings.setDatabaseEnabled(true);
+ }
+ settings.setDomStorageEnabled(true);
+
+ if (clearAllCache) {
+ AmazonCookieManager.getInstance().removeAllCookie();
+ } else if (clearSessionCache) {
+ AmazonCookieManager.getInstance().removeSessionCookie();
+ }
+
+ inAppWebView.loadUrl(url);
+ inAppWebView.setId(6);
+ inAppWebView.getSettings().setLoadWithOverviewMode(true);
+ inAppWebView.getSettings().setUseWideViewPort(true);
+ inAppWebView.requestFocus();
+ inAppWebView.requestFocusFromTouch();
+
+ // Add the back and forward buttons to our action button container layout
+ actionButtonContainer.addView(back);
+ actionButtonContainer.addView(forward);
+
+ // Add the views to our toolbar
+ toolbar.addView(actionButtonContainer);
+ toolbar.addView(edittext);
+ toolbar.addView(close);
+
+ // Don't add the toolbar if its been disabled
+ if (getShowLocationBar()) {
+ // Add our toolbar to our main view/layout
+ main.addView(toolbar);
+ }
+
+ // Add our webview to our main view/layout
+ main.addView(inAppWebView);
+
+ WindowManager.LayoutParams lp = new WindowManager.LayoutParams();
+ lp.copyFrom(dialog.getWindow().getAttributes());
+ lp.width = WindowManager.LayoutParams.MATCH_PARENT;
+ lp.height = WindowManager.LayoutParams.MATCH_PARENT;
+
+ dialog.setContentView(main);
+ dialog.show();
+ dialog.getWindow().setAttributes(lp);
+ // the goal of openhidden is to load the url and not display it
+ // Show() needs to be called to cause the URL to be loaded
+ if(openWindowHidden) {
+ dialog.hide();
+ }
+ }
+ };
+ this.cordova.getActivity().runOnUiThread(runnable);
+ return "";
+ }
+
+ /**
+ * Create a new plugin success result and send it back to JavaScript
+ *
+ * @param obj a JSONObject contain event payload information
+ */
+ private void sendUpdate(JSONObject obj, boolean keepCallback) {
+ sendUpdate(obj, keepCallback, PluginResult.Status.OK);
+ }
+
+ /**
+ * Create a new plugin result and send it back to JavaScript
+ *
+ * @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) {
+ if (callbackContext != null) {
+ PluginResult result = new PluginResult(status, obj);
+ result.setKeepCallback(keepCallback);
+ callbackContext.sendPluginResult(result);
+ if (!keepCallback) {
+ callbackContext = null;
+ }
+ }
+ }
+
+ /**
+ * The webview client receives notifications about appView
+ */
+ public class InAppBrowserClient extends AmazonWebViewClient {
+ EditText edittext;
+ CordovaWebView webView;
+
+ /**
+ * Constructor.
+ *
+ * @param mContext
+ * @param edittext
+ */
+ public InAppBrowserClient(CordovaWebView webView, EditText mEditText) {
+ this.webView = webView;
+ this.edittext = mEditText;
+ }
+
+ /**
+ * Notify the host application that a page has started loading.
+ *
+ * @param view The webview initiating the callback.
+ * @param url The url of the page.
+ */
+ @Override
+ public void onPageStarted(AmazonWebView view, String url, Bitmap favicon) {
+ super.onPageStarted(view, url, favicon);
+ String newloc = "";
+ if (url.startsWith("http:") || url.startsWith("https:") || url.startsWith("file:")) {
+ newloc = url;
+ }
+ // If dialing phone (tel:5551212)
+ else if (url.startsWith(AmazonWebView.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());
+ }
+ }
+
+ else if (url.startsWith("geo:") || url.startsWith(AmazonWebView.SCHEME_MAILTO) || url.startsWith("market:")) {
+ try {
+ Intent intent = new Intent(Intent.ACTION_VIEW);
+ intent.setData(Uri.parse(url));
+ cordova.getActivity().startActivity(intent);
+ } catch (android.content.ActivityNotFoundException e) {
+ LOG.e(LOG_TAG, "Error with " + url + ": " + e.toString());
+ }
+ }
+ // If sms:5551212?body=This is the message
+ else if (url.startsWith("sms:")) {
+ try {
+ Intent intent = new Intent(Intent.ACTION_VIEW);
+
+ // Get address
+ String address = null;
+ int parmIndex = url.indexOf('?');
+ if (parmIndex == -1) {
+ address = url.substring(4);
+ }
+ else {
+ address = url.substring(4, parmIndex);
+
+ // If body, then set sms body
+ Uri uri = Uri.parse(url);
+ String query = uri.getQuery();
+ if (query != null) {
+ if (query.startsWith("body=")) {
+ intent.putExtra("sms_body", query.substring(5));
+ }
+ }
+ }
+ intent.setData(Uri.parse("sms:" + address));
+ intent.putExtra("address", address);
+ intent.setType("vnd.android-dir/mms-sms");
+ cordova.getActivity().startActivity(intent);
+ } catch (android.content.ActivityNotFoundException e) {
+ LOG.e(LOG_TAG, "Error sending sms " + url + ":" + e.toString());
+ }
+ }
+ else {
+ newloc = "http://" + url;
+ }
+
+ if (!newloc.equals(edittext.getText().toString())) {
+ edittext.setText(newloc);
+ }
+
+ try {
+ JSONObject obj = new JSONObject();
+ obj.put("type", LOAD_START_EVENT);
+ obj.put("url", newloc);
+
+ sendUpdate(obj, true);
+ } catch (JSONException ex) {
+ Log.d(LOG_TAG, "Should never happen");
+ }
+ }
+
+ public void onPageFinished(AmazonWebView view, String url) {
+ super.onPageFinished(view, url);
+
+ try {
+ JSONObject obj = new JSONObject();
+ obj.put("type", LOAD_STOP_EVENT);
+ obj.put("url", url);
+
+ sendUpdate(obj, true);
+ } catch (JSONException ex) {
+ Log.d(LOG_TAG, "Should never happen");
+ }
+ }
+
+ public void onReceivedError(AmazonWebView view, int errorCode, String description, String failingUrl) {
+ super.onReceivedError(view, errorCode, description, failingUrl);
+
+ try {
+ JSONObject obj = new JSONObject();
+ obj.put("type", LOAD_ERROR_EVENT);
+ obj.put("url", failingUrl);
+ obj.put("code", errorCode);
+ obj.put("message", description);
+
+ sendUpdate(obj, true, PluginResult.Status.ERROR);
+ } catch (JSONException ex) {
+ Log.d(LOG_TAG, "Should never happen");
+ }
+ }
+ }
+}
diff --git a/plugins/cordova-plugin-inappbrowser/src/amazon/InAppChromeClient.java b/plugins/cordova-plugin-inappbrowser/src/amazon/InAppChromeClient.java
new file mode 100644
index 00000000..37cf101f
--- /dev/null
+++ b/plugins/cordova-plugin-inappbrowser/src/amazon/InAppChromeClient.java
@@ -0,0 +1,146 @@
+/*
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied. See the License for the
+ specific language governing permissions and limitations
+ under the License.
+*/
+package org.apache.cordova.inappbrowser;
+
+import org.apache.cordova.CordovaWebView;
+import org.apache.cordova.LOG;
+import org.apache.cordova.PluginResult;
+import org.json.JSONArray;
+import org.json.JSONException;
+
+import com.amazon.android.webkit.AmazonWebChromeClient;
+import com.amazon.android.webkit.AmazonGeolocationPermissions.Callback;
+import com.amazon.android.webkit.AmazonJsPromptResult;
+import com.amazon.android.webkit.AmazonWebStorage;
+import com.amazon.android.webkit.AmazonWebView;
+import com.amazon.android.webkit.AmazonWebViewClient;
+
+public class InAppChromeClient extends AmazonWebChromeClient {
+
+ private CordovaWebView webView;
+ private String LOG_TAG = "InAppChromeClient";
+ private long MAX_QUOTA = 100 * 1024 * 1024;
+
+ public InAppChromeClient(CordovaWebView webView) {
+ super();
+ this.webView = webView;
+ }
+ /**
+ * Handle database quota exceeded notification.
+ *
+ * @param url
+ * @param databaseIdentifier
+ * @param currentQuota
+ * @param estimatedSize
+ * @param totalUsedQuota
+ * @param quotaUpdater
+ */
+ @Override
+ public void onExceededDatabaseQuota(String url, String databaseIdentifier, long currentQuota, long estimatedSize,
+ long totalUsedQuota, AmazonWebStorage.QuotaUpdater quotaUpdater)
+ {
+ LOG.d(LOG_TAG, "onExceededDatabaseQuota estimatedSize: %d currentQuota: %d totalUsedQuota: %d", estimatedSize, currentQuota, totalUsedQuota);
+
+ if (estimatedSize < MAX_QUOTA)
+ {
+ //increase for 1Mb
+ long newQuota = estimatedSize;
+ LOG.d(LOG_TAG, "calling quotaUpdater.updateQuota newQuota: %d", newQuota);
+ quotaUpdater.updateQuota(newQuota);
+ }
+ else
+ {
+ // Set the quota to whatever it is and force an error
+ // TODO: get docs on how to handle this properly
+ quotaUpdater.updateQuota(currentQuota);
+ }
+ }
+
+ /**
+ * Instructs the client to show a prompt to ask the user to set the Geolocation permission state for the specified origin.
+ *
+ * @param origin
+ * @param callback
+ */
+ @Override
+ public void onGeolocationPermissionsShowPrompt(String origin, Callback callback) {
+ super.onGeolocationPermissionsShowPrompt(origin, callback);
+ callback.invoke(origin, true, false);
+ }
+
+ /**
+ * Tell the client to display a prompt dialog to the user.
+ * If the client returns true, WebView will assume that the client will
+ * handle the prompt dialog and call the appropriate JsPromptResult method.
+ *
+ * The prompt bridge provided for the InAppBrowser is capable of executing any
+ * oustanding callback belonging to the InAppBrowser plugin. Care has been
+ * taken that other callbacks cannot be triggered, and that no other code
+ * execution is possible.
+ *
+ * To trigger the bridge, the prompt default value should be of the form:
+ *
+ * gap-iab://<callbackId>
+ *
+ * where <callbackId> is the string id of the callback to trigger (something
+ * like "InAppBrowser0123456789")
+ *
+ * If present, the prompt message is expected to be a JSON-encoded value to
+ * pass to the callback. A JSON_EXCEPTION is returned if the JSON is invalid.
+ *
+ * @param view
+ * @param url
+ * @param message
+ * @param defaultValue
+ * @param result
+ */
+ @Override
+ public boolean onJsPrompt(AmazonWebView view, String url, String message, String defaultValue, AmazonJsPromptResult result) {
+ // See if the prompt string uses the 'gap-iab' protocol. If so, the remainder should be the id of a callback to execute.
+ if (defaultValue != null && defaultValue.startsWith("gap")) {
+ if(defaultValue.startsWith("gap-iab://")) {
+ PluginResult scriptResult;
+ String scriptCallbackId = defaultValue.substring(10);
+ if (scriptCallbackId.startsWith("InAppBrowser")) {
+ if(message == null || message.length() == 0) {
+ scriptResult = new PluginResult(PluginResult.Status.OK, new JSONArray());
+ } else {
+ try {
+ scriptResult = new PluginResult(PluginResult.Status.OK, new JSONArray(message));
+ } catch(JSONException e) {
+ scriptResult = new PluginResult(PluginResult.Status.JSON_EXCEPTION, e.getMessage());
+ }
+ }
+ this.webView.sendPluginResult(scriptResult, scriptCallbackId);
+ result.confirm("");
+ return true;
+ }
+ }
+ else
+ {
+ // Anything else with a gap: prefix should get this message
+ LOG.w(LOG_TAG, "InAppBrowser does not support Cordova API calls: " + url + " " + defaultValue);
+ result.cancel();
+ return true;
+ }
+ }
+ return false;
+ }
+
+}
diff --git a/plugins/cordova-plugin-inappbrowser/src/android/InAppBrowser.java b/plugins/cordova-plugin-inappbrowser/src/android/InAppBrowser.java
new file mode 100644
index 00000000..217e48e1
--- /dev/null
+++ b/plugins/cordova-plugin-inappbrowser/src/android/InAppBrowser.java
@@ -0,0 +1,879 @@
+/*
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied. See the License for the
+ specific language governing permissions and limitations
+ under the License.
+*/
+package org.apache.cordova.inappbrowser;
+
+import android.annotation.SuppressLint;
+import org.apache.cordova.inappbrowser.InAppBrowserDialog;
+import android.content.Context;
+import android.content.Intent;
+import android.provider.Browser;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.drawable.Drawable;
+import android.net.Uri;
+import android.os.Build;
+import android.os.Bundle;
+import android.text.InputType;
+import android.util.Log;
+import android.util.TypedValue;
+import android.view.Gravity;
+import android.view.KeyEvent;
+import android.view.View;
+import android.view.Window;
+import android.view.WindowManager;
+import android.view.WindowManager.LayoutParams;
+import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.InputMethodManager;
+import android.webkit.CookieManager;
+import android.webkit.WebSettings;
+import android.webkit.WebView;
+import android.webkit.WebViewClient;
+import android.widget.Button;
+import android.widget.EditText;
+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.PluginManager;
+import org.apache.cordova.PluginResult;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.HashMap;
+import java.util.StringTokenizer;
+
+@SuppressLint("SetJavaScriptEnabled")
+public class InAppBrowser extends CordovaPlugin {
+
+ private static final String NULL = "null";
+ protected static final String LOG_TAG = "InAppBrowser";
+ private static final String SELF = "_self";
+ private static final String SYSTEM = "_system";
+ // private static final String BLANK = "_blank";
+ private static final String EXIT_EVENT = "exit";
+ private static final String LOCATION = "location";
+ private static final String ZOOM = "zoom";
+ private static final String HIDDEN = "hidden";
+ private static final String LOAD_START_EVENT = "loadstart";
+ private static final String LOAD_STOP_EVENT = "loadstop";
+ private static final String LOAD_ERROR_EVENT = "loaderror";
+ private static final String CLEAR_ALL_CACHE = "clearcache";
+ private static final String CLEAR_SESSION_CACHE = "clearsessioncache";
+ private static final String HARDWARE_BACK_BUTTON = "hardwareback";
+
+ private InAppBrowserDialog dialog;
+ private WebView inAppWebView;
+ private EditText edittext;
+ private CallbackContext callbackContext;
+ private boolean showLocationBar = true;
+ private boolean showZoomControls = true;
+ private boolean openWindowHidden = false;
+ private boolean clearAllCache= false;
+ private boolean clearSessionCache=false;
+ private boolean hadwareBackButton=true;
+
+ /**
+ * Executes the request and returns PluginResult.
+ *
+ * @param action The action to execute.
+ * @param args JSONArry of arguments for the plugin.
+ * @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, 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");
+ /* This code exists for compatibility between 3.x and 4.x versions of Cordova.
+ * Previously the Config class had a static method, isUrlWhitelisted(). That
+ * responsibility has been moved to the plugins, with an aggregating method in
+ * PluginManager.
+ */
+ Boolean shouldAllowNavigation = null;
+ if (url.startsWith("javascript:")) {
+ shouldAllowNavigation = true;
+ }
+ if (shouldAllowNavigation == null) {
+ try {
+ Method iuw = Config.class.getMethod("isUrlWhiteListed", String.class);
+ shouldAllowNavigation = (Boolean)iuw.invoke(null, url);
+ } catch (NoSuchMethodException e) {
+ } catch (IllegalAccessException e) {
+ } catch (InvocationTargetException e) {
+ }
+ }
+ if (shouldAllowNavigation == null) {
+ try {
+ Method gpm = webView.getClass().getMethod("getPluginManager");
+ PluginManager pm = (PluginManager)gpm.invoke(webView);
+ Method san = pm.getClass().getMethod("shouldAllowNavigation", String.class);
+ shouldAllowNavigation = (Boolean)san.invoke(pm, url);
+ } catch (NoSuchMethodException e) {
+ } catch (IllegalAccessException e) {
+ } catch (InvocationTargetException e) {
+ }
+ }
+ // load in webview
+ if (Boolean.TRUE.equals(shouldAllowNavigation)) {
+ Log.d(LOG_TAG, "loading in webview");
+ webView.loadUrl(url);
+ }
+ //Load the dialer
+ else if (url.startsWith(WebView.SCHEME_TEL))
+ {
+ try {
+ Log.d(LOG_TAG, "loading in dialer");
+ 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 {
+ Log.d(LOG_TAG, "loading in InAppBrowser");
+ result = showWebPage(url, features);
+ }
+ }
+ // SYSTEM
+ else if (SYSTEM.equals(target)) {
+ Log.d(LOG_TAG, "in system");
+ result = openExternal(url);
+ }
+ // BLANK - or anything else
+ else {
+ Log.d(LOG_TAG, "in blank");
+ result = showWebPage(url, features);
+ }
+
+ PluginResult pluginResult = new PluginResult(PluginResult.Status.OK, result);
+ pluginResult.setKeepCallback(true);
+ callbackContext.sendPluginResult(pluginResult);
+ }
+ });
+ }
+ 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();
+ }
+ });
+ 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
+ * provides a consistent method for injecting JavaScript code into the document.
+ *
+ * If a wrapper string is supplied, then the source string will be JSON-encoded (adding
+ * quotes) and wrapped using string formatting. (The wrapper string should have a single
+ * '%s' marker)
+ *
+ * @param source The source object (filename or script/style text) to inject into
+ * the document.
+ * @param jsWrapper A JavaScript string to wrap the source string in, so that the object
+ * is properly injected, or null if the source string is JavaScript text
+ * which should be executed directly.
+ */
+ private void injectDeferredObject(String source, String jsWrapper) {
+ String scriptToInject;
+ if (jsWrapper != null) {
+ org.json.JSONArray jsonEsc = new org.json.JSONArray();
+ jsonEsc.put(source);
+ String jsonRepr = jsonEsc.toString();
+ String jsonSourceString = jsonRepr.substring(1, jsonRepr.length()-1);
+ scriptToInject = String.format(jsWrapper, jsonSourceString);
+ } else {
+ scriptToInject = source;
+ }
+ final String finalScriptToInject = scriptToInject;
+ this.cordova.getActivity().runOnUiThread(new Runnable() {
+ @SuppressLint("NewApi")
+ @Override
+ public void run() {
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
+ // This action will have the side-effect of blurring the currently focused element
+ inAppWebView.loadUrl("javascript:" + finalScriptToInject);
+ } else {
+ inAppWebView.evaluateJavascript(finalScriptToInject, null);
+ }
+ }
+ });
+ }
+
+ /**
+ * Put the list of features into a hash map
+ *
+ * @param optString
+ * @return
+ */
+ private HashMap<String, Boolean> parseFeature(String optString) {
+ if (optString.equals(NULL)) {
+ return null;
+ } else {
+ HashMap<String, Boolean> map = new HashMap<String, Boolean>();
+ StringTokenizer features = new StringTokenizer(optString, ",");
+ StringTokenizer option;
+ while(features.hasMoreElements()) {
+ option = new StringTokenizer(features.nextToken(), "=");
+ if (option.hasMoreElements()) {
+ String key = option.nextToken();
+ Boolean value = option.nextToken().equals("no") ? Boolean.FALSE : Boolean.TRUE;
+ map.put(key, value);
+ }
+ }
+ return map;
+ }
+ }
+
+ /**
+ * Display a new browser with the specified URL.
+ *
+ * @param url The url to load.
+ * @param usePhoneGap Load url in PhoneGap webview
+ * @return "" if ok, or error message.
+ */
+ public String openExternal(String url) {
+ try {
+ Intent intent = null;
+ intent = new Intent(Intent.ACTION_VIEW);
+ // Omitting the MIME type for file: URLs causes "No Activity found to handle Intent".
+ // Adding the MIME type to http: URLs causes them to not be handled by the downloader.
+ Uri uri = Uri.parse(url);
+ if ("file".equals(uri.getScheme())) {
+ intent.setDataAndType(uri, webView.getResourceApi().getMimeType(uri));
+ } else {
+ intent.setData(uri);
+ }
+ intent.putExtra(Browser.EXTRA_APPLICATION_ID, cordova.getActivity().getPackageName());
+ this.cordova.getActivity().startActivity(intent);
+ return "";
+ } catch (android.content.ActivityNotFoundException e) {
+ Log.d(LOG_TAG, "InAppBrowser: Error loading url "+url+":"+ e.toString());
+ return e.toString();
+ }
+ }
+
+ /**
+ * Closes the dialog
+ */
+ 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.setWebViewClient(new WebViewClient() {
+ // NB: wait for about:blank before dismissing
+ public void onPageFinished(WebView view, String url) {
+ if (dialog != null) {
+ dialog.dismiss();
+ }
+ }
+ });
+ // NB: From SDK 19: "If you call methods on WebView from any thread
+ // other than your app's UI thread, it can cause unexpected results."
+ // http://developer.android.com/guide/webapps/migrating.html#Threads
+ childView.loadUrl("about:blank");
+ }
+ });
+
+ try {
+ JSONObject obj = new JSONObject();
+ obj.put("type", EXIT_EVENT);
+ sendUpdate(obj, false);
+ } catch (JSONException ex) {
+ Log.d(LOG_TAG, "Should never happen");
+ }
+ }
+
+ /**
+ * Checks to see if it is possible to go back one page in history, then does so.
+ */
+ public void goBack() {
+ if (this.inAppWebView.canGoBack()) {
+ this.inAppWebView.goBack();
+ }
+ }
+
+ /**
+ * Can the web browser go back?
+ * @return boolean
+ */
+ public boolean canGoBack() {
+ return this.inAppWebView.canGoBack();
+ }
+
+ /**
+ * Has the user set the hardware back button to go back
+ * @return boolean
+ */
+ public boolean hardwareBack() {
+ return hadwareBackButton;
+ }
+
+ /**
+ * Checks to see if it is possible to go forward one page in history, then does so.
+ */
+ private void goForward() {
+ if (this.inAppWebView.canGoForward()) {
+ this.inAppWebView.goForward();
+ }
+ }
+
+ /**
+ * Navigate to the new page
+ *
+ * @param url to load
+ */
+ private void navigate(String url) {
+ InputMethodManager imm = (InputMethodManager)this.cordova.getActivity().getSystemService(Context.INPUT_METHOD_SERVICE);
+ imm.hideSoftInputFromWindow(edittext.getWindowToken(), 0);
+
+ if (!url.startsWith("http") && !url.startsWith("file:")) {
+ this.inAppWebView.loadUrl("http://" + url);
+ } else {
+ this.inAppWebView.loadUrl(url);
+ }
+ this.inAppWebView.requestFocus();
+ }
+
+
+ /**
+ * Should we show the location bar?
+ *
+ * @return boolean
+ */
+ private boolean getShowLocationBar() {
+ return this.showLocationBar;
+ }
+
+ /**
+ * Should we show the zoom controls?
+ *
+ * @return boolean
+ */
+ private boolean getShowZoomControls() {
+ return this.showZoomControls;
+ }
+
+ private InAppBrowser getInAppBrowser(){
+ return this;
+ }
+
+ /**
+ * Display a new browser with the specified URL.
+ *
+ * @param url The url to load.
+ * @param jsonObject
+ */
+ public String showWebPage(final String url, HashMap<String, Boolean> features) {
+ // Determine if we should hide the location bar.
+ showLocationBar = true;
+ showZoomControls = true;
+ openWindowHidden = false;
+ if (features != null) {
+ Boolean show = features.get(LOCATION);
+ if (show != null) {
+ showLocationBar = show.booleanValue();
+ }
+ Boolean zoom = features.get(ZOOM);
+ if (zoom != null) {
+ showZoomControls = zoom.booleanValue();
+ }
+ Boolean hidden = features.get(HIDDEN);
+ if (hidden != null) {
+ openWindowHidden = hidden.booleanValue();
+ }
+ Boolean hardwareBack = features.get(HARDWARE_BACK_BUTTON);
+ if (hardwareBack != null) {
+ hadwareBackButton = hardwareBack.booleanValue();
+ }
+ Boolean cache = features.get(CLEAR_ALL_CACHE);
+ if (cache != null) {
+ clearAllCache = cache.booleanValue();
+ } else {
+ cache = features.get(CLEAR_SESSION_CACHE);
+ if (cache != null) {
+ clearSessionCache = cache.booleanValue();
+ }
+ }
+ }
+
+ final CordovaWebView thatWebView = this.webView;
+
+ // Create dialog in new thread
+ Runnable runnable = new Runnable() {
+ /**
+ * Convert our DIP units to Pixels
+ *
+ * @return int
+ */
+ private int dpToPixels(int dipValue) {
+ int value = (int) TypedValue.applyDimension( TypedValue.COMPLEX_UNIT_DIP,
+ (float) dipValue,
+ cordova.getActivity().getResources().getDisplayMetrics()
+ );
+
+ return value;
+ }
+
+ @SuppressLint("NewApi")
+ public void run() {
+ // Let's create the main dialog
+ dialog = new InAppBrowserDialog(cordova.getActivity(), android.R.style.Theme_NoTitleBar);
+ dialog.getWindow().getAttributes().windowAnimations = android.R.style.Animation_Dialog;
+ dialog.requestWindowFeature(Window.FEATURE_NO_TITLE);
+ dialog.setCancelable(true);
+ dialog.setInAppBroswer(getInAppBrowser());
+
+ // Main container layout
+ LinearLayout main = new LinearLayout(cordova.getActivity());
+ main.setOrientation(LinearLayout.VERTICAL);
+
+ // Toolbar layout
+ RelativeLayout toolbar = new RelativeLayout(cordova.getActivity());
+ //Please, no more black!
+ toolbar.setBackgroundColor(android.graphics.Color.LTGRAY);
+ toolbar.setLayoutParams(new RelativeLayout.LayoutParams(LayoutParams.MATCH_PARENT, this.dpToPixels(44)));
+ toolbar.setPadding(this.dpToPixels(2), this.dpToPixels(2), this.dpToPixels(2), this.dpToPixels(2));
+ toolbar.setHorizontalGravity(Gravity.LEFT);
+ toolbar.setVerticalGravity(Gravity.TOP);
+
+ // Action Button Container layout
+ RelativeLayout actionButtonContainer = new RelativeLayout(cordova.getActivity());
+ actionButtonContainer.setLayoutParams(new RelativeLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
+ actionButtonContainer.setHorizontalGravity(Gravity.LEFT);
+ actionButtonContainer.setVerticalGravity(Gravity.CENTER_VERTICAL);
+ actionButtonContainer.setId(1);
+
+ // Back button
+ Button back = new Button(cordova.getActivity());
+ RelativeLayout.LayoutParams backLayoutParams = new RelativeLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT);
+ backLayoutParams.addRule(RelativeLayout.ALIGN_LEFT);
+ back.setLayoutParams(backLayoutParams);
+ back.setContentDescription("Back Button");
+ back.setId(2);
+ Resources activityRes = cordova.getActivity().getResources();
+ int backResId = activityRes.getIdentifier("ic_action_previous_item", "drawable", cordova.getActivity().getPackageName());
+ Drawable backIcon = activityRes.getDrawable(backResId);
+ if(android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.JELLY_BEAN)
+ {
+ back.setBackgroundDrawable(backIcon);
+ }
+ else
+ {
+ back.setBackground(backIcon);
+ }
+ back.setOnClickListener(new View.OnClickListener() {
+ public void onClick(View v) {
+ goBack();
+ }
+ });
+
+ // Forward button
+ Button forward = new Button(cordova.getActivity());
+ RelativeLayout.LayoutParams forwardLayoutParams = new RelativeLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT);
+ forwardLayoutParams.addRule(RelativeLayout.RIGHT_OF, 2);
+ forward.setLayoutParams(forwardLayoutParams);
+ forward.setContentDescription("Forward Button");
+ forward.setId(3);
+ int fwdResId = activityRes.getIdentifier("ic_action_next_item", "drawable", cordova.getActivity().getPackageName());
+ Drawable fwdIcon = activityRes.getDrawable(fwdResId);
+ if(android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.JELLY_BEAN)
+ {
+ forward.setBackgroundDrawable(fwdIcon);
+ }
+ else
+ {
+ forward.setBackground(fwdIcon);
+ }
+ forward.setOnClickListener(new View.OnClickListener() {
+ public void onClick(View v) {
+ goForward();
+ }
+ });
+
+ // Edit Text Box
+ edittext = new EditText(cordova.getActivity());
+ RelativeLayout.LayoutParams textLayoutParams = new RelativeLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
+ textLayoutParams.addRule(RelativeLayout.RIGHT_OF, 1);
+ textLayoutParams.addRule(RelativeLayout.LEFT_OF, 5);
+ edittext.setLayoutParams(textLayoutParams);
+ edittext.setId(4);
+ edittext.setSingleLine(true);
+ edittext.setText(url);
+ edittext.setInputType(InputType.TYPE_TEXT_VARIATION_URI);
+ edittext.setImeOptions(EditorInfo.IME_ACTION_GO);
+ edittext.setInputType(InputType.TYPE_NULL); // Will not except input... Makes the text NON-EDITABLE
+ edittext.setOnKeyListener(new View.OnKeyListener() {
+ public boolean onKey(View v, int keyCode, KeyEvent event) {
+ // If the event is a key-down event on the "enter" button
+ if ((event.getAction() == KeyEvent.ACTION_DOWN) && (keyCode == KeyEvent.KEYCODE_ENTER)) {
+ navigate(edittext.getText().toString());
+ return true;
+ }
+ return false;
+ }
+ });
+
+ // Close/Done button
+ Button close = new Button(cordova.getActivity());
+ RelativeLayout.LayoutParams closeLayoutParams = new RelativeLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT);
+ closeLayoutParams.addRule(RelativeLayout.ALIGN_PARENT_RIGHT);
+ close.setLayoutParams(closeLayoutParams);
+ forward.setContentDescription("Close Button");
+ close.setId(5);
+ int closeResId = activityRes.getIdentifier("ic_action_remove", "drawable", cordova.getActivity().getPackageName());
+ Drawable closeIcon = activityRes.getDrawable(closeResId);
+ if(android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.JELLY_BEAN)
+ {
+ close.setBackgroundDrawable(closeIcon);
+ }
+ else
+ {
+ close.setBackground(closeIcon);
+ }
+ close.setOnClickListener(new View.OnClickListener() {
+ public void onClick(View v) {
+ closeDialog();
+ }
+ });
+
+ // WebView
+ inAppWebView = new WebView(cordova.getActivity());
+ inAppWebView.setLayoutParams(new LinearLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
+ inAppWebView.setWebChromeClient(new InAppChromeClient(thatWebView));
+ WebViewClient client = new InAppBrowserClient(thatWebView, edittext);
+ inAppWebView.setWebViewClient(client);
+ WebSettings settings = inAppWebView.getSettings();
+ settings.setJavaScriptEnabled(true);
+ settings.setJavaScriptCanOpenWindowsAutomatically(true);
+ settings.setBuiltInZoomControls(getShowZoomControls());
+ settings.setPluginState(android.webkit.WebSettings.PluginState.ON);
+
+ //Toggle whether this is enabled or not!
+ Bundle appSettings = cordova.getActivity().getIntent().getExtras();
+ boolean enableDatabase = appSettings == null ? true : appSettings.getBoolean("InAppBrowserStorageEnabled", true);
+ if (enableDatabase) {
+ String databasePath = cordova.getActivity().getApplicationContext().getDir("inAppBrowserDB", Context.MODE_PRIVATE).getPath();
+ settings.setDatabasePath(databasePath);
+ settings.setDatabaseEnabled(true);
+ }
+ settings.setDomStorageEnabled(true);
+
+ if (clearAllCache) {
+ CookieManager.getInstance().removeAllCookie();
+ } else if (clearSessionCache) {
+ CookieManager.getInstance().removeSessionCookie();
+ }
+
+ inAppWebView.loadUrl(url);
+ inAppWebView.setId(6);
+ inAppWebView.getSettings().setLoadWithOverviewMode(true);
+ inAppWebView.getSettings().setUseWideViewPort(true);
+ inAppWebView.requestFocus();
+ inAppWebView.requestFocusFromTouch();
+
+ // Add the back and forward buttons to our action button container layout
+ actionButtonContainer.addView(back);
+ actionButtonContainer.addView(forward);
+
+ // Add the views to our toolbar
+ toolbar.addView(actionButtonContainer);
+ toolbar.addView(edittext);
+ toolbar.addView(close);
+
+ // Don't add the toolbar if its been disabled
+ if (getShowLocationBar()) {
+ // Add our toolbar to our main view/layout
+ main.addView(toolbar);
+ }
+
+ // Add our webview to our main view/layout
+ main.addView(inAppWebView);
+
+ WindowManager.LayoutParams lp = new WindowManager.LayoutParams();
+ lp.copyFrom(dialog.getWindow().getAttributes());
+ lp.width = WindowManager.LayoutParams.MATCH_PARENT;
+ lp.height = WindowManager.LayoutParams.MATCH_PARENT;
+
+ dialog.setContentView(main);
+ dialog.show();
+ dialog.getWindow().setAttributes(lp);
+ // the goal of openhidden is to load the url and not display it
+ // Show() needs to be called to cause the URL to be loaded
+ if(openWindowHidden) {
+ dialog.hide();
+ }
+ }
+ };
+ this.cordova.getActivity().runOnUiThread(runnable);
+ return "";
+ }
+
+ /**
+ * Create a new plugin success result and send it back to JavaScript
+ *
+ * @param obj a JSONObject contain event payload information
+ */
+ private void sendUpdate(JSONObject obj, boolean keepCallback) {
+ sendUpdate(obj, keepCallback, PluginResult.Status.OK);
+ }
+
+ /**
+ * Create a new plugin result and send it back to JavaScript
+ *
+ * @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) {
+ if (callbackContext != null) {
+ PluginResult result = new PluginResult(status, obj);
+ result.setKeepCallback(keepCallback);
+ callbackContext.sendPluginResult(result);
+ if (!keepCallback) {
+ callbackContext = null;
+ }
+ }
+ }
+
+ /**
+ * The webview client receives notifications about appView
+ */
+ public class InAppBrowserClient extends WebViewClient {
+ EditText edittext;
+ CordovaWebView webView;
+
+ /**
+ * Constructor.
+ *
+ * @param mContext
+ * @param edittext
+ */
+ public InAppBrowserClient(CordovaWebView webView, EditText mEditText) {
+ this.webView = webView;
+ this.edittext = mEditText;
+ }
+
+ /**
+ * Notify the host application that a page has started loading.
+ *
+ * @param view The webview initiating the callback.
+ * @param url The url of the page.
+ */
+ @Override
+ public void onPageStarted(WebView view, String url, Bitmap favicon) {
+ super.onPageStarted(view, url, favicon);
+ String newloc = "";
+ if (url.startsWith("http:") || url.startsWith("https:") || url.startsWith("file:")) {
+ newloc = url;
+ }
+ // If dialing phone (tel:5551212)
+ 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());
+ }
+ }
+
+ else if (url.startsWith("geo:") || url.startsWith(WebView.SCHEME_MAILTO) || url.startsWith("market:")) {
+ try {
+ Intent intent = new Intent(Intent.ACTION_VIEW);
+ intent.setData(Uri.parse(url));
+ cordova.getActivity().startActivity(intent);
+ } catch (android.content.ActivityNotFoundException e) {
+ LOG.e(LOG_TAG, "Error with " + url + ": " + e.toString());
+ }
+ }
+ // If sms:5551212?body=This is the message
+ else if (url.startsWith("sms:")) {
+ try {
+ Intent intent = new Intent(Intent.ACTION_VIEW);
+
+ // Get address
+ String address = null;
+ int parmIndex = url.indexOf('?');
+ if (parmIndex == -1) {
+ address = url.substring(4);
+ }
+ else {
+ address = url.substring(4, parmIndex);
+
+ // If body, then set sms body
+ Uri uri = Uri.parse(url);
+ String query = uri.getQuery();
+ if (query != null) {
+ if (query.startsWith("body=")) {
+ intent.putExtra("sms_body", query.substring(5));
+ }
+ }
+ }
+ intent.setData(Uri.parse("sms:" + address));
+ intent.putExtra("address", address);
+ intent.setType("vnd.android-dir/mms-sms");
+ cordova.getActivity().startActivity(intent);
+ } catch (android.content.ActivityNotFoundException e) {
+ LOG.e(LOG_TAG, "Error sending sms " + url + ":" + e.toString());
+ }
+ }
+ else {
+ newloc = "http://" + url;
+ }
+
+ if (!newloc.equals(edittext.getText().toString())) {
+ edittext.setText(newloc);
+ }
+
+ try {
+ JSONObject obj = new JSONObject();
+ obj.put("type", LOAD_START_EVENT);
+ obj.put("url", newloc);
+
+ sendUpdate(obj, true);
+ } catch (JSONException ex) {
+ Log.d(LOG_TAG, "Should never happen");
+ }
+ }
+
+ public void onPageFinished(WebView view, String url) {
+ super.onPageFinished(view, url);
+
+ try {
+ JSONObject obj = new JSONObject();
+ obj.put("type", LOAD_STOP_EVENT);
+ obj.put("url", url);
+
+ sendUpdate(obj, true);
+ } catch (JSONException ex) {
+ Log.d(LOG_TAG, "Should never happen");
+ }
+ }
+
+ public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) {
+ super.onReceivedError(view, errorCode, description, failingUrl);
+
+ try {
+ JSONObject obj = new JSONObject();
+ obj.put("type", LOAD_ERROR_EVENT);
+ obj.put("url", failingUrl);
+ obj.put("code", errorCode);
+ obj.put("message", description);
+
+ sendUpdate(obj, true, PluginResult.Status.ERROR);
+ } catch (JSONException ex) {
+ Log.d(LOG_TAG, "Should never happen");
+ }
+ }
+ }
+}
diff --git a/plugins/cordova-plugin-inappbrowser/src/android/InAppBrowserDialog.java b/plugins/cordova-plugin-inappbrowser/src/android/InAppBrowserDialog.java
new file mode 100644
index 00000000..d7017202
--- /dev/null
+++ b/plugins/cordova-plugin-inappbrowser/src/android/InAppBrowserDialog.java
@@ -0,0 +1,58 @@
+/*
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied. See the License for the
+ specific language governing permissions and limitations
+ under the License.
+*/
+package org.apache.cordova.inappbrowser;
+
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.content.Context;
+import android.util.Log;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+/**
+ * Created by Oliver on 22/11/2013.
+ */
+public class InAppBrowserDialog extends Dialog {
+ Context context;
+ InAppBrowser inAppBrowser = null;
+
+ public InAppBrowserDialog(Context context, int theme) {
+ super(context, theme);
+ this.context = context;
+ }
+
+ public void setInAppBroswer(InAppBrowser browser) {
+ this.inAppBrowser = browser;
+ }
+
+ public void onBackPressed () {
+ if (this.inAppBrowser == null) {
+ this.dismiss();
+ } else {
+ // better to go through the in inAppBrowser
+ // because it does a clean up
+ if (this.inAppBrowser.hardwareBack() && this.inAppBrowser.canGoBack()) {
+ this.inAppBrowser.goBack();
+ } else {
+ this.inAppBrowser.closeDialog();
+ }
+ }
+ }
+}
diff --git a/plugins/cordova-plugin-inappbrowser/src/android/InAppChromeClient.java b/plugins/cordova-plugin-inappbrowser/src/android/InAppChromeClient.java
new file mode 100644
index 00000000..a2145e6a
--- /dev/null
+++ b/plugins/cordova-plugin-inappbrowser/src/android/InAppChromeClient.java
@@ -0,0 +1,133 @@
+/*
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied. See the License for the
+ specific language governing permissions and limitations
+ under the License.
+*/
+package org.apache.cordova.inappbrowser;
+
+import org.apache.cordova.CordovaWebView;
+import org.apache.cordova.LOG;
+import org.apache.cordova.PluginResult;
+import org.json.JSONArray;
+import org.json.JSONException;
+
+import android.webkit.JsPromptResult;
+import android.webkit.WebChromeClient;
+import android.webkit.WebStorage;
+import android.webkit.WebView;
+import android.webkit.WebViewClient;
+import android.webkit.GeolocationPermissions.Callback;
+
+public class InAppChromeClient extends WebChromeClient {
+
+ private CordovaWebView webView;
+ private String LOG_TAG = "InAppChromeClient";
+ private long MAX_QUOTA = 100 * 1024 * 1024;
+
+ public InAppChromeClient(CordovaWebView webView) {
+ super();
+ this.webView = webView;
+ }
+ /**
+ * Handle database quota exceeded notification.
+ *
+ * @param url
+ * @param databaseIdentifier
+ * @param currentQuota
+ * @param estimatedSize
+ * @param totalUsedQuota
+ * @param quotaUpdater
+ */
+ @Override
+ public void onExceededDatabaseQuota(String url, String databaseIdentifier, long currentQuota, long estimatedSize,
+ long totalUsedQuota, WebStorage.QuotaUpdater quotaUpdater)
+ {
+ LOG.d(LOG_TAG, "onExceededDatabaseQuota estimatedSize: %d currentQuota: %d totalUsedQuota: %d", estimatedSize, currentQuota, totalUsedQuota);
+ quotaUpdater.updateQuota(MAX_QUOTA);
+ }
+
+ /**
+ * Instructs the client to show a prompt to ask the user to set the Geolocation permission state for the specified origin.
+ *
+ * @param origin
+ * @param callback
+ */
+ @Override
+ public void onGeolocationPermissionsShowPrompt(String origin, Callback callback) {
+ super.onGeolocationPermissionsShowPrompt(origin, callback);
+ callback.invoke(origin, true, false);
+ }
+
+ /**
+ * Tell the client to display a prompt dialog to the user.
+ * If the client returns true, WebView will assume that the client will
+ * handle the prompt dialog and call the appropriate JsPromptResult method.
+ *
+ * The prompt bridge provided for the InAppBrowser is capable of executing any
+ * oustanding callback belonging to the InAppBrowser plugin. Care has been
+ * taken that other callbacks cannot be triggered, and that no other code
+ * execution is possible.
+ *
+ * To trigger the bridge, the prompt default value should be of the form:
+ *
+ * gap-iab://<callbackId>
+ *
+ * where <callbackId> is the string id of the callback to trigger (something
+ * like "InAppBrowser0123456789")
+ *
+ * If present, the prompt message is expected to be a JSON-encoded value to
+ * pass to the callback. A JSON_EXCEPTION is returned if the JSON is invalid.
+ *
+ * @param view
+ * @param url
+ * @param message
+ * @param defaultValue
+ * @param result
+ */
+ @Override
+ public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result) {
+ // See if the prompt string uses the 'gap-iab' protocol. If so, the remainder should be the id of a callback to execute.
+ if (defaultValue != null && defaultValue.startsWith("gap")) {
+ if(defaultValue.startsWith("gap-iab://")) {
+ PluginResult scriptResult;
+ String scriptCallbackId = defaultValue.substring(10);
+ if (scriptCallbackId.startsWith("InAppBrowser")) {
+ if(message == null || message.length() == 0) {
+ scriptResult = new PluginResult(PluginResult.Status.OK, new JSONArray());
+ } else {
+ try {
+ scriptResult = new PluginResult(PluginResult.Status.OK, new JSONArray(message));
+ } catch(JSONException e) {
+ scriptResult = new PluginResult(PluginResult.Status.JSON_EXCEPTION, e.getMessage());
+ }
+ }
+ this.webView.sendPluginResult(scriptResult, scriptCallbackId);
+ result.confirm("");
+ return true;
+ }
+ }
+ else
+ {
+ // Anything else with a gap: prefix should get this message
+ LOG.w(LOG_TAG, "InAppBrowser does not support Cordova API calls: " + url + " " + defaultValue);
+ result.cancel();
+ return true;
+ }
+ }
+ return false;
+ }
+
+}
diff --git a/plugins/cordova-plugin-inappbrowser/src/android/res/drawable-hdpi/ic_action_next_item.png b/plugins/cordova-plugin-inappbrowser/src/android/res/drawable-hdpi/ic_action_next_item.png
new file mode 100644
index 00000000..fa469d88
--- /dev/null
+++ b/plugins/cordova-plugin-inappbrowser/src/android/res/drawable-hdpi/ic_action_next_item.png
Binary files differ
diff --git a/plugins/cordova-plugin-inappbrowser/src/android/res/drawable-hdpi/ic_action_previous_item.png b/plugins/cordova-plugin-inappbrowser/src/android/res/drawable-hdpi/ic_action_previous_item.png
new file mode 100644
index 00000000..e861ecce
--- /dev/null
+++ b/plugins/cordova-plugin-inappbrowser/src/android/res/drawable-hdpi/ic_action_previous_item.png
Binary files differ
diff --git a/plugins/cordova-plugin-inappbrowser/src/android/res/drawable-hdpi/ic_action_remove.png b/plugins/cordova-plugin-inappbrowser/src/android/res/drawable-hdpi/ic_action_remove.png
new file mode 100644
index 00000000..f889617e
--- /dev/null
+++ b/plugins/cordova-plugin-inappbrowser/src/android/res/drawable-hdpi/ic_action_remove.png
Binary files differ
diff --git a/plugins/cordova-plugin-inappbrowser/src/android/res/drawable-mdpi/ic_action_next_item.png b/plugins/cordova-plugin-inappbrowser/src/android/res/drawable-mdpi/ic_action_next_item.png
new file mode 100644
index 00000000..47365a30
--- /dev/null
+++ b/plugins/cordova-plugin-inappbrowser/src/android/res/drawable-mdpi/ic_action_next_item.png
Binary files differ
diff --git a/plugins/cordova-plugin-inappbrowser/src/android/res/drawable-mdpi/ic_action_previous_item.png b/plugins/cordova-plugin-inappbrowser/src/android/res/drawable-mdpi/ic_action_previous_item.png
new file mode 100644
index 00000000..4ad2df42
--- /dev/null
+++ b/plugins/cordova-plugin-inappbrowser/src/android/res/drawable-mdpi/ic_action_previous_item.png
Binary files differ
diff --git a/plugins/cordova-plugin-inappbrowser/src/android/res/drawable-mdpi/ic_action_remove.png b/plugins/cordova-plugin-inappbrowser/src/android/res/drawable-mdpi/ic_action_remove.png
new file mode 100644
index 00000000..e84853e4
--- /dev/null
+++ b/plugins/cordova-plugin-inappbrowser/src/android/res/drawable-mdpi/ic_action_remove.png
Binary files differ
diff --git a/plugins/cordova-plugin-inappbrowser/src/android/res/drawable-xhdpi/ic_action_next_item.png b/plugins/cordova-plugin-inappbrowser/src/android/res/drawable-xhdpi/ic_action_next_item.png
new file mode 100644
index 00000000..5f304742
--- /dev/null
+++ b/plugins/cordova-plugin-inappbrowser/src/android/res/drawable-xhdpi/ic_action_next_item.png
Binary files differ
diff --git a/plugins/cordova-plugin-inappbrowser/src/android/res/drawable-xhdpi/ic_action_previous_item.png b/plugins/cordova-plugin-inappbrowser/src/android/res/drawable-xhdpi/ic_action_previous_item.png
new file mode 100644
index 00000000..ed8ac91d
--- /dev/null
+++ b/plugins/cordova-plugin-inappbrowser/src/android/res/drawable-xhdpi/ic_action_previous_item.png
Binary files differ
diff --git a/plugins/cordova-plugin-inappbrowser/src/android/res/drawable-xhdpi/ic_action_remove.png b/plugins/cordova-plugin-inappbrowser/src/android/res/drawable-xhdpi/ic_action_remove.png
new file mode 100644
index 00000000..4cd0458b
--- /dev/null
+++ b/plugins/cordova-plugin-inappbrowser/src/android/res/drawable-xhdpi/ic_action_remove.png
Binary files differ
diff --git a/plugins/cordova-plugin-inappbrowser/src/android/res/drawable-xxhdpi/ic_action_next_item.png b/plugins/cordova-plugin-inappbrowser/src/android/res/drawable-xxhdpi/ic_action_next_item.png
new file mode 100644
index 00000000..51479d8d
--- /dev/null
+++ b/plugins/cordova-plugin-inappbrowser/src/android/res/drawable-xxhdpi/ic_action_next_item.png
Binary files differ
diff --git a/plugins/cordova-plugin-inappbrowser/src/android/res/drawable-xxhdpi/ic_action_previous_item.png b/plugins/cordova-plugin-inappbrowser/src/android/res/drawable-xxhdpi/ic_action_previous_item.png
new file mode 100644
index 00000000..bc8ff124
--- /dev/null
+++ b/plugins/cordova-plugin-inappbrowser/src/android/res/drawable-xxhdpi/ic_action_previous_item.png
Binary files differ
diff --git a/plugins/cordova-plugin-inappbrowser/src/android/res/drawable-xxhdpi/ic_action_remove.png b/plugins/cordova-plugin-inappbrowser/src/android/res/drawable-xxhdpi/ic_action_remove.png
new file mode 100644
index 00000000..331c545b
--- /dev/null
+++ b/plugins/cordova-plugin-inappbrowser/src/android/res/drawable-xxhdpi/ic_action_remove.png
Binary files differ
diff --git a/plugins/cordova-plugin-inappbrowser/src/blackberry10/README.md b/plugins/cordova-plugin-inappbrowser/src/blackberry10/README.md
new file mode 100644
index 00000000..f0fa8607
--- /dev/null
+++ b/plugins/cordova-plugin-inappbrowser/src/blackberry10/README.md
@@ -0,0 +1,43 @@
+<!---
+ license: Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied. See the License for the
+ specific language governing permissions and limitations
+ under the License.
+-->
+# BlackBerry 10 In-App-Browser Plugin
+
+The in app browser functionality is entirely contained within common js. There is no native implementation required.
+To install this plugin, follow the [Command-line Interface Guide](http://cordova.apache.org/docs/en/edge/guide_cli_index.md.html#The%20Command-line%20Interface).
+
+If you are not using the Cordova Command-line Interface, follow [Using Plugman to Manage Plugins](http://cordova.apache.org/docs/en/edge/guide_plugin_ref_plugman.md.html).
+./cordova-plugin-battery-status/README.md
+./cordova-plugin-camera/README.md
+./cordova-plugin-console/README.md
+./cordova-plugin-contacts/README.md
+./cordova-plugin-device/README.md
+./cordova-plugin-device-motion/README.md
+./cordova-plugin-device-orientation/README.md
+./cordova-plugin-device-orientation/src/blackberry10/README.md
+./cordova-plugin-file/README.md
+./cordova-plugin-file-transfer/README.md
+./cordova-plugin-geolocation/README.md
+./cordova-plugin-globalization/README.md
+./cordova-plugin-inappbrowser/README.md
+./cordova-plugin-inappbrowser/src/blackberry10/README.md
+./cordova-plugin-media/README.md
+./cordova-plugin-media-capture/README.md
+./cordova-plugin-network-information/README.md
+./cordova-plugin-splashscreen/README.md
+./cordova-plugin-vibration/README.md
diff --git a/plugins/cordova-plugin-inappbrowser/src/browser/InAppBrowserProxy.js b/plugins/cordova-plugin-inappbrowser/src/browser/InAppBrowserProxy.js
new file mode 100644
index 00000000..33fbe476
--- /dev/null
+++ b/plugins/cordova-plugin-inappbrowser/src/browser/InAppBrowserProxy.js
@@ -0,0 +1,221 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+*/
+
+var cordova = require('cordova'),
+ channel = require('cordova/channel'),
+ urlutil = require('cordova/urlutil');
+
+var browserWrap,
+ popup,
+ navigationButtonsDiv,
+ navigationButtonsDivInner,
+ backButton,
+ forwardButton,
+ closeButton;
+
+function attachNavigationEvents(element, callback) {
+ var onError = function () {
+ callback({ type: "loaderror", url: this.contentWindow.location}, {keepCallback: true});
+ };
+
+ element.addEventListener("pageshow", function () {
+ callback({ type: "loadstart", url: this.contentWindow.location}, {keepCallback: true});
+ });
+
+ element.addEventListener("load", function () {
+ callback({ type: "loadstop", url: this.contentWindow.location}, {keepCallback: true});
+ });
+
+ element.addEventListener("error", onError);
+ element.addEventListener("abort", onError);
+}
+
+var IAB = {
+ close: function (win, lose) {
+ if (browserWrap) {
+ if (win) win({ type: "exit" });
+
+ browserWrap.parentNode.removeChild(browserWrap);
+ browserWrap = null;
+ popup = null;
+ }
+ },
+
+ show: function (win, lose) {
+ if (browserWrap) {
+ browserWrap.style.display = "block";
+ }
+ },
+
+ open: function (win, lose, args) {
+ var strUrl = args[0],
+ target = args[1],
+ features = args[2],
+ url;
+
+ if (target === "_system" || target === "_self" || !target) {
+ window.location = strUrl;
+ } else {
+ // "_blank" or anything else
+ if (!browserWrap) {
+ browserWrap = document.createElement("div");
+ browserWrap.style.position = "absolute";
+ browserWrap.style.borderWidth = "40px";
+ browserWrap.style.width = "calc(100% - 80px)";
+ browserWrap.style.height = "calc(100% - 80px)";
+ browserWrap.style.borderStyle = "solid";
+ browserWrap.style.borderColor = "rgba(0,0,0,0.25)";
+
+ browserWrap.onclick = function () {
+ setTimeout(function () {
+ IAB.close(win);
+ }, 0);
+ };
+
+ document.body.appendChild(browserWrap);
+ }
+
+ if (features.indexOf("hidden=yes") !== -1) {
+ browserWrap.style.display = "none";
+ }
+
+ popup = document.createElement("iframe");
+ popup.style.borderWidth = "0px";
+ popup.style.width = "100%";
+
+ browserWrap.appendChild(popup);
+
+ if (features.indexOf("location=yes") !== -1 || features.indexOf("location") === -1) {
+ popup.style.height = "calc(100% - 60px)";
+
+ navigationButtonsDiv = document.createElement("div");
+ navigationButtonsDiv.style.height = "60px";
+ navigationButtonsDiv.style.backgroundColor = "#404040";
+ navigationButtonsDiv.style.zIndex = "999";
+ navigationButtonsDiv.onclick = function (e) {
+ e.cancelBubble = true;
+ };
+
+ navigationButtonsDivInner = document.createElement("div");
+ navigationButtonsDivInner.style.paddingTop = "10px";
+ navigationButtonsDivInner.style.height = "50px";
+ navigationButtonsDivInner.style.width = "160px";
+ navigationButtonsDivInner.style.margin = "0 auto";
+ navigationButtonsDivInner.style.backgroundColor = "#404040";
+ navigationButtonsDivInner.style.zIndex = "999";
+ navigationButtonsDivInner.onclick = function (e) {
+ e.cancelBubble = true;
+ };
+
+
+ backButton = document.createElement("button");
+ backButton.style.width = "40px";
+ backButton.style.height = "40px";
+ backButton.style.borderRadius = "40px";
+
+ backButton.innerHTML = "←";
+ backButton.addEventListener("click", function (e) {
+ if (popup.canGoBack)
+ popup.goBack();
+ });
+
+ forwardButton = document.createElement("button");
+ forwardButton.style.marginLeft = "20px";
+ forwardButton.style.width = "40px";
+ forwardButton.style.height = "40px";
+ forwardButton.style.borderRadius = "40px";
+
+ forwardButton.innerHTML = "→";
+ forwardButton.addEventListener("click", function (e) {
+ if (popup.canGoForward)
+ popup.goForward();
+ });
+
+ closeButton = document.createElement("button");
+ closeButton.style.marginLeft = "20px";
+ closeButton.style.width = "40px";
+ closeButton.style.height = "40px";
+ closeButton.style.borderRadius = "40px";
+
+ closeButton.innerHTML = "✖";
+ closeButton.addEventListener("click", function (e) {
+ setTimeout(function () {
+ IAB.close(win);
+ }, 0);
+ });
+
+ // iframe navigation is not yet supported
+ backButton.disabled = true;
+ forwardButton.disabled = true;
+
+ navigationButtonsDivInner.appendChild(backButton);
+ navigationButtonsDivInner.appendChild(forwardButton);
+ navigationButtonsDivInner.appendChild(closeButton);
+ navigationButtonsDiv.appendChild(navigationButtonsDivInner);
+
+ browserWrap.appendChild(navigationButtonsDiv);
+ } else {
+ popup.style.height = "100%";
+ }
+
+ // start listening for navigation events
+ attachNavigationEvents(popup, win);
+
+ popup.src = strUrl;
+ }
+ },
+
+ injectScriptCode: function (win, fail, args) {
+ var code = args[0],
+ hasCallback = args[1];
+
+ if (browserWrap && popup) {
+ try {
+ popup.contentWindow.eval(code);
+ hasCallback && win([]);
+ } catch(e) {
+ console.error('Error occured while trying to injectScriptCode: ' + JSON.stringify(e));
+ }
+ }
+ },
+
+ injectScriptFile: function (win, fail, args) {
+ var msg = 'Browser cordova-plugin-inappbrowser injectScriptFile is not yet implemented';
+ console.warn(msg);
+ fail && fail(msg);
+ },
+
+ injectStyleCode: function (win, fail, args) {
+ var msg = 'Browser cordova-plugin-inappbrowser injectStyleCode is not yet implemented';
+ console.warn(msg);
+ fail && fail(msg);
+ },
+
+ injectStyleFile: function (win, fail, args) {
+ var msg = 'Browser cordova-plugin-inappbrowser injectStyleFile is not yet implemented';
+ console.warn(msg);
+ fail && fail(msg);
+ }
+};
+
+module.exports = IAB;
+
+require("cordova/exec/proxy").add("InAppBrowser", module.exports);
diff --git a/plugins/cordova-plugin-inappbrowser/src/firefoxos/InAppBrowserProxy.js b/plugins/cordova-plugin-inappbrowser/src/firefoxos/InAppBrowserProxy.js
new file mode 100644
index 00000000..f0d44c12
--- /dev/null
+++ b/plugins/cordova-plugin-inappbrowser/src/firefoxos/InAppBrowserProxy.js
@@ -0,0 +1,191 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+*/
+
+// https://developer.mozilla.org/en-US/docs/WebAPI/Browser
+
+var cordova = require('cordova'),
+ channel = require('cordova/channel'),
+ modulemapper = require('cordova/modulemapper');
+
+var origOpenFunc = modulemapper.getOriginalSymbol(window, 'window.open');
+var browserWrap;
+
+var IABExecs = {
+
+ close: function (win, lose) {
+ if (browserWrap) {
+ browserWrap.parentNode.removeChild(browserWrap);
+ browserWrap = null;
+ if (typeof(win) == "function") win({type:'exit'});
+ }
+ },
+
+ /*
+ * Reveal browser if opened hidden
+ */
+ show: function (win, lose) {
+ console.error('[FirefoxOS] show not implemented');
+ },
+
+ open: function (win, lose, args) {
+ var strUrl = args[0],
+ target = args[1],
+ features_string = args[2] || "location=yes", //location=yes is default
+ features = {},
+ url,
+ elem;
+
+ var features_list = features_string.split(',');
+ features_list.forEach(function(feature) {
+ var tup = feature.split('=');
+ if (tup[1] == 'yes') {
+ tup[1] = true;
+ } else if (tup[1] == 'no') {
+ tup[1] = false;
+ } else {
+ var number = parseInt(tup[1]);
+ if (!isNaN(number)) {
+ tup[1] = number;
+ }
+ }
+ features[tup[0]] = tup[1];
+ });
+
+ function updateIframeSizeNoLocation() {
+ browserWrap.style.width = window.innerWidth + 'px';
+ browserWrap.style.height = window.innerHeight + 'px';
+ browserWrap.style.zIndex = '999999999';
+ browserWrap.browser.style.height = (window.innerHeight - 60) + 'px';
+ browserWrap.browser.style.width = browserWrap.style.width;
+ }
+
+ if (target === '_system') {
+ origOpenFunc.apply(window, [strUrl, '_blank']);
+ } else if (target === '_blank') {
+ var browserElem = document.createElement('iframe');
+ browserElem.setAttribute('mozbrowser', true);
+ // make this loaded in its own child process
+ browserElem.setAttribute('remote', true);
+ browserElem.setAttribute('src', strUrl);
+ if (browserWrap) {
+ document.body.removeChild(browserWrap);
+ }
+ browserWrap = document.createElement('div');
+ // assign browser element to browserWrap for future reference
+ browserWrap.browser = browserElem;
+
+ browserWrap.classList.add('inAppBrowserWrap');
+ // position fixed so that it works even when page is scrolled
+ browserWrap.style.position = 'fixed';
+ browserElem.style.position = 'absolute';
+ browserElem.style.border = 0;
+ browserElem.style.top = '60px';
+ browserElem.style.left = '0px';
+ updateIframeSizeNoLocation();
+
+ var menu = document.createElement('menu');
+ menu.setAttribute('type', 'toolbar');
+ var close = document.createElement('li');
+ var back = document.createElement('li');
+ var forward = document.createElement('li');
+
+ close.appendChild(document.createTextNode('×'));
+ back.appendChild(document.createTextNode('<'));
+ forward.appendChild(document.createTextNode('>'));
+
+ close.classList.add('inAppBrowserClose');
+ back.classList.add('inAppBrowserBack');
+ forward.classList.add('inAppBrowserForward');
+
+ function checkForwardBackward() {
+ var backReq = browserElem.getCanGoBack();
+ backReq.onsuccess = function() {
+ if (this.result) {
+ back.classList.remove('disabled');
+ } else {
+ back.classList.add('disabled');
+ }
+ }
+ var forwardReq = browserElem.getCanGoForward();
+ forwardReq.onsuccess = function() {
+ if (this.result) {
+ forward.classList.remove('disabled');
+ } else {
+ forward.classList.add('disabled');
+ }
+ }
+ };
+
+ browserElem.addEventListener('mozbrowserloadend', checkForwardBackward);
+
+ close.addEventListener('click', function () {
+ setTimeout(function () {
+ IABExecs.close(win, lose);
+ }, 0);
+ }, false);
+
+ back.addEventListener('click', function () {
+ browserElem.goBack();
+ }, false);
+
+ forward.addEventListener('click', function () {
+ browserElem.goForward();
+ }, false);
+
+ menu.appendChild(back);
+ menu.appendChild(forward);
+ menu.appendChild(close);
+
+ browserWrap.appendChild(menu);
+ browserWrap.appendChild(browserElem);
+ document.body.appendChild(browserWrap);
+
+ //we use mozbrowserlocationchange instead of mozbrowserloadstart to get the url
+ browserElem.addEventListener('mozbrowserlocationchange', function(e){
+ win({
+ type:'loadstart',
+ url : e.detail
+ })
+ }, false);
+ browserElem.addEventListener('mozbrowserloadend', function(e){
+ win({type:'loadstop'})
+ }, false);
+ browserElem.addEventListener('mozbrowsererror', function(e){
+ win({type:'loaderror'})
+ }, false);
+ browserElem.addEventListener('mozbrowserclose', function(e){
+ win({type:'exit'})
+ }, false);
+ } else {
+ window.location = strUrl;
+ }
+ },
+ injectScriptCode: function (code, bCB) {
+ console.error('[FirefoxOS] injectScriptCode not implemented');
+ },
+ injectScriptFile: function (file, bCB) {
+ console.error('[FirefoxOS] injectScriptFile not implemented');
+ }
+};
+
+module.exports = IABExecs;
+
+require('cordova/exec/proxy').add('InAppBrowser', module.exports);
diff --git a/plugins/cordova-plugin-inappbrowser/src/ios/CDVInAppBrowser.h b/plugins/cordova-plugin-inappbrowser/src/ios/CDVInAppBrowser.h
new file mode 100644
index 00000000..1ccc7b14
--- /dev/null
+++ b/plugins/cordova-plugin-inappbrowser/src/ios/CDVInAppBrowser.h
@@ -0,0 +1,113 @@
+/*
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied. See the License for the
+ specific language governing permissions and limitations
+ under the License.
+ */
+
+#import <Cordova/CDVPlugin.h>
+#import <Cordova/CDVInvokedUrlCommand.h>
+#import <Cordova/CDVScreenOrientationDelegate.h>
+
+#ifdef __CORDOVA_4_0_0
+ #import <Cordova/CDVUIWebViewDelegate.h>
+#else
+ #import <Cordova/CDVWebViewDelegate.h>
+#endif
+
+@class CDVInAppBrowserViewController;
+
+@interface CDVInAppBrowser : CDVPlugin {
+ BOOL _injectedIframeBridge;
+}
+
+@property (nonatomic, retain) CDVInAppBrowserViewController* inAppBrowserViewController;
+@property (nonatomic, copy) NSString* callbackId;
+@property (nonatomic, copy) NSRegularExpression *callbackIdPattern;
+
+- (void)open:(CDVInvokedUrlCommand*)command;
+- (void)close:(CDVInvokedUrlCommand*)command;
+- (void)injectScriptCode:(CDVInvokedUrlCommand*)command;
+- (void)show:(CDVInvokedUrlCommand*)command;
+
+@end
+
+@interface CDVInAppBrowserOptions : NSObject {}
+
+@property (nonatomic, assign) BOOL location;
+@property (nonatomic, assign) BOOL toolbar;
+@property (nonatomic, copy) NSString* closebuttoncaption;
+@property (nonatomic, copy) NSString* toolbarposition;
+@property (nonatomic, assign) BOOL clearcache;
+@property (nonatomic, assign) BOOL clearsessioncache;
+
+@property (nonatomic, copy) NSString* presentationstyle;
+@property (nonatomic, copy) NSString* transitionstyle;
+
+@property (nonatomic, assign) BOOL enableviewportscale;
+@property (nonatomic, assign) BOOL mediaplaybackrequiresuseraction;
+@property (nonatomic, assign) BOOL allowinlinemediaplayback;
+@property (nonatomic, assign) BOOL keyboarddisplayrequiresuseraction;
+@property (nonatomic, assign) BOOL suppressesincrementalrendering;
+@property (nonatomic, assign) BOOL hidden;
+@property (nonatomic, assign) BOOL disallowoverscroll;
+
++ (CDVInAppBrowserOptions*)parseOptions:(NSString*)options;
+
+@end
+
+@interface CDVInAppBrowserViewController : UIViewController <UIWebViewDelegate, CDVScreenOrientationDelegate>{
+ @private
+ NSString* _userAgent;
+ NSString* _prevUserAgent;
+ NSInteger _userAgentLockToken;
+ CDVInAppBrowserOptions *_browserOptions;
+
+#ifdef __CORDOVA_4_0_0
+ CDVUIWebViewDelegate* _webViewDelegate;
+#else
+ CDVWebViewDelegate* _webViewDelegate;
+#endif
+
+}
+
+@property (nonatomic, strong) IBOutlet UIWebView* webView;
+@property (nonatomic, strong) IBOutlet UIBarButtonItem* closeButton;
+@property (nonatomic, strong) IBOutlet UILabel* addressLabel;
+@property (nonatomic, strong) IBOutlet UIBarButtonItem* backButton;
+@property (nonatomic, strong) IBOutlet UIBarButtonItem* forwardButton;
+@property (nonatomic, strong) IBOutlet UIActivityIndicatorView* spinner;
+@property (nonatomic, strong) IBOutlet UIToolbar* toolbar;
+
+@property (nonatomic, weak) id <CDVScreenOrientationDelegate> orientationDelegate;
+@property (nonatomic, weak) CDVInAppBrowser* navigationDelegate;
+@property (nonatomic) NSURL* currentURL;
+
+- (void)close;
+- (void)navigateTo:(NSURL*)url;
+- (void)showLocationBar:(BOOL)show;
+- (void)showToolBar:(BOOL)show : (NSString *) toolbarPosition;
+- (void)setCloseButtonTitle:(NSString*)title;
+
+- (id)initWithUserAgent:(NSString*)userAgent prevUserAgent:(NSString*)prevUserAgent browserOptions: (CDVInAppBrowserOptions*) browserOptions;
+
+@end
+
+@interface CDVInAppBrowserNavigationController : UINavigationController
+
+@property (nonatomic, weak) id <CDVScreenOrientationDelegate> orientationDelegate;
+
+@end
+
diff --git a/plugins/cordova-plugin-inappbrowser/src/ios/CDVInAppBrowser.m b/plugins/cordova-plugin-inappbrowser/src/ios/CDVInAppBrowser.m
new file mode 100644
index 00000000..24f56c49
--- /dev/null
+++ b/plugins/cordova-plugin-inappbrowser/src/ios/CDVInAppBrowser.m
@@ -0,0 +1,1022 @@
+/*
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied. See the License for the
+ specific language governing permissions and limitations
+ under the License.
+ */
+
+#import "CDVInAppBrowser.h"
+#import <Cordova/CDVPluginResult.h>
+#import <Cordova/CDVUserAgentUtil.h>
+
+#define kInAppBrowserTargetSelf @"_self"
+#define kInAppBrowserTargetSystem @"_system"
+#define kInAppBrowserTargetBlank @"_blank"
+
+#define kInAppBrowserToolbarBarPositionBottom @"bottom"
+#define kInAppBrowserToolbarBarPositionTop @"top"
+
+#define TOOLBAR_HEIGHT 44.0
+#define LOCATIONBAR_HEIGHT 21.0
+#define FOOTER_HEIGHT ((TOOLBAR_HEIGHT) + (LOCATIONBAR_HEIGHT))
+
+#pragma mark CDVInAppBrowser
+
+@interface CDVInAppBrowser () {
+ NSInteger _previousStatusBarStyle;
+}
+@end
+
+@implementation CDVInAppBrowser
+
+- (void)pluginInitialize
+{
+ _previousStatusBarStyle = -1;
+ _callbackIdPattern = nil;
+}
+
+- (void)onReset
+{
+ [self close:nil];
+}
+
+- (void)close:(CDVInvokedUrlCommand*)command
+{
+ if (self.inAppBrowserViewController == nil) {
+ NSLog(@"IAB.close() called but it was already closed.");
+ return;
+ }
+ // Things are cleaned up in browserExit.
+ [self.inAppBrowserViewController close];
+}
+
+- (BOOL) isSystemUrl:(NSURL*)url
+{
+ if ([[url host] isEqualToString:@"itunes.apple.com"]) {
+ return YES;
+ }
+
+ return NO;
+}
+
+- (void)open:(CDVInvokedUrlCommand*)command
+{
+ CDVPluginResult* pluginResult;
+
+ NSString* url = [command argumentAtIndex:0];
+ NSString* target = [command argumentAtIndex:1 withDefault:kInAppBrowserTargetSelf];
+ NSString* options = [command argumentAtIndex:2 withDefault:@"" andClass:[NSString class]];
+
+ self.callbackId = command.callbackId;
+
+ if (url != nil) {
+#ifdef __CORDOVA_4_0_0
+ NSURL* baseUrl = [self.webViewEngine URL];
+#else
+ NSURL* baseUrl = [self.webView.request URL];
+#endif
+ NSURL* absoluteUrl = [[NSURL URLWithString:url relativeToURL:baseUrl] absoluteURL];
+
+ if ([self isSystemUrl:absoluteUrl]) {
+ target = kInAppBrowserTargetSystem;
+ }
+
+ if ([target isEqualToString:kInAppBrowserTargetSelf]) {
+ [self openInCordovaWebView:absoluteUrl withOptions:options];
+ } else if ([target isEqualToString:kInAppBrowserTargetSystem]) {
+ [self openInSystem:absoluteUrl];
+ } else { // _blank or anything else
+ [self openInInAppBrowser:absoluteUrl withOptions:options];
+ }
+
+ pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK];
+ } else {
+ pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"incorrect number of arguments"];
+ }
+
+ [pluginResult setKeepCallback:[NSNumber numberWithBool:YES]];
+ [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
+}
+
+- (void)openInInAppBrowser:(NSURL*)url withOptions:(NSString*)options
+{
+ CDVInAppBrowserOptions* browserOptions = [CDVInAppBrowserOptions parseOptions:options];
+
+ if (browserOptions.clearcache) {
+ NSHTTPCookie *cookie;
+ NSHTTPCookieStorage *storage = [NSHTTPCookieStorage sharedHTTPCookieStorage];
+ for (cookie in [storage cookies])
+ {
+ if (![cookie.domain isEqual: @".^filecookies^"]) {
+ [storage deleteCookie:cookie];
+ }
+ }
+ }
+
+ if (browserOptions.clearsessioncache) {
+ NSHTTPCookie *cookie;
+ NSHTTPCookieStorage *storage = [NSHTTPCookieStorage sharedHTTPCookieStorage];
+ for (cookie in [storage cookies])
+ {
+ if (![cookie.domain isEqual: @".^filecookies^"] && cookie.isSessionOnly) {
+ [storage deleteCookie:cookie];
+ }
+ }
+ }
+
+ if (self.inAppBrowserViewController == nil) {
+ NSString* originalUA = [CDVUserAgentUtil originalUserAgent];
+ self.inAppBrowserViewController = [[CDVInAppBrowserViewController alloc] initWithUserAgent:originalUA prevUserAgent:[self.commandDelegate userAgent] browserOptions: browserOptions];
+ self.inAppBrowserViewController.navigationDelegate = self;
+
+ if ([self.viewController conformsToProtocol:@protocol(CDVScreenOrientationDelegate)]) {
+ self.inAppBrowserViewController.orientationDelegate = (UIViewController <CDVScreenOrientationDelegate>*)self.viewController;
+ }
+ }
+
+ [self.inAppBrowserViewController showLocationBar:browserOptions.location];
+ [self.inAppBrowserViewController showToolBar:browserOptions.toolbar :browserOptions.toolbarposition];
+ if (browserOptions.closebuttoncaption != nil) {
+ [self.inAppBrowserViewController setCloseButtonTitle:browserOptions.closebuttoncaption];
+ }
+ // Set Presentation Style
+ UIModalPresentationStyle presentationStyle = UIModalPresentationFullScreen; // default
+ if (browserOptions.presentationstyle != nil) {
+ if ([[browserOptions.presentationstyle lowercaseString] isEqualToString:@"pagesheet"]) {
+ presentationStyle = UIModalPresentationPageSheet;
+ } else if ([[browserOptions.presentationstyle lowercaseString] isEqualToString:@"formsheet"]) {
+ presentationStyle = UIModalPresentationFormSheet;
+ }
+ }
+ self.inAppBrowserViewController.modalPresentationStyle = presentationStyle;
+
+ // Set Transition Style
+ UIModalTransitionStyle transitionStyle = UIModalTransitionStyleCoverVertical; // default
+ if (browserOptions.transitionstyle != nil) {
+ if ([[browserOptions.transitionstyle lowercaseString] isEqualToString:@"fliphorizontal"]) {
+ transitionStyle = UIModalTransitionStyleFlipHorizontal;
+ } else if ([[browserOptions.transitionstyle lowercaseString] isEqualToString:@"crossdissolve"]) {
+ transitionStyle = UIModalTransitionStyleCrossDissolve;
+ }
+ }
+ self.inAppBrowserViewController.modalTransitionStyle = transitionStyle;
+
+ // prevent webView from bouncing
+ if (browserOptions.disallowoverscroll) {
+ if ([self.inAppBrowserViewController.webView respondsToSelector:@selector(scrollView)]) {
+ ((UIScrollView*)[self.inAppBrowserViewController.webView scrollView]).bounces = NO;
+ } else {
+ for (id subview in self.inAppBrowserViewController.webView.subviews) {
+ if ([[subview class] isSubclassOfClass:[UIScrollView class]]) {
+ ((UIScrollView*)subview).bounces = NO;
+ }
+ }
+ }
+ }
+
+ // UIWebView options
+ self.inAppBrowserViewController.webView.scalesPageToFit = browserOptions.enableviewportscale;
+ self.inAppBrowserViewController.webView.mediaPlaybackRequiresUserAction = browserOptions.mediaplaybackrequiresuseraction;
+ self.inAppBrowserViewController.webView.allowsInlineMediaPlayback = browserOptions.allowinlinemediaplayback;
+ if (IsAtLeastiOSVersion(@"6.0")) {
+ self.inAppBrowserViewController.webView.keyboardDisplayRequiresUserAction = browserOptions.keyboarddisplayrequiresuseraction;
+ self.inAppBrowserViewController.webView.suppressesIncrementalRendering = browserOptions.suppressesincrementalrendering;
+ }
+
+ [self.inAppBrowserViewController navigateTo:url];
+ if (!browserOptions.hidden) {
+ [self show:nil];
+ }
+}
+
+- (void)show:(CDVInvokedUrlCommand*)command
+{
+ if (self.inAppBrowserViewController == nil) {
+ NSLog(@"Tried to show IAB after it was closed.");
+ return;
+ }
+ if (_previousStatusBarStyle != -1) {
+ NSLog(@"Tried to show IAB while already shown");
+ return;
+ }
+
+ _previousStatusBarStyle = [UIApplication sharedApplication].statusBarStyle;
+
+ CDVInAppBrowserNavigationController* nav = [[CDVInAppBrowserNavigationController alloc]
+ initWithRootViewController:self.inAppBrowserViewController];
+ nav.orientationDelegate = self.inAppBrowserViewController;
+ nav.navigationBarHidden = YES;
+ // Run later to avoid the "took a long time" log message.
+ dispatch_async(dispatch_get_main_queue(), ^{
+ if (self.inAppBrowserViewController != nil) {
+ [self.viewController presentViewController:nav animated:YES completion:nil];
+ }
+ });
+}
+
+- (void)openInCordovaWebView:(NSURL*)url withOptions:(NSString*)options
+{
+ if ([self.commandDelegate URLIsWhitelisted:url]) {
+ NSURLRequest* request = [NSURLRequest requestWithURL:url];
+#ifdef __CORDOVA_4_0_0
+ [self.webViewEngine loadRequest:request];
+#else
+ [self.webView loadRequest:request];
+#endif
+ } else { // this assumes the InAppBrowser can be excepted from the white-list
+ [self openInInAppBrowser:url withOptions:options];
+ }
+}
+
+- (void)openInSystem:(NSURL*)url
+{
+ if ([[UIApplication sharedApplication] canOpenURL:url]) {
+ [[UIApplication sharedApplication] openURL:url];
+ } else { // handle any custom schemes to plugins
+ [[NSNotificationCenter defaultCenter] postNotification:[NSNotification notificationWithName:CDVPluginHandleOpenURLNotification object:url]];
+ }
+}
+
+// This is a helper method for the inject{Script|Style}{Code|File} API calls, which
+// provides a consistent method for injecting JavaScript code into the document.
+//
+// If a wrapper string is supplied, then the source string will be JSON-encoded (adding
+// quotes) and wrapped using string formatting. (The wrapper string should have a single
+// '%@' marker).
+//
+// If no wrapper is supplied, then the source string is executed directly.
+
+- (void)injectDeferredObject:(NSString*)source withWrapper:(NSString*)jsWrapper
+{
+ if (!_injectedIframeBridge) {
+ _injectedIframeBridge = YES;
+ // Create an iframe bridge in the new document to communicate with the CDVInAppBrowserViewController
+ [self.inAppBrowserViewController.webView stringByEvaluatingJavaScriptFromString:@"(function(d){var e = _cdvIframeBridge = d.createElement('iframe');e.style.display='none';d.body.appendChild(e);})(document)"];
+ }
+
+ if (jsWrapper != nil) {
+ NSData* jsonData = [NSJSONSerialization dataWithJSONObject:@[source] options:0 error:nil];
+ NSString* sourceArrayString = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
+ if (sourceArrayString) {
+ NSString* sourceString = [sourceArrayString substringWithRange:NSMakeRange(1, [sourceArrayString length] - 2)];
+ NSString* jsToInject = [NSString stringWithFormat:jsWrapper, sourceString];
+ [self.inAppBrowserViewController.webView stringByEvaluatingJavaScriptFromString:jsToInject];
+ }
+ } else {
+ [self.inAppBrowserViewController.webView stringByEvaluatingJavaScriptFromString:source];
+ }
+}
+
+- (void)injectScriptCode:(CDVInvokedUrlCommand*)command
+{
+ NSString* jsWrapper = nil;
+
+ if ((command.callbackId != nil) && ![command.callbackId isEqualToString:@"INVALID"]) {
+ jsWrapper = [NSString stringWithFormat:@"_cdvIframeBridge.src='gap-iab://%@/'+encodeURIComponent(JSON.stringify([eval(%%@)]));", command.callbackId];
+ }
+ [self injectDeferredObject:[command argumentAtIndex:0] withWrapper:jsWrapper];
+}
+
+- (void)injectScriptFile:(CDVInvokedUrlCommand*)command
+{
+ NSString* jsWrapper;
+
+ if ((command.callbackId != nil) && ![command.callbackId isEqualToString:@"INVALID"]) {
+ jsWrapper = [NSString stringWithFormat:@"(function(d) { var c = d.createElement('script'); c.src = %%@; c.onload = function() { _cdvIframeBridge.src='gap-iab://%@'; }; d.body.appendChild(c); })(document)", command.callbackId];
+ } else {
+ jsWrapper = @"(function(d) { var c = d.createElement('script'); c.src = %@; d.body.appendChild(c); })(document)";
+ }
+ [self injectDeferredObject:[command argumentAtIndex:0] withWrapper:jsWrapper];
+}
+
+- (void)injectStyleCode:(CDVInvokedUrlCommand*)command
+{
+ NSString* jsWrapper;
+
+ if ((command.callbackId != nil) && ![command.callbackId isEqualToString:@"INVALID"]) {
+ jsWrapper = [NSString stringWithFormat:@"(function(d) { var c = d.createElement('style'); c.innerHTML = %%@; c.onload = function() { _cdvIframeBridge.src='gap-iab://%@'; }; d.body.appendChild(c); })(document)", command.callbackId];
+ } else {
+ jsWrapper = @"(function(d) { var c = d.createElement('style'); c.innerHTML = %@; d.body.appendChild(c); })(document)";
+ }
+ [self injectDeferredObject:[command argumentAtIndex:0] withWrapper:jsWrapper];
+}
+
+- (void)injectStyleFile:(CDVInvokedUrlCommand*)command
+{
+ NSString* jsWrapper;
+
+ if ((command.callbackId != nil) && ![command.callbackId isEqualToString:@"INVALID"]) {
+ jsWrapper = [NSString stringWithFormat:@"(function(d) { var c = d.createElement('link'); c.rel='stylesheet'; c.type='text/css'; c.href = %%@; c.onload = function() { _cdvIframeBridge.src='gap-iab://%@'; }; d.body.appendChild(c); })(document)", command.callbackId];
+ } else {
+ jsWrapper = @"(function(d) { var c = d.createElement('link'); c.rel='stylesheet', c.type='text/css'; c.href = %@; d.body.appendChild(c); })(document)";
+ }
+ [self injectDeferredObject:[command argumentAtIndex:0] withWrapper:jsWrapper];
+}
+
+- (BOOL)isValidCallbackId:(NSString *)callbackId
+{
+ NSError *err = nil;
+ // Initialize on first use
+ if (self.callbackIdPattern == nil) {
+ self.callbackIdPattern = [NSRegularExpression regularExpressionWithPattern:@"^InAppBrowser[0-9]{1,10}$" options:0 error:&err];
+ if (err != nil) {
+ // Couldn't initialize Regex; No is safer than Yes.
+ return NO;
+ }
+ }
+ if ([self.callbackIdPattern firstMatchInString:callbackId options:0 range:NSMakeRange(0, [callbackId length])]) {
+ return YES;
+ }
+ return NO;
+}
+
+/**
+ * The iframe bridge provided for the InAppBrowser is capable of executing any oustanding callback belonging
+ * to the InAppBrowser plugin. Care has been taken that other callbacks cannot be triggered, and that no
+ * other code execution is possible.
+ *
+ * To trigger the bridge, the iframe (or any other resource) should attempt to load a url of the form:
+ *
+ * gap-iab://<callbackId>/<arguments>
+ *
+ * where <callbackId> is the string id of the callback to trigger (something like "InAppBrowser0123456789")
+ *
+ * If present, the path component of the special gap-iab:// url is expected to be a URL-escaped JSON-encoded
+ * value to pass to the callback. [NSURL path] should take care of the URL-unescaping, and a JSON_EXCEPTION
+ * is returned if the JSON is invalid.
+ */
+- (BOOL)webView:(UIWebView*)theWebView shouldStartLoadWithRequest:(NSURLRequest*)request navigationType:(UIWebViewNavigationType)navigationType
+{
+ NSURL* url = request.URL;
+ BOOL isTopLevelNavigation = [request.URL isEqual:[request mainDocumentURL]];
+
+ // See if the url uses the 'gap-iab' protocol. If so, the host should be the id of a callback to execute,
+ // and the path, if present, should be a JSON-encoded value to pass to the callback.
+ if ([[url scheme] isEqualToString:@"gap-iab"]) {
+ NSString* scriptCallbackId = [url host];
+ CDVPluginResult* pluginResult = nil;
+
+ if ([self isValidCallbackId:scriptCallbackId]) {
+ NSString* scriptResult = [url path];
+ NSError* __autoreleasing error = nil;
+
+ // The message should be a JSON-encoded array of the result of the script which executed.
+ if ((scriptResult != nil) && ([scriptResult length] > 1)) {
+ scriptResult = [scriptResult substringFromIndex:1];
+ NSData* decodedResult = [NSJSONSerialization JSONObjectWithData:[scriptResult dataUsingEncoding:NSUTF8StringEncoding] options:kNilOptions error:&error];
+ if ((error == nil) && [decodedResult isKindOfClass:[NSArray class]]) {
+ pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsArray:(NSArray*)decodedResult];
+ } else {
+ pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_JSON_EXCEPTION];
+ }
+ } else {
+ pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsArray:@[]];
+ }
+ [self.commandDelegate sendPluginResult:pluginResult callbackId:scriptCallbackId];
+ return NO;
+ }
+ } else if ((self.callbackId != nil) && isTopLevelNavigation) {
+ // Send a loadstart event for each top-level navigation (includes redirects).
+ CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK
+ messageAsDictionary:@{@"type":@"loadstart", @"url":[url absoluteString]}];
+ [pluginResult setKeepCallback:[NSNumber numberWithBool:YES]];
+
+ [self.commandDelegate sendPluginResult:pluginResult callbackId:self.callbackId];
+ }
+
+ return YES;
+}
+
+- (void)webViewDidStartLoad:(UIWebView*)theWebView
+{
+ _injectedIframeBridge = NO;
+}
+
+- (void)webViewDidFinishLoad:(UIWebView*)theWebView
+{
+ if (self.callbackId != nil) {
+ // TODO: It would be more useful to return the URL the page is actually on (e.g. if it's been redirected).
+ NSString* url = [self.inAppBrowserViewController.currentURL absoluteString];
+ CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK
+ messageAsDictionary:@{@"type":@"loadstop", @"url":url}];
+ [pluginResult setKeepCallback:[NSNumber numberWithBool:YES]];
+
+ [self.commandDelegate sendPluginResult:pluginResult callbackId:self.callbackId];
+ }
+}
+
+- (void)webView:(UIWebView*)theWebView didFailLoadWithError:(NSError*)error
+{
+ if (self.callbackId != nil) {
+ NSString* url = [self.inAppBrowserViewController.currentURL absoluteString];
+ CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR
+ messageAsDictionary:@{@"type":@"loaderror", @"url":url, @"code": [NSNumber numberWithInteger:error.code], @"message": error.localizedDescription}];
+ [pluginResult setKeepCallback:[NSNumber numberWithBool:YES]];
+
+ [self.commandDelegate sendPluginResult:pluginResult callbackId:self.callbackId];
+ }
+}
+
+- (void)browserExit
+{
+ if (self.callbackId != nil) {
+ CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK
+ messageAsDictionary:@{@"type":@"exit"}];
+ [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];
+ }
+
+ _previousStatusBarStyle = -1; // this value was reset before reapplying it. caused statusbar to stay black on ios7
+}
+
+@end
+
+#pragma mark CDVInAppBrowserViewController
+
+@implementation CDVInAppBrowserViewController
+
+@synthesize currentURL;
+
+- (id)initWithUserAgent:(NSString*)userAgent prevUserAgent:(NSString*)prevUserAgent browserOptions: (CDVInAppBrowserOptions*) browserOptions
+{
+ self = [super init];
+ if (self != nil) {
+ _userAgent = userAgent;
+ _prevUserAgent = prevUserAgent;
+ _browserOptions = browserOptions;
+#ifdef __CORDOVA_4_0_0
+ _webViewDelegate = [[CDVUIWebViewDelegate alloc] initWithDelegate:self];
+#else
+ _webViewDelegate = [[CDVWebViewDelegate alloc] initWithDelegate:self];
+#endif
+
+ [self createViews];
+ }
+
+ return self;
+}
+
+- (void)createViews
+{
+ // We create the views in code for primarily for ease of upgrades and not requiring an external .xib to be included
+
+ CGRect webViewBounds = self.view.bounds;
+ BOOL toolbarIsAtBottom = ![_browserOptions.toolbarposition isEqualToString:kInAppBrowserToolbarBarPositionTop];
+ webViewBounds.size.height -= _browserOptions.location ? FOOTER_HEIGHT : TOOLBAR_HEIGHT;
+ self.webView = [[UIWebView alloc] initWithFrame:webViewBounds];
+
+ self.webView.autoresizingMask = (UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight);
+
+ [self.view addSubview:self.webView];
+ [self.view sendSubviewToBack:self.webView];
+
+ self.webView.delegate = _webViewDelegate;
+ self.webView.backgroundColor = [UIColor whiteColor];
+
+ self.webView.clearsContextBeforeDrawing = YES;
+ self.webView.clipsToBounds = YES;
+ self.webView.contentMode = UIViewContentModeScaleToFill;
+ self.webView.multipleTouchEnabled = YES;
+ self.webView.opaque = YES;
+ self.webView.scalesPageToFit = NO;
+ self.webView.userInteractionEnabled = YES;
+
+ self.spinner = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhite];
+ self.spinner.alpha = 1.000;
+ self.spinner.autoresizesSubviews = YES;
+ self.spinner.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleTopMargin;
+ self.spinner.clearsContextBeforeDrawing = NO;
+ self.spinner.clipsToBounds = NO;
+ self.spinner.contentMode = UIViewContentModeScaleToFill;
+ self.spinner.frame = CGRectMake(454.0, 231.0, 20.0, 20.0);
+ self.spinner.hidden = YES;
+ self.spinner.hidesWhenStopped = YES;
+ self.spinner.multipleTouchEnabled = NO;
+ self.spinner.opaque = NO;
+ self.spinner.userInteractionEnabled = NO;
+ [self.spinner stopAnimating];
+
+ self.closeButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemDone target:self action:@selector(close)];
+ self.closeButton.enabled = YES;
+
+ UIBarButtonItem* flexibleSpaceButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFlexibleSpace target:nil action:nil];
+
+ UIBarButtonItem* fixedSpaceButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFixedSpace target:nil action:nil];
+ fixedSpaceButton.width = 20;
+
+ float toolbarY = toolbarIsAtBottom ? self.view.bounds.size.height - TOOLBAR_HEIGHT : 0.0;
+ CGRect toolbarFrame = CGRectMake(0.0, toolbarY, self.view.bounds.size.width, TOOLBAR_HEIGHT);
+
+ self.toolbar = [[UIToolbar alloc] initWithFrame:toolbarFrame];
+ self.toolbar.alpha = 1.000;
+ self.toolbar.autoresizesSubviews = YES;
+ self.toolbar.autoresizingMask = toolbarIsAtBottom ? (UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleTopMargin) : UIViewAutoresizingFlexibleWidth;
+ self.toolbar.barStyle = UIBarStyleBlackOpaque;
+ self.toolbar.clearsContextBeforeDrawing = NO;
+ self.toolbar.clipsToBounds = NO;
+ self.toolbar.contentMode = UIViewContentModeScaleToFill;
+ self.toolbar.hidden = NO;
+ self.toolbar.multipleTouchEnabled = NO;
+ self.toolbar.opaque = NO;
+ self.toolbar.userInteractionEnabled = YES;
+
+ CGFloat labelInset = 5.0;
+ float locationBarY = toolbarIsAtBottom ? self.view.bounds.size.height - FOOTER_HEIGHT : self.view.bounds.size.height - LOCATIONBAR_HEIGHT;
+
+ self.addressLabel = [[UILabel alloc] initWithFrame:CGRectMake(labelInset, locationBarY, self.view.bounds.size.width - labelInset, LOCATIONBAR_HEIGHT)];
+ self.addressLabel.adjustsFontSizeToFitWidth = NO;
+ self.addressLabel.alpha = 1.000;
+ self.addressLabel.autoresizesSubviews = YES;
+ self.addressLabel.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleTopMargin;
+ self.addressLabel.backgroundColor = [UIColor clearColor];
+ self.addressLabel.baselineAdjustment = UIBaselineAdjustmentAlignCenters;
+ self.addressLabel.clearsContextBeforeDrawing = YES;
+ self.addressLabel.clipsToBounds = YES;
+ self.addressLabel.contentMode = UIViewContentModeScaleToFill;
+ self.addressLabel.enabled = YES;
+ self.addressLabel.hidden = NO;
+ self.addressLabel.lineBreakMode = NSLineBreakByTruncatingTail;
+
+ if ([self.addressLabel respondsToSelector:NSSelectorFromString(@"setMinimumScaleFactor:")]) {
+ [self.addressLabel setValue:@(10.0/[UIFont labelFontSize]) forKey:@"minimumScaleFactor"];
+ } else if ([self.addressLabel respondsToSelector:NSSelectorFromString(@"setMinimumFontSize:")]) {
+ [self.addressLabel setValue:@(10.0) forKey:@"minimumFontSize"];
+ }
+
+ self.addressLabel.multipleTouchEnabled = NO;
+ self.addressLabel.numberOfLines = 1;
+ self.addressLabel.opaque = NO;
+ self.addressLabel.shadowOffset = CGSizeMake(0.0, -1.0);
+ self.addressLabel.text = NSLocalizedString(@"Loading...", nil);
+ self.addressLabel.textAlignment = NSTextAlignmentLeft;
+ self.addressLabel.textColor = [UIColor colorWithWhite:1.000 alpha:1.000];
+ self.addressLabel.userInteractionEnabled = NO;
+
+ NSString* frontArrowString = NSLocalizedString(@"►", nil); // create arrow from Unicode char
+ self.forwardButton = [[UIBarButtonItem alloc] initWithTitle:frontArrowString style:UIBarButtonItemStylePlain target:self action:@selector(goForward:)];
+ self.forwardButton.enabled = YES;
+ self.forwardButton.imageInsets = UIEdgeInsetsZero;
+
+ NSString* backArrowString = NSLocalizedString(@"◄", nil); // create arrow from Unicode char
+ self.backButton = [[UIBarButtonItem alloc] initWithTitle:backArrowString style:UIBarButtonItemStylePlain target:self action:@selector(goBack:)];
+ self.backButton.enabled = YES;
+ self.backButton.imageInsets = UIEdgeInsetsZero;
+
+ [self.toolbar setItems:@[self.closeButton, flexibleSpaceButton, self.backButton, fixedSpaceButton, self.forwardButton]];
+
+ self.view.backgroundColor = [UIColor grayColor];
+ [self.view addSubview:self.toolbar];
+ [self.view addSubview:self.addressLabel];
+ [self.view addSubview:self.spinner];
+}
+
+- (void) setWebViewFrame : (CGRect) frame {
+ NSLog(@"Setting the WebView's frame to %@", NSStringFromCGRect(frame));
+ [self.webView setFrame:frame];
+}
+
+- (void)setCloseButtonTitle:(NSString*)title
+{
+ // the advantage of using UIBarButtonSystemItemDone is the system will localize it for you automatically
+ // but, if you want to set this yourself, knock yourself out (we can't set the title for a system Done button, so we have to create a new one)
+ self.closeButton = nil;
+ self.closeButton = [[UIBarButtonItem alloc] initWithTitle:title style:UIBarButtonItemStyleBordered target:self action:@selector(close)];
+ self.closeButton.enabled = YES;
+ self.closeButton.tintColor = [UIColor colorWithRed:60.0 / 255.0 green:136.0 / 255.0 blue:230.0 / 255.0 alpha:1];
+
+ NSMutableArray* items = [self.toolbar.items mutableCopy];
+ [items replaceObjectAtIndex:0 withObject:self.closeButton];
+ [self.toolbar setItems:items];
+}
+
+- (void)showLocationBar:(BOOL)show
+{
+ CGRect locationbarFrame = self.addressLabel.frame;
+
+ BOOL toolbarVisible = !self.toolbar.hidden;
+
+ // prevent double show/hide
+ if (show == !(self.addressLabel.hidden)) {
+ return;
+ }
+
+ if (show) {
+ self.addressLabel.hidden = NO;
+
+ if (toolbarVisible) {
+ // toolBar at the bottom, leave as is
+ // put locationBar on top of the toolBar
+
+ CGRect webViewBounds = self.view.bounds;
+ webViewBounds.size.height -= FOOTER_HEIGHT;
+ [self setWebViewFrame:webViewBounds];
+
+ locationbarFrame.origin.y = webViewBounds.size.height;
+ self.addressLabel.frame = locationbarFrame;
+ } else {
+ // no toolBar, so put locationBar at the bottom
+
+ CGRect webViewBounds = self.view.bounds;
+ webViewBounds.size.height -= LOCATIONBAR_HEIGHT;
+ [self setWebViewFrame:webViewBounds];
+
+ locationbarFrame.origin.y = webViewBounds.size.height;
+ self.addressLabel.frame = locationbarFrame;
+ }
+ } else {
+ self.addressLabel.hidden = YES;
+
+ if (toolbarVisible) {
+ // locationBar is on top of toolBar, hide locationBar
+
+ // webView take up whole height less toolBar height
+ CGRect webViewBounds = self.view.bounds;
+ webViewBounds.size.height -= TOOLBAR_HEIGHT;
+ [self setWebViewFrame:webViewBounds];
+ } else {
+ // no toolBar, expand webView to screen dimensions
+ [self setWebViewFrame:self.view.bounds];
+ }
+ }
+}
+
+- (void)showToolBar:(BOOL)show : (NSString *) toolbarPosition
+{
+ CGRect toolbarFrame = self.toolbar.frame;
+ CGRect locationbarFrame = self.addressLabel.frame;
+
+ BOOL locationbarVisible = !self.addressLabel.hidden;
+
+ // prevent double show/hide
+ if (show == !(self.toolbar.hidden)) {
+ return;
+ }
+
+ if (show) {
+ self.toolbar.hidden = NO;
+ CGRect webViewBounds = self.view.bounds;
+
+ if (locationbarVisible) {
+ // locationBar at the bottom, move locationBar up
+ // put toolBar at the bottom
+ webViewBounds.size.height -= FOOTER_HEIGHT;
+ locationbarFrame.origin.y = webViewBounds.size.height;
+ self.addressLabel.frame = locationbarFrame;
+ self.toolbar.frame = toolbarFrame;
+ } else {
+ // no locationBar, so put toolBar at the bottom
+ CGRect webViewBounds = self.view.bounds;
+ webViewBounds.size.height -= TOOLBAR_HEIGHT;
+ self.toolbar.frame = toolbarFrame;
+ }
+
+ if ([toolbarPosition isEqualToString:kInAppBrowserToolbarBarPositionTop]) {
+ toolbarFrame.origin.y = 0;
+ webViewBounds.origin.y += toolbarFrame.size.height;
+ [self setWebViewFrame:webViewBounds];
+ } else {
+ toolbarFrame.origin.y = (webViewBounds.size.height + LOCATIONBAR_HEIGHT);
+ }
+ [self setWebViewFrame:webViewBounds];
+
+ } else {
+ self.toolbar.hidden = YES;
+
+ if (locationbarVisible) {
+ // locationBar is on top of toolBar, hide toolBar
+ // put locationBar at the bottom
+
+ // webView take up whole height less locationBar height
+ CGRect webViewBounds = self.view.bounds;
+ webViewBounds.size.height -= LOCATIONBAR_HEIGHT;
+ [self setWebViewFrame:webViewBounds];
+
+ // move locationBar down
+ locationbarFrame.origin.y = webViewBounds.size.height;
+ self.addressLabel.frame = locationbarFrame;
+ } else {
+ // no locationBar, expand webView to screen dimensions
+ [self setWebViewFrame:self.view.bounds];
+ }
+ }
+}
+
+- (void)viewDidLoad
+{
+ [super viewDidLoad];
+}
+
+- (void)viewDidUnload
+{
+ [self.webView loadHTMLString:nil baseURL:nil];
+ [CDVUserAgentUtil releaseLock:&_userAgentLockToken];
+ [super viewDidUnload];
+}
+
+- (UIStatusBarStyle)preferredStatusBarStyle
+{
+ return UIStatusBarStyleDefault;
+}
+
+- (void)close
+{
+ [CDVUserAgentUtil releaseLock:&_userAgentLockToken];
+ self.currentURL = nil;
+
+ if ((self.navigationDelegate != nil) && [self.navigationDelegate respondsToSelector:@selector(browserExit)]) {
+ [self.navigationDelegate browserExit];
+ }
+
+ // Run later to avoid the "took a long time" log message.
+ dispatch_async(dispatch_get_main_queue(), ^{
+ if ([self respondsToSelector:@selector(presentingViewController)]) {
+ [[self presentingViewController] dismissViewControllerAnimated:YES completion:nil];
+ } else {
+ [[self parentViewController] dismissViewControllerAnimated:YES completion:nil];
+ }
+ });
+}
+
+- (void)navigateTo:(NSURL*)url
+{
+ NSURLRequest* request = [NSURLRequest requestWithURL:url];
+
+ if (_userAgentLockToken != 0) {
+ [self.webView loadRequest:request];
+ } else {
+ [CDVUserAgentUtil acquireLock:^(NSInteger lockToken) {
+ _userAgentLockToken = lockToken;
+ [CDVUserAgentUtil setUserAgent:_userAgent lockToken:lockToken];
+ [self.webView loadRequest:request];
+ }];
+ }
+}
+
+- (void)goBack:(id)sender
+{
+ [self.webView goBack];
+}
+
+- (void)goForward:(id)sender
+{
+ [self.webView goForward];
+}
+
+- (void)viewWillAppear:(BOOL)animated
+{
+ if (IsAtLeastiOSVersion(@"7.0")) {
+ [[UIApplication sharedApplication] setStatusBarStyle:[self preferredStatusBarStyle]];
+ }
+ [self rePositionViews];
+
+ [super viewWillAppear:animated];
+}
+
+//
+// On iOS 7 the status bar is part of the view's dimensions, therefore it's height has to be taken into account.
+// The height of it could be hardcoded as 20 pixels, but that would assume that the upcoming releases of iOS won't
+// change that value.
+//
+- (float) getStatusBarOffset {
+ CGRect statusBarFrame = [[UIApplication sharedApplication] statusBarFrame];
+ float statusBarOffset = IsAtLeastiOSVersion(@"7.0") ? MIN(statusBarFrame.size.width, statusBarFrame.size.height) : 0.0;
+ return statusBarOffset;
+}
+
+- (void) rePositionViews {
+ if ([_browserOptions.toolbarposition isEqualToString:kInAppBrowserToolbarBarPositionTop]) {
+ [self.webView setFrame:CGRectMake(self.webView.frame.origin.x, TOOLBAR_HEIGHT, self.webView.frame.size.width, self.webView.frame.size.height)];
+ [self.toolbar setFrame:CGRectMake(self.toolbar.frame.origin.x, [self getStatusBarOffset], self.toolbar.frame.size.width, self.toolbar.frame.size.height)];
+ }
+}
+
+#pragma mark UIWebViewDelegate
+
+- (void)webViewDidStartLoad:(UIWebView*)theWebView
+{
+ // loading url, start spinner, update back/forward
+
+ self.addressLabel.text = NSLocalizedString(@"Loading...", nil);
+ self.backButton.enabled = theWebView.canGoBack;
+ self.forwardButton.enabled = theWebView.canGoForward;
+
+ [self.spinner startAnimating];
+
+ return [self.navigationDelegate webViewDidStartLoad:theWebView];
+}
+
+- (BOOL)webView:(UIWebView*)theWebView shouldStartLoadWithRequest:(NSURLRequest*)request navigationType:(UIWebViewNavigationType)navigationType
+{
+ BOOL isTopLevelNavigation = [request.URL isEqual:[request mainDocumentURL]];
+
+ if (isTopLevelNavigation) {
+ self.currentURL = request.URL;
+ }
+ return [self.navigationDelegate webView:theWebView shouldStartLoadWithRequest:request navigationType:navigationType];
+}
+
+- (void)webViewDidFinishLoad:(UIWebView*)theWebView
+{
+ // update url, stop spinner, update back/forward
+
+ self.addressLabel.text = [self.currentURL absoluteString];
+ self.backButton.enabled = theWebView.canGoBack;
+ self.forwardButton.enabled = theWebView.canGoForward;
+
+ [self.spinner stopAnimating];
+
+ // Work around a bug where the first time a PDF is opened, all UIWebViews
+ // reload their User-Agent from NSUserDefaults.
+ // This work-around makes the following assumptions:
+ // 1. The app has only a single Cordova Webview. If not, then the app should
+ // take it upon themselves to load a PDF in the background as a part of
+ // their start-up flow.
+ // 2. That the PDF does not require any additional network requests. We change
+ // the user-agent here back to that of the CDVViewController, so requests
+ // from it must pass through its white-list. This *does* break PDFs that
+ // contain links to other remote PDF/websites.
+ // More info at https://issues.apache.org/jira/browse/CB-2225
+ BOOL isPDF = [@"true" isEqualToString :[theWebView stringByEvaluatingJavaScriptFromString:@"document.body==null"]];
+ if (isPDF) {
+ [CDVUserAgentUtil setUserAgent:_prevUserAgent lockToken:_userAgentLockToken];
+ }
+
+ [self.navigationDelegate webViewDidFinishLoad:theWebView];
+}
+
+- (void)webView:(UIWebView*)theWebView didFailLoadWithError:(NSError*)error
+{
+ // log fail message, stop spinner, update back/forward
+ NSLog(@"webView:didFailLoadWithError - %ld: %@", (long)error.code, [error localizedDescription]);
+
+ self.backButton.enabled = theWebView.canGoBack;
+ self.forwardButton.enabled = theWebView.canGoForward;
+ [self.spinner stopAnimating];
+
+ self.addressLabel.text = NSLocalizedString(@"Load Error", nil);
+
+ [self.navigationDelegate webView:theWebView didFailLoadWithError:error];
+}
+
+#pragma mark CDVScreenOrientationDelegate
+
+- (BOOL)shouldAutorotate
+{
+ if ((self.orientationDelegate != nil) && [self.orientationDelegate respondsToSelector:@selector(shouldAutorotate)]) {
+ return [self.orientationDelegate shouldAutorotate];
+ }
+ return YES;
+}
+
+- (NSUInteger)supportedInterfaceOrientations
+{
+ if ((self.orientationDelegate != nil) && [self.orientationDelegate respondsToSelector:@selector(supportedInterfaceOrientations)]) {
+ return [self.orientationDelegate supportedInterfaceOrientations];
+ }
+
+ return 1 << UIInterfaceOrientationPortrait;
+}
+
+- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
+{
+ if ((self.orientationDelegate != nil) && [self.orientationDelegate respondsToSelector:@selector(shouldAutorotateToInterfaceOrientation:)]) {
+ return [self.orientationDelegate shouldAutorotateToInterfaceOrientation:interfaceOrientation];
+ }
+
+ return YES;
+}
+
+@end
+
+@implementation CDVInAppBrowserOptions
+
+- (id)init
+{
+ if (self = [super init]) {
+ // default values
+ self.location = YES;
+ self.toolbar = YES;
+ self.closebuttoncaption = nil;
+ self.toolbarposition = kInAppBrowserToolbarBarPositionBottom;
+ self.clearcache = NO;
+ self.clearsessioncache = NO;
+
+ self.enableviewportscale = NO;
+ self.mediaplaybackrequiresuseraction = NO;
+ self.allowinlinemediaplayback = NO;
+ self.keyboarddisplayrequiresuseraction = YES;
+ self.suppressesincrementalrendering = NO;
+ self.hidden = NO;
+ self.disallowoverscroll = NO;
+ }
+
+ return self;
+}
+
++ (CDVInAppBrowserOptions*)parseOptions:(NSString*)options
+{
+ CDVInAppBrowserOptions* obj = [[CDVInAppBrowserOptions alloc] init];
+
+ // NOTE: this parsing does not handle quotes within values
+ NSArray* pairs = [options componentsSeparatedByString:@","];
+
+ // parse keys and values, set the properties
+ for (NSString* pair in pairs) {
+ NSArray* keyvalue = [pair componentsSeparatedByString:@"="];
+
+ if ([keyvalue count] == 2) {
+ NSString* key = [[keyvalue objectAtIndex:0] lowercaseString];
+ NSString* value = [keyvalue objectAtIndex:1];
+ NSString* value_lc = [value lowercaseString];
+
+ BOOL isBoolean = [value_lc isEqualToString:@"yes"] || [value_lc isEqualToString:@"no"];
+ NSNumberFormatter* numberFormatter = [[NSNumberFormatter alloc] init];
+ [numberFormatter setAllowsFloats:YES];
+ BOOL isNumber = [numberFormatter numberFromString:value_lc] != nil;
+
+ // set the property according to the key name
+ if ([obj respondsToSelector:NSSelectorFromString(key)]) {
+ if (isNumber) {
+ [obj setValue:[numberFormatter numberFromString:value_lc] forKey:key];
+ } else if (isBoolean) {
+ [obj setValue:[NSNumber numberWithBool:[value_lc isEqualToString:@"yes"]] forKey:key];
+ } else {
+ [obj setValue:value forKey:key];
+ }
+ }
+ }
+ }
+
+ return obj;
+}
+
+@end
+
+@implementation CDVInAppBrowserNavigationController : UINavigationController
+
+- (void) viewDidLoad {
+
+ CGRect frame = [UIApplication sharedApplication].statusBarFrame;
+
+ // simplified from: http://stackoverflow.com/a/25669695/219684
+
+ UIToolbar* bgToolbar = [[UIToolbar alloc] initWithFrame:frame];
+ bgToolbar.barStyle = UIBarStyleDefault;
+ [self.view addSubview:bgToolbar];
+
+ [super viewDidLoad];
+}
+
+
+#pragma mark CDVScreenOrientationDelegate
+
+- (BOOL)shouldAutorotate
+{
+ if ((self.orientationDelegate != nil) && [self.orientationDelegate respondsToSelector:@selector(shouldAutorotate)]) {
+ return [self.orientationDelegate shouldAutorotate];
+ }
+ return YES;
+}
+
+- (NSUInteger)supportedInterfaceOrientations
+{
+ if ((self.orientationDelegate != nil) && [self.orientationDelegate respondsToSelector:@selector(supportedInterfaceOrientations)]) {
+ return [self.orientationDelegate supportedInterfaceOrientations];
+ }
+
+ return 1 << UIInterfaceOrientationPortrait;
+}
+
+- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
+{
+ if ((self.orientationDelegate != nil) && [self.orientationDelegate respondsToSelector:@selector(shouldAutorotateToInterfaceOrientation:)]) {
+ return [self.orientationDelegate shouldAutorotateToInterfaceOrientation:interfaceOrientation];
+ }
+
+ return YES;
+}
+
+
+@end
+
diff --git a/plugins/cordova-plugin-inappbrowser/src/ubuntu/InAppBrowser.qml b/plugins/cordova-plugin-inappbrowser/src/ubuntu/InAppBrowser.qml
new file mode 100644
index 00000000..781e8a6e
--- /dev/null
+++ b/plugins/cordova-plugin-inappbrowser/src/ubuntu/InAppBrowser.qml
@@ -0,0 +1,92 @@
+/*
+ *
+ * Copyright 2013 Canonical Ltd.
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+*/
+import QtQuick 2.0
+import Ubuntu.Components.Popups 0.1
+import Ubuntu.Components 0.1
+import com.canonical.Oxide 1.0
+
+Rectangle {
+ anchors.fill: parent
+ id: inappbrowser
+ property string url1
+ Rectangle {
+ border.color: "black"
+ width: parent.width
+ height: urlEntry.height
+ color: "gray"
+ TextInput {
+ id: urlEntry
+ width: parent.width - closeButton.width
+ text: url1
+ activeFocusOnPress: false
+ }
+ Image {
+ id: closeButton
+ width: height
+ x: parent.width - width
+ height: parent.height
+ source: "close.png"
+ MouseArea {
+ anchors.fill: parent
+ onClicked: {
+ root.exec("InAppBrowser", "close", [0, 0])
+ }
+ }
+ }
+ }
+
+ property string usContext: "oxide://main-world/2"
+
+ function executeJS(scId, code) {
+ var req = _view.rootFrame.sendMessage(usContext, "EXECUTE", {code: code});
+
+ req.onreply = function(response) {
+ var code = 'cordova.callback(' + scId + ', JSON.parse(\'' + JSON.stringify(response.result) + '\'))';
+ console.warn(code);
+ cordova.javaScriptExecNeeded(code);
+ console.warn("RESP:" + JSON.stringify(response));
+ };
+ }
+
+ WebView {
+ width: parent.width
+ y: urlEntry.height
+ height: parent.height - y
+ url: url1
+ id: _view
+ onLoadingStateChanged: {
+ root.exec("InAppBrowser", "loadFinished", [_view.loading])
+ }
+ context: WebContext {
+ id: webcontext
+
+ userScripts: [
+ UserScript {
+ context: usContext
+ emulateGreasemonkey: true
+ url: "InAppBrowser_escapeScript.js"
+ }
+ ]
+ }
+ }
+}
diff --git a/plugins/cordova-plugin-inappbrowser/src/ubuntu/InAppBrowser_escapeScript.js b/plugins/cordova-plugin-inappbrowser/src/ubuntu/InAppBrowser_escapeScript.js
new file mode 100644
index 00000000..07661bb6
--- /dev/null
+++ b/plugins/cordova-plugin-inappbrowser/src/ubuntu/InAppBrowser_escapeScript.js
@@ -0,0 +1,29 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+*/
+
+oxide.addMessageHandler("EXECUTE", function(msg) {
+ var code = msg.args.code;
+ try {
+ msg.reply({result: eval(code)});
+ } catch(e) {
+ msg.error("Code threw exception: \"" + e + "\"");
+ }
+});
diff --git a/plugins/cordova-plugin-inappbrowser/src/ubuntu/close.png b/plugins/cordova-plugin-inappbrowser/src/ubuntu/close.png
new file mode 100644
index 00000000..56373d1f
--- /dev/null
+++ b/plugins/cordova-plugin-inappbrowser/src/ubuntu/close.png
Binary files differ
diff --git a/plugins/cordova-plugin-inappbrowser/src/ubuntu/inappbrowser.cpp b/plugins/cordova-plugin-inappbrowser/src/ubuntu/inappbrowser.cpp
new file mode 100644
index 00000000..c5a9e64a
--- /dev/null
+++ b/plugins/cordova-plugin-inappbrowser/src/ubuntu/inappbrowser.cpp
@@ -0,0 +1,105 @@
+/*
+ *
+ * Copyright 2013 Canonical Ltd.
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+*/
+
+#include <QQuickView>
+#include <QQuickItem>
+
+#include "inappbrowser.h"
+#include <cordova.h>
+
+Inappbrowser::Inappbrowser(Cordova *cordova): CPlugin(cordova), _eventCb(0) {
+}
+
+const char code[] = "\
+var component; \
+function createObject() { \
+ component = Qt.createComponent(%1); \
+ if (component.status == Component.Ready) \
+ finishCreation(); \
+ else \
+ component.statusChanged.connect(finishCreation); \
+} \
+function finishCreation() { \
+ CordovaWrapper.global.inappbrowser = component.createObject(root, \
+ {root: root, cordova: cordova, url1: %2}); \
+} \
+createObject()";
+
+const char EXIT_EVENT[] = "{type: 'exit'}";
+const char LOADSTART_EVENT[] = "{type: 'loadstart'}";
+const char LOADSTOP_EVENT[] = "{type: 'loadstop'}";
+const char LOADERROR_EVENT[] = "{type: 'loaderror'}";
+
+void Inappbrowser::open(int cb, int, const QString &url, const QString &, const QString &) {
+ assert(_eventCb == 0);
+
+ _eventCb = cb;
+
+ QString path = m_cordova->get_app_dir() + "/../qml/InAppBrowser.qml";
+ QString qml = QString(code)
+ .arg(CordovaInternal::format(path)).arg(CordovaInternal::format(url));
+ m_cordova->execQML(qml);
+}
+
+void Inappbrowser::show(int, int) {
+ m_cordova->execQML("CordovaWrapper.global.inappbrowser.visible = true");
+}
+
+void Inappbrowser::close(int, int) {
+ m_cordova->execQML("CordovaWrapper.global.inappbrowser.destroy()");
+ this->callbackWithoutRemove(_eventCb, EXIT_EVENT);
+ _eventCb = 0;
+}
+
+void Inappbrowser::injectStyleFile(int scId, int ecId, const QString& src, bool b) {
+ QString code("(function(d) { var c = d.createElement('link'); c.rel='stylesheet'; c.type='text/css'; c.href = %1; d.head.appendChild(c);})(document)");
+ code = code.arg(CordovaInternal::format(src));
+
+ injectScriptCode(scId, ecId, code, b);
+}
+
+void Inappbrowser::injectStyleCode(int scId, int ecId, const QString& src, bool b) {
+ QString code("(function(d) { var c = d.createElement('style'); c.innerHTML = %1; d.body.appendChild(c); })(document)");
+ code = code.arg(CordovaInternal::format(src));
+
+ injectScriptCode(scId, ecId, code, b);
+}
+
+void Inappbrowser::injectScriptFile(int scId, int ecId, const QString& src, bool b) {
+ QString code("(function(d) { var c = d.createElement('script'); c.src = %1; d.body.appendChild(c);})(document)");
+ code = code.arg(CordovaInternal::format(src));
+
+ injectScriptCode(scId, ecId, code, b);
+}
+
+void Inappbrowser::injectScriptCode(int scId, int, const QString& code, bool) {
+ m_cordova->execQML(QString("CordovaWrapper.global.inappbrowser.executeJS(%2, %1)").arg(CordovaInternal::format(code)).arg(scId));
+}
+
+void Inappbrowser::loadFinished(bool status) {
+ if (!status) {
+ this->callbackWithoutRemove(_eventCb, LOADSTOP_EVENT);
+ } else {
+ this->callbackWithoutRemove(_eventCb, LOADSTART_EVENT);
+ }
+}
diff --git a/plugins/cordova-plugin-inappbrowser/src/ubuntu/inappbrowser.h b/plugins/cordova-plugin-inappbrowser/src/ubuntu/inappbrowser.h
new file mode 100644
index 00000000..1da4e033
--- /dev/null
+++ b/plugins/cordova-plugin-inappbrowser/src/ubuntu/inappbrowser.h
@@ -0,0 +1,61 @@
+/*
+ *
+ * Copyright 2013 Canonical Ltd.
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+*/
+#ifndef INAPPBROWSER_H
+#define INAPPBROWSER_H
+
+#include <QtCore>
+#include <cplugin.h>
+
+class Inappbrowser: public CPlugin {
+ Q_OBJECT
+public:
+ Inappbrowser(Cordova *cordova);
+
+ virtual const QString fullName() override {
+ return Inappbrowser::fullID();
+ }
+
+ virtual const QString shortName() override {
+ return "InAppBrowser";
+ }
+
+ static const QString fullID() {
+ return "InAppBrowser";
+ }
+
+public slots:
+ void open(int cb, int, const QString &url, const QString &windowName, const QString &windowFeatures);
+ void show(int, int);
+ void close(int, int);
+ void injectStyleFile(int cb, int, const QString&, bool);
+ void injectStyleCode(int cb, int, const QString&, bool);
+ void injectScriptFile(int cb, int, const QString&, bool);
+ void injectScriptCode(int cb, int, const QString&, bool);
+
+ void loadFinished(bool status);
+
+private:
+ int _eventCb;
+};
+
+#endif
diff --git a/plugins/cordova-plugin-inappbrowser/src/windows/InAppBrowserProxy.js b/plugins/cordova-plugin-inappbrowser/src/windows/InAppBrowserProxy.js
new file mode 100644
index 00000000..817516e5
--- /dev/null
+++ b/plugins/cordova-plugin-inappbrowser/src/windows/InAppBrowserProxy.js
@@ -0,0 +1,326 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+*/
+
+/*jslint sloppy:true */
+/*global Windows:true, require, document, setTimeout, window, module */
+
+
+
+var cordova = require('cordova'),
+ channel = require('cordova/channel'),
+ urlutil = require('cordova/urlutil');
+
+var browserWrap,
+ popup,
+ navigationButtonsDiv,
+ navigationButtonsDivInner,
+ backButton,
+ forwardButton,
+ closeButton,
+ bodyOverflowStyle;
+
+// x-ms-webview is available starting from Windows 8.1 (platformId is 'windows')
+// http://msdn.microsoft.com/en-us/library/windows/apps/dn301831.aspx
+var isWebViewAvailable = cordova.platformId == 'windows';
+
+function attachNavigationEvents(element, callback) {
+ if (isWebViewAvailable) {
+ element.addEventListener("MSWebViewNavigationStarting", function (e) {
+ callback({ type: "loadstart", url: e.uri}, {keepCallback: true} );
+ });
+
+ element.addEventListener("MSWebViewNavigationCompleted", function (e) {
+ callback({ type: e.isSuccess ? "loadstop" : "loaderror", url: e.uri}, {keepCallback: true});
+ });
+
+ element.addEventListener("MSWebViewUnviewableContentIdentified", function (e) {
+ // WebView found the content to be not HTML.
+ // http://msdn.microsoft.com/en-us/library/windows/apps/dn609716.aspx
+ callback({ type: "loaderror", url: e.uri}, {keepCallback: true});
+ });
+
+ element.addEventListener("MSWebViewContentLoading", function (e) {
+ if (navigationButtonsDiv) {
+ backButton.disabled = !popup.canGoBack;
+ forwardButton.disabled = !popup.canGoForward;
+ }
+ });
+ } else {
+ var onError = function () {
+ callback({ type: "loaderror", url: this.contentWindow.location}, {keepCallback: true});
+ };
+
+ element.addEventListener("unload", function () {
+ callback({ type: "loadstart", url: this.contentWindow.location}, {keepCallback: true});
+ });
+
+ element.addEventListener("load", function () {
+ callback({ type: "loadstop", url: this.contentWindow.location}, {keepCallback: true});
+ });
+
+ element.addEventListener("error", onError);
+ element.addEventListener("abort", onError);
+ }
+}
+
+var IAB = {
+ close: function (win, lose) {
+ if (browserWrap) {
+ if (win) win({ type: "exit" });
+
+ browserWrap.parentNode.removeChild(browserWrap);
+ // Reset body overflow style to initial value
+ document.body.style.msOverflowStyle = bodyOverflowStyle;
+ browserWrap = null;
+ popup = null;
+ }
+ },
+ show: function (win, lose) {
+ if (browserWrap) {
+ browserWrap.style.display = "block";
+ }
+ },
+ open: function (win, lose, args) {
+ var strUrl = args[0],
+ target = args[1],
+ features = args[2],
+ url;
+
+ if (target === "_system") {
+ url = new Windows.Foundation.Uri(strUrl);
+ Windows.System.Launcher.launchUriAsync(url);
+ } else if (target === "_self" || !target) {
+ window.location = strUrl;
+ } else {
+ // "_blank" or anything else
+ if (!browserWrap) {
+ var browserWrapStyle = document.createElement('link');
+ browserWrapStyle.rel = "stylesheet";
+ browserWrapStyle.type = "text/css";
+ browserWrapStyle.href = urlutil.makeAbsolute("/www/css/inappbrowser.css");
+
+ document.head.appendChild(browserWrapStyle);
+
+ browserWrap = document.createElement("div");
+ browserWrap.className = "inAppBrowserWrap";
+
+ if (features.indexOf("fullscreen=yes") > -1) {
+ browserWrap.classList.add("inAppBrowserWrapFullscreen");
+ }
+
+ // Save body overflow style to be able to reset it back later
+ bodyOverflowStyle = document.body.style.msOverflowStyle;
+
+ browserWrap.onclick = function () {
+ setTimeout(function () {
+ IAB.close(win);
+ }, 0);
+ };
+
+ document.body.appendChild(browserWrap);
+ // Hide scrollbars for the whole body while inappbrowser's window is open
+ document.body.style.msOverflowStyle = "none";
+ }
+
+ if (features.indexOf("hidden=yes") !== -1) {
+ browserWrap.style.display = "none";
+ }
+
+ popup = document.createElement(isWebViewAvailable ? "x-ms-webview" : "iframe");
+ if (popup instanceof HTMLIFrameElement) {
+ // For iframe we need to override bacground color of parent element here
+ // otherwise pages without background color set will have transparent background
+ popup.style.backgroundColor = "white";
+ }
+ popup.style.borderWidth = "0px";
+ popup.style.width = "100%";
+
+ browserWrap.appendChild(popup);
+
+ if (features.indexOf("location=yes") !== -1 || features.indexOf("location") === -1) {
+ popup.style.height = "calc(100% - 60px)";
+
+ navigationButtonsDiv = document.createElement("div");
+ navigationButtonsDiv.style.height = "60px";
+ navigationButtonsDiv.style.backgroundColor = "#404040";
+ navigationButtonsDiv.style.zIndex = "999";
+ navigationButtonsDiv.onclick = function (e) {
+ e.cancelBubble = true;
+ };
+
+ navigationButtonsDivInner = document.createElement("div");
+ navigationButtonsDivInner.style.paddingTop = "10px";
+ navigationButtonsDivInner.style.height = "50px";
+ navigationButtonsDivInner.style.width = "160px";
+ navigationButtonsDivInner.style.margin = "0 auto";
+ navigationButtonsDivInner.style.backgroundColor = "#404040";
+ navigationButtonsDivInner.style.zIndex = "999";
+ navigationButtonsDivInner.onclick = function (e) {
+ e.cancelBubble = true;
+ };
+
+
+ backButton = document.createElement("button");
+ backButton.style.width = "40px";
+ backButton.style.height = "40px";
+ backButton.style.borderRadius = "40px";
+
+ backButton.innerText = "<-";
+ backButton.addEventListener("click", function (e) {
+ if (popup.canGoBack)
+ popup.goBack();
+ });
+
+ forwardButton = document.createElement("button");
+ forwardButton.style.marginLeft = "20px";
+ forwardButton.style.width = "40px";
+ forwardButton.style.height = "40px";
+ forwardButton.style.borderRadius = "40px";
+
+ forwardButton.innerText = "->";
+ forwardButton.addEventListener("click", function (e) {
+ if (popup.canGoForward)
+ popup.goForward();
+ });
+
+ closeButton = document.createElement("button");
+ closeButton.style.marginLeft = "20px";
+ closeButton.style.width = "40px";
+ closeButton.style.height = "40px";
+ closeButton.style.borderRadius = "40px";
+
+ closeButton.innerText = "x";
+ closeButton.addEventListener("click", function (e) {
+ setTimeout(function () {
+ IAB.close(win);
+ }, 0);
+ });
+
+ if (!isWebViewAvailable) {
+ // iframe navigation is not yet supported
+ backButton.disabled = true;
+ forwardButton.disabled = true;
+ }
+
+ navigationButtonsDivInner.appendChild(backButton);
+ navigationButtonsDivInner.appendChild(forwardButton);
+ navigationButtonsDivInner.appendChild(closeButton);
+ navigationButtonsDiv.appendChild(navigationButtonsDivInner);
+
+ browserWrap.appendChild(navigationButtonsDiv);
+ } else {
+ popup.style.height = "100%";
+ }
+
+ // start listening for navigation events
+ attachNavigationEvents(popup, win);
+
+ if (isWebViewAvailable) {
+ strUrl = strUrl.replace("ms-appx://", "ms-appx-web://");
+ }
+ popup.src = strUrl;
+ }
+ },
+
+ injectScriptCode: function (win, fail, args) {
+ var code = args[0],
+ hasCallback = args[1];
+
+ if (isWebViewAvailable && browserWrap && popup) {
+ var op = popup.invokeScriptAsync("eval", code);
+ op.oncomplete = function (e) {
+ var result = [e.target.result];
+ hasCallback && win(result);
+ };
+ op.onerror = function () { };
+ op.start();
+ }
+ },
+
+ injectScriptFile: function (win, fail, args) {
+ var filePath = args[0],
+ hasCallback = args[1];
+
+ if (!!filePath) {
+ filePath = urlutil.makeAbsolute(filePath);
+ }
+
+ if (isWebViewAvailable && browserWrap && popup) {
+ var uri = new Windows.Foundation.Uri(filePath);
+ Windows.Storage.StorageFile.getFileFromApplicationUriAsync(uri).done(function (file) {
+ Windows.Storage.FileIO.readTextAsync(file).done(function (code) {
+ var op = popup.invokeScriptAsync("eval", code);
+ op.oncomplete = function(e) {
+ var result = [e.target.result];
+ hasCallback && win(result);
+ };
+ op.onerror = function () { };
+ op.start();
+ });
+ });
+ }
+ },
+
+ injectStyleCode: function (win, fail, args) {
+ var code = args[0],
+ hasCallback = args[1];
+
+ if (isWebViewAvailable && browserWrap && popup) {
+ injectCSS(popup, code, hasCallback && win);
+ }
+ },
+
+ injectStyleFile: function (win, fail, args) {
+ var filePath = args[0],
+ hasCallback = args[1];
+
+ filePath = filePath && urlutil.makeAbsolute(filePath);
+
+ if (isWebViewAvailable && browserWrap && popup) {
+ var uri = new Windows.Foundation.Uri(filePath);
+ Windows.Storage.StorageFile.getFileFromApplicationUriAsync(uri).then(function (file) {
+ return Windows.Storage.FileIO.readTextAsync(file);
+ }).done(function (code) {
+ injectCSS(popup, code, hasCallback && win);
+ }, function () {
+ // no-op, just catch an error
+ });
+ }
+ }
+};
+
+function injectCSS (webView, cssCode, callback) {
+ // This will automatically escape all thing that we need (quotes, slashes, etc.)
+ var escapedCode = JSON.stringify(cssCode);
+ var evalWrapper = "(function(d){var c=d.createElement('style');c.innerHTML=%s;d.head.appendChild(c);})(document)"
+ .replace('%s', escapedCode);
+
+ var op = webView.invokeScriptAsync("eval", evalWrapper);
+ op.oncomplete = function() {
+ callback && callback([]);
+ };
+ op.onerror = function () { };
+ op.start();
+}
+
+module.exports = IAB;
+
+require("cordova/exec/proxy").add("InAppBrowser", module.exports);
diff --git a/plugins/cordova-plugin-inappbrowser/src/wp/InAppBrowser.cs b/plugins/cordova-plugin-inappbrowser/src/wp/InAppBrowser.cs
new file mode 100644
index 00000000..ddb51227
--- /dev/null
+++ b/plugins/cordova-plugin-inappbrowser/src/wp/InAppBrowser.cs
@@ -0,0 +1,515 @@
+/*
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+*/
+
+using System;
+using System.Diagnostics;
+using System.IO;
+using System.Runtime.Serialization;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Media;
+using Microsoft.Phone.Controls;
+using Microsoft.Phone.Shell;
+
+#if WP8
+using System.Threading.Tasks;
+using Windows.ApplicationModel;
+using Windows.Storage;
+using Windows.System;
+
+//Use alias in case Cordova File Plugin is enabled. Then the File class will be declared in both and error will occur.
+using IOFile = System.IO.File;
+#else
+using Microsoft.Phone.Tasks;
+#endif
+
+namespace WPCordovaClassLib.Cordova.Commands
+{
+ [DataContract]
+ public class BrowserOptions
+ {
+ [DataMember]
+ public string url;
+
+ [DataMember]
+ public bool isGeolocationEnabled;
+ }
+
+ public class InAppBrowser : BaseCommand
+ {
+
+ private static WebBrowser browser;
+ private static ApplicationBarIconButton backButton;
+ private static ApplicationBarIconButton fwdButton;
+
+ protected ApplicationBar AppBar;
+
+ protected bool ShowLocation {get;set;}
+ protected bool StartHidden {get;set;}
+
+ protected string NavigationCallbackId { get; set; }
+
+ public void open(string options)
+ {
+ // reset defaults on ShowLocation + StartHidden features
+ ShowLocation = true;
+ StartHidden = false;
+
+ string[] args = JSON.JsonHelper.Deserialize<string[]>(options);
+ //BrowserOptions opts = JSON.JsonHelper.Deserialize<BrowserOptions>(options);
+ string urlLoc = args[0];
+ string target = args[1];
+ string featString = args[2];
+ this.NavigationCallbackId = args[3];
+
+ if (!string.IsNullOrEmpty(featString))
+ {
+ string[] features = featString.Split(',');
+ foreach (string str in features)
+ {
+ try
+ {
+ string[] split = str.Split('=');
+ switch (split[0])
+ {
+ case "location":
+ ShowLocation = split[1].StartsWith("yes", StringComparison.OrdinalIgnoreCase);
+ break;
+ case "hidden":
+ StartHidden = split[1].StartsWith("yes", StringComparison.OrdinalIgnoreCase);
+ break;
+ }
+ }
+ catch (Exception)
+ {
+ // some sort of invalid param was passed, moving on ...
+ }
+ }
+ }
+ /*
+ _self - opens in the Cordova WebView if url is in the white-list, else it opens in the InAppBrowser
+ _blank - always open in the InAppBrowser
+ _system - always open in the system web browser
+ */
+ switch (target)
+ {
+ case "_blank":
+ ShowInAppBrowser(urlLoc);
+ break;
+ case "_self":
+ ShowCordovaBrowser(urlLoc);
+ break;
+ case "_system":
+ ShowSystemBrowser(urlLoc);
+ break;
+ }
+ }
+
+ public void show(string options)
+ {
+ string[] args = JSON.JsonHelper.Deserialize<string[]>(options);
+
+
+ if (browser != null)
+ {
+ Deployment.Current.Dispatcher.BeginInvoke(() =>
+ {
+ browser.Visibility = Visibility.Visible;
+ AppBar.IsVisible = true;
+ });
+ }
+ }
+
+ public void injectScriptCode(string options)
+ {
+ string[] args = JSON.JsonHelper.Deserialize<string[]>(options);
+
+ bool bCallback = false;
+ if (bool.TryParse(args[1], out bCallback)) { };
+
+ string callbackId = args[2];
+
+ if (browser != null)
+ {
+ Deployment.Current.Dispatcher.BeginInvoke(() =>
+ {
+ var res = browser.InvokeScript("eval", new string[] { args[0] });
+
+ if (bCallback)
+ {
+ PluginResult result = new PluginResult(PluginResult.Status.OK, res.ToString());
+ result.KeepCallback = false;
+ this.DispatchCommandResult(result);
+ }
+
+ });
+ }
+ }
+
+ public void injectScriptFile(string options)
+ {
+ Debug.WriteLine("Error : Windows Phone cordova-plugin-inappbrowser does not currently support executeScript");
+ string[] args = JSON.JsonHelper.Deserialize<string[]>(options);
+ // throw new NotImplementedException("Windows Phone does not currently support 'executeScript'");
+ }
+
+ public void injectStyleCode(string options)
+ {
+ Debug.WriteLine("Error : Windows Phone cordova-plugin-inappbrowser does not currently support insertCSS");
+ return;
+
+ //string[] args = JSON.JsonHelper.Deserialize<string[]>(options);
+ //bool bCallback = false;
+ //if (bool.TryParse(args[1], out bCallback)) { };
+
+ //string callbackId = args[2];
+
+ //if (browser != null)
+ //{
+ //Deployment.Current.Dispatcher.BeginInvoke(() =>
+ //{
+ // if (bCallback)
+ // {
+ // string cssInsertString = "try{(function(doc){var c = '<style>body{background-color:#ffff00;}</style>'; doc.head.innerHTML += c;})(document);}catch(ex){alert('oops : ' + ex.message);}";
+ // //cssInsertString = cssInsertString.Replace("_VALUE_", args[0]);
+ // Debug.WriteLine("cssInsertString = " + cssInsertString);
+ // var res = browser.InvokeScript("eval", new string[] { cssInsertString });
+ // if (bCallback)
+ // {
+ // PluginResult result = new PluginResult(PluginResult.Status.OK, res.ToString());
+ // result.KeepCallback = false;
+ // this.DispatchCommandResult(result);
+ // }
+ // }
+
+ //});
+ //}
+ }
+
+ public void injectStyleFile(string options)
+ {
+ Debug.WriteLine("Error : Windows Phone cordova-plugin-inappbrowser does not currently support insertCSS");
+ return;
+
+ //string[] args = JSON.JsonHelper.Deserialize<string[]>(options);
+ //throw new NotImplementedException("Windows Phone does not currently support 'insertCSS'");
+ }
+
+ private void ShowCordovaBrowser(string url)
+ {
+ Uri loc = new Uri(url, UriKind.RelativeOrAbsolute);
+ Deployment.Current.Dispatcher.BeginInvoke(() =>
+ {
+ PhoneApplicationFrame frame = Application.Current.RootVisual as PhoneApplicationFrame;
+ if (frame != null)
+ {
+ PhoneApplicationPage page = frame.Content as PhoneApplicationPage;
+ if (page != null)
+ {
+ CordovaView cView = page.FindName("CordovaView") as CordovaView;
+ if (cView != null)
+ {
+ WebBrowser br = cView.Browser;
+ br.Navigate2(loc);
+ }
+ }
+
+ }
+ });
+ }
+
+#if WP8
+ private async void ShowSystemBrowser(string url)
+ {
+ var pathUri = new Uri(url, UriKind.Absolute);
+ if (pathUri.Scheme == Uri.UriSchemeHttp || pathUri.Scheme == Uri.UriSchemeHttps)
+ {
+ await Launcher.LaunchUriAsync(pathUri);
+ return;
+ }
+
+ var file = await GetFile(pathUri.AbsolutePath.Replace('/', Path.DirectorySeparatorChar));
+ if (file != null)
+ {
+ await Launcher.LaunchFileAsync(file);
+ }
+ else
+ {
+ Debug.WriteLine("File not found.");
+ }
+ }
+
+ private async Task<StorageFile> GetFile(string fileName)
+ {
+ //first try to get the file from the isolated storage
+ var localFolder = ApplicationData.Current.LocalFolder;
+ if (IOFile.Exists(Path.Combine(localFolder.Path, fileName)))
+ {
+ return await localFolder.GetFileAsync(fileName);
+ }
+
+ //if file is not found try to get it from the xap
+ var filePath = Path.Combine(Package.Current.InstalledLocation.Path, fileName);
+ if (IOFile.Exists(filePath))
+ {
+ return await StorageFile.GetFileFromPathAsync(filePath);
+ }
+
+ return null;
+ }
+#else
+ private void ShowSystemBrowser(string url)
+ {
+ WebBrowserTask webBrowserTask = new WebBrowserTask();
+ webBrowserTask.Uri = new Uri(url, UriKind.Absolute);
+ webBrowserTask.Show();
+ }
+#endif
+
+ private void ShowInAppBrowser(string url)
+ {
+ Uri loc = new Uri(url, UriKind.RelativeOrAbsolute);
+
+ Deployment.Current.Dispatcher.BeginInvoke(() =>
+ {
+ if (browser != null)
+ {
+ //browser.IsGeolocationEnabled = opts.isGeolocationEnabled;
+ browser.Navigate2(loc);
+ }
+ else
+ {
+ PhoneApplicationFrame frame = Application.Current.RootVisual as PhoneApplicationFrame;
+ if (frame != null)
+ {
+ PhoneApplicationPage page = frame.Content as PhoneApplicationPage;
+
+ string baseImageUrl = "Images/";
+
+ if (page != null)
+ {
+ Grid grid = page.FindName("LayoutRoot") as Grid;
+ if (grid != null)
+ {
+ browser = new WebBrowser();
+ browser.IsScriptEnabled = true;
+ browser.LoadCompleted += new System.Windows.Navigation.LoadCompletedEventHandler(browser_LoadCompleted);
+
+ browser.Navigating += new EventHandler<NavigatingEventArgs>(browser_Navigating);
+ browser.NavigationFailed += new System.Windows.Navigation.NavigationFailedEventHandler(browser_NavigationFailed);
+ browser.Navigated += new EventHandler<System.Windows.Navigation.NavigationEventArgs>(browser_Navigated);
+ browser.Navigate2(loc);
+
+ if (StartHidden)
+ {
+ browser.Visibility = Visibility.Collapsed;
+ }
+
+ //browser.IsGeolocationEnabled = opts.isGeolocationEnabled;
+ grid.Children.Add(browser);
+ }
+
+ ApplicationBar bar = new ApplicationBar();
+ bar.BackgroundColor = Colors.Gray;
+ bar.IsMenuEnabled = false;
+
+ backButton = new ApplicationBarIconButton();
+ backButton.Text = "Back";
+
+ backButton.IconUri = new Uri(baseImageUrl + "appbar.back.rest.png", UriKind.Relative);
+ backButton.Click += new EventHandler(backButton_Click);
+ bar.Buttons.Add(backButton);
+
+
+ fwdButton = new ApplicationBarIconButton();
+ fwdButton.Text = "Forward";
+ fwdButton.IconUri = new Uri(baseImageUrl + "appbar.next.rest.png", UriKind.Relative);
+ fwdButton.Click += new EventHandler(fwdButton_Click);
+ bar.Buttons.Add(fwdButton);
+
+ ApplicationBarIconButton closeBtn = new ApplicationBarIconButton();
+ closeBtn.Text = "Close";
+ closeBtn.IconUri = new Uri(baseImageUrl + "appbar.close.rest.png", UriKind.Relative);
+ closeBtn.Click += new EventHandler(closeBtn_Click);
+ bar.Buttons.Add(closeBtn);
+
+ page.ApplicationBar = bar;
+ bar.IsVisible = !StartHidden;
+ AppBar = bar;
+
+ page.BackKeyPress += page_BackKeyPress;
+
+ }
+
+ }
+ }
+ });
+ }
+
+ void page_BackKeyPress(object sender, System.ComponentModel.CancelEventArgs e)
+ {
+#if WP8
+ if (browser.CanGoBack)
+ {
+ browser.GoBack();
+ }
+ else
+ {
+ close();
+ }
+ e.Cancel = true;
+#else
+ browser.InvokeScript("execScript", "history.back();");
+#endif
+ }
+
+ void browser_LoadCompleted(object sender, System.Windows.Navigation.NavigationEventArgs e)
+ {
+
+ }
+
+ void fwdButton_Click(object sender, EventArgs e)
+ {
+ if (browser != null)
+ {
+ try
+ {
+#if WP8
+ browser.GoForward();
+#else
+ browser.InvokeScript("execScript", "history.forward();");
+#endif
+ }
+ catch (Exception)
+ {
+
+ }
+ }
+ }
+
+ void backButton_Click(object sender, EventArgs e)
+ {
+ if (browser != null)
+ {
+ try
+ {
+#if WP8
+ browser.GoBack();
+#else
+ browser.InvokeScript("execScript", "history.back();");
+#endif
+ }
+ catch (Exception)
+ {
+
+ }
+ }
+ }
+
+ void closeBtn_Click(object sender, EventArgs e)
+ {
+ this.close();
+ }
+
+
+ public void close(string options = "")
+ {
+ if (browser != null)
+ {
+ Deployment.Current.Dispatcher.BeginInvoke(() =>
+ {
+ PhoneApplicationFrame frame = Application.Current.RootVisual as PhoneApplicationFrame;
+ if (frame != null)
+ {
+ PhoneApplicationPage page = frame.Content as PhoneApplicationPage;
+ if (page != null)
+ {
+ Grid grid = page.FindName("LayoutRoot") as Grid;
+ if (grid != null)
+ {
+ grid.Children.Remove(browser);
+ }
+ page.ApplicationBar = null;
+ page.BackKeyPress -= page_BackKeyPress;
+ }
+ }
+
+ browser = null;
+ string message = "{\"type\":\"exit\"}";
+ PluginResult result = new PluginResult(PluginResult.Status.OK, message);
+ result.KeepCallback = false;
+ this.DispatchCommandResult(result, NavigationCallbackId);
+ });
+ }
+ }
+
+ void browser_Navigated(object sender, System.Windows.Navigation.NavigationEventArgs e)
+ {
+#if WP8
+ if (browser != null)
+ {
+ backButton.IsEnabled = browser.CanGoBack;
+ fwdButton.IsEnabled = browser.CanGoForward;
+
+ }
+#endif
+ string message = "{\"type\":\"loadstop\", \"url\":\"" + e.Uri.OriginalString + "\"}";
+ PluginResult result = new PluginResult(PluginResult.Status.OK, message);
+ result.KeepCallback = true;
+ this.DispatchCommandResult(result, NavigationCallbackId);
+ }
+
+ void browser_NavigationFailed(object sender, System.Windows.Navigation.NavigationFailedEventArgs e)
+ {
+ string message = "{\"type\":\"error\",\"url\":\"" + e.Uri.OriginalString + "\"}";
+ PluginResult result = new PluginResult(PluginResult.Status.ERROR, message);
+ result.KeepCallback = true;
+ this.DispatchCommandResult(result, NavigationCallbackId);
+ }
+
+ void browser_Navigating(object sender, NavigatingEventArgs e)
+ {
+ string message = "{\"type\":\"loadstart\",\"url\":\"" + e.Uri.OriginalString + "\"}";
+ PluginResult result = new PluginResult(PluginResult.Status.OK, message);
+ result.KeepCallback = true;
+ this.DispatchCommandResult(result, NavigationCallbackId);
+ }
+
+ }
+
+ internal static class WebBrowserExtensions
+ {
+ /// <summary>
+ /// Improved method to initiate request to the provided URI. Supports 'data:text/html' urls.
+ /// </summary>
+ /// <param name="browser">The browser instance</param>
+ /// <param name="uri">The requested uri</param>
+ internal static void Navigate2(this WebBrowser browser, Uri uri)
+ {
+ // IE10 does not support data uri so we use NavigateToString method instead
+ if (uri.Scheme == "data")
+ {
+ // we should remove the scheme identifier and unescape the uri
+ string uriString = Uri.UnescapeDataString(uri.AbsoluteUri);
+ // format is 'data:text/html, ...'
+ string html = new System.Text.RegularExpressions.Regex("^data:text/html,").Replace(uriString, "");
+ browser.NavigateToString(html);
+ }
+ else
+ {
+ browser.Navigate(uri);
+ }
+ }
+ }
+}