This is an automated email from the ASF dual-hosted git repository.

GitToTheHub pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/cordova-android.git


The following commit(s) were added to refs/heads/master by this push:
     new 53943274 fix(android): handle each back action once (#2029)
53943274 is described below

commit 53943274dfca1c72db069daace27ae7fedb9cd43
Author: Manuel Beck <[email protected]>
AuthorDate: Mon Oct 26 20:14:27 2026 +0100

    fix(android): handle each back action once (#2029)
    
    - On Android 16, one Back key press can invoke the AndroidX back callback 
and then deliver the original key to the WebView. Cordova's synthetic key 
redispatch therefore fires backbutton twice, causing applications to navigate 
back two screens. Edge swipes fire once.
    - Replace synthetic DOWN/UP keys with a shared back-action handler. 
Callbacks and back key releases use the same action order: dismiss a custom 
view, notify a JavaScript backbutton listener, or navigate WebView history. 
Unhandled callback actions delegate to Android with the callback temporarily 
disabled to avoid recursion.
    - Keep a single callback timestamp in the WebView to consume an original 
key already handled by the callback. Clear it when the JavaScript override is 
removed. This avoids a debounce interval and preserves rapid consecutive inputs.
    - Fixes #2028
---
 .../src/org/apache/cordova/CordovaWebView.java     |  30 +++
 .../src/org/apache/cordova/CordovaWebViewImpl.java |  78 ++++++--
 framework/src/org/apache/cordova/CoreAndroid.java  |  34 ++--
 .../cordova/unittests/BackButtonDispatchTest.java  | 206 +++++++++++++++++++++
 .../main/assets/www/backbuttondispatch/index.html  |  37 ++++
 .../main/assets/www/backbuttondispatch/second.html |  21 +++
 6 files changed, 374 insertions(+), 32 deletions(-)

diff --git a/framework/src/org/apache/cordova/CordovaWebView.java 
b/framework/src/org/apache/cordova/CordovaWebView.java
index f57858d6..0bfb4e3f 100644
--- a/framework/src/org/apache/cordova/CordovaWebView.java
+++ b/framework/src/org/apache/cordova/CordovaWebView.java
@@ -57,6 +57,36 @@ public interface CordovaWebView {
 
     boolean backHistory();
 
+    /**
+     * Performs Cordova's handling of a single Android back action.
+     *
+     * <p>A back press can reach Cordova in two ways:</p>
+     * <ul>
+     *   <li>As a back-press callback. Cordova's {@link CoreAndroid} plugin 
registers one
+     *       while the back button is overridden, that is while a JavaScript 
"backbutton"
+     *       listener exists, and calls this method from it.</li>
+     *   <li>As a back key event, handled by the web view's key event 
handling. This
+     *       also works without a "backbutton" listener.</li>
+     * </ul>
+     * <p>Depending on the Android version, one back press may arrive through 
one or both
+     * ways. The implementation makes sure that the press is handled only 
once.</p>
+     *
+     * <p>Android invokes the back-press callback on the UI thread. This 
method does not
+     * switch threads itself, so any other caller must also call it on the UI 
thread.</p>
+     *
+     * <p>The first applicable action is performed, in this order:</p>
+     * <ol>
+     *   <li>Dismiss a custom view, such as a full-screen video.</li>
+     *   <li>Fire the JavaScript "backbutton" event, if the back button is 
overridden.</li>
+     *   <li>Navigate back in the WebView history, if possible.</li>
+     * </ol>
+     *
+     * @return true if one of these actions was performed and the back action 
is consumed.
+     *         false if Cordova did nothing, so the caller should let Android 
handle the
+     *         back action, for example by finishing the activity.
+     */
+    boolean handleBackButton();
+
     void handlePause(boolean keepRunning);
 
     void onNewIntent(Intent intent);
diff --git a/framework/src/org/apache/cordova/CordovaWebViewImpl.java 
b/framework/src/org/apache/cordova/CordovaWebViewImpl.java
index 039a503e..0fd8f79f 100644
--- a/framework/src/org/apache/cordova/CordovaWebViewImpl.java
+++ b/framework/src/org/apache/cordova/CordovaWebViewImpl.java
@@ -23,6 +23,7 @@ import android.content.ActivityNotFoundException;
 import android.content.Context;
 import android.content.Intent;
 import android.net.Uri;
+import android.os.SystemClock;
 import android.view.Gravity;
 import android.view.KeyEvent;
 import android.view.View;
@@ -64,6 +65,12 @@ public class CordovaWebViewImpl implements CordovaWebView {
     private NativeToJsMessageQueue nativeToJsMessageQueue;
     private EngineClient engineClient = new EngineClient();
     private boolean hasPausedEver;
+    // A back press can reach us as a back-press callback (handleBackButton) 
and as back key
+    // events (EngineClient.onDispatchKeyEvent). Depending on the Android 
version, one or
+    // both arrive. On Android 16 both do: the callback runs when the key is 
released, just
+    // before its key-up event. This is the time when the callback last 
handled a back
+    // press, or -1 if none, so that key-up can be ignored.
+    private long lastBackCallbackTime = -1;
 
     // The URL passed to loadUrl(), not necessarily the URL of the current 
page.
     String loadedUrl;
@@ -402,8 +409,49 @@ public class CordovaWebViewImpl implements CordovaWebView {
         appPlugin.fireJavascriptEvent(event);
     }
 
+    /**
+     * Called by the Android back-press callback. Performs the back action 
and, if it was
+     * handled, records the time so the duplicate key event of the same press 
is ignored.
+     */
+    @Override
+    public boolean handleBackButton() {
+        long callbackTime = SystemClock.uptimeMillis();
+        boolean handled = performBackButtonAction();
+        if (handled) {
+            lastBackCallbackTime = callbackTime;
+        }
+        return handled;
+    }
+
+    /**
+     * Performs the first applicable back action: dismiss a custom view, fire 
the
+     * JavaScript "backbutton" event if the back button is overridden, or 
navigate back in
+     * the WebView history. Called by {@link #handleBackButton()} (back-press 
callback) and
+     * by the back key handling in {@link 
EngineClient#onDispatchKeyEvent(KeyEvent)}
+     * (back key event).
+     *
+     * @return true if an action was performed, false if Android should handle 
the back action
+     */
+    private boolean performBackButtonAction() {
+        // 1. A custom view (for example a full-screen video) is showing: 
close it.
+        if (mCustomView != null) {
+            hideCustomView();
+            return true;
+        }
+        // 2. The app listens to the "backbutton" event: let JavaScript decide 
what to do.
+        if (boundKeyCodes.contains(KeyEvent.KEYCODE_BACK)) {
+            sendJavascriptEvent("backbutton");
+            return true;
+        }
+        // 3. Otherwise go back in the WebView history. Returns false if there 
is none.
+        return engine.goBack();
+    }
+
     @Override
     public void setButtonPlumbedToJs(int keyCode, boolean override) {
+        if (keyCode == KeyEvent.KEYCODE_BACK && !override) {
+            lastBackCallbackTime = -1;
+        }
         switch (keyCode) {
             case KeyEvent.KEYCODE_VOLUME_DOWN:
             case KeyEvent.KEYCODE_VOLUME_UP:
@@ -612,20 +660,23 @@ public class CordovaWebViewImpl implements CordovaWebView 
{
         @Override
         public Boolean onDispatchKeyEvent(KeyEvent event) {
             int keyCode = event.getKeyCode();
-            boolean isBackButton = keyCode == KeyEvent.KEYCODE_BACK;
-            if (event.getAction() == KeyEvent.ACTION_DOWN) {
-                if (isBackButton && mCustomView != null) {
+            if (keyCode == KeyEvent.KEYCODE_BACK) {
+                // On Android 16, one back press reaches us twice: the back 
callback runs
+                // when the key is released, just before the key-up event of 
the same press
+                // arrives here. Skip that key-up (an event not newer than the 
callback)
+                // so back is handled only once.
+                if (event.getEventTime() <= lastBackCallbackTime) {
                     return true;
-                } else if (boundKeyCodes.contains(keyCode)) {
-                    return true;
-                } else if (isBackButton) {
-                    return engine.canGoBack();
                 }
-            } else if (event.getAction() == KeyEvent.ACTION_UP) {
-                if (isBackButton && mCustomView != null) {
-                    hideCustomView();
+                if (event.getAction() == KeyEvent.ACTION_DOWN) {
+                    return mCustomView != null || 
boundKeyCodes.contains(keyCode) || engine.canGoBack();
+                } else if (event.getAction() == KeyEvent.ACTION_UP) {
+                    return performBackButtonAction();
+                }
+            } else if (boundKeyCodes.contains(keyCode)) {
+                if (event.getAction() == KeyEvent.ACTION_DOWN) {
                     return true;
-                } else if (boundKeyCodes.contains(keyCode)) {
+                } else if (event.getAction() == KeyEvent.ACTION_UP) {
                     String eventName = null;
                     switch (keyCode) {
                         case KeyEvent.KEYCODE_VOLUME_DOWN:
@@ -640,16 +691,11 @@ public class CordovaWebViewImpl implements CordovaWebView 
{
                         case KeyEvent.KEYCODE_MENU:
                             eventName = "menubutton";
                             break;
-                        case KeyEvent.KEYCODE_BACK:
-                            eventName = "backbutton";
-                            break;
                     }
                     if (eventName != null) {
                         sendJavascriptEvent(eventName);
                         return true;
                     }
-                } else if (isBackButton) {
-                    return engine.goBack();
                 }
             }
             return null;
diff --git a/framework/src/org/apache/cordova/CoreAndroid.java 
b/framework/src/org/apache/cordova/CoreAndroid.java
index 969cc296..6c811137 100755
--- a/framework/src/org/apache/cordova/CoreAndroid.java
+++ b/framework/src/org/apache/cordova/CoreAndroid.java
@@ -29,7 +29,6 @@ import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
 import android.os.Build;
-import android.os.SystemClock;
 import android.content.IntentFilter;
 import android.telephony.TelephonyManager;
 import android.view.KeyEvent;
@@ -269,7 +268,7 @@ public class CoreAndroid extends CordovaPlugin {
                             backCallback = new OnBackPressedCallback(true) {
                                 @Override
                                 public void handleOnBackPressed() {
-                                    dispatchBackKeyEvent();
+                                    handleBackPressed();
                                 }
                             };
                             OnBackPressedDispatcherOwner 
backPressedDispatcherOwner = cordova.getActivity();
@@ -290,23 +289,26 @@ public class CoreAndroid extends CordovaPlugin {
         });
     }
 
-    private void dispatchBackKeyEvent() {
-        // Build a synthetic BACK key press (DOWN + UP) and route it through 
the WebView.
-        // This lets Cordova's existing key handling fire the JS "backbutton" 
event.
-        final long eventTime = SystemClock.uptimeMillis();
-        final KeyEvent downEvent = new KeyEvent(eventTime, eventTime, 
KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_BACK, 0);
-        final KeyEvent upEvent = new KeyEvent(eventTime, eventTime, 
KeyEvent.ACTION_UP, KeyEvent.KEYCODE_BACK, 0);
-
-        final boolean handledDown = 
webView.getView().dispatchKeyEvent(downEvent);
-        final boolean handledUp = webView.getView().dispatchKeyEvent(upEvent);
-
-        // If either event was consumed, Cordova/WebView already handled back 
behavior.
-        if (handledDown || handledUp) {
+    /**
+     * Called by the back-press callback registered in {@link 
#overrideBackbutton(boolean)},
+     * so it only runs while the back button is overridden. The JavaScript 
side enables the
+     * override when the first "backbutton" event listener is added and 
disables it when
+     * the last one is removed.
+     *
+     * <p>Lets the web view handle the back press first. Only if it reports 
the press as
+     * unhandled is the press passed on to Android's default handling. 
Normally this does
+     * not happen while the override is active, because the JavaScript 
"backbutton" event
+     * handles it.</p>
+     */
+    private void handleBackPressed() {
+        // Let Cordova handle the back press first: close a custom view, fire 
the JS
+        // "backbutton" event, or go back in the WebView history. If it did, 
we are done.
+        if (webView.handleBackButton()) {
             return;
         }
 
-        // Otherwise, delegate to Android's default back dispatcher.
-        // Temporarily disable this callback to avoid recursive re-entry.
+        // Cordova did nothing, so pass the back press on to Android's default 
handling.
+        // This callback is disabled meanwhile, otherwise the dispatcher would 
call it again.
         backCallback.setEnabled(false);
         try {
             cordova.getActivity().getOnBackPressedDispatcher().onBackPressed();
diff --git 
a/test/androidx/app/src/androidTest/java/org/apache/cordova/unittests/BackButtonDispatchTest.java
 
b/test/androidx/app/src/androidTest/java/org/apache/cordova/unittests/BackButtonDispatchTest.java
new file mode 100644
index 00000000..2db61f50
--- /dev/null
+++ 
b/test/androidx/app/src/androidTest/java/org/apache/cordova/unittests/BackButtonDispatchTest.java
@@ -0,0 +1,206 @@
+/*
+    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.unittests;
+
+import android.content.Intent;
+import android.os.SystemClock;
+import android.view.KeyEvent;
+import android.view.View;
+import android.webkit.WebView;
+
+import androidx.test.rule.ActivityTestRule;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.concurrent.ArrayBlockingQueue;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * A back press can reach Cordova as a back-press callback and as a back key 
event, and
+ * on some Android versions (for example Android 16) one press arrives as 
both. These
+ * tests make sure each back press is handled once. The callback is triggered 
through
+ * the activity's OnBackPressedDispatcher, the key event through the WebView's
+ * dispatchKeyEvent. The test page listens to "backbutton" and counts the 
events.
+ */
+@RunWith(AndroidJUnit4.class)
+public class BackButtonDispatchTest {
+    @Rule
+    public ActivityTestRule<TestActivity> activityRule = new 
ActivityTestRule<>(
+            TestActivity.class, true, false);
+
+    private TestActivity activity;
+    private WebView webView;
+
+    @Before
+    public void launchSample() throws Throwable {
+        Intent intent = new Intent();
+        intent.putExtra("startUrl", 
"https://localhost/backbuttondispatch/index.html";);
+        activity = activityRule.launchActivity(intent);
+        webView = (WebView) activity.getWebInterface().getView();
+        assertNotNull(activity.onPageFinishedUrl.poll(10, TimeUnit.SECONDS));
+        awaitValue("window.backTestReady === true", "true");
+        long deadline = SystemClock.uptimeMillis() + 10000;
+        while 
(!activity.getWebInterface().isButtonPlumbedToJs(KeyEvent.KEYCODE_BACK)
+                && SystemClock.uptimeMillis() < deadline) {
+            Thread.sleep(20);
+        }
+        
assertTrue(activity.getWebInterface().isButtonPlumbedToJs(KeyEvent.KEYCODE_BACK));
+    }
+
+    /** One back press arriving as a callback and then as a key event fires 
"backbutton" once. */
+    @Test
+    public void callbackAndOriginalKeyFireOnlyOnce() throws Throwable {
+        activityRule.runOnUiThread(() -> {
+            // Simulate Android 16 invoking the callback before forwarding the 
same
+            // physical key. Its timestamp precedes the callback's back action.
+            long keyTime = SystemClock.uptimeMillis() - 1;
+            activity.getOnBackPressedDispatcher().onBackPressed();
+            assertTrue(webView.dispatchKeyEvent(new KeyEvent(keyTime, keyTime,
+                    KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_BACK, 0)));
+            assertTrue(webView.dispatchKeyEvent(new KeyEvent(keyTime, keyTime,
+                    KeyEvent.ACTION_UP, KeyEvent.KEYCODE_BACK, 0)));
+        });
+        assertEventCount(1);
+        assertFalse(activity.isFinishing());
+    }
+
+    /** Separate back presses in quick succession are all handled, none is 
dropped as a duplicate. */
+    @Test
+    public void consecutiveCallbacksAreNotDebounced() throws Throwable {
+        activityRule.runOnUiThread(() -> {
+            for (int i = 0; i < 3; i++) {
+                activity.getOnBackPressedDispatcher().onBackPressed();
+            }
+        });
+        assertEventCount(3);
+    }
+
+    /**
+     * A back key event that is newer than the last callback is a new back 
press, not a
+     * duplicate, so it still fires "backbutton".
+     */
+    @Test
+    public void laterLegacyKeyStillFires() throws Throwable {
+        activityRule.runOnUiThread(() -> 
activity.getOnBackPressedDispatcher().onBackPressed());
+        assertEventCount(1);
+        activityRule.runOnUiThread(() -> {
+            long keyTime = SystemClock.uptimeMillis();
+            assertTrue(webView.dispatchKeyEvent(new KeyEvent(keyTime, keyTime,
+                    KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_BACK, 0)));
+            assertTrue(webView.dispatchKeyEvent(new KeyEvent(keyTime, keyTime,
+                    KeyEvent.ACTION_UP, KeyEvent.KEYCODE_BACK, 0)));
+        });
+        assertEventCount(2);
+    }
+
+    /** A back press that closes a custom view does not also fire "backbutton" 
for its duplicate key event. */
+    @Test
+    public void closingCustomViewDoesNotAlsoFireBackbutton() throws Throwable {
+        AtomicBoolean customViewHidden = new AtomicBoolean();
+        activityRule.runOnUiThread(() -> {
+            activity.getWebInterface().showCustomView(new View(activity),
+                    () -> customViewHidden.set(true));
+            long keyTime = SystemClock.uptimeMillis() - 1;
+            activity.getOnBackPressedDispatcher().onBackPressed();
+            assertTrue(customViewHidden.get());
+            webView.dispatchKeyEvent(new KeyEvent(keyTime, keyTime,
+                    KeyEvent.ACTION_UP, KeyEvent.KEYCODE_BACK, 0));
+        });
+        assertEventCount(0);
+    }
+
+    /** Without a "backbutton" listener, handleBackButton() goes back in the 
WebView history. */
+    @Test
+    public void sharedHandlerNavigatesHistoryWithoutListener() throws 
Throwable {
+        removeBackListener();
+        activityRule.runOnUiThread(() -> 
webView.loadUrl("https://localhost/backbuttondispatch/second.html";));
+        assertNotNull(activity.onPageFinishedUrl.poll(10, TimeUnit.SECONDS));
+        awaitValue("window.backHistoryPageReady === true", "true");
+        activity.onPageFinishedUrl.clear();
+        activityRule.runOnUiThread(() -> {
+            assertTrue("History size=" + 
webView.copyBackForwardList().getSize()
+                    + ", index=" + 
webView.copyBackForwardList().getCurrentIndex()
+                    + ", URL=" + webView.getUrl(), 
activity.getWebInterface().canGoBack());
+            assertTrue(activity.getWebInterface().handleBackButton());
+        });
+        // Evaluating JavaScript while the back navigation is still in 
progress can lose the result.
+        assertEquals("https://localhost/backbuttondispatch/index.html";,
+                activity.onPageFinishedUrl.poll(10, TimeUnit.SECONDS));
+        awaitValue("window.backTestReady === true", "true");
+        assertEventCount(0);
+    }
+
+    /** With no listener, custom view or history, handleBackButton() returns 
false so Android handles the press. */
+    @Test
+    public void sharedHandlerLeavesUnhandledBackToAndroid() throws Throwable {
+        removeBackListener();
+        activityRule.runOnUiThread(() -> {
+            assertFalse(activity.getWebInterface().canGoBack());
+            assertFalse(activity.getWebInterface().handleBackButton());
+        });
+        assertEventCount(0);
+    }
+
+    private void removeBackListener() throws Throwable {
+        evaluate("document.removeEventListener('backbutton', 
window.backTestHandler)");
+        long deadline = SystemClock.uptimeMillis() + 10000;
+        while 
(activity.getWebInterface().isButtonPlumbedToJs(KeyEvent.KEYCODE_BACK)
+                && SystemClock.uptimeMillis() < deadline) {
+            Thread.sleep(20);
+        }
+        
assertFalse(activity.getWebInterface().isButtonPlumbedToJs(KeyEvent.KEYCODE_BACK));
+    }
+
+    private void assertEventCount(int expected) throws Throwable {
+        awaitValue("window.backEventCount", Integer.toString(expected));
+        // Allow pending bridge deliveries to expose an unexpected second 
event.
+        Thread.sleep(200);
+        assertEquals(Integer.toString(expected), 
evaluate("window.backEventCount"));
+    }
+
+    private void awaitValue(String script, String expected) throws Throwable {
+        long deadline = SystemClock.uptimeMillis() + 10000;
+        while (SystemClock.uptimeMillis() < deadline) {
+            if (expected.equals(evaluate(script))) {
+                return;
+            }
+            Thread.sleep(20);
+        }
+        assertEquals(expected, evaluate(script));
+    }
+
+    private String evaluate(String script) throws Throwable {
+        ArrayBlockingQueue<String> result = new ArrayBlockingQueue<>(1);
+        activityRule.runOnUiThread(() -> webView.evaluateJavascript(script, 
result::add));
+        String value = result.poll(10, TimeUnit.SECONDS);
+        assertNotNull("JavaScript evaluation timed out", value);
+        return value;
+    }
+}
diff --git 
a/test/androidx/app/src/main/assets/www/backbuttondispatch/index.html 
b/test/androidx/app/src/main/assets/www/backbuttondispatch/index.html
new file mode 100644
index 00000000..6b24b4c3
--- /dev/null
+++ b/test/androidx/app/src/main/assets/www/backbuttondispatch/index.html
@@ -0,0 +1,37 @@
+<!DOCTYPE html>
+<!--
+    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.
+-->
+<html>
+<head>
+    <meta charset="utf-8">
+    <title>Back button dispatch regression</title>
+    <script src="../cordova.js"></script>
+    <script>
+        window.backEventCount = 0;
+        document.addEventListener('deviceready', function () {
+            window.backTestHandler = function () {
+                window.backEventCount += 1;
+            };
+            document.addEventListener('backbutton', window.backTestHandler);
+            window.backTestReady = true;
+        });
+    </script>
+</head>
+<body>Back button dispatch regression</body>
+</html>
diff --git 
a/test/androidx/app/src/main/assets/www/backbuttondispatch/second.html 
b/test/androidx/app/src/main/assets/www/backbuttondispatch/second.html
new file mode 100644
index 00000000..92dc999c
--- /dev/null
+++ b/test/androidx/app/src/main/assets/www/backbuttondispatch/second.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<!--
+    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.
+-->
+<html><head><meta charset="utf-8"><title>Back history second 
page</title></head>
+<body>Second page<script>window.backHistoryPageReady = 
true;</script></body></html>


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to