Repository navigation
Runtime Custom CSS JavaScript and cookie persistence #19
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
a445b31
74b3caa
654334c
25c9c45
4e286a3
f517926
5fd660e
eb6cca4
08cd42e
d52b5d5
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -25,6 +25,7 @@ | |
| import de.starlightunit.wrapper.launch.QuantumIntroController; | ||
| import de.starlightunit.wrapper.media.QuantumNativeMediaPlayer; | ||
| import de.starlightunit.wrapper.navigation.NavigationPolicy; | ||
| import de.starlightunit.wrapper.session.CookiePersistencePolicy; | ||
| import de.starlightunit.wrapper.session.QuantumSessionCookieStore; | ||
| import de.starlightunit.wrapper.ui.LoadingIndicatorController; | ||
| import de.starlightunit.wrapper.web.GameWebChromeClient; | ||
|
|
@@ -84,8 +85,13 @@ protected void onCreate(Bundle savedInstanceState) { | |
| introController.start(savedInstanceState != null); | ||
|
|
||
| WebViewConfigurator.configure(this, webView); | ||
| sessionCookieStore = new QuantumSessionCookieStore(this); | ||
| sessionCookieStore.restore(); | ||
| if (CookiePersistencePolicy.usesEncryptedPersistence(AppConfig.COOKIE_PERSISTENCE_MODE)) { | ||
| sessionCookieStore = new QuantumSessionCookieStore(this); | ||
| sessionCookieStore.restore(); | ||
| } else if (savedInstanceState == null | ||
| && CookiePersistencePolicy.startsFreshSession(AppConfig.COOKIE_PERSISTENCE_MODE)) { | ||
| CookiePersistencePolicy.clearForFreshSession(); | ||
|
Comment on lines
+88
to
+93
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When an installed app changes from the legacy/persistent mode to Useful? React with 👍 / 👎. |
||
| } | ||
|
|
||
| navigationPolicy = new NavigationPolicy(AppConfig.TRUSTED_DOMAIN); | ||
| requestHeaders = WrapperRequestHeaders.create(); | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,47 @@ | ||
| package de.starlightunit.wrapper.session; | ||
|
|
||
| import android.webkit.CookieManager; | ||
|
|
||
| import java.util.Locale; | ||
|
|
||
| public final class CookiePersistencePolicy { | ||
| public static final String MODE_PERSISTENT = "persistent"; | ||
| public static final String MODE_SERVER = "server"; | ||
| public static final String MODE_SESSION = "session"; | ||
|
|
||
| private CookiePersistencePolicy() { | ||
| } | ||
|
|
||
| public static String normalize(String configuredMode) { | ||
| String mode = configuredMode == null | ||
| ? "" | ||
| : configuredMode.trim().toLowerCase(Locale.ROOT); | ||
| switch (mode) { | ||
| case MODE_SERVER: | ||
| case MODE_SESSION: | ||
| case MODE_PERSISTENT: | ||
| return mode; | ||
| case "default": | ||
| case "": | ||
| default: | ||
| // Legacy Quantum builds always persisted trusted-origin session | ||
| // cookies. Keep that behavior unless a profile explicitly opts | ||
| // into server defaults or session-only mode. | ||
| return MODE_PERSISTENT; | ||
| } | ||
| } | ||
|
|
||
| public static boolean usesEncryptedPersistence(String configuredMode) { | ||
| return MODE_PERSISTENT.equals(normalize(configuredMode)); | ||
| } | ||
|
|
||
| public static boolean startsFreshSession(String configuredMode) { | ||
| return MODE_SESSION.equals(normalize(configuredMode)); | ||
| } | ||
|
|
||
| public static void clearForFreshSession() { | ||
| CookieManager cookies = CookieManager.getInstance(); | ||
| cookies.removeAllCookies(null); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When Useful? React with 👍 / 👎. |
||
| cookies.flush(); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -79,6 +79,7 @@ public void onPageStarted(WebView view, String url, Bitmap favicon) { | |
| @Override | ||
| public void onPageFinished(WebView view, String url) { | ||
| if (navigationPolicy.isTrustedHttps(url)) { | ||
| WebOverrides.apply(view); | ||
|
Comment on lines
81
to
+82
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When a trusted page rapidly navigates to an in-WebView Useful? React with 👍 / 👎. |
||
| campaignAudioHandoff.inject(view); | ||
| if (assetStore != null && AppConfig.ASSET_STORE_PAGE_WARMUP_ENABLED) { | ||
| view.postDelayed( | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,46 @@ | ||
| package de.starlightunit.wrapper.web; | ||
|
|
||
| import android.webkit.WebView; | ||
|
|
||
| import org.json.JSONObject; | ||
|
|
||
| import de.starlightunit.wrapper.config.AppConfig; | ||
|
|
||
| /** | ||
| * Applies profile-provided CSS and JavaScript only after the caller has | ||
| * confirmed that the current page belongs to the trusted app origin. | ||
| */ | ||
| public final class WebOverrides { | ||
| private static final String STYLE_ELEMENT_ID = "quantum-profile-custom-css"; | ||
|
|
||
| private WebOverrides() { | ||
| } | ||
|
|
||
| public static void apply(WebView webView) { | ||
| String css = AppConfig.CUSTOM_CSS; | ||
| if (css != null && !css.trim().isEmpty()) { | ||
| webView.evaluateJavascript(cssInjectionScript(css), null); | ||
| } | ||
|
|
||
| String javascript = AppConfig.CUSTOM_JAVASCRIPT; | ||
| if (javascript != null && !javascript.trim().isEmpty()) { | ||
| webView.evaluateJavascript(javascriptInjectionScript(javascript), null); | ||
| } | ||
| } | ||
|
|
||
| static String cssInjectionScript(String css) { | ||
| String quotedCss = JSONObject.quote(css == null ? "" : css); | ||
| return "(function(){" | ||
| + "var d=document;" | ||
| + "var s=d.getElementById('" + STYLE_ELEMENT_ID + "');" | ||
| + "if(!s){s=d.createElement('style');s.id='" + STYLE_ELEMENT_ID + "';" | ||
| + "(d.head||d.documentElement).appendChild(s);}" | ||
| + "s.textContent=" + quotedCss + ";" | ||
| + "})();"; | ||
| } | ||
|
|
||
| static String javascriptInjectionScript(String javascript) { | ||
| String source = javascript == null ? "" : javascript; | ||
| return source + "\n//# sourceURL=quantum-profile-custom.js"; | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,29 @@ | ||
| package de.starlightunit.wrapper.session; | ||
|
|
||
| import org.junit.Test; | ||
|
|
||
| import static org.junit.Assert.assertEquals; | ||
| import static org.junit.Assert.assertFalse; | ||
| import static org.junit.Assert.assertTrue; | ||
|
|
||
| public final class CookiePersistencePolicyTest { | ||
|
|
||
| @Test | ||
| public void persistentIsDefaultAndLegacyDefaultRemainsPersistent() { | ||
| assertEquals("persistent", CookiePersistencePolicy.normalize(null)); | ||
| assertEquals("persistent", CookiePersistencePolicy.normalize("")); | ||
| assertEquals("persistent", CookiePersistencePolicy.normalize("default")); | ||
| assertTrue(CookiePersistencePolicy.usesEncryptedPersistence("persistent")); | ||
| assertTrue(CookiePersistencePolicy.usesEncryptedPersistence("default")); | ||
| } | ||
|
|
||
| @Test | ||
| public void serverAndSessionModesRemainDistinct() { | ||
| assertEquals("server", CookiePersistencePolicy.normalize("server")); | ||
| assertEquals("session", CookiePersistencePolicy.normalize("session")); | ||
| assertFalse(CookiePersistencePolicy.usesEncryptedPersistence("server")); | ||
| assertFalse(CookiePersistencePolicy.usesEncryptedPersistence("session")); | ||
| assertTrue(CookiePersistencePolicy.startsFreshSession("session")); | ||
| assertFalse(CookiePersistencePolicy.startsFreshSession("server")); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,30 @@ | ||
| package de.starlightunit.wrapper.web; | ||
|
|
||
| import org.junit.Test; | ||
|
|
||
| import static org.junit.Assert.assertEquals; | ||
| import static org.junit.Assert.assertTrue; | ||
|
|
||
| public final class WebOverridesTest { | ||
|
|
||
| @Test | ||
| public void cssInjectionEscapesContentAndUsesStableStyleElement() { | ||
| String script = WebOverrides.cssInjectionScript("body { color: red; }\n.x { display: block; }"); | ||
| assertTrue(script.contains("quantum-profile-custom-css")); | ||
| assertTrue(script.contains("body { color: red; }\\n.x { display: block; }")); | ||
| assertTrue(script.contains("s.textContent=")); | ||
| } | ||
|
|
||
| @Test | ||
| public void javascriptInjectionPreservesSourceAndAddsDebugSourceName() { | ||
| String source = "window.quantumTest = (window.quantumTest || 0) + 1;"; | ||
| String script = WebOverrides.javascriptInjectionScript(source); | ||
| assertTrue(script.startsWith(source)); | ||
| assertTrue(script.endsWith("//# sourceURL=quantum-profile-custom.js")); | ||
| } | ||
|
|
||
| @Test | ||
| public void nullJavascriptStillProducesValidDebugSource() { | ||
| assertEquals("\n//# sourceURL=quantum-profile-custom.js", WebOverrides.javascriptInjectionScript(null)); | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When Android kills the app in the background and later recreates this Activity, it supplies a non-null
savedInstanceStateeven though a new app process and cookie session have started. Insessionmode this condition therefore skips the fresh-session clear; any server-persistent authentication cookie remains available whilerestoreState()resumes the prior page, defeating session-only behavior after a common process-death restore. Session initialization must distinguish a new process from an in-process Activity recreation rather than relying solely onsavedInstanceState.Useful? React with 👍 / 👎.