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/test2.xul b/testing/marionette/client/marionette/www/test2.xul
new file mode 100644
index 00000000000..5b8aef24382
--- /dev/null
+++ b/testing/marionette/client/marionette/www/test2.xul
@@ -0,0 +1,15 @@
+
+
+
+
diff --git a/testing/marionette/client/marionette/www/test_inner_iframe.html b/testing/marionette/client/marionette/www/test_inner_iframe.html
new file mode 100644
index 00000000000..321c440f0c9
--- /dev/null
+++ b/testing/marionette/client/marionette/www/test_inner_iframe.html
@@ -0,0 +1,9 @@
+
+
+
+Inner Iframe
+
+
+
+
+
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 @@
+
+
+
+
+
diff --git a/testing/marionette/marionette-actors.js b/testing/marionette/marionette-actors.js
index 89deec69e77..e23143da435 100644
--- a/testing/marionette/marionette-actors.js
+++ b/testing/marionette/marionette-actors.js
@@ -119,6 +119,8 @@ function MarionetteDriverActor(aConnection)
this.timer = null;
this.marionetteLog = new MarionetteLogObj();
this.command_id = null;
+ this.mainFrame = null; //topmost chrome frame
+ this.curFrame = null; //subframe that currently has focus
//register all message listeners
this.messageManager.addMessageListener("Marionette:ok", this);
@@ -219,10 +221,15 @@ MarionetteDriverActor.prototype = {
*/
getCurrentWindow: function MDA_getCurrentWindow() {
let type = null;
- if (appName != "B2G" && this.context == "content") {
- type = 'navigator:browser';
+ if (this.curFrame == null) {
+ if (appName != "B2G" && this.context == "content") {
+ type = 'navigator:browser';
+ }
+ return this.windowMediator.getMostRecentWindow(type);
+ }
+ else {
+ return this.curFrame;
}
- return this.windowMediator.getMostRecentWindow(type);
},
/**
@@ -275,6 +282,8 @@ MarionetteDriverActor.prototype = {
* True if this is the first time we're talking to this browser
*/
startBrowser: function MDA_startBrowser(win, newSession) {
+ this.mainFrame = win;
+ this.curFrame = null;
this.addBrowser(win);
this.curBrowser.newSession = newSession;
this.curBrowser.startSession(newSession);
@@ -711,7 +720,7 @@ MarionetteDriverActor.prototype = {
* Searches based on name, then id.
*
* @param object aRequest
- * 'value' member holds the id of the window to switch to
+ * 'value' member holds the name or id of the window to switch to
*/
switchToWindow: function MDA_switchToWindow(aRequest) {
let winEn = this.getWinEnumerator();
@@ -738,10 +747,73 @@ MarionetteDriverActor.prototype = {
* Switch to a given frame within the current window
*
* @param object aRequest
- * 'value' holds the id of the frame to switch to
+ * 'element' is the element to switch to
+ * 'value' if element is not set, then this
+ * holds either the id, name or index
+ * of the frame to switch to
*/
switchToFrame: function MDA_switchToFrame(aRequest) {
- this.sendAsync("switchToFrame", aRequest);
+ let curWindow = this.getCurrentWindow();
+ if (this.context == "chrome") {
+ let foundFrame = null;
+ if ((aRequest.value == null) && (aRequest.element == null)) {
+ this.curFrame = null;
+ this.mainFrame.focus();
+ this.sendOk();
+ return;
+ }
+ if (aRequest.element != undefined) {
+ if (this.curBrowser.elementManager.seenItems[aRequest.element] != undefined) {
+ let wantedFrame = this.curBrowser.elementManager.getKnownElement(aRequest.element, curWindow); //HTMLIFrameElement
+ let numFrames = curWindow.frames.length;
+ for (let i = 0; i < numFrames; i++) {
+ if (curWindow.frames[i].frameElement == wantedFrame) {
+ curWindow = curWindow.frames[i];
+ this.curFrame = curWindow;
+ this.curFrame.focus();
+ this.sendOk();
+ return;
+ }
+ }
+ }
+ }
+ switch(typeof(aRequest.value)) {
+ case "string" :
+ let foundById = null;
+ let numFrames = curWindow.frames.length;
+ for (let i = 0; i < numFrames; i++) {
+ //give precedence to name
+ let frame = curWindow.frames[i];
+ let frameElement = frame.frameElement;
+ if (frame.name == aRequest.value) {
+ foundFrame = i;
+ break;
+ } else if ((foundById == null) && (frameElement.id == aRequest.value)) {
+ foundById = i;
+ }
+ }
+ if ((foundFrame == null) && (foundById != null)) {
+ foundFrame = foundById;
+ }
+ break;
+ case "number":
+ if (curWindow.frames[aRequest.value] != undefined) {
+ foundFrame = aRequest.value;
+ }
+ break;
+ }
+ if (foundFrame != null) {
+ curWindow = curWindow.frames[foundFrame];
+ this.curFrame = curWindow;
+ this.curFrame.focus();
+ this.sendOk();
+ } else {
+ this.sendError("Unable to locate frame: " + aRequest.value, 8, null);
+ }
+ }
+ else {
+ this.sendAsync("switchToFrame", aRequest);
+ }
},
/**
@@ -1106,11 +1178,6 @@ MarionetteDriverActor.prototype = {
}
}
return reg;
- case "Marionette:goUrl":
- // if content determines that the goUrl call is directed at a top level window (not an iframe)
- // it calls back into chrome to load the uri.
- this.curBrowser.loadURI(message.json.value, this);
- break;
}
},
/**
@@ -1243,25 +1310,6 @@ BrowserObj.prototype = {
this.tab = this.browser.addTab(uri, true);
},
- /**
- * Load a uri in the current tab
- *
- * @param string uri
- * URI to load
- * @param EventListener listener
- * event listener fired on load
- */
- loadURI: function BO_openURI(uri, listener) {
- if (appName != "B2G") {
- this.browser.addEventListener("DOMContentLoaded", listener, false);
- this.browser.loadURI(uri);
- }
- else {
- this.messageManager.addMessageListener("DOMContentLoaded", listener, true);
- this.browser.selectedBrowser.loadURI(uri);
- }
- },
-
/**
* Loads content listeners if we don't already have them
*
diff --git a/testing/marionette/marionette-listener.js b/testing/marionette/marionette-listener.js
index f96c60d857f..9828e20aa47 100644
--- a/testing/marionette/marionette-listener.js
+++ b/testing/marionette/marionette-listener.js
@@ -26,7 +26,7 @@ let marionetteTimeout = null;
let winUtil = content.QueryInterface(Components.interfaces.nsIInterfaceRequestor).getInterface(Components.interfaces.nsIDOMWindowUtils);
let listenerId = null; //unique ID of this listener
let activeFrame = null;
-let win = content;
+let curWindow = content;
let elementManager = new ElementManager([]);
/**
@@ -179,6 +179,7 @@ function sendError(message, status, trace, command_id) {
*/
function resetValues() {
marionetteTimeout = null;
+ curWin = content;
}
/**
@@ -227,9 +228,9 @@ function createExecuteContentSandbox(aWindow, marionette, args) {
*/
function executeScript(msg, directInject) {
let script = msg.json.value;
- let marionette = new Marionette(false, win, "content", marionetteLogObj);
+ let marionette = new Marionette(false, curWindow, "content", marionetteLogObj);
- let sandbox = createExecuteContentSandbox(win, marionette, msg.json.args);
+ let sandbox = createExecuteContentSandbox(curWindow, marionette, msg.json.args);
if (!sandbox)
return;
@@ -299,7 +300,7 @@ function executeJSScript(msg) {
* method is called, or if it times out.
*/
function executeWithCallback(msg, timeout) {
- win.addEventListener("unload", errUnload, false);
+ curWindow.addEventListener("unload", errUnload, false);
let script = msg.json.value;
let command_id = msg.json.id;
@@ -308,21 +309,21 @@ function executeWithCallback(msg, timeout) {
// However Selenium code returns 28, see
// http://code.google.com/p/selenium/source/browse/trunk/javascript/firefox-driver/js/evaluate.js.
// We'll stay compatible with the Selenium code.
- let timeoutId = win.setTimeout(function() {
+ let timeoutId = curWindow.setTimeout(function() {
contentAsyncReturnFunc('timed out', 28);
}, marionetteTimeout);
- win.addEventListener('error', function win__onerror(evt) {
- win.removeEventListener('error', win__onerror, true);
+ curWindow.addEventListener('error', function win__onerror(evt) {
+ curWindow.removeEventListener('error', win__onerror, true);
contentAsyncReturnFunc(evt, 17);
return true;
}, true);
function contentAsyncReturnFunc(value, status) {
- win.removeEventListener("unload", errUnload, false);
+ curWindow.removeEventListener("unload", errUnload, false);
/* clear all timeouts potentially generated by the script*/
for(let i=0; i<=timeoutId; i++) {
- win.clearTimeout(i);
+ curWindow.clearTimeout(i);
}
sendSyncMessage("Marionette:testLog", {value: elementManager.wrapValue(marionetteLogObj.getLogs())});
@@ -349,9 +350,9 @@ function executeWithCallback(msg, timeout) {
"__marionetteFunc.apply(null, __marionetteParams); ";
}
- let marionette = new Marionette(true, win, "content", marionetteLogObj);
+ let marionette = new Marionette(true, curWindow, "content", marionetteLogObj);
- let sandbox = createExecuteContentSandbox(win, marionette, msg.json.args);
+ let sandbox = createExecuteContentSandbox(curWindow, marionette, msg.json.args);
if (!sandbox)
return;
@@ -387,37 +388,32 @@ function setSearchTimeout(msg) {
* All other navigation is handled by the server (in chrome space).
*/
function goUrl(msg) {
- if (activeFrame != null) {
- win.document.location = msg.json.value;
- //TODO: replace this with event firing when Bug 720714 is resolved
- let checkTimer = Components.classes["@mozilla.org/timer;1"].createInstance(Components.interfaces.nsITimer);
- let checkLoad = function () {
- if (win.document.readyState == "complete") {
- sendOk();
- }
- else {
- checkTimer.initWithCallback(checkLoad, 100, Components.interfaces.nsITimer.TYPE_ONE_SHOT);
- }
- };
- checkLoad();
- }
- else {
- sendAsyncMessage("Marionette:goUrl", {value: msg.json.value});
- }
+ curWindow.location = msg.json.value;
+ //TODO: replace this with DOMContentLoaded event listening when Bug 720714 is resolved
+ let checkTimer = Components.classes["@mozilla.org/timer;1"].createInstance(Components.interfaces.nsITimer);
+ let checkLoad = function () {
+ if (curWindow.document.readyState == "complete") {
+ sendOk();
+ }
+ else {
+ checkTimer.initWithCallback(checkLoad, 100, Components.interfaces.nsITimer.TYPE_ONE_SHOT);
+ }
+ };
+ checkTimer.initWithCallback(checkLoad, 100, Components.interfaces.nsITimer.TYPE_ONE_SHOT);
}
/**
* Get the current URI
*/
function getUrl(msg) {
- sendResponse({value: win.location.href});
+ sendResponse({value: curWindow.location.href});
}
/**
* Go back in history
*/
function goBack(msg) {
- win.history.back();
+ curWindow.history.back();
sendOk();
}
@@ -425,7 +421,7 @@ function goBack(msg) {
* Go forward in history
*/
function goForward(msg) {
- win.history.forward();
+ curWindow.history.forward();
sendOk();
}
@@ -433,7 +429,7 @@ function goForward(msg) {
* Refresh the page
*/
function refresh(msg) {
- win.location.reload(true);
+ curWindow.location.reload(true);
let listen = function() { removeEventListener("DOMContentLoaded", arguments.callee, false); sendOk() } ;
addEventListener("DOMContentLoaded", listen, false);
}
@@ -445,8 +441,7 @@ function findElementContent(msg) {
let id;
try {
let notify = function(id) { sendResponse({value:id});};
- let curWin = activeFrame ? win.frames[activeFrame] : win;
- id = elementManager.find(curWin, msg.json, notify, false);
+ id = elementManager.find(curWindow, msg.json, notify, false);
}
catch (e) {
sendError(e.message, e.num, e.stack);
@@ -460,8 +455,7 @@ function findElementsContent(msg) {
let id;
try {
let notify = function(id) { sendResponse({value:id});};
- let curWin = activeFrame ? win.frames[activeFrame] : win;
- id = elementManager.find(curWin, msg.json, notify, true);
+ id = elementManager.find(curWindow, msg.json, notify, true);
}
catch (e) {
sendError(e.message, e.num, e.stack);
@@ -474,8 +468,7 @@ function findElementsContent(msg) {
function clickElement(msg) {
let el;
try {
- //el = elementManager.click(msg.json.element, win);
- el = elementManager.getKnownElement(msg.json.element, win);
+ el = elementManager.getKnownElement(msg.json.element, curWindow);
utils.click(el);
sendOk();
}
@@ -489,7 +482,7 @@ function clickElement(msg) {
*/
function getElementAttribute(msg) {
try {
- let el = elementManager.getKnownElement(msg.json.element, win);
+ let el = elementManager.getKnownElement(msg.json.element, curWindow);
sendResponse({value: utils.getElementAttribute(el, msg.json.name)});
}
catch (e) {
@@ -502,7 +495,7 @@ function getElementAttribute(msg) {
*/
function getElementText(msg) {
try {
- let el = elementManager.getKnownElement(msg.json.element, win);
+ let el = elementManager.getKnownElement(msg.json.element, curWindow);
sendResponse({value: utils.getElementText(el)});
}
catch (e) {
@@ -515,7 +508,7 @@ function getElementText(msg) {
*/
function isElementDisplayed(msg) {
try {
- let el = elementManager.getKnownElement(msg.json.element, win);
+ let el = elementManager.getKnownElement(msg.json.element, curWindow);
sendResponse({value: utils.isElementDisplayed(el)});
}
catch (e) {
@@ -528,7 +521,7 @@ function isElementDisplayed(msg) {
*/
function isElementEnabled(msg) {
try {
- let el = elementManager.getKnownElement(msg.json.element, win);
+ let el = elementManager.getKnownElement(msg.json.element, curWindow);
sendResponse({value: utils.isElementEnabled(el)});
}
catch (e) {
@@ -541,7 +534,7 @@ function isElementEnabled(msg) {
*/
function isElementSelected(msg) {
try {
- let el = elementManager.getKnownElement(msg.json.element, win);
+ let el = elementManager.getKnownElement(msg.json.element, curWindow);
sendResponse({value: utils.isElementSelected(el)});
}
catch (e) {
@@ -554,7 +547,7 @@ function isElementSelected(msg) {
*/
function sendKeysToElement(msg) {
try {
- let el = elementManager.getKnownElement(msg.json.element, win);
+ let el = elementManager.getKnownElement(msg.json.element, curWindow);
utils.sendKeysToElement(el, msg.json.value);
sendOk();
}
@@ -568,7 +561,7 @@ function sendKeysToElement(msg) {
*/
function clearElement(msg) {
try {
- let el = elementManager.getKnownElement(msg.json.element, win);
+ let el = elementManager.getKnownElement(msg.json.element, curWindow);
utils.clearElement(el);
sendOk();
}
@@ -584,21 +577,19 @@ function clearElement(msg) {
function switchToFrame(msg) {
let foundFrame = null;
if ((msg.json.value == null) && (msg.json.element == null)) {
- win = content;
- activeFrame = null;
- content.focus();
+ curWindow = content;
+ curWindow.focus();
sendOk();
return;
}
if (msg.json.element != undefined) {
if (elementManager.seenItems[msg.json.element] != undefined) {
- let wantedFrame = elementManager.getKnownElement(msg.json.element, win);//HTMLIFrameElement
- let numFrames = win.frames.length;
+ let wantedFrame = elementManager.getKnownElement(msg.json.element, curWindow); //HTMLIFrameElement
+ let numFrames = curWindow.frames.length;
for (let i = 0; i < numFrames; i++) {
- if (win.frames[i].frameElement == wantedFrame) {
- win = win.frames[i];
- activeFrame = i;
- win.focus();
+ if (curWindow.frames[i].frameElement == wantedFrame) {
+ curWindow = curWindow.frames[i];
+ curWindow.focus();
sendOk();
return;
}
@@ -608,10 +599,10 @@ function switchToFrame(msg) {
switch(typeof(msg.json.value)) {
case "string" :
let foundById = null;
- let numFrames = win.frames.length;
+ let numFrames = curWindow.frames.length;
for (let i = 0; i < numFrames; i++) {
//give precedence to name
- let frame = win.frames[i];
+ let frame = curWindow.frames[i];
let frameElement = frame.frameElement;
if (frameElement.name == msg.json.value) {
foundFrame = i;
@@ -625,17 +616,14 @@ function switchToFrame(msg) {
}
break;
case "number":
- if (win.frames[msg.json.value] != undefined) {
+ if (curWindow.frames[msg.json.value] != undefined) {
foundFrame = msg.json.value;
}
break;
}
- //TODO: implement index
if (foundFrame != null) {
- let frameWindow = win.frames[foundFrame];
- activeFrame = foundFrame;
- win = frameWindow;
- win.focus();
+ curWindow = curWindow.frames[foundFrame];
+ curWindow.focus();
sendOk();
} else {
sendError("Unable to locate frame: " + msg.json.value, 8, null);
diff --git a/testing/talos/talos_from_code.py b/testing/talos/talos_from_code.py
index 25d9334f0b0..a581656c896 100644
--- a/testing/talos/talos_from_code.py
+++ b/testing/talos/talos_from_code.py
@@ -48,8 +48,8 @@ def main():
entity = get_value(jsonFilename, key)
if passesRestrictions(options.talos_json_url, entity["url"]):
# the key is at the same time the filename e.g. talos.zip
+ print "INFO: Downloading %s as %s" % (entity["url"], os.path.join(entity["path"], key))
download_file(entity["url"], entity["path"], key)
- print "INFO: %s -> %s" % (entity["url"], os.path.join(entity["path"], key))
else:
print "ERROR: You have tried to download a file " + \
"from: %s " % fileUrl + \
diff --git a/toolkit/mozapps/update/test/unit/data/old_version_mar.mar b/toolkit/mozapps/update/test/unit/data/old_version_mar.mar
new file mode 100644
index 00000000000..31550698a0f
Binary files /dev/null and b/toolkit/mozapps/update/test/unit/data/old_version_mar.mar differ
diff --git a/toolkit/mozapps/update/test/unit/data/wrong_product_channel_mar.mar b/toolkit/mozapps/update/test/unit/data/wrong_product_channel_mar.mar
new file mode 100644
index 00000000000..1e39cc214f9
Binary files /dev/null and b/toolkit/mozapps/update/test/unit/data/wrong_product_channel_mar.mar differ
diff --git a/toolkit/mozapps/update/test/unit/head_update.js.in b/toolkit/mozapps/update/test/unit/head_update.js.in
index a21d67aeee3..4be57247fcf 100644
--- a/toolkit/mozapps/update/test/unit/head_update.js.in
+++ b/toolkit/mozapps/update/test/unit/head_update.js.in
@@ -87,11 +87,14 @@ const APPLY_TO_DIR_SUFFIX = "_applyToDir/";
const HELPER_BIN_FILE = "TestAUSHelper" + BIN_SUFFIX;
const MAR_COMPLETE_FILE = "data/complete.mar";
const MAR_PARTIAL_FILE = "data/partial.mar";
+const MAR_OLD_VERSION_FILE = "data/old_version_mar.mar";
+const MAR_WRONG_CHANNEL_FILE = "data/wrong_product_channel_mar.mar";
const UPDATER_BIN_FILE = "updater" + BIN_SUFFIX;
const MAINTENANCE_SERVICE_BIN_FILE = "maintenanceservice.exe";
const MAINTENANCE_SERVICE_INSTALLER_BIN_FILE = "maintenanceservice_installer.exe";
const UPDATE_SETTINGS_INI_FILE = "update-settings.ini";
-const UPDATE_SETTINGS_CONTENTS = "[Settings]\nMAR_CHANNEL_ID=xpcshell-test\n"
+const UPDATE_SETTINGS_CONTENTS = "[Settings]\n" +
+ "ACCEPTED_MAR_CHANNEL_IDS=xpcshell-test\n"
const UPDATES_DIR_SUFFIX = "_mar";
const LOG_COMPLETE_SUCCESS = "data/complete_log_success";
diff --git a/toolkit/mozapps/update/test/unit/test_0113_versionDowngradeCheck.js b/toolkit/mozapps/update/test/unit/test_0113_versionDowngradeCheck.js
new file mode 100644
index 00000000000..a27a51f7b6e
--- /dev/null
+++ b/toolkit/mozapps/update/test/unit/test_0113_versionDowngradeCheck.js
@@ -0,0 +1,65 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (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.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is mozilla.org code.
+ *
+ * The Initial Developer of the Original Code is
+ * the Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2012
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Brian R. Bondy (Original Author)
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK *****
+ */
+
+/* Test version downgrade MAR security check */
+
+const TEST_ID = "0113";
+
+// We don't actually care if the MAR has any data, we only care about the
+// application return code and update.status result.
+const TEST_FILES = [];
+
+const VERSION_DOWNGRADE_ERROR = "23";
+
+function run_test() {
+ // Setup an old version MAR file
+ do_register_cleanup(cleanupUpdaterTest);
+ setupUpdaterTest(MAR_OLD_VERSION_FILE);
+
+ // Apply the MAR
+ let exitValue = runUpdate();
+ logTestInfo("testing updater binary process exitValue for failure when " +
+ "applying a version downgrade MAR");
+ // Make sure the updater executed successfully
+ do_check_eq(exitValue, 0);
+ let updatesDir = do_get_file(TEST_ID + UPDATES_DIR_SUFFIX);
+
+ //Make sure we get a version downgrade error
+ let updateStatus = readStatusFile(updatesDir);
+ do_check_eq(updateStatus.split(": ")[1], VERSION_DOWNGRADE_ERROR);
+}
diff --git a/toolkit/mozapps/update/test/unit/test_0114_productChannelCheck.js b/toolkit/mozapps/update/test/unit/test_0114_productChannelCheck.js
new file mode 100644
index 00000000000..3d5f7174852
--- /dev/null
+++ b/toolkit/mozapps/update/test/unit/test_0114_productChannelCheck.js
@@ -0,0 +1,65 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (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.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is mozilla.org code.
+ *
+ * The Initial Developer of the Original Code is
+ * the Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2012
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Brian R. Bondy (Original Author)
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK *****
+ */
+
+/* Test product/channel MAR security check */
+
+const TEST_ID = "0114";
+
+// We don't actually care if the MAR has any data, we only care about the
+// application return code and update.status result.
+const TEST_FILES = [];
+
+const MAR_CHANNEL_MISMATCH_ERROR = "22";
+
+function run_test() {
+ // Setup a wrong channel MAR file
+ do_register_cleanup(cleanupUpdaterTest);
+ setupUpdaterTest(MAR_WRONG_CHANNEL_FILE);
+
+ // Apply the MAR
+ let exitValue = runUpdate();
+ logTestInfo("testing updater binary process exitValue for failure when " +
+ "applying a wrong product and channel MAR file");
+ // Make sure the updater executed successfully
+ do_check_eq(exitValue, 0);
+ let updatesDir = do_get_file(TEST_ID + UPDATES_DIR_SUFFIX);
+
+ //Make sure we get a version downgrade error
+ let updateStatus = readStatusFile(updatesDir);
+ do_check_eq(updateStatus.split(": ")[1], MAR_CHANNEL_MISMATCH_ERROR);
+}
diff --git a/toolkit/mozapps/update/test/unit/xpcshell_updater_windows.ini b/toolkit/mozapps/update/test/unit/xpcshell_updater_windows.ini
index cdda5ec9b4d..8aaa26607b5 100644
--- a/toolkit/mozapps/update/test/unit/xpcshell_updater_windows.ini
+++ b/toolkit/mozapps/update/test/unit/xpcshell_updater_windows.ini
@@ -1,3 +1,5 @@
+[test_0113_versionDowngradeCheck.js]
+[test_0114_productChannelCheck.js]
[test_0150_appBinReplaced_xp_win_complete.js]
[test_0151_appBinPatched_xp_win_partial.js]
[test_0160_appInUse_xp_win_complete.js]
diff --git a/webapprt/ContentPolicy.js b/webapprt/ContentPolicy.js
index 37c70468d90..b71aff196e7 100644
--- a/webapprt/ContentPolicy.js
+++ b/webapprt/ContentPolicy.js
@@ -16,6 +16,7 @@ const allowedOrigins = [
"https://www.facebook.com",
"https://accounts.google.com",
"https://www.google.com",
+ "https://twitter.com",
"https://api.twitter.com",
];
diff --git a/xpfe/components/autocomplete/resources/content/autocomplete.xml b/xpfe/components/autocomplete/resources/content/autocomplete.xml
index 6cebcfb05cf..873458d5bc4 100644
--- a/xpfe/components/autocomplete/resources/content/autocomplete.xml
+++ b/xpfe/components/autocomplete/resources/content/autocomplete.xml
@@ -139,6 +139,10 @@
onset="this.setAttribute('timeout', val); return val;"
onget="return parseInt(this.getAttribute('timeout')) || 0;"/>
+
+