Bug 1587552 - Update GeckoView content blocking exception API and add better support for removing exceptions. r=esawin,Ehsan,geckoview-reviewers,snorp

Differential Revision: https://phabricator.services.mozilla.com/D58828

--HG--
extra : moz-landing-system : lando
This commit is contained in:
Dylan Roeh 2020-01-14 16:40:46 +00:00
parent d7a0a95c68
commit 0971e00ebf
7 changed files with 215 additions and 36 deletions

View File

@ -110,6 +110,7 @@ GeckoViewStartup.prototype = {
ged: [
"ContentBlocking:AddException",
"ContentBlocking:RemoveException",
"ContentBlocking:RemoveExceptionByPrincipal",
"ContentBlocking:CheckException",
"ContentBlocking:SaveList",
"ContentBlocking:RestoreList",

View File

@ -40,6 +40,7 @@ import java.io.InputStream;
import java.lang.Boolean;
import java.lang.CharSequence;
import java.lang.Class;
import java.lang.Deprecated;
import java.lang.Exception;
import java.lang.Float;
import java.lang.Integer;
@ -296,8 +297,16 @@ package org.mozilla.geckoview {
method @UiThread public void clearExceptionList();
method @UiThread @NonNull public GeckoResult<List<ContentBlockingController.LogEntry>> getLog(@NonNull GeckoSession);
method @UiThread public void removeException(@NonNull GeckoSession);
method @UiThread public void restoreExceptionList(@NonNull ContentBlockingController.ExceptionList);
method @UiThread @NonNull public GeckoResult<ContentBlockingController.ExceptionList> saveExceptionList();
method @AnyThread public void removeException(@NonNull ContentBlockingController.ContentBlockingException);
method @Deprecated @UiThread public void restoreExceptionList(@NonNull ContentBlockingController.ExceptionList);
method @AnyThread public void restoreExceptionList(@NonNull List<ContentBlockingController.ContentBlockingException>);
method @UiThread @NonNull public GeckoResult<List<ContentBlockingController.ContentBlockingException>> saveExceptionList();
}
@AnyThread public static class ContentBlockingController.ContentBlockingException {
method @NonNull public static ContentBlockingController.ContentBlockingException fromJson(@NonNull JSONObject);
method @NonNull public JSONObject toJson();
field @NonNull public final String uri;
}
public static class ContentBlockingController.Event {
@ -323,7 +332,7 @@ package org.mozilla.geckoview {
field public static final int LOADED_SOCIALTRACKING_CONTENT = 131072;
}
@AnyThread public class ContentBlockingController.ExceptionList {
@Deprecated @AnyThread public class ContentBlockingController.ExceptionList {
ctor public ExceptionList(@NonNull String);
ctor public ExceptionList(@NonNull JSONObject);
method @NonNull public String[] getUris();

View File

@ -13,6 +13,7 @@ import org.junit.Test
import org.junit.runner.RunWith
import org.mozilla.geckoview.ContentBlocking
import org.mozilla.geckoview.ContentBlockingController
import org.mozilla.geckoview.ContentBlockingController.ContentBlockingException
import org.mozilla.geckoview.GeckoSession
import org.mozilla.geckoview.test.rule.GeckoSessionTestRule
import org.mozilla.geckoview.test.rule.GeckoSessionTestRule.AssertCalled
@ -44,6 +45,7 @@ class ContentBlockingControllerTest : BaseSessionTest() {
}
})
// Add exception for this site.
sessionRule.runtime.contentBlockingController.addException(sessionRule.session)
sessionRule.runtime.contentBlockingController.checkException(sessionRule.session).accept {
@ -52,7 +54,7 @@ class ContentBlockingControllerTest : BaseSessionTest() {
var list = sessionRule.waitForResult(sessionRule.runtime.contentBlockingController.saveExceptionList())
assertThat("Exceptions list should not be null", list, Matchers.notNullValue())
assertThat("Exceptions list should have one entry", list.uris.size, Matchers.equalTo(1))
assertThat("Exceptions list should have one entry", list.size, Matchers.equalTo(1))
sessionRule.session.reload()
sessionRule.waitForPageStop()
@ -65,11 +67,78 @@ class ContentBlockingControllerTest : BaseSessionTest() {
}
})
// Remove exception for this site by passing GeckoSession.
sessionRule.runtime.contentBlockingController.removeException(sessionRule.session)
list = sessionRule.waitForResult(sessionRule.runtime.contentBlockingController.saveExceptionList())
assertThat("Exceptions list should not be null", list, Matchers.notNullValue())
assertThat("Exceptions list should have one entry", list.uris.size, Matchers.equalTo(0))
assertThat("Exceptions list should have one entry", list.size, Matchers.equalTo(0))
sessionRule.session.reload()
sessionRule.waitUntilCalled(
object : Callbacks.ContentBlockingDelegate {
@GeckoSessionTestRule.AssertCalled(count=3)
override fun onContentBlocked(session: GeckoSession,
event: ContentBlocking.BlockEvent) {
assertThat("Category should be set",
event.antiTrackingCategory,
Matchers.equalTo(category))
assertThat("URI should not be null", event.uri, Matchers.notNullValue())
assertThat("URI should match", event.uri, Matchers.endsWith("tracker.js"))
}
})
}
// disable test on debug for frequently failing #Bug 1580223
@Test
fun trackingProtectionExceptionRemoveByException() {
assumeThat(sessionRule.env.isDebugBuild, equalTo(false))
val category = ContentBlocking.AntiTracking.TEST
sessionRule.runtime.settings.contentBlocking.setAntiTracking(category)
sessionRule.session.loadTestPath(TRACKERS_PATH)
sessionRule.waitUntilCalled(
object : Callbacks.ContentBlockingDelegate {
@GeckoSessionTestRule.AssertCalled(count=3)
override fun onContentBlocked(session: GeckoSession,
event: ContentBlocking.BlockEvent) {
assertThat("Category should be set",
event.antiTrackingCategory,
Matchers.equalTo(category))
assertThat("URI should not be null", event.uri, Matchers.notNullValue())
assertThat("URI should match", event.uri, Matchers.endsWith("tracker.js"))
}
})
// Add exception for this site.
sessionRule.runtime.contentBlockingController.addException(sessionRule.session)
sessionRule.runtime.contentBlockingController.checkException(sessionRule.session).accept {
assertThat("Site should be on exceptions list", it, Matchers.equalTo(true))
}
var list = sessionRule.waitForResult(sessionRule.runtime.contentBlockingController.saveExceptionList())
assertThat("Exceptions list should not be null", list, Matchers.notNullValue())
assertThat("Exceptions list should have one entry", list.size, Matchers.equalTo(1))
sessionRule.session.reload()
sessionRule.waitForPageStop()
sessionRule.forCallbacksDuringWait(
object : Callbacks.ContentBlockingDelegate {
@GeckoSessionTestRule.AssertCalled(false)
override fun onContentBlocked(session: GeckoSession,
event: ContentBlocking.BlockEvent) {
}
})
// Remove exception for this site by passing ContentBlockingException.
sessionRule.runtime.contentBlockingController.removeException(list.get(0))
list = sessionRule.waitForResult(sessionRule.runtime.contentBlockingController.saveExceptionList())
assertThat("Exceptions list should not be null", list, Matchers.notNullValue())
assertThat("Exceptions list should have one entry", list.size, Matchers.equalTo(0))
sessionRule.session.reload()
@ -103,12 +172,9 @@ class ContentBlockingControllerTest : BaseSessionTest() {
var export = sessionRule.waitForResult(sessionRule.runtime.contentBlockingController
.saveExceptionList())
assertThat("Exported list must not be null", export, Matchers.notNullValue())
assertThat("Exported list must contain one entry", export.uris.size, Matchers.equalTo(1))
assertThat("Exported list must contain one entry", export.size, Matchers.equalTo(1))
val exportStr = export.toString()
assertThat("Exported string must not be null", exportStr, Matchers.notNullValue())
val exportJson = export.toJson()
val exportJson = export.get(0).toJson()
assertThat("Exported JSON must not be null", exportJson, Matchers.notNullValue())
// Wipe
@ -116,32 +182,16 @@ class ContentBlockingControllerTest : BaseSessionTest() {
export = sessionRule.waitForResult(sessionRule.runtime.contentBlockingController
.saveExceptionList())
assertThat("Exported list must not be null", export, Matchers.notNullValue())
assertThat("Exported list must contain zero entries", export.uris.size, Matchers.equalTo(0))
// Restore from String
val importStr = sessionRule.runtime.contentBlockingController.ExceptionList(exportStr)
sessionRule.runtime.contentBlockingController.restoreExceptionList(importStr)
export = sessionRule.waitForResult(sessionRule.runtime.contentBlockingController
.saveExceptionList())
assertThat("Exported list must not be null", export, Matchers.notNullValue())
assertThat("Exported list must contain one entry", export.uris.size, Matchers.equalTo(1))
// Wipe
sessionRule.runtime.contentBlockingController.clearExceptionList()
export = sessionRule.waitForResult(sessionRule.runtime.contentBlockingController
.saveExceptionList())
assertThat("Exported list must not be null", export, Matchers.notNullValue())
assertThat("Exported list must contain zero entries", export.uris.size, Matchers.equalTo(0))
assertThat("Exported list must contain zero entries", export.size, Matchers.equalTo(0))
// Restore from JSON
val importJson = sessionRule.runtime.contentBlockingController.ExceptionList(exportJson)
val importJson = listOf(ContentBlockingException.fromJson(exportJson))
sessionRule.runtime.contentBlockingController.restoreExceptionList(importJson)
export = sessionRule.waitForResult(sessionRule.runtime.contentBlockingController
.saveExceptionList())
assertThat("Exported list must not be null", export, Matchers.notNullValue())
assertThat("Exported list must contain one entry", export.uris.size, Matchers.equalTo(1))
assertThat("Exported list must contain one entry", export.size, Matchers.equalTo(1))
// Wipe so as not to break other tests.
sessionRule.runtime.contentBlockingController.clearExceptionList()

View File

@ -33,11 +33,57 @@ import java.util.List;
public class ContentBlockingController {
private static final String LOGTAG = "GeckoContentBlocking";
@AnyThread
public static class ContentBlockingException {
private final @NonNull String mEncodedPrincipal;
/**
* A String representing the URI of this content blocking exception.
*/
public final @NonNull String uri;
/* package */ ContentBlockingException(final @NonNull String encodedPrincipal,
final @NonNull String uri) {
mEncodedPrincipal = encodedPrincipal;
this.uri = uri;
}
/**
* Returns a JSONObject representation of the content blocking exception.
*
* @return A JSONObject representing the exception.
*
* @throws JSONException if conversion to JSONObject fails.
*/
public @NonNull JSONObject toJson() throws JSONException {
final JSONObject res = new JSONObject();
res.put("principal", mEncodedPrincipal);
res.put("uri", uri);
return res;
}
/**
*
* Returns a ContentBlockingException reconstructed from JSON.
*
* @param savedException A JSONObject representation of a saved exception; should be the output of
* {@link toJson}.
*
* @return A ContentBlockingException reconstructed from the supplied JSONObject.
*
* @throws JSONException if the JSONObject cannot be converted for any reason.
*/
public static @NonNull ContentBlockingException fromJson(final @NonNull JSONObject savedException) throws JSONException {
return new ContentBlockingException(savedException.getString("principal"), savedException.getString("uri"));
}
}
/**
* ExceptionList represents a content blocking exception list exported
* from Gecko. It can be used to persist the list or to inspect the URIs
* present in the list.
*/
@Deprecated
@AnyThread
public class ExceptionList {
private final @NonNull GeckoBundle mBundle;
@ -143,6 +189,21 @@ public class ContentBlockingController {
EventDispatcher.getInstance().dispatch("ContentBlocking:RemoveException", msg);
}
/**
* Remove the exception specified by the supplied {@link ContentBlockingException} from
* the content blocking exception list, if it is present. If there is no such exception,
* this is a no-op.
*
* @param exception A {@link ContentBlockingException} which will be removed from the
* content blocking exception list.
*/
@AnyThread
public void removeException(final @NonNull ContentBlockingException exception) {
final GeckoBundle msg = new GeckoBundle(1);
msg.putString("principal", exception.mEncodedPrincipal);
EventDispatcher.getInstance().dispatch("ContentBlocking:RemoveExceptionByPrincipal", msg);
}
/**
* Check whether or not there is an exception for the site currently loaded by the
* supplied {@link GeckoSession}.
@ -168,17 +229,31 @@ public class ContentBlockingController {
}
/**
* Save the current content blocking exception list as an {@link ExceptionList}.
* Save the current content blocking exception list as a List of {@link ContentBlockingException}.
*
* @return An {@link ExceptionList} which can be used to restore the current
* exception list.
* @return A List of {@link ContentBlockingException} which can be used to restore or
* inspect the current exception list.
*/
@UiThread
public @NonNull GeckoResult<ExceptionList> saveExceptionList() {
final CallbackResult<ExceptionList> result = new CallbackResult<ExceptionList>() {
public @NonNull GeckoResult<List<ContentBlockingException>> saveExceptionList() {
final CallbackResult<List<ContentBlockingException>> result = new CallbackResult<List<ContentBlockingException>>() {
@Override
public void sendSuccess(final Object value) {
complete(new ExceptionList((GeckoBundle) value));
final String[] principals = ((GeckoBundle) value).getStringArray("principals");
final String[] uris = ((GeckoBundle) value).getStringArray("uris");
if (principals == null || uris == null) {
completeExceptionally(new RuntimeException("Received invalid content blocking exception list"));
return;
}
final ArrayList<ContentBlockingException> res = new ArrayList<ContentBlockingException>(principals.length);
for (int i = 0; i < principals.length; i++) {
res.add(new ContentBlockingException(principals[i], uris[i]));
}
complete(Collections.unmodifiableList(res));
}
};
EventDispatcher.getInstance().dispatch("ContentBlocking:SaveList", null, result);
@ -190,11 +265,34 @@ public class ContentBlockingController {
*
* @param list An {@link ExceptionList} originally created by {@link saveExceptionList}.
*/
@Deprecated
@UiThread
public void restoreExceptionList(final @NonNull ExceptionList list) {
EventDispatcher.getInstance().dispatch("ContentBlocking:RestoreList", list.getBundle());
}
/**
* Restore the supplied List of {@link ContentBlockingException}, overwriting the existing exception list.
*
* @param list A List of {@link ContentBlockingException} originally created by {@link saveExceptionList}.
*/
@AnyThread
public void restoreExceptionList(final @NonNull List<ContentBlockingException> list) {
final GeckoBundle bundle = new GeckoBundle(2);
final String[] principals = new String[list.size()];
final String[] uris = new String[list.size()];
for (int i = 0; i < list.size(); i++) {
principals[i] = list.get(i).mEncodedPrincipal;
uris[i] = list.get(i).uri;
}
bundle.putStringArray("principals", principals);
bundle.putStringArray("uris", uris);
EventDispatcher.getInstance().dispatch("ContentBlocking:RestoreList", bundle);
}
/**
* Clear the content blocking exception list entirely.
*/

View File

@ -29,6 +29,10 @@ exclude: true
- Added ['WebRequestError#certificate'][74.6], which is the server certificate used in the
failed request, if any.
([bug 1508730]({{bugzilla}}1508730))
- ⚠️ Updated [`ContentBlockingController`][74.7] to use new representation for content blocking
exceptions and to add better support for removing exceptions. This deprecates [`ExceptionList`][74.8]
and [`restoreExceptionList`][74.9] with the intent to remove them in 76.
([bug 1587552]({{bugzilla}}1587552))
[74.1]: {{javadoc_uri}}/WebExtensionController.html#enable-org.mozilla.geckoview.WebExtension-int-
[74.2]: {{javadoc_uri}}/WebExtensionController.html#disable-org.mozilla.geckoview.WebExtension-int-
@ -36,6 +40,9 @@ exclude: true
[74.4]: {{javadoc_uri}}/WebResponse.html#isSecure
[74.5]: {{javadoc_uri}}/WebResponse.html#certificate
[74.6]: {{javadoc_uri}}/WebRequestError.html#certificate
[74.7]: {{javadoc_uri}}/ContentBlockingController.html
[74.8]: {{javadoc_uri}}/ContentBlockingController.ExceptionList.html
[74.9]: {{javadoc_uri}}/ContentBlockingController.html#restoreExceptionList-org.mozilla.geckoview.ContentBlockingController.ExceptionList-
## v73
- Added [`WebExtensionController.install`][73.1] and [`uninstall`][73.2] to
@ -546,4 +553,4 @@ exclude: true
[65.24]: {{javadoc_uri}}/CrashReporter.html#sendCrashReport-android.content.Context-android.os.Bundle-java.lang.String-
[65.25]: {{javadoc_uri}}/GeckoResult.html
[api-version]: 34255117d007cf40a19b289285f00206f55ea792
[api-version]: e89f3bb79d0c5cc936812de4c2d8f4d30923afb9

View File

@ -54,6 +54,12 @@ const GeckoViewContentBlockingController = {
break;
}
case "ContentBlocking:RemoveExceptionByPrincipal": {
const principal = E10SUtils.deserializePrincipal(aData.principal);
ContentBlockingAllowList.removeByPrincipal(principal);
break;
}
case "ContentBlocking:CheckException": {
const sessionWindow = Services.ww.getWindowByName(
aData.sessionId,

View File

@ -100,6 +100,14 @@ const ContentBlockingAllowList = {
Services.perms.removeFromPrincipal(prin, type);
},
/**
* Remove the given principal from the Content Blocking allow list if present.
*/
removeByPrincipal(principal) {
Services.perms.removeFromPrincipal(principal, "trackingprotection");
Services.perms.removeFromPrincipal(principal, "trackingprotection-pb");
},
/**
* Returns true if the current browser has loaded a document that is on the
* Content Blocking allow list.