JavaScript 與 Desktop - Desktop and WebKit
本文是《JavaScript 與 Desktop》系列最後一篇。前兩篇文章中,分別述敘了在 gjs/seed 中呼叫系統函數庫與調用 WebKit 處理圖形化使用介面的工作。
但是在這個架構中,實際上存在了兩個 JavaScript host (host 是 ECMAScript/JavaScript 規範術語,意指 JavaScript 語言解譯器寄宿的環境,故有人將之譯為「宿主」) 。一個是 gjs/seed,另一個便是 WebKit JavaScriptCore 。這兩個 host 都是獨立的環境空間,彼此之間的資源不能直接互通。例如 gjs/seed 這個 host 提供的資源可以載入 DBus 服務,調用 DBus 方法;但是 WebKit JavaScriptCore 並不提供這類資源,所以不能調用 DBus 方法。是以我們需要找出一個互通訊息的途徑,讓這兩個 host的程式碼可以互動。本文將說明其中一種基於事件觸發的途徑。
內外部互動模式
在《JavaScript 與 Desktop - WebKit》一文中,我們讓 gjs/seed 載入 libwebkit 函數庫,透過該函數庫配置了一個 WebKit 環境空間。在該 WebKit 環境中,它包含了一個獨立的 HTML render 與 JavaScript host。在這個自成一格的內部空間中,它本身就能獨自處理 HTML 與相關資源,運作自己的一套 JavaScript host。如果我們想要從外部干涉這個內部空間,或是讓內部空間中的狀況可以為外部所得知,我們就要依靠 libwebkit 所提供的 API 才能實現(WebKit 專案有許多分支,其中有些分支直接提供了內外部互動的 API 。例如 OS X 的 WebKit 或是 Nokia 的 QtWebKit。那些分支提供的特殊 API 不具相容性,不適用於本文的開發環境)。
libwebkit 提供了一些特定的 API 讓我們存取 WebKit 內部的屬性狀態。例如: webkit_web_view_get_title(), webkit_web_view_get_uri(), signal:notify:load-status, signal:title-changed。但這些 API 的用途有限。如果我們要實現更複雜的互動架構,我們需要分兩方面討論。一方面是由外部調用內部資源,另一方面是由內部調用外部資源。
外部調用內部資源
由外部調用內部資源最直接的方式,就是由 gjs 端呼叫 WebKit API 的 execute_script()。這個動作相當於在 WebKit 端呼叫 eval()
。舉例來說,如果我想要從 gjs 端要求 WebKit 端顯示一個 alert 視窗,我可以在 gjs 端執行 view.execute_script("alert('hello')")
。
execute_script() 可以從外部送出任何程式碼給內部執行,但是並不能將結果傳給外部。我們要再配合下一段「內部調用外部資源」的方法。
內部調用外部資源
WebKit 端沒有提供任何讓 WebKit host 內的程式碼接觸到外部資源的正式途徑。所以我們必須思考一下偏門技巧。我首先想到的就是利用 WebKit 中的 Signals 機制,藉由訊號觸發的方式,讓外部傾聽與接收內部訊號送出的訊息,再將其分析為內部調用外部資源的指令。而在 WebKit 提供的 Signals 中,唯一沒有副作用,而且可以傳遞長字串訊息的訊號,則是 title-changed。title-changed 接受一個任意內容的字串,做為訊號送出時附帶的訊息。
在內部 (WebKit 端),可以藉由設定 document.title 的內容,觸發 title-changed 訊號,並將 document.title 的新值做為訊號送出時附帶的訊息。外部 (gjs端) 只要去傾聽 title-changed 訊號,就可以接收到內部送出的指令請求,再根據請求內容調用外部資源。至於外部資源的回傳結果,可以參考前段的 execute_script() 送回內部。通常我們會採用 Ajax 常見的 callback 設計模式,呼叫內部訊息指定的回呼函數,將外部資源的回傳結果送回。
我在尋找解決方案時,找到《HOWTO Create Python GUIs using HTML》這篇文章。它採用的內外部互動模式,就是本文所採用的模式。如果我的說明不足以令你了解此互動模式的運作方式,請再閱讀該文內容。
實作
結合前兩篇的範例以及本文所述的互動模式,我撰寫了一個 gtk-webkit-2.js。將第一篇文章中調用 DBus 的函數,加到第二篇文章調用 WebKit 處理 GUI 的範例之中,並增加一個 notify 動作。當使用者在 UI 的文字框中輸入關鍵字後,除了原本查詢 flickr 顯示圖片的動作之外,還會在桌面上彈出一個訊息方塊。
第一步,我先定義調用 DBus Notify 方法的函數,參考《JavaScript 與 Desktop - DBus》。
第二步,我要由外部要求內部定義一個notify() 轉接函數供內部端呼叫。這個動作利用 execute_script() 方法實現。此轉接函數實際上將觸發 title-changed 事件訊號,讓外部接收內部的請求,進而調用 DBus Notify 方法在桌面上顯示訊息方塊。一如我們過往在 Ajax 中學到的經驗,若我們想要額外地、動態地定義 JavaScript 函數,最好是等到頁面載入完成,瀏覽器發出 onload 事件之後。對於外部的 gjs 端而言,則是透過 WebKit 的 notify::load-status 訊號捕抓 onload 事件。所以我將定義 notify() 轉接函數的動作,寫在 view.loadStatus == WebKitLoadStatus.WEBKIT_LOAD_FINISHED
成立之後的程式區塊中。
第三步,利用 title-changed 接收內部送出的訊息方塊顯示請求,調用 DBus Notify 方法。接著再利用 execute_script() 調用回呼函數,將外部資源的回傳結果送回。本範例中並未定義回呼函數的處理方式,只是簡單地以 alert() 代表回呼函數。
最後,我繼續使用《Hello HTML5 and XULRunner》中已經存在的文件,只是改成載入 index2.html (複製自 index.html,並加了一行程式碼)。
至於 UI 的部份,我將《Hello HTML5 and XULRunner》的 index.html 的內容另存為 index2.html,並在其 event_change_name()() 之中,多加了一個呼叫 notify(name
的動作。這個 notify() 函數,是由 gjs 從外部額外加進來的。文件內容摘要於下:
Reference
- JavaScript 與 Desktop - DBus - 本系列文章第一篇
- JavaScript 與 Desktop - WebKit - 本系列文章第二篇
- HOWTO Create Python GUIs using HTML - 基於 PyWebKit 的參考文章
- Hello HTML5 and XULRunner
- GTK+ Reference Manual - GTK 視窗 C API 參考文件.
- WebKitGTK+ Reference Manual - WebKitGtk+ C API 參考文件.
樂多舊回應