原文連接:https://developer.chrome.com/native-client/devguide/tutorial/tutorial-part2javascript
本教程介紹如何將完成的PNaCl Web應用程序從第1部分轉換 爲使用Native Client SDK構建系統和經常使用JavaScript文件。它還演示了一些使您的Web應用程序符合內容安全策略(CSP)的技術,這是Chrome應用程序所必需的。html
使用Native Client SDK構建系統能夠輕鬆地使用全部SDK工具鏈進行構建,並在Debug和Release配置之間切換。它還簡化了項目的makefile,咱們將在下一節中看到。最後,它添加了一些用於運行和調試 應用程序的有用命令。java
能夠pepper_$(VERSION)/getting_started/part2
在Native Client SDK下載的目錄中找到此示例的完成代碼 。chrome
本節介紹如何使用SDK構建系統。爲此,咱們將在makefile中進行更改。由於part1和part2中的makefile是如此不一樣,因此從頭開始更容易。這是新makefile的內容。如下部分將更詳細地描述它。api
part1的makefile只支持一個工具鏈(PNaCl)和一個配置(Release)。它也只支持一個源文件。它相對簡單,但若是咱們想要添加對多個工具鏈,配置,源文件或構建步驟的支持,它將變得愈來愈複雜。SDK構建系統使用一組變量和宏來實現這一點,而不會顯着增長makefile的複雜性。瀏覽器
這是新的makefile,支持三個工具鏈(PNaCl,Newlib NaCl,Glibc NaCl)和兩個配置(Debug,Release)。安全
VALID_TOOLCHAINS := pnacl clang-newlib glibc NACL_SDK_ROOT ?= $(abspath $(CURDIR)/../..) include $(NACL_SDK_ROOT)/tools/common.mk TARGET = part2 LIBS = ppapi_cpp ppapi CFLAGS = -Wall SOURCES = hello_tutorial.cc # Build rules generated by macros from common.mk: $(foreach src,$(SOURCES),$(eval $(call COMPILE_RULE,$(src),$(CFLAGS)))) # The PNaCl workflow uses both an unstripped and finalized/stripped binary. # On NaCl, only produce a stripped binary for Release configs (not Debug). ifneq (,$(or $(findstring pnacl,$(TOOLCHAIN)),$(findstring Release,$(CONFIG)))) $(eval $(call LINK_RULE,$(TARGET)_unstripped,$(SOURCES),$(LIBS),$(DEPS))) $(eval $(call STRIP_RULE,$(TARGET),$(TARGET)_unstripped)) else $(eval $(call LINK_RULE,$(TARGET),$(SOURCES),$(LIBS),$(DEPS))) endif $(eval $(call NMF_RULE,$(TARGET),))
makefile首先指定對此項目有效的工具鏈。Native Client SDK構建系統支持其示例和庫的多工具鏈項目,但一般您在開始項目時選擇一個工具鏈,而不會更改它。有關詳細信息,請參閱Native Client概述的 工具鏈部分。服務器
在這個例子中,咱們支持pnacl
,clang-newlib
以及glibc
工具鏈。網絡
VALID_TOOLCHAINS := pnacl clang-newlib glibc
接下來,爲方便起見,咱們指定要查找的位置NACL_SDK_ROOT
。因爲此示例位於pepper_$(VERSION)/getting_started/part2
,所以SDK的根目錄是兩個目錄。app
NACL_SDK_ROOT ?= $(abspath $(CURDIR)/../..)
在您本身的項目中,您能夠在此處使用已安裝SDK的絕對路徑。您還能夠經過設置
NACL_SDK_ROOT
環境變量來覆蓋此默認值。有關更多詳細信息,請參閱本教程第1部分的步驟5。
接下來,咱們包含該文件tools/common.mk
。此文件提供Native Client SDK構建系統的功能,包括用於編譯和連接項目的新構建規則,咱們將在下面使用。
include $(NACL_SDK_ROOT)/tools/common.mk
包含以後tools/common.mk
,咱們經過指定項目名稱,它使用的源和庫來配置項目:
TARGET = part2 LIBS = ppapi_cpp ppapi CFLAGS = -Wall SOURCES = hello_tutorial.cc
這些變量名稱不是必需的,也不是SDK構建系統使用的; 它們僅用於下述規則。按照慣例,全部SDK makefile都使用如下變量:
目標
要構建的項目的名稱。此變量肯定將生成的庫或可執行文件的名稱。在上面的例子中,咱們調用目標part2
,它將生成一個名爲part2.pexe
PNaCl 的可執行文件 。對於NaCl工具鏈,可執行文件的文件名將爲其體系結構提供後綴。例如,調用ARM可執行文件part2_arm.nexe
。
LIBS
此可執行文件須要連接的庫列表。庫搜索路徑已設置爲僅查看當前工具鏈和體系結構的目錄。在這個例子中,咱們連接ppapi_cpp
和ppapi
。ppapi_cpp
須要使用Pepper C ++接口。ppapi
須要與瀏覽器通訊。
CFLAGS
要傳遞給編譯器的額外標誌列表。在這個例子中,咱們傳遞 -Wall
,它打開全部警告。
LDFLAGS
要傳遞給連接器的其餘標誌的列表。此示例不須要任何特殊的連接器標誌,所以省略此變量。
來源
要編譯的C或C ++源列表,用空格分隔。若是您有很長的源列表,若是您將每一個文件放在它本身的行上,而且\
用做行繼續符,則可能更容易閱讀。這是一個例子:
SOURCES = foo.cc \ bar.cc \ baz.cc \ quux.cc
對於許多項目,不須要更改如下構建宏; 他們將使用咱們上面定義的變量。
$(foreach src,$(SOURCES),$(eval $(call COMPILE_RULE,$(src),$(CFLAGS)))) ifneq (,$(or $(findstring pnacl,$(TOOLCHAIN)),$(findstring Release,$(CONFIG)))) $(eval $(call LINK_RULE,$(TARGET)_unstripped,$(SOURCES),$(LIBS),$(DEPS))) $(eval $(call STRIP_RULE,$(TARGET),$(TARGET)_unstripped)) else $(eval $(call LINK_RULE,$(TARGET),$(SOURCES),$(LIBS),$(DEPS))) endif $(eval $(call NMF_RULE,$(TARGET),))
第一行定義了SOURCES
使用如下標誌編譯每一個源的規則CFLAGS
:
$(foreach src,$(SOURCES),$(eval $(call COMPILE_RULE,$(src),$(CFLAGS))))
接下來的六行定義了將目標文件連接到一個或多個可執行文件的規則。當TOOLCHAIN
是pnacl
,僅存在一個生成的可執行:在上面的例子中,part2.pexe
。當使用NaCl工具鏈時,將生成三個可執行文件,每一個體繫結構對應一個體繫結構:在上面的示例中part2_arm.nexe
,part2_x86_32.nexe
和 part2_x86_64.nexe
。
若是CONFIG
是Release
,每一個可執行文件也被脫除調試信息,減小文件大小。不然,當TOOLCHAIN
是pnacl
,工做流程涉及建立一個未經剝離的二進制文件以進行調試,而後完成它並剝離它以進行發佈。
ifneq (,$(or $(findstring pnacl,$(TOOLCHAIN)),$(findstring Release,$(CONFIG)))) $(eval $(call LINK_RULE,$(TARGET)_unstripped,$(SOURCES),$(LIBS),$(DEPS))) $(eval $(call STRIP_RULE,$(TARGET),$(TARGET)_unstripped)) else $(eval $(call LINK_RULE,$(TARGET),$(SOURCES),$(LIBS),$(DEPS))) endif
最後,NMF規則生成一個NaCl清單文件(.nmf
),它引用上一步中生成的每一個可執行文件:
$(eval $(call NMF_RULE,$(TARGET),))
本節介紹了使part1與CSP兼容的HTML和JavaScript所需的更改。若是您要構建Chrome應用程序,則須要這樣作,但若是您想在開放網絡上使用PNaCl,則不須要這樣作。
Chrome Apps CSP限制您執行如下操做:
<script>
塊和事件處理程序(<button onclick="...">
)。eval()
和那樣的字符串到JavaScript的方法new Function()
。爲了使咱們的應用程序符合CSP,咱們必須刪除內聯腳本。如上所述,咱們不能使用內聯<script>
塊或事件處理程序。這很容易 - 咱們只會從腳本標記中引用一些新文件,並刪除全部內聯腳本:
<head> ... <script type="text/javascript" src="common.js"></script> <script type="text/javascript" src="example.js"></script> </head>
common.js
具備全部SDK示例使用的共享代碼,稍後將在本文檔中進行介紹。example.js
是一個腳本,具備特定於此示例的代碼。
咱們還須要刪除body標籤上的內聯事件處理程序:
<body onload="pageDidLoad()"> ...
這個邏輯如今由common.js
。處理。
最後,index.html
對於CSP合規性而言,有一些更改是沒必要要的,但有助於使SDK示例更通用。
首先,咱們 向body元素添加一些數據屬性,以指定名稱,支持的工具鏈,支持的配置和.nmf
文件路徑:
<body data-name="part2" data-tools="clang-newlib glibc pnacl" data-configs="Debug Release" data-path="{tc}/{config}"> ...
common.js
將讀取這些數據屬性,以容許您經過更改URL的查詢字符串來加載具備不一樣工具鏈的相同示例。例如,您能夠經過導航到加載此示例的glibc Debug版本 index.html?tc=glibc&config=Debug
。例如../
,路徑URI 不適用於data-path參數或其對應的查詢字符串。
接下來,咱們刪除embed
HTML中描述的元素。這將common.js
根據當前的工具鏈/配置組合自動添加:
<!-- Just as in part1, the <embed> element will be wrapped inside the <div> element with the id "listener". In part1, the embed was specified in HTML, here the common.js module creates a new <embed> element and adds it to the <div> for us. --> <div id="listener"></div>
common.js
包含JavaScript代碼,每一個示例用於建立NaCl模塊,處理來自該模塊的消息以及其餘常見任務,如顯示模塊加載狀態和記錄消息。解釋全部 common.js
內容超出了本文檔的範圍,但請查看該文件中的文檔以獲取更多信息。
既然咱們已經添加<script>
標籤common.js
,並example.js
給 head
元素,他們將被載入和文檔的其他部分被解析以前執行。所以,在嘗試建立embed元素並將其添加到頁面以前,咱們必須等待頁面完成加載。
咱們能夠經過呼叫addEventListener
和收聽 DOMContentLoaded
事件來作到這一點:
// Listen for the DOM content to be loaded. This event is fired when parsing of // the page's document has finished. document.addEventListener('DOMContentLoaded', function() { ... });
在此函數中,咱們解析URL查詢字符串,並將其與數據屬性進行比較:
// From https://developer.mozilla.org/en-US/docs/DOM/window.location var searchVars = {}; if (window.location.search.length > 1) { var pairs = window.location.search.substr(1).split('&'); for (var key_ix = 0; key_ix < pairs.length; key_ix++) { var keyValue = pairs[key_ix].split('='); searchVars[unescape(keyValue[0])] = keyValue.length > 1 ? unescape(keyValue[1]) : ''; } } ... var toolchains = body.dataset.tools.split(' '); var configs = body.dataset.configs.split(' '); ... var tc = toolchains.indexOf(searchVars.tc) !== -1 ? searchVars.tc : toolchains[0]; // If the config value is included in the search vars, use that. // Otherwise default to Release if it is valid, or the first value if // Release is not valid. if (configs.indexOf(searchVars.config) !== -1) var config = searchVars.config; else if (configs.indexOf('Release') !== -1) var config = 'Release'; else var config = configs[0];
而後domContentLoaded
調用,執行一些檢查以查看瀏覽器是否支持Native Client,而後建立NaCl模塊。
function domContentLoaded(name, tool, path, width, height, attrs) { updateStatus('Page loaded.'); if (!browserSupportsNaCl(tool)) { updateStatus( 'Browser does not support NaCl (' + tool + '), or NaCl is disabled'); } else if (common.naclModule == null) { updateStatus('Creating embed: ' + tool); // We use a non-zero sized embed to give Chrome space to place the bad // plug-in graphic, if there is a problem. width = typeof width !== 'undefined' ? width : 200; height = typeof height !== 'undefined' ? height : 200; attachDefaultListeners(); createNaClModule(name, tool, path, width, height, attrs); } else { // It's possible that the Native Client module onload event fired // before the page's onload event. In this case, the status message // will reflect 'SUCCESS', but won't be displayed. This call will // display the current message. updateStatus('Waiting.'); } }
attachDefaultListeners
在建立模塊以前添加,以確保沒有消息丟失。注意,window.attachListeners
也稱爲; 這是common.js
容許每一個示例以不一樣方式配置自身的方式。若是一個例子定義了該attachListeners
函數,它將被調用common.js
。
function attachDefaultListeners() { var listenerDiv = document.getElementById('listener'); listenerDiv.addEventListener('load', moduleDidLoad, true); listenerDiv.addEventListener('message', handleMessage, true); listenerDiv.addEventListener('crash', handleCrash, true); if (typeof window.attachListeners !== 'undefined') { window.attachListeners(); } }
最後,createNaClModule
實際上建立了embed
,並將其做爲元素的子項追加到id listener
:
function createNaClModule(name, tool, path, width, height, attrs) { var moduleEl = document.createElement('embed'); moduleEl.setAttribute('name', 'nacl_module'); moduleEl.setAttribute('id', 'nacl_module'); moduleEl.setAttribute('width', width); moduleEl.setAttribute('height', height); moduleEl.setAttribute('path', path); moduleEl.setAttribute('src', path + '/' + name + '.nmf'); ... var mimetype = mimeTypeForTool(tool); moduleEl.setAttribute('type', mimetype); var listenerDiv = document.getElementById('listener'); listenerDiv.appendChild(moduleEl); ... }
當模塊完成加載時,它將調度一個load
事件,並moduleDidLoad
調用上面()註冊的事件監聽器函數。請注意,common.js
容許每一個示例定義一個 window.moduleDidLoad
函數,該函數也將在此處調用。
function moduleDidLoad() { common.naclModule = document.getElementById('nacl_module'); updateStatus('RUNNING'); if (typeof window.moduleDidLoad !== 'undefined') { window.moduleDidLoad(); } }
如上一節所述,common.js
將在模塊加載過程當中調用某些函數。這個例子只須要響應兩個: moduleDidLoad
和handleMessage
。
// This function is called by common.js when the NaCl module is // loaded. function moduleDidLoad() { // Once we load, hide the plugin. In this example, we don't display anything // in the plugin, so it is fine to hide it. common.hideModule(); // After the NaCl module has loaded, common.naclModule is a reference to the // NaCl module's <embed> element. // // postMessage sends a message to it. common.naclModule.postMessage('hello'); } // This function is called by common.js when a message is received from the // NaCl module. function handleMessage(message) { var logEl = document.getElementById('log'); logEl.textContent += message.data; }
經過make
再次運行該命令來編譯Native Client模塊。
經過運行啓動SDK Web服務器make server
。
經過http://localhost:5103/part2
在Chrome中從新加載來從新運行該應用程序。
Chrome加載Native Client模塊後,您應該會看到從模塊發送的消息。
CC-By 3.0許可下提供的內容