diff --git a/browser/base/content/tabbrowser.xml b/browser/base/content/tabbrowser.xml index c80b387b89b..9436d942ca4 100644 --- a/browser/base/content/tabbrowser.xml +++ b/browser/base/content/tabbrowser.xml @@ -92,13 +92,17 @@ this.tabContainer.childNodes; + + null + Components.classes["@mozilla.org/docshell/urifixup;1"] .getService(Components.interfaces.nsIURIFixup); @@ -151,9 +155,6 @@ false #endif - - null - null @@ -1240,8 +1241,6 @@ aIsUTF8 = params.isUTF8; } - this._browsers = null; // invalidate cache - // if we're adding tabs, we're past interrupt mode, ditch the owner if (this.mCurrentTab.owner) this.mCurrentTab.owner = null; @@ -1291,10 +1290,11 @@ }, 0, this.tabContainer); } - this.tabContainer.appendChild(t); - - // invalidate cache, because tabContainer is about to change + // invalidate caches this._browsers = null; + this._visibleTabs = null; + + this.tabContainer.appendChild(t); // If this new tab is owned by another, assert that relationship if (aOwner) @@ -1625,6 +1625,7 @@ aTab.closing = true; this._removingTabs.push(aTab); + this._visibleTabs = null; // invalidate cache if (newTab) this.addTab(BROWSER_NEW_TAB_URL, {skipAnimation: true}); else @@ -2009,7 +2010,10 @@ + null @@ -2126,11 +2134,14 @@ aIndex = aIndex < aTab._tPos ? aIndex: aIndex+1; this.mCurrentTab._selected = false; + + // invalidate caches + this._browsers = null; + this._visibleTabs = null; + // use .item() instead of [] because dragging to the end of the strip goes out of // bounds: .item() returns null (so it acts like appendChild), but [] throws this.tabContainer.insertBefore(aTab, this.tabs.item(aIndex)); - // invalidate cache, because tabContainer is about to change - this._browsers = null; for (let i = 0; i < this.tabs.length; i++) { this.tabs[i]._tPos = i; diff --git a/mobile/android/base/BrowserToolbar.java b/mobile/android/base/BrowserToolbar.java index 6e5c7833df5..ab5d0b4b4c1 100644 --- a/mobile/android/base/BrowserToolbar.java +++ b/mobile/android/base/BrowserToolbar.java @@ -60,6 +60,7 @@ import android.widget.Button; import android.widget.ImageButton; import android.widget.ImageView; import android.widget.LinearLayout; +import android.widget.RelativeLayout.LayoutParams; import android.widget.TextView; import android.widget.TextSwitcher; import android.widget.ViewSwitcher.ViewFactory; @@ -172,6 +173,18 @@ public class BrowserToolbar { mFavicon = (ImageButton) mLayout.findViewById(R.id.favicon); mSiteSecurity = (ImageButton) mLayout.findViewById(R.id.site_security); + mSiteSecurity.setOnClickListener(new Button.OnClickListener() { + public void onClick(View view) { + int[] lockLocation = new int[2]; + view.getLocationOnScreen(lockLocation); + LayoutParams lockLayoutParams = (LayoutParams) view.getLayoutParams(); + + // Calculate the left margin for the arrow based on the position of the lock icon. + int leftMargin = lockLocation[0] - lockLayoutParams.rightMargin; + GeckoApp.mSiteIdentityPopup.show(leftMargin); + } + }); + mProgressSpinner = (AnimationDrawable) resources.getDrawable(R.drawable.progress_spinner); mStop = (ImageButton) mLayout.findViewById(R.id.stop); @@ -322,9 +335,9 @@ public class BrowserToolbar { public void setSecurityMode(String mode) { mTitleCanExpand = false; - if (mode.equals("identified")) { + if (mode.equals(SiteIdentityPopup.IDENTIFIED)) { mSiteSecurity.setImageLevel(1); - } else if (mode.equals("verified")) { + } else if (mode.equals(SiteIdentityPopup.VERIFIED)) { mSiteSecurity.setImageLevel(2); } else { mSiteSecurity.setImageLevel(0); diff --git a/mobile/android/base/GeckoApp.java b/mobile/android/base/GeckoApp.java index 851654ba4b5..f6cb97744b7 100644 --- a/mobile/android/base/GeckoApp.java +++ b/mobile/android/base/GeckoApp.java @@ -138,6 +138,7 @@ abstract public class GeckoApp public static BrowserToolbar mBrowserToolbar; public static DoorHangerPopup mDoorHangerPopup; + public static SiteIdentityPopup mSiteIdentityPopup; public static FormAssistPopup mFormAssistPopup; public Favicons mFavicons; @@ -665,7 +666,7 @@ abstract public class GeckoApp tab.setContentType(contentType); tab.updateFavicon(null); tab.updateFaviconURL(null); - tab.updateSecurityMode("unknown"); + tab.updateIdentityData(null); tab.removeTransientDoorHangers(); tab.setHasTouchListeners(false); tab.setCheckerboardColor(Color.WHITE); @@ -677,7 +678,7 @@ abstract public class GeckoApp if (Tabs.getInstance().isSelectedTab(tab)) { mBrowserToolbar.setTitle(uri); mBrowserToolbar.setFavicon(null); - mBrowserToolbar.setSecurityMode("unknown"); + mBrowserToolbar.setSecurityMode(tab.getSecurityMode()); mDoorHangerPopup.updatePopup(); mBrowserToolbar.setShadowVisibility(!(tab.getURL().startsWith("about:"))); @@ -688,17 +689,17 @@ abstract public class GeckoApp }); } - void handleSecurityChange(final int tabId, final String mode) { + void handleSecurityChange(final int tabId, final JSONObject identityData) { final Tab tab = Tabs.getInstance().getTab(tabId); if (tab == null) return; - tab.updateSecurityMode(mode); + tab.updateIdentityData(identityData); mMainHandler.post(new Runnable() { public void run() { if (Tabs.getInstance().isSelectedTab(tab)) - mBrowserToolbar.setSecurityMode(mode); + mBrowserToolbar.setSecurityMode(tab.getSecurityMode()); } }); } @@ -871,9 +872,9 @@ abstract public class GeckoApp handleLocationChange(tabId, uri, documentURI, contentType, sameDocument); } else if (event.equals("Content:SecurityChange")) { final int tabId = message.getInt("tabID"); - final String mode = message.getString("mode"); - Log.i(LOGTAG, "Security Mode - " + mode); - handleSecurityChange(tabId, mode); + final JSONObject identity = message.getJSONObject("identity"); + Log.i(LOGTAG, "Security Mode - " + identity.getString("mode")); + handleSecurityChange(tabId, identity); } else if (event.equals("Content:StateChange")) { final int tabId = message.getInt("tabID"); final String uri = message.getString("uri"); @@ -1204,7 +1205,7 @@ abstract public class GeckoApp return; tab.setState("about:home".equals(uri) ? Tab.STATE_SUCCESS : Tab.STATE_LOADING); - tab.updateSecurityMode("unknown"); + tab.updateIdentityData(null); if (Tabs.getInstance().isSelectedTab(tab)) getLayerController().getView().getRenderer().resetCheckerboard(); mMainHandler.post(new Runnable() { @@ -1665,6 +1666,7 @@ abstract public class GeckoApp mPluginContainer = (AbsoluteLayout) findViewById(R.id.plugin_container); mDoorHangerPopup = new DoorHangerPopup(this); + mSiteIdentityPopup = new SiteIdentityPopup(this); mFormAssistPopup = (FormAssistPopup) findViewById(R.id.form_assist_popup); Log.w(LOGTAG, "zerdatime " + SystemClock.uptimeMillis() + " - UI almost up"); @@ -1955,6 +1957,9 @@ abstract public class GeckoApp // Undo whatever we did in onPause. super.onResume(); + if (mSiteIdentityPopup != null) + mSiteIdentityPopup.dismiss(); + int newOrientation = getResources().getConfiguration().orientation; if (mOrientation != newOrientation) { @@ -2081,6 +2086,8 @@ abstract public class GeckoApp mOrientation = newConfig.orientation; if (mFormAssistPopup != null) mFormAssistPopup.hide(); + if (mSiteIdentityPopup != null) + mSiteIdentityPopup.dismiss(); refreshActionBar(); } } @@ -2551,6 +2558,11 @@ abstract public class GeckoApp return; } + if (mSiteIdentityPopup.isShowing()) { + mSiteIdentityPopup.dismiss(); + return; + } + if (mDOMFullScreen) { GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("FullScreen:Exit", null)); return; diff --git a/mobile/android/base/GlobalHistory.java b/mobile/android/base/GlobalHistory.java index 2e3b362913a..c22d2591581 100644 --- a/mobile/android/base/GlobalHistory.java +++ b/mobile/android/base/GlobalHistory.java @@ -45,6 +45,7 @@ import java.lang.ref.SoftReference; import android.content.ContentResolver; import android.database.Cursor; +import android.net.Uri; import android.os.Handler; import android.util.Log; @@ -115,12 +116,49 @@ class GlobalHistory { GeckoAppShell.notifyUriVisited(uri); } + // Logic ported from nsNavHistory::CanAddURI. + // http://mxr.mozilla.org/mozilla-central/source/toolkit/components/places/nsNavHistory.cpp#1272 + private boolean canAddURI(String uri) { + if (uri == null || uri.length() == 0) + return false; + + // First, heck the most common cases (HTTP, HTTPS) to avoid most of the work. + if (uri.startsWith("http:") || uri.startsWith("https:")) + return true; + + String scheme = Uri.parse(uri).getScheme(); + if (scheme == null) + return false; + + // Now check for all bad things. + if (scheme.equals("about") || + scheme.equals("imap") || + scheme.equals("news") || + scheme.equals("mailbox") || + scheme.equals("moz-anno") || + scheme.equals("view-source") || + scheme.equals("chrome") || + scheme.equals("resource") || + scheme.equals("data") || + scheme.equals("wyciwyg") || + scheme.equals("javascript")) + return false; + + return true; + } + public void add(String uri) { + if (!canAddURI(uri)) + return; + BrowserDB.updateVisitedHistory(GeckoApp.mAppContext.getContentResolver(), uri); addToGeckoOnly(uri); } public void update(String uri, String title) { + if (!canAddURI(uri)) + return; + ContentResolver resolver = GeckoApp.mAppContext.getContentResolver(); BrowserDB.updateHistoryTitle(resolver, uri, title); } diff --git a/mobile/android/base/Makefile.in b/mobile/android/base/Makefile.in index dd764a517e7..24b32ddd1be 100644 --- a/mobile/android/base/Makefile.in +++ b/mobile/android/base/Makefile.in @@ -110,6 +110,7 @@ FENNEC_JAVA_FILES = \ sqlite/SQLiteBridgeException.java \ RemoteTabs.java \ SetupScreen.java \ + SiteIdentityPopup.java \ SurfaceBits.java \ Tab.java \ Tabs.java \ @@ -272,6 +273,7 @@ RES_LAYOUT = \ res/layout/notification_progress_text.xml \ res/layout/site_setting_title.xml \ res/layout/setup_screen.xml \ + res/layout/site_identity_popup.xml \ res/layout/remote_tabs.xml \ res/layout/remote_tabs_child.xml \ res/layout/remote_tabs_group.xml \ @@ -366,6 +368,8 @@ RES_DRAWABLE_BASE = \ res/drawable/doorhanger_bg.9.png \ res/drawable/doorhanger_shadow_bg.9.png \ res/drawable/doorhanger_popup_bg.9.png \ + res/drawable/larry_blue.png \ + res/drawable/larry_green.png \ res/drawable/site_security_identified.png \ res/drawable/site_security_verified.png \ res/drawable/urlbar_stop.png \ @@ -423,6 +427,8 @@ RES_DRAWABLE_HDPI = \ res/drawable-hdpi/doorhanger_bg.9.png \ res/drawable-hdpi/doorhanger_shadow_bg.9.png \ res/drawable-hdpi/doorhanger_popup_bg.9.png \ + res/drawable-hdpi/larry_blue.png \ + res/drawable-hdpi/larry_green.png \ res/drawable-hdpi/site_security_identified.png \ res/drawable-hdpi/site_security_verified.png \ res/drawable-hdpi/urlbar_stop.png \ @@ -492,6 +498,8 @@ RES_DRAWABLE_XHDPI_V11 = \ res/drawable-xhdpi-v11/doorhanger_shadow_bg.9.png \ res/drawable-xhdpi-v11/doorhanger_popup_bg.9.png \ res/drawable-xhdpi-v11/urlbar_stop.png \ + res/drawable-xhdpi-v11/larry_blue.png \ + res/drawable-xhdpi-v11/larry_green.png \ res/drawable-xhdpi-v11/site_security_identified.png \ res/drawable-xhdpi-v11/site_security_verified.png \ res/drawable-xhdpi-v11/tabs_button_tail.9.png \ diff --git a/mobile/android/base/SiteIdentityPopup.java b/mobile/android/base/SiteIdentityPopup.java new file mode 100644 index 00000000000..5255f263977 --- /dev/null +++ b/mobile/android/base/SiteIdentityPopup.java @@ -0,0 +1,142 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package org.mozilla.gecko; + +import android.content.Context; +import android.content.res.Resources; +import android.graphics.drawable.BitmapDrawable; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.PopupWindow; +import android.widget.LinearLayout; +import android.widget.RelativeLayout; +import android.widget.RelativeLayout.LayoutParams; +import android.widget.ImageView; +import android.widget.TextView; + +import org.json.JSONObject; +import org.json.JSONException; + +public class SiteIdentityPopup extends PopupWindow { + private static final String LOGTAG = "GeckoSiteIdentityPopup"; + + public static final String UNKNOWN = "unknown"; + public static final String VERIFIED = "verified"; + public static final String IDENTIFIED = "identified"; + + private Context mContext; + private Resources mResources; + private boolean mInflated; + + private TextView mHost; + private TextView mOwner; + private TextView mSupplemental; + private TextView mVerifier; + private TextView mEncrypted; + + private ImageView mLarry; + private ImageView mArrow; + + public SiteIdentityPopup(Context aContext) { + super(aContext); + mContext = aContext; + mResources = aContext.getResources(); + mInflated = false; + } + + private void init() { + setBackgroundDrawable(new BitmapDrawable()); + setOutsideTouchable(true); + setWindowLayoutMode(LayoutParams.FILL_PARENT, LayoutParams.WRAP_CONTENT); + + LayoutInflater inflater = LayoutInflater.from(mContext); + RelativeLayout layout = (RelativeLayout) inflater.inflate(R.layout.site_identity_popup, null); + setContentView(layout); + + mHost = (TextView) layout.findViewById(R.id.host); + mOwner = (TextView) layout.findViewById(R.id.owner); + mSupplemental = (TextView) layout.findViewById(R.id.supplemental); + mVerifier = (TextView) layout.findViewById(R.id.verifier); + mEncrypted = (TextView) layout.findViewById(R.id.encrypted); + + mLarry = (ImageView) layout.findViewById(R.id.larry); + mArrow = (ImageView) layout.findViewById(R.id.arrow); + + mInflated = true; + } + + public void show(int leftMargin) { + JSONObject identityData = Tabs.getInstance().getSelectedTab().getIdentityData(); + if (identityData == null) { + Log.e(LOGTAG, "Tab has no identity data"); + return; + } + + String mode; + try { + mode = identityData.getString("mode"); + } catch (JSONException e) { + Log.e(LOGTAG, "Exception trying to get identity mode: " + e); + return; + } + + if (!mode.equals(VERIFIED) && !mode.equals(IDENTIFIED)) { + Log.e(LOGTAG, "Can't show site identity popup in non-identified state"); + return; + } + + if (!mInflated) + init(); + + try { + String host = identityData.getString("host"); + mHost.setText(host); + + String owner = identityData.getString("owner"); + mOwner.setText(owner); + + String verifier = identityData.getString("verifier"); + mVerifier.setText(verifier); + + String encrypted = identityData.getString("encrypted"); + mEncrypted.setText(encrypted); + } catch (JSONException e) { + Log.e(LOGTAG, "Exception trying to get identity data: " + e); + return; + } + + try { + String supplemental = identityData.getString("supplemental"); + mSupplemental.setText(supplemental); + mSupplemental.setVisibility(View.VISIBLE); + } catch (JSONException e) { + mSupplemental.setVisibility(View.INVISIBLE); + } + + if (mode.equals(VERIFIED)) { + // Use a blue theme for SSL + mLarry.setImageResource(R.drawable.larry_blue); + mHost.setTextColor(mResources.getColor(R.color.identity_verified)); + mOwner.setTextColor(mResources.getColor(R.color.identity_verified)); + mSupplemental.setTextColor(mResources.getColor(R.color.identity_verified)); + } else { + // Use a green theme for EV + mLarry.setImageResource(R.drawable.larry_green); + mHost.setTextColor(mResources.getColor(R.color.identity_identified)); + mOwner.setTextColor(mResources.getColor(R.color.identity_identified)); + mSupplemental.setTextColor(mResources.getColor(R.color.identity_identified)); + } + + // Position the mArrow according to lock position + LayoutParams layoutParams = (LayoutParams) mArrow.getLayoutParams(); + LayoutParams newLayoutParams = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT); + newLayoutParams.setMargins(leftMargin, layoutParams.topMargin, 0, 0); + mArrow.setLayoutParams(newLayoutParams); + + // This will place the popup at the correct vertical position + showAsDropDown(GeckoApp.mBrowserToolbar.mSiteSecurity); + } +} diff --git a/mobile/android/base/Tab.java b/mobile/android/base/Tab.java index 58bd868f6c2..861c01ba2f2 100644 --- a/mobile/android/base/Tab.java +++ b/mobile/android/base/Tab.java @@ -77,7 +77,7 @@ public final class Tab { private String mTitle; private Drawable mFavicon; private String mFaviconUrl; - private String mSecurityMode; + private JSONObject mIdentityData; private Drawable mThumbnail; private List mHistory; private int mHistoryIndex; @@ -119,7 +119,7 @@ public final class Tab { mTitle = title; mFavicon = null; mFaviconUrl = null; - mSecurityMode = "unknown"; + mIdentityData = null; mThumbnail = null; mHistory = new ArrayList(); mHistoryIndex = -1; @@ -270,7 +270,16 @@ public final class Tab { } public String getSecurityMode() { - return mSecurityMode; + try { + return mIdentityData.getString("mode"); + } catch (Exception e) { + // If mIdentityData is null, or we get a JSONException + return SiteIdentityPopup.UNKNOWN; + } + } + + public JSONObject getIdentityData() { + return mIdentityData; } public boolean isBookmark() { @@ -368,8 +377,9 @@ public final class Tab { Log.i(LOGTAG, "Updated favicon URL for tab with id: " + mId); } - public void updateSecurityMode(String mode) { - mSecurityMode = mode; + + public void updateIdentityData(JSONObject identityData) { + mIdentityData = identityData; } private void updateBookmark() { diff --git a/mobile/android/base/db/LocalBrowserDB.java b/mobile/android/base/db/LocalBrowserDB.java index 1ecb26b1862..2667645929e 100644 --- a/mobile/android/base/db/LocalBrowserDB.java +++ b/mobile/android/base/db/LocalBrowserDB.java @@ -167,8 +167,10 @@ public class LocalBrowserDB implements BrowserDB.BrowserDBIface { // approximation using the Cauchy distribution: multiplier = 15^2 / (age^2 + 15^2). // Using 15 as our scale parameter, we get a constant 15^2 = 225. Following this math, // frecencyScore = numVisits * max(1, 100 * 225 / (age*age + 225)). (See bug 704977) + // We also give bookmarks an extra bonus boost by adding 100 points to their frecency score. final String age = "(" + Combined.DATE_LAST_VISITED + " - " + System.currentTimeMillis() + ") / 86400000"; - final String sortOrder = Combined.VISITS + " * MAX(1, 100 * 225 / (" + age + "*" + age + " + 225)) DESC"; + final String sortOrder = "(CASE WHEN " + Combined.BOOKMARK_ID + " > -1 THEN 100 ELSE 0 END) + " + + Combined.VISITS + " * MAX(1, 100 * 225 / (" + age + "*" + age + " + 225)) DESC"; Cursor c = cr.query(combinedUriWithLimit(limit), projection, diff --git a/mobile/android/base/locales/en-US/android_strings.dtd b/mobile/android/base/locales/en-US/android_strings.dtd index 7205aab9ba5..4975c917193 100644 --- a/mobile/android/base/locales/en-US/android_strings.dtd +++ b/mobile/android/base/locales/en-US/android_strings.dtd @@ -126,3 +126,18 @@ + + + + + diff --git a/mobile/android/base/resources/drawable-hdpi/larry_blue.png b/mobile/android/base/resources/drawable-hdpi/larry_blue.png new file mode 100644 index 00000000000..4007364d425 Binary files /dev/null and b/mobile/android/base/resources/drawable-hdpi/larry_blue.png differ diff --git a/mobile/android/base/resources/drawable-hdpi/larry_green.png b/mobile/android/base/resources/drawable-hdpi/larry_green.png new file mode 100644 index 00000000000..886aebd5f74 Binary files /dev/null and b/mobile/android/base/resources/drawable-hdpi/larry_green.png differ diff --git a/mobile/android/base/resources/drawable-xhdpi-v11/larry_blue.png b/mobile/android/base/resources/drawable-xhdpi-v11/larry_blue.png new file mode 100644 index 00000000000..3fa00fe743d Binary files /dev/null and b/mobile/android/base/resources/drawable-xhdpi-v11/larry_blue.png differ diff --git a/mobile/android/base/resources/drawable-xhdpi-v11/larry_green.png b/mobile/android/base/resources/drawable-xhdpi-v11/larry_green.png new file mode 100644 index 00000000000..fc037ba1975 Binary files /dev/null and b/mobile/android/base/resources/drawable-xhdpi-v11/larry_green.png differ diff --git a/mobile/android/base/resources/drawable/larry_blue.png b/mobile/android/base/resources/drawable/larry_blue.png new file mode 100644 index 00000000000..d31b1ef9384 Binary files /dev/null and b/mobile/android/base/resources/drawable/larry_blue.png differ diff --git a/mobile/android/base/resources/drawable/larry_green.png b/mobile/android/base/resources/drawable/larry_green.png new file mode 100644 index 00000000000..ad6f84c26ab Binary files /dev/null and b/mobile/android/base/resources/drawable/larry_green.png differ diff --git a/mobile/android/base/resources/layout/site_identity_popup.xml b/mobile/android/base/resources/layout/site_identity_popup.xml new file mode 100644 index 00000000000..41ae2782886 --- /dev/null +++ b/mobile/android/base/resources/layout/site_identity_popup.xml @@ -0,0 +1,82 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/mobile/android/base/resources/values/colors.xml b/mobile/android/base/resources/values/colors.xml index a66706089e2..5c2538dac4e 100644 --- a/mobile/android/base/resources/values/colors.xml +++ b/mobile/android/base/resources/values/colors.xml @@ -6,5 +6,7 @@ #ffffff #ACC4D5 #ffffff + #77BAFF + #B7D46A diff --git a/mobile/android/base/strings.xml.in b/mobile/android/base/strings.xml.in index 1c6ae935bab..6f708b0579f 100644 --- a/mobile/android/base/strings.xml.in +++ b/mobile/android/base/strings.xml.in @@ -148,4 +148,8 @@ @bookmarks_aboutHome@ about:home + + + &identity_connected_to; + &identity_run_by; diff --git a/mobile/android/base/ui/Axis.java b/mobile/android/base/ui/Axis.java index aa1dc3a83c9..18403ccf764 100644 --- a/mobile/android/base/ui/Axis.java +++ b/mobile/android/base/ui/Axis.java @@ -227,8 +227,14 @@ abstract class Axis { * possible and this axis has not been scroll locked while panning. Otherwise, returns false. */ private boolean scrollable() { - return getViewportLength() <= getPageLength() - MIN_SCROLLABLE_DISTANCE && - !mScrollingDisabled; + // If we're scrolling a subdocument, ignore the viewport length restrictions (since those + // apply to the top-level document) and only take into account axis locking. + if (mSubscroller.scrolling()) { + return !mScrollingDisabled; + } else { + return getViewportLength() <= getPageLength() - MIN_SCROLLABLE_DISTANCE && + !mScrollingDisabled; + } } /* @@ -305,8 +311,9 @@ abstract class Axis { // Performs displacement of the viewport position according to the current velocity. void displace() { - if (!mSubscroller.scrolling() && !scrollable()) + if (!scrollable()) { return; + } if (mFlingState == FlingStates.PANNING) mDisplacement += (mLastTouchPos - mTouchPos) * getEdgeResistance(); diff --git a/mobile/android/base/ui/PanZoomController.java b/mobile/android/base/ui/PanZoomController.java index 136eecee959..cde96f1a7f7 100644 --- a/mobile/android/base/ui/PanZoomController.java +++ b/mobile/android/base/ui/PanZoomController.java @@ -597,14 +597,14 @@ public class PanZoomController return getVelocity() < STOPPED_THRESHOLD; } - PointF getDisplacement() { + PointF resetDisplacement() { return new PointF(mX.resetDisplacement(), mY.resetDisplacement()); } private void updatePosition() { mX.displace(); mY.displace(); - PointF displacement = getDisplacement(); + PointF displacement = resetDisplacement(); if (FloatUtils.fuzzyEquals(displacement.x, 0.0f) && FloatUtils.fuzzyEquals(displacement.y, 0.0f)) { return; } @@ -947,12 +947,6 @@ public class PanZoomController sendPointToGecko("Gesture:LongPress", motionEvent); } - @Override - public boolean onDown(MotionEvent motionEvent) { - sendPointToGecko("Gesture:ShowPress", motionEvent); - return false; - } - @Override public boolean onSingleTapConfirmed(MotionEvent motionEvent) { GeckoApp.mFormAssistPopup.hide(); diff --git a/mobile/android/base/ui/SubdocumentScrollHelper.java b/mobile/android/base/ui/SubdocumentScrollHelper.java index 5175f018a09..516ea04545e 100644 --- a/mobile/android/base/ui/SubdocumentScrollHelper.java +++ b/mobile/android/base/ui/SubdocumentScrollHelper.java @@ -57,15 +57,31 @@ class SubdocumentScrollHelper implements GeckoEventListener { private final PanZoomController mPanZoomController; private final Handler mUiHandler; + /* This is the amount of displacement we have accepted but not yet sent to JS; this is + * only valid when mOverrideScrollPending is true. */ + private final PointF mPendingDisplacement; + + /* When this is true, we're sending scroll events to JS to scroll the active subdocument. */ private boolean mOverridePanning; + + /* When this is true, we have received an ack for the last scroll event we sent to JS, and + * are ready to send the next scroll event. Note we only ever have one scroll event inflight + * at a time. */ private boolean mOverrideScrollAck; + + /* When this is true, we have a pending scroll that we need to send to JS; we were unable + * to send it when it was initially requested because mOverrideScrollAck was not true. */ private boolean mOverrideScrollPending; + + /* When this is true, the last scroll event we sent actually did some amount of scrolling on + * the subdocument; we use this to decide when we have reached the end of the subdocument. */ private boolean mScrollSucceeded; SubdocumentScrollHelper(PanZoomController controller) { mPanZoomController = controller; // mUiHandler will be bound to the UI thread since that's where this constructor runs mUiHandler = new Handler(); + mPendingDisplacement = new PointF(); GeckoAppShell.registerGeckoEventListener(MESSAGE_PANNING_OVERRIDE, this); GeckoAppShell.registerGeckoEventListener(MESSAGE_CANCEL_OVERRIDE, this); @@ -79,12 +95,11 @@ class SubdocumentScrollHelper implements GeckoEventListener { if (! mOverrideScrollAck) { mOverrideScrollPending = true; + mPendingDisplacement.x += displacement.x; + mPendingDisplacement.y += displacement.y; return true; } - mOverrideScrollAck = false; - mOverrideScrollPending = false; - JSONObject json = new JSONObject(); try { json.put("x", displacement.x); @@ -94,6 +109,13 @@ class SubdocumentScrollHelper implements GeckoEventListener { } GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent(MESSAGE_SCROLL, json.toString())); + mOverrideScrollAck = false; + mOverrideScrollPending = false; + // clear the |mPendingDisplacement| after serializing |displacement| to + // JSON because they might be the same object + mPendingDisplacement.x = 0; + mPendingDisplacement.y = 0; + return true; } @@ -128,7 +150,7 @@ class SubdocumentScrollHelper implements GeckoEventListener { mOverrideScrollAck = true; mScrollSucceeded = message.getBoolean("scrolled"); if (mOverridePanning && mOverrideScrollPending) { - scrollBy(mPanZoomController.getDisplacement()); + scrollBy(mPendingDisplacement); } } } catch (Exception e) { diff --git a/mobile/android/chrome/content/aboutDownloads.js b/mobile/android/chrome/content/aboutDownloads.js index c42da6f074a..ab85bd46a69 100644 --- a/mobile/android/chrome/content/aboutDownloads.js +++ b/mobile/android/chrome/content/aboutDownloads.js @@ -81,6 +81,7 @@ let Downloads = { switch (aTopic) { case "dl-failed": case "dl-cancel": + break; case "dl-done": if (!this._getElementForDownload(download.id)) { let item = this._createItem(downloadTemplate, { @@ -102,10 +103,10 @@ let Downloads = { this._stmt.finalize(); this._stmt = this._dlmgr.DBConnection.createStatement( - "SELECT id, name, source, state, startTime, endTime, referrer, " + - "currBytes, maxBytes, state IN (?1, ?2, ?3, ?4, ?5) isActive " + + "SELECT id, name, source, startTime, endTime, referrer, " + + "currBytes, maxBytes " + "FROM moz_downloads " + - "WHERE NOT isActive " + + "WHERE state = :download_state " + "ORDER BY endTime DESC"); }, @@ -177,11 +178,7 @@ let Downloads = { clearTimeout(this._timeoutID); this._stmt.reset(); - this._stmt.bindInt32Parameter(0, Ci.nsIDownloadManager.DOWNLOAD_NOTSTARTED); - this._stmt.bindInt32Parameter(1, Ci.nsIDownloadManager.DOWNLOAD_DOWNLOADING); - this._stmt.bindInt32Parameter(2, Ci.nsIDownloadManager.DOWNLOAD_PAUSED); - this._stmt.bindInt32Parameter(3, Ci.nsIDownloadManager.DOWNLOAD_QUEUED); - this._stmt.bindInt32Parameter(4, Ci.nsIDownloadManager.DOWNLOAD_SCANNING); + this._stmt.params.download_state = Ci.nsIDownloadManager.DOWNLOAD_FINISHED; // Take a quick break before we actually start building the list let self = this; diff --git a/mobile/android/chrome/content/browser.js b/mobile/android/chrome/content/browser.js index 8b2f987cdc9..4a6f82ecdbe 100644 --- a/mobile/android/chrome/content/browser.js +++ b/mobile/android/chrome/content/browser.js @@ -2213,6 +2213,8 @@ Tab.prototype = { if (contentWin != contentWin.top) return; + this._hostChanged = true; + let browser = BrowserApp.getBrowserForWindow(contentWin); let uri = browser.currentURI.spec; let documentURI = ""; @@ -2249,26 +2251,29 @@ Tab.prototype = { } }, + // Properties used to cache security state used to update the UI + _state: null, + _hostChanged: false, // onLocationChange will flip this bit + onSecurityChange: function(aWebProgress, aRequest, aState) { - let mode = "unknown"; - if (aState & Ci.nsIWebProgressListener.STATE_IDENTITY_EV_TOPLEVEL) - mode = "identified"; - else if (aState & Ci.nsIWebProgressListener.STATE_SECURE_HIGH) - mode = "verified"; - else if (aState & Ci.nsIWebProgressListener.STATE_IS_BROKEN) - mode = "mixed"; - else - mode = "unknown"; + // Don't need to do anything if the data we use to update the UI hasn't changed + if (this._state == aState && !this._hostChanged) + return; + + this._state = aState; + this._hostChanged = false; + + let identity = IdentityHandler.checkIdentity(aState, this.browser); let message = { gecko: { type: "Content:SecurityChange", tabID: this.id, - mode: mode + identity: identity } }; - sendMessageToJava(message); + sendMessageToJava(message); }, onProgressChange: function(aWebProgress, aRequest, aCurSelfProgress, aMaxSelfProgress, aCurTotalProgress, aMaxTotalProgress) { @@ -2521,13 +2526,44 @@ Tab.prototype = { var BrowserEventHandler = { init: function init() { Services.obs.addObserver(this, "Gesture:SingleTap", false); - Services.obs.addObserver(this, "Gesture:ShowPress", false); Services.obs.addObserver(this, "Gesture:CancelTouch", false); Services.obs.addObserver(this, "Gesture:DoubleTap", false); Services.obs.addObserver(this, "Gesture:Scroll", false); Services.obs.addObserver(this, "dom-touch-listener-added", false); BrowserApp.deck.addEventListener("DOMUpdatePageReport", PopupBlockerObserver.onUpdatePageReport, false); + BrowserApp.deck.addEventListener("touchstart", this, false); + }, + + handleEvent: function(aEvent) { + if (!BrowserApp.isBrowserContentDocumentDisplayed() || aEvent.touches.length > 1 || aEvent.defaultPrevented) + return; + + let closest = aEvent.target; + + if (closest) { + // If we've pressed a scrollable element, let Java know that we may + // want to override the scroll behaviour (for document sub-frames) + this._scrollableElement = this._findScrollableElement(closest, true); + this._firstScrollEvent = true; + + if (this._scrollableElement != null) { + // Discard if it's the top-level scrollable, we let Java handle this + let doc = BrowserApp.selectedBrowser.contentDocument; + if (this._scrollableElement != doc.body && this._scrollableElement != doc.documentElement) + sendMessageToJava({ gecko: { type: "Panning:Override" } }); + } + } + + if (!ElementTouchHelper.isElementClickable(closest)) + closest = ElementTouchHelper.elementFromPoint(BrowserApp.selectedBrowser.contentWindow, + aEvent.changedTouches[0].screenX, + aEvent.changedTouches[0].screenY); + if (!closest) + closest = aEvent.target; + + if (closest) + this._doTapHighlight(closest); }, observe: function(aSubject, aTopic, aData) { @@ -2562,12 +2598,19 @@ var BrowserEventHandler = { // the user wanted, and neither can any non-root sub-frame, cancel the // override so that Java can handle panning the main document. let data = JSON.parse(aData); + + // round the scroll amounts because they come in as floats and might be + // subject to minor rounding errors because of zoom values. I've seen values + // like 0.99 come in here and get truncated to 0; this avoids that problem. + data.x = Math.round(data.x); + data.y = Math.round(data.y); + if (this._firstScrollEvent) { while (this._scrollableElement != null && !this._elementCanScroll(this._scrollableElement, data.x, data.y)) this._scrollableElement = this._findScrollableElement(this._scrollableElement, false); let doc = BrowserApp.selectedBrowser.contentDocument; - if (this._scrollableElement == doc.body || this._scrollableElement == doc.documentElement) { + if (this._scrollableElement == null || this._scrollableElement == doc.body || this._scrollableElement == doc.documentElement) { sendMessageToJava({ gecko: { type: "Panning:CancelOverride" } }); return; } @@ -2584,32 +2627,12 @@ var BrowserEventHandler = { } } else if (aTopic == "Gesture:CancelTouch") { this._cancelTapHighlight(); - } else if (aTopic == "Gesture:ShowPress") { - let data = JSON.parse(aData); - let closest = ElementTouchHelper.elementFromPoint(BrowserApp.selectedBrowser.contentWindow, data.x, data.y); - if (!closest) - closest = ElementTouchHelper.anyElementFromPoint(BrowserApp.selectedBrowser.contentWindow, data.x, data.y); - if (closest) { - this._doTapHighlight(closest); - - // If we've pressed a scrollable element, let Java know that we may - // want to override the scroll behaviour (for document sub-frames) - this._scrollableElement = this._findScrollableElement(closest, true); - this._firstScrollEvent = true; - - if (this._scrollableElement != null) { - // Discard if it's the top-level scrollable, we let Java handle this - let doc = BrowserApp.selectedBrowser.contentDocument; - if (this._scrollableElement != doc.body && this._scrollableElement != doc.documentElement) - sendMessageToJava({ gecko: { type: "Panning:Override" } }); - } - } } else if (aTopic == "Gesture:SingleTap") { let element = this._highlightElement; if (element && !SelectHelper.handleClick(element)) { try { let data = JSON.parse(aData); - + this._sendMouseEvent("mousemove", element, data.x, data.y); this._sendMouseEvent("mousedown", element, data.x, data.y); this._sendMouseEvent("mouseup", element, data.x, data.y); @@ -2824,24 +2847,11 @@ var BrowserEventHandler = { }, _elementCanScroll: function(elem, x, y) { - let scrollX = true; - let scrollY = true; + let scrollX = (x < 0 && elem.scrollLeft > 0) + || (x > 0 && elem.scrollLeft < (elem.scrollWidth - elem.clientWidth)); - if (x < 0) { - if (elem.scrollLeft <= 0) { - scrollX = false; - } - } else if (elem.scrollLeft >= (elem.scrollWidth - elem.clientWidth)) { - scrollX = false; - } - - if (y < 0) { - if (elem.scrollTop <= 0) { - scrollY = false; - } - } else if (elem.scrollTop >= (elem.scrollHeight - elem.clientHeight)) { - scrollY = false; - } + let scrollY = (y < 0 && elem.scrollTop > 0) + || (y > 0 && elem.scrollTop < (elem.scrollHeight - elem.clientHeight)); return scrollX || scrollY; } @@ -3593,14 +3603,11 @@ var ViewportHandler = { if (doctype && /(WAP|WML|Mobile)/.test(doctype.publicId)) return { defaultZoom: 1, autoSize: true, allowZoom: true, autoScale: true }; - let windowUtils = aWindow.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils); - let handheldFriendly = windowUtils.getDocumentMetadata("HandheldFriendly"); - if (handheldFriendly == "true") - return { defaultZoom: 1, autoSize: true, allowZoom: true, autoScale: true }; - if (aWindow.document instanceof XULDocument) return { defaultZoom: 1, autoSize: true, allowZoom: false, autoScale: false }; + let windowUtils = aWindow.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils); + // viewport details found here // http://developer.apple.com/safari/library/documentation/AppleApplications/Reference/SafariHTMLRef/Articles/MetaTags.html // http://developer.apple.com/safari/library/documentation/AppleApplications/Reference/SafariWebContent/UsingtheViewport/UsingtheViewport.html @@ -3619,6 +3626,15 @@ var ViewportHandler = { let allowZoomStr = windowUtils.getDocumentMetadata("viewport-user-scalable"); let allowZoom = !/^(0|no|false)$/.test(allowZoomStr); // WebKit allows 0, "no", or "false" + + if (scale == NaN && minScale == NaN && maxScale == NaN && allowZoomStr == "" && widthStr == "" && heightStr == "") { + // Only check for HandheldFriendly if we don't have a viewport meta tag + let handheldFriendly = windowUtils.getDocumentMetadata("HandheldFriendly"); + + if (handheldFriendly == "true") + return { defaultZoom: 1, autoSize: true, allowZoom: true, autoScale: true }; + } + scale = this.clamp(scale, kViewportMinScale, kViewportMaxScale); minScale = this.clamp(minScale, kViewportMinScale, kViewportMaxScale); maxScale = this.clamp(maxScale, kViewportMinScale, kViewportMaxScale); @@ -4605,6 +4621,159 @@ var CharacterEncoding = { } }; +var IdentityHandler = { + // Mode strings used to control CSS display + IDENTITY_MODE_IDENTIFIED : "identified", // High-quality identity information + IDENTITY_MODE_DOMAIN_VERIFIED : "verified", // Minimal SSL CA-signed domain verification + IDENTITY_MODE_UNKNOWN : "unknown", // No trusted identity information + + // Cache the most recent SSLStatus and Location seen in getIdentityStrings + _lastStatus : null, + _lastLocation : null, + + /** + * Helper to parse out the important parts of _lastStatus (of the SSL cert in + * particular) for use in constructing identity UI strings + */ + getIdentityData : function() { + let result = {}; + let status = this._lastStatus.QueryInterface(Components.interfaces.nsISSLStatus); + let cert = status.serverCert; + + // Human readable name of Subject + result.subjectOrg = cert.organization; + + // SubjectName fields, broken up for individual access + if (cert.subjectName) { + result.subjectNameFields = {}; + cert.subjectName.split(",").forEach(function(v) { + let field = v.split("="); + this[field[0]] = field[1]; + }, result.subjectNameFields); + + // Call out city, state, and country specifically + result.city = result.subjectNameFields.L; + result.state = result.subjectNameFields.ST; + result.country = result.subjectNameFields.C; + } + + // Human readable name of Certificate Authority + result.caOrg = cert.issuerOrganization || cert.issuerCommonName; + result.cert = cert; + + return result; + }, + + getIdentityMode: function getIdentityMode(aState) { + if (aState & Ci.nsIWebProgressListener.STATE_IDENTITY_EV_TOPLEVEL) + return this.IDENTITY_MODE_IDENTIFIED; + + if (aState & Ci.nsIWebProgressListener.STATE_SECURE_HIGH) + return this.IDENTITY_MODE_DOMAIN_VERIFIED; + + return this.IDENTITY_MODE_UNKNOWN; + }, + + /** + * Determine the identity of the page being displayed by examining its SSL cert + * (if available). Return the data needed to update the UI. + */ + checkIdentity: function checkIdentity(aState, aBrowser) { + this._lastStatus = aBrowser.securityUI + .QueryInterface(Components.interfaces.nsISSLStatusProvider) + .SSLStatus; + + // Don't pass in the actual location object, since it can cause us to + // hold on to the window object too long. Just pass in the fields we + // care about. (bug 424829) + let locationObj = {}; + try { + let location = aBrowser.contentWindow.location; + locationObj.host = location.host; + locationObj.hostname = location.hostname; + locationObj.port = location.port; + } catch (ex) { + // Can sometimes throw if the URL being visited has no host/hostname, + // e.g. about:blank. The _state for these pages means we won't need these + // properties anyways, though. + } + this._lastLocation = locationObj; + + let mode = this.getIdentityMode(aState); + let result = { mode: mode }; + + // We can't to do anything else for pages without identity data + if (mode == this.IDENTITY_MODE_UNKNOWN) + return result; + + // Ideally we'd just make this a Java string + result.encrypted = Strings.browser.GetStringFromName("identity.encrypted2"); + result.host = this.getEffectiveHost(); + + let iData = this.getIdentityData(); + result.verifier = Strings.browser.formatStringFromName("identity.identified.verifier", [iData.caOrg], 1); + + // If the cert is identified, then we can populate the results with credentials + if (mode == this.IDENTITY_MODE_IDENTIFIED) { + result.owner = iData.subjectOrg; + + // Build an appropriate supplemental block out of whatever location data we have + let supplemental = ""; + if (iData.city) + supplemental += iData.city + "\n"; + if (iData.state && iData.country) + supplemental += Strings.browser.formatStringFromName("identity.identified.state_and_country", [iData.state, iData.country], 2); + else if (iData.state) // State only + supplemental += iData.state; + else if (iData.country) // Country only + supplemental += iData.country; + result.supplemental = supplemental; + + return result; + } + + // Otherwise, we don't know the cert owner + result.owner = Strings.browser.GetStringFromName("identity.ownerUnknown2"); + + // Cache the override service the first time we need to check it + if (!this._overrideService) + this._overrideService = Cc["@mozilla.org/security/certoverride;1"].getService(Ci.nsICertOverrideService); + + // Check whether this site is a security exception. XPConnect does the right + // thing here in terms of converting _lastLocation.port from string to int, but + // the overrideService doesn't like undefined ports, so make sure we have + // something in the default case (bug 432241). + // .hostname can return an empty string in some exceptional cases - + // hasMatchingOverride does not handle that, so avoid calling it. + // Updating the tooltip value in those cases isn't critical. + // FIXME: Fixing bug 646690 would probably makes this check unnecessary + if (this._lastLocation.hostname && + this._overrideService.hasMatchingOverride(this._lastLocation.hostname, + (this._lastLocation.port || 443), + iData.cert, {}, {})) + result.verifier = Strings.browser.GetStringFromName("identity.identified.verified_by_you"); + + return result; + }, + + /** + * Return the eTLD+1 version of the current hostname + */ + getEffectiveHost: function getEffectiveHost() { + if (!this._IDNService) + this._IDNService = Cc["@mozilla.org/network/idn-service;1"] + .getService(Ci.nsIIDNService); + try { + let baseDomain = Services.eTLD.getBaseDomainFromHost(this._lastLocation.hostname); + return this._IDNService.convertToDisplayIDN(baseDomain, {}); + } catch (e) { + // If something goes wrong (e.g. hostname is an IP address) just fail back + // to the full domain. + return this._lastLocation.hostname; + } + } +}; + function OverscrollController(aTab) { this.tab = aTab; } diff --git a/mobile/android/locales/en-US/chrome/browser.properties b/mobile/android/locales/en-US/chrome/browser.properties index 150cde853dd..5d9cee0d34c 100644 --- a/mobile/android/locales/en-US/chrome/browser.properties +++ b/mobile/android/locales/en-US/chrome/browser.properties @@ -75,8 +75,6 @@ identity.identified.verified_by_you=You have added a security exception for this identity.identified.state_and_country=%S, %S identity.identified.title_with_country=%S (%S) identity.encrypted2=Encrypted -identity.unencrypted2=Not encrypted -identity.unknown.tooltip=This website does not supply identity information. identity.ownerUnknown2=(unknown) # Geolocation UI diff --git a/testing/marionette/client/marionette/tests/unit/test_switch_frame.py b/testing/marionette/client/marionette/tests/unit/test_switch_frame.py index c2e6133f9ac..644b7ce1218 100644 --- a/testing/marionette/client/marionette/tests/unit/test_switch_frame.py +++ b/testing/marionette/client/marionette/tests/unit/test_switch_frame.py @@ -46,3 +46,55 @@ class TestSwitchFrame(MarionetteTestCase): self.assertEqual("Marionette IFrame Test", self.marionette.execute_script("return window.document.title;")) self.marionette.switch_to_frame("test_iframe") self.assertTrue("test.html" in self.marionette.get_url()) + + def test_switch_nested(self): + self.assertTrue(self.marionette.execute_script("window.location.href = 'about:blank'; return true;")) + self.assertEqual("about:blank", self.marionette.execute_script("return window.location.href;")) + test_html = self.marionette.absolute_url("test_nested_iframe.html") + self.marionette.navigate(test_html) + self.assertNotEqual("about:blank", self.marionette.execute_script("return window.location.href;")) + self.assertEqual("Marionette IFrame Test", self.marionette.execute_script("return window.document.title;")) + self.marionette.switch_to_frame("test_iframe") + self.assertTrue("test_inner_iframe.html" in self.marionette.get_url()) + self.marionette.switch_to_frame("inner_frame") + self.assertTrue("test.html" in self.marionette.get_url()) + self.marionette.switch_to_frame() # go back to main frame + self.assertTrue("test_nested_iframe.html" in self.marionette.get_url()) + #test that we're using the right window object server-side + self.assertTrue("test_nested_iframe.html" in self.marionette.execute_script("return window.location.href;")) + +class TestSwitchFrameChrome(MarionetteTestCase): + def setUp(self): + MarionetteTestCase.setUp(self) + self.marionette.set_context("chrome") + self.win = self.marionette.get_window() + #need to get the file:// path for xul + unit = os.path.abspath(os.path.join(os.path.realpath(__file__), os.path.pardir)) + tests = os.path.abspath(os.path.join(unit, os.path.pardir)) + mpath = os.path.abspath(os.path.join(tests, os.path.pardir)) + xul = "file://" + os.path.join(mpath, "www", "test.xul") + self.marionette.execute_script("window.open('" + xul +"', '_blank', 'chrome,centerscreen');") + + def tearDown(self): + self.marionette.execute_script("window.close();") + self.marionette.switch_to_window(self.win) + MarionetteTestCase.tearDown(self) + + def test_switch_simple(self): + self.assertTrue("test.xul" in self.marionette.get_url()) + self.marionette.switch_to_frame(0) + self.assertTrue("test2.xul" in self.marionette.get_url()) + self.marionette.switch_to_frame() + self.assertTrue("test.xul" in self.marionette.get_url()) + self.marionette.switch_to_frame("iframe") + self.assertTrue("test2.xul" in self.marionette.get_url()) + self.marionette.switch_to_frame() + self.assertTrue("test.xul" in self.marionette.get_url()) + self.marionette.switch_to_frame("iframename") + self.assertTrue("test2.xul" in self.marionette.get_url()) + self.marionette.switch_to_frame() + self.assertTrue("test.xul" in self.marionette.get_url()) + + #I can't seem to access a xul iframe within a xul iframe + def test_switch_nested(self): + pass diff --git a/testing/marionette/client/marionette/www/test.xul b/testing/marionette/client/marionette/www/test.xul index 5b8aef24382..bdc991cc7d6 100644 --- a/testing/marionette/client/marionette/www/test.xul +++ b/testing/marionette/client/marionette/www/test.xul @@ -12,4 +12,6 @@ + + + diff --git a/testing/marionette/client/marionette/www/test_nested_iframe.html b/testing/marionette/client/marionette/www/test_nested_iframe.html new file mode 100644 index 00000000000..81eb596d26e --- /dev/null +++ b/testing/marionette/client/marionette/www/test_nested_iframe.html @@ -0,0 +1,9 @@ + + + +Marionette IFrame Test + + + + + diff --git a/testing/marionette/client/marionette/www/test_nested_iframe.xul b/testing/marionette/client/marionette/www/test_nested_iframe.xul new file mode 100644 index 00000000000..ee5d0c87afa --- /dev/null +++ b/testing/marionette/client/marionette/www/test_nested_iframe.xul @@ -0,0 +1,6 @@ + + + +