这篇博客文章由 Luca Greco 撰写,他是一位 Mozilla 社区成员,热爱黑客技术,尤其是在 JavaScript 和其他与 Web 相关的技术方面。
许多开发人员已经使用 Web 技术(例如,由 Phonegap/Cordova 等容器提供支持)创建移动应用程序,
通常是为了开发跨平台应用程序或利用他们现有的代码和/或专业知识。
因此,Firefox OS 由于以下几个原因,对许多人来说是一个非常有趣的项目。
- Web 应用程序可以无缝访问平台。
- Web 应用程序是本地的(更少的抽象级别,性能更佳)。
- 平台本身是基于 Web 的(并且可以使用 Web 技术进行定制)。
基于 Web 技术的移动平台可能是未来,我们现在可以触及它,更重要的是,我们可以帮助定义它并推动它发展,这得益于一个完全公开开发的平台(Firefox OS)。我无法抗拒这种诱惑,所以我开始深入研究 Firefox OS 代码,使用 MXR 抓取它,并研究 维基百科上的文档。
在 Firefox OS 上进行黑客攻击
在几周内,我准备好组装一个示例应用程序,并在该主题上进行一次非官方演示(在当地的 LinuxDay 2012 上展示)。
示例应用程序:Gaia(Firefox OS 的用户界面)的 Chrono
在这场演示中,我试图强调我认为 Firefox OS 和 Firefox 共有的一个重要优势。
“它不是一个静态的构建,它是有生命的!一个真正互动的环境,就像 Web 一样。”
我可以 telnet 到一个 B2G 实例内部,四处寻找有趣的东西,或运行 JavaScript 代码段以交互式地试验新的 WebAPI。有不止一种方法可以在 B2G 内部获取远程 JavaScript shell。
- Marionette,主要用于自动化测试。
- B2G 远程 JS Shell,一个可选地在 tcp 端口上公开的最小 JavaScript shell(我认为它将在未来的版本中被弃用)。
不幸的是,这些工具目前缺乏集成的检查实用程序(例如 console.log/console.dir 或 MozRepl repl.inspect 和 repl.search),因此在演示中,我选择将 MozRepl 作为扩展安装在 B2G 模拟器上,但在最近几周,远程 WebConsole 落到了 Firefox Nightly 中。
显然,RemoteWebConsole 还没有成熟,所以我们需要为错误和静默错误做好准备(例如,我们需要确保 B2G 启用了“远程调试器”,否则它将失败,不会显示任何错误),但它具有对象检查、网络进度记录、JavaScript 和 CSS 错误(与我们本地的 Firefox WebConsole 相同)的功能。
为 Firefox OS 开发
根据我的经验,为 Firefox OS 开发 OpenWebApps 与基于 Phonegap 类技术的混合应用程序并没有什么不同。
我们将尝试使用台式机浏览器及其开发工具,使用模拟/垫片代替原生功能,对应用程序的主要部分进行编码和测试。但在研究的 2 周时间里,我收集了大量的有用工作笔记,可以与大家分享,因此在接下来的部分中,我将
- 回顾开发流程和工具。
- 总结一些有用的技巧和窍门。
- 发布到公众(独立发布和在 Mozilla 市场 发布)。
我选择创建一个简单的秒表作为示例应用程序。
工作流程和工具
在此体验过程中,我的工具集由以下部分组成:
- VoloJS – 开发服务器和自动生产构建(压缩 js/css,生成 manifest.appcache)。
- Firefox Nightly 网页开发者工具 – 标记视图、倾斜、响应式设计视图和 Web 控制台。
- R2D2B2G – 从 Firefox 附加组件集成/互操作 B2G。
使用 Volo,我可以从 volo 集成的 http 服务器测试我的应用程序,使用 Require.js 将 JavaScript 代码拆分为模块,最后生成一个生产版本,该版本经过压缩,并可选地配备一个自动生成的 manifest.appcache。
在我的开发周期中,我会反复
- 进行更改。
- 重新加载并在台式机浏览器上检查更改。
- 使用 R2D2B2G 在 b2g-simulator 上尝试更改。
- 在台式机浏览器上或远程在 b2g-simulator 上进行调试。
- 重新开始循环 :-)
使用我喜欢的台式机浏览器(当然是 Firefox :-P),我有机会使用非常强大的检查/调试工具,这些工具通常在移动 Web 运行时不可用。
- 标记查看器用于检查和更改 DOM 树状态。
- 样式编辑器用于检查和更改 CSS 属性。
- 倾斜用于检查屏幕外 DOM 元素的位置。
- Web 控制台用于检查和更改 JavaScript 环境。
得益于 Mozilla 的新项目,如“Firefox OS”和“Firefox for Android”,越来越多的此类工具现在可以作为“远程 Web 工具”使用,并且可以连接到远程实例。
技巧和窍门
Gaia UI 构建块
Gaia 不仅仅是一个 B2G UI 实现,它还是一个设计风格指南和一个预构建的 CSS 样式集合,这些样式实现了这些指南。
我们可以从上面的库中导入组件的样式并将其应用于我们的应用程序,以便在 Firefox OS 上实现非常美观的原生外观和感觉。一些组件并不稳定,这意味着它们可能与其他组件的样式发生不良交互,或者无法在所有平台上完美运行(例如 Firefox Desktop 或 Firefox for Android),但通常我们可以通过使用一些自定义和更具体的 CSS 规则来解决这个问题。
它不像一个成熟的、完整的 CSS 框架(例如 Bootstrap),但它很有前景,我相信它会变得更好。
使用响应式设计视图,我们可以测试不同的分辨率(和方向),这有助于即使不测试我们应用程序在 Firefox OS 或 Firefox for Android 设备上的表现,也能获得良好的、一致的结果,但我们应该注意与 dpi 相关的调整,因为目前我们无法完全识别它在使用台式机浏览器时的外观。
应用程序面板
许多应用程序需要多个面板,所以我首先查看了官方 Gaia 应用程序,以了解原生应用程序如何实现这个几乎必不可少的特性。这是 Gaia 时钟应用程序从“倾斜”角度看的样子。
面板是简单的 DOM 元素(例如 section 或 div 标签),最初位于屏幕外,并使用 CSS 过渡移动到屏幕上。
在“Chrono”应用程序中,你会在抽屉(一个不稳定的 Gaia UI 构建块)中找到这种策略。
以及在 Laps 和 About 面板中(与 :target 伪类 相结合)。
迷人的 -moz-element 技巧
这是一个非常迷人的技巧,它用在 Firefox OS 上的时间选择器组件中。
以及在 Firefox Nightly Desktop 上的标记视图中(当前已禁用)。
得益于这个非标准的 CSS 特性,我们可以将一个 DOM 元素用作另一个 DOM 元素的背景图像,例如,将一个复杂的屏幕外视觉组件集成到可见空间作为单个 DOM 元素。
来自 index.html 的片段
...
...
来自 chrono.css 的片段
...
/* NOTE: set fixed size on empty visible elements
[role="chrono"] > p span {
width: -moz-calc(100% / 3);
height: 100%;
...
}
...
/* NOTE: move real offscreen DOM elements
[role="chrono"] > div {
position: absolute;
left: -moz-calc((100%) + 10px);
...
}
...
来自 chrono.js 的片段
...
// NOTE: generate seconds, minutes and hours elements
for (var i=0; i<60; i++) {
var txt = i < 10 ? "0"+i : i;
var el = $("
使用 -moz-element
和 -moz-calc
(在 CSS 规则中计算组件大小,并已包含在 CSS3 中作为 calc
)非常简单,但你可以在 MDN 上了解更多关于该主题的信息。
发布到公众
WebApp 清单
在我们的开发周期中,我们使用 R2D2B2G 菜单选项将应用程序安装到 B2G 模拟器中,所以我们不需要真正的 manifest.webapp,但当准备好公开发布或发布给测试用户时,我们必须创建一个真正的 manifest.webapp。
创建 manifest.webapp 并不困难,它只是一个简单且有良好文档的 json 文件格式:App/Manifest 在 MDN 上。
调试与此清单文件相关的错误仍然是一个未知领域,一些技巧可能会有用。
- 如果清单文件包含语法错误或无法下载,则将向旧的错误控制台(不,它们不会报告到新的 Web 控制台中)静默报告错误。
- 如果你的应用程序在其域中作为子目录访问,则必须将此路径包含在清单中指定的资源路径(例如 launch_path、appcache_path、icons)中,稍后会详细介绍。
- 你可以在应用程序中添加一个卸载按钮,以帮助你作为开发人员(和测试用户)以平台无关的方式卸载应用程序(因为“如何卸载”已安装的 Web 应用程序在台式机、Android 或 Firefox OS 上会有所不同)。
使用 OpenWebApps API,我在“Chrono”中添加了一些代码,让用户能够自己安装它。
从你的浏览器将应用安装到你的桌面系统。
/* Mozilla/Firefox installation */
var base = location.href.split("#")[0]; // WORKAROUND: remove hash from url
base = base.replace("index.html",""); // WORKAROUND: remove index.html
install.mozillaInstallUrl = base + '/manifest.webapp';
install.mozillaInstall = function () {
var installRequest = navigator.mozApps.install(install.mozillaInstallUrl);
installRequest.onsuccess = function (data) {
triggerChange('installed');
};
installRequest.onerror = function (err) {
install.error = err;
triggerChange('error');
};
};
检查它是否已安装(作为 Web 应用运行时应用程序或浏览器选项卡中的自助安装程序)。
function get_chrono_installed(success,error) {
try1();
// TRY1: get our application management object using getSelf()
// this works correctly when running into the webapp runtime container
function try1() {
req1 = navigator.mozApps.getSelf();
req1.onsuccess = function () {
if (req1.result === null) {
try2();
} else {
success(req1.result);
}
};
req1.onerror = function () {
try2();
}
}
// TRY1: get our application management object using getInstalled()
// this works correctly when running as "self service installer"
// in a Firefox browser tab
function try2() {
req2 = navigator.mozApps.getInstalled();
req2.onsuccess = function () {
var result = null;
var myorigin = window.location.protocol + "//" + window.location.host;
if (req2.result !== null) {
req2.result.forEach(function (app) {
if (app.origin == myorigin)
result = app;
});
}
success(result);
}
req2.onerror = error;
}
}
在 Linux 桌面系统上,当你从 Firefox 安装 OpenWebApp 时,它会在你的“.local/share/applications”隐藏目录中创建一个新的启动器(“.desktop”文件)。
$ cat ~/.local/share/applications/owa-http;alcacoop.github.com.desktop
[Desktop Entry]
Name=Chrono
Comment=Gaia Chronometer Example App
Exec="/home/rpl/.http;alcacoop.github.com/webapprt-stub"
Icon=/home/rpl/.http;alcacoop.github.com/icon.png
Type=Application
Terminal=false
Actions=Uninstall;
[Desktop Action Uninstall]
Name=Uninstall App
Exec=/home/rpl/.http;alcacoop.github.com/webapprt-stub -remove
正如你将注意到的,当前的惯例(和实现)只支持每个域一个应用程序,如果你看一下我们已安装的 Web 应用的隐藏目录,你会发现一个单独的 webapp.json 配置文件。
$ ls /home/rpl/.http;alcacoop.github.com/ Crash Reports icon.png profiles.ini webapp.ini webapp.json webapprt-stub z8dvpe0j.default
这些限制的原因在 MDN 上有记录:关于应用清单的常见问题解答
为了帮助你自己调试应用程序在 Web 应用运行时运行时的错误,你可以从命令行运行它并启用旧的(但仍然有用的)错误控制台。
$ ~/.http;alcacoop.github.com/webapprt-srt -jsconsole
卸载 OpenWebApp 非常简单,你可以使用“OpenWebApp 隐藏目录”中的“wbapprt-stub”可执行文件手动将其删除(平台相关方法)。
$ ~/.http;alcacoop.github.com/webapprt-stub -remove
或者从 JavaScript 代码中卸载,就像我在“Chrono”中做的那样,让用户能够从 Firefox 浏览器选项卡中卸载应用程序。
function uninstall_error() { $('.install-error').html("UNINSTALL ERROR: retry later."); }
function uninstall_success() {
install.state = "uninstalled";
updateInstallButton();
$('.install-error').html("UNINSTALL: app removed.");
}
function uninstall() {
get_chrono_installed(function (app) {
var req2 = app.uninstall();
req2.onsuccess = uninstall_success;
req2.onerror = uninstall_error;
}, uninstall_error);
return false;
}
AppCache 清单
这是一个很久以前就集成到主要浏览器中的功能,但现在,由于 OpenWebApps 的出现,它变得非常必要:如果没有 manifest.appcache 和处理升级的适当 JavaScript 代码,我们的 Web 应用将无法正确离线工作,并且感觉不到像一个真正的已安装应用程序。
目前,Appcache 就像一种黑魔法,它应该有一个像 Chuck Norris 一样的“事实页面”:AppCacheFacts.
感谢 volo-appcache 命令,生成 manifest.appcache 就像一条命令一样简单。
$ volo appache
...
$ ls -l www-build/manifest.appcache
...
$ tar cjvf release.tar.bz2 www-build
...
不幸的是,当你需要调试/测试你的 manifest.appcache 时,你只能靠自己,因为目前 Firefox 中没有集成任何友好的调试工具。
- appcache 下载进度(和错误)目前没有在 WebConsole 中报告。
- appcache 错误不包含错误消息/描述。
- Firefox for Android 和 Firefox OS 没有 UI 来清理你的 applicationCache。
调试 appcache 问题可能非常棘手,因此这里有一些我在这个实验中学到的技巧。
- 订阅每个 window.applicationCache 事件('error'、'checking'、'noupdate'、'progress'、'downloading'、'cached'、'updateready' 等),并在开发和调试期间记录所有接收到的事件/错误消息。
- 在你的第一个公开版本中添加升级处理代码(或者准备好挨家挨户帮助你的用户升级 :-D)。
- 在 Firefox for Desktop 上,你可以从“偏好设置”对话框中删除 applicationCache。
- 分析服务器端日志,了解“appcache 更新”卡在哪里。
- 当你运行 Firefox 或 B2G 时,激活 applicationCache 内部日志记录,了解它卡在哪里。
(来自 https://mxr.mozilla.org/mozilla-central/source/uriloader/prefetch/nsOfflineCacheUpdate.cpp#62)export NSPR_LOG_MODULES=nsOfflineCacheUpdate:5 export NSPR_LOG_FILE=offlineupdate.log firefox -no-remote -ProfileManager & tail -f offlineupdate.log -1614710976[7fc59e91f590]: nsOfflineCacheUpdate::Init [7fc55959ce50] -1614710976[7fc59e91f590]: nsOfflineCacheUpdate::AddObserver [7fc56a9fcc08] to update [7fc55959ce50] -1614710976[7fc59e91f590]: nsOfflineCacheUpdate::AddObserver [7fc55c3264d8] to update [7fc55959ce50] -1614710976[7fc59e91f590]: nsOfflineCacheUpdate::Schedule [7fc55959ce50] -1614710976[7fc59e91f590]: nsOfflineCacheUpdateService::Schedule [7fc57428dac0, update=7fc55959ce50] -1614710976[7fc59e91f590]: nsOfflineCacheUpdateService::ProcessNextUpdate [7fc57428dac0, num=1] -1614710976[7fc59e91f590]: nsOfflineCacheUpdate::Begin [7fc55959ce50] -1614710976[7fc59e91f590]: nsOfflineCacheUpdate::NotifyState [7fc55959ce50, 2] -1614710976[7fc59e91f590]: 7fc559d0df00: Opening channel for http://html5dev:8888/gaia-chrono-app/manifest.appcache -1614710976[7fc59e91f590]: loaded 3981 bytes into offline cache [offset=0] -1614710976[7fc59e91f590]: Update not needed, downloaded manifest content is byte-for-byte identical -1614710976[7fc59e91f590]: done fetching offline item [status=0] -1614710976[7fc59e91f590]: nsOfflineCacheUpdate::LoadCompleted [7fc55959ce50] -1614710976[7fc59e91f590]: nsOfflineCacheUpdate::NotifyState [7fc55959ce50, 3] -1614710976[7fc59e91f590]: nsOfflineCacheUpdate::Finish [7fc55959ce50] -1614710976[7fc59e91f590]: nsOfflineCacheUpdateService::UpdateFinished [7fc57428dac0, update=7fc55959ce50] -1614710976[7fc59e91f590]: nsOfflineCacheUpdateService::ProcessNextUpdate [7fc57428dac0, num=0] -1614710976[7fc59e91f590]: nsOfflineCacheUpdate::NotifyState [7fc55959ce50, 10] -1614710976[7fc59e91f590]: nsOfflineCacheUpdate::RemoveObserver [7fc56a9fcc08] from update [7fc55959ce50] -1614710976[7fc59e91f590]: nsOfflineCacheUpdate::RemoveObserver [7fc55c3264d8] from update [7fc55959ce50]
在“Gaia Chrono App”中添加 applicationCache 支持后,我使用了所有这些技巧,最终发现 Firefox 没有向我发送“updateready”事件,因此我无法告诉用户重新加载页面以开始使用新的(并且已缓存的)版本。在更好地理解了问题之后,并在 MXR 上搜索代码,并在 Bugzilla 上搜索票证,我终于在错误跟踪器中找到一个现有的票证:错误 683794:当 HTML5 应用缓存应用程序更新时,onupdateready 事件不会触发.
解决此错误的方法非常简单(比跟踪它更容易):在脚本标签中向 applicationCache 对象添加一个虚拟的“updateready”监听器,以确保它会被触发。
...
如果你打算开始使用此功能(迟早你会使用的),请准备好
- 按照标准建议实现。
- 调试它为什么没有按预期工作的原因。
- 搜索已有的错误,或者如果它没有被报告,就提交一个错误(注意:这一点非常重要!:-D)。
- 找到一个解决方法。
这绝对是一个需要更多 Web 开发者工具支持的功能,因为普通的 Web 开发者不想从“浏览器内部”的角度调试他们的 Web 应用。
移植到 Firefox for Android
OpenWebApps 的一个有趣功能是“它们可以在任何受支持的平台上安装,几乎不需要任何修改”。例如,我们可以使用 Firefox Nightly 在我们的桌面系统上安装我们的“Chrono”应用程序,并使用 Firefox for Android Nightly 在 Android 上安装。
在我看来,Firefox for Android 可以成为 OpenWebApps 甚至 Firefox OS 未来发展的一个战略平台:Android 已经是广受欢迎的移动平台,让开发者能够从同一个代码库中发布他们的应用程序到 Firefox OS 和 Android 上,这是一个很大的优势。
我在将“Chrono”应用程序移植到 Android 时遇到的唯一问题与 Firefox for Android 的不同渲染行为有关(以及随之而来的 WebAppRT 中的渲染行为,WebAppRT 包含我们的应用程序)。
GeckoScreenshot 服务只会当它检测到更改时,才会在更改的地方强制重新绘制。此功能与 -moz-element 技巧配合得不好,需要一些帮助来了解真正需要重新绘制的内容。
// Firefox For Android: force redraw workaround
define(function (require) {
var is_firefox = navigator.userAgent.toLowerCase().indexOf('firefox') > -1;
var is_android = navigator.userAgent.toLowerCase().indexOf("android") > -1;
var n = document.createTextNode(' ');
return {
force_redraw: function (element) {
if(is_firefox && is_android) {
rafId = window.mozRequestAnimationFrame(
function(){
element.appendChild(n);
n.parentNode.removeChild(n)
window.mozCancelRequestAnimationFrame(rafId);
}
);
}
}
}
});
发布到公众
GitHub 页面是一个快速简便的发布应用程序到公众的选择,感谢 volo-ghdeploy 命令,发布变得更加简单。
$ volo appcache && volo ghdeploy
...
当你将 OpenWebApp 部署到给定域的子目录中时(例如使用 GitHub Pages),你应该考虑到 manifest.webapp 中的路径需要相对于你的源(协议+主机+端口),而不是你当前的 URL。
{
...
"launch_path": "/gaia-chrono-app/index.html",
"appcache_path": "/gaia-chrono-app/manifest.appcache",
...
"icons": {
"128": "/gaia-chrono-app/icons/chrono-128.png",
"64": "/gaia-chrono-app/icons/chrono-64.png",
"120": "/gaia-chrono-app/icons/chrono-120.png",
"60": "/gaia-chrono-app/icons/chrono-60.png"
},
...
}
我们只能从每个源安装一个 OpenWebApp,因此如果你想从你的 GitHub 页面部署多个应用程序,你需要配置 GitHub 页面,使其暴露在自定义域上:GitHub 帮助 - 使用页面设置自定义域.
当我们的应用程序最终上线并公开可访问时,我们可以将其提交到 Mozilla 市场,并获得更高的知名度。
在应用程序提交过程中,你的 manifest.webapp 将被验证,如果需要,你将收到有关如何调整它以完成提交的警告。
- 缺少信息(例如名称或图标)相关的错误。
- 无效值(例如方向)相关的错误。
与其他移动市场一样,你应该收集并填写你的提交信息。
- manifest.webapp URL(注意:它将在开发者面板中是只读的,你无法更改它)。
- 更长的描述和功能列表。
- 简短的版本描述。
- 一个或多个屏幕截图。
迷你市场
Mozilla 市场的目标是帮助 OpenWebApps 提高知名度,就像其他移动商店目前为其生态系统所做的那样,但是 Mozilla 将这个项目命名为 OpenWebApps 是有原因的。
Mozilla 并不是唯一一个可以为 OpenWebApps 创建市场的人!Mozilla 的设计理念是赋予我们与 Web 上相同的自由,不多也不少。
这是一个非常强大的功能,因为它为开发者打开了大量有趣的用例。
- Firefox OS 设备上的运营商应用程序市场。
- 非公开应用程序安装程序/管理器。
- 内联网应用程序安装程序/管理器。
结论
显然,Firefox OS 和 OpenWebApps 目前还没有完全完成(但正在以惊人的速度改进),Firefox OS 还没有正式发布 SDK,但是 Web 也没有正式发布 SDK,我们已经每天都在使用它做一些很棒的事情。
因此,如果你对移动平台感兴趣,并且想了解移动平台是如何诞生和发展的,或者你是一名 Web 开发者,并且希望在移动生态系统中使用更多 Web 技术……
你应该认真考虑一下 Firefox OS。
我们应该拥有一个更开放的移动生态系统,让我们现在开始推动它,让我们帮助 OpenWebApps 和 Firefox OS 成为我们新的强大工具!
祝你编程愉快!
12 条评论