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月21日木曜日
2016年7月17日日曜日
WebViewのJavaScriptを無効化する
WebViewが持つWebPreferencesのjavaScriptEnabledをNOに設定すれば良い。
参考: WebPreferences Class Reference - developer.apple.com
なお、xibでインスタンスを作っている場合はEnable JavaScriptというチェックボックスがあったはず。
ソースコード中で自分でallocした場合はデフォルトでYESになっている模様。
self.view.preferences.javaScriptEnabled = NO;
参考: WebPreferences Class Reference - developer.apple.com
なお、xibでインスタンスを作っている場合はEnable JavaScriptというチェックボックスがあったはず。
ソースコード中で自分でallocした場合はデフォルトでYESになっている模様。
2015年7月29日水曜日
WebKitでズーム(拡大縮小)用カーソルを指定するスタイル
WebKitでは
参考: Introducing the New CSS Cursor Styles in CSS3
Safariで画像を直接開いたときにウインドウサイズ以上の画像は拡大縮小できるじゃないですか。
あれをJS+CSSを使って自分のWebView上で再現してみたいと思っています。
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**で書き出すのが簡単なようです。
他にも色々アプローチがある模様。
参考: How do you save to WebArchive webView editable content?
参考: webArchive | raizan2ame
自作アプリで保存した.webarchiveがSafariで読めない場合は
自作アプリで保存した.webarchiveファイルが開けないも参考にしてみてください。
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ファイルが開けないも参考にしてみてください。
ラベル:
Cocoa,
Mac,
Objective-C,
WebView
自作アプリで保存した.webarchiveファイルが開けない
一時的な解決策としては、システム環境設定 - セキュリティとプライバシー - 一般 - ダウンロードしたアプリケーションの実行許可:で「すべてのアプリケーションを許可」に変更すると開ける。
(英語だとSystemPreferences - Security & Privacy - General - Allow apps downloaded from: - Anywhere)
要するに、code sign無しのアプリと同じ扱いにされているようだった。
これ以外の回避方法はまだ不明。
ダブルクリックで開こうとすると、
ただしダブルクリックでは開けないものの、
Safariの開くメニューからだとシステム環境設定をいじらずとも開くことができた。
なお、Console.appでは
http://www.osstatus.com/search/results?platform=all&framework=all&search=-67026
Safari自身が書き出した.webarchiveファイルはダブルクリックで普通に開けるようで、
ファイルを書き出したアプリがそのファイルを読み込むのは問題無いという扱いなのかも。
(英語だとSystemPreferences - Security & Privacy - General - Allow apps downloaded from: - Anywhere)
要するに、code sign無しのアプリと同じ扱いにされているようだった。
これ以外の回避方法はまだ不明。
ダブルクリックで開こうとすると、
“hogehoge.webarchive”は壊れているため開けません。 “ゴミ箱”に入れる必要があります。
このファイルは“fugafuga”により今日の 11:21 に作成されました。というようなアラートがでて開けなかった。
ただしダブルクリックでは開けないものの、
Safariの開くメニューからだとシステム環境設定をいじらずとも開くことができた。
なお、Console.appでは
というエラーが出ており、-67026はOSStatus.comによればerrSecCSFileHardQuarantinedというエラーの模様。2015/07/27 11:20:16.244 CoreServicesUIAgent[15194]: Error SecAssessmentCreate: 操作を完了できませんでした。(OSStatus エラー -67026)
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
参考: 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
参考: 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
コンテキストメニューを表示させなくすることが可能です。
参考: 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で済ませました。
また、クロスプラットホームに対応させる場合には、
他にもいろいろ指定するする必要があるようです。
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:を実行させたい場合を考えます。大雑把な手順と実装例は以下のとおりです。
詳細割愛。
2. delegateで-[WebFrameLoadDelegate webView:didClearWindowObject:forFrame:]を実装する
おそらくJS上でdelegateをconsoleとして扱うよう設定しているんだと思います。setValue:forKey:はKVC由来でしょうか。なお、CallJSではwebView:windowScriptObjectAvailable:で同様の処理を行っていますが、10.4でdeprecatedになっているようです。- (void)webView:(WebView *)webView didClearWindowObject:(WebScriptObject *)windowObject forFrame:(WebFrame *)frame {[windowObject setValue:self forKey:@"console"];}
3. delegateで+[NSObject(WebScripting) isSelectorExcludedFromWebScript:]を実装する
doConsoleLog:だけJSから実行可能にしているんだと思います。+ (BOOL)isSelectorExcludedFromWebScript:(SEL)selector {return selector != @selector(doConsoleLog:);}
4. delegateで+[NSObject(WebScripting) webScriptNameForSelector:]を実装する
こちらはJSとObjCとの間でメソッドを関連づけているのだと思います。+ (NSString *) webScriptNameForSelector:(SEL)sel {return (sel == @selector(doConsoleLog:)) ? @"log" : nil;}
5. delegateでconsole.log()実行時に呼ばれるメソッドを実装する
console.log()の引数がNSStringとして渡されてきます。- (void)doConsoleLog:(NSString *)message {NSLog(@"message:%@", message);}
以上で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で、例えば以下のようなキーと値を持っています。
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
参考: [webkit-dev] How to catch JS exceptions/log messages from a WebView?
参考: WebUIDelegatePrivate.h
第2引数はNSDictionaryで、例えば以下のようなキーと値を持っています。
jqueryを入れ忘れた時のエラーメッセージですね。message:{MessageLevel = ErrorMessageLevel;MessageSource = JSMessageSource;columnNumber = 2;lineNumber = 119;message = "ReferenceError: Can't find variable: $";sourceURL = "file:///foo/bar/js.js";}
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側:
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:]は不完全らしいので、さらに改善が必要かもしれません。
objc側:
JS側:- (void)updateWithHTML:(NSString *)html {html = [html stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];NSData *data = [html dataUsingEncoding:NSUTF8StringEncodingallowLossyConversion:NO];NSString *base64String = [data base64EncodedStringWithOptions:0];NSString *jsString = [NSString stringWithFormat:@"updateWithHTML('%@');", base64String];NSString *result = [self.webView stringByEvaluatingJavaScriptFromString:jsString];NSLog(@"result:%@", result);}
function updateWithHTML(base64html) {var html = decodeURIComponent(window.atob(base64html));// do some stuffreturn '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:]は不完全らしいので、さらに改善が必要かもしれません。
ラベル:
JavaScript,
Objective-C,
WebView
2015年2月3日火曜日
WebView(not UIWebView)でHTTPリクエストヘッダを付加する
MacOSのWebKitでは、WebResourceLoadDelegateのwebView:resource:willSendRequest:redirectResponse:fromDataSource:が呼ばれたタイミングで個々のリソースに対するリクエストヘッダを変更することができます。
下記の例では、メインフレーム取得時にIf-Modified-Sinceを付加しています。
下記の例では、メインフレーム取得時にIf-Modified-Sinceを付加しています。
なお、self.lastModifiedは前回リクエスト時にレスポンスヘッダのLast-Modifiedを保存したものです。#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;}
WebView(not UIWebView)でHTTPステータスコードを取得する
MacOSのWebViewではWebDataSourceのresponseプロパティからstatusCodeを参照できます。
例えばWebFrameLoadDelegateのwebView:didFinishLoadForFrame:で
同様に、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の両方で同じことをするなら後者の方法を採用するのが良いかもしれません。
例えばWebFrameLoadDelegateのwebView:didFinishLoadForFrame:で
てな感じでメインフレームの取得結果を表示することができます。メインフレームだけでなく、frame.dataSource.subresourcesで全リソースの取得結果を参照することも可能です。#pragma mark WebFrameLoadDelegate- (void)webView:(WebView *)sender didFinishLoadForFrame:(WebFrame *)frame {if ([[sender mainFrame] isEqualTo:frame]) {NSLog(@"%@", @([(NSHTTPURLResponse *)[frame.dataSource response] statusCode]));}// do something}
同様に、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の両方で同じことをするなら後者の方法を採用するのが良いかもしれません。
登録:
投稿 (Atom)