這裡的犀牛長的不太一樣!#ifdef in JavaScript?
Firefox OS 裡的 Gecko 層,主要是由 C++ 和 JavaScript 兩種程式語言編寫而成。不過 Gecko 裡的 JS 程式碼長相和我們常見的 client-side JS 非常不一樣。剛開始閱讀程式碼時,常常會看到一些奇妙的玩意。
比方說,JS 裡頭竟然會有 #ifdef !?
原來這是 Mozilla 程式師在內部施展的一點小魔法,讓工程師在 Gecko 層撰寫 JS 程式碼時,能如同寫 C/C++ 一樣,直覺地使用 #ifdef/#endif 來決定編譯的區塊。這麼一來,不僅寫法簡單,而且讓 C++ 和 JS 原始碼能共用相同的識別符號 (identifier),實在是非常方便。
以下就一個簡單的例子,分享在 Gecko JS 裡面使用 #ifdef 的小撇步。
在 JavaScript 裡加入 #ifdef/#endif
當時我的一個任務是修改 DOM API 的 TCPSocket,每當有 TCP 流量傳送時,必須呼叫 networkStatsServiceProxy 提供的 API 把流量統計給記錄下來。問題是,networkStatsServiceProxy 是 B2G 獨有的服務元件,在桌面版並沒有這個元件。此時,我們的作法就是在 JavaScript 裡面用 #ifdef/#endif 把這段程式碼給包起來,以確保 Mozilla 程式庫在其他平台上 build 不會失敗。
以下是在 dom/network/src/TCPSocket.js (簡化過的)程式碼片段,重點在於這段程式是在 JS 裡面,用 #ifdef/#endif 括起來的:
#ifdef MOZ_WIDGET_GONK _saveNetworkStats: function ts_saveNetworkStats() { let nssProxy = Cc["@mozilla.org/networkstatsServiceProxy;1"] .getService(Ci.nsINetworkStatsServiceProxy); nssProxy.saveAppStats(this._appId, this._activeNetwork, Date.now(), this._rxBytes, this._txBytes); // Reset the counters once the statistics is saved. this._txBytes = this._rxBytes = 0; }, #endif
在 moz.build 裡面加入 EXTRA_PP_COMPONENTS
嗯,很好,#ifdef/#endif 的寫法看起來和 C/C++ 完全沒兩樣。然後我們要如何讓這個前置處理指令發揮效用呢?非常簡單,只要修改相同目錄下的 moz.build,加入一個特殊的 EXTRA_PP_COMPONENTS 區塊即可。
以 TCPSocket.js 的例子而言,此時我們只要修改 dom/network/src/moz.build,加入以下幾行就可以囉!
EXTRA_PP_COMPONENTS += [ 'TCPSocket.js', ]
That’s it!此時我們的 TCPSocket.js 就具有能夠使用前置處理指令 #ifdef/#endif 的能力了(當然也可以用 #ifndef, #elif)。可是,你是否仍然疑惑,JavaScript 沒有編譯階段更沒有前置處理階段呀,這一切是怎麼辦到的?
其實 JavaScript 程式碼仍舊是在執行期間時,直譯器 (interpreter) 才會去解析的。這個小魔法是在 build 階段,當 builder 發現 JavaScript 原始檔被宣告在 EXTRA_PP_COMPONENTS 裡面時,就會自動去修改原本的 JavaScript 原始碼的內容。
檢查 JavaScript 原始碼是否有被修改過
在 build.sh [gecko] 指令執行完之後,我們可以到 [obj_dir]/dist/bin/components 目錄下查看 JS 檔案。一般來說,此目錄下的 JS 檔案只是 soft link,指到原本程式庫裡真正的原始碼檔案。但是經過 builder “前置處理" 過的 JavaScript 檔案,就變成此目錄下的實體檔案而非連結檔了。以上述的例子而言,只有 TCPSocket.js 不是連結檔。
$ cd /dist/bin/components $ ls -l *TCP* 75 10:37 TCPServerSocket.js -> /dom/network/src/TCPServerSocket.js 29082 10:37 TCPSocket.js 75 10:37 TCPSocket.manifest -> /dom/network/src/TCPSocket.manifest 87 10:37 TCPSocketParentIntermediary.js -> /dom/network/src/TCPSocketParentIntermediary.js
此時打開 TCPSocket.js,會發現原本宣告為 #ifdef/#endif 的兩行程式碼,已被自動置換成註解囉。
//@line 328 "/home/ethan/workspace/hg/mozilla-central/dom/network/src/TCPSocket.js" _saveNetworkStats: function ts_saveNetworkStats(enforce) { let nssProxy = Cc["@mozilla.org/networkstatsServiceProxy;1"] .getService(Ci.nsINetworkStatsServiceProxy); nssProxy.saveAppStats(this._appId, this._activeNetwork, Date.now(), this._rxBytes, this._txBytes); // Reset the counters once the statistics is saved to NetworkStatsServiceProxy. this._txBytes = this._rxBytes = 0; }, //@line 358 "/home/ethan/workspace/hg/mozilla-central/dom/network/src/TCPSocket.js"