2018-09-11 01:30:55 -05:00
|
|
|
// -*- Mode: ObjC; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*-
|
|
|
|
//
|
|
|
|
// This file is part of the LibreOffice project.
|
|
|
|
//
|
|
|
|
// This Source Code Form is subject to the terms of the Mozilla Public
|
|
|
|
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
|
|
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
|
|
|
|
|
|
#import "config.h"
|
|
|
|
|
2018-09-13 11:16:00 -05:00
|
|
|
#import <string>
|
|
|
|
#import <vector>
|
|
|
|
|
|
|
|
#import <poll.h>
|
|
|
|
|
|
|
|
#import "ios.h"
|
|
|
|
#import "FakeSocket.hpp"
|
2018-11-06 09:39:38 -06:00
|
|
|
#import "Log.hpp"
|
2018-10-16 09:20:55 -05:00
|
|
|
#import "Util.hpp"
|
2018-09-13 11:16:00 -05:00
|
|
|
|
2018-09-11 01:30:55 -05:00
|
|
|
#import "DocumentViewController.h"
|
|
|
|
|
|
|
|
@interface DocumentViewController() <WKNavigationDelegate, WKUIDelegate, WKScriptMessageHandler> {
|
2018-10-12 09:48:45 -05:00
|
|
|
int closeNotificationPipeForForwardingThread[2];
|
2018-09-11 01:30:55 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
@end
|
|
|
|
|
|
|
|
@implementation DocumentViewController
|
|
|
|
|
|
|
|
- (void)viewDidLoad {
|
|
|
|
[super viewDidLoad];
|
|
|
|
|
|
|
|
WKWebViewConfiguration *configuration = [[WKWebViewConfiguration alloc] init];
|
|
|
|
WKUserContentController *userContentController = [[WKUserContentController alloc] init];
|
|
|
|
|
|
|
|
[userContentController addScriptMessageHandler:self name:@"debug"];
|
|
|
|
[userContentController addScriptMessageHandler:self name:@"lool"];
|
|
|
|
[userContentController addScriptMessageHandler:self name:@"error"];
|
|
|
|
|
|
|
|
configuration.userContentController = userContentController;
|
|
|
|
|
|
|
|
self.webView = [[WKWebView alloc] initWithFrame:CGRectZero configuration:configuration];
|
|
|
|
self.webView.translatesAutoresizingMaskIntoConstraints = NO;
|
|
|
|
[self.view addSubview:self.webView];
|
|
|
|
|
|
|
|
self.webView.navigationDelegate = self;
|
|
|
|
self.webView.UIDelegate = self;
|
|
|
|
|
|
|
|
WKWebView *webViewP = self.webView;
|
|
|
|
NSDictionary *views = NSDictionaryOfVariableBindings(webViewP);
|
|
|
|
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-0-[webViewP(>=0)]-0-|"
|
|
|
|
options:0
|
|
|
|
metrics:nil
|
|
|
|
views:views]];
|
|
|
|
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-0-[webViewP(>=0)]-0-|"
|
|
|
|
options:0
|
|
|
|
metrics:nil
|
|
|
|
views:views]];
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)viewWillAppear:(BOOL)animated {
|
|
|
|
[super viewWillAppear:animated];
|
|
|
|
|
2018-11-29 11:21:49 -06:00
|
|
|
// When the user uses the camer to insert a photo, when the camera is displayed, this view is
|
|
|
|
// removed. After the photo is taken it is then added back to the hierarchy. Our Document object
|
|
|
|
// is still there intact, however, so no need to re-open the document when we re-appear.
|
|
|
|
|
|
|
|
// Check whether the Document object is an already initialised one.
|
|
|
|
if (self.document->fakeClientFd >= 0)
|
|
|
|
return;
|
|
|
|
|
2018-09-11 01:30:55 -05:00
|
|
|
[self.document openWithCompletionHandler:^(BOOL success) {
|
|
|
|
if (success) {
|
|
|
|
// Display the content of the document
|
|
|
|
} else {
|
|
|
|
// Make sure to handle the failed import appropriately, e.g., by presenting an error message to the user.
|
|
|
|
}
|
|
|
|
}];
|
|
|
|
}
|
|
|
|
|
|
|
|
- (IBAction)dismissDocumentViewController {
|
|
|
|
[self dismissViewControllerAnimated:YES completion:^ {
|
2018-10-17 07:59:19 -05:00
|
|
|
[self.document closeWithCompletionHandler:^(BOOL success){
|
2018-11-06 09:39:38 -06:00
|
|
|
LOG_TRC("close completion handler gets " << (success?"YES":"NO"));
|
2018-10-17 07:59:19 -05:00
|
|
|
}];
|
2018-09-11 01:30:55 -05:00
|
|
|
}];
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)webView:(WKWebView *)webView didCommitNavigation:(WKNavigation *)navigation {
|
2018-11-06 09:39:38 -06:00
|
|
|
LOG_TRC("didCommitNavigation: " << [[navigation description] UTF8String]);
|
2018-09-11 01:30:55 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
- (void)webView:(WKWebView *)webView didFailNavigation:(WKNavigation *)navigation withError:(NSError *)error {
|
2018-11-06 09:39:38 -06:00
|
|
|
LOG_TRC("didFailNavigation: " << [[navigation description] UTF8String]);
|
2018-09-11 01:30:55 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
- (void)webView:(WKWebView *)webView didFailProvisionalNavigation:(WKNavigation *)navigation withError:(NSError *)error {
|
2018-11-06 09:39:38 -06:00
|
|
|
LOG_TRC("didFailProvisionalNavigation: " << [[navigation description] UTF8String]);
|
2018-09-11 01:30:55 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
- (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation {
|
2018-11-06 09:39:38 -06:00
|
|
|
LOG_TRC("didFinishNavigation: " << [[navigation description] UTF8String]);
|
2018-09-11 01:30:55 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
- (void)webView:(WKWebView *)webView didReceiveServerRedirectForProvisionalNavigation:(WKNavigation *)navigation {
|
2018-11-06 09:39:38 -06:00
|
|
|
LOG_TRC("didReceiveServerRedirectForProvisionalNavigation: " << [[navigation description] UTF8String]);
|
2018-09-11 01:30:55 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
- (void)webView:(WKWebView *)webView didStartProvisionalNavigation:(WKNavigation *)navigation {
|
2018-11-06 09:39:38 -06:00
|
|
|
LOG_TRC("didStartProvisionalNavigation: " << [[navigation description] UTF8String]);
|
2018-09-11 01:30:55 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler {
|
2018-11-06 09:39:38 -06:00
|
|
|
LOG_TRC("decidePolicyForNavigationAction: " << [[navigationAction description] UTF8String]);
|
2018-09-11 01:30:55 -05:00
|
|
|
decisionHandler(WKNavigationActionPolicyAllow);
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)webView:(WKWebView *)webView decidePolicyForNavigationResponse:(WKNavigationResponse *)navigationResponse decisionHandler:(void (^)(WKNavigationResponsePolicy))decisionHandler {
|
2018-11-06 09:39:38 -06:00
|
|
|
LOG_TRC("decidePolicyForNavigationResponse: " << [[navigationResponse description] UTF8String]);
|
2018-09-11 01:30:55 -05:00
|
|
|
decisionHandler(WKNavigationResponsePolicyAllow);
|
|
|
|
}
|
|
|
|
|
|
|
|
- (WKWebView *)webView:(WKWebView *)webView createWebViewWithConfiguration:(WKWebViewConfiguration *)configuration forNavigationAction:(WKNavigationAction *)navigationAction windowFeatures:(WKWindowFeatures *)windowFeatures {
|
2018-11-06 09:39:38 -06:00
|
|
|
LOG_TRC("createWebViewWithConfiguration");
|
2018-09-11 01:30:55 -05:00
|
|
|
return webView;
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)webView:(WKWebView *)webView runJavaScriptAlertPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(void))completionHandler {
|
2018-11-06 09:39:38 -06:00
|
|
|
LOG_TRC("runJavaScriptAlertPanelWithMessage: " << [message UTF8String]);
|
2018-09-11 01:30:55 -05:00
|
|
|
// UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@""
|
|
|
|
// message:message
|
|
|
|
// delegate:nil
|
|
|
|
// cancelButtonTitle:nil
|
|
|
|
// otherButtonTitles:@"OK", nil];
|
|
|
|
// [alert show];
|
|
|
|
completionHandler();
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)webView:(WKWebView *)webView runJavaScriptConfirmPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(BOOL result))completionHandler {
|
2018-11-06 09:39:38 -06:00
|
|
|
LOG_TRC("runJavaScriptConfirmPanelWithMessage: " << [message UTF8String]);
|
2018-09-11 01:30:55 -05:00
|
|
|
completionHandler(YES);
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)webView:(WKWebView *)webView runJavaScriptTextInputPanelWithPrompt:(NSString *)prompt defaultText:(NSString *)defaultText initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(NSString *result))completionHandler {
|
2018-11-06 09:39:38 -06:00
|
|
|
LOG_TRC("runJavaScriptTextInputPanelWithPrompt: " << [prompt UTF8String]);
|
2018-09-11 01:30:55 -05:00
|
|
|
completionHandler(@"Something happened.");
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message {
|
2018-09-13 11:16:00 -05:00
|
|
|
int rc;
|
|
|
|
struct pollfd p;
|
|
|
|
|
2018-09-11 01:30:55 -05:00
|
|
|
if ([message.name isEqualToString:@"error"]) {
|
2018-11-06 09:39:38 -06:00
|
|
|
LOG_ERR("Error from WebView: " << [message.body UTF8String]);
|
2018-09-11 01:30:55 -05:00
|
|
|
} else if ([message.name isEqualToString:@"debug"]) {
|
2018-11-09 17:26:12 -06:00
|
|
|
LOG_TRC_NOFILE("==> " << [message.body UTF8String]);
|
2018-09-11 01:30:55 -05:00
|
|
|
} else if ([message.name isEqualToString:@"lool"]) {
|
2018-11-28 17:01:12 -06:00
|
|
|
NSString *subBody = [message.body substringToIndex:std::min(100ul, ((NSString*)message.body).length)];
|
2018-11-28 16:23:39 -06:00
|
|
|
if (subBody.length < ((NSString*)message.body).length)
|
|
|
|
subBody = [subBody stringByAppendingString:@"..."];
|
|
|
|
|
|
|
|
LOG_TRC("To Online: " << [subBody UTF8String]);
|
2018-09-13 11:16:00 -05:00
|
|
|
|
|
|
|
if ([message.body isEqualToString:@"HULLO"]) {
|
|
|
|
// Now we know that the JS has started completely
|
|
|
|
|
|
|
|
// Contact the permanently (during app lifetime) listening LOOLWSD server
|
|
|
|
// "public" socket
|
|
|
|
assert(loolwsd_server_socket_fd != -1);
|
|
|
|
rc = fakeSocketConnect(self.document->fakeClientFd, loolwsd_server_socket_fd);
|
|
|
|
assert(rc != -1);
|
|
|
|
|
2018-10-12 09:48:45 -05:00
|
|
|
// Create a socket pair to notify the below thread when the document has been closed
|
|
|
|
fakeSocketPipe2(closeNotificationPipeForForwardingThread);
|
|
|
|
|
2018-09-13 11:16:00 -05:00
|
|
|
// Start another thread to read responses and forward them to the JavaScript
|
|
|
|
dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0),
|
|
|
|
^{
|
2018-10-16 09:20:55 -05:00
|
|
|
Util::setThreadName("app2js");
|
2018-09-13 11:16:00 -05:00
|
|
|
while (true) {
|
2018-10-12 09:48:45 -05:00
|
|
|
struct pollfd p[2];
|
|
|
|
p[0].fd = self.document->fakeClientFd;
|
|
|
|
p[0].events = POLLIN;
|
|
|
|
p[1].fd = self->closeNotificationPipeForForwardingThread[1];
|
|
|
|
p[1].events = POLLIN;
|
|
|
|
if (fakeSocketPoll(p, 2, -1) > 0) {
|
|
|
|
if (p[1].revents == POLLIN) {
|
|
|
|
// The code below handling the "BYE" fake Websocket
|
|
|
|
// message has closed the other end of the
|
|
|
|
// closeNotificationPipeForForwardingThread. Let's close
|
|
|
|
// the other end too just for cleanliness, even if a
|
|
|
|
// FakeSocket as such is not a system resource so nothing
|
|
|
|
// is saved by closing it.
|
2018-10-15 16:01:04 -05:00
|
|
|
fakeSocketClose(self->closeNotificationPipeForForwardingThread[1]);
|
2018-10-12 09:48:45 -05:00
|
|
|
|
|
|
|
// Close our end of the fake socket connection to the
|
|
|
|
// ClientSession thread, so that it terminates
|
|
|
|
fakeSocketClose(self.document->fakeClientFd);
|
|
|
|
|
2018-09-13 11:16:00 -05:00
|
|
|
return;
|
2018-10-12 09:48:45 -05:00
|
|
|
}
|
|
|
|
if (p[0].revents == POLLIN) {
|
|
|
|
int n = fakeSocketAvailableDataLength(self.document->fakeClientFd);
|
|
|
|
if (n == 0)
|
|
|
|
return;
|
|
|
|
std::vector<char> buf(n);
|
|
|
|
n = fakeSocketRead(self.document->fakeClientFd, buf.data(), n);
|
|
|
|
[self.document send2JS:buf.data() length:n];
|
|
|
|
}
|
2018-09-13 11:16:00 -05:00
|
|
|
}
|
|
|
|
else
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
assert(false);
|
|
|
|
});
|
|
|
|
|
|
|
|
// First we simply send it the URL. This corresponds to the GET request with Upgrade to
|
|
|
|
// WebSocket.
|
|
|
|
std::string url([[[self.document fileURL] absoluteString] UTF8String]);
|
|
|
|
p.fd = self.document->fakeClientFd;
|
|
|
|
p.events = POLLOUT;
|
2018-09-19 12:01:13 -05:00
|
|
|
fakeSocketPoll(&p, 1, -1);
|
2018-09-13 11:16:00 -05:00
|
|
|
fakeSocketWrite(self.document->fakeClientFd, url.c_str(), url.size());
|
|
|
|
|
2018-10-12 09:48:45 -05:00
|
|
|
return;
|
|
|
|
} else if ([message.body isEqualToString:@"BYE"]) {
|
2018-11-06 09:39:38 -06:00
|
|
|
LOG_TRC("Document window terminating on JavaScript side. Closing our end of the socket.");
|
2018-10-12 09:48:45 -05:00
|
|
|
|
|
|
|
// Close one end of the socket pair, that will wake up the forwarding thread above
|
|
|
|
fakeSocketClose(closeNotificationPipeForForwardingThread[0]);
|
|
|
|
|
2018-11-27 05:18:24 -06:00
|
|
|
[self.document saveToURL:[self.document fileURL]
|
|
|
|
forSaveOperation:UIDocumentSaveForOverwriting
|
|
|
|
completionHandler:^(BOOL success) {
|
|
|
|
LOG_TRC("save completion handler gets " << (success?"YES":"NO"));
|
|
|
|
}];
|
|
|
|
|
2018-10-15 16:01:04 -05:00
|
|
|
[self dismissDocumentViewController];
|
2018-09-13 11:16:00 -05:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
const char *buf = [message.body UTF8String];
|
|
|
|
p.fd = self.document->fakeClientFd;
|
|
|
|
p.events = POLLOUT;
|
2018-09-19 12:01:13 -05:00
|
|
|
fakeSocketPoll(&p, 1, -1);
|
2018-09-13 11:16:00 -05:00
|
|
|
fakeSocketWrite(self.document->fakeClientFd, buf, strlen(buf));
|
2018-09-11 01:30:55 -05:00
|
|
|
} else {
|
2018-11-06 09:39:38 -06:00
|
|
|
LOG_ERR("Unrecognized kind of message received from WebView: " << [message.name UTF8String] << ":" << [message.body UTF8String]);
|
2018-09-11 01:30:55 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@end
|
|
|
|
|
|
|
|
// vim:set shiftwidth=4 softtabstop=4 expandtab:
|