author | Dylan Roeh <droeh@mozilla.com> |
Tue, 19 May 2020 17:05:42 +0000 | |
changeset 530862 | 328927902c0607dd2600063e259bda1fa51c2e7b |
parent 530861 | 36ca759b04e6e72d627655914cdd3635bbaf5c2c |
child 530863 | d69e32a6f3b0144635c7b93c81589f31a84fcc36 |
push id | 116418 |
push user | droeh@mozilla.com |
push date | Tue, 19 May 2020 17:30:29 +0000 |
treeherder | autoland@328927902c06 [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
reviewers | snorp, mattwoodrow, geckoview-reviewers, agi |
bugs | 1596825 |
milestone | 78.0a1 |
first release with | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
last release without | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
--- a/mobile/android/geckoview/api.txt +++ b/mobile/android/geckoview/api.txt @@ -739,16 +739,17 @@ package org.mozilla.geckoview { public static interface GeckoSession.NavigationDelegate { method @UiThread default public void onCanGoBack(@NonNull GeckoSession, boolean); method @UiThread default public void onCanGoForward(@NonNull GeckoSession, boolean); method @UiThread @Nullable default public GeckoResult<String> onLoadError(@NonNull GeckoSession, @Nullable String, @NonNull WebRequestError); 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; field public static final int TARGET_WINDOW_NONE = 0; } public static class GeckoSession.NavigationDelegate.LoadRequest { ctor protected LoadRequest();
new file mode 100644 --- /dev/null +++ b/mobile/android/geckoview/src/androidTest/assets/www/iframe_unknown_protocol.html @@ -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>
--- a/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/BaseSessionTest.kt +++ b/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/BaseSessionTest.kt @@ -64,16 +64,17 @@ open class BaseSessionTest(noErrorCollec const val FIXED_VH = "/assets/www/fixedvh.html" const val FIXED_PERCENT = "/assets/www/fixedpercent.html" const val STORAGE_TITLE_HTML_PATH = "/assets/www/reflect_local_storage_into_title.html" const val HUNG_SCRIPT = "/assets/www/hungScript.html" const val PUSH_HTML_PATH = "/assets/www/push/push.html" 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 } @get:Rule val sessionRule = GeckoSessionTestRule() @get:Rule val errors = ErrorCollector()
--- a/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/NavigationDelegateTest.kt +++ b/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/NavigationDelegateTest.kt @@ -202,16 +202,41 @@ class NavigationDelegateTest : BaseSessi @Ignore // Disabled for bug 1619344. @Test fun loadUnknownProtocol() { testLoadEarlyError(UNKNOWN_PROTOCOL_URI, WebRequestError.ERROR_CATEGORY_URI, 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() { val category = ContentBlocking.AntiTracking.TEST sessionRule.runtime.settings.contentBlocking.setAntiTracking(category) sessionRule.session.loadTestPath(TRACKERS_PATH) sessionRule.waitUntilCalled( @@ -304,21 +329,32 @@ class NavigationDelegateTest : BaseSessi // We shouldn't be firing onLoadRequest for iframes, including redirects. sessionRule.forCallbacksDuringWait(object : Callbacks.NavigationDelegate { @AssertCalled(count = 1) override fun onLoadRequest(session: GeckoSession, 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 } }) } @Test fun redirectDenyLoad() { val redirectUri = if (sessionRule.env.isAutomation) { "http://example.org/tests/junit/hello.html"
--- a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoSession.java +++ b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoSession.java @@ -1144,17 +1144,17 @@ public class GeckoSession implements Par protected void finalize() throws Throwable { close(); disposeNative(); } @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. return GeckoResult.fromValue(false); } GeckoResult<Boolean> res = new GeckoResult<>(); ThreadUtils.postToUiThread(new Runnable() { @@ -1165,17 +1165,19 @@ public class GeckoSession implements Par if (delegate == null) { res.complete(false); return; } 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); return; } reqResponse.accept(value -> { if (value == AllowOrDeny.DENY) { @@ -3701,16 +3703,34 @@ public class GeckoSession implements Par */ @UiThread default @Nullable GeckoResult<AllowOrDeny> onLoadRequest(@NonNull GeckoSession session, @NonNull LoadRequest request) { 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 * returned GeckoSession must be a newly-created one. * * @param session The GeckoSession that initiated the callback. * @param uri The URI to be loaded. * * @return A {@link GeckoResult} which holds the returned GeckoSession. May be null, in
--- a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/doc-files/CHANGELOG.md +++ b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/doc-files/CHANGELOG.md @@ -20,21 +20,24 @@ exclude: true and will be removed in GeckoView 81. - Added [`CookieBehavior.ACCEPT_FIRST_PARTY_AND_ISOLATE_OTHERS`][78.2] to allow enabling dynamic first party isolation; this will block tracking cookies and isolate all other third party cookies by keying them based on the first party from which they are accessed. - 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. ([bug 1626979]({{bugzilla}}1626979)) - ⚠️ Remove the `DynamicToolbarAnimator` API along with accesors on `GeckoView` and `GeckoSession`. ([bug 1627716]({{bugzilla}}1627716)) [77.1]: {{javadoc_uri}}/GeckoRuntime.html#appendAppNotesToCrashReport-java.lang.String- @@ -688,9 +691,9 @@ exclude: true [65.19]: {{javadoc_uri}}/GeckoSession.NavigationDelegate.LoadRequest.html#isRedirect [65.20]: {{javadoc_uri}}/GeckoSession.html#LOAD_FLAGS_BYPASS_CLASSIFIER [65.21]: {{javadoc_uri}}/GeckoSession.ContentDelegate.ContextElement.html [65.22]: {{javadoc_uri}}/GeckoSession.ContentDelegate.html#onContextMenu-org.mozilla.geckoview.GeckoSession-int-int-org.mozilla.geckoview.GeckoSession.ContentDelegate.ContextElement- [65.23]: {{javadoc_uri}}/GeckoSession.FinderResult.html [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
--- a/mobile/android/geckoview_example/src/main/java/org/mozilla/geckoview_example/GeckoViewActivity.java +++ b/mobile/android/geckoview_example/src/main/java/org/mozilla/geckoview_example/GeckoViewActivity.java @@ -1659,17 +1659,29 @@ public class GeckoViewActivity } @Override public GeckoResult<AllowOrDeny> onLoadRequest(final GeckoSession session, final LoadRequest request) { 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); } @Override public GeckoResult<GeckoSession> onNewSession(final GeckoSession session, final String uri) { final TabSession newSession = createSession(); mToolbarView.updateTabCount();
--- a/netwerk/ipc/DocumentLoadListener.cpp +++ b/netwerk/ipc/DocumentLoadListener.cpp @@ -477,27 +477,26 @@ bool DocumentLoadListener::Open( browsingContext); openInfo->Prepare(); #ifdef ANDROID RefPtr<MozPromise<bool, bool, false>> promise; 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(); RefPtr<nsWindow> window = nsWindow::From(widget); if (window) { promise = window->OnLoadRequest( aLoadState->URI(), nsIBrowserDOMWindow::OPEN_CURRENTWINDOW, aLoadState->LoadFlags(), aLoadState->TriggeringPrincipal(), - aHasGesture); + aHasGesture, browsingContext->IsTopContent()); } } if (promise) { RefPtr<DocumentLoadListener> self = this; promise->Then( GetCurrentThreadSerialEventTarget(), __func__, [=](const MozPromise<bool, bool, false>::ResolveOrRejectValue& aValue) { @@ -1836,25 +1835,24 @@ DocumentLoadListener::AsyncOnChannelRedi #ifdef ANDROID nsCOMPtr<nsIURI> uriBeingLoaded = AntiTrackingUtils::MaybeGetDocumentURIBeingLoaded(mChannel); RefPtr<CanonicalBrowsingContext> bc = 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) { RefPtr<nsIAsyncVerifyRedirectCallback> cb = aCallback; promise->Then( GetCurrentThreadSerialEventTarget(), __func__, [=](const MozPromise<bool, bool, false>::ResolveOrRejectValue& aValue) { if (aValue.IsResolve()) {
--- a/widget/android/nsWindow.cpp +++ b/widget/android/nsWindow.cpp @@ -359,17 +359,18 @@ class nsWindow::GeckoViewSupport final void AttachAccessibility(const GeckoSession::Window::LocalRef& inst, jni::Object::Param aSessionAccessibility); void OnReady(jni::Object::Param aQueue = nullptr); 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; }; /** * PanZoomController handles its native calls on the UI thread, so make * it separate from GeckoViewSupport. */ class nsWindow::NPZCSupport final : public java::PanZoomController::NativeProvider::Natives<NPZCSupport> { @@ -1660,17 +1661,18 @@ void nsWindow::SetParent(nsIWidget* aNew // if we are now in the toplevel window's hierarchy, schedule a redraw if (FindTopLevel() == nsWindow::TopWindow()) RedrawAll(); } 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__); } nsAutoCString spec, triggeringSpec; if (aUri) { aUri->GetDisplaySpec(spec); } @@ -1685,17 +1687,18 @@ RefPtr<MozPromise<bool, bool, false>> ns if (triggeringUri) { triggeringUri->GetDisplaySpec(triggeringSpec); } } } 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; } float nsWindow::GetDPI() { float dpi = 160.0f; @@ -2411,24 +2414,24 @@ void nsWindow::UpdateSafeAreaInsets(cons if (mAttachedWidgetListener) { mAttachedWidgetListener->SafeAreaInsetsChanged(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() { nsCOMPtr<nsIWidget> window = new nsWindow(); return window.forget(); } already_AddRefed<nsIWidget> nsIWidget::CreateChildWindow() {
--- a/widget/android/nsWindow.h +++ b/widget/android/nsWindow.h @@ -56,17 +56,18 @@ class nsWindow final : public nsBaseWidg NS_INLINE_DECL_REFCOUNTING_INHERITED(nsWindow, nsBaseWidget) static void InitNatives(); void SetScreenId(uint32_t aScreenId) { mScreenId = aScreenId; } 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; // An Event subclass that guards against stale events. template <typename Lambda, bool IsStatic = Lambda::isStatic, typename InstanceType = typename Lambda::ThisArgType, class Impl = typename Lambda::TargetClass>