原文连接: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许可下提供的内容