Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 17 additions & 9 deletions jsaddle-wkwebview/cbits-cocoa/AppDelegate.m
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@
#import <Cocoa/Cocoa.h>
#import <WebKit/WebKit.h>

extern void callIO(HsStablePtr);
extern bool callWithCStringReturningBool(const char * _Nonnull, HsStablePtr);
extern void callWithCString(const char * _Nonnull, HsStablePtr);
extern void callWithWebView(WKWebView *, HsStablePtr);
// The Haskell callbacks come through the registered jsaddleCallbacks table
// (see cbits/WKWebView-callbacks.h for why they are not extern symbols). The
// table itself is defined in WKWebView-cbits.m, which the composite Cocoa
// translation unit #includes before this file; the header is included here
// anyway (it is #pragma once) so this file does not depend on that order.
#include "../cbits/WKWebView-callbacks.h"

@interface AppDelegate : NSObject <NSApplicationDelegate>
@property (nonatomic, assign) IBOutlet NSWindow *window;
Expand Down Expand Up @@ -41,7 +43,9 @@ -(id)initApp:(HsStablePtr)handler progName:(NSString *)progName {
}

-(BOOL)application:(NSApplication *)sender openFile:(NSString *)filename {
bool b = callWithCStringReturningBool([filename UTF8String], global_applicationOpenFile);
bool b = jsaddleCallbacks.callWithCStringReturningBool
? jsaddleCallbacks.callWithCStringReturningBool([filename UTF8String], global_applicationOpenFile)
: false;
return b ? YES : NO;
}

Expand All @@ -52,8 +56,10 @@ -(void)applicationWillFinishLaunching:(NSNotification *)notification {
}
WKWebView *webView = [[WKWebView alloc] initWithFrame: [_window.contentView frame] configuration:theConfiguration];
[_window setContentView:webView];
callWithWebView(webView, _handler);
callIO(global_willFinishLaunchingWithOptions);
if (jsaddleCallbacks.callWithWebView)
jsaddleCallbacks.callWithWebView((void *)webView, _handler);
if (jsaddleCallbacks.callIO)
jsaddleCallbacks.callIO(global_willFinishLaunchingWithOptions);
// Listen to URL events
NSAppleEventManager *manager = [NSAppleEventManager sharedAppleEventManager];
[manager
Expand All @@ -65,14 +71,16 @@ -(void)applicationWillFinishLaunching:(NSNotification *)notification {

- (void)applicationUniversalLink:(NSAppleEventDescriptor *)event {
NSString *url = [[event paramDescriptorForKeyword:keyDirectObject] stringValue];
callWithCString([url UTF8String], global_applicationUniversalLink);
if (jsaddleCallbacks.callWithCString)
jsaddleCallbacks.callWithCString([url UTF8String], global_applicationUniversalLink);
}

-(void)applicationDidFinishLaunching:(NSNotification *)notification {
[_window orderFrontRegardless];
[_window center];
[NSApp activateIgnoringOtherApps:YES];
callIO(global_didFinishLaunchingWithOptions);
if (jsaddleCallbacks.callIO)
jsaddleCallbacks.callIO(global_didFinishLaunchingWithOptions);
}

-(BOOL) applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)app {
Expand Down
30 changes: 15 additions & 15 deletions jsaddle-wkwebview/cbits-uikit/AppDelegate.m
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@
#import <UserNotifications/UserNotifications.h>
#import <stdint.h>

extern void callIO(HsStablePtr);
extern void callWithCString(const char * _Nonnull, HsStablePtr);
extern void callWithCIntCString(int n, const char * _Nonnull, HsStablePtr);
// The Haskell callbacks come through the registered jsaddleCallbacks table
// (see cbits/WKWebView-callbacks.h for why they are not extern symbols).
#include "../cbits/WKWebView-callbacks.h"

@interface AppDelegate ()

Expand Down Expand Up @@ -35,7 +35,7 @@ @implementation AppDelegate

- (BOOL)application:(UIApplication *)application willFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Tells the delegate that the launch process has begun but that state restoration has not yet occurred.
callIO(global_willFinishLaunchingWithOptions);
if (jsaddleCallbacks.callIO) jsaddleCallbacks.callIO(global_willFinishLaunchingWithOptions);
return YES;
}

Expand Down Expand Up @@ -70,45 +70,45 @@ - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(
[application registerForRemoteNotifications];
}
}
callIO(global_didFinishLaunchingWithOptions);
if (jsaddleCallbacks.callIO) jsaddleCallbacks.callIO(global_didFinishLaunchingWithOptions);
return YES;
}

- (void)applicationWillResignActive:(UIApplication *)application {
// Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
// Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game.
callIO(global_applicationWillResignActive);
if (jsaddleCallbacks.callIO) jsaddleCallbacks.callIO(global_applicationWillResignActive);
}


- (void)applicationDidEnterBackground:(UIApplication *)application {
// Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
// If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
callIO(global_applicationDidEnterBackground);
if (jsaddleCallbacks.callIO) jsaddleCallbacks.callIO(global_applicationDidEnterBackground);
}


- (void)applicationWillEnterForeground:(UIApplication *)application {
// Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background.
callIO(global_applicationWillEnterForeground);
if (jsaddleCallbacks.callIO) jsaddleCallbacks.callIO(global_applicationWillEnterForeground);
}


- (void)applicationDidBecomeActive:(UIApplication *)application {
// Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
callIO(global_applicationDidBecomeActive);
if (jsaddleCallbacks.callIO) jsaddleCallbacks.callIO(global_applicationDidBecomeActive);
}


- (void)applicationWillTerminate:(UIApplication *)application {
// Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
callIO(global_applicationWillTerminate);
if (jsaddleCallbacks.callIO) jsaddleCallbacks.callIO(global_applicationWillTerminate);
}

- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken {
// Tells the delegate that the app successfully registered with Apple Push Notification service (APNs).
NSString *deviceTokenString = [deviceToken base64EncodedStringWithOptions: 0];
callWithCString([deviceTokenString UTF8String], global_didRegisterForRemoteNotificationsWithDeviceToken);
if (jsaddleCallbacks.callWithCString) jsaddleCallbacks.callWithCString([deviceTokenString UTF8String], global_didRegisterForRemoteNotificationsWithDeviceToken);
}

- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult result))completionHandler {
Expand All @@ -121,7 +121,7 @@ - (void)application:(UIApplication *)application didReceiveRemoteNotification:(N
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:userInfo options:0 error:&error];
if (jsonData) {
NSString *jsonString = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
callWithCIntCString((int) application.applicationState, [jsonString UTF8String], global_applicationDidReceiveRemoteNotification);
if (jsaddleCallbacks.callWithCIntCString) jsaddleCallbacks.callWithCIntCString((int) application.applicationState, [jsonString UTF8String], global_applicationDidReceiveRemoteNotification);
} else {
NSLog(@"jsaddle-wkwebview didReceiveRemoteNotification handler: error while serialising push notification as JSON");
}
Expand All @@ -131,19 +131,19 @@ - (void)application:(UIApplication *)application didReceiveRemoteNotification:(N
- (void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error {
// Sent to the delegate when Apple Push Notification service cannot successfully complete the registration process.
NSString *errorString = [error localizedDescription];
callWithCString([errorString UTF8String], global_didFailToRegisterForRemoteNotificationsWithError);
if (jsaddleCallbacks.callWithCString) jsaddleCallbacks.callWithCString([errorString UTF8String], global_didFailToRegisterForRemoteNotificationsWithError);
}

- (void)applicationSignificantTimeChange:(UIApplication *)application {
// Tells the delegate when there is a significant change in the time.
callIO(global_applicationSignificantTimeChange);
if (jsaddleCallbacks.callIO) jsaddleCallbacks.callIO(global_applicationSignificantTimeChange);
}

- (BOOL)application:(UIApplication *)application continueUserActivity:(NSUserActivity *)userActivity restorationHandler:(void (^)(NSArray *restorableObjects))restorationHandler {
// TODO: Reroute universal links when they're clicked in-app.
// https://developer.apple.com/reference/webkit/wknavigationdelegate/1455643-webview?language=objc
if ([userActivity.activityType isEqualToString:NSUserActivityTypeBrowsingWeb] && userActivity.webpageURL) {
callWithCString([userActivity.webpageURL.absoluteString UTF8String], global_applicationUniversalLink);
if (jsaddleCallbacks.callWithCString) jsaddleCallbacks.callWithCString([userActivity.webpageURL.absoluteString UTF8String], global_applicationUniversalLink);
return YES;
}
return NO;
Expand Down
5 changes: 3 additions & 2 deletions jsaddle-wkwebview/cbits-uikit/ViewController.m
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#import "ViewController.h"

extern void callWithWebView(WKWebView *, HsStablePtr);
// See cbits/WKWebView-callbacks.h: Haskell callbacks are registered, not extern.
#include "../cbits/WKWebView-callbacks.h"

@interface ViewController ()

Expand All @@ -27,7 +28,7 @@ - (void)loadView {

- (void)viewDidLoad {
[super viewDidLoad];
callWithWebView(_webView, _handler);
if (jsaddleCallbacks.callWithWebView) jsaddleCallbacks.callWithWebView((void *)_webView, _handler);
}


Expand Down
32 changes: 32 additions & 0 deletions jsaddle-wkwebview/cbits/WKWebView-callbacks.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// Haskell callbacks for the jsaddle-wkwebview Objective-C.
//
// These are registered at runtime by the Haskell side
// (jsaddle_wk_set_callbacks, called from
// Language.Javascript.JSaddle.WKWebView.Internal.registerJSaddleCallbacks
// before anything can fire a callback) rather than linked as extern foreign
// exports. On toolchains where GHCi's RTS linker loads this package's
// archive, the foreign exports are not dyld-visible — and the ObjC, which
// must then be loaded by dyld for its classes to be registered with the
// Objective-C runtime (e.g. compiled into a dylib and preloaded with
// ghci -L<dir> -l<name>; see the objc-in-library cabal flag), could not
// reference them by name. Function pointers created with
// `foreign import ccall "wrapper"` are callable from both worlds, and
// behave identically in an ordinarily compiled application.
#pragma once
#include "HsFFI.h"
#include <stdbool.h>

typedef struct {
void (*jsaddleStart)(HsStablePtr);
void (*jsaddleResult)(HsStablePtr, const char * _Nonnull);
void (*jsaddleSyncResult)(HsStablePtr, void * _Nonnull handler, const char * _Nonnull);
void (*callIO)(HsStablePtr);
void (*callWithCString)(const char * _Nonnull, HsStablePtr);
bool (*callWithCStringReturningBool)(const char * _Nonnull, HsStablePtr);
void (*callWithWebView)(void * _Nonnull webView, HsStablePtr);
void (*callWithCIntCString)(int, const char * _Nonnull, HsStablePtr);
} jsaddle_wk_callbacks;

// Defined in WKWebView-cbits.m (one definition; the iOS build compiles the
// sources as separate translation units, so it cannot be static there).
extern jsaddle_wk_callbacks jsaddleCallbacks;
37 changes: 31 additions & 6 deletions jsaddle-wkwebview/cbits/WKWebView-cbits.m
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,31 @@ - (instancetype)initHandler:(HsStablePtr)startHandler resultHandler:(HsStablePtr

@end

extern void jsaddleStart(HsStablePtr);
extern void jsaddleResult(HsStablePtr, const char * _Nonnull result);
extern void jsaddleSyncResult(HsStablePtr, JSaddleHandler *, const char * _Nonnull result);
// The Haskell callbacks come through the registered jsaddleCallbacks table —
// see WKWebView-callbacks.h for why they are not extern symbol references.
#include "WKWebView-callbacks.h"

jsaddle_wk_callbacks jsaddleCallbacks; // zero-initialised

void jsaddle_wk_set_callbacks(
void (*jsaddleStart_)(HsStablePtr),
void (*jsaddleResult_)(HsStablePtr, const char * _Nonnull),
void (*jsaddleSyncResult_)(HsStablePtr, void * _Nonnull, const char * _Nonnull),
void (*callIO_)(HsStablePtr),
void (*callWithCString_)(const char * _Nonnull, HsStablePtr),
bool (*callWithCStringReturningBool_)(const char * _Nonnull, HsStablePtr),
void (*callWithWebView_)(void * _Nonnull, HsStablePtr),
void (*callWithCIntCString_)(int, const char * _Nonnull, HsStablePtr))
{
jsaddleCallbacks.jsaddleStart = jsaddleStart_;
jsaddleCallbacks.jsaddleResult = jsaddleResult_;
jsaddleCallbacks.jsaddleSyncResult = jsaddleSyncResult_;
jsaddleCallbacks.callIO = callIO_;
jsaddleCallbacks.callWithCString = callWithCString_;
jsaddleCallbacks.callWithCStringReturningBool = callWithCStringReturningBool_;
jsaddleCallbacks.callWithWebView = callWithWebView_;
jsaddleCallbacks.callWithCIntCString = callWithCIntCString_;
}

@implementation JSaddleHandler

Expand All @@ -34,21 +56,24 @@ - (instancetype)initHandler:(HsStablePtr)startHandler resultHandler:(HsStablePtr

- (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation
{
jsaddleStart(self.startHandler);
if (jsaddleCallbacks.jsaddleStart)
jsaddleCallbacks.jsaddleStart(self.startHandler);
}

- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message
{
NSString * s = (NSString *) message.body;
jsaddleResult(self.resultHandler, [s UTF8String]);
if (jsaddleCallbacks.jsaddleResult)
jsaddleCallbacks.jsaddleResult(self.resultHandler, [s UTF8String]);
}

- (void)webView:(WKWebView *)webView runJavaScriptTextInputPanelWithPrompt:(NSString *)prompt
defaultText:(NSString *)defaultText initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(NSString *result))completionHandler
{
if([prompt isEqualToString:@"JSaddleSync"]) {
_completionHandler = completionHandler;
jsaddleSyncResult(self.syncHandler, self, [defaultText UTF8String]);
if (jsaddleCallbacks.jsaddleSyncResult)
jsaddleCallbacks.jsaddleSyncResult(self.syncHandler, (void *)self, [defaultText UTF8String]);
}
else {
// TODO decide what if anything should go here
Expand Down
41 changes: 30 additions & 11 deletions jsaddle-wkwebview/jsaddle-wkwebview.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,30 @@ tested-with: GHC==9.12.2, GHC==9.10.1, GHC==9.8.4, GHC==9.6.7, GHC==9.4.8, GHC==

-- #included by cbits-cocoa/WKWebView-AppDelegate.m (the single Cocoa
-- translation unit) rather than compiled directly, so they must be
-- shipped explicitly.
-- shipped explicitly. (WKWebView-callbacks.h is #included everywhere.)
extra-source-files:
cbits/WKWebView-cbits.m
cbits/WKWebView-callbacks.h
cbits-cocoa/AppDelegate.m

flag include-app-delegate
description: Include default AppDelegate C sources.
default: True

flag objc-in-library
description:
Compile the Objective-C into the Haskell library (the default).
Disable for GHCi sessions on toolchains where the RTS linker loads
this library's archive: Objective-C classes are only registered with
the runtime when their image is loaded by dyld, so such a session
must instead preload the ObjC compiled as a dylib (e.g.
cc -dynamiclib cbits-cocoa/WKWebView-AppDelegate.m -DUSE_COCOA
-Icbits ... and ghci -L<dir> -l<name>); the Haskell side then
reaches it through dyld, and it calls back in through the FunPtrs
registered by registerJSaddleCallbacks.
default: True
manual: True

source-repository head
type: git
location: https://github.com/ghcjs/jsaddle
Expand Down Expand Up @@ -67,14 +82,16 @@ library
if os(ios)
frameworks: UIKit, UserNotifications
if flag(include-app-delegate)
cxx-sources:
cbits/WKWebView-cbits.m
cbits-uikit/AppDelegate.m
cbits-uikit/ViewController.m
if flag(objc-in-library)
cxx-sources:
cbits/WKWebView-cbits.m
cbits-uikit/AppDelegate.m
cbits-uikit/ViewController.m
cpp-options: -DUSE_UIKIT
else
cxx-sources:
cbits/WKWebView-cbits.m
if flag(objc-in-library)
cxx-sources:
cbits/WKWebView-cbits.m
else
frameworks: Cocoa
if flag(include-app-delegate)
Expand All @@ -84,9 +101,11 @@ library
-- rejects the duplicate ObjC protocol metadata every ObjC
-- object emits, so the package's ObjC code must form one
-- archive member for dependents' TH splices to load it.
cxx-sources:
cbits-cocoa/WKWebView-AppDelegate.m
if flag(objc-in-library)
cxx-sources:
cbits-cocoa/WKWebView-AppDelegate.m
cpp-options: -DUSE_COCOA
else
cxx-sources:
cbits/WKWebView-cbits.m
if flag(objc-in-library)
cxx-sources:
cbits/WKWebView-cbits.m
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ import qualified Data.Set as Set
import Data.Word
import Foreign.Marshal.Utils (fromBool)
import Language.Javascript.JSaddle.WKWebView.Internal
(jsaddleMain, jsaddleMainHTMLWithBaseURL, jsaddleMainFile, WKWebView(..), mainBundleResourcePath)
(jsaddleMain, jsaddleMainHTMLWithBaseURL, jsaddleMainFile, WKWebView(..),
mainBundleResourcePath, registerJSaddleCallbacks)
import System.Environment (getProgName)
import Foreign.C.String (CString, withCString)
import Foreign.C.Types (CBool, CInt)
Expand Down Expand Up @@ -163,6 +164,9 @@ run' :: AppDelegateConfig
-> (WKWebView -> IO ())
-> IO ()
run' cfg main = do
-- The ObjC side calls Haskell through registered FunPtrs (see Internal);
-- register before runInWKWebView can fire any of them.
registerJSaddleCallbacks
handler <- newStablePtr main
progName <- getProgName

Expand Down
Loading
Loading