mirror of
https://github.com/Codeux-Software/Textual.git
synced 2026-06-16 13:24:32 +00:00
1637 lines
46 KiB
Objective-C
Executable File
1637 lines
46 KiB
Objective-C
Executable File
/* *********************************************************************
|
|
* _____ _ _
|
|
* |_ _|____ _| |_ _ _ __ _| |
|
|
* | |/ _ \ \/ / __| | | |/ _` | |
|
|
* | | __/> <| |_| |_| | (_| | |
|
|
* |_|\___/_/\_\\__|\__,_|\__,_|_|
|
|
*
|
|
* Copyright (c) 2008 - 2010 Satoshi Nakagawa <psychs AT limechat DOT net>
|
|
* Copyright (c) 2010 - 2020 Codeux Software, LLC & respective contributors.
|
|
* Please see Acknowledgements.pdf for additional information.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions
|
|
* are met:
|
|
*
|
|
* * Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
* * Redistributions in binary form must reproduce the above copyright
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
* documentation and/or other materials provided with the distribution.
|
|
* * Neither the name of Textual, "Codeux Software, LLC", nor the
|
|
* names of its contributors may be used to endorse or promote products
|
|
* derived from this software without specific prior written permission.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
|
|
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
|
|
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
|
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
|
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
|
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
|
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
|
* SUCH DAMAGE.
|
|
*
|
|
*********************************************************************** */
|
|
|
|
#include <objc/message.h>
|
|
|
|
#import "GTMEncodeHTML.h"
|
|
#import "WebScriptObjectHelperPrivate.h"
|
|
#import "NSObjectHelperPrivate.h"
|
|
#import "TXMasterController.h"
|
|
#import "TPCPreferencesLocal.h"
|
|
#import "TPCThemeController.h"
|
|
#import "TPCTheme.h"
|
|
#import "THOPluginDispatcherPrivate.h"
|
|
#import "THOPluginManagerPrivate.h"
|
|
#import "THOPluginProtocolPrivate.h"
|
|
#import "IRCClient.h"
|
|
#import "IRCChannel.h"
|
|
#import "IRCUserNicknameColorStyleGeneratorPrivate.h"
|
|
#import "IRCWorld.h"
|
|
#import "TVCMainWindow.h"
|
|
#import "TVCLogControllerPrivate.h"
|
|
#import "TVCLogPolicyPrivate.h"
|
|
#import "TVCLogRenderer.h"
|
|
#import "TVCLogViewPrivate.h"
|
|
#import "TVCLogViewInternalWK1.h"
|
|
#import "TVCLogViewInternalWK2.h"
|
|
#import "TVCLogScriptEventSinkPrivate.h"
|
|
|
|
NS_ASSUME_NONNULL_BEGIN
|
|
|
|
@interface TVCLogScriptEventSink ()
|
|
@property (nonatomic, weak) TVCLogView *webView;
|
|
@end
|
|
|
|
@interface TVCLogScriptEventSinkContext : NSObject
|
|
@property (nonatomic, weak) TVCLogView *webView;
|
|
@property (readonly) TVCLogPolicy *webViewPolicy;
|
|
@property (readonly) TVCLogController *viewController;
|
|
@property (readonly) IRCClient *associatedClient;
|
|
@property (readonly, nullable) IRCChannel *associatedChannel;
|
|
@property (nonatomic, copy) NSString *caller;
|
|
@property (nonatomic, copy, nullable) NSArray *arguments;
|
|
@property (nonatomic, copy, nullable) void (^completionBlock)(id _Nullable returnValue);
|
|
@end
|
|
|
|
@implementation TVCLogScriptEventSink
|
|
|
|
- (instancetype)init
|
|
{
|
|
return [self initWithWebView:nil];
|
|
}
|
|
|
|
- (instancetype)initWithWebView:(nullable TVCLogView *)webView
|
|
{
|
|
if ((self = [super init])) {
|
|
self.webView = webView;
|
|
|
|
return self;
|
|
}
|
|
|
|
return nil;
|
|
}
|
|
|
|
+ (BOOL)isSelectorExcludedFromWebScript:(SEL)selector
|
|
{
|
|
if (selector == @selector(init) ||
|
|
selector == @selector(initWithWebView:) ||
|
|
selector == @selector(webView) ||
|
|
selector == @selector(webViewPolicy) ||
|
|
selector == @selector(associatedClient) ||
|
|
selector == @selector(associatedChannel) ||
|
|
selector == @selector(objectValueToCommon:) ||
|
|
selector == @selector(userContentController:didReceiveScriptMessage:) ||
|
|
selector == @selector(processInputData:inWebView:withSelector:) ||
|
|
selector == @selector(processInputData:inWebView:withSelector:minimumArgumentCount:withValidation:))
|
|
{
|
|
return YES;
|
|
}
|
|
|
|
if ([NSStringFromSelector(selector) hasPrefix:@"_"]) {
|
|
return NO;
|
|
}
|
|
|
|
return NO;
|
|
}
|
|
|
|
+ (nullable NSString *)webScriptNameForSelector:(SEL)sel
|
|
{
|
|
return nil;
|
|
}
|
|
|
|
- (id)invokeUndefinedMethodFromWebScript:(NSString *)name withArguments:(NSArray *)arguments
|
|
{
|
|
SEL handlerSelector = NSSelectorFromString([name stringByAppendingString:@":inWebView:"]);
|
|
|
|
if ([self respondsToSelector:handlerSelector] == NO) {
|
|
return @(NO);
|
|
}
|
|
|
|
id argument = nil;
|
|
|
|
if (arguments && arguments.count > 0) {
|
|
argument = arguments[0];
|
|
|
|
TEXTUAL_IGNORE_WEBKIT_DEPRECATIONS_BEGIN
|
|
if ([argument isKindOfClass:[WebScriptObject class]]) {
|
|
argument = [self.webView webScriptObjectToCommon:argument];
|
|
}
|
|
TEXTUAL_IGNORE_WEBKIT_DEPRECATIONS_END
|
|
}
|
|
|
|
NSMethodSignature *signature = [self methodSignatureForSelector:handlerSelector];
|
|
|
|
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
|
|
|
|
[invocation setTarget:self];
|
|
|
|
[invocation setSelector:handlerSelector];
|
|
[invocation setArgument:&argument atIndex:2];
|
|
|
|
TVCLogView *webView = self.webView;
|
|
[invocation setArgument:&webView atIndex:3];
|
|
|
|
[invocation invoke];
|
|
|
|
return @(YES);
|
|
}
|
|
|
|
+ (BOOL)isKeyExcludedFromWebScript:(const char *)name
|
|
{
|
|
return YES;
|
|
}
|
|
|
|
+ (nullable NSString *)webScriptNameForKey:(const char *)name
|
|
{
|
|
return nil;
|
|
}
|
|
|
|
+ (nullable id)objectValueToCommon:(id)object
|
|
{
|
|
if ([object isKindOfClass:[NSNull class]] ||
|
|
TEXTUAL_IGNORE_WEBKIT_DEPRECATIONS_BEGIN
|
|
[object isKindOfClass:[WebUndefined class]])
|
|
TEXTUAL_IGNORE_WEBKIT_DEPRECATIONS_END
|
|
{
|
|
return nil;
|
|
}
|
|
|
|
if ([object isKindOfClass:[NSString class]]) {
|
|
return [object gtm_stringByUnescapingFromHTML];
|
|
}
|
|
|
|
return object;
|
|
}
|
|
|
|
+ (NSString *)standardizeLineNumber:(NSString *)lineNumber
|
|
{
|
|
NSParameterAssert(lineNumber != nil);
|
|
|
|
if ([lineNumber hasPrefix:@"line-"]) {
|
|
return [lineNumber substringFromIndex:5];
|
|
}
|
|
|
|
return lineNumber;
|
|
}
|
|
|
|
+ (NSArray<NSString *> *)standardizeLineNumbers:(NSArray<NSString *> *)lineNumbers
|
|
{
|
|
NSParameterAssert(lineNumbers != nil);
|
|
|
|
NSMutableArray<NSString *> *lineNumbersOut = [NSMutableArray arrayWithCapacity:lineNumbers.count];
|
|
|
|
for (NSString *lineNumber in lineNumbers) {
|
|
[lineNumbersOut addObject:
|
|
[self standardizeLineNumber:lineNumber]];
|
|
}
|
|
|
|
return [lineNumbersOut copy];
|
|
}
|
|
|
|
- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message
|
|
{
|
|
NSString *handlerName = message.name;
|
|
|
|
SEL handlerSelector = NSSelectorFromString([handlerName stringByAppendingString:@":inWebView:"]);
|
|
|
|
if ([self respondsToSelector:handlerSelector] == NO) {
|
|
return;
|
|
}
|
|
|
|
if ([self.class isSelectorExcludedFromWebScript:handlerSelector]) {
|
|
return;
|
|
}
|
|
|
|
NSMethodSignature *signature = [self methodSignatureForSelector:handlerSelector];
|
|
|
|
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
|
|
|
|
[invocation setTarget:self];
|
|
[invocation setSelector:handlerSelector];
|
|
|
|
id body = message.body;
|
|
[invocation setArgument:&body atIndex:2];
|
|
|
|
WKWebView *webView = message.webView;
|
|
[invocation setArgument:&webView atIndex:3];
|
|
|
|
[invocation invoke];
|
|
}
|
|
|
|
- (void)processInputData:(id)inputData
|
|
forCaller:(NSString *)caller
|
|
inWebView:(id)webView
|
|
withSelector:(SEL)selector
|
|
{
|
|
[self processInputData:inputData
|
|
forCaller:caller
|
|
inWebView:webView
|
|
withSelector:selector
|
|
minimumArgumentCount:0
|
|
withValidation:nil];
|
|
}
|
|
|
|
- (void)processInputData:(id)inputData
|
|
forCaller:(NSString *)caller
|
|
inWebView:(id)webView
|
|
withSelector:(SEL)selector
|
|
minimumArgumentCount:(NSUInteger)minimumArgumentCount
|
|
withValidation:(BOOL (NS_NOESCAPE ^ _Nullable)(NSUInteger argumentIndex, id argument))validateArgumentBlock
|
|
{
|
|
TVCLogView *intWebView = nil;
|
|
|
|
if ([webView isKindOfClass:[TVCLogView class]]) {
|
|
intWebView = webView;
|
|
} else if ([webView isKindOfClass:[TVCLogViewInternalWK1 class]] ||
|
|
[webView isKindOfClass:[TVCLogViewInternalWK2 class]])
|
|
{
|
|
intWebView = [webView t_parentView];
|
|
} else {
|
|
return;
|
|
}
|
|
|
|
/* -t_parentView in TVCLogViewInternalWK2 is maintained
|
|
as a weak reference. That means that a race condition for
|
|
WebKit2 is possible. A script event is received, is invoked
|
|
on this function, and while it is waiting to be invoked,
|
|
another thread deconstructs the parent view. */
|
|
if (intWebView == nil) {
|
|
LogToConsoleFault("(intWebView == nil) condition faulted. \
|
|
Possible race condition. \
|
|
Invoking '%{public}@'", NSStringFromSelector(selector));
|
|
|
|
return;
|
|
}
|
|
|
|
NSInteger promiseIndex = (-1);
|
|
|
|
NSArray *values = nil;
|
|
|
|
/* Extract relevant information from inputData */
|
|
if ([inputData isKindOfClass:[NSDictionary class]]) {
|
|
/* Check that the object exists in the dictionary before
|
|
setting the value. If the object does not exist and we
|
|
do not do this, then -integerValue will return 0 which
|
|
is considered a valid promiseIndex value. */
|
|
id promiseIndexObj = [inputData valueForKey:@"promiseIndex"];
|
|
|
|
if (promiseIndexObj) {
|
|
if ([promiseIndexObj isKindOfClass:[NSNumber class]] == NO) {
|
|
[self.class throwJavaScriptException:@"'promiseIndex' must be a number"
|
|
forCaller:caller
|
|
inWebView:intWebView];
|
|
|
|
return;
|
|
}
|
|
|
|
promiseIndex = [promiseIndexObj integerValue];
|
|
}
|
|
|
|
/* Values should always be in an array */
|
|
if (minimumArgumentCount > 0) {
|
|
id valuesObj = [inputData valueForKey:@"values"];
|
|
|
|
if (valuesObj == nil || [valuesObj isKindOfClass:[NSArray class]] == NO) {
|
|
[self.class throwJavaScriptException:@"'values' must be an array"
|
|
forCaller:caller
|
|
inWebView:intWebView];
|
|
|
|
return;
|
|
} else {
|
|
values = valuesObj;
|
|
}
|
|
}
|
|
}
|
|
else if ([inputData isKindOfClass:[NSString class]] ||
|
|
[inputData isKindOfClass:[NSNumber class]])
|
|
{
|
|
if (minimumArgumentCount > 0) {
|
|
values = @[inputData];
|
|
}
|
|
}
|
|
else if ([inputData isKindOfClass:[NSArray class]])
|
|
{
|
|
if (minimumArgumentCount > 0) {
|
|
values = inputData;
|
|
}
|
|
}
|
|
else if ([inputData isKindOfClass:[NSNull class]] ||
|
|
TEXTUAL_IGNORE_WEBKIT_DEPRECATIONS_BEGIN
|
|
[inputData isKindOfClass:[WebUndefined class]])
|
|
TEXTUAL_IGNORE_WEBKIT_DEPRECATIONS_END
|
|
{
|
|
if (minimumArgumentCount > 0) {
|
|
values = @[[NSNull null]];
|
|
}
|
|
}
|
|
|
|
/* Perform validation if needed */
|
|
if (minimumArgumentCount > 0 && values.count < minimumArgumentCount) {
|
|
[self.class throwJavaScriptException:@"Minimum number of arguments (%lu) condition not met"
|
|
forCaller:caller
|
|
inWebView:intWebView, minimumArgumentCount];
|
|
|
|
return;
|
|
}
|
|
|
|
if (validateArgumentBlock) {
|
|
__block BOOL validationPassed = YES;
|
|
|
|
[values enumerateObjectsUsingBlock:^(id object, NSUInteger index, BOOL *stop) {
|
|
if (validateArgumentBlock(index, object) == NO) {
|
|
validationPassed = NO;
|
|
|
|
*stop = YES;
|
|
}
|
|
}];
|
|
|
|
if (validationPassed == NO) {
|
|
[self.class throwJavaScriptException:@"Invalid argument type(s)"
|
|
forCaller:caller
|
|
inWebView:intWebView];
|
|
|
|
return;
|
|
}
|
|
}
|
|
|
|
/* Pass validated data to selector */
|
|
TVCLogScriptEventSinkContext *context = [TVCLogScriptEventSinkContext new];
|
|
|
|
context.webView = intWebView;
|
|
|
|
context.caller = caller;
|
|
|
|
context.arguments = values;
|
|
|
|
void (^completionBlock)(id) = nil;
|
|
|
|
if (promiseIndex >= 0) {
|
|
__weak typeof(intWebView) intWebViewWeak = intWebView;
|
|
|
|
completionBlock = ^(id _Nullable returnValue) {
|
|
if (returnValue == nil) {
|
|
returnValue = [NSNull null];
|
|
}
|
|
|
|
[intWebViewWeak evaluateFunction:@"appInternal.promiseKept"
|
|
withArguments:@[@(promiseIndex), returnValue]];
|
|
};
|
|
}
|
|
|
|
context.completionBlock = completionBlock;
|
|
|
|
NSMethodSignature *signature = [self methodSignatureForSelector:selector];
|
|
|
|
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
|
|
|
|
[invocation setTarget:self];
|
|
[invocation setSelector:selector];
|
|
|
|
[invocation setArgument:&context atIndex:2];
|
|
|
|
[invocation invoke];
|
|
}
|
|
|
|
+ (void)logToJavaScriptConsole:(NSString *)message inWebView:(TVCLogView *)webView, ...
|
|
{
|
|
NSParameterAssert(message != nil);
|
|
NSParameterAssert(webView != nil);
|
|
|
|
va_list arguments;
|
|
va_start(arguments, webView);
|
|
|
|
[self logToJavaScriptConsole:message inWebView:webView withArguments:arguments];
|
|
|
|
va_end(arguments);
|
|
}
|
|
|
|
+ (void)logToJavaScriptConsole:(NSString *)message inWebView:(TVCLogView *)webView withArguments:(va_list)arguments
|
|
{
|
|
NSParameterAssert(message != nil);
|
|
NSParameterAssert(webView != nil);
|
|
NSParameterAssert(arguments != NULL);
|
|
|
|
NSString *messageFormatted = [[NSString alloc] initWithFormat:message arguments:arguments];
|
|
|
|
[webView evaluateFunction:@"console.log" withArguments:@[messageFormatted]];
|
|
}
|
|
|
|
+ (void)throwJavaScriptException:(NSString *)message inWebView:(TVCLogView *)webView, ...
|
|
{
|
|
NSParameterAssert(message != nil);
|
|
NSParameterAssert(webView != nil);
|
|
|
|
va_list arguments;
|
|
va_start(arguments, webView);
|
|
|
|
[self throwJavaScriptException:message
|
|
forCaller:nil
|
|
inWebView:webView
|
|
withArguments:arguments];
|
|
|
|
va_end(arguments);
|
|
}
|
|
|
|
+ (void)throwJavaScriptException:(NSString *)message forCaller:(nullable NSString *)caller inWebView:(TVCLogView *)webView, ...
|
|
{
|
|
NSParameterAssert(message != nil);
|
|
NSParameterAssert(webView != nil);
|
|
|
|
va_list arguments;
|
|
va_start(arguments, webView);
|
|
|
|
[self throwJavaScriptException:message
|
|
forCaller:caller
|
|
inWebView:webView
|
|
withArguments:arguments];
|
|
|
|
va_end(arguments);
|
|
}
|
|
|
|
+ (void)throwJavaScriptException:(NSString *)message forCaller:(nullable NSString *)caller inWebView:(TVCLogView *)webView withArguments:(va_list)arguments
|
|
{
|
|
NSParameterAssert(message != nil);
|
|
NSParameterAssert(webView != nil);
|
|
NSParameterAssert(arguments != NULL);
|
|
|
|
NSString *messageFormatted = [[NSString alloc] initWithFormat:message arguments:arguments];
|
|
|
|
if (caller) {
|
|
messageFormatted = [NSString stringWithFormat:@"Bridged function %@ returned error: %@", caller, messageFormatted];
|
|
}
|
|
|
|
[webView evaluateFunction:@"console.error" withArguments:@[messageFormatted]];
|
|
}
|
|
|
|
#pragma mark -
|
|
#pragma mark Private Implementation
|
|
|
|
- (void)channelIsActive:(id)inputData inWebView:(id)webView
|
|
{
|
|
[self processInputData:inputData
|
|
forCaller:@"app.channelIsActive()"
|
|
inWebView:webView
|
|
withSelector:@selector(_channelIsActive:)];
|
|
}
|
|
|
|
- (void)channelMemberCount:(id)inputData inWebView:(id)webView
|
|
{
|
|
[self processInputData:inputData
|
|
forCaller:@"app.channelMemberCount()"
|
|
inWebView:webView
|
|
withSelector:@selector(_channelMemberCount:)];
|
|
}
|
|
|
|
- (void)channelName:(id)inputData inWebView:(id)webView
|
|
{
|
|
[self processInputData:inputData
|
|
forCaller:@"app.channelName()"
|
|
inWebView:webView
|
|
withSelector:@selector(_channelName:)];
|
|
}
|
|
|
|
- (void)channelNameDoubleClicked:(id)inputData inWebView:(id)webView
|
|
{
|
|
[self processInputData:inputData
|
|
forCaller:@"app.channelNameDoubleClicked()"
|
|
inWebView:webView
|
|
withSelector:@selector(_channelNameDoubleClicked:)];
|
|
}
|
|
|
|
- (void)displayContextMenu:(id)inputData inWebView:(id)webView
|
|
{
|
|
[self processInputData:inputData
|
|
forCaller:@"app.displayContextMenu()"
|
|
inWebView:webView
|
|
withSelector:@selector(_displayContextMenu:)];
|
|
}
|
|
|
|
- (void)copySelectionWhenPermitted:(id)inputData inWebView:(id)webView
|
|
{
|
|
[self processInputData:inputData
|
|
forCaller:@"app.copySelectionWhenPermitted()"
|
|
inWebView:webView
|
|
withSelector:@selector(_copySelectionWhenPermitted:)];
|
|
}
|
|
|
|
- (void)encryptionAuthenticateUser:(id)inputData inWebView:(id)webView
|
|
{
|
|
[self processInputData:inputData
|
|
forCaller:@"app.encryptionAuthenticateUser()"
|
|
inWebView:webView
|
|
withSelector:@selector(_encryptionAuthenticateUser:)];
|
|
}
|
|
|
|
- (void)inlineMediaEnabledForView:(id)inputData inWebView:(id)webView
|
|
{
|
|
[self processInputData:inputData
|
|
forCaller:@"app.inlineMediaEnabledForView()"
|
|
inWebView:webView
|
|
withSelector:@selector(_inlineMediaEnabledForView:)];
|
|
}
|
|
|
|
- (void)loadInlineMedia:(id)inputData inWebView:(id)webView
|
|
{
|
|
[self processInputData:inputData
|
|
forCaller:@"app.loadInlineMedia()"
|
|
inWebView:webView
|
|
withSelector:@selector(_loadInlineMedia:)
|
|
minimumArgumentCount:4
|
|
withValidation:^BOOL(NSUInteger argumentIndex, id argument) {
|
|
if (argumentIndex <= 2) {
|
|
return [argument isKindOfClass:[NSString class]];
|
|
} else {
|
|
return [argument isKindOfClass:[NSNumber class]];
|
|
}
|
|
}];
|
|
}
|
|
|
|
- (void)localUserHostmask:(id)inputData inWebView:(id)webView
|
|
{
|
|
[self processInputData:inputData
|
|
forCaller:@"app.localUserHostmask()"
|
|
inWebView:webView
|
|
withSelector:@selector(_localUserHostmask:)];
|
|
}
|
|
|
|
- (void)localUserNickname:(id)inputData inWebView:(id)webView
|
|
{
|
|
[self processInputData:inputData
|
|
forCaller:@"app.localUserNickname()"
|
|
inWebView:webView
|
|
withSelector:@selector(_localUserNickname:)];
|
|
}
|
|
|
|
- (void)logToConsole:(id)inputData inWebView:(id)webView
|
|
{
|
|
[self processInputData:inputData
|
|
forCaller:@"app.logToConsole()"
|
|
inWebView:webView
|
|
withSelector:@selector(_logToConsole:)
|
|
minimumArgumentCount:1
|
|
withValidation:^BOOL(NSUInteger argumentIndex, id argument) {
|
|
return [argument isKindOfClass:[NSString class]];
|
|
}];
|
|
}
|
|
|
|
- (void)networkName:(id)inputData inWebView:(id)webView
|
|
{
|
|
[self processInputData:inputData
|
|
forCaller:@"app.networkName()"
|
|
inWebView:webView
|
|
withSelector:@selector(_networkName:)];
|
|
}
|
|
|
|
- (void)nicknameColorStyleHash:(id)inputData inWebView:(id)webView
|
|
{
|
|
[self processInputData:inputData
|
|
forCaller:@"app.nicknameColorStyleHash()"
|
|
inWebView:webView
|
|
withSelector:@selector(_nicknameColorStyleHash:)
|
|
minimumArgumentCount:2
|
|
withValidation:^BOOL(NSUInteger argumentIndex, id argument) {
|
|
return [argument isKindOfClass:[NSString class]];
|
|
}];
|
|
}
|
|
|
|
- (void)nicknameDoubleClicked:(id)inputData inWebView:(id)webView
|
|
{
|
|
[self processInputData:inputData
|
|
forCaller:@"app.nicknameDoubleClicked()"
|
|
inWebView:webView
|
|
withSelector:@selector(_nicknameDoubleClicked:)];
|
|
}
|
|
|
|
- (void)notifyJumpToLineCallback:(id)inputData inWebView:(id)webView
|
|
{
|
|
[self processInputData:inputData
|
|
forCaller:@"app.notifyJumpToLineCallback()"
|
|
inWebView:webView
|
|
withSelector:@selector(_notifyJumpToLineCallback:)
|
|
minimumArgumentCount:2
|
|
withValidation:^BOOL(NSUInteger argumentIndex, id argument) {
|
|
if (argumentIndex == 0) {
|
|
return [argument isKindOfClass:[NSString class]];
|
|
} else if (argumentIndex == 1 ||
|
|
argumentIndex == 2)
|
|
{
|
|
return [argument isKindOfClass:[NSNumber class]];
|
|
}
|
|
|
|
return NO;
|
|
}];
|
|
}
|
|
|
|
- (void)notifyLinesAddedToView:(id)inputData inWebView:(id)webView
|
|
{
|
|
[self processInputData:inputData
|
|
forCaller:@"app.notifyLinesAddedToView()"
|
|
inWebView:webView
|
|
withSelector:@selector(_notifyLinesAddedToView:)
|
|
minimumArgumentCount:1
|
|
withValidation:^BOOL(NSUInteger argumentIndex, id argument) {
|
|
return ([argument isKindOfClass:[NSArray class]] ||
|
|
[argument isKindOfClass:[NSString class]]);
|
|
}];
|
|
}
|
|
|
|
- (void)notifyLinesRemovedFromView:(id)inputData inWebView:(id)webView
|
|
{
|
|
[self processInputData:inputData
|
|
forCaller:@"app.notifyLinesRemovedFromView()"
|
|
inWebView:webView
|
|
withSelector:@selector(_notifyLinesRemovedFromView:)
|
|
minimumArgumentCount:1
|
|
withValidation:^BOOL(NSUInteger argumentIndex, id argument) {
|
|
return ([argument isKindOfClass:[NSArray class]] ||
|
|
[argument isKindOfClass:[NSString class]]);
|
|
}];
|
|
}
|
|
|
|
- (void)printDebugInformation:(id)inputData inWebView:(id)webView
|
|
{
|
|
[self processInputData:inputData
|
|
forCaller:@"app.printDebugInformation()"
|
|
inWebView:webView
|
|
withSelector:@selector(_printDebugInformation:)
|
|
minimumArgumentCount:1
|
|
withValidation:^BOOL(NSUInteger argumentIndex, id argument) {
|
|
return [argument isKindOfClass:[NSString class]];
|
|
}];
|
|
}
|
|
|
|
- (void)printDebugInformationToConsole:(id)inputData inWebView:(id)webView
|
|
{
|
|
[self processInputData:inputData
|
|
forCaller:@"app.printDebugInformationToConsole()"
|
|
inWebView:webView
|
|
withSelector:@selector(_printDebugInformationToConsole:)
|
|
minimumArgumentCount:1
|
|
withValidation:^BOOL(NSUInteger argumentIndex, id argument) {
|
|
return [argument isKindOfClass:[NSString class]];
|
|
}];
|
|
}
|
|
|
|
- (void)renderMessagesBefore:(id)inputData inWebView:(id)webView
|
|
{
|
|
[self processInputData:inputData
|
|
forCaller:@"app.renderMessagesBefore()"
|
|
inWebView:webView
|
|
withSelector:@selector(_renderMessagesBefore:)
|
|
minimumArgumentCount:2
|
|
withValidation:^BOOL(NSUInteger argumentIndex, id argument) {
|
|
if (argumentIndex == 0) {
|
|
return [argument isKindOfClass:[NSString class]];
|
|
} else if (argumentIndex == 1) {
|
|
return [argument isKindOfClass:[NSNumber class]];
|
|
}
|
|
|
|
return NO;
|
|
}];
|
|
}
|
|
|
|
- (void)renderMessagesAfter:(id)inputData inWebView:(id)webView
|
|
{
|
|
[self processInputData:inputData
|
|
forCaller:@"app.renderMessagesAfter()"
|
|
inWebView:webView
|
|
withSelector:@selector(_renderMessagesAfter:)
|
|
minimumArgumentCount:2
|
|
withValidation:^BOOL(NSUInteger argumentIndex, id argument) {
|
|
if (argumentIndex == 0) {
|
|
return [argument isKindOfClass:[NSString class]];
|
|
} else if (argumentIndex == 1) {
|
|
return [argument isKindOfClass:[NSNumber class]];
|
|
}
|
|
|
|
return NO;
|
|
}];
|
|
}
|
|
|
|
- (void)renderMessagesInRange:(id)inputData inWebView:(id)webView
|
|
{
|
|
[self processInputData:inputData
|
|
forCaller:@"app.renderMessagesInRange()"
|
|
inWebView:webView
|
|
withSelector:@selector(_renderMessagesInRange:)
|
|
minimumArgumentCount:3
|
|
withValidation:^BOOL(NSUInteger argumentIndex, id argument) {
|
|
if (argumentIndex == 0 ||
|
|
argumentIndex == 1)
|
|
{
|
|
return [argument isKindOfClass:[NSString class]];
|
|
} else if (argumentIndex == 2) {
|
|
return [argument isKindOfClass:[NSNumber class]];
|
|
}
|
|
|
|
return NO;
|
|
}];
|
|
}
|
|
|
|
- (void)renderMessageWithSiblings:(id)inputData inWebView:(id)webView
|
|
{
|
|
[self processInputData:inputData
|
|
forCaller:@"app.renderMessageWithSiblings()"
|
|
inWebView:webView
|
|
withSelector:@selector(_renderMessageWithSiblings:)
|
|
minimumArgumentCount:3
|
|
withValidation:^BOOL(NSUInteger argumentIndex, id argument) {
|
|
if (argumentIndex == 0) {
|
|
return [argument isKindOfClass:[NSString class]];
|
|
} else if (argumentIndex == 1 ||
|
|
argumentIndex == 2)
|
|
{
|
|
return [argument isKindOfClass:[NSNumber class]];
|
|
}
|
|
|
|
return NO;
|
|
}];
|
|
}
|
|
|
|
- (void)retrievePreferencesWithMethodName:(id)inputData inWebView:(id)webView
|
|
{
|
|
[self processInputData:inputData
|
|
forCaller:@"app.retrievePreferencesWithMethodName()"
|
|
inWebView:webView
|
|
withSelector:@selector(_retrievePreferencesWithMethodName:)
|
|
minimumArgumentCount:1
|
|
withValidation:^BOOL(NSUInteger argumentIndex, id argument) {
|
|
return [argument isKindOfClass:[NSString class]];
|
|
}];
|
|
}
|
|
|
|
- (void)renderTemplate:(id)inputData inWebView:(id)webView
|
|
{
|
|
[self processInputData:inputData
|
|
forCaller:@"app.renderTemplate()"
|
|
inWebView:webView
|
|
withSelector:@selector(_renderTemplate:)
|
|
minimumArgumentCount:1
|
|
withValidation:^BOOL(NSUInteger argumentIndex, id argument) {
|
|
if (argumentIndex == 0) {
|
|
return [argument isKindOfClass:[NSString class]];
|
|
} else if (argumentIndex == 1) {
|
|
return ([argument isKindOfClass:[NSNull class]] ||
|
|
[argument isKindOfClass:[NSDictionary class]]);
|
|
}
|
|
|
|
return NO;
|
|
}];
|
|
}
|
|
|
|
- (void)sendPluginPayload:(id)inputData inWebView:(id)webView
|
|
{
|
|
[self processInputData:inputData
|
|
forCaller:@"app.sendPluginPayload()"
|
|
inWebView:webView
|
|
withSelector:@selector(_sendPluginPayload:)
|
|
minimumArgumentCount:2
|
|
withValidation:^BOOL(NSUInteger argumentIndex, id argument) {
|
|
if (argumentIndex == 0) {
|
|
return [argument isKindOfClass:[NSString class]];
|
|
} else {
|
|
return YES;
|
|
}
|
|
}];
|
|
}
|
|
|
|
- (void)serverAddress:(id)inputData inWebView:(id)webView
|
|
{
|
|
[self processInputData:inputData
|
|
forCaller:@"app.serverAddress()"
|
|
inWebView:webView
|
|
withSelector:@selector(_serverAddress:)];
|
|
}
|
|
|
|
- (void)serverChannelCount:(id)inputData inWebView:(id)webView
|
|
{
|
|
[self processInputData:inputData
|
|
forCaller:@"app.serverChannelCount()"
|
|
inWebView:webView
|
|
withSelector:@selector(_serverChannelCount:)];
|
|
}
|
|
|
|
- (void)serverIsConnected:(id)inputData inWebView:(id)webView
|
|
{
|
|
[self processInputData:inputData
|
|
forCaller:@"app.serverIsConnected()"
|
|
inWebView:webView
|
|
withSelector:@selector(_serverIsConnected:)];
|
|
}
|
|
|
|
- (void)setAutomaticScrollingEnabled:(id)inputData inWebView:(id)webView
|
|
{
|
|
[self processInputData:inputData
|
|
forCaller:@"app.setAutomaticScrollingEnabled()"
|
|
inWebView:webView
|
|
withSelector:@selector(_setAutomaticScrollingEnabled:)
|
|
minimumArgumentCount:1
|
|
withValidation:^BOOL(NSUInteger argumentIndex, id argument) {
|
|
return ([argument isKindOfClass:[NSNumber class]]);
|
|
}];
|
|
}
|
|
|
|
- (void)setChannelName:(id)inputData inWebView:(id)webView
|
|
{
|
|
[self processInputData:inputData
|
|
forCaller:@"app.setChannelName()"
|
|
inWebView:webView
|
|
withSelector:@selector(_setChannelName:)
|
|
minimumArgumentCount:1
|
|
withValidation:^BOOL(NSUInteger argumentIndex, id argument) {
|
|
return ([argument isKindOfClass:[NSNull class]] ||
|
|
[argument isKindOfClass:[NSString class]]);
|
|
}];
|
|
}
|
|
|
|
- (void)setNickname:(id)inputData inWebView:(id)webView
|
|
{
|
|
[self processInputData:inputData
|
|
forCaller:@"app.setNickname()"
|
|
inWebView:webView
|
|
withSelector:@selector(_setNickname:)
|
|
minimumArgumentCount:1
|
|
withValidation:^BOOL(NSUInteger argumentIndex, id argument) {
|
|
return ([argument isKindOfClass:[NSNull class]] ||
|
|
[argument isKindOfClass:[NSString class]]);
|
|
}];
|
|
}
|
|
|
|
- (void)setSelection:(id)inputData inWebView:(id)webView
|
|
{
|
|
[self processInputData:inputData
|
|
forCaller:@"app.setSelection()"
|
|
inWebView:webView
|
|
withSelector:@selector(_setSelection:)
|
|
minimumArgumentCount:1
|
|
withValidation:^BOOL(NSUInteger argumentIndex, id argument) {
|
|
return ([argument isKindOfClass:[NSNull class]] ||
|
|
[argument isKindOfClass:[NSString class]]);
|
|
}];
|
|
}
|
|
|
|
- (void)setURLAddress:(id)inputData inWebView:(id)webView
|
|
{
|
|
[self processInputData:inputData
|
|
forCaller:@"app.setURLAddress()"
|
|
inWebView:webView
|
|
withSelector:@selector(_setURLAddress:)
|
|
minimumArgumentCount:1
|
|
withValidation:^BOOL(NSUInteger argumentIndex, id argument) {
|
|
return ([argument isKindOfClass:[NSNull class]] ||
|
|
[argument isKindOfClass:[NSString class]]);
|
|
}];
|
|
}
|
|
|
|
- (void)sidebarInversionIsEnabled:(id)inputData inWebView:(id)webView
|
|
{
|
|
[self processInputData:inputData
|
|
forCaller:@"app.sidebarInversionIsEnabled()"
|
|
inWebView:webView
|
|
withSelector:@selector(_sidebarInversionIsEnabled:)];
|
|
}
|
|
|
|
- (void)appearance:(id)inputData inWebView:(id)webView
|
|
{
|
|
[self processInputData:inputData
|
|
forCaller:@"app.appearance()"
|
|
inWebView:webView
|
|
withSelector:@selector(_appearance:)];
|
|
}
|
|
|
|
- (void)styleSettingsRetrieveValue:(id)inputData inWebView:(id)webView
|
|
{
|
|
[self processInputData:inputData
|
|
forCaller:@"app.styleSettingsRetrieveValue()"
|
|
inWebView:webView
|
|
withSelector:@selector(_styleSettingsRetrieveValue:)
|
|
minimumArgumentCount:1
|
|
withValidation:^BOOL(NSUInteger argumentIndex, id argument) {
|
|
return [argument isKindOfClass:[NSString class]];
|
|
}];
|
|
}
|
|
|
|
- (void)styleSettingsSetValue:(id)inputData inWebView:(id)webView
|
|
{
|
|
[self processInputData:inputData
|
|
forCaller:@"app.styleSettingsSetValue()"
|
|
inWebView:webView
|
|
withSelector:@selector(_styleSettingsSetValue:)
|
|
minimumArgumentCount:2
|
|
withValidation:^BOOL(NSUInteger argumentIndex, id argument) {
|
|
if (argumentIndex == 0) {
|
|
return [argument isKindOfClass:[NSString class]];
|
|
} else {
|
|
return ([argument isKindOfClass:[NSArray class]] ||
|
|
[argument isKindOfClass:[NSDictionary class]] ||
|
|
[argument isKindOfClass:[NSNull class]] ||
|
|
[argument isKindOfClass:[NSNumber class]] ||
|
|
[argument isKindOfClass:[NSString class]]);
|
|
}
|
|
}];
|
|
}
|
|
|
|
- (void)topicBarDoubleClicked:(id)inputData inWebView:(id)webView
|
|
{
|
|
[self processInputData:inputData
|
|
forCaller:@"app.topicBarDoubleClicked()"
|
|
inWebView:webView
|
|
withSelector:@selector(_topicBarDoubleClicked:)];
|
|
}
|
|
|
|
- (void)finishedLayingOutView:(id)inputData inWebView:(id)webView
|
|
{
|
|
[self processInputData:inputData
|
|
forCaller:@"app.finishedLayingOutView()"
|
|
inWebView:webView
|
|
withSelector:@selector(_finishedLayingOutView:)];
|
|
}
|
|
|
|
#pragma mark -
|
|
#pragma mark Private Implementation
|
|
|
|
- (void)_channelIsActive:(TVCLogScriptEventSinkContext *)context
|
|
{
|
|
context.completionBlock( @(context.associatedChannel.isActive) );
|
|
}
|
|
|
|
- (void)_channelMemberCount:(TVCLogScriptEventSinkContext *)context
|
|
{
|
|
IRCChannel *channel = context.associatedChannel;
|
|
|
|
if (channel == nil || channel.isChannel == NO) {
|
|
[self.class throwJavaScriptException:@"View is not a channel"
|
|
forCaller:context.caller
|
|
inWebView:context.webView];
|
|
|
|
return;
|
|
}
|
|
|
|
context.completionBlock( @(channel.numberOfMembers) );
|
|
}
|
|
|
|
- (void)_channelName:(TVCLogScriptEventSinkContext *)context
|
|
{
|
|
context.completionBlock( context.associatedChannel.name );
|
|
}
|
|
|
|
- (void)_channelNameDoubleClicked:(TVCLogScriptEventSinkContext *)context
|
|
{
|
|
[context.webViewPolicy channelNameDoubleClicked];
|
|
}
|
|
|
|
- (void)_displayContextMenu:(TVCLogScriptEventSinkContext *)context
|
|
{
|
|
[context.webViewPolicy displayContextMenuInWebView:context.webView];
|
|
}
|
|
|
|
- (void)_copySelectionWhenPermitted:(TVCLogScriptEventSinkContext *)context
|
|
{
|
|
if ([TPCPreferences copyOnSelect]) {
|
|
NSString *selection = context.webView.selection;
|
|
|
|
if (selection) {
|
|
RZPasteboard().stringContent = selection;
|
|
|
|
context.completionBlock( @(YES) );
|
|
}
|
|
}
|
|
|
|
context.completionBlock( @(NO) );
|
|
}
|
|
|
|
- (void)_encryptionAuthenticateUser:(TVCLogScriptEventSinkContext *)context
|
|
{
|
|
#if TEXTUAL_BUILT_WITH_ADVANCED_ENCRYPTION == 1
|
|
IRCClient *client = context.associatedClient;
|
|
|
|
if (client.isLoggedIn == NO) {
|
|
return;
|
|
}
|
|
|
|
IRCChannel *channel = context.associatedChannel;
|
|
|
|
if (channel == nil || channel.isPrivateMessage == NO) {
|
|
[self.class throwJavaScriptException:@"View is not a private message"
|
|
forCaller:context.caller
|
|
inWebView:context.webView];
|
|
|
|
return;
|
|
}
|
|
|
|
[client encryptionAuthenticateUser:channel.name];
|
|
#endif
|
|
}
|
|
|
|
- (void)_inlineMediaEnabledForView:(TVCLogScriptEventSinkContext *)context
|
|
{
|
|
context.completionBlock( @(context.viewController.inlineMediaEnabledForView) );
|
|
}
|
|
|
|
- (void)_loadInlineMedia:(TVCLogScriptEventSinkContext *)context
|
|
{
|
|
NSArray *arguments = context.arguments;
|
|
|
|
NSString *address = [self.class objectValueToCommon:arguments[0]];
|
|
|
|
if (address.length == 0) {
|
|
[self.class throwJavaScriptException:@"Length of address is 0"
|
|
forCaller:context.caller
|
|
inWebView:context.webView];
|
|
|
|
return;
|
|
}
|
|
|
|
NSString *uniqueIdentifier = [self.class objectValueToCommon:arguments[1]];
|
|
|
|
if (uniqueIdentifier.length == 0) {
|
|
[self.class throwJavaScriptException:@"Length of unique identifier is 0"
|
|
forCaller:context.caller
|
|
inWebView:context.webView];
|
|
|
|
return;
|
|
}
|
|
|
|
NSString *lineNumber = [self.class objectValueToCommon:arguments[2]];
|
|
|
|
lineNumber = [self.class standardizeLineNumber:lineNumber];
|
|
|
|
if (lineNumber.length == 0) {
|
|
[self.class throwJavaScriptException:@"Length of line number is 0"
|
|
forCaller:context.caller
|
|
inWebView:context.webView];
|
|
|
|
return;
|
|
}
|
|
|
|
NSNumber *index = [self.class objectValueToCommon:arguments[3]];
|
|
|
|
[context.viewController processInlineMediaAtAddress:address
|
|
withUniqueIdentifier:uniqueIdentifier
|
|
atLineNumber:lineNumber
|
|
index:index.unsignedIntegerValue];
|
|
}
|
|
|
|
- (void)_localUserHostmask:(TVCLogScriptEventSinkContext *)context
|
|
{
|
|
context.completionBlock( context.associatedClient.userHostmask );
|
|
}
|
|
|
|
- (void)_localUserNickname:(TVCLogScriptEventSinkContext *)context
|
|
{
|
|
context.completionBlock( context.associatedClient.userNickname );
|
|
}
|
|
|
|
- (void)_logToConsole:(TVCLogScriptEventSinkContext *)context
|
|
{
|
|
NSArray *arguments = context.arguments;
|
|
|
|
NSString *message = [self.class objectValueToCommon:arguments[0]];
|
|
|
|
LogToConsole("JavaScript: %{public}@", message);
|
|
}
|
|
|
|
- (void)_networkName:(TVCLogScriptEventSinkContext *)context
|
|
{
|
|
context.completionBlock( context.associatedClient.networkName );
|
|
}
|
|
|
|
- (void)_nicknameColorStyleHash:(TVCLogScriptEventSinkContext *)context
|
|
{
|
|
NSArray *arguments = context.arguments;
|
|
|
|
NSString *inputString = [self.class objectValueToCommon:arguments[0]];
|
|
|
|
NSString *colorStyle = [self.class objectValueToCommon:arguments[1]];
|
|
|
|
TPCThemeSettingsNicknameColorStyle colorStyleEnum;
|
|
|
|
if ([colorStyle isEqualToString:@"HSL-dark"]) {
|
|
colorStyleEnum = TPCThemeSettingsNicknameColorStyleDark;
|
|
} else if ([colorStyle isEqualToString:@"HSL-light"]) {
|
|
colorStyleEnum = TPCThemeSettingsNicknameColorStyleLight;
|
|
} else {
|
|
[self.class throwJavaScriptException:@"Invalid style"
|
|
forCaller:context.caller
|
|
inWebView:context.webView];
|
|
|
|
return;
|
|
}
|
|
|
|
context.completionBlock( [IRCUserNicknameColorStyleGenerator hashForString:inputString colorStyle:colorStyleEnum] );
|
|
}
|
|
|
|
- (void)_nicknameDoubleClicked:(TVCLogScriptEventSinkContext *)context
|
|
{
|
|
[context.webViewPolicy nicknameDoubleClicked];
|
|
}
|
|
|
|
- (void)_notifyJumpToLineCallback:(TVCLogScriptEventSinkContext *)context
|
|
{
|
|
void (^contextCompletionBlock)(id _Nullable) = context.completionBlock;
|
|
|
|
NSArray *arguments = context.arguments;
|
|
|
|
NSString *lineNumber = [self.class objectValueToCommon:arguments[0]];
|
|
|
|
lineNumber = [self.class standardizeLineNumber:lineNumber];
|
|
|
|
if (lineNumber.length == 0) {
|
|
[self.class throwJavaScriptException:@"Length of line number is 0"
|
|
forCaller:context.caller
|
|
inWebView:context.webView];
|
|
|
|
contextCompletionBlock(nil);
|
|
|
|
return;
|
|
}
|
|
|
|
BOOL successful = [[self.class objectValueToCommon:arguments[1]] boolValue];
|
|
|
|
BOOL scrolledToBottom = [[self.class objectValueToCommon:arguments[2]] boolValue];
|
|
|
|
[context.viewController notifyJumpToLine:lineNumber successful:successful scrolledToBottom:scrolledToBottom];
|
|
}
|
|
|
|
- (void)_notifyLinesAddedToView:(TVCLogScriptEventSinkContext *)context
|
|
{
|
|
[self _notifyLinesAdded:YES context:context];
|
|
}
|
|
|
|
- (void)_notifyLinesRemovedFromView:(TVCLogScriptEventSinkContext *)context
|
|
{
|
|
[self _notifyLinesAdded:NO context:context];
|
|
}
|
|
|
|
- (void)_notifyLinesAdded:(BOOL)added context:(TVCLogScriptEventSinkContext *)context
|
|
{
|
|
NSArray *arguments = context.arguments;
|
|
|
|
id lineNumbersUncut = [self.class objectValueToCommon:arguments[0]];
|
|
|
|
if ([lineNumbersUncut isKindOfClass:[NSString class]]) {
|
|
lineNumbersUncut = @[lineNumbersUncut];
|
|
}
|
|
|
|
NSArray *lineNumbers = [self.class standardizeLineNumbers:lineNumbersUncut];
|
|
|
|
if (added) {
|
|
[context.viewController notifyLinesAddedToView:[lineNumbers copy]];
|
|
} else {
|
|
[context.viewController notifyLinesRemovedFromView:[lineNumbers copy]];
|
|
}
|
|
}
|
|
|
|
- (void)_printDebugInformation:(TVCLogScriptEventSinkContext *)context
|
|
{
|
|
NSArray *arguments = context.arguments;
|
|
|
|
NSString *message = [self.class objectValueToCommon:arguments[0]];
|
|
|
|
[context.associatedClient printDebugInformation:message inChannel:context.associatedChannel];
|
|
}
|
|
|
|
- (void)_printDebugInformationToConsole:(TVCLogScriptEventSinkContext *)context
|
|
{
|
|
NSArray *arguments = context.arguments;
|
|
|
|
NSString *message = [self.class objectValueToCommon:arguments[0]];
|
|
|
|
[context.associatedClient printDebugInformationToConsole:message];
|
|
}
|
|
|
|
- (void)_renderMessagesBefore:(TVCLogScriptEventSinkContext *)context
|
|
{
|
|
[self _renderMessagesAfter:NO context:context];
|
|
}
|
|
|
|
- (void)_renderMessagesAfter:(TVCLogScriptEventSinkContext *)context
|
|
{
|
|
[self _renderMessagesAfter:YES context:context];
|
|
}
|
|
|
|
- (void)_renderMessagesAfter:(BOOL)after context:(TVCLogScriptEventSinkContext *)context
|
|
{
|
|
void (^contextCompletionBlock)(id _Nullable) = context.completionBlock;
|
|
|
|
NSArray *arguments = context.arguments;
|
|
|
|
NSString *lineNumber = [self.class objectValueToCommon:arguments[0]];
|
|
|
|
lineNumber = [self.class standardizeLineNumber:lineNumber];
|
|
|
|
if (lineNumber.length == 0) {
|
|
[self.class throwJavaScriptException:@"Length of line number is 0"
|
|
forCaller:context.caller
|
|
inWebView:context.webView];
|
|
|
|
contextCompletionBlock(nil);
|
|
|
|
return;
|
|
}
|
|
|
|
NSInteger maximumNumberOfLines = [[self.class objectValueToCommon:arguments[1]] integerValue];
|
|
|
|
if (maximumNumberOfLines <= 0) {
|
|
[self.class throwJavaScriptException:@"Maximum number of lines must be equal to 1 or greater"
|
|
forCaller:context.caller
|
|
inWebView:context.webView];
|
|
|
|
contextCompletionBlock(nil);
|
|
|
|
return;
|
|
}
|
|
|
|
void (^renderCompletionBlock)(NSArray *) = ^(NSArray<NSDictionary<NSString *, id> *> *renderedLogLines) {
|
|
contextCompletionBlock(renderedLogLines);
|
|
};
|
|
|
|
if (after == NO) {
|
|
[context.viewController renderLogLinesBeforeLineNumber:lineNumber
|
|
maximumNumberOfLines:maximumNumberOfLines
|
|
completionBlock:renderCompletionBlock];
|
|
} else {
|
|
[context.viewController renderLogLinesAfterLineNumber:lineNumber
|
|
maximumNumberOfLines:maximumNumberOfLines
|
|
completionBlock:renderCompletionBlock];
|
|
}
|
|
}
|
|
|
|
- (void)_renderMessagesInRange:(TVCLogScriptEventSinkContext *)context
|
|
{
|
|
void (^contextCompletionBlock)(id _Nullable) = context.completionBlock;
|
|
|
|
NSArray *arguments = context.arguments;
|
|
|
|
NSString *lineNumberAfter = [self.class objectValueToCommon:arguments[0]];
|
|
NSString *lineNumberBefore = [self.class objectValueToCommon:arguments[1]];
|
|
|
|
lineNumberAfter = [self.class standardizeLineNumber:lineNumberAfter];
|
|
lineNumberBefore = [self.class standardizeLineNumber:lineNumberBefore];
|
|
|
|
if (lineNumberAfter.length == 0 ||
|
|
lineNumberBefore.length == 0)
|
|
{
|
|
[self.class throwJavaScriptException:@"Length of line number is 0"
|
|
forCaller:context.caller
|
|
inWebView:context.webView];
|
|
|
|
contextCompletionBlock(nil);
|
|
|
|
return;
|
|
}
|
|
|
|
NSInteger maximumNumberOfLines = [[self.class objectValueToCommon:arguments[2]] integerValue];
|
|
|
|
if (maximumNumberOfLines < 0) {
|
|
[self.class throwJavaScriptException:@"Maximum number of lines must be equal to 0 or greater"
|
|
forCaller:context.caller
|
|
inWebView:context.webView];
|
|
|
|
contextCompletionBlock(nil);
|
|
|
|
return;
|
|
}
|
|
|
|
void (^renderCompletionBlock)(NSArray *) = ^(NSArray<NSDictionary<NSString *, id> *> *renderedLogLines) {
|
|
contextCompletionBlock(renderedLogLines);
|
|
};
|
|
|
|
[context.viewController renderLogLinesAfterLineNumber:lineNumberAfter
|
|
beforeLineNumber:lineNumberBefore
|
|
maximumNumberOfLines:maximumNumberOfLines
|
|
completionBlock:renderCompletionBlock];
|
|
}
|
|
|
|
- (void)_renderMessageWithSiblings:(TVCLogScriptEventSinkContext *)context
|
|
{
|
|
void (^contextCompletionBlock)(id _Nullable) = context.completionBlock;
|
|
|
|
NSArray *arguments = context.arguments;
|
|
|
|
NSString *lineNumber = [self.class objectValueToCommon:arguments[0]];
|
|
|
|
lineNumber = [self.class standardizeLineNumber:lineNumber];
|
|
|
|
if (lineNumber.length == 0) {
|
|
[self.class throwJavaScriptException:@"Length of line number is 0"
|
|
forCaller:context.caller
|
|
inWebView:context.webView];
|
|
|
|
contextCompletionBlock(nil);
|
|
|
|
return;
|
|
}
|
|
|
|
NSInteger numberOfLinesBefore = [[self.class objectValueToCommon:arguments[1]] integerValue];
|
|
NSInteger numberOfLinesAfter = [[self.class objectValueToCommon:arguments[2]] integerValue];
|
|
|
|
if (numberOfLinesBefore < 0 ||
|
|
numberOfLinesAfter < 0)
|
|
{
|
|
[self.class throwJavaScriptException:@"Number of lines must be equal to 0 or greater"
|
|
forCaller:context.caller
|
|
inWebView:context.webView];
|
|
|
|
contextCompletionBlock(nil);
|
|
|
|
return;
|
|
}
|
|
|
|
void (^renderCompletionBlock)(NSArray *) = ^(NSArray<NSDictionary<NSString *, id> *> *renderedLogLines) {
|
|
contextCompletionBlock(renderedLogLines);
|
|
};
|
|
|
|
[context.viewController renderLogLineAtLineNumber:lineNumber
|
|
numberOfLinesBefore:numberOfLinesBefore
|
|
numberOfLinesAfter:numberOfLinesAfter
|
|
completionBlock:renderCompletionBlock];
|
|
}
|
|
|
|
- (void)_renderTemplate:(TVCLogScriptEventSinkContext *)context
|
|
{
|
|
NSArray *arguments = context.arguments;
|
|
|
|
NSString *templateName = [self.class objectValueToCommon:arguments[0]];
|
|
|
|
if (templateName.length == 0) {
|
|
[self.class throwJavaScriptException:@"Length of template name is 0"
|
|
forCaller:context.caller
|
|
inWebView:context.webView];
|
|
|
|
context.completionBlock(nil);
|
|
|
|
return;
|
|
}
|
|
|
|
NSDictionary *templateAttributes = [self.class objectValueToCommon:arguments[1]];
|
|
|
|
NSString *renderedTemplate = [TVCLogRenderer renderTemplateNamed:templateName attributes:templateAttributes];
|
|
|
|
context.completionBlock( renderedTemplate );
|
|
}
|
|
|
|
- (void)_retrievePreferencesWithMethodName:(TVCLogScriptEventSinkContext *)context
|
|
{
|
|
NSArray *arguments = context.arguments;
|
|
|
|
NSString *methodName = [self.class objectValueToCommon:arguments[0]];
|
|
|
|
SEL methodSelector = NSSelectorFromString(methodName);
|
|
|
|
NSMethodSignature *methodSignature =
|
|
[TPCPreferences methodSignatureForSelector:methodSelector];
|
|
|
|
if (methodSignature == nil) {
|
|
[self.class throwJavaScriptException:@"Unknown method named: '%@'"
|
|
forCaller:context.caller
|
|
inWebView:context.webView];
|
|
|
|
context.completionBlock(nil);
|
|
|
|
return;
|
|
} else if (strcmp(methodSignature.methodReturnType, @encode(void)) == 0) {
|
|
[self.class throwJavaScriptException:@"Method named '%@' does not return a value"
|
|
forCaller:context.caller
|
|
inWebView:context.webView];
|
|
|
|
context.completionBlock(nil);
|
|
|
|
return;
|
|
}
|
|
|
|
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:methodSignature];
|
|
|
|
invocation.target = [TPCPreferences class];
|
|
|
|
invocation.selector = methodSelector;
|
|
|
|
[invocation invoke];
|
|
|
|
void *returnValue;
|
|
|
|
[invocation getReturnValue:&returnValue];
|
|
|
|
context.completionBlock( [NSValue valueWithPrimitive:returnValue withType:methodSignature.methodReturnType] );
|
|
}
|
|
|
|
- (void)_sendPluginPayload:(TVCLogScriptEventSinkContext *)context
|
|
{
|
|
if ([sharedPluginManager() supportsFeature:THOPluginItemSupportedFeatureWebViewJavaScriptPayloads] == NO) {
|
|
[self.class throwJavaScriptException:@"There are no plugins loaded that support JavaScript payloads"
|
|
forCaller:context.caller
|
|
inWebView:context.webView];
|
|
|
|
return;
|
|
}
|
|
|
|
NSArray *arguments = context.arguments;
|
|
|
|
NSString *payloadLabel = [self.class objectValueToCommon:arguments[0]];
|
|
|
|
if (payloadLabel.length == 0) {
|
|
[self.class throwJavaScriptException:@"Length of payload label is 0"
|
|
forCaller:context.caller
|
|
inWebView:context.webView];
|
|
|
|
return;
|
|
}
|
|
|
|
id payloadContents = [self.class objectValueToCommon:arguments[1]];
|
|
|
|
THOPluginWebViewJavaScriptPayloadConcreteObject *payloadObject =
|
|
[THOPluginWebViewJavaScriptPayloadConcreteObject new];
|
|
|
|
payloadObject.payloadLabel = payloadLabel;
|
|
payloadObject.payloadContents = payloadContents;
|
|
|
|
[THOPluginDispatcher didReceiveJavaScriptPayload:payloadObject fromViewController:context.viewController];
|
|
}
|
|
|
|
- (void)_serverAddress:(TVCLogScriptEventSinkContext *)context
|
|
{
|
|
context.completionBlock( context.associatedClient.serverAddress );
|
|
}
|
|
|
|
- (void)_serverChannelCount:(TVCLogScriptEventSinkContext *)context
|
|
{
|
|
context.completionBlock( @(context.associatedClient.channelCount) );
|
|
}
|
|
|
|
- (void)_serverIsConnected:(TVCLogScriptEventSinkContext *)context
|
|
{
|
|
context.completionBlock( @(context.associatedClient.isLoggedIn) );
|
|
}
|
|
|
|
- (void)_setAutomaticScrollingEnabled:(TVCLogScriptEventSinkContext *)context
|
|
{
|
|
NSArray *arguments = context.arguments;
|
|
|
|
BOOL enabled = [[self.class objectValueToCommon:arguments[0]] boolValue];
|
|
|
|
[context.webView setAutomaticScrollingEnabled:enabled];
|
|
}
|
|
|
|
- (void)_setChannelName:(TVCLogScriptEventSinkContext *)context
|
|
{
|
|
NSArray *arguments = context.arguments;
|
|
|
|
NSString *value = [self.class objectValueToCommon:arguments[0]];
|
|
|
|
context.webViewPolicy.channelName = value;
|
|
}
|
|
|
|
- (void)_setNickname:(TVCLogScriptEventSinkContext *)context
|
|
{
|
|
NSArray *arguments = context.arguments;
|
|
|
|
NSString *value = [self.class objectValueToCommon:arguments[0]];
|
|
|
|
context.webViewPolicy.nickname = value;
|
|
}
|
|
|
|
- (void)_setSelection:(TVCLogScriptEventSinkContext *)context
|
|
{
|
|
NSArray *arguments = context.arguments;
|
|
|
|
NSString *selection = [self.class objectValueToCommon:arguments[0]];
|
|
|
|
if (selection && selection.length == 0) {
|
|
selection = nil;
|
|
}
|
|
|
|
context.webView.selection = selection;
|
|
}
|
|
|
|
- (void)_setURLAddress:(TVCLogScriptEventSinkContext *)context
|
|
{
|
|
NSArray *arguments = context.arguments;
|
|
|
|
NSString *value = [self.class objectValueToCommon:arguments[0]];
|
|
|
|
context.webViewPolicy.anchorURL = value;
|
|
}
|
|
|
|
- (void)_sidebarInversionIsEnabled:(TVCLogScriptEventSinkContext *)context
|
|
{
|
|
TVCMainWindowAppearance *appearance = context.viewController.attachedWindow.userInterfaceObjects;
|
|
|
|
context.completionBlock( @(appearance.isDarkAppearance) );
|
|
}
|
|
|
|
- (void)_appearance:(TVCLogScriptEventSinkContext *)context
|
|
{
|
|
TVCMainWindowAppearance *appearance = context.viewController.attachedWindow.userInterfaceObjects;
|
|
|
|
context.completionBlock( appearance.shortAppearanceDescription );
|
|
}
|
|
|
|
- (void)_styleSettingsRetrieveValue:(TVCLogScriptEventSinkContext *)context
|
|
{
|
|
NSArray *arguments = context.arguments;
|
|
|
|
NSString *keyName = [self.class objectValueToCommon:arguments[0]];
|
|
|
|
NSString *errorValue = nil;
|
|
|
|
id result = [themeSettings() styleSettingsRetrieveValueForKey:keyName error:&errorValue];
|
|
|
|
if (errorValue) {
|
|
[self.class throwJavaScriptException:errorValue
|
|
forCaller:context.caller
|
|
inWebView:context.webView];
|
|
}
|
|
|
|
context.completionBlock( result );
|
|
}
|
|
|
|
- (void)_styleSettingsSetValue:(TVCLogScriptEventSinkContext *)context
|
|
{
|
|
NSArray *arguments = context.arguments;
|
|
|
|
NSString *keyName = [self.class objectValueToCommon:arguments[0]];
|
|
|
|
id keyValue = [self.class objectValueToCommon:arguments[1]];
|
|
|
|
NSString *errorValue = nil;
|
|
|
|
BOOL result = [themeSettings() styleSettingsSetValue:keyValue forKey:keyName error:&errorValue];
|
|
|
|
if (errorValue) {
|
|
[self.class throwJavaScriptException:errorValue
|
|
forCaller:context.caller
|
|
inWebView:context.webView];
|
|
}
|
|
|
|
if (result) {
|
|
[worldController() evaluateFunctionOnAllViews:@"Textual.styleSettingDidChange" arguments:@[keyName]];
|
|
}
|
|
|
|
context.completionBlock( @(result) );
|
|
}
|
|
|
|
- (void)_topicBarDoubleClicked:(TVCLogScriptEventSinkContext *)context
|
|
{
|
|
[context.webViewPolicy topicBarDoubleClicked];
|
|
}
|
|
|
|
- (void)_finishedLayingOutView:(TVCLogScriptEventSinkContext *)context
|
|
{
|
|
[context.webView setViewFinishedLayout];
|
|
}
|
|
|
|
@end
|
|
|
|
#pragma mark -
|
|
|
|
@implementation TVCLogScriptEventSinkContext
|
|
|
|
- (TVCLogController *)viewController
|
|
{
|
|
return self.webView.viewController;
|
|
}
|
|
|
|
- (TVCLogPolicy *)webViewPolicy
|
|
{
|
|
return self.webView.webViewPolicy;
|
|
}
|
|
|
|
- (IRCClient *)associatedClient
|
|
{
|
|
return self.viewController.associatedClient;
|
|
}
|
|
|
|
- (nullable IRCChannel *)associatedChannel
|
|
{
|
|
return self.viewController.associatedChannel;
|
|
}
|
|
|
|
@end
|
|
|
|
NS_ASSUME_NONNULL_END
|