/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*- * vim: ts=4 sw=4 expandtab: * 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.geckoview; import android.os.Parcel; import android.os.Parcelable; import androidx.annotation.AnyThread; import androidx.annotation.IntDef; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.Arrays; import java.util.Collection; import org.mozilla.gecko.util.GeckoBundle; @AnyThread public final class GeckoSessionSettings implements Parcelable { /** Settings builder used to construct the settings object. */ @AnyThread public static final class Builder { private final GeckoSessionSettings mSettings; @SuppressWarnings("checkstyle:javadocmethod") public Builder() { mSettings = new GeckoSessionSettings(); } @SuppressWarnings("checkstyle:javadocmethod") public Builder(final GeckoSessionSettings settings) { mSettings = new GeckoSessionSettings(settings); } /** * Finalize and return the settings. * * @return The constructed settings. */ public @NonNull GeckoSessionSettings build() { return new GeckoSessionSettings(mSettings); } /** * Set the chrome URI. * * @param uri The URI to set the Chrome URI to. * @return This Builder instance. */ public @NonNull Builder chromeUri(final @NonNull String uri) { mSettings.setChromeUri(uri); return this; } /** * Set the screen id. * * @param id The screen id. * @return This Builder instance. */ public @NonNull Builder screenId(final int id) { mSettings.setScreenId(id); return this; } /** * Set the privacy mode for this instance. * * @param flag A flag determining whether Private Mode should be enabled. Default is false. * @return This Builder instance. */ public @NonNull Builder usePrivateMode(final boolean flag) { mSettings.setUsePrivateMode(flag); return this; } /** * Set the session context ID for this instance. Setting a context ID partitions the cookie jars * based on the provided IDs. This isolates the browser storage like cookies and localStorage * between sessions, only sessions that share the same ID share storage data. * *

Warning: Storage data is collected persistently for each context, to delete context data, * call {@link StorageController#clearDataForSessionContext} for the given context. * * @param value The custom context ID. The default ID is null, which removes isolation for this * instance. * @return This Builder instance. */ public @NonNull Builder contextId(final @Nullable String value) { mSettings.setContextId(value); return this; } /** * Set whether tracking protection should be enabled. * * @param flag A flag determining whether tracking protection should be enabled. Default is * false. * @return This Builder instance. */ public @NonNull Builder useTrackingProtection(final boolean flag) { mSettings.setUseTrackingProtection(flag); return this; } /** * Set the user agent mode. * * @param mode The mode to set the user agent to. Use one or more of the {@link * GeckoSessionSettings#USER_AGENT_MODE_MOBILE GeckoSessionSettings.USER_AGENT_MODE_*} * flags. * @return This Builder instance. */ public @NonNull Builder userAgentMode(@UserAgentMode final int mode) { mSettings.setUserAgentMode(mode); return this; } /** * Override the user agent. * * @param agent The user agent to use. * @return This Builder instance. */ public @NonNull Builder userAgentOverride(final @NonNull String agent) { mSettings.setUserAgentOverride(agent); return this; } /** * Specify which display-mode to use. * * @param mode The mode to set the display to. Use one or more of the {@link * GeckoSessionSettings#DISPLAY_MODE_BROWSER GeckoSessionSettings.DISPLAY_MODE_*} flags. * @return This Builder instance. */ public @NonNull Builder displayMode(@DisplayMode final int mode) { mSettings.setDisplayMode(mode); return this; } /** * Set whether to suspend the playing of media when the session is inactive. * * @param flag A flag determining whether media should be suspended. Default is false. * @return This Builder instance. */ public @NonNull Builder suspendMediaWhenInactive(final boolean flag) { mSettings.setSuspendMediaWhenInactive(flag); return this; } /** * Set whether JavaScript support should be enabled. * * @param flag A flag determining whether JavaScript should be enabled. Default is true. * @return This Builder instance. */ public @NonNull Builder allowJavascript(final boolean flag) { mSettings.setAllowJavascript(flag); return this; } /** * Set whether the entire accessible tree should be exposed with no caching. * * @param flag A flag determining if the entire accessible tree should be exposed. Default is * false. * @return This Builder instance. */ public @NonNull Builder fullAccessibilityTree(final boolean flag) { mSettings.setFullAccessibilityTree(flag); return this; } /** * Specify which viewport mode to use. * * @param mode The mode to set the viewport to. Use one or more of the {@link * GeckoSessionSettings#VIEWPORT_MODE_MOBILE GeckoSessionSettings.VIEWPORT_MODE_*} flags. * @return This Builder instance. */ public @NonNull Builder viewportMode(@ViewportMode final int mode) { mSettings.setViewportMode(mode); return this; } } private static final String LOGTAG = "GeckoSessionSettings"; private static final boolean DEBUG = false; /** This value is for the display member of Web App Manifests */ @Retention(RetentionPolicy.SOURCE) @IntDef({ DISPLAY_MODE_BROWSER, DISPLAY_MODE_MINIMAL_UI, DISPLAY_MODE_STANDALONE, DISPLAY_MODE_FULLSCREEN }) public @interface DisplayMode {} // This needs to match GeckoViewSettings.jsm /** "browser" value of the display member in Web App Manifests */ public static final int DISPLAY_MODE_BROWSER = 0; /** "minimal-ui" value of the display member in Web App Manifests */ public static final int DISPLAY_MODE_MINIMAL_UI = 1; /** "standalone" value of the display member in Web App Manifests */ public static final int DISPLAY_MODE_STANDALONE = 2; /** "fullscreen" value of the display member in Web App Manifests */ public static final int DISPLAY_MODE_FULLSCREEN = 3; /** The user agent string mode */ @Retention(RetentionPolicy.SOURCE) @IntDef({ USER_AGENT_MODE_MOBILE, USER_AGENT_MODE_DESKTOP, USER_AGENT_MODE_VR, }) public @interface UserAgentMode {} // This needs to match GeckoViewSettingsChild.js and GeckoViewSettings.jsm /** The user agent mode is mobile device */ public static final int USER_AGENT_MODE_MOBILE = 0; /** The user agent mobe is desktop device */ public static final int USER_AGENT_MODE_DESKTOP = 1; /** The user agent mode is VR device */ public static final int USER_AGENT_MODE_VR = 2; /** The view port mode */ @Retention(RetentionPolicy.SOURCE) @IntDef({VIEWPORT_MODE_MOBILE, VIEWPORT_MODE_DESKTOP}) public @interface ViewportMode {} // This needs to match GeckoViewSettingsChild.js /** * Mobile-friendly pages will be rendered using a viewport based on their <meta> viewport * tag. All other pages will be rendered using a special desktop mode viewport, which has a width * of 980 CSS px. */ public static final int VIEWPORT_MODE_MOBILE = 0; /** * All pages will be rendered using the special desktop mode viewport, which has a width of 980 * CSS px, regardless of whether the page has a <meta> viewport tag specified or not. */ public static final int VIEWPORT_MODE_DESKTOP = 1; public static class Key { /* package */ final String name; /* package */ final boolean initOnly; /* package */ final Collection values; /* package */ Key(final String name) { this(name, /* initOnly */ false, /* values */ null); } /* package */ Key(final String name, final boolean initOnly, final Collection values) { this.name = name; this.initOnly = initOnly; this.values = values; } } /** * Key to set the chrome window URI, or null to use default URI. Read-only once session is open. */ private static final Key CHROME_URI = new Key("chromeUri", /* initOnly */ true, /* values */ null); /** Key to set the window screen ID, or 0 to use default ID. Read-only once session is open. */ private static final Key SCREEN_ID = new Key("screenId", /* initOnly */ true, /* values */ null); /** Key to enable and disable tracking protection. */ private static final Key USE_TRACKING_PROTECTION = new Key("useTrackingProtection"); /** Key to enable and disable private mode browsing. Read-only once session is open. */ private static final Key USE_PRIVATE_MODE = new Key("usePrivateMode", /* initOnly */ true, /* values */ null); /** Key to specify which user agent mode we should use. */ private static final Key USER_AGENT_MODE = new Key( "userAgentMode", /* initOnly */ false, Arrays.asList(USER_AGENT_MODE_MOBILE, USER_AGENT_MODE_DESKTOP, USER_AGENT_MODE_VR)); /** * Key to specify the user agent override string. Set value to null to use the user agent * specified by USER_AGENT_MODE. */ private static final Key USER_AGENT_OVERRIDE = new Key("userAgentOverride", /* initOnly */ false, /* values */ null); /** Key to specify which viewport mode we should use. */ private static final Key VIEWPORT_MODE = new Key( "viewportMode", /* initOnly */ false, Arrays.asList(VIEWPORT_MODE_MOBILE, VIEWPORT_MODE_DESKTOP)); /** Key to specify which display-mode we should use. */ private static final Key DISPLAY_MODE = new Key( "displayMode", /* initOnly */ false, Arrays.asList( DISPLAY_MODE_BROWSER, DISPLAY_MODE_MINIMAL_UI, DISPLAY_MODE_STANDALONE, DISPLAY_MODE_FULLSCREEN)); /** Key to specify if media should be suspended when the session is inactive. */ private static final Key SUSPEND_MEDIA_WHEN_INACTIVE = new Key("suspendMediaWhenInactive", /* initOnly */ false, /* values */ null); /** Key to specify if JavaScript should be allowed on this session. */ private static final Key ALLOW_JAVASCRIPT = new Key("allowJavascript", /* initOnly */ false, /* values */ null); /** Key to specify if entire accessible tree should be exposed with no caching. */ private static final Key FULL_ACCESSIBILITY_TREE = new Key("fullAccessibilityTree", /* initOnly */ false, /* values */ null); /** * Key to specify if this GeckoSession is a Popup or not. Popup sessions can paint over other * sessions and are not exposed to the tabs WebExtension API. */ private static final Key IS_POPUP = new Key("isPopup", /* initOnly */ false, /* values */ null); /** Internal Gecko key to specify the session context ID. Derived from `UNSAFE_CONTEXT_ID`. */ private static final Key CONTEXT_ID = new Key("sessionContextId", /* initOnly */ true, /* values */ null); /** User-provided key to specify the session context ID. */ private static final Key UNSAFE_CONTEXT_ID = new Key("unsafeSessionContextId", /* initOnly */ true, /* values */ null); private final GeckoSession mSession; private final GeckoBundle mBundle; @SuppressWarnings("checkstyle:javadocmethod") public GeckoSessionSettings() { this(null, null); } @SuppressWarnings("checkstyle:javadocmethod") public GeckoSessionSettings(final @NonNull GeckoSessionSettings settings) { this(settings, null); } /* package */ GeckoSessionSettings( final @Nullable GeckoSessionSettings settings, final @Nullable GeckoSession session) { mSession = session; if (settings != null) { mBundle = new GeckoBundle(settings.mBundle); return; } mBundle = new GeckoBundle(); mBundle.putString(CHROME_URI.name, null); mBundle.putInt(SCREEN_ID.name, 0); mBundle.putBoolean(USE_TRACKING_PROTECTION.name, false); mBundle.putBoolean(USE_PRIVATE_MODE.name, false); mBundle.putBoolean(SUSPEND_MEDIA_WHEN_INACTIVE.name, false); mBundle.putBoolean(ALLOW_JAVASCRIPT.name, true); mBundle.putBoolean(FULL_ACCESSIBILITY_TREE.name, false); mBundle.putBoolean(IS_POPUP.name, false); mBundle.putInt(USER_AGENT_MODE.name, USER_AGENT_MODE_MOBILE); mBundle.putString(USER_AGENT_OVERRIDE.name, null); mBundle.putInt(VIEWPORT_MODE.name, VIEWPORT_MODE_MOBILE); mBundle.putInt(DISPLAY_MODE.name, DISPLAY_MODE_BROWSER); mBundle.putString(CONTEXT_ID.name, null); mBundle.putString(UNSAFE_CONTEXT_ID.name, null); } /** * Set whether tracking protection should be enabled. * * @param value A flag determining whether tracking protection should be enabled. Default is * false. */ public void setUseTrackingProtection(final boolean value) { setBoolean(USE_TRACKING_PROTECTION, value); } /** * Set the privacy mode for this instance. * * @param value A flag determining whether Private Mode should be enabled. Default is false. */ private void setUsePrivateMode(final boolean value) { setBoolean(USE_PRIVATE_MODE, value); } /** * Set whether to suspend the playing of media when the session is inactive. * * @param value A flag determining whether media should be suspended. Default is false. */ public void setSuspendMediaWhenInactive(final boolean value) { setBoolean(SUSPEND_MEDIA_WHEN_INACTIVE, value); } /** * Set whether JavaScript support should be enabled. * * @param value A flag determining whether JavaScript should be enabled. Default is true. */ public void setAllowJavascript(final boolean value) { setBoolean(ALLOW_JAVASCRIPT, value); } /** * Set whether the entire accessible tree should be exposed with no caching. * * @param value A flag determining full accessibility tree should be exposed. Default is false. */ public void setFullAccessibilityTree(final boolean value) { setBoolean(FULL_ACCESSIBILITY_TREE, value); } /* package */ void setIsPopup(final boolean value) { setBoolean(IS_POPUP, value); } private void setBoolean(final Key key, final boolean value) { synchronized (mBundle) { if (valueChangedLocked(key, value)) { mBundle.putBoolean(key.name, value); dispatchUpdate(); } } } /** * Whether tracking protection is enabled. * * @return true if tracking protection is enabled, false if not. */ public boolean getUseTrackingProtection() { return getBoolean(USE_TRACKING_PROTECTION); } /** * Whether private mode is enabled. * * @return true if private mode is enabled, false if not. */ public boolean getUsePrivateMode() { return getBoolean(USE_PRIVATE_MODE); } /** * The context ID for this session. * * @return The context ID for this session. */ public @Nullable String getContextId() { // Return the user-provided unsafe string. return getString(UNSAFE_CONTEXT_ID); } /** * Whether media will be suspended when the session is inactice. * * @return true if media will be suspended, false if not. */ public boolean getSuspendMediaWhenInactive() { return getBoolean(SUSPEND_MEDIA_WHEN_INACTIVE); } /** * Whether javascript execution is allowed. * * @return true if javascript execution is allowed, false if not. */ public boolean getAllowJavascript() { return getBoolean(ALLOW_JAVASCRIPT); } /** * Whether entire accessible tree is exposed with no caching. * * @return true if accessibility tree is exposed, false if not. */ public boolean getFullAccessibilityTree() { return getBoolean(FULL_ACCESSIBILITY_TREE); } /* package */ boolean getIsPopup() { return getBoolean(IS_POPUP); } private boolean getBoolean(final Key key) { synchronized (mBundle) { return mBundle.getBoolean(key.name); } } /** * Set the screen id. * * @param value The screen id. */ private void setScreenId(final int value) { setInt(SCREEN_ID, value); } /** * Specify which user agent mode we should use * * @param value One or more of the {@link GeckoSessionSettings#USER_AGENT_MODE_MOBILE * GeckoSessionSettings.USER_AGENT_MODE_*} flags. */ public void setUserAgentMode(@UserAgentMode final int value) { setInt(USER_AGENT_MODE, value); } /** * Set the display mode. * * @param value The mode to set the display to. Use one or more of the {@link * GeckoSessionSettings#DISPLAY_MODE_BROWSER GeckoSessionSettings.DISPLAY_MODE_*} flags. */ public void setDisplayMode(@DisplayMode final int value) { setInt(DISPLAY_MODE, value); } /** * Specify which viewport mode we should use * * @param value One or more of the {@link GeckoSessionSettings#VIEWPORT_MODE_MOBILE * GeckoSessionSettings.VIEWPORT_MODE_*} flags. */ public void setViewportMode(@ViewportMode final int value) { setInt(VIEWPORT_MODE, value); } private void setInt(final Key key, final int value) { synchronized (mBundle) { if (valueChangedLocked(key, value)) { mBundle.putInt(key.name, value); dispatchUpdate(); } } } /** * Set the window screen ID. Read-only once session is open. Use the {@link Builder} to set on * session open. * * @return Key to set the window screen ID. 0 is the default ID. */ public int getScreenId() { return getInt(SCREEN_ID); } /** * The current user agent Mode * * @return One or more of the {@link GeckoSessionSettings#USER_AGENT_MODE_MOBILE * GeckoSessionSettings.USER_AGENT_MODE_*} flags. */ public @UserAgentMode int getUserAgentMode() { return getInt(USER_AGENT_MODE); } /** * The current display mode. * * @return One or more of the {@link GeckoSessionSettings#DISPLAY_MODE_BROWSER * GeckoSessionSettings.DISPLAY_MODE_*} flags. */ public @DisplayMode int getDisplayMode() { return getInt(DISPLAY_MODE); } /** * The current viewport Mode * * @return One or more of the {@link GeckoSessionSettings#VIEWPORT_MODE * GeckoSessionSettings.VIEWPORT_MODE_*} flags. */ public @ViewportMode int getViewportMode() { return getInt(VIEWPORT_MODE); } private int getInt(final Key key) { synchronized (mBundle) { return mBundle.getInt(key.name); } } /** * Set the chrome URI. * * @param value The URI to set the Chrome URI to. */ private void setChromeUri(final @NonNull String value) { setString(CHROME_URI, value); } /** * Specify the user agent override string. Set value to null to use the user agent specified by * USER_AGENT_MODE. * * @param value The string to override the user agent with. */ public void setUserAgentOverride(final @Nullable String value) { setString(USER_AGENT_OVERRIDE, value); } private void setContextId(final @Nullable String value) { setString(UNSAFE_CONTEXT_ID, value); setString(CONTEXT_ID, StorageController.createSafeSessionContextId(value)); } private void setString(final Key key, final String value) { synchronized (mBundle) { if (valueChangedLocked(key, value)) { mBundle.putString(key.name, value); dispatchUpdate(); } } } /** * Set the chrome window URI. Read-only once session is open. Use the {@link Builder} to set on * session open. * * @return Key to set the chrome window URI, or null to use default URI. */ public @Nullable String getChromeUri() { return getString(CHROME_URI); } /** * The user agent override string. * * @return The current user agent string or null if the agent is specified by {@link * GeckoSessionSettings#USER_AGENT_MODE} */ public @Nullable String getUserAgentOverride() { return getString(USER_AGENT_OVERRIDE); } private String getString(final Key key) { synchronized (mBundle) { return mBundle.getString(key.name); } } /* package */ @NonNull GeckoBundle toBundle() { return new GeckoBundle(mBundle); } @Override public String toString() { return mBundle.toString(); } @Override public boolean equals(final Object other) { return other instanceof GeckoSessionSettings && mBundle.equals(((GeckoSessionSettings) other).mBundle); } @Override public int hashCode() { return mBundle.hashCode(); } private boolean valueChangedLocked(final Key key, final T value) { if (key.initOnly && mSession != null) { throw new IllegalStateException("Read-only property"); } else if (key.values != null && !key.values.contains(value)) { throw new IllegalArgumentException("Invalid value"); } final Object old = mBundle.get(key.name); return (old != value) && (old == null || !old.equals(value)); } private void dispatchUpdate() { if (mSession != null) { mSession.getEventDispatcher().dispatch("GeckoView:UpdateSettings", toBundle()); } } @Override // Parcelable public int describeContents() { return 0; } @Override // Parcelable public void writeToParcel(final @NonNull Parcel out, final int flags) { mBundle.writeToParcel(out, flags); } // AIDL code may call readFromParcel even though it's not part of Parcelable. @SuppressWarnings("checkstyle:javadocmethod") public void readFromParcel(final @NonNull Parcel source) { mBundle.readFromParcel(source); } public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { @Override public GeckoSessionSettings createFromParcel(final Parcel in) { final GeckoSessionSettings settings = new GeckoSessionSettings(); settings.readFromParcel(in); return settings; } @Override public GeckoSessionSettings[] newArray(final int size) { return new GeckoSessionSettings[size]; } }; }