1// Copyright 2013 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5package org.chromium.chrome.test.util.browser.contextmenu;
6
7import android.test.ActivityInstrumentationTestCase2;
8import android.text.TextUtils;
9import android.view.ContextMenu;
10import android.view.MenuItem;
11
12import junit.framework.Assert;
13
14import org.chromium.chrome.browser.EmptyTabObserver;
15import org.chromium.chrome.browser.Tab;
16import org.chromium.content.browser.test.util.CallbackHelper;
17import org.chromium.content.browser.test.util.Criteria;
18import org.chromium.content.browser.test.util.CriteriaHelper;
19import org.chromium.content.browser.test.util.DOMUtils;
20
21import java.lang.ref.WeakReference;
22import java.util.concurrent.TimeoutException;
23
24/**
25 * A utility class to help open and interact with context menus triggered by a WebContents.
26 */
27public class ContextMenuUtils {
28    /**
29     * Callback helper that also provides access to the last display ContextMenu.
30     */
31    private static class OnContextMenuShownHelper extends CallbackHelper {
32        private WeakReference<ContextMenu> mContextMenu;
33
34        public void notifyCalled(ContextMenu menu) {
35            mContextMenu = new WeakReference<ContextMenu>(menu);
36            notifyCalled();
37        }
38
39        public ContextMenu getContextMenu() {
40            assert getCallCount() > 0;
41            return mContextMenu.get();
42        }
43    }
44
45    /**
46     * Opens a context menu.
47     * @param testCase              The test harness.
48     * @param tab                   The tab to open a context menu for.
49     * @param openerDOMNodeId       The DOM node to long press to open the context menu for.
50     * @return                      The {@link ContextMenu} that was opened.
51     * @throws InterruptedException
52     * @throws TimeoutException
53     */
54    public static ContextMenu openContextMenu(ActivityInstrumentationTestCase2 testCase,
55            Tab tab, String openerDOMNodeId)
56                    throws InterruptedException, TimeoutException {
57        final OnContextMenuShownHelper helper = new OnContextMenuShownHelper();
58        tab.addObserver(new EmptyTabObserver() {
59            @Override
60            public void onContextMenuShown(Tab tab, ContextMenu menu) {
61                helper.notifyCalled(menu);
62            }
63        });
64        int callCount = helper.getCallCount();
65        DOMUtils.longPressNode(testCase, tab.getContentViewCore(), openerDOMNodeId);
66
67        helper.waitForCallback(callCount);
68        return helper.getContextMenu();
69    }
70
71    /**
72     * Opens and selects an item from a context menu.
73     * @param testCase              The test harness.
74     * @param tab                   The tab to open a context menu for.
75     * @param openerDOMNodeId       The DOM node to long press to open the context menu for.
76     * @param itemId                The context menu item ID to select.
77     * @throws InterruptedException
78     * @throws TimeoutException
79     */
80    public static void selectContextMenuItem(ActivityInstrumentationTestCase2 testCase,
81            Tab tab, String openerDOMNodeId,
82            final int itemId) throws InterruptedException, TimeoutException {
83        ContextMenu menu = openContextMenu(testCase, tab, openerDOMNodeId);
84        Assert.assertNotNull("Failed to open context menu", menu);
85
86        selectOpenContextMenuItem(testCase, menu, itemId);
87    }
88
89    /**
90     * Opens and selects an item from a context menu.
91     * @param testCase              The test harness.
92     * @param tab                   The tab to open a context menu for.
93     * @param openerDOMNodeId       The DOM node to long press to open the context menu for.
94     * @param itemTitle             The title of the context menu item to select.
95     * @throws InterruptedException
96     * @throws TimeoutException
97     */
98    public static void selectContextMenuItemByTitle(ActivityInstrumentationTestCase2 testCase,
99            Tab tab, String openerDOMNodeId,
100            String itemTitle) throws InterruptedException, TimeoutException {
101
102        ContextMenu menu = openContextMenu(testCase, tab, openerDOMNodeId);
103        Assert.assertNotNull("Failed to open context menu", menu);
104
105        Integer itemId = null;
106        for (int i = 0; i < menu.size(); i++) {
107            MenuItem item = menu.getItem(i);
108            if (TextUtils.equals(item.getTitle(), itemTitle)) {
109                itemId = item.getItemId();
110                break;
111            }
112        }
113        Assert.assertNotNull("Couldn't find context menu item for '" + itemTitle + "'", itemId);
114
115        selectOpenContextMenuItem(testCase, menu, itemId);
116    }
117
118    private static void selectOpenContextMenuItem(final ActivityInstrumentationTestCase2 testCase,
119            final ContextMenu menu, final int itemId) throws InterruptedException {
120        MenuItem item = menu.findItem(itemId);
121        Assert.assertNotNull("Could not find '" + itemId + "' in menu", item);
122        Assert.assertTrue("'" + itemId + "' is not visible", item.isVisible());
123        Assert.assertTrue("'" + itemId + "' is not enabled", item.isEnabled());
124
125        testCase.getInstrumentation().runOnMainSync(new Runnable() {
126            @Override
127            public void run() {
128                boolean activated = menu.performIdentifierAction(itemId, 0);
129                Assert.assertTrue("Failed to activate '" + itemId + "' in menu", activated);
130            }
131        });
132
133        Assert.assertTrue("Activity did not regain focus.",
134                CriteriaHelper.pollForCriteria(new Criteria() {
135                    @Override
136                    public boolean isSatisfied() {
137                        return testCase.getActivity().hasWindowFocus();
138                    }
139                }));
140    }
141}