Bug 165627

Summary: itunes app links fail to navigate window.top when URL inside timeout
Product: WebKit Reporter: Mohammed Khatib <mkhatib727>
Component: FramesAssignee: Nobody <webkit-unassigned>
Status: NEW ---    
Severity: Normal CC: ap, bfulgham, cdumez, dvoytenko, fred.wang, ggaren, jfernandez, rbuis, simon.fraser, thorton, webkit-bug-importer, wenson_hsieh
Priority: P2 Keywords: InRadar
Version: Safari 10   
Hardware: Unspecified   
OS: Unspecified   
See Also: https://bugs.webkit.org/show_bug.cgi?id=167341
Description Flags
video of failure (simulator) none

Description Mohammed Khatib 2016-12-08 14:37:22 PST
These tests were done in this environment: 

top: https://hello.com
  iframe: https://whatsapp.com
     script loaded from: https://whatsapp.com
        executes in timeout -> window.top.location.replace('https://anylink.com') // <- Navigation works as expected
        executes in timeout -> window.top.location.replace('https://itunes.apple.com/us/app/id828256236') // <- Nothing happens.
        executes in timeout -> window.top.location.href = 'https://anylink.com' // <- Navigation works as expected.
        executes in timeout -> window.top.location.href = 'https://itunes.apple.com/us/app/id828256236' // <- Nothing happens.

Here's the real examples:
Using href = url inside timeout in cross origin iframes: http://top.webjs.run/top-location-redirect/index-12.html
Using top.location.replace(url) inside timeout in cross origin iframes: http://top.webjs.run/top-location-redirect/index-13.html

Notice in both examples, on Safari Mac OS X both of them redirects the user after 5seconds to itunes store (as expected).
On iOS Safari however, the first timeout (5 seconds) fails to navigate the user to itunes store (app or web page) and instead after 7 seconds the second timeout takes the user to google.com normally.
Comment 1 Radar WebKit Bug Importer 2017-02-26 17:07:49 PST
Comment 2 Frédéric Wang (:fredw) 2018-02-12 06:38:15 PST
(In reply to Mohammed Khatib from comment #0)
> Here's the real examples:
> Using href = url inside timeout in cross origin iframes:
> http://top.webjs.run/top-location-redirect/index-12.html
> Using top.location.replace(url) inside timeout in cross origin iframes:
> http://top.webjs.run/top-location-redirect/index-13.html

@Mohammed: Can you please provide new test cases to reproduce the issue? These two URLs do not seem to work any more :-(
Comment 3 Frédéric Wang (:fredw) 2018-04-12 00:21:50 PDT
Created attachment 337779 [details]

This is a testcase provided by AMP developers. It works well with a device but is a bit harder to reproduce on the simulator (and I've not identified a reliable way to reproduce it).
Comment 4 Frédéric Wang (:fredw) 2018-04-12 00:25:01 PDT
Created attachment 337780 [details]
video of failure (simulator)

This video shows when the testcase fails with the simulator. A popup message opens with "Safari cannot open the page because the address is invalid" and blocks the redirection to itunes and then the redirection to google happens. Looking for this message on the Web, I see similar issues e.g. https://stackoverflow.com/questions/27049228/safari-cannot-open-the-page-because-the-address-is-invalid-for-google-maps-lin
Comment 5 Frédéric Wang (:fredw) 2018-04-27 10:48:57 PDT
So trying to debug on the simulator, "window.top.location.href = 'https://itunes.apple.com/us/app/id828256236'" triggers the following backtrace on the Web process:

#0 WebCore::NavigationScheduler::startTimer()
#1 WebCore::NavigationScheduler::schedule
#2 WebCore::NavigationScheduler::scheduleLocationChange
#3 WebCore::DOMWindow::setLocation
#4 WebCore::Location::setLocation
#5 WebCore::Location::setHref

Which in turn schedules a load to 'https://itunes.apple.com/us/app/id828256236':

#0 WebKit::WebFrameLoaderClient::dispatchDecidePolicyForNavigationAction
#1 WebCore::PolicyChecker::checkNavigationPolicy
#2 WebCore::FrameLoader::loadWithDocumentLoader
#3 WebCore::FrameLoader::loadWithNavigationAction
#4 WebCore::FrameLoader::loadURL
#5 WebCore::FrameLoader::loadFrameRequest
#6 WebCore::FrameLoader::urlSelected
#7 WebCore::FrameLoader::changeLocation
#8 WebCore::ScheduledLocationChange::fire
#9 WebCore::NavigationScheduler::timerFired()

As said in comment 3, the issue is a bit difficult to reproduce on the simulator, but when the message "Safari cannot open the page because the address is invalid" does show up, I actually later see a request to 'itmss://itunes.apple.com/us/app/id828256236':

#0 WebKit::WebFrameLoaderClient::dispatchDecidePolicyForNavigationAction
#1 WebCore::PolicyChecker::checkNavigationPolicy
#2 WebCore::PolicyChecker::checkNavigationPolicy
#3 WebCore::DocumentLoader::willSendRequest
#4 WebCore::DocumentLoader::redirectReceived
#5 WebCore::iterateClients
#6 WebCore::CachedRawResource::redirectReceived
#7 WebCore::SubresourceLoader::willSendRequestInternal
#8 WebCore::ResourceLoader::willSendRequest
#9 WebKit::WebResourceLoader::willSendRequest

Debugging the network process, the redirection from 'https://' to 'itmss://' protocol actually passes here: 

#0 WebKit::NetworkResourceLoader::continueWillSendRedirectedRequest
#1 WebKit::NetworkResourceLoader::willSendRedirectedRequest
#2 WebKit::NetworkLoad::willPerformHTTPRedirection
#3 WebKit::NetworkDataTaskCocoa::willPerformHTTPRedirection
#4 ::-[WKNetworkSessionDelegate URLSession:task:willPerformHTTPRedirection:newRequest:completionHandler:](NSURLSession *, NSURLSessionTask *, NSHTTPURLResponse *, NSURLRequest *, void (^)(NSURLRequest *))

I could not find out from where willPerformHTTPRedirection is called and hence where the protocol changes actually happens though.

I noticed that opening directly the 'itmss://' URL on the simulator does result in the "Safari cannot open the page because the address is invalid" alert showing up, so that would explain what I see on the simulator. Not sure if that's the same issue on a device.
Comment 6 Frédéric Wang (:fredw) 2018-04-30 06:08:41 PDT
So now debugging the UIProcess after the DecidePolicyForNavigationAction is dispatched, I get the following trace:

# decisionHandlerWithPolicies(WKNavigationActionPolicy, _WKWebsitePolicies*)
# ...
# (Proprietary code in MobileSafari)
# ...
# WebKit::NavigationState::NavigationClient::decidePolicyForNavigationAction
# WebKit::WebPageProxy::decidePolicyForNavigationAction

For the 'https://' URL, the action policy passed to decisionHandlerWithPolicies is "_WKNavigationActionPolicyAllowWithoutTryingAppLink" and for the 'itmss://' URL it is "WKNavigationActionPolicyCancel" so the app link won't be open in any case.

Even if the action policy was "WKNavigationActionPolicyAllow", tryAppLink() would still exit early because navigationAction->shouldOpenAppLinks() is false. More precisely, WebPageProxy::decidePolicyForNavigationAction sets navigationAction.m_shouldOpenAppLink true but has navigationActionData.shouldOpenExternalURLsPolicy == ShouldNotAllow.

I'm not even sure what an "App Link" is but attachment 337779 [details] also fails if we directly use itmss://itunes.apple.com/us/app/id828256236 or itms-apps://itunes.apple.com/us/app/id828256236 to avoid the redirect. On the simulator, I also get the "Safari cannot open the page because the address is invalid" message if I directly open these two links.
Comment 7 Tim Horton 2018-04-30 09:13:25 PDT
Those are URLs with custom schemes, not app links (universal links: https://developer.apple.com/library/content/documentation/General/Conceptual/AppSearch/UniversalLinks.html).
Comment 8 Frédéric Wang (:fredw) 2019-01-29 03:11:32 PST
This bug still happens on iOS 12.2 beta 1 (January 24).

@Chris: I believe you recently modified the proprietary navigation code in order to fix 167341. Maybe you'll want to look into that one?