-
Notifications
You must be signed in to change notification settings - Fork 955
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: Implement the webview sandbox using a microfront end for rn to …
…implement the full html renderer (#2691) * init Signed-off-by: Innei <tukon479@gmail.com> * feat: parser and renderer Signed-off-by: Innei <tukon479@gmail.com> * feat: shared webview * feat: add UIColor hex conversion extension and accent color asset and xhr rewrite * feat(mobile): enhance WebView and HTML rendering for iOS - Refactor SharedWebView module with improved JavaScript injection and link handling - Add ModalWebViewController for opening links in a modal view - Implement dynamic content height measurement - Update HTML renderer with Jotai state management - Improve WebView configuration and debug mode Signed-off-by: Innei <tukon479@gmail.com> * lockfile Signed-off-by: Innei <tukon479@gmail.com> * chore: auto-fix linting and formatting issues * fix(mobile): improve WebView debug mode and UI text styling - Remove debug-only conditional for WebView inspector - Add text label class to GroupedInsetListActionCell for consistent styling Signed-off-by: Innei <tukon479@gmail.com> * update Signed-off-by: Innei <tukon479@gmail.com> * feat(mobile): add FullScreenWKWebView for iOS WebView layout - Create custom WKWebView subclass to override safe area insets - Enables full-screen WebView rendering without default padding Signed-off-by: Innei <tukon479@gmail.com> * chore(mobile): remove FullScreenWKWebView Swift file - Delete unused custom WKWebView subclass for iOS - Cleanup unnecessary file after previous implementation Signed-off-by: Innei <tukon479@gmail.com> * feat(mobile): refactor iOS WebView infrastructure Signed-off-by: Innei <tukon479@gmail.com> * feat(mobile): add native helper module and improve entry detail screen - Add HelperModule for iOS to open links in a modal WebView - Implement cross-platform link opening mechanism - Enhance entry detail screen with dynamic title and scroll behavior - Add loading indicator for entry content - Refactor navigation and scroll view components Signed-off-by: Innei <tukon479@gmail.com> * fix(mobile): improve WebView JavaScript and URL loading thread safety - Dispatch JavaScript evaluation and URL loading on the main thread - Ensure thread-safe WebView interactions for iOS - Prevent potential race conditions in SharedWebViewModule Signed-off-by: Innei <tukon479@gmail.com> * refactor(mobile): remove useOpenLink hook and update link opening mechanism - Replace custom useOpenLink hook with native openLink function - Update WebView navigation and recommendation list item to use new link opening method - Remove unnecessary hook and simplify link handling Signed-off-by: Innei <tukon479@gmail.com> --------- Signed-off-by: Innei <tukon479@gmail.com>
- Loading branch information
Showing
65 changed files
with
1,674 additions
and
292 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
// | ||
// HelperModule.swift | ||
// Pods | ||
// | ||
// Created by Innei on 2025/2/7. | ||
// | ||
import ExpoModulesCore | ||
|
||
public class HelperModule: Module { | ||
public func definition() -> ExpoModulesCore.ModuleDefinition { | ||
Name("Helper") | ||
|
||
Function("openLink") { (urlString: String) in | ||
guard let url = URL(string: urlString) else { | ||
return | ||
} | ||
DispatchQueue.main.async { | ||
guard let rootVC = UIApplication.shared.windows.first?.rootViewController else { return } | ||
WebViewManager.presentModalWebView(url: url, from: rootVC) | ||
} | ||
|
||
} | ||
} | ||
} |
38 changes: 38 additions & 0 deletions
38
apps/mobile/native/ios/Media.xcassets/Accent.colorset/Contents.json
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
{ | ||
"colors": [ | ||
{ | ||
"color": { | ||
"color-space": "srgb", | ||
"components": { | ||
"alpha": "1.000", | ||
"blue": "0x00", | ||
"green": "0x5C", | ||
"red": "0xFF" | ||
} | ||
}, | ||
"idiom": "universal" | ||
}, | ||
{ | ||
"appearances": [ | ||
{ | ||
"appearance": "luminosity", | ||
"value": "dark" | ||
} | ||
], | ||
"color": { | ||
"color-space": "srgb", | ||
"components": { | ||
"alpha": "1.000", | ||
"blue": "0x00", | ||
"green": "0x5C", | ||
"red": "0xFF" | ||
} | ||
}, | ||
"idiom": "universal" | ||
} | ||
], | ||
"info": { | ||
"author": "xcode", | ||
"version": 1 | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
{ | ||
"info": { | ||
"author": "xcode", | ||
"version": 1 | ||
} | ||
} |
90 changes: 90 additions & 0 deletions
90
apps/mobile/native/ios/SharedWebView/CustomURLSchemeHandler.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,90 @@ | ||
// | ||
// CustomURLSchemeHandler.swift | ||
// Pods | ||
// | ||
// Created by Innei on 2025/2/7. | ||
// | ||
|
||
import WebKit | ||
import Foundation | ||
|
||
|
||
class CustomURLSchemeHandler: NSObject, WKURLSchemeHandler { | ||
static let rewriteScheme = "follow-xhr" | ||
private let queue = DispatchQueue(label: "com.follow.urlschemehandler") | ||
private var activeTasks: [String: URLSessionDataTask] = [:] | ||
|
||
func webView(_ webView: WKWebView, start urlSchemeTask: WKURLSchemeTask) { | ||
guard let url = urlSchemeTask.request.url, | ||
let originalURLString = url.absoluteString.replacingOccurrences( | ||
of: CustomURLSchemeHandler.rewriteScheme, with: "https" | ||
).removingPercentEncoding, | ||
let originalURL = URL(string: originalURLString) | ||
else { | ||
urlSchemeTask.didFailWithError(NSError(domain: "", code: -1)) | ||
return | ||
} | ||
|
||
var request = URLRequest(url: originalURL) | ||
|
||
request.httpMethod = urlSchemeTask.request.httpMethod | ||
request.httpBody = urlSchemeTask.request.httpBody | ||
|
||
// setting headers | ||
var headers = urlSchemeTask.request.allHTTPHeaderFields ?? [:] | ||
if let urlComponents = URLComponents(url: originalURL, resolvingAgainstBaseURL: false), | ||
let scheme = urlComponents.scheme, | ||
let host = urlComponents.host | ||
{ | ||
let origin = "\(scheme)://\(host)\(urlComponents.port.map { ":\($0)" } ?? "")" | ||
headers["Referer"] = origin | ||
headers["Origin"] = origin | ||
|
||
} | ||
request.allHTTPHeaderFields = headers | ||
|
||
let taskID = urlSchemeTask.description | ||
|
||
let task = URLSession.shared.dataTask(with: request) { [weak self] data, response, error in | ||
guard let self = self else { return } | ||
|
||
self.queue.sync { | ||
// Check if task is still active | ||
guard self.activeTasks[taskID] != nil else { return } | ||
|
||
if let error = error { | ||
urlSchemeTask.didFailWithError(error) | ||
self.activeTasks.removeValue(forKey: taskID) | ||
return | ||
} | ||
|
||
if let response = response as? HTTPURLResponse, let data = data { | ||
do { | ||
urlSchemeTask.didReceive(response) | ||
urlSchemeTask.didReceive(data) | ||
urlSchemeTask.didFinish() | ||
} catch { | ||
// Ignore errors that might occur if task was stopped | ||
print("Error completing URL scheme task: \(error)") | ||
} | ||
} | ||
self.activeTasks.removeValue(forKey: taskID) | ||
} | ||
} | ||
queue.sync { | ||
activeTasks[taskID] = task | ||
} | ||
|
||
task.resume() | ||
} | ||
|
||
func webView(_ webView: WKWebView, stop urlSchemeTask: WKURLSchemeTask) { | ||
let taskID = urlSchemeTask.description | ||
queue.sync { | ||
if let task = activeTasks[taskID] { | ||
task.cancel() | ||
activeTasks.removeValue(forKey: taskID) | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
// | ||
// FOWebView.swift | ||
// FollowNative | ||
// | ||
// Created by Innei on 2025/2/7. | ||
// | ||
|
||
import WebKit | ||
|
||
class FOWebView: WKWebView { | ||
private func setupView() { | ||
scrollView.isScrollEnabled = false | ||
scrollView.bounces = false | ||
scrollView.contentInsetAdjustmentBehavior = .never | ||
|
||
isOpaque = false | ||
backgroundColor = UIColor.clear | ||
scrollView.backgroundColor = UIColor.clear | ||
tintColor = Utils.accentColor | ||
|
||
if #available(iOS 16.4, *) { | ||
isInspectable = true | ||
} | ||
|
||
} | ||
|
||
override init(frame: CGRect, configuration: WKWebViewConfiguration) { | ||
super.init(frame: frame, configuration: configuration) | ||
setupView() | ||
} | ||
|
||
required init?(coder: NSCoder) { | ||
fatalError("init(coder:) has not been implemented") | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
// | ||
// at_end.js | ||
// Pods | ||
// | ||
// Created by Innei on 2025/2/6. | ||
// | ||
|
||
;(() => { | ||
const root = document.querySelector("#root") | ||
const handleHeight = () => { | ||
window.webkit.messageHandlers.message.postMessage( | ||
JSON.stringify({ | ||
type: "setContentHeight", | ||
payload: root.scrollHeight, | ||
}), | ||
) | ||
} | ||
window.addEventListener("load", handleHeight) | ||
const observer = new ResizeObserver(handleHeight) | ||
|
||
setTimeout(() => { | ||
handleHeight() | ||
}, 1000) | ||
observer.observe(root) | ||
})() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
// | ||
// at_start.js | ||
// Pods | ||
// | ||
// Created by Innei on 2025/2/6. | ||
// | ||
;(() => { | ||
window.__RN__ = true | ||
|
||
function send(data) { | ||
window.webkit.messageHandlers.message.postMessage?.(JSON.stringify(data)) | ||
} | ||
|
||
Object.assign(window.webkit, { | ||
measure: () => { | ||
send({ | ||
type: "measure", | ||
}) | ||
}, | ||
setContentHeight: (height) => { | ||
send({ | ||
type: "setContentHeight", | ||
payload: height, | ||
}) | ||
}, | ||
}) | ||
})() |
Oops, something went wrong.