Attention! Translated article might be found on my English blog.
ラベル WebView の投稿を表示しています。 すべての投稿を表示
ラベル WebView の投稿を表示しています。 すべての投稿を表示

2016年7月21日木曜日

WebView内のJavaScriptで実行した通信結果を取得する

JavaScriptでの通信結果を取得するには、WebViewのWebResourceLoadDelegateでwebView:resource:didFinishLoadingFromDataSource:を実装すれば良い。
ただし、WebDataSourceが持つsubresourcesを個々に調べる必要がありそう。(subresourcesはWebResourceの配列)

どこと通信したかはURLプロパティ、通信内容はdataプロパティを見る。
dataはNSDataだが、文字列の場合は-[NSString initWithData:encoding:]でNSStringにできる。

参考: WebResourceLoadDelegate Protocol Reference - Apple Developer

2016年7月17日日曜日

WebViewのJavaScriptを無効化する

WebViewが持つWebPreferencesのjavaScriptEnabledをNOに設定すれば良い。

self.view.preferences.javaScriptEnabled = NO;

参考: WebPreferences Class Reference - developer.apple.com

なお、xibでインスタンスを作っている場合はEnable JavaScriptというチェックボックスがあったはず。
ソースコード中で自分でallocした場合はデフォルトでYESになっている模様。

2015年7月29日水曜日

WebKitでズーム(拡大縮小)用カーソルを指定するスタイル

WebKitでは
cursor: -webkit-zoom-in
もしくは
cursor: -webkit-zoom-out
というスタイルでズームイン、ズームアウトのカーソルが使えるようです。

参考: Introducing the New CSS Cursor Styles in CSS3

Safariで画像を直接開いたときにウインドウサイズ以上の画像は拡大縮小できるじゃないですか。
あれをJS+CSSを使って自分のWebView上で再現してみたいと思っています。

2015年7月27日月曜日

WebViewの表示内容を.webarchiveとして書き出す

[[[[webView mainFrame] DOMDocument] webArchive] data]でNSDataに変換し、
writeTo**で書き出すのが簡単なようです。

    NSData *data = [[[[self.webView mainFrame] DOMDocument] webArchive] data];
    BOOL success = [data writeToFile:path atomically:YES];

他にも色々アプローチがある模様。
参考: How do you save to WebArchive webView editable content?
参考: webArchive | raizan2ame

自作アプリで保存した.webarchiveがSafariで読めない場合は
自作アプリで保存した.webarchiveファイルが開けないも参考にしてみてください。

自作アプリで保存した.webarchiveファイルが開けない

一時的な解決策としては、システム環境設定 - セキュリティとプライバシー - 一般 - ダウンロードしたアプリケーションの実行許可:で「すべてのアプリケーションを許可」に変更すると開ける。
(英語だとSystemPreferences - Security & Privacy - General - Allow apps downloaded from: - Anywhere)

要するに、code sign無しのアプリと同じ扱いにされているようだった。
これ以外の回避方法はまだ不明。

ダブルクリックで開こうとすると、
“hogehoge.webarchive”は壊れているため開けません。 “ゴミ箱”に入れる必要があります。
このファイルは“fugafuga”により今日の 11:21 に作成されました。 
というようなアラートがでて開けなかった。
ただしダブルクリックでは開けないものの、
Safariの開くメニューからだとシステム環境設定をいじらずとも開くことができた。

なお、Console.appでは
2015/07/27 11:20:16.244 CoreServicesUIAgent[15194]: Error SecAssessmentCreate: 操作を完了できませんでした。(OSStatus エラー -67026)
というエラーが出ており、-67026はOSStatus.comによればerrSecCSFileHardQuarantinedというエラーの模様。
http://www.osstatus.com/search/results?platform=all&framework=all&search=-67026

Safari自身が書き出した.webarchiveファイルはダブルクリックで普通に開けるようで、
ファイルを書き出したアプリがそのファイルを読み込むのは問題無いという扱いなのかも。




2015年7月20日月曜日

WebViewへのドラッグ&ドロップを禁止する

WebViewのWebUIDelegateでwebView:dragDestinationActionMaskForDraggingInfo:を実装し、WebDragDestinationActionNoneを返せば良い。

参考: WebUIDelegate Protocol Reference

NSWindowのタイトルバーをQuickTime Playerのように隠したかったが失敗したメモ

参考: insidegui/FOTWindow · GitHub - タイトルバーを自前で描画
参考: irlabs/TransparentWebView · GitHub - タイトルバーの有無を切り替える度にウインドウを生成
参考: objective c - Hide NSWindow title bar - Stack Overflow - タイトルバー表示/非表示の方法


タイトルバーのアルファを変える手法で実装を試みた。

10.9 SDKではxib上のNSWindowの"Full Size Content View"プロパティにチェックを付けると
警告が出てしまうのだが、
これを付けないとタイトルバーの描画が更新されなかった。

また、WebViewの場合はどういうわけかタイトルバー部分が描画されなかった。
(逆に言うと普通のNSViewサブクラスの場合は問題無さげだった)
描画されないと言うか、あくまでタイトルバーを除いた領域を全体として描画されて
しまった。
CALayerが関連だろうか?

WebViewの実装を見てみるべきか。WebKit Nightly Builds

2015年7月10日金曜日

WebView(not UIWebView)でコンテキストメニューを表示させない方法

WebUIDelegateのwebView:contextMenuItemsForElement:defaultMenuItems:を実装しnilを返すことで、
コンテキストメニューを表示させなくすることが可能です。

参考: WebUIDelegate Protocol

2015年7月7日火曜日

CSSを使って英語文字列の途中で強制改行する

【CSS】長い文字列をCSSで強制的に改行させたいときは『word-wrap:break-word;』を使うべし。 - すたら日記によると、
word-wrap: break-word;を設定すれば可能のようでした。

しかし上記サイトの解説の通り、同時にwidthを指定する必要がありました。
試してみたところ、widthは%ではなくpxなどの固定値でなければならないようで、
ウインドウリサイズに追随しないという問題がありました。

他の解決策を探したところ、Word wrapping/hyphenation using CSS. — Kenneth Auchenbergに
動的なリサイズでも改行してくれる方法がありました。 
今回はWebKitだけ対応できれば良いので、word-break:break-all;を設定したところ、
無事リサイズに追随して改行されるようになりました。 

なお記事にある通り、break-allだとワード単位で区切られないという副作用があり、
break-wordという非公式な値を指定することでこの副作用を抑制しているようです。
自分の場合はそこまで神経質になる必要が無いのでbreak-allで済ませました。

また、クロスプラットホームに対応させる場合には、
他にもいろいろ指定するする必要があるようです。

2015年5月28日木曜日

Safariと自作アプリでセッション(cookie)を共有する方法を調べた

2016/10/01 追記: 10.11から、アプリケーション間でcookieを共有できなくなった様です。
NSHTTPCookieStorage.hのsharedHTTPCookieStorageのdiscussionによると、
Starting in OS X 10.11, each app has its own sharedHTTPCookieStorage singleton, 
which will not be shared with other applications.
とのこと。むう…ユーザにいちいちログインしてもらったりするのはフィッシングサイト(アプリ)だと疑われそうで嫌ですね…。
2015/08/07 追記:WebViewを使えばSafariとセッションを共有できているようです。
共有するような設定ってどっかに有ったっけか…。
基本的に何もしなくても共有されるような気がします。
[NSHTTPCookieStorage sharedHTTPCookieStorage]というのが使えそう。

参考: Safari 5.1 cookie format specs - Stack Overflow
参考: NSHTTPCookieStorage Class Reference

なお、Safariのクッキーは~/Library/Cookies内で保存している模様。

参考: (旧) Cocoaの日々: Safari のクッキーはどう保存されているのか?

但し、現在はplistではなく、.binarycookiesというフォーマットのようです。
.binarycookiesは普通のバイナリですが、解析用スクリプトが色々あるようで、
こちらからもアプローチできそうです。

参考: Another Forensics Blog: Safari Binary Cookies - Now with more parsing power!
参考: Safari/iOS - Cookies.binarycookies reader

2015年5月10日日曜日

WebViewでのconsole.log()の内容を取得する

WebView上でconsole.log()を実行した時にWebFrameLoadDelegateのdoConsoleLog:を実行させたい場合を考えます。大雑把な手順と実装例は以下のとおりです。


1. WebViewにWebFrameLoadDelegateをセットする

  詳細割愛。

2. delegateで-[WebFrameLoadDelegate webView:didClearWindowObject:forFrame:]を実装する

- (void)webView:(WebView *)webView didClearWindowObject:(WebScriptObject *)windowObject forFrame:(WebFrame *)frame {
    [windowObject setValue:self forKey:@"console"];
}
おそらくJS上でdelegateをconsoleとして扱うよう設定しているんだと思います。setValue:forKey:はKVC由来でしょうか。なお、CallJSではwebView:windowScriptObjectAvailable:で同様の処理を行っていますが、10.4でdeprecatedになっているようです。

3. delegateで+[NSObject(WebScripting) isSelectorExcludedFromWebScript:]を実装する

+ (BOOL)isSelectorExcludedFromWebScript:(SEL)selector {
    return selector != @selector(doConsoleLog:);
}
doConsoleLog:だけJSから実行可能にしているんだと思います。

4. delegateで+[NSObject(WebScripting) webScriptNameForSelector:]を実装する
+ (NSString *) webScriptNameForSelector:(SEL)sel {
    return (sel == @selector(doConsoleLog:)) ? @"log" : nil;
}
こちらはJSとObjCとの間でメソッドを関連づけているのだと思います。

5. delegateでconsole.log()実行時に呼ばれるメソッドを実装する
- (void)doConsoleLog:(NSString *)message {
    NSLog(@"message:%@", message);
}
console.log()の引数がNSStringとして渡されてきます。

以上でconsole.log()をNSLog()で表示することが出来るようになります。
少しだけObjC-JS間のデバッグが楽になるんじゃないでしょうか…!

参考: CallJS - Apple Developer

WebViewのJavaScriptエラーを取得する(デバッグ用途のみ)

デバッグ用途のみであれば、隠しAPIの[WebUIDelegatePrivate webView:addMessageToConsole:]をWebUIDelegateで実装すると、JavaScriptエラー発生時に内容を取得できるようです。

参考: [webkit-dev] How to catch JS exceptions/log messages from a WebView?
参考: WebUIDelegatePrivate.h

第2引数はNSDictionaryで、例えば以下のようなキーと値を持っています。

message:{
    MessageLevel = ErrorMessageLevel;
    MessageSource = JSMessageSource;
    columnNumber = 2;
    lineNumber = 119;
    message = "ReferenceError: Can't find variable: $";
    sourceURL = "file:///foo/bar/js.js";
}

jqueryを入れ忘れた時のエラーメッセージですね。

iOSの場合はこちらのアプローチも参考になるかもです。

参考: How can my iPhone Objective-C code get notified of Javascript errors in a UIWebView? - Stack Overflow
参考: ios - Setting UIDelegate for UIWebView - Stack Overflow

2015年5月9日土曜日

Objective-CからJavaScriptに複雑な文字列を渡したかった

結論から言うと、UTF8でURLエンコードとBase64エンコードを重ねて対処しました。

objc側:

- (void)updateWithHTML:(NSString *)html {
    html = [html stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
    
    NSData *data = [html dataUsingEncoding:NSUTF8StringEncoding
                      allowLossyConversion:NO];
    
    NSString *base64String = [data base64EncodedStringWithOptions:0];
    
    NSString *jsString = [NSString stringWithFormat:@"updateWithHTML('%@');", base64String];
    
    NSString *result = [self.webView stringByEvaluatingJavaScriptFromString:jsString];
    
    NSLog(@"result:%@", result);
}
JS側:
function updateWithHTML(base64html) {
    var html = decodeURIComponent(window.atob(base64html));
    
    // do some stuff
    
    return 'OK';
}


Base64エンコードを使う理由:
文字列に含まれる括弧やシングルクォートのせいか、JSを実行できていないようでした。
(objcコード中の引数htmlは特定ページを表現するための完全なHTML文字列で、JSコードも含んでいます)
JSON化も試してみたのですが、いまいち期待通り動かなかったためBase64化して渡すことにしました。
うまく使えばJSONでも解決できそうなのですが…必要があればまた検討します。

URLエンコードを使う理由: 
URLエンコード無しの場合、JS側でBase64デコード後の日本語が文字化けしてしまいました。
window.btoa - Web API インターフェイス | MDNによると、Unicodeに対してatob()を使うと例外が出るようでした。
私の書いたJSコードをSafariで動かしてみても特に例外は出ていないようでしたが、
上記サイトの通りURLエンコードも行うようにしたところ、期待通り日本語文字列を取得できるようになりました。

なお、ios - How do I URL encode a string - Stack Overflowによると、[NSString stringByAddingPercentEscapesUsingEncoding:]は不完全らしいので、さらに改善が必要かもしれません。




2015年2月3日火曜日

WebView(not UIWebView)でHTTPリクエストヘッダを付加する

MacOSのWebKitでは、WebResourceLoadDelegateのwebView:resource:willSendRequest:redirectResponse:fromDataSource:が呼ばれたタイミングで個々のリソースに対するリクエストヘッダを変更することができます。
下記の例では、メインフレーム取得時にIf-Modified-Sinceを付加しています。
#pragma mark WebResourceLoadDelegate

- (NSURLRequest *)webView:(WebView *)sender resource:(id)identifier willSendRequest:(NSURLRequest *)request redirectResponse:(NSURLResponse *)redirectResponse fromDataSource:(WebDataSource *)dataSource {
    if ([[sender mainFrameURL] isEqualToString:[[request URL] absoluteString]] && self.lastModified) {
        [(NSMutableURLRequest *)request addValue:self.lastModified forHTTPHeaderField:@"If-Modified-Since"];
    }
    return request;
}

 なお、self.lastModifiedは前回リクエスト時にレスポンスヘッダのLast-Modifiedを保存したものです。

WebView(not UIWebView)でHTTPステータスコードを取得する

MacOSのWebViewではWebDataSourceのresponseプロパティからstatusCodeを参照できます。
例えばWebFrameLoadDelegateのwebView:didFinishLoadForFrame:で
#pragma mark WebFrameLoadDelegate

- (void)webView:(WebView *)sender didFinishLoadForFrame:(WebFrame *)frame {
    if ([[sender mainFrame] isEqualTo:frame]) {
        NSLog(@"%@", @([(NSHTTPURLResponse *)[frame.dataSource response] statusCode]));
    }

    // do something
}

てな感じでメインフレームの取得結果を表示することができます。メインフレームだけでなく、frame.dataSource.subresourcesで全リソースの取得結果を参照することも可能です。
同様に、WebFrameLoadDelegate以外にもWebResourceLoadDelegateのwebView:resource:didReceiveResponse:fromDataSource:やwebView:resource:didFinishLoadingFromDataSource:のタイミングでも個々のリソースに対するレスポンスを参照することができます。

なお、UIWebViewではNSCachedURLResponseを使ったり、NSURLProtocolのサブクラスを使う方法があるようです。

参考:  Response headers / statusCode from a UIWebView (using workaround till the webview gets extended!)
参考: Objective-C - iOSのUIWebViewでHTTPステータスをチェックする - Qiita

もしWebViewとUIWebViewの両方で同じことをするなら後者の方法を採用するのが良いかもしれません。