Bug 1596825 - Add NavigationDelegate.onSubframeLoadRequest to handle non-top-level load requests. r=snorp,mattwoodrow,geckoview-reviewers,agi

Differential Revision: https://phabricator.services.mozilla.com/D74939
This commit is contained in:
Dylan Roeh 2020-05-19 17:05:42 +00:00
Родитель 42e3da4a1c
Коммит 2157c7820c
10 изменённых файлов: 109 добавлений и 25 удалений

Просмотреть файл

@ -744,6 +744,7 @@ package org.mozilla.geckoview {
method @UiThread @Nullable default public GeckoResult<AllowOrDeny> onLoadRequest(@NonNull GeckoSession, @NonNull GeckoSession.NavigationDelegate.LoadRequest);
method @UiThread default public void onLocationChange(@NonNull GeckoSession, @Nullable String);
method @UiThread @Nullable default public GeckoResult<GeckoSession> onNewSession(@NonNull GeckoSession, @NonNull String);
method @UiThread @Nullable default public GeckoResult<AllowOrDeny> onSubframeLoadRequest(@NonNull GeckoSession, @NonNull GeckoSession.NavigationDelegate.LoadRequest);
field public static final int LOAD_REQUEST_IS_REDIRECT = 8388608;
field public static final int TARGET_WINDOW_CURRENT = 1;
field public static final int TARGET_WINDOW_NEW = 2;

Просмотреть файл

@ -0,0 +1,9 @@
<html>
<head>
<title>Hello, world!</title>
</head>
<body>
<p>Hello, world! From Top Level.</p>
<iframe src="foo://bar"></iframe>
</body>
</html>

Просмотреть файл

@ -69,6 +69,7 @@ open class BaseSessionTest(noErrorCollector: Boolean = false) {
const val OPEN_WINDOW_PATH = "/assets/www/worker/open_window.html"
const val OPEN_WINDOW_TARGET_PATH = "/assets/www/worker/open_window_target.html"
const val DATA_URI_PATH = "/assets/www/data_uri.html"
const val IFRAME_UNKNOWN_PROTOCOL = "/assets/www/iframe_unknown_protocol.html"
const val TEST_ENDPOINT = GeckoSessionTestRule.TEST_ENDPOINT
}

Просмотреть файл

@ -207,6 +207,31 @@ class NavigationDelegateTest : BaseSessionTest() {
WebRequestError.ERROR_UNKNOWN_PROTOCOL)
}
@Test fun loadUnknownProtocolIframe() {
// Should match iframe URI from IFRAME_UNKNOWN_PROTOCOL
val iframeUri = "foo://bar"
sessionRule.session.loadTestPath(IFRAME_UNKNOWN_PROTOCOL)
sessionRule.session.waitForPageStop()
sessionRule.forCallbacksDuringWait(object : Callbacks.NavigationDelegate {
@AssertCalled(count = 1)
override fun onLoadRequest(session: GeckoSession, request: LoadRequest) : GeckoResult<AllowOrDeny>? {
assertThat("URI should not be null", request.uri, notNullValue())
assertThat("URI should match", request.uri, endsWith(IFRAME_UNKNOWN_PROTOCOL))
return null
}
@AssertCalled(count = 1)
override fun onSubframeLoadRequest(session: GeckoSession,
request: LoadRequest):
GeckoResult<AllowOrDeny>? {
assertThat("URI should not be null", request.uri, notNullValue())
assertThat("URI should match", request.uri, endsWith(iframeUri))
return null
}
})
}
@Setting(key = Setting.Key.USE_TRACKING_PROTECTION, value = "true")
@Ignore // TODO: Bug 1564373
@Test fun trackingProtection() {
@ -309,11 +334,22 @@ class NavigationDelegateTest : BaseSessionTest() {
request: LoadRequest):
GeckoResult<AllowOrDeny>? {
assertThat("Session should not be null", session, notNullValue())
assertThat("App requested this load", request.isDirectNavigation,
equalTo(true))
assertThat("App requested this load", request.isDirectNavigation, equalTo(true))
assertThat("URI should not be null", request.uri, notNullValue())
assertThat("URI should match", request.uri,
startsWith(GeckoSessionTestRule.TEST_ENDPOINT))
assertThat("URI should match", request.uri, endsWith(path))
assertThat("isRedirect should match", request.isRedirect, equalTo(false))
return null
}
@AssertCalled(count = 2)
override fun onSubframeLoadRequest(session: GeckoSession,
request: LoadRequest):
GeckoResult<AllowOrDeny>? {
assertThat("Session should not be null", session, notNullValue())
assertThat("App did not request this load", request.isDirectNavigation, equalTo(false))
assertThat("URI should not be null", request.uri, notNullValue())
assertThat("isRedirect should match", request.isRedirect,
equalTo(forEachCall(false, true)))
return null
}
})

Просмотреть файл

@ -1149,7 +1149,7 @@ public class GeckoSession implements Parcelable {
@WrapForJNI(calledFrom = "gecko")
private GeckoResult<Boolean> onLoadRequest(final @NonNull String uri, final int windowType,
final int flags, final @Nullable String triggeringUri,
final boolean hasUserGesture) {
final boolean hasUserGesture, final boolean isTopLevel) {
final GeckoSession session = (mOwner == null) ? null : mOwner.get();
if (session == null) {
// Don't handle any load request if we can't get the session for some reason.
@ -1170,7 +1170,9 @@ public class GeckoSession implements Parcelable {
final String trigger = TextUtils.isEmpty(triggeringUri) ? null : triggeringUri;
final NavigationDelegate.LoadRequest req = new NavigationDelegate.LoadRequest(uri,
trigger, windowType, flags, hasUserGesture, false /* isDirectNavigation */);
final GeckoResult<AllowOrDeny> reqResponse = delegate.onLoadRequest(session, req);
final GeckoResult<AllowOrDeny> reqResponse = isTopLevel ?
delegate.onLoadRequest(session, req) :
delegate.onSubframeLoadRequest(session, req);
if (reqResponse == null) {
res.complete(false);
@ -3705,6 +3707,24 @@ public class GeckoSession implements Parcelable {
return null;
}
/**
* A request to load a URI in a non-top-level context.
*
* @param session The GeckoSession that initiated the callback.
* @param request The {@link LoadRequest} containing the request details.
*
* @return A {@link GeckoResult} with a {@link AllowOrDeny} value which indicates whether
* or not the load was handled. If unhandled, Gecko will continue the
* load as normal. If handled (a {@link AllowOrDeny#DENY DENY} value), Gecko
* will abandon the load. A null return value is interpreted as
* {@link AllowOrDeny#ALLOW ALLOW} (unhandled).
*/
@UiThread
default @Nullable GeckoResult<AllowOrDeny> onSubframeLoadRequest(@NonNull GeckoSession session,
@NonNull LoadRequest request) {
return null;
}
/**
* A request has been made to open a new session. The URI is provided only for
* informational purposes. Do not call GeckoSession.loadUri() here. Additionally, the

Просмотреть файл

@ -25,11 +25,14 @@ exclude: true
- Added `cookieStoreId` field to [`WebExtension.CreateTabDetails`][78.3]. This adds the optional
ability to create a tab with a given cookie store ID for its [`contextual identity`][78.4].
([bug 1622500]({{bugzilla}}1622500))
- Added [`NavigationDelegate.onSubframeLoadRequest`][78.5] to allow intercepting
non-top-level navigations.
[78.1]: {{javadoc_uri}}/WebExtensionController.html#installBuiltIn-java.lang.String-
[78.2]: {{javadoc_uri}}/ContentBlocking.CookieBehavior.html#ACCEPT_FIRST_PARTY_AND_ISOLATE_OTHERS
[78.3]: {{javadoc_uri}}/WebExtension.CreateTabDetails.html
[78.4]: https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/contextualIdentities
[78.5]: {{javadoc_uri}}/GeckoSession.NavigationDelegate.html#onSubframeLoadRequest-org.mozilla.geckoview.GeckoSession-org.mozilla.geckoview.GeckoSession.NavigationDelegate.LoadRequest-
## v77
- Added [`GeckoRuntime.appendAppNotesToCrashReport`][77.1] For adding app notes to the crash report.
@ -693,4 +696,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]: 5460cbfe03322d19964ce94082ac2ae99af6d791
[api-version]: bde8001c948235193636d0d21f684baeb551e739

Просмотреть файл

@ -1664,7 +1664,19 @@ public class GeckoViewActivity
Log.d(LOGTAG, "onLoadRequest=" + request.uri +
" triggerUri=" + request.triggerUri +
" where=" + request.target +
" isRedirect=" + request.isRedirect);
" isRedirect=" + request.isRedirect +
" isDirectNavigation=" + request.isDirectNavigation);
return GeckoResult.fromValue(AllowOrDeny.ALLOW);
}
@Override
public GeckoResult<AllowOrDeny> onSubframeLoadRequest(final GeckoSession session,
final LoadRequest request) {
Log.d(LOGTAG, "onSubframeLoadRequest=" + request.uri +
" triggerUri=" + request.triggerUri +
" isRedirect=" + request.isRedirect +
"isDirectNavigation=" + request.isDirectNavigation);
return GeckoResult.fromValue(AllowOrDeny.ALLOW);
}

Просмотреть файл

@ -482,7 +482,6 @@ bool DocumentLoadListener::Open(
if (aLoadState->LoadType() != LOAD_ERROR_PAGE &&
!(aLoadState->HasLoadFlags(
nsDocShell::INTERNAL_LOAD_FLAGS_BYPASS_LOAD_URI_DELEGATE)) &&
browsingContext->IsTopContent() &&
!(aLoadState->LoadType() & LOAD_HISTORY)) {
nsCOMPtr<nsIWidget> widget =
browsingContext->GetParentProcessWidgetContaining();
@ -492,7 +491,7 @@ bool DocumentLoadListener::Open(
promise = window->OnLoadRequest(
aLoadState->URI(), nsIBrowserDOMWindow::OPEN_CURRENTWINDOW,
aLoadState->LoadFlags(), aLoadState->TriggeringPrincipal(),
aHasGesture);
aHasGesture, browsingContext->IsTopContent());
}
}
@ -1841,15 +1840,14 @@ DocumentLoadListener::AsyncOnChannelRedirect(
mParentChannelListener->GetBrowsingContext();
RefPtr<MozPromise<bool, bool, false>> promise;
if (bc->IsTopContent()) {
nsCOMPtr<nsIWidget> widget = bc->GetParentProcessWidgetContaining();
RefPtr<nsWindow> window = nsWindow::From(widget);
nsCOMPtr<nsIWidget> widget = bc->GetParentProcessWidgetContaining();
RefPtr<nsWindow> window = nsWindow::From(widget);
if (window) {
promise = window->OnLoadRequest(
uriBeingLoaded, nsIBrowserDOMWindow::OPEN_CURRENTWINDOW,
nsIWebNavigation::LOAD_FLAGS_IS_REDIRECT, nullptr, false);
}
if (window) {
promise = window->OnLoadRequest(uriBeingLoaded,
nsIBrowserDOMWindow::OPEN_CURRENTWINDOW,
nsIWebNavigation::LOAD_FLAGS_IS_REDIRECT,
nullptr, false, bc->IsTopContent());
}
if (promise) {

Просмотреть файл

@ -364,7 +364,8 @@ class nsWindow::GeckoViewSupport final
auto OnLoadRequest(mozilla::jni::String::Param aUri, int32_t aWindowType,
int32_t aFlags, mozilla::jni::String::Param aTriggeringUri,
bool aHasUserGesture) const -> java::GeckoResult::LocalRef;
bool aHasUserGesture, bool aIsTopLevel) const
-> java::GeckoResult::LocalRef;
};
/**
@ -1665,7 +1666,8 @@ nsIWidget* nsWindow::GetParent() { return mParent; }
RefPtr<MozPromise<bool, bool, false>> nsWindow::OnLoadRequest(
nsIURI* aUri, int32_t aWindowType, int32_t aFlags,
nsIPrincipal* aTriggeringPrincipal, bool aHasUserGesture) {
nsIPrincipal* aTriggeringPrincipal, bool aHasUserGesture,
bool aIsTopLevel) {
if (!mGeckoViewSupport) {
return MozPromise<bool, bool, false>::CreateAndResolve(false, __func__);
}
@ -1690,7 +1692,8 @@ RefPtr<MozPromise<bool, bool, false>> nsWindow::OnLoadRequest(
auto geckoResult = mGeckoViewSupport->OnLoadRequest(
spec.get(), aWindowType, aFlags,
isNullPrincipal ? nullptr : triggeringSpec.get(), aHasUserGesture);
isNullPrincipal ? nullptr : triggeringSpec.get(), aHasUserGesture,
aIsTopLevel);
return geckoResult
? MozPromise<bool, bool, false>::FromGeckoResult(geckoResult)
: nullptr;
@ -2416,14 +2419,14 @@ void nsWindow::UpdateSafeAreaInsets(const ScreenIntMargin& aSafeAreaInsets) {
auto nsWindow::GeckoViewSupport::OnLoadRequest(
mozilla::jni::String::Param aUri, int32_t aWindowType, int32_t aFlags,
mozilla::jni::String::Param aTriggeringUri, bool aHasUserGesture) const
-> java::GeckoResult::LocalRef {
mozilla::jni::String::Param aTriggeringUri, bool aHasUserGesture,
bool aIsTopLevel) const -> java::GeckoResult::LocalRef {
GeckoSession::Window::LocalRef window(mGeckoViewWindow);
if (!window) {
return nullptr;
}
return window->OnLoadRequest(aUri, aWindowType, aFlags, aTriggeringUri,
aHasUserGesture);
aHasUserGesture, aIsTopLevel);
}
already_AddRefed<nsIWidget> nsIWidget::CreateTopLevelWindow() {

Просмотреть файл

@ -61,7 +61,8 @@ class nsWindow final : public nsBaseWidget {
void OnGeckoViewReady();
RefPtr<mozilla::MozPromise<bool, bool, false>> OnLoadRequest(
nsIURI* aUri, int32_t aWindowType, int32_t aFlags,
nsIPrincipal* aTriggeringPrincipal, bool aHasUserGesture);
nsIPrincipal* aTriggeringPrincipal, bool aHasUserGesture,
bool aIsTopLevel);
private:
uint32_t mScreenId;