🔥Firebase
夏休みなのでFirebaseの練習をした。
ライブフォトをシェアするSNSのプロトタイプを作った。ライブフォト好きだけど、他の人に共有しづらくて困っている。
📝参考にしたもの
基本的にFirebase Guidesを読んで作った。 これでだいたい分かるので、すごい。
ただ、Apple Developer Portalの設定が必要な箇所になると急に説明が雑になるので、そこは茅場町モバイルアプリもくもく会で教えてもらった。
🔥開発の様子: Firebase
ログイン
Firebase Authenticationを使うと、あっさりGoogleアカウントログインが作れる。便利。
Firestore
Firestoreでプロフィール情報を保存するようにした。
コンソールからデータが確認できて便利。
データの保存にはCodableFirebaseが便利だった。 Codable
な構造体を型安全に扱えてよい。
func get<T: Codable>(documentPath: String) -> SignalProducer<T?, NoError> { let ref = Firestore.defaultStore().collection("collectionName") return SignalProducer { observer, _ in ref.document(documentPath).getDocument { document, error in if let data = document?.data(), let entity = try? FirestoreDecoder().decode(T.self, from: data) { observer.send(value: entity) } observer.sendCompleted() } } }
Storage
Cloud Storageで画像を保存できるようにした。
通知
Firebase Cloud MessagingとCloud Functionsを使ってfav通知を作った。
Cloud MessagingでAPNsを使う方法は2通りあるがAPNs authentication keyのほうが、どのアプリでも使えて便利。(茅場町モバイルアプリもくもく会で教えてもらった)
通知のロジックを書く場所が分からなかったが、Cloud FunctionsでDB等の変更を監視して通知を送るのがよいらしい。
(What can I do with Cloud Functions?から引用)
コンソールから通知を送る方法が分からなかったが、Growカテゴリの下にあった。
📱進捗: Firebase以外
差分更新
画面更新にはDifferenceKitを使った差分更新を採用した。reloadData
を読んでた箇所をDifferenceKitに書き換えるだけでよかったので便利。
index c7e6661..ab0d428 100644 --- a/Sources/Controllers/PhotosViewController.swift +++ b/Sources/Controllers/PhotosViewController.swift @@ -5,6 +5,7 @@ // Created by mzp on 2018/08/20. // Copyright © 2018 mzp. All rights reserved. // +import DifferenceKit import MobileCoreServices import NorthLayout import Photos @@ -40,11 +41,15 @@ public class PhotosViewController: UICollectionViewController, UIBarButtonItem(barButtonSystemItem: .add, target: self, action: #s elector(add)) collectionView.register(UICollectionViewCell.self, forCellWithReuseIdentifier: kCell) + + self.dataSource.combinePrevious([]).signal.observeValues { oldValue, newValue in + let changeset = StagedChangeset(source: oldValue, target: newValue) + self.collectionView.reload(using: changeset) { _ in } + } PhotoFetchLatest().call().collect().startWithResult { switch $0 { case .success(let photos): self.dataSource.swap(photos) - self.collectionView.reloadData() case .failure(.firebase(let error)): self.presentError(title: "Firebabse", message: error.message) case .failure(.filesystem(let error)):
ライブフォトの読み書き
ライブフォトの読み書きはアンドキュメントな機能を使う必要がある。
読み込みは、imagePickerController(_:,didFinishPickingMediaWithInfo:)
の info
から取得する。
extension ViewController: UIImagePickerControllerDelegate { public func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey: Any]) { guard let livePhoto = info[UIImagePickerController.InfoKey.livePhoto] as? PHLivePhoto else { return } guard let imageURL = photo.value(forKey: "imageURL") as? URL else { return } guard let videoURL = photo.value(forKey: "videoURL") as? URL else { return } // .... } }
作成は、PHLivePhoto
の request
に、 画像と動画のURLを渡すことで生成できる。 PHLivePhotoInfoCancelledKey
に対応する値が0になるまで待たないと生成されない。
func create(imageURL: URL, videoURL: URL) { PHLivePhoto.request( withResourceFileURLs: [imageURL, videoURL], placeholderImage: nil, targetSize: .zero, contentMode: .aspectFit) { livePhoto, info in if let canceled = info[PHLivePhotoInfoCancelledKey] as? NSNumber, canceled == 0, let livePhoto = livePhoto { // ... } } }
✨その他
GitGuardian
なにも考えずにPublicレポジトリでやっていたら、GitGuardianから警告メールが来た。 あわててrevokeしてprivate レポジトリにした。
夏休み
気分転換のために何度か近所の喫茶店に行ったが、夏休みの宿題をやっているらしき兄弟がいて、夏を感じた。
そうか東京の子どもは喫茶店で宿題やるのか。 そんな文化しらないぞ、という気持ちにもなる。
🐣個人esa
Misocaを退職したらMisocaのesaにアクセスできなくなった。不便だったので個人でesaを契約した。
📝日報
esa標準の日報テンプレートにしたがって「今日の作業」「発生した問題」「明日の予定」「所感」を書いている。
一緒に日報書きましょうと募集したら4人あつまった。毎日、みんな書いてて偉い。
見る人がいるのでローカルのメモとブログの中間の温度感になっている。 たのしい。往年の会員制SNSを彷彿させる。
「個人esaにメモ残すの楽しそう」「実際は4人くらい見てるので個人esaじゃない。会員制ブログみたいなイメージ」「mixiだ」「今はオンラインサロンって言葉があるんですよ」という会話をしてた
— mzp (@mzp) August 7, 2018
🛠各種メモ
esaは次の特徴があるのでメモを書くときにちょうどいい。
- Markdownでサクサク書ける。エディタも使いやすい。
- WIP/ShipItの区別がわかりやすい。
- 記事ごとに共有URLが発行できる
🐞WebKitへのバグレポート - みずぴー日記も最初はesaに書いて、まとまった段階ではてなブログに移した。 WIPではじめれるので、気軽に書きはじめれる。 また早くShipItしたいという気持ちも働く。
引越しに関するメモも置いてある。 これは、かなり頻繁に参照している。
イベントの予定もここに書いている。 共有URLを発行して関係者に共有できて便利。
✨その他
個人esaの契約どうしようかなーと思ってトップページにいったら、 mzpという人がおすすめコメントを寄せてた。 なるほどと納得したので契約した。
日報メンバーのりんすきさんがアプリのドキュメンテーションにesaを使いはじめてた。 esaの輪の広がっている。
🐞WebKitへのバグレポート
Safari 12では、日本語入力中でもTabキーによるフォーカス移動が動作する。
過去のバージョンやChromeなどとは動作が異なるためリグレッションバグと考え、WebKitにバグレポート(#188370)を行なった。 修正コミットがtrunkにされたので、いずれSafariでも修正されるはず。(2018-8-17追記: Version 12.0 (14606.1.36.1.3)で反映された)
💥遭遇
AquaSKKでは入力中にTabキーを押すと入力内容が補完される。 これを使ってTweetしようとした際に、フォーカスが移動してしまって困った。
問題箇所の切り分けのために、いろいろな環境で動作を確認した。その結果、以下のことが分かった。
- 標準の日本語入力でもAquaSKKでも発生する。
- Google Chromeでは発生しない。
- MojaveでもHigh Sierraでも発生する。
- Safari 11では発生しないが、Safari 12で発生する。
- Safari Technology PreviewやWebKit Nightlyでも発生する。
- Keyboard Event Viewerで見るかぎりJSレベルではイベントは変化してない。
どうやらWebKitで問題が発生しているらしい。
🛠WebKitのビルド
trunk(問題のあるビルド)
詳しく調べるためにWebKitを手元でビルドした。 Building WebKit | WebKitにいくつかの方法が記載されているが、今回はデバッガを利用したいのでXcodeによるビルドを選択した。
起動して問題が再現することを確認した。
古いWebKit(問題のないビルド)
比較のために問題が再現しないWebKitをビルドする。WebKit Tracからそれっぽいタグを選ぶ。 Safari 11.1.2 (13605.3.8)が問題ないことが分かっているので、Safari-605.3.8
を選択した。 後半の数字と日付がそれっぽいので選んだが、本当に対応してるかはよく分からない。
svn checkout https://svn.webkit.org/repository/webkit/tags/Safari-605.3.8
あとはtrunkと同様にXcodeのワークスペースを開いてビルドする。
👀スタックトレース
☕️ JavaScriptと入力メソッド - みずぴー日記を参考にコードをみてたらTabキーのハンドラみつけた。 これに辿りつくかどうかが肝では、と予想を立てる。
// Soruce/WebCore/page/EventHandler.cpp void EventHandler::defaultTabEventHandler(KeyboardEvent& event) { Ref<Frame> protectedFrame(m_frame); ASSERT(event.type() == eventNames().keydownEvent); // We should only advance focus on tabs if no special modifier keys are held down. if (event.ctrlKey() || event.metaKey() || event.altGraphKey()) return; Page* page = m_frame.page(); if (!page) return; if (!page->tabKeyCyclesThroughElements()) return; FocusDirection focusDirection = event.shiftKey() ? FocusDirectionBackward : FocusDirectionForward; // Tabs can be used in design mode editing. if (m_frame.document()->inDesignMode()) return; if (page->focusController().advanceFocus(focusDirection, &event)) event.setDefaultHandled(); }
trunkの defaultTabEventHandler
にブレイクポイントを仕掛けて、現在のスタックトレースを入手する。
trunkでのスタックトレース
#0 0x000000059024dbb8 in WebCore::EventHandler::defaultTabEventHandler(WebCore::KeyboardEvent&) at /Volumes/SwiftSSD/webkit/Source/WebCore/page/EventHandler.cpp:3940 #1 0x000000059024da35 in WebCore::EventHandler::defaultKeyboardEventHandler(WebCore::KeyboardEvent&) at /Volumes/SwiftSSD/webkit/Source/WebCore/page/EventHandler.cpp:3470 #2 0x000000058f9b0e24 in WebCore::Node::defaultEventHandler(WebCore::Event&) at /Volumes/SwiftSSD/webkit/Source/WebCore/dom/Node.cpp:2383 #3 0x000000058fc7809d in WebCore::HTMLInputElement::defaultEventHandler(WebCore::Event&) at /Volumes/SwiftSSD/webkit/Source/WebCore/html/HTMLInputElement.cpp:1165 #4 0x000000058f947846 in WebCore::callDefaultEventHandlersInBubblingOrder(WebCore::Event&, WebCore::EventPath const&) at /Volumes/SwiftSSD/webkit/Source/WebCore/dom/EventDispatcher.cpp:61 #5 0x000000058f9472ac in WebCore::EventDispatcher::dispatchEvent(WebCore::Node&, WebCore::Event&) at /Volumes/SwiftSSD/webkit/Source/WebCore/dom/EventDispatcher.cpp:175 #6 0x000000058f9b05cd in WebCore::Node::dispatchEvent(WebCore::Event&) at /Volumes/SwiftSSD/webkit/Source/WebCore/dom/Node.cpp:2329 #7 0x000000059024cbd4 in WebCore::EventHandler::internalKeyEvent(WebCore::PlatformKeyboardEvent const&) at /Volumes/SwiftSSD/webkit/Source/WebCore/page/EventHandler.cpp:3297 #8 0x000000059024c2d9 in WebCore::EventHandler::keyEvent(WebCore::PlatformKeyboardEvent const&) at /Volumes/SwiftSSD/webkit/Source/WebCore/page/EventHandler.cpp:3163 #9 0x0000000590dd531b in WebCore::UserInputBridge::handleKeyEvent(WebCore::PlatformKeyboardEvent const&, WebCore::InputSource) at /Volumes/SwiftSSD/webkit/Source/WebCore/replay/UserInputBridge.cpp:82 #10 0x0000000588bb3899 in WebKit::handleKeyEvent(WebKit::WebKeyboardEvent const&, WebCore::Page*) at /Volumes/SwiftSSD/webkit/Source/WebKit/WebProcess/WebPage/WebPage.cpp:2465 #11 0x0000000588bb3742 in WebKit::WebPage::keyEvent(WebKit::WebKeyboardEvent const&) at /Volumes/SwiftSSD/webkit/Source/WebKit/WebProcess/WebPage/WebPage.cpp:2476 #12 0x0000000588c5299a in void IPC::callMemberFunctionImpl<WebKit::WebPage, void (WebKit::WebPage::*)(WebKit::WebKeyboardEvent const&), std::__1::tuple<WebKit::WebKeyboardEvent>, 0ul>(WebKit::WebPage*, void (WebKit::WebPage::*)(WebKit::WebKeyboardEvent const&), std::__1::tuple<WebKit::WebKeyboardEvent>&&, std::__1::integer_sequence<unsigned long, 0ul>) at /Volumes/SwiftSSD/webkit/Source/WebKit/Platform/IPC/HandleMessage.h:41 #13 0x0000000588c528f0 in void IPC::callMemberFunction<WebKit::WebPage, void (WebKit::WebPage::*)(WebKit::WebKeyboardEvent const&), std::__1::tuple<WebKit::WebKeyboardEvent>, std::__1::integer_sequence<unsigned long, 0ul> >(std::__1::tuple<WebKit::WebKeyboardEvent>&&, WebKit::WebPage*, void (WebKit::WebPage::*)(WebKit::WebKeyboardEvent const&)) at /Volumes/SwiftSSD/webkit/Source/WebKit/Platform/IPC/HandleMessage.h:47 #14 0x0000000588c3da7a in void IPC::handleMessage<Messages::WebPage::KeyEvent, WebKit::WebPage, void (WebKit::WebPage::*)(WebKit::WebKeyboardEvent const&)>(IPC::Decoder&, WebKit::WebPage*, void (WebKit::WebPage::*)(WebKit::WebKeyboardEvent const&)) at /Volumes/SwiftSSD/webkit/Source/WebKit/Platform/IPC/HandleMessage.h:127 #15 0x0000000588c36142 in WebKit::WebPage::didReceiveWebPageMessage(IPC::Connection&, IPC::Decoder&) at /Volumes/SwiftSSD/webkit/WebKitBuild/WebKit/Build/Products/Debug/DerivedSources/WebKit2/WebPageMessageReceiver.cpp:213 #16 0x0000000588bba85e in WebKit::WebPage::didReceiveMessage(IPC::Connection&, IPC::Decoder&) at /Volumes/SwiftSSD/webkit/Source/WebKit/WebProcess/WebPage/WebPage.cpp:4038 #17 0x00000005882863ec in IPC::MessageReceiverMap::dispatchMessage(IPC::Connection&, IPC::Decoder&) at /Volumes/SwiftSSD/webkit/Source/WebKit/Platform/IPC/MessageReceiverMap.cpp:123 #18 0x0000000588e37e0d in WebKit::WebProcess::didReceiveMessage(IPC::Connection&, IPC::Decoder&) at /Volumes/SwiftSSD/webkit/Source/WebKit/WebProcess/WebProcess.cpp:645 #19 0x000000058816c93c in IPC::Connection::dispatchMessage(IPC::Decoder&) at /Volumes/SwiftSSD/webkit/Source/WebKit/Platform/IPC/Connection.cpp:940 #20 0x000000058815f998 in IPC::Connection::dispatchMessage(std::__1::unique_ptr<IPC::Decoder, std::__1::default_delete<IPC::Decoder> >) at /Volumes/SwiftSSD/webkit/Source/WebKit/Platform/IPC/Connection.cpp:967 #21 0x000000058816d4c4 in IPC::Connection::dispatchOneIncomingMessage() at /Volumes/SwiftSSD/webkit/Source/WebKit/Platform/IPC/Connection.cpp:1036 #22 0x00000005881864a8 in IPC::Connection::enqueueIncomingMessage(std::__1::unique_ptr<IPC::Decoder, std::__1::default_delete<IPC::Decoder> >)::$_14::operator()() at /Volumes/SwiftSSD/webkit/Source/WebKit/Platform/IPC/Connection.cpp:933 #23 0x00000005881863b9 in WTF::Function<void ()>::CallableWrapper<IPC::Connection::enqueueIncomingMessage(std::__1::unique_ptr<IPC::Decoder, std::__1::default_delete<IPC::Decoder> >)::$_14>::call() at /Volumes/SwiftSSD/webkit/WebKitBuild/WebKit/Build/Products/Debug/usr/local/include/wtf/Function.h:101 #24 0x000000059d58b96f in WTF::Function<void ()>::operator()() const at /Volumes/SwiftSSD/webkit/WebKitBuild/WebKit/Build/Products/Debug/usr/local/include/wtf/Function.h:56 #25 0x000000059d5e4103 in WTF::RunLoop::performWork() at /Volumes/SwiftSSD/webkit/Source/WTF/wtf/RunLoop.cpp:106 #26 0x000000059d5e4a94 in WTF::RunLoop::performWork(void*) at /Volumes/SwiftSSD/webkit/Source/WTF/wtf/cf/RunLoopCF.cpp:38
このスタックトレースを参考に、古いWebKitの各所にブレイクポイントを設定して動作を比較する。 その結果、EventDispatcher::dispatchEvent
が WebCore::callDefaultEventHandlersInBubblingOrder
を呼ぶかどうかの差を発見した。
605.3.8のスタックトレース
#0 0x00000003cee0dc25 in WebCore::EventDispatcher::dispatchEvent(WebCore::Node&, WebCore::Event&) at /Users/mzp/Safari-605.3.8/Source/WebCore/dom/EventDispatcher.cpp:176 #1 0x00000003cee6d81d in WebCore::Node::dispatchEvent(WebCore::Event&) at /Users/mzp/Safari-605.3.8/Source/WebCore/dom/Node.cpp:2330 #2 0x00000003cf5bfce5 in WebCore::EventHandler::internalKeyEvent(WebCore::PlatformKeyboardEvent const&) at /Users/mzp/Safari-605.3.8/Source/WebCore/page/EventHandler.cpp:3279 #3 0x00000003cf5bf419 in WebCore::EventHandler::keyEvent(WebCore::PlatformKeyboardEvent const&) at /Users/mzp/Safari-605.3.8/Source/WebCore/page/EventHandler.cpp:3145 #4 0x00000003d00bde4b in WebCore::UserInputBridge::handleKeyEvent(WebCore::PlatformKeyboardEvent const&, WebCore::InputSource) at /Users/mzp/Safari-605.3.8/Source/WebCore/replay/UserInputBridge.cpp:83 #5 0x00000003c8a26289 in WebKit::handleKeyEvent(WebKit::WebKeyboardEvent const&, WebCore::Page*) at /Users/mzp/Safari-605.3.8/Source/WebKit/WebProcess/WebPage/WebPage.cpp:2440 #6 0x00000003c8a26132 in WebKit::WebPage::keyEvent(WebKit::WebKeyboardEvent const&) at /Users/mzp/Safari-605.3.8/Source/WebKit/WebProcess/WebPage/WebPage.cpp:2451 #7 0x00000003c8ab982a in void IPC::callMemberFunctionImpl<WebKit::WebPage, void (WebKit::WebPage::*)(WebKit::WebKeyboardEvent const&), std::__1::tuple<WebKit::WebKeyboardEvent>, 0ul>(WebKit::WebPage*, void (WebKit::WebPage::*)(WebKit::WebKeyboardEvent const&), std::__1::tuple<WebKit::WebKeyboardEvent>&&, std::__1::integer_sequence<unsigned long, 0ul>) at /Users/mzp/Safari-605.3.8/Source/WebKit/Platform/IPC/HandleMessage.h:40 #8 0x00000003c8ab9780 in void IPC::callMemberFunction<WebKit::WebPage, void (WebKit::WebPage::*)(WebKit::WebKeyboardEvent const&), std::__1::tuple<WebKit::WebKeyboardEvent>, std::__1::integer_sequence<unsigned long, 0ul> >(std::__1::tuple<WebKit::WebKeyboardEvent>&&, WebKit::WebPage*, void (WebKit::WebPage::*)(WebKit::WebKeyboardEvent const&)) at /Users/mzp/Safari-605.3.8/Source/WebKit/Platform/IPC/HandleMessage.h:46 #9 0x00000003c8aa7f66 in void IPC::handleMessage<Messages::WebPage::KeyEvent, WebKit::WebPage, void (WebKit::WebPage::*)(WebKit::WebKeyboardEvent const&)>(IPC::Decoder&, WebKit::WebPage*, void (WebKit::WebPage::*)(WebKit::WebKeyboardEvent const&)) at /Users/mzp/Safari-605.3.8/Source/WebKit/Platform/IPC/HandleMessage.h:126 #10 0x00000003c8aa1070 in WebKit::WebPage::didReceiveWebPageMessage(IPC::Connection&, IPC::Decoder&) at /Users/mzp/Safari-605.3.8/WebKitBuild/WebKit/Build/Products/Debug/DerivedSources/WebKit2/WebPageMessageReceiver.cpp:204 #11 0x00000003c8a2cd4e in WebKit::WebPage::didReceiveMessage(IPC::Connection&, IPC::Decoder&) at /Users/mzp/Safari-605.3.8/Source/WebKit/WebProcess/WebPage/WebPage.cpp:3965 #12 0x00000003c824c5e8 in IPC::MessageReceiverMap::dispatchMessage(IPC::Connection&, IPC::Decoder&) at /Users/mzp/Safari-605.3.8/Source/WebKit/Platform/IPC/MessageReceiverMap.cpp:123 #13 0x00000003c8c6e7fd in WebKit::WebProcess::didReceiveMessage(IPC::Connection&, IPC::Decoder&) at /Users/mzp/Safari-605.3.8/Source/WebKit/WebProcess/WebProcess.cpp:648 #14 0x00000003c8141843 in IPC::Connection::dispatchMessage(IPC::Decoder&) at /Users/mzp/Safari-605.3.8/Source/WebKit/Platform/IPC/Connection.cpp:907 #15 0x00000003c81369d8 in IPC::Connection::dispatchMessage(std::__1::unique_ptr<IPC::Decoder, std::__1::default_delete<IPC::Decoder> >) at /Users/mzp/Safari-605.3.8/Source/WebKit/Platform/IPC/Connection.cpp:934 #16 0x00000003c8141e94 in IPC::Connection::dispatchOneMessage() at /Users/mzp/Safari-605.3.8/Source/WebKit/Platform/IPC/Connection.cpp:965 #17 0x00000003c8159add in IPC::Connection::enqueueIncomingMessage(std::__1::unique_ptr<IPC::Decoder, std::__1::default_delete<IPC::Decoder> >)::$_14::operator()() at /Users/mzp/Safari-605.3.8/Source/WebKit/Platform/IPC/Connection.cpp:901 #18 0x00000003c8159a39 in WTF::Function<void ()>::CallableWrapper<IPC::Connection::enqueueIncomingMessage(std::__1::unique_ptr<IPC::Decoder, std::__1::default_delete<IPC::Decoder> >)::$_14>::call() at /Users/mzp/Safari-605.3.8/WebKitBuild/WebKit/Build/Products/Debug/usr/local/include/wtf/Function.h:101 #19 0x00000003dd2f9a9b in WTF::Function<void ()>::operator()() const at /Users/mzp/Safari-605.3.8/WebKitBuild/WebKit/Build/Products/Debug/usr/local/include/wtf/Function.h:56 #20 0x00000003dd33e5f3 in WTF::RunLoop::performWork() at /Users/mzp/Safari-605.3.8/Source/WTF/wtf/RunLoop.cpp:106
🔍コード確認
EventDispatcher::dispatchEvent
を確認すると、callDefaultEventHandlersInBubblingOrder
にはifによるガードがついている。
// WebCore/dom/EventDispatcher.cpp if (!event.defaultPrevented() && !event.defaultHandled()) { // FIXME: Not clear why we need to reset the target for the default event handlers. // We should research this, and remove this code if possible. auto* finalTarget = event.target(); event.setTarget(EventPath::eventTargetRespectingTargetRules(node)); callDefaultEventHandlersInBubblingOrder(event, eventPath); event.setTarget(finalTarget); }
デバッガを使って条件節の値を確認する。 すると、古いSafariではevent.defaultHandled()
が trueだが、trunkではevent.defaultHandled()
が falseになっている。
コードを目で比較すると、trunkでは resetBeforeDispatch()
の呼び出しがある。
// WebCore/dom/EventDispatcher.cppASSERT_WITH_SECURITY_IMPLICATION(ScriptDisallowedScope::InMainThread::isEventDispatchAllowedInSubtree(node)); LOG(Events, "EventDispatcher::dispatchEvent %s on node %s", event.type().string().utf8().data(), node.nodeName().utf8().data()); auto protectedNode = makeRef(node); auto protectedView = makeRefPtr(node.document().view()); EventPath eventPath { node, event }; ChildNodesLazySnapshot::takeChildNodesLazySnapshot(); event.resetBeforeDispatch(); // <- ??????
コードを確認すると、defaultHandled
を初期化している。
void Event::resetBeforeDispatch() { m_defaultHandled = false; }
なので日本語入力とTabキーの問題を追っていたが、真の問題は入力メソッドが処理したキー入力に対してもデフォルトのハンドラが起動することらしい。
📖コミット調査
trackのblame機能を使って、どのコミットでこれが増えたか特定する。
r228260が原因らしい。 コミットログによると、イベント配信間の影響を切るために導入されているらしい。
(WebCore::Event::resetBeforeDispatch): Added. Clears m_defaultHandled so a value left over from a previous dispatch doesn't affect the next dispatch. ---- (WebCore::Event::resetBeforeDispatch): 追加。以前のイベント配信が次の配信に影響しないようにm_defaultHandledをリセットする。
🗣バグレポート
adhocに直す方法はいくつかあるが、どれを選択するのがいいか分からない。 WebKitとRadarにレポートした。どう書くか迷ったがサマリーでは具体的に困ってることを書いて、真の問題はdetailに書くことにした。 またタイトルにregressionというワードを含めた。
- 188370 – Events handled by input method invoke default event handler[regression]
- rdar://42991694: Safari moves focus by tab key during composing text by input method.
その結果、以下のような修正が適用された。入力メソッドが処理したことを示す isDefaultEventHandlerIgnored()
関数が追加された。
Index: trunk/Source/WebCore/dom/Event.h =================================================================== --- a/trunk/Source/WebCore/dom/Event.h +++ b/trunk/Source/WebCore/dom/Event.h @@ -120,4 +120,7 @@ void setDefaultHandled() { m_defaultHandled = true; } + bool isDefaultEventHandlerIgnored() const { return m_isDefaultEventHandlerIgnored; } + void setIsDefaultEventHandlerIgnored() { m_isDefaultEventHandlerIgnored = true; } + void setInPassiveListener(bool value) { m_isExecutingPassiveEventListener = value; } @@ -157,4 +160,5 @@ bool m_wasCanceled { false }; bool m_defaultHandled { false }; + bool m_isDefaultEventHandlerIgnored { false }; bool m_isTrusted { false }; bool m_isExecutingPassiveEventListener { false }; Index: trunk/Source/WebCore/dom/EventDispatcher.cpp =================================================================== --- a/trunk/Source/WebCore/dom/EventDispatcher.cpp +++ b/trunk/Source/WebCore/dom/EventDispatcher.cpp @@ -168,5 +168,5 @@ // default handling, the detail of which handlers are called is an internal // implementation detail and not part of the DOM. - if (!event.defaultPrevented() && !event.defaultHandled()) { + if (!event.defaultPrevented() && !event.defaultHandled() && !event.isDefaultEventHandlerIgnored()) { // FIXME: Not clear why we need to reset the target for the default event handlers. // We should research this, and remove this code if possible. Index: trunk/Source/WebCore/page/EventHandler.cpp =================================================================== --- a/trunk/Source/WebCore/page/EventHandler.cpp +++ b/trunk/Source/WebCore/page/EventHandler.cpp @@ -3289,5 +3289,5 @@ keydown = KeyboardEvent::create(keyDownEvent, &m_frame.windowProxy()); keydown->setTarget(element); - keydown->setDefaultHandled(); + keydown->setIsDefaultEventHandlerIgnored(); }
🛤北の果て
北海道の稚内に行った。
空港に出汁之介というゆるキャラがいた。食べるのかな...。
🌊最北端
最北端にある宗谷岬を見た。
Appleマップでも果てになってる。
北方領土もあるし最北端って言い方は正しいのかな、と思っていたら「私たちが自由に往来できる日本の領土としては最も北に位置する...」という書き方になってた。 なるほど。
周囲は草原が広がっててよかった。
草原の向こうに風車小屋が見えてるのは現実感が乏しくていい。
アンテナ台みたいなのもあってエモい。
🚆線路の果て
稚内駅に線路の果てもあった。
なぜか駅の外に北端があって謎。
💫天文台
火星最接近の日だったので天文台に行った。
難易度の高いスタンプラリーが開催さてれいた。
曇りだったので火星は見えなかった。
👣風景
スケール感のある風景があってよい。
空港でたらなにもない。
道も広い。牛もいる。
歩道の幅が広すぎてびびる。
噴水。
🍽食事
北海道なのでご飯がおいしい。
寿司。
稚内ではうにが捕れるらしい。
ソフトクリームうまい。放牧牛の牛乳で作ってあると書いてあった。
そういえばザンギ食べてないな残念だな、と思いながら空港を歩いてたら急にラーメン屋がでてきた。しょうがない。
🇸🇬シンガポール
シンガポール旅行をした。
ANAのプレミアムポイントを貯めているので飛行機で遠くに行きたいという気持ちと、東南アジア観光をしたいという気持ちが合致した。
🍽食事
ホーカーズという屋外のフードコートのような場所で食べていた。 おいしいし安いので最高。近所に欲しい。
南海チキンライス。
ラクサ。 ココナッツ風味のスープに麺がはいっている。
呼び方が分からないけど蝦麺と書いてあった。 そのままのものがでてきた。
あとは空港のレストランで肉骨茶を食べた。 胡椒の聞いたスープに肉がはいっている。
🚶♀️観光
動物園
シンガポール動物園がすごいと聞いたので見にいった。 檻とかがなくて見応えがあった。
カワウソかわいい。
バクをはじめてみた。思ったより景色に溶けこんでて写真に写りづらい。
水辺にはトラがいる。
マントヒヒ。
象。
すごいなーと見てたら急にタヌキがでてきてびっくりした。
植物園
広大な植物園も見た。 休憩しながら回っていたので5時間くらいかかった。
ほぼ森の中を歩いてるような気分になる。
広場みたいなエリアも広くて、おもしろい。
有料エリアで蘭が見れるのてたのしい。
ランドマーク
マーライオン。 マーライオン公園にあるやつを見て満足してたが、Wikipediaによると他にもいくつかあったらしい。
なんか上に船がのってる建物があるわ、ウケる、と思ってみてたら、有名なマリーナサンベイズだったらしい。
シンガポールのAppleStoreも確認した。
📱通信
空港の出口を出た瞬間にプリペイドSIMが販売されていて最高だった。
HISの変なSIMも持っていったが、日本での初期設定が不足してたためか、うまく使えなかった。
🛌ホテル
予算相場を把握しないまま予約したらやたら豪華なホテルになってしまった。調理器具一式がついてたが、いっさい触れなかった。
✨その他
歩道に鳥の足跡がのこっててかわいかった。
飛行機のWifiのパスワード入力例の主張が激しい。
🌓ダークモード
メニューバーからダークモード切り替えをするアプリケーションを作った。
ダウンロード: https://github.com/mzp/DarkMenuBar/releases
🖼デモ
🌑ダークモードの登場
macOS HighSierraからメニューバーおよびドックを黒にするダークモードが利用できる。さらにMojaveではウインドウ等も黒にできるようになった。
(WWDC 2018 Keynoteより引用)
これらの変更はシステム環境設定から変更できる。
しかし、「周囲が暗いのでダークモードに切り替えたい」「気分転換にモードを変えたい」といったときに、都度システム環境設定を開くのは面倒である。 もっと気軽にメニューバーから切り替えたい。
🌟メニューバーからの切り替え
そこでメニューバーからダークモードのオン・オフをするアプリケーションを作った。https://github.com/mzp/DarkMenuBar/releasesからダウンロードできる。
🛠仕組み
ダークモードへの切り替え
切り替えはSkyLightというprivate frameworkを使うとできる。 こんな感じ。
@objc func enterDarkMode(sender: Any!) { NSLog("%@", "enter dark mode") SLSSetAppearanceThemeLegacy(1) } @objc func leaveDarkMode(sender: Any!) { NSLog("%@", "leave dark mode") SLSSetAppearanceThemeLegacy(0) }
メソッド名にLegacyが含まれてるのが不吉だけど、とりあえず無視する。 /System/Library/PreferencePanes/Appearance.prefPane
をディスアセンブルしてもこれ使ってたので、まちがってはいないと思う。
メニューバーへの組込み
WeatherBar by bgreenleeにある方法でメニューバーに組み込む。
アイコンはSketchで適当に書いた。
ログイン時に起動
ログイン時に起動はHow to launch a macOS app at login? - The.Swift.Devで実現した。
/Applications
に配置しないと動作しないのでデバッグが面倒だった。
🚧IM@S Engineer Talks メイキング #imas_hack
IM@S Engineer Talks - connpassでアイドルとして勤務する -理論と実践-という話をした。IM@S Engineer Talksで喋ってきた話のメイキングのおはなし #imas_hack - ツバメになったバリスタがたのしかったので、ボクもメイキングを書く。
アイマスハッカソンの打ち上げの帰りにtrebyさんから「今度長めにしゃべりませんか」と言われて「いいですよー」と答えてたら本当にしゃべることになった。 結果としてMisoca在籍中の最後の登壇となったのがおもしろい。
🤔狙い: 「実際にやってみた」を全面に出す
アイマスハッカソンでやってた心拍数ハックやライブフォト生成はid:banjunが話すので、別のネタで行くことにした。 ちょうどパペ文字によるVR出社をやりはじめたころなので、アバター出社で行くことにした。
そのまま作ると「FaceRigを使ってみました」という内容になって技術的な難易度は低い上に、担当アイドルへの愛の強さは他の人にはかなわないので、工夫が必要だった。
そこで「実際にアバター出社をやってみるとおきるあれこれ」というのを主題にした。 アバター出社をしている人はあまりいないので、実体験をメインに据えれば興味深いになるのではと考えた。
また会社に交通費補助がある場合はスライド会社の紹介をいれるのが自分のルールなので、出社の話をすれば違和感なく会社の紹介ができるなという狙いもある。スライドでは次のようなシナリオにして会社の紹介をいれた。
- Misocaで勤務しています
- Misocaではリモート勤務を推奨しています
- そこで発生する問題を解決するためにアバター出社を導入しました
📝スライドメイキング: マイドマップと原稿
マインドマップ風にがーと書いた。ドトールで書いてたら水滴をおとして文字がにじんでしまった。
そのあと原稿にまとめていく。 300文字/分と聞いたのでそれを目安に書いてたかデモを挿入すると崩壊すると学んだ。
このあたりで開催まで1週間をきったので、焦りだした。
🙌当日の反応
デモも問題なくできたし、みんなもりあがってくれてよかった。
予想はしていたが、声に関する質問がおおかった。 「ビジュアルはいいけど、声はどうなんですか?」という質問、プロデューサーっぽいね、次はきっとダンスのこと聞かれるよ、という話を懇親会でしててたのしかった。
追加の質問は https://quesdon.rinsuki.net/@mzp@mstdn.jp で受け付けている。
⚡️後日の反応
ブログに発表資料と原稿を書いたら、めっちゃブックマークされてびびった。 今みたら431ブクマされている。
タイトルにハッシュタグを含めたせいでimachack SlackのTwitterエゴサチャンネルが崩壊した。
オモコロのトップページにも掲載された。
今週のはてなブログランキング〔2018年7月第2週〕 にも掲載された。 13位がMisocaの社長の記事なので、若干きまずい。
これで臨界点を越えたのかアバター出社する人が増えた。
はー我かわいすぎか😊😊 pic.twitter.com/DUuNNiQZSb
— こまたつ (@k0matatsu) 2018年7月13日
こっちですよ pic.twitter.com/kVZlvixOrK
— ころちゃん (@corocn) 2018年7月15日