merge fx-team to mozilla-central a=merge

This commit is contained in:
Carsten "Tomcat" Book 2015-07-31 11:59:12 +02:00
commit 6226f32485
52 changed files with 691 additions and 423 deletions

View File

@ -48,10 +48,10 @@
<button id="ruleview-add-rule-button" title="&addRuleButtonTooltip;" class="devtools-button"></button>
<button id="pseudo-class-panel-toggle" title="&togglePseudoClassPanel;" class="devtools-button"></button>
</div>
<div id="pseudo-class-panel" class="devtools-toolbar" hidden="true">
<label><input id="pseudo-hover-toggle" type="checkbox" value=":hover" />:hover</label>
<label><input id="pseudo-active-toggle" type="checkbox" value=":active" />:active</label>
<label><input id="pseudo-focus-toggle" type="checkbox" value=":focus" />:focus</label>
<div id="pseudo-class-panel" class="devtools-toolbar" hidden="true" tabindex="-1">
<label><input id="pseudo-hover-toggle" type="checkbox" value=":hover" tabindex="-1" />:hover</label>
<label><input id="pseudo-active-toggle" type="checkbox" value=":active" tabindex="-1" />:active</label>
<label><input id="pseudo-focus-toggle" type="checkbox" value=":focus" tabindex="-1" />:focus</label>
</div>
</div>

View File

@ -2298,9 +2298,16 @@ CssRuleView.prototype = {
_onTogglePseudoClassPanel: function() {
if (this.pseudoClassPanel.hidden) {
this.pseudoClassToggle.setAttribute("checked", "true");
this.hoverCheckbox.setAttribute("tabindex", "0");
this.activeCheckbox.setAttribute("tabindex", "0");
this.focusCheckbox.setAttribute("tabindex", "0");
} else {
this.pseudoClassToggle.removeAttribute("checked");
this.hoverCheckbox.setAttribute("tabindex", "-1");
this.activeCheckbox.setAttribute("tabindex", "-1");
this.focusCheckbox.setAttribute("tabindex", "-1");
}
this.pseudoClassPanel.hidden = !this.pseudoClassPanel.hidden;
},

View File

@ -29,40 +29,38 @@ add_task(function*() {
let {inspector, view} = yield openRuleView();
yield selectNode("div", inspector);
yield assertPseudoPanelClosed(view);
info("Toggle the pseudo class panel open");
ok(view.pseudoClassPanel.hidden, "Pseudo Class Panel Hidden");
view.pseudoClassToggle.click();
ok(!view.pseudoClassPanel.hidden, "Pseudo Class Panel Opened");
ok(!view.hoverCheckbox.disabled, ":hover checkbox is not disabled");
ok(!view.activeCheckbox.disabled, ":active checkbox is not disabled");
ok(!view.focusCheckbox.disabled, ":focus checkbox is not disabled");
yield assertPseudoPanelOpened(view);
info("Toggle each pseudo lock and check that the pseudo lock is added");
yield togglePseudoClass(inspector, view, view.hoverCheckbox);
yield togglePseudoClass(inspector, view.hoverCheckbox);
yield assertPseudoAdded(inspector, view, ":hover", 3, 1);
yield togglePseudoClass(inspector, view, view.hoverCheckbox);
yield togglePseudoClass(inspector, view.hoverCheckbox);
yield assertPseudoRemoved(inspector, view, 2);
yield togglePseudoClass(inspector, view, view.activeCheckbox);
yield togglePseudoClass(inspector, view.activeCheckbox);
yield assertPseudoAdded(inspector, view, ":active", 3, 1);
yield togglePseudoClass(inspector, view, view.activeCheckbox);
yield togglePseudoClass(inspector, view.activeCheckbox);
yield assertPseudoRemoved(inspector, view, 2);
yield togglePseudoClass(inspector, view, view.focusCheckbox);
yield togglePseudoClass(inspector, view.focusCheckbox);
yield assertPseudoAdded(inspector, view, ":focus", 3, 1);
yield togglePseudoClass(inspector, view, view.focusCheckbox);
yield togglePseudoClass(inspector, view.focusCheckbox);
yield assertPseudoRemoved(inspector, view, 2);
info("Toggle all pseudo lock and check that the pseudo lock is added");
yield togglePseudoClass(inspector, view, view.hoverCheckbox);
yield togglePseudoClass(inspector, view, view.activeCheckbox);
yield togglePseudoClass(inspector, view, view.focusCheckbox);
yield togglePseudoClass(inspector, view.hoverCheckbox);
yield togglePseudoClass(inspector, view.activeCheckbox);
yield togglePseudoClass(inspector, view.focusCheckbox);
yield assertPseudoAdded(inspector, view, ":focus", 5, 1);
yield assertPseudoAdded(inspector, view, ":active", 5, 2);
yield assertPseudoAdded(inspector, view, ":hover", 5, 3);
yield togglePseudoClass(inspector, view, view.hoverCheckbox);
yield togglePseudoClass(inspector, view, view.activeCheckbox);
yield togglePseudoClass(inspector, view, view.focusCheckbox);
yield togglePseudoClass(inspector, view.hoverCheckbox);
yield togglePseudoClass(inspector, view.activeCheckbox);
yield togglePseudoClass(inspector, view.focusCheckbox);
yield assertPseudoRemoved(inspector, view, 2);
info("Select a null element");
@ -76,29 +74,62 @@ add_task(function*() {
info("Toggle the pseudo class panel close");
view.pseudoClassToggle.click();
ok(view.pseudoClassPanel.hidden, "Pseudo Class Panel Closed");
yield assertPseudoPanelClosed(view);
});
function* togglePseudoClass(inspector, ruleView, pseudoClassOption) {
function* togglePseudoClass(inspector, pseudoClassOption) {
info("Toggle the pseudoclass, wait for it to be applied");
let onRefresh = inspector.once("rule-view-refreshed");
pseudoClassOption.click();
yield onRefresh;
}
function* assertPseudoAdded(inspector, ruleView, pseudoClass, numRules,
function* assertPseudoAdded(inspector, view, pseudoClass, numRules,
childIndex) {
info("Check that the ruleview contains the pseudo-class rule");
is(ruleView.element.children.length, numRules,
is(view.element.children.length, numRules,
"Should have " + numRules + " rules.");
is(getRuleViewRuleEditor(ruleView, childIndex).rule.selectorText,
is(getRuleViewRuleEditor(view, childIndex).rule.selectorText,
"div" + pseudoClass, "rule view is showing " + pseudoClass + " rule");
}
function* assertPseudoRemoved(inspector, ruleView, numRules) {
function* assertPseudoRemoved(inspector, view, numRules) {
info("Check that the ruleview no longer contains the pseudo-class rule");
is(ruleView.element.children.length, numRules,
is(view.element.children.length, numRules,
"Should have " + numRules + " rules.");
is(getRuleViewRuleEditor(ruleView, 1).rule.selectorText, "div",
is(getRuleViewRuleEditor(view, 1).rule.selectorText, "div",
"Second rule is div");
}
function* assertPseudoPanelOpened(view) {
info("Check the opened state of the pseudo class panel");
ok(!view.pseudoClassPanel.hidden, "Pseudo Class Panel Opened");
ok(!view.hoverCheckbox.disabled, ":hover checkbox is not disabled");
ok(!view.activeCheckbox.disabled, ":active checkbox is not disabled");
ok(!view.focusCheckbox.disabled, ":focus checkbox is not disabled");
is(view.pseudoClassPanel.getAttribute("tabindex"), "-1",
"Pseudo Class Panel has a tabindex of -1");
is(view.hoverCheckbox.getAttribute("tabindex"), "0",
":hover checkbox has a tabindex of 0");
is(view.activeCheckbox.getAttribute("tabindex"), "0",
":active checkbox has a tabindex of 0");
is(view.focusCheckbox.getAttribute("tabindex"), "0",
":focus checkbox has a tabindex of 0");
}
function* assertPseudoPanelClosed(view) {
info("Check the closed state of the pseudo clas panel");
ok(view.pseudoClassPanel.hidden, "Pseudo Class Panel Hidden");
is(view.pseudoClassPanel.getAttribute("tabindex"), "-1",
"Pseudo Class Panel has a tabindex of -1");
is(view.hoverCheckbox.getAttribute("tabindex"), "-1",
":hover checkbox has a tabindex of -1");
is(view.activeCheckbox.getAttribute("tabindex"), "-1",
":active checkbox has a tabindex of -1");
is(view.focusCheckbox.getAttribute("tabindex"), "-1",
":focus checkbox has a tabindex of -1");
}

View File

@ -309,7 +309,7 @@
</intent-filter>
</receiver>
<receiver android:name="org.mozilla.gecko.RestrictionProvider">
<receiver android:name="org.mozilla.gecko.restrictions.RestrictionProvider">
<intent-filter>
<action android:name="android.intent.action.GET_RESTRICTION_ENTRIES" />
</intent-filter>

View File

@ -55,6 +55,7 @@ import org.mozilla.gecko.preferences.ClearOnShutdownPref;
import org.mozilla.gecko.preferences.GeckoPreferences;
import org.mozilla.gecko.prompts.Prompt;
import org.mozilla.gecko.prompts.PromptListItem;
import org.mozilla.gecko.restrictions.Restriction;
import org.mozilla.gecko.sync.Utils;
import org.mozilla.gecko.sync.repositories.android.FennecTabsRepository;
import org.mozilla.gecko.sync.setup.SyncAccounts;
@ -1975,7 +1976,7 @@ public class BrowserApp extends GeckoApp
}
}
if (AppConstants.MOZ_STUMBLER_BUILD_TIME_ENABLED) {
if (AppConstants.MOZ_STUMBLER_BUILD_TIME_ENABLED && RestrictedProfiles.isAllowed(this, Restriction.DISALLOW_LOCATION_SERVICE)) {
// Start (this acts as ping if started already) the stumbler lib; if the stumbler has queued data it will upload it.
// Stumbler operates on its own thread, and startup impact is further minimized by delaying work (such as upload) a few seconds.
// Avoid any potential startup CPU/thread contention by delaying the pref broadcast.
@ -2634,6 +2635,10 @@ public class BrowserApp extends GeckoApp
}
private void showFirstrunPager() {
// Do not show first run if we're in an Android Restricted Profile
if (RestrictedProfiles.isUserRestricted(this)) {
return;
}
if (mFirstrunPane == null) {
final ViewStub firstrunPagerStub = (ViewStub) findViewById(R.id.firstrun_pager_stub);
mFirstrunPane = (FirstrunPane) firstrunPagerStub.inflate();
@ -2690,7 +2695,7 @@ public class BrowserApp extends GeckoApp
});
// Don't show the banner in guest mode.
if (!getProfile().inGuestMode()) {
if (!RestrictedProfiles.isUserRestricted()) {
final ViewStub homeBannerStub = (ViewStub) findViewById(R.id.home_banner_stub);
final HomeBanner homeBanner = (HomeBanner) homeBannerStub.inflate();
mHomePager.setBanner(homeBanner);
@ -3286,12 +3291,12 @@ public class BrowserApp extends GeckoApp
}
// Disable share menuitem for about:, chrome:, file:, and resource: URIs
final boolean shareVisible = RestrictedProfiles.isAllowed(this, RestrictedProfiles.Restriction.DISALLOW_SHARE);
final boolean shareVisible = RestrictedProfiles.isAllowed(this, Restriction.DISALLOW_SHARE);
share.setVisible(shareVisible);
final boolean shareEnabled = StringUtils.isShareableUrl(url) && shareVisible;
share.setEnabled(shareEnabled);
MenuUtils.safeSetEnabled(aMenu, R.id.addons, RestrictedProfiles.isAllowed(this, RestrictedProfiles.Restriction.DISALLOW_INSTALL_EXTENSION));
MenuUtils.safeSetEnabled(aMenu, R.id.downloads, RestrictedProfiles.isAllowed(this, RestrictedProfiles.Restriction.DISALLOW_DOWNLOADS));
MenuUtils.safeSetEnabled(aMenu, R.id.addons, RestrictedProfiles.isAllowed(this, Restriction.DISALLOW_INSTALL_EXTENSION));
MenuUtils.safeSetEnabled(aMenu, R.id.downloads, RestrictedProfiles.isAllowed(this, Restriction.DISALLOW_DOWNLOADS));
// NOTE: Use MenuUtils.safeSetEnabled because some actions might
// be on the BrowserToolbar context menu.
@ -3356,9 +3361,12 @@ public class BrowserApp extends GeckoApp
}
// Hide tools menu if restriction is active
final boolean toolsVisible = RestrictedProfiles.isAllowed(this, RestrictedProfiles.Restriction.DISALLOW_TOOLS_MENU);
final boolean toolsVisible = RestrictedProfiles.isAllowed(this, Restriction.DISALLOW_TOOLS_MENU);
MenuUtils.safeSetVisible(aMenu, R.id.tools, toolsVisible);
final boolean privateTabVisible = RestrictedProfiles.isAllowed(this, Restriction.DISALLOW_PRIVATE_BROWSING);
MenuUtils.safeSetVisible(aMenu, R.id.new_private_tab, privateTabVisible);
// Disable save as PDF for about:home and xul pages.
saveAsPDF.setEnabled(!(isAboutHome(tab) ||
tab.getContentType().equals("application/vnd.mozilla.xul+xml") ||

View File

@ -1209,12 +1209,6 @@ public class GeckoAppShell
final Intent intent = getOpenURIIntentInner(context, targetURI, mimeType, action, title);
if (intent != null) {
// Setting category on file:// URIs breaks about:downloads (Bug 1176018)
if (!targetURI.startsWith("file:")) {
// Only handle applications which can accept arbitrary data from a browser.
intent.addCategory(Intent.CATEGORY_BROWSABLE);
}
// Some applications use this field to return to the same browser after processing the
// Intent. While there is some danger (e.g. denial of service), other major browsers already
// use it and so it's the norm.
@ -1245,15 +1239,18 @@ public class GeckoAppShell
}
final String scheme = uri.getScheme();
if ("intent".equals(scheme)) {
if ("intent".equals(scheme) || "android-app".equals(scheme)) {
final Intent intent;
try {
intent = Intent.parseUri(targetURI, Intent.URI_INTENT_SCHEME);
intent = Intent.parseUri(targetURI, 0);
} catch (final URISyntaxException e) {
Log.e(LOGTAG, "Unable to parse URI - " + e);
return null;
}
// Only open applications which can accept arbitrary data from a browser.
intent.addCategory(Intent.CATEGORY_BROWSABLE);
// Prevent site from explicitly opening our internal activities, which can leak data.
intent.setComponent(null);
nullIntentSelector(intent);

View File

@ -5,18 +5,17 @@
package org.mozilla.gecko;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Set;
import org.json.JSONException;
import org.json.JSONObject;
import org.mozilla.gecko.AppConstants.Versions;
import org.mozilla.gecko.mozglue.RobocopTarget;
import org.mozilla.gecko.mozglue.generatorannotations.WrapElementForJNI;
import org.mozilla.gecko.restrictions.DefaultConfiguration;
import org.mozilla.gecko.restrictions.GuestProfileConfiguration;
import org.mozilla.gecko.restrictions.RestrictedProfileConfiguration;
import org.mozilla.gecko.restrictions.Restriction;
import org.mozilla.gecko.restrictions.RestrictionConfiguration;
import android.annotation.TargetApi;
import android.content.Context;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.UserManager;
@ -26,118 +25,55 @@ import android.util.Log;
public class RestrictedProfiles {
private static final String LOGTAG = "GeckoRestrictedProfiles";
private static volatile Boolean inGuest = null;
private static RestrictionConfiguration configuration;
@SuppressWarnings("serial")
private static final List<String> BANNED_SCHEMES = new ArrayList<String>() {{
add("file");
add("chrome");
add("resource");
add("jar");
add("wyciwyg");
}};
private static final String ABOUT_ADDONS = "about:addons";
/**
* This is a hack to allow non-GeckoApp activities to safely call into
* RestrictedProfiles without reworking this class or GeckoProfile.
*
* It can be removed after Bug 1077590 lands.
*/
public static void initWithProfile(GeckoProfile profile) {
inGuest = profile.inGuestMode();
}
private static boolean getInGuest() {
if (inGuest == null) {
inGuest = GeckoAppShell.getGeckoInterface().getProfile().inGuestMode();
private static RestrictionConfiguration getConfiguration(Context context) {
if (configuration == null) {
configuration = createConfiguration(context);
}
return inGuest;
return configuration;
}
@SuppressWarnings("serial")
private static final List<String> BANNED_URLS = new ArrayList<String>() {{
add("about:config");
}};
/* This is a list of things we can restrict you from doing. Some of these are reflected in Android UserManager constants.
* Others are specific to us.
* These constants should be in sync with the ones from toolkit/components/parentalcontrols/nsIParentalControlServices.idl
*/
public enum Restriction {
// These restrictions have no strings assigned because they are only used in guest mode and not shown in the
// restricted profiles settings UI
DISALLOW_DOWNLOADS(1, "no_download_files", 0, 0),
DISALLOW_BROWSE_FILES(4, "no_browse_files", 0, 0),
DISALLOW_SHARE(5, "no_share", 0, 0),
DISALLOW_BOOKMARK(6, "no_bookmark", 0, 0),
DISALLOW_ADD_CONTACTS(7, "no_add_contacts", 0, 0),
DISALLOW_SET_IMAGE(8, "no_set_image", 0, 0),
DISALLOW_MODIFY_ACCOUNTS(9, "no_modify_accounts", 0, 0), // UserManager.DISALLOW_MODIFY_ACCOUNTS
DISALLOW_REMOTE_DEBUGGING(10, "no_remote_debugging", 0, 0),
// These restrictions are used for restricted profiles and therefore need to have strings assigned for the profile
// settings UI.
DISALLOW_INSTALL_EXTENSION(2, "no_install_extensions", R.string.restriction_disallow_addons_title, R.string.restriction_disallow_addons_description),
DISALLOW_INSTALL_APPS(3, "no_install_apps", R.string.restriction_disallow_apps_title, R.string.restriction_disallow_apps_description), // UserManager.DISALLOW_INSTALL_APPS
DISALLOW_IMPORT_SETTINGS(11, "no_report_site_issue", R.string.restriction_disallow_import_settings_title, R.string.restriction_disallow_import_settings_description),
DISALLOW_TOOLS_MENU(12, "no_tools_menu", R.string.restriction_disallow_tools_menu_title, R.string.restriction_disallow_tools_menu_description),
DISALLOW_REPORT_SITE_ISSUE(13, "no_report_site_issue", R.string.restriction_disallow_report_site_issue_title, R.string.restriction_disallow_report_site_issue_description);
public final int id;
public final String name;
public final int titleResource;
public final int descriptionResource;
Restriction(final int id, final String name, int titleResource, int descriptionResource) {
this.id = id;
this.name = name;
this.titleResource = titleResource;
this.descriptionResource = descriptionResource;
public static synchronized RestrictionConfiguration createConfiguration(Context context) {
if (configuration != null) {
// This method is synchronized and another thread might already have created the configuration.
return configuration;
}
public String getTitle(Context context) {
if (titleResource == 0) {
return toString();
if (isGuestProfile()) {
return new GuestProfileConfiguration();
} else if(isRestrictedProfile(context)) {
return new RestrictedProfileConfiguration(context);
} else {
return new DefaultConfiguration();
}
}
private static boolean isGuestProfile() {
return GeckoAppShell.getGeckoInterface().getProfile().inGuestMode();
}
@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2)
private static boolean isRestrictedProfile(Context context) {
if (Versions.preJBMR2) {
// Early versions don't support restrictions at all
return false;
}
final UserManager mgr = (UserManager) context.getSystemService(Context.USER_SERVICE);
Bundle restrictions = mgr.getApplicationRestrictions(context.getPackageName());
for (String key : restrictions.keySet()) {
if (restrictions.getBoolean(key)) {
// At least one restriction is enabled -> We are a restricted profile
return true;
}
return context.getResources().getString(titleResource);
}
public String getDescription(Context context) {
if (descriptionResource == 0) {
return name;
}
return context.getResources().getString(descriptionResource);
}
return false;
}
static List<Restriction> GUEST_RESTRICTIONS = Arrays.asList(
Restriction.DISALLOW_DOWNLOADS,
Restriction.DISALLOW_INSTALL_EXTENSION,
Restriction.DISALLOW_INSTALL_APPS,
Restriction.DISALLOW_BROWSE_FILES,
Restriction.DISALLOW_SHARE,
Restriction.DISALLOW_BOOKMARK,
Restriction.DISALLOW_ADD_CONTACTS,
Restriction.DISALLOW_SET_IMAGE,
Restriction.DISALLOW_MODIFY_ACCOUNTS,
Restriction.DISALLOW_REMOTE_DEBUGGING,
Restriction.DISALLOW_IMPORT_SETTINGS
);
// Restricted profiles will automatically have these restrictions by default
static List<Restriction> RESTRICTED_PROFILE_RESTRICTIONS = Arrays.asList(
Restriction.DISALLOW_INSTALL_EXTENSION,
Restriction.DISALLOW_INSTALL_APPS,
Restriction.DISALLOW_TOOLS_MENU,
Restriction.DISALLOW_REPORT_SITE_ISSUE,
Restriction.DISALLOW_IMPORT_SETTINGS
);
private static Restriction geckoActionToRestriction(int action) {
for (Restriction rest : Restriction.values()) {
if (rest.id == action) {
@ -148,71 +84,8 @@ public class RestrictedProfiles {
throw new IllegalArgumentException("Unknown action " + action);
}
@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2)
private static Bundle getRestrictions(final Context context) {
final UserManager mgr = (UserManager) context.getSystemService(Context.USER_SERVICE);
return mgr.getUserRestrictions();
}
@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2)
private static Bundle getAppRestrictions(final Context context) {
final UserManager mgr = (UserManager) context.getSystemService(Context.USER_SERVICE);
return mgr.getApplicationRestrictions(context.getPackageName());
}
/**
* This method does the system version check for you.
*
* Returns false if the system doesn't support restrictions,
* or the provided value is not present in the set of user
* restrictions.
*
* Returns true otherwise.
*/
private static boolean getRestriction(final Context context, final Restriction restriction) {
// Early versions don't support restrictions at all,
// so no action can be restricted.
if (Versions.preJBMR2) {
return false;
}
if (!isUserRestricted(context)) {
return false;
}
return getAppRestrictions(context).getBoolean(restriction.name, RESTRICTED_PROFILE_RESTRICTIONS.contains(restriction));
}
private static boolean canLoadUrl(final Context context, final String url) {
// Null URLs are always permitted.
if (url == null) {
return true;
}
try {
// If we're not in guest mode, and the system restriction isn't in place, everything is allowed.
if (!getInGuest() &&
!getRestriction(context, Restriction.DISALLOW_BROWSE_FILES)) {
return true;
}
} catch (IllegalArgumentException ex) {
Log.i(LOGTAG, "Invalid action", ex);
}
final Uri u = Uri.parse(url);
final String scheme = u.getScheme();
if (BANNED_SCHEMES.contains(scheme)) {
return false;
}
for (String banned : BANNED_URLS) {
if (url.startsWith(banned)) {
return false;
}
}
// TODO: The UserManager should support blacklisting URLs by the device owner.
return true;
return getConfiguration(context).canLoadUrl(url);
}
@WrapElementForJNI
@ -221,36 +94,15 @@ public class RestrictedProfiles {
}
public static boolean isUserRestricted(final Context context) {
// Guest mode is supported in all Android versions.
if (getInGuest()) {
return true;
}
if (Versions.preJBMR2) {
return false;
}
Bundle restrictions = getRestrictions(context);
for (String key : restrictions.keySet()) {
if (restrictions.getBoolean(key)) {
// At least one restriction is enabled -> We are a restricted profile
return true;
}
}
return false;
return getConfiguration(context).isRestricted();
}
public static boolean isAllowed(final Context context, final Restriction action) {
return isAllowed(context, action, null);
public static boolean isAllowed(final Context context, final Restriction restriction) {
return getConfiguration(context).isAllowed(restriction);
}
@WrapElementForJNI
public static boolean isAllowed(int action, String url) {
return isAllowed(GeckoAppShell.getContext(), action, url);
}
private static boolean isAllowed(final Context context, int action, String url) {
final Restriction restriction;
try {
restriction = geckoActionToRestriction(action);
@ -261,62 +113,12 @@ public class RestrictedProfiles {
return false;
}
return isAllowed(context, restriction, url);
}
final Context context = GeckoAppShell.getContext();
private static boolean isAllowed(final Context context, final Restriction restriction, String url) {
if (getInGuest()) {
if (Restriction.DISALLOW_BROWSE_FILES == restriction) {
return canLoadUrl(context, url);
}
return !GUEST_RESTRICTIONS.contains(restriction);
if (Restriction.DISALLOW_BROWSE_FILES == restriction) {
return canLoadUrl(context, url);
} else {
return isAllowed(context, restriction);
}
// Disallow browsing about:addons if 'disallow install extension' restriction is enforced
if (restriction == Restriction.DISALLOW_BROWSE_FILES
&& url.toLowerCase().startsWith(ABOUT_ADDONS)
&& !isAllowed(context, Restriction.DISALLOW_INSTALL_EXTENSION)) {
return false;
}
// NOTE: Restrictions hold the opposite intention, so we need to flip it.
return !getRestriction(context, restriction);
}
@WrapElementForJNI
public static String getUserRestrictions() {
return getUserRestrictions(GeckoAppShell.getContext());
}
private static String getUserRestrictions(final Context context) {
// Guest mode is supported in all Android versions
if (getInGuest()) {
StringBuilder builder = new StringBuilder("{ ");
for (Restriction restriction : Restriction.values()) {
builder.append("\"" + restriction.name + "\": true, ");
}
builder.append(" }");
return builder.toString();
}
if (Versions.preJBMR2) {
return "{}";
}
final JSONObject json = new JSONObject();
final Bundle restrictions = getRestrictions(context);
final Set<String> keys = restrictions.keySet();
for (String key : keys) {
try {
json.put(key, restrictions.get(key));
} catch (JSONException e) {
}
}
return json.toString();
}
}

View File

@ -21,6 +21,7 @@ import org.mozilla.gecko.home.HomeConfig.OnReloadListener;
import org.mozilla.gecko.home.HomeConfig.PanelConfig;
import org.mozilla.gecko.home.HomeConfig.PanelType;
import org.mozilla.gecko.home.HomeConfig.State;
import org.mozilla.gecko.restrictions.Restriction;
import org.mozilla.gecko.util.HardwareUtils;
import android.content.BroadcastReceiver;
@ -80,7 +81,7 @@ class HomeConfigPrefsBackend implements HomeConfigBackend {
// We disable Synced Tabs for guest mode profiles.
final PanelConfig remoteTabsEntry;
if (RestrictedProfiles.isAllowed(mContext, RestrictedProfiles.Restriction.DISALLOW_MODIFY_ACCOUNTS)) {
if (RestrictedProfiles.isAllowed(mContext, Restriction.DISALLOW_MODIFY_ACCOUNTS)) {
remoteTabsEntry = createBuiltinPanelConfig(mContext, PanelType.REMOTE_TABS);
} else {
remoteTabsEntry = null;

View File

@ -7,14 +7,13 @@ package org.mozilla.gecko.home;
import java.util.EnumSet;
import org.json.JSONException;
import org.json.JSONObject;
import org.mozilla.gecko.EditBookmarkDialog;
import org.mozilla.gecko.GeckoAppShell;
import org.mozilla.gecko.GeckoEvent;
import org.mozilla.gecko.GeckoProfile;
import org.mozilla.gecko.R;
import org.mozilla.gecko.ReaderModeUtils;
import org.mozilla.gecko.RestrictedProfiles;
import org.mozilla.gecko.Telemetry;
import org.mozilla.gecko.TelemetryContract;
import org.mozilla.gecko.db.BrowserDB;
@ -24,6 +23,7 @@ import org.mozilla.gecko.home.HomeContextMenuInfo.RemoveItemType;
import org.mozilla.gecko.home.HomePager.OnUrlOpenInBackgroundListener;
import org.mozilla.gecko.home.HomePager.OnUrlOpenListener;
import org.mozilla.gecko.home.TopSitesGridView.TopSitesGridContextMenuInfo;
import org.mozilla.gecko.restrictions.Restriction;
import org.mozilla.gecko.util.Clipboard;
import org.mozilla.gecko.util.StringUtils;
import org.mozilla.gecko.util.ThreadUtils;
@ -151,6 +151,10 @@ public abstract class HomeFragment extends Fragment {
if (!StringUtils.isShareableUrl(info.url) || GeckoProfile.get(getActivity()).inGuestMode()) {
menu.findItem(R.id.home_share).setVisible(false);
}
if (!RestrictedProfiles.isAllowed(view.getContext(), Restriction.DISALLOW_PRIVATE_BROWSING)) {
menu.findItem(R.id.home_open_private_tab).setVisible(false);
}
}
@Override

View File

@ -5,9 +5,11 @@ import android.database.Cursor;
import android.database.DataSetObserver;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewStub;
import android.widget.AbsListView;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.ImageView;
@ -197,6 +199,10 @@ public class RemoteTabsSplitPlaneFragment extends RemoteTabsBaseFragment {
// Now the adapter is wrapped; we can remove our footer view.
mClientList.removeFooterView(mFooterView);
// Register touch handler to conditionally enable swipe refresh layout.
mClientList.setOnTouchListener(new ListTouchListener(mClientList));
mTabList.setOnTouchListener(new ListTouchListener(mTabList));
// Create callbacks before the initial loader is started
mCursorLoaderCallbacks = new CursorLoaderCallbacks();
loadIfVisible();
@ -362,4 +368,35 @@ public class RemoteTabsSplitPlaneFragment extends RemoteTabsBaseFragment {
return view;
}
}
/**
* OnTouchListener implementation for ListView that enables swipe to refresh on the touch down event iff list cannot scroll up.
* This implementation does not consume the <code>MotionEvent</code>.
*/
private class ListTouchListener implements View.OnTouchListener {
private final AbsListView listView;
public ListTouchListener(AbsListView listView) {
this.listView = listView;
}
@Override
public boolean onTouch(View v, MotionEvent event) {
final int action = event.getAction();
switch (action) {
case MotionEvent.ACTION_DOWN:
// Enable swipe to refresh iff the first item is visible and is at the top.
mRefreshLayout.setEnabled(listView.getCount() <= 0
|| (listView.getFirstVisiblePosition() <= 0 && listView.getChildAt(0).getTop() >= 0));
break;
case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_UP:
mRefreshLayout.setEnabled(true);
break;
}
// Event is not handled here, it will be consumed in enclosing SwipeRefreshLayout.
return false;
}
}
}

View File

@ -18,6 +18,7 @@ import java.util.Map;
import org.mozilla.gecko.GeckoProfile;
import org.mozilla.gecko.Locales;
import org.mozilla.gecko.R;
import org.mozilla.gecko.RestrictedProfiles;
import org.mozilla.gecko.Tab;
import org.mozilla.gecko.Tabs;
import org.mozilla.gecko.Telemetry;
@ -33,6 +34,7 @@ import org.mozilla.gecko.home.HomePager.OnUrlOpenListener;
import org.mozilla.gecko.home.PinSiteDialog.OnSiteSelectedListener;
import org.mozilla.gecko.home.TopSitesGridView.OnEditPinnedSiteListener;
import org.mozilla.gecko.home.TopSitesGridView.TopSitesGridContextMenuInfo;
import org.mozilla.gecko.restrictions.Restriction;
import org.mozilla.gecko.tiles.TilesRecorder;
import org.mozilla.gecko.tiles.Tile;
import org.mozilla.gecko.util.StringUtils;
@ -369,6 +371,10 @@ public class TopSitesPanel extends HomeFragment {
if (!StringUtils.isShareableUrl(info.url) || GeckoProfile.get(getActivity()).inGuestMode()) {
menu.findItem(R.id.home_share).setVisible(false);
}
if (!RestrictedProfiles.isAllowed(view.getContext(), Restriction.DISALLOW_PRIVATE_BROWSING)) {
menu.findItem(R.id.home_open_private_tab).setVisible(false);
}
}
@Override

View File

@ -693,4 +693,11 @@ just addresses the organization to follow, e.g. "This site is run by " -->
<!ENTITY restriction_disallow_addons_description "Disallow installation of add-ons.">
<!ENTITY restriction_disallow_apps_title "Disallow apps">
<!ENTITY restriction_disallow_apps_description "Disallow installing apps from Firefox Marketplace.">
<!ENTITY restriction_disallow_devtools_title "Disallow developer tools">
<!ENTITY restriction_disallow_devtools_description "Disallow usage of developer tools.">
<!ENTITY restriction_disallow_customize_home_title "Disallow customizing home">
<!ENTITY restriction_disallow_customize_home_description "Disallow customizing home panels.">
<!ENTITY restriction_disallow_private_browsing_title "Disallow private browsing">
<!ENTITY restriction_disallow_private_browsing_description "Disallow private browsing mode.">
<!ENTITY restriction_disallow_location_services_title "Disallow location services">
<!ENTITY restriction_disallow_location_services_description "Disallow sharing of location data to improve geolocation service.">

View File

@ -434,7 +434,12 @@ gbjar.sources += [
'RemoteTabsExpandableListAdapter.java',
'Restarter.java',
'RestrictedProfiles.java',
'RestrictionProvider.java',
'restrictions/DefaultConfiguration.java',
'restrictions/GuestProfileConfiguration.java',
'restrictions/RestrictedProfileConfiguration.java',
'restrictions/Restriction.java',
'restrictions/RestrictionConfiguration.java',
'restrictions/RestrictionProvider.java',
'ServiceNotificationClient.java',
'SessionParser.java',
'SharedPreferencesHelper.java',

View File

@ -9,7 +9,7 @@ import org.mozilla.gecko.AppConstants.Versions;
import org.mozilla.gecko.R;
import org.mozilla.gecko.util.ThreadUtils;
import org.mozilla.gecko.RestrictedProfiles;
import org.mozilla.gecko.RestrictedProfiles.Restriction;
import org.mozilla.gecko.restrictions.Restriction;
import java.util.Set;

View File

@ -7,6 +7,7 @@ package org.mozilla.gecko.preferences;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
@ -37,6 +38,7 @@ import org.mozilla.gecko.TelemetryContract.Method;
import org.mozilla.gecko.background.common.GlobalConstants;
import org.mozilla.gecko.background.healthreport.HealthReportConstants;
import org.mozilla.gecko.db.BrowserContract.SuggestedSites;
import org.mozilla.gecko.restrictions.Restriction;
import org.mozilla.gecko.updater.UpdateService;
import org.mozilla.gecko.updater.UpdateServiceHelper;
import org.mozilla.gecko.util.GeckoEventListener;
@ -130,6 +132,9 @@ OnSharedPreferenceChangeListener
public static final String PREFS_VOICE_INPUT_ENABLED = NON_PREF_PREFIX + "voice_input_enabled";
public static final String PREFS_QRCODE_ENABLED = NON_PREF_PREFIX + "qrcode_enabled";
private static final String PREFS_DEVTOOLS = NON_PREF_PREFIX + "devtools.enabled";
private static final String PREFS_CUSTOMIZE_HOME = NON_PREF_PREFIX + "customize_home";
private static final String PREFS_TRACKING_PROTECTION_PRIVATE_BROWSING = "privacy.trackingprotection.pbmode.enabled";
private static final String PREFS_TRACKING_PROTECTION_LEARN_MORE = NON_PREF_PREFIX + "trackingprotection.learn_more";
private static final String ACTION_STUMBLER_UPLOAD_PREF = AppConstants.ANDROID_PACKAGE_NAME + ".STUMBLER_PREF";
@ -309,9 +314,6 @@ OnSharedPreferenceChangeListener
@Override
protected void onCreate(Bundle savedInstanceState) {
// Make sure RestrictedProfiles is ready.
RestrictedProfiles.initWithProfile(GeckoProfile.get(this));
// Apply the current user-selected locale, if necessary.
checkLocale();
@ -477,15 +479,18 @@ OnSharedPreferenceChangeListener
if (onIsMultiPane()) {
loadHeadersFromResource(R.xml.preference_headers, target);
// If locale switching is disabled, remove the section
// entirely. This logic will need to be extended when
// content language selection (Bug 881510) is implemented.
if (!localeSwitchingIsEnabled) {
for (Header header : target) {
if (header.id == R.id.pref_header_language) {
target.remove(header);
break;
}
Iterator<Header> iterator = target.iterator();
while (iterator.hasNext()) {
Header header = iterator.next();
if (header.id == R.id.pref_header_language && !localeSwitchingIsEnabled) {
// If locale switching is disabled, remove the section
// entirely. This logic will need to be extended when
// content language selection (Bug 881510) is implemented.
iterator.remove();
} else if (header.id == R.id.pref_header_devtools && !RestrictedProfiles.isAllowed(this, Restriction.DISALLOW_DEVELOPER_TOOLS)) {
iterator.remove();
}
}
}
@ -703,6 +708,14 @@ OnSharedPreferenceChangeListener
continue;
}
if (PREFS_CUSTOMIZE_HOME.equals(key)) {
if (!RestrictedProfiles.isAllowed(this, Restriction.DISALLOW_CUSTOMIZE_HOME)) {
preferences.removePreference(pref);
i--;
continue;
}
}
setupPreferences((PreferenceGroup) pref, prefs);
} else {
pref.setOnPreferenceChangeListener(this);
@ -727,7 +740,7 @@ OnSharedPreferenceChangeListener
}
} else if (PREFS_OPEN_URLS_IN_PRIVATE.equals(key)) {
// Remove UI for opening external links in private browsing on non-Nightly builds.
if (!AppConstants.NIGHTLY_BUILD) {
if (!AppConstants.NIGHTLY_BUILD || !RestrictedProfiles.isAllowed(this, Restriction.DISALLOW_PRIVATE_BROWSING)) {
preferences.removePreference(pref);
i--;
continue;
@ -753,19 +766,19 @@ OnSharedPreferenceChangeListener
}
} else if (PREFS_GEO_REPORTING.equals(key) ||
PREFS_GEO_LEARN_MORE.equals(key)) {
if (!AppConstants.MOZ_STUMBLER_BUILD_TIME_ENABLED) {
if (!AppConstants.MOZ_STUMBLER_BUILD_TIME_ENABLED || !RestrictedProfiles.isAllowed(this, Restriction.DISALLOW_LOCATION_SERVICE)) {
preferences.removePreference(pref);
i--;
continue;
}
} else if (PREFS_DEVTOOLS_REMOTE_USB_ENABLED.equals(key)) {
if (!RestrictedProfiles.isAllowed(this, RestrictedProfiles.Restriction.DISALLOW_REMOTE_DEBUGGING)) {
if (!RestrictedProfiles.isAllowed(this, Restriction.DISALLOW_REMOTE_DEBUGGING)) {
preferences.removePreference(pref);
i--;
continue;
}
} else if (PREFS_DEVTOOLS_REMOTE_WIFI_ENABLED.equals(key)) {
if (!RestrictedProfiles.isAllowed(this, RestrictedProfiles.Restriction.DISALLOW_REMOTE_DEBUGGING)) {
if (!RestrictedProfiles.isAllowed(this, Restriction.DISALLOW_REMOTE_DEBUGGING)) {
preferences.removePreference(pref);
i--;
continue;
@ -788,7 +801,7 @@ OnSharedPreferenceChangeListener
continue;
} else if (PREFS_SYNC.equals(key)) {
// Don't show sync prefs while in guest mode.
if (!RestrictedProfiles.isAllowed(this, RestrictedProfiles.Restriction.DISALLOW_MODIFY_ACCOUNTS)) {
if (!RestrictedProfiles.isAllowed(this, Restriction.DISALLOW_MODIFY_ACCOUNTS)) {
preferences.removePreference(pref);
i--;
continue;
@ -837,6 +850,18 @@ OnSharedPreferenceChangeListener
i--;
continue;
}
} else if (PREFS_TRACKING_PROTECTION_PRIVATE_BROWSING.equals(key)) {
if (!RestrictedProfiles.isAllowed(this, Restriction.DISALLOW_PRIVATE_BROWSING)) {
preferences.removePreference(pref);
i--;
continue;
}
} else if (PREFS_TRACKING_PROTECTION_LEARN_MORE.equals(key)) {
if (!RestrictedProfiles.isAllowed(this, Restriction.DISALLOW_PRIVATE_BROWSING)) {
preferences.removePreference(pref);
i--;
continue;
}
}
// Some Preference UI elements are not actually preferences,

View File

@ -49,17 +49,19 @@
</LinearLayout>
<RelativeLayout android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="@dimen/new_tablet_tab_highlight_stroke_width"
android:background="@drawable/tab_thumbnail"
android:duplicateParentState="true">
<org.mozilla.gecko.widget.TabThumbnailWrapper
android:id="@+id/wrapper"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="@dimen/new_tablet_tab_highlight_stroke_width"
android:background="@drawable/tab_thumbnail"
android:duplicateParentState="true">
<org.mozilla.gecko.widget.ThumbnailView android:id="@+id/thumbnail"
android:layout_width="@dimen/new_tablet_tab_thumbnail_width"
android:layout_height="@dimen/new_tablet_tab_thumbnail_height"
/>
</RelativeLayout>
</org.mozilla.gecko.widget.TabThumbnailWrapper>
</org.mozilla.gecko.tabs.TabsLayoutItemView>

View File

@ -18,6 +18,6 @@
android:background="@color/about_page_header_grey"
android:layout_gravity="top"
gecko:strip="@drawable/home_tab_menu_strip"
gecko:tabContentStart="72dp" />
gecko:tabContentStart="@dimen/tab_strip_content_start" />
</org.mozilla.gecko.home.HomePager>

View File

@ -16,8 +16,7 @@
<org.mozilla.gecko.widget.GeckoSwipeRefreshLayout
android:id="@id/remote_tabs_refresh_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:enabled="false">
android:layout_height="match_parent">
<LinearLayout android:layout_width="match_parent"
android:layout_height="match_parent"

View File

@ -15,12 +15,14 @@
android:paddingRight="1dip"
android:gravity="center">
<RelativeLayout android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="6dip"
android:padding="4dip"
android:background="@drawable/tab_thumbnail"
android:duplicateParentState="true">
<org.mozilla.gecko.widget.TabThumbnailWrapper
android:id="@+id/wrapper"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="6dip"
android:padding="4dip"
android:background="@drawable/tab_thumbnail"
android:duplicateParentState="true">
<org.mozilla.gecko.widget.ThumbnailView android:id="@+id/thumbnail"
android:layout_width="@dimen/tab_thumbnail_width"
@ -55,6 +57,6 @@
</LinearLayout>
</RelativeLayout>
</org.mozilla.gecko.widget.TabThumbnailWrapper>
</org.mozilla.gecko.tabs.TabsLayoutItemView>

View File

@ -31,4 +31,6 @@
<!-- Should be closer to 0.83 (140/168) but various roundings mean that 0.9 works better -->
<item name="thumbnail_aspect_ratio" format="float" type="dimen">0.9</item>
<item name="tab_strip_content_start" type="dimen">72dp</item>
</resources>

View File

@ -215,4 +215,6 @@
<item name="match_parent" type="dimen">-1</item>
<item name="wrap_content" type="dimen">-2</item>
<item name="tab_strip_content_start" type="dimen">12dp</item>
</resources>

View File

@ -41,7 +41,8 @@
</header>
<header android:fragment="org.mozilla.gecko.preferences.GeckoPreferenceFragment"
android:title="@string/pref_header_devtools">
android:title="@string/pref_header_devtools"
android:id="@+id/pref_header_devtools">
<extra android:name="resource"
android:value="preferences_devtools"/>
</header>

View File

@ -9,7 +9,8 @@
xmlns:gecko="http://schemas.android.com/apk/res-auto"
android:enabled="false">
<PreferenceScreen android:title="@string/pref_category_home"
<PreferenceScreen android:key="android.not_a_preference.customize_home"
android:title="@string/pref_category_home"
android:summary="@string/pref_category_home_summary"
android:fragment="org.mozilla.gecko.preferences.GeckoPreferenceFragment" >
<extra android:name="resource"

View File

@ -16,7 +16,8 @@
android:title="@string/pref_sync"
android:persistent="false" />
<PreferenceScreen android:title="@string/pref_category_home"
<PreferenceScreen android:key="android.not_a_preference.customize_home"
android:title="@string/pref_category_home"
android:summary="@string/pref_category_home_summary"
android:fragment="org.mozilla.gecko.preferences.GeckoPreferenceFragment" >
<extra android:name="resource"

View File

@ -10,4 +10,7 @@
<header android:fragment="org.mozilla.gecko.preferences.GeckoPreferenceFragment"
android:id="@+id/pref_header_language">
</header>
<header android:fragment="org.mozilla.gecko.preferences.GeckoPreferenceFragment"
android:id="@+id/pref_header_devtools">
</header>
</preference-headers>

View File

@ -7,7 +7,9 @@
xmlns:gecko="http://schemas.android.com/apk/res-auto"
android:enabled="false">
<PreferenceScreen android:title="@string/pref_category_home"
<PreferenceScreen
android:key="android.not_a_preference.customize_home"
android:title="@string/pref_category_home"
android:summary="@string/pref_category_home_summary" >
<intent android:action="android.intent.action.VIEW"
android:targetPackage="@string/android_package_name"

View File

@ -0,0 +1,27 @@
/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
* 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.restrictions;
/**
* Default implementation of RestrictionConfiguration interface. Used whenever no restrictions are enforced for the
* current profile.
*/
public class DefaultConfiguration implements RestrictionConfiguration {
@Override
public boolean isAllowed(Restriction restriction) {
return true;
}
@Override
public boolean canLoadUrl(String url) {
return true;
}
@Override
public boolean isRestricted() {
return false;
}
}

View File

@ -0,0 +1,75 @@
/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
* 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.restrictions;
import android.net.Uri;
import java.util.Arrays;
import java.util.List;
/**
* RestrictionConfiguration implementation for guest profiles.
*/
public class GuestProfileConfiguration implements RestrictionConfiguration {
static List<Restriction> DEFAULT_RESTRICTIONS = Arrays.asList(
Restriction.DISALLOW_DOWNLOADS,
Restriction.DISALLOW_INSTALL_EXTENSION,
Restriction.DISALLOW_INSTALL_APPS,
Restriction.DISALLOW_BROWSE_FILES,
Restriction.DISALLOW_SHARE,
Restriction.DISALLOW_BOOKMARK,
Restriction.DISALLOW_ADD_CONTACTS,
Restriction.DISALLOW_SET_IMAGE,
Restriction.DISALLOW_MODIFY_ACCOUNTS,
Restriction.DISALLOW_REMOTE_DEBUGGING,
Restriction.DISALLOW_IMPORT_SETTINGS
);
@SuppressWarnings("serial")
private static final List<String> BANNED_SCHEMES = Arrays.asList(
"file",
"chrome",
"resource",
"jar",
"wyciwyg"
);
private static final List<String> BANNED_URLS = Arrays.asList(
"about:config"
);
@Override
public boolean isAllowed(Restriction restriction) {
return !DEFAULT_RESTRICTIONS.contains(restriction);
}
@Override
public boolean canLoadUrl(String url) {
// Null URLs are always permitted.
if (url == null) {
return true;
}
final Uri u = Uri.parse(url);
final String scheme = u.getScheme();
if (BANNED_SCHEMES.contains(scheme)) {
return false;
}
for (String banned : BANNED_URLS) {
if (url.startsWith(banned)) {
return false;
}
}
return true;
}
@Override
public boolean isRestricted() {
return true;
}
}

View File

@ -0,0 +1,81 @@
/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
* 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.restrictions;
import android.annotation.TargetApi;
import android.content.Context;
import android.os.Build;
import android.os.Bundle;
import android.os.UserManager;
import java.util.Arrays;
import java.util.List;
public class RestrictedProfileConfiguration implements RestrictionConfiguration {
static List<Restriction> DEFAULT_RESTRICTIONS = Arrays.asList(
Restriction.DISALLOW_INSTALL_EXTENSION,
Restriction.DISALLOW_INSTALL_APPS,
Restriction.DISALLOW_TOOLS_MENU,
Restriction.DISALLOW_REPORT_SITE_ISSUE,
Restriction.DISALLOW_IMPORT_SETTINGS,
Restriction.DISALLOW_DEVELOPER_TOOLS,
Restriction.DISALLOW_CUSTOMIZE_HOME,
Restriction.DISALLOW_PRIVATE_BROWSING,
Restriction.DISALLOW_LOCATION_SERVICE
);
private static final String ABOUT_ADDONS = "about:addons";
private static final String ABOUT_PRIVATE_BROWSING = "about:privatebrowsing";
private Context context;
public RestrictedProfileConfiguration(Context context) {
this.context = context.getApplicationContext();
}
@Override
public boolean isAllowed(Restriction restriction) {
boolean isAllowed = !getAppRestrictions(context).getBoolean(restriction.name, DEFAULT_RESTRICTIONS.contains(restriction));
if (isAllowed) {
// If this restriction is not enforced by the app setup then check wether this is a restriction that is
// enforced by the system.
isAllowed = !getUserRestrictions(context).getBoolean(restriction.name, false);
}
return isAllowed;
}
@Override
public boolean canLoadUrl(String url) {
if (!isAllowed(Restriction.DISALLOW_INSTALL_EXTENSION) && url.toLowerCase().startsWith(ABOUT_ADDONS)) {
return false;
}
if (!isAllowed(Restriction.DISALLOW_PRIVATE_BROWSING) && url.toLowerCase().startsWith(ABOUT_PRIVATE_BROWSING)) {
return false;
}
return true;
}
@Override
public boolean isRestricted() {
return true;
}
@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2)
private Bundle getAppRestrictions(final Context context) {
final UserManager mgr = (UserManager) context.getSystemService(Context.USER_SERVICE);
return mgr.getApplicationRestrictions(context.getPackageName());
}
@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2)
private Bundle getUserRestrictions(final Context context) {
final UserManager mgr = (UserManager) context.getSystemService(Context.USER_SERVICE);
return mgr.getUserRestrictions();
}
}

View File

@ -0,0 +1,118 @@
/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
* 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.restrictions;
import org.mozilla.gecko.R;
import android.content.Context;
/**
* This is a list of things we can restrict you from doing. Some of these are reflected in Android UserManager constants.
* Others are specific to us.
* These constants should be in sync with the ones from toolkit/components/parentalcontrols/nsIParentalControlsService.idl
*/
public enum Restriction {
DISALLOW_DOWNLOADS(
1, "no_download_files", 0, 0),
DISALLOW_INSTALL_EXTENSION(
2, "no_install_extensions",
R.string.restriction_disallow_addons_title,
R.string.restriction_disallow_addons_description),
// UserManager.DISALLOW_INSTALL_APPS
DISALLOW_INSTALL_APPS(
3, "no_install_apps",
R.string.restriction_disallow_apps_title,
R.string.restriction_disallow_apps_description),
DISALLOW_BROWSE_FILES(
4, "no_browse_files", 0, 0),
DISALLOW_SHARE(
5, "no_share", 0, 0),
DISALLOW_BOOKMARK(
6, "no_bookmark", 0, 0),
DISALLOW_ADD_CONTACTS(
7, "no_add_contacts", 0, 0),
DISALLOW_SET_IMAGE(
8, "no_set_image", 0, 0),
// UserManager.DISALLOW_MODIFY_ACCOUNTS
DISALLOW_MODIFY_ACCOUNTS(
9, "no_modify_accounts", 0, 0),
DISALLOW_REMOTE_DEBUGGING(
10, "no_remote_debugging", 0, 0),
DISALLOW_IMPORT_SETTINGS(
11, "no_report_site_issue",
R.string.restriction_disallow_import_settings_title,
R.string.restriction_disallow_import_settings_description),
DISALLOW_TOOLS_MENU(
12, "no_tools_menu",
R.string.restriction_disallow_tools_menu_title,
R.string.restriction_disallow_tools_menu_description),
DISALLOW_REPORT_SITE_ISSUE(
13, "no_report_site_issue",
R.string.restriction_disallow_report_site_issue_title,
R.string.restriction_disallow_report_site_issue_description),
DISALLOW_DEVELOPER_TOOLS(
14, "no_developer_tools",
R.string.restriction_disallow_devtools_title,
R.string.restriction_disallow_devtools_description
),
DISALLOW_CUSTOMIZE_HOME(
15, "no_customize_home",
R.string.restriction_disallow_customize_home_title,
R.string.restriction_disallow_customize_home_description
),
DISALLOW_PRIVATE_BROWSING(
16, "no_private_browsing",
R.string.restriction_disallow_private_browsing_title,
R.string.restriction_disallow_private_browsing_description
),
DISALLOW_LOCATION_SERVICE(
17, "no_location_service",
R.string.restriction_disallow_location_services_title,
R.string.restriction_disallow_location_services_description
);
public final int id;
public final String name;
public final int titleResource;
public final int descriptionResource;
Restriction(final int id, final String name, int titleResource, int descriptionResource) {
this.id = id;
this.name = name;
this.titleResource = titleResource;
this.descriptionResource = descriptionResource;
}
public String getTitle(Context context) {
if (titleResource == 0) {
return toString();
}
return context.getResources().getString(titleResource);
}
public String getDescription(Context context) {
if (descriptionResource == 0) {
return name;
}
return context.getResources().getString(descriptionResource);
}
}

View File

@ -0,0 +1,26 @@
/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
* 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.restrictions;
/**
* Interface for classes that RestrictedProfiles will delegate to for making decisions.
*/
public interface RestrictionConfiguration {
/**
* Is the user allowed to perform this action?
*/
boolean isAllowed(Restriction restriction);
/**
* Is the user allowed to load the given URL?
*/
boolean canLoadUrl(String url);
/**
* Is this user restricted in any way?
*/
boolean isRestricted();
}

View File

@ -3,7 +3,11 @@
* 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;
package org.mozilla.gecko.restrictions;
import org.mozilla.gecko.AppConstants;
import org.mozilla.gecko.restrictions.RestrictedProfileConfiguration;
import org.mozilla.gecko.restrictions.Restriction;
import android.annotation.TargetApi;
import android.app.Activity;
@ -13,7 +17,6 @@ import android.content.Intent;
import android.content.RestrictionEntry;
import android.os.Build;
import android.os.Bundle;
import android.util.Log;
import java.util.ArrayList;
@ -49,7 +52,7 @@ public class RestrictionProvider extends BroadcastReceiver {
private ArrayList<RestrictionEntry> initRestrictions(Context context, Bundle oldRestrictions) {
ArrayList<RestrictionEntry> entries = new ArrayList<RestrictionEntry>();
for (RestrictedProfiles.Restriction restriction : RestrictedProfiles.RESTRICTED_PROFILE_RESTRICTIONS) {
for (Restriction restriction : RestrictedProfileConfiguration.DEFAULT_RESTRICTIONS) {
RestrictionEntry entry = createRestrictionEntryWithDefaultValue(context, restriction,
oldRestrictions.getBoolean(restriction.name, true));
entries.add(entry);
@ -58,7 +61,7 @@ public class RestrictionProvider extends BroadcastReceiver {
return entries;
}
private RestrictionEntry createRestrictionEntryWithDefaultValue(Context context, RestrictedProfiles.Restriction restriction, boolean defaultValue) {
private RestrictionEntry createRestrictionEntryWithDefaultValue(Context context, Restriction restriction, boolean defaultValue) {
RestrictionEntry entry = new RestrictionEntry(restriction.name, defaultValue);
entry.setTitle(restriction.getTitle(context));

View File

@ -549,6 +549,14 @@
<string name="restriction_disallow_addons_description">&restriction_disallow_addons_description;</string>
<string name="restriction_disallow_apps_title">&restriction_disallow_apps_title;</string>
<string name="restriction_disallow_apps_description">&restriction_disallow_apps_description;</string>
<string name="restriction_disallow_devtools_title">&restriction_disallow_devtools_title;</string>
<string name="restriction_disallow_devtools_description">&restriction_disallow_devtools_description;</string>
<string name="restriction_disallow_customize_home_title">&restriction_disallow_customize_home_title;</string>
<string name="restriction_disallow_customize_home_description">&restriction_disallow_customize_home_description;</string>
<string name="restriction_disallow_private_browsing_title">&restriction_disallow_private_browsing_title;</string>
<string name="restriction_disallow_private_browsing_description">&restriction_disallow_private_browsing_description;</string>
<string name="restriction_disallow_location_services_title">&restriction_disallow_location_services_title;</string>
<string name="restriction_disallow_location_services_description">&restriction_disallow_location_services_description;</string>
<!-- Miscellaneous -->
<string name="ellipsis">&ellipsis;</string>

View File

@ -9,14 +9,16 @@ import org.mozilla.gecko.AppConstants.Versions;
import org.mozilla.gecko.GeckoApp;
import org.mozilla.gecko.GeckoApplication;
import org.mozilla.gecko.R;
import org.mozilla.gecko.RestrictedProfiles;
import org.mozilla.gecko.Tab;
import org.mozilla.gecko.Tabs;
import org.mozilla.gecko.Telemetry;
import org.mozilla.gecko.TelemetryContract;
import org.mozilla.gecko.animation.PropertyAnimator;
import org.mozilla.gecko.Tab;
import org.mozilla.gecko.Tabs;
import org.mozilla.gecko.animation.ViewHelper;
import org.mozilla.gecko.lwt.LightweightTheme;
import org.mozilla.gecko.lwt.LightweightThemeDrawable;
import org.mozilla.gecko.restrictions.Restriction;
import org.mozilla.gecko.util.HardwareUtils;
import org.mozilla.gecko.widget.GeckoPopupMenu;
import org.mozilla.gecko.widget.IconTabWidget;
@ -139,6 +141,10 @@ public class TabsPanel extends LinearLayout
mTabWidget.addTab(R.drawable.tabs_normal, R.string.tabs_normal);
mTabWidget.addTab(R.drawable.tabs_private, R.string.tabs_private);
if (!RestrictedProfiles.isAllowed(mContext, Restriction.DISALLOW_PRIVATE_BROWSING)) {
mTabWidget.setVisibility(View.GONE);
}
mTabWidget.setTabSelectionListener(this);
mMenuButton = (ImageButton) findViewById(R.id.menu);
@ -166,7 +172,8 @@ public class TabsPanel extends LinearLayout
// Each panel has a "+" shortcut button, so don't show it for that panel.
menu.findItem(R.id.new_tab).setVisible(mCurrentPanel != Panel.NORMAL_TABS);
menu.findItem(R.id.new_private_tab).setVisible(mCurrentPanel != Panel.PRIVATE_TABS);
menu.findItem(R.id.new_private_tab).setVisible(mCurrentPanel != Panel.PRIVATE_TABS
&& RestrictedProfiles.isAllowed(mContext, Restriction.DISALLOW_PRIVATE_BROWSING));
// Only show "Clear * tabs" for current panel.
menu.findItem(R.id.close_all_tabs).setVisible(mCurrentPanel == Panel.NORMAL_TABS);

View File

@ -2,11 +2,11 @@ package org.mozilla.gecko.widget;
import android.content.Context;
import android.util.AttributeSet;
import android.widget.FrameLayout;
import android.widget.RelativeLayout;
import org.mozilla.gecko.R;
public class TabThumbnailWrapper extends FrameLayout {
public class TabThumbnailWrapper extends RelativeLayout {
private boolean mRecording;
private static final int[] STATE_RECORDING = { R.attr.state_recording };

View File

@ -7,8 +7,8 @@
package org.mozilla.gecko.widget;
import org.mozilla.gecko.GeckoApplication;
import org.mozilla.gecko.R;
import org.mozilla.gecko.lwt.LightweightTheme;
import org.mozilla.gecko.R;
import android.content.Context;
import android.content.res.TypedArray;

View File

@ -7,8 +7,8 @@
package org.mozilla.gecko.widget;
import org.mozilla.gecko.GeckoApplication;
import org.mozilla.gecko.R;
import org.mozilla.gecko.lwt.LightweightTheme;
import org.mozilla.gecko.R;
import android.content.Context;
import android.content.res.TypedArray;

View File

@ -7,8 +7,8 @@
package org.mozilla.gecko.widget;
import org.mozilla.gecko.GeckoApplication;
import org.mozilla.gecko.R;
import org.mozilla.gecko.lwt.LightweightTheme;
import org.mozilla.gecko.R;
import android.content.Context;
import android.content.res.TypedArray;

View File

@ -7,8 +7,8 @@
package org.mozilla.gecko.widget;
import org.mozilla.gecko.GeckoApplication;
import org.mozilla.gecko.R;
import org.mozilla.gecko.lwt.LightweightTheme;
import org.mozilla.gecko.R;
import android.content.Context;
import android.content.res.TypedArray;

View File

@ -7,8 +7,8 @@
package org.mozilla.gecko.widget;
import org.mozilla.gecko.GeckoApplication;
import org.mozilla.gecko.R;
import org.mozilla.gecko.lwt.LightweightTheme;
import org.mozilla.gecko.R;
import android.content.Context;
import android.content.res.TypedArray;

View File

@ -7,8 +7,8 @@
package org.mozilla.gecko.widget;
import org.mozilla.gecko.GeckoApplication;
import org.mozilla.gecko.R;
import org.mozilla.gecko.lwt.LightweightTheme;
import org.mozilla.gecko.R;
import android.content.Context;
import android.content.res.TypedArray;

View File

@ -7,8 +7,8 @@
package org.mozilla.gecko.widget;
import org.mozilla.gecko.GeckoApplication;
import org.mozilla.gecko.R;
import org.mozilla.gecko.lwt.LightweightTheme;
import org.mozilla.gecko.R;
import android.content.Context;
import android.content.res.TypedArray;

View File

@ -7,8 +7,8 @@
package org.mozilla.gecko.widget;
import org.mozilla.gecko.GeckoApplication;
import org.mozilla.gecko.R;
import org.mozilla.gecko.lwt.LightweightTheme;
import org.mozilla.gecko.R;
import android.content.Context;
import android.content.res.TypedArray;

View File

@ -8,7 +8,7 @@
package org.mozilla.gecko.widget;
import org.mozilla.gecko.GeckoApplication;
import org.mozilla.gecko.LightweightTheme;
import org.mozilla.gecko.lwt.LightweightTheme;
import org.mozilla.gecko.R;
import android.content.Context;

View File

@ -38,6 +38,11 @@ function copyStringAndToast(string, notifyString) {
// Delay filtering while typing in MS
const FILTER_DELAY = 500;
/* Constants for usage telemetry */
const LOGINS_LIST_VIEWED = 0;
const LOGIN_VIEWED = 1;
const LOGIN_EDITED = 2;
const LOGIN_PW_TOGGLED = 3;
let Logins = {
_logins: [],
@ -162,6 +167,7 @@ let Logins = {
},
_showList: function () {
Services.telemetry.getHistogramById("PWMGR_ABOUT_LOGINS_USAGE").add(LOGINS_LIST_VIEWED);
let loginsListPage = document.getElementById("logins-list-page");
loginsListPage.classList.remove("hidden");
@ -184,6 +190,7 @@ let Logins = {
}
},
_showEditLoginDialog: function (login) {
Services.telemetry.getHistogramById("PWMGR_ABOUT_LOGINS_USAGE").add(LOGIN_VIEWED);
let listPage = document.getElementById("logins-list-page");
listPage.classList.add("hidden");
@ -210,6 +217,7 @@ let Logins = {
},
_onSaveEditLogin: function() {
Services.telemetry.getHistogramById("PWMGR_ABOUT_LOGINS_USAGE").add(LOGIN_EDITED);
let newUsername = document.getElementById("username").value;
let newPassword = document.getElementById("password").value;
let newDomain = document.getElementById("hostname").value;
@ -248,6 +256,7 @@ let Logins = {
},
_onPasswordBtn: function () {
Services.telemetry.getHistogramById("PWMGR_ABOUT_LOGINS_USAGE").add(LOGIN_PW_TOGGLED);
this._updatePasswordBtn(this._isPasswordBtnInHideMode());
},

View File

@ -690,27 +690,35 @@ var BrowserApp = {
});
});
NativeWindow.contextmenus.add(stringGetter("contextmenu.openInPrivateTab"),
NativeWindow.contextmenus.linkOpenableContext,
function(aTarget) {
UITelemetry.addEvent("action.1", "contextmenu", null, "web_open_private_tab");
UITelemetry.addEvent("loadurl.1", "contextmenu", null);
let showOpenInPrivateTab = true;
if ("@mozilla.org/parental-controls-service;1" in Cc) {
let pc = Cc["@mozilla.org/parental-controls-service;1"].createInstance(Ci.nsIParentalControlsService);
showOpenInPrivateTab = pc.isAllowed(Ci.nsIParentalControlsService.PRIVATE_BROWSING);
}
let url = NativeWindow.contextmenus._getLinkURL(aTarget);
ContentAreaUtils.urlSecurityCheck(url, aTarget.ownerDocument.nodePrincipal);
let tab = BrowserApp.addTab(url, { selected: false, parentId: BrowserApp.selectedTab.id, isPrivate: true });
if (showOpenInPrivateTab) {
NativeWindow.contextmenus.add(stringGetter("contextmenu.openInPrivateTab"),
NativeWindow.contextmenus.linkOpenableContext,
function (aTarget) {
UITelemetry.addEvent("action.1", "contextmenu", null, "web_open_private_tab");
UITelemetry.addEvent("loadurl.1", "contextmenu", null);
let newtabStrings = Strings.browser.GetStringFromName("newprivatetabpopup.opened");
let label = PluralForm.get(1, newtabStrings).replace("#1", 1);
let buttonLabel = Strings.browser.GetStringFromName("newtabpopup.switch");
NativeWindow.toast.show(label, "long", {
button: {
icon: "drawable://switch_button_icon",
label: buttonLabel,
callback: () => { BrowserApp.selectTab(tab); },
}
let url = NativeWindow.contextmenus._getLinkURL(aTarget);
ContentAreaUtils.urlSecurityCheck(url, aTarget.ownerDocument.nodePrincipal);
let tab = BrowserApp.addTab(url, {selected: false, parentId: BrowserApp.selectedTab.id, isPrivate: true});
let newtabStrings = Strings.browser.GetStringFromName("newprivatetabpopup.opened");
let label = PluralForm.get(1, newtabStrings).replace("#1", 1);
let buttonLabel = Strings.browser.GetStringFromName("newtabpopup.switch");
NativeWindow.toast.show(label, "long", {
button: {
icon: "drawable://switch_button_icon",
label: buttonLabel,
callback: () => { BrowserApp.selectTab(tab); },
}
});
});
});
}
NativeWindow.contextmenus.add(stringGetter("contextmenu.addToReadingList"),
NativeWindow.contextmenus.linkOpenableContext,

View File

@ -29,27 +29,4 @@ add_task(function test_isUserRestricted() {
do_check_true(pc.isAllowed(Ci.nsIParentalControlsService.MODIFY_ACCOUNTS));
});
add_task(function test_getUserRestrictions() {
// In an admin profile, like the tests: {}
// In a restricted profile: {"no_modify_accounts":true,"no_share_location":true}
let restrictions = "{}";
var jenv = null;
try {
jenv = JNI.GetForThread();
var profile = JNI.LoadClass(jenv, "org.mozilla.gecko.RestrictedProfiles", {
static_methods: [
{ name: "getUserRestrictions", sig: "()Ljava/lang/String;" },
],
});
restrictions = JNI.ReadString(jenv, profile.getUserRestrictions());
} finally {
if (jenv) {
JNI.UnloadClasses(jenv);
}
}
do_check_eq(restrictions, "{}");
});
run_next_test();

View File

@ -213,6 +213,7 @@ body {
.empty-text {
color: #363B40;
font-size: 25px;
font-weight: lighter;
margin-bottom: 20px;
}

View File

@ -11,7 +11,7 @@ interface nsIFile;
interface nsIInterfaceRequestor;
interface nsIArray;
[scriptable, uuid(406808a5-2838-4c29-9e39-5bfb885c89bf)]
[scriptable, uuid(4ee714a7-e9a8-43ed-a061-60155b63e290)]
interface nsIParentalControlsService : nsISupports
{
/**
@ -30,6 +30,8 @@ interface nsIParentalControlsService : nsISupports
const short IMPORT_SETTINGS = 11; // Importing settings from other apps
const short TOOLS_MENU = 12; // Hide tools menu entry
const short REPORT_SITE_ISSUE = 13; // Hide "Report Site Issue" menu entry
const short PRIVATE_BROWSING = 16; // Disallow usage of private browsing
const short LOCATION_SERVICE = 17; // Sharing of location data to location service
/**
* @returns true if the current user account has parental controls

View File

@ -3387,7 +3387,7 @@
"description": "Number of thumbnails stored in the browser DB *** No longer needed (bug 1156565). Delete histogram and accumulation code! ***"
},
"FENNEC_READING_LIST_COUNT": {
"expires_in_version": "40",
"expires_in_version": "50",
"kind": "exponential",
"high": "1000",
"n_buckets": 10,
@ -8231,6 +8231,12 @@
"extended_statistics_ok": true,
"description": "How long getAllLogins() on about:logins takes for mobile users"
},
"PWMGR_ABOUT_LOGINS_USAGE": {
"expires_in_version": "55",
"kind": "enumerated",
"n_values": 12,
"description": "Usage of about:logins 0= list of logins viewed, 1=a login's specifics page was viewed, 2=user edited login credentials 3=user toggled the show/hide button"
},
"PWMGR_BLOCKLIST_NUM_SITES": {
"expires_in_version": "never",
"kind": "exponential",

View File

@ -765,14 +765,6 @@ void GeckoJavaSampler::UnpauseJavaProfiling()
constexpr char RestrictedProfiles::name[];
constexpr char RestrictedProfiles::GetUserRestrictions_t::name[];
constexpr char RestrictedProfiles::GetUserRestrictions_t::signature[];
mozilla::jni::String::LocalRef RestrictedProfiles::GetUserRestrictions()
{
return mozilla::jni::Method<GetUserRestrictions_t>::Call(nullptr, nullptr);
}
constexpr char RestrictedProfiles::IsAllowed_t::name[];
constexpr char RestrictedProfiles::IsAllowed_t::signature[];

View File

@ -1818,23 +1818,6 @@ public:
protected:
RestrictedProfiles(jobject instance) : Class(instance) {}
public:
struct GetUserRestrictions_t {
typedef RestrictedProfiles Owner;
typedef mozilla::jni::String::LocalRef ReturnType;
typedef mozilla::jni::String::Param SetterType;
typedef mozilla::jni::Args<> Args;
static constexpr char name[] = "getUserRestrictions";
static constexpr char signature[] =
"()Ljava/lang/String;";
static const bool isStatic = true;
static const bool isMultithreaded = false;
static const mozilla::jni::ExceptionMode exceptionMode =
mozilla::jni::ExceptionMode::ABORT;
};
static mozilla::jni::String::LocalRef GetUserRestrictions();
public:
struct IsAllowed_t {
typedef RestrictedProfiles Owner;