みずぴー日記

人間の再起動ボタンはハワイのビーチにある

AquaSKK 4.6.0/4.6.1: Mojave対応

Mojave対応を行なったAquaSKKを4.6.0としてリリースした。2018-9-1: HighSierraで一部機能(#84)が動かない不具合が見付かったので、修正して4.6.1としてリリースした。

f:id:mzp:20180901105107p:plain

https://github.com/codefirst/aquaskk/releases/tag/4.6.1

🌓ダークモード

Mojaveではダークモードが導入された。

f:id:mzp:20180729124441p:plain (WWDC2018 Keynoteより引用)

AquaSKKもダークモードに対応させ、候補ウインドウがダークモードで表示されるようにした。 さらに、アクセントカラーも扱うようにした。

f:id:mzp:20180831210108p:plain

🎨 アイコン色の調整

ダークモードではASCIIモードのモードアイコンの視認性がかなり低くなる。

f:id:mzp:20180831085106p:plain

Mojaveより前から存在していた問題(Issue #75)だが、ダークモードが強化されたMojaveではより顕著になる。

色合いを調整して、視認性を向上させた。

f:id:mzp:20180831102112p:plain

👋32bitサポートの廃止/libc++への切り替え

Xcode10から32ビットアプリケーションのサポートが廃止された。Mojave以降は64ビットアプリケーションのみのサポートとなるので、これを気に32ビットサポートは廃止した。

f:id:mzp:20180831101611j:plain (WWDC2017 Keynoteより引用)

またlibstdc++のサポートが廃止されたので、libc++に切り替えた。これは目に見える影響はないと思う。

🙊NDAへの配慮

本記事はNDAに配慮し、Xcode 10やmacOS Mojaveのスクショショットは利用していない。Mojaveが正式リリースしてから書けば楽だったが、キリがついたのでリリースした。若干、無理がある。

ダークモード対応中の様子は楽しいので、それはどこかでロンダリングした上で出したい。

🔥Firebase

夏休みなのでFirebaseの練習をした。

ライブフォトをシェアするSNSのプロトタイプを作った。ライブフォト好きだけど、他の人に共有しづらくて困っている。

f:id:mzp:20180828221417p:plain

📝参考にしたもの

基本的にFirebase Guidesを読んで作った。 これでだいたい分かるので、すごい。

ただ、Apple Developer Portalの設定が必要な箇所になると急に説明が雑になるので、そこは茅場町モバイルアプリもくもく会で教えてもらった。

🔥開発の様子: Firebase

ログイン

Firebase Authenticationを使うと、あっさりGoogleアカウントログインが作れる。便利。

Firestore

Firestoreでプロフィール情報を保存するようにした。

コンソールからデータが確認できて便利。

f:id:mzp:20180828222603p:plain

データの保存には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 MessagingCloud Functionsを使ってfav通知を作った。

Cloud MessagingでAPNsを使う方法は2通りあるがAPNs authentication keyのほうが、どのアプリでも使えて便利。(茅場町モバイルアプリもくもく会で教えてもらった)

通知のロジックを書く場所が分からなかったが、Cloud FunctionsでDB等の変更を監視して通知を送るのがよいらしい。

f:id:mzp:20180828230025p:plain (What can I do with Cloud Functions?から引用)

コンソールから通知を送る方法が分からなかったが、Growカテゴリの下にあった。

f:id:mzp:20180828230530p:plain

📱進捗: 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 }
        // ....
    }
}

作成は、PHLivePhotorequest に、 画像と動画の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 レポジトリにした。

f:id:mzp:20180828232548p:plain

夏休み

気分転換のために何度か近所の喫茶店に行ったが、夏休みの宿題をやっているらしき兄弟がいて、夏を感じた。

そうか東京の子どもは喫茶店で宿題やるのか。 そんな文化しらないぞ、という気持ちにもなる。

🐣個人esa

Misocaを退職したらMisocaのesaにアクセスできなくなった。不便だったので個人でesaを契約した。

f:id:mzp:20180813154908p:plain

📝日報

esa標準の日報テンプレートにしたがって「今日の作業」「発生した問題」「明日の予定」「所感」を書いている。

f:id:mzp:20180813155125p:plain

一緒に日報書きましょうと募集したら4人あつまった。毎日、みんな書いてて偉い。

f:id:mzp:20180813161720p:plain

見る人がいるのでローカルのメモとブログの中間の温度感になっている。 たのしい。往年の会員制SNSを彷彿させる。

🛠各種メモ

esaは次の特徴があるのでメモを書くときにちょうどいい。

  • Markdownでサクサク書ける。エディタも使いやすい。
  • WIP/ShipItの区別がわかりやすい。
  • 記事ごとに共有URLが発行できる

🐞WebKitへのバグレポート - みずぴー日記も最初はesaに書いて、まとまった段階ではてなブログに移した。 WIPではじめれるので、気軽に書きはじめれる。 また早くShipItしたいという気持ちも働く。

f:id:mzp:20180813155052p:plain

引越しに関するメモも置いてある。 これは、かなり頻繁に参照している。

f:id:mzp:20180813160050p:plain

イベントの予定もここに書いている。 共有URLを発行して関係者に共有できて便利。

f:id:mzp:20180813160301p:plain

✨その他

個人esaの契約どうしようかなーと思ってトップページにいったら、 mzpという人がおすすめコメントを寄せてた。 なるほどと納得したので契約した。

f:id:mzp:20180813160909p:plain

日報メンバーのりんすきさんがアプリのドキュメンテーションesaを使いはじめてた。 esaの輪の広がっている。

🐞WebKitへのバグレポート

Safari 12では、日本語入力中でもTabキーによるフォーカス移動が動作する。

f:id:mzp:20180809233359p:plain

過去のバージョンやChromeなどとは動作が異なるためリグレッションバグと考え、WebKitにバグレポート(#188370)を行なった。 修正コミットがtrunkにされたので、いずれSafariでも修正されるはず。(2018-8-17追記: Version 12.0 (14606.1.36.1.3)で反映された)

💥遭遇

AquaSKKでは入力中にTabキーを押すと入力内容が補完される。 これを使ってTweetしようとした際に、フォーカスが移動してしまって困った。

問題箇所の切り分けのために、いろいろな環境で動作を確認した。その結果、以下のことが分かった。

どうやらWebKitで問題が発生しているらしい。

🛠WebKitのビルド

trunk(問題のあるビルド)

詳しく調べるためにWebKitを手元でビルドした。 Building WebKit | WebKitにいくつかの方法が記載されているが、今回はデバッガを利用したいのでXcodeによるビルドを選択した。

f:id:mzp:20180809234355p:plain

起動して問題が再現することを確認した。

古い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::dispatchEventWebCore::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機能を使って、どのコミットでこれが増えたか特定する。

f:id:mzp:20180809235829p:plain

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というワードを含めた。

その結果、以下のような修正が適用された。入力メソッドが処理したことを示す 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();
     }

🛤北の果て

北海道の稚内に行った。

空港に出汁之介というゆるキャラがいた。食べるのかな...。

f:id:mzp:20180730171609j:plain

🌊最北端

最北端にある宗谷岬を見た。

f:id:mzp:20180730180717j:plain

Appleマップでも果てになってる。

f:id:mzp:20180806161256p:plain

北方領土もあるし最北端って言い方は正しいのかな、と思っていたら「私たちが自由に往来できる日本の領土としては最も北に位置する...」という書き方になってた。 なるほど。

f:id:mzp:20180730180438j:plain

周囲は草原が広がっててよかった。

f:id:mzp:20180730181639j:plain

草原の向こうに風車小屋が見えてるのは現実感が乏しくていい。

f:id:mzp:20180730181637j:plain

アンテナ台みたいなのもあってエモい。

f:id:mzp:20180730182047j:plain

🚆線路の果て

稚内駅に線路の果てもあった。

f:id:mzp:20180730203912j:plain

なぜか駅の外に北端があって謎。

f:id:mzp:20180806161401p:plain

💫天文台

火星最接近の日だったので天文台に行った。

f:id:mzp:20180731144918j:plain

難易度の高いスタンプラリーが開催さてれいた。

f:id:mzp:20180731132904j:plain

曇りだったので火星は見えなかった。

👣風景

スケール感のある風景があってよい。

空港でたらなにもない。

f:id:mzp:20180730172846j:plain

道も広い。牛もいる。

f:id:mzp:20180731082505j:plain

歩道の幅が広すぎてびびる。

f:id:mzp:20180730205410j:plain

噴水。

f:id:mzp:20180731130830j:plain

🍽食事

北海道なのでご飯がおいしい。

寿司。

f:id:mzp:20180730201440j:plain

稚内ではうにが捕れるらしい。

f:id:mzp:20180801113332j:plain

ソフトクリームうまい。放牧牛の牛乳で作ってあると書いてあった。

f:id:mzp:20180731100440j:plain

そういえばザンギ食べてないな残念だな、と思いながら空港を歩いてたら急にラーメン屋がでてきた。しょうがない。

f:id:mzp:20180801185226j:plain

🇸🇬シンガポール

シンガポール旅行をした。

ANAのプレミアムポイントを貯めているので飛行機で遠くに行きたいという気持ちと、東南アジア観光をしたいという気持ちが合致した。

🍽食事

ホーカーズという屋外のフードコートのような場所で食べていた。 おいしいし安いので最高。近所に欲しい。

f:id:mzp:20180730222442p:plain

南海チキンライス。

f:id:mzp:20180730222508p:plain

ラクサ。 ココナッツ風味のスープに麺がはいっている。

f:id:mzp:20180730222552p:plain

呼び方が分からないけど蝦麺と書いてあった。 そのままのものがでてきた。

f:id:mzp:20180725192959j:plain

あとは空港のレストランで肉骨茶を食べた。 胡椒の聞いたスープに肉がはいっている。

f:id:mzp:20180730222752p:plain

🚶‍♀️観光

動物園

シンガポール動物園がすごいと聞いたので見にいった。 檻とかがなくて見応えがあった。

カワウソかわいい。

f:id:mzp:20180730222953p:plain

バクをはじめてみた。思ったより景色に溶けこんでて写真に写りづらい。

f:id:mzp:20180730223017p:plain

水辺にはトラがいる。

f:id:mzp:20180730223119p:plain

マントヒヒ。

f:id:mzp:20180730223153p:plain

象。

f:id:mzp:20180730223420p:plain

すごいなーと見てたら急にタヌキがでてきてびっくりした。

f:id:mzp:20180730224601p:plain

植物園

広大な植物園も見た。 休憩しながら回っていたので5時間くらいかかった。

ほぼ森の中を歩いてるような気分になる。

f:id:mzp:20180730224128p:plain

f:id:mzp:20180730224136p:plain

f:id:mzp:20180730224145p:plain

広場みたいなエリアも広くて、おもしろい。

f:id:mzp:20180730224147p:plain

有料エリアで蘭が見れるのてたのしい。

f:id:mzp:20180730224209p:plain

f:id:mzp:20180730224154p:plain

f:id:mzp:20180730224508p:plain

ランドマーク

マーライオンマーライオン公園にあるやつを見て満足してたが、Wikipediaによると他にもいくつかあったらしい。

f:id:mzp:20180730224847p:plain

なんか上に船がのってる建物があるわ、ウケる、と思ってみてたら、有名なマリーナサンベイズだったらしい。

f:id:mzp:20180730224842p:plain

シンガポールのAppleStoreも確認した。

f:id:mzp:20180725153020j:plain

📱通信

空港の出口を出た瞬間にプリペイドSIMが販売されていて最高だった。

f:id:mzp:20180726162806j:plain

HISの変なSIMも持っていったが、日本での初期設定が不足してたためか、うまく使えなかった。

🛌ホテル

予算相場を把握しないまま予約したらやたら豪華なホテルになってしまった。調理器具一式がついてたが、いっさい触れなかった。

f:id:mzp:20180730230639p:plain

f:id:mzp:20180730230648p:plain

✨その他

歩道に鳥の足跡がのこっててかわいかった。

f:id:mzp:20180730231202p:plain

飛行機のWifiのパスワード入力例の主張が激しい。

f:id:mzp:20180730231230p:plain

🌓ダークモード

メニューバーからダークモード切り替えをするアプリケーションを作った。

f:id:mzp:20180729124035p:plain

ダウンロード: https://github.com/mzp/DarkMenuBar/releases

🖼デモ

🌑ダークモードの登場

macOS HighSierraからメニューバーおよびドックを黒にするダークモードが利用できる。さらにMojaveではウインドウ等も黒にできるようになった。

f:id:mzp:20180729124441p:plain (WWDC 2018 Keynoteより引用)

これらの変更はシステム環境設定から変更できる。

f:id:mzp:20180729124739p:plain

しかし、「周囲が暗いのでダークモードに切り替えたい」「気分転換にモードを変えたい」といったときに、都度システム環境設定を開くのは面倒である。 もっと気軽にメニューバーから切り替えたい。

🌟メニューバーからの切り替え

そこでメニューバーからダークモードのオン・オフをするアプリケーションを作った。https://github.com/mzp/DarkMenuBar/releasesからダウンロードできる。

f:id:mzp:20180729124035p:plain

f:id:mzp:20180729123947p:plain

🛠仕組み

ダークモードへの切り替え

切り替えは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で適当に書いた。

f:id:mzp:20180729125623p:plain

ログイン時に起動

ログイン時に起動はHow to launch a macOS app at login? - The.Swift.Devで実現した。

/Applications に配置しないと動作しないのでデバッグが面倒だった。