如何在 Firefox OS 上制作浏览器应用程序

Firefox OS 是一个基于 Firefox 网页浏览器引擎构建的操作系统,该引擎称为 Gecko。Firefox OS 上的浏览器应用程序提供了一个使用 HTML5 技术编写的用户界面,并使用 浏览器 API 管理网页浏览。它还根据实现管理选项卡、浏览历史记录、书签等。

虽然 Firefox OS 已经包含一个浏览器,但您可以使用浏览器 API 来创建自己的浏览器或将浏览器功能添加到您的应用程序。本文介绍了如何为 Firefox OS 设备构建浏览器应用程序。按照这些步骤,您将获得一个基本的 Firefox OS 浏览器应用程序,其中包含一个地址栏和后退/前进按钮以浏览网页。

源代码可以在 https://github.com/begeeben/firefox-os-browser-sample 下载。

先决条件:WebIDE

您可以通过 Firefox 34 或更高版本使用 WebIDE。开发、构建或测试浏览器应用程序不需要 Firefox OS 设备。通过使用 WebIDE,我们可以轻松地引导一个 Web 应用程序,进行 HTML/CSS/JS 修改,并在 Firefox OS 模拟器之一上运行应用程序。要在 Firefox 中打开 WebIDE,请从顶部菜单中选择**工具 > Web 开发者 > WebIDE**

open webide

从模板创建应用程序

首先,使用 WebIDE 空模板引导一个 Web 应用程序。应用程序类型需要是特权的,以便可以设置browser权限。为了使用 浏览器 API,需要此权限。 浏览器 API 提供了管理 iframe 的附加方法和事件。

new priviledged app

以下是应用程序的外观截图。接下来我们将开始添加代码以制作一个简单的浏览器应用程序。

mybrowser empty

编辑 manifest.webapp

让我们开始进行一些代码更改。模板已经在 manifest.webapp 中将应用程序类型设置为privileged

  "type": "privileged",

要允许我们的应用程序使用浏览器 API,我们需要指定“browser”权限。

  "permissions": {
    "browser": {}
  },

HTML 结构

我们正在构建的浏览器应用程序将在顶部有一个工具栏,以及一个作为 iframe 容器的 div,默认情况下它显示“Hello myBrowser!”。浏览器 iframe 将稍后使用 JavaScript 创建。我们可以在 HTML 中添加 iframe,但在本示例中,将动态创建新的浏览器 iframe。


Hello myBrowser!

以下是简单浏览器应用程序的截图

browser startup

管理浏览器 iframe

浏览器应用程序必须处理mozbrowser事件,这些事件在应用程序具有browser权限时可用。为了分离 UI 和mozbrowser事件处理,并保留将来支持多个选项卡的灵活性,我们添加了一个 tab.js 文件。以下是 tab.js 中的代码,该代码使用“mozbrowser”属性创建了一个 iframe 以启用 浏览器 API 的使用。

/**
 * Returns an iframe which runs in a child process with Browser API enabled
 * and fullscreen is allowed
 *
 * @param  {String} [url] Optional URL
 * @return {iframe}       An OOP mozbrowser iframe
 */
function createIFrame (url) {
  var iframe = document.createElement('iframe');
  iframe.setAttribute('mozbrowser', true);
  iframe.setAttribute('mozallowfullscreen', true);
  iframe.setAttribute('remote', true);

  if (url) {
    iframe.src = url;
  }

  return iframe;
}

mozallowfullscreen属性使 iframe 的嵌入网页能够使用全屏模式。网页可以通过调用Element.mozRequestFullscreen()来请求全屏模式。

remote”属性将嵌入的 iframe 分隔到另一个子进程中。出于安全原因,需要这样做,以防止恶意网站破坏浏览器应用程序。目前,此属性在此示例浏览器应用程序中没有任何作用,因为嵌套 OOP 尚未实现。请参阅 错误 1020135 (nested-oop) [meta] 允许嵌套 oop <iframe mozbrowser>.

接下来,将“Tab”对象构造函数添加到 tab.js。此构造函数处理浏览器 iframe 创建和浏览器事件处理。当构造一个 Tab 时,它会创建一个浏览器 iframe 并将mozbrowser事件侦听器附加到它。

/**
 * The browser tab constructor.
 *
 * Creates an iframe and attaches mozbrowser events for web browsing.
 *
 * Implements EventListener Interface.
 *
 * @param {String} url An optional plaintext URL
 */
function Tab (url) {
  this.iframe = createIFrame(url);
  this.title = null;
  this.url = url;

  this.iframe.addEventListener('mozbrowserloadstart', this);
  this.iframe.addEventListener('mozbrowserlocationchange', this);
  this.iframe.addEventListener('mozbrowsertitlechange', this);
  this.iframe.addEventListener('mozbrowserloadend', this);
  this.iframe.addEventListener('mozbrowsererror', this);
};

还有其他未在此简单浏览器应用程序中使用的mozbrowser事件。在此示例中使用的事件可以访问有关 iframe 的有用信息,而我们的浏览器应用程序使用它们来提供网页详细信息,例如标题、URL、加载进度、上下文菜单等。

例如,要在工具栏上显示页面标题,我们使用mozbrowsertitlechange事件来检索网页的标题。更新标题后,会分发一个带有 Tab 本身的详细信息的CustomEvent 'tab:titlechange',以通知其他组件更新标题。

Tab.prototype.mozbrowsertitlechange = function _mozbrowsertitlechange (e) {
  if (e.detail) {
    this.title = e.detail;
  }

  var event = new CustomEvent('tab:titlechange', { detail: this });
  window.dispatchEvent(event);
};

现在可以在主 app.js 中添加代码来处理此自定义事件,该事件将更新标题。

/**
 * Display the title of the currentTab on titlechange event.
 */
window.addEventListener('tab:titlechange', function (e) {
  if (currentTab === e.detail) {
    urlInput.value = currentTab.title;
  }
});

在提交事件中浏览网页

在浏览器中使用地址栏作为搜索栏是一种常见的实用做法。当用户提交输入时,首先我们会检查它是否是有效的 URL。如果不是,则输入被视为搜索词,并将其附加到搜索引擎 URI。请注意,可以以多种方式处理 URL 的验证。示例应用程序使用了一个类型属性设置为 URL 的输入元素。还可以使用一个 URL 对象,并将 URL 字符串作为第一个参数传递给构造函数。如果 URL 无效,则会抛出 DOM 异常。然后可以捕获此异常,并假定 URL 字符串是搜索词。

/**
 * The default search engine URI
 *
 * @type {String}
 */
var searchEngineUri = 'https://search.yahoo.com/search?p={searchTerms}';

/**
 * Using an input element to check the validity of the input URL. If the input
 * is not valid, returns a search URL.
 *
 * @param  {String} input           A plaintext URL or search terms
 * @param  {String} searchEngineUri The search engine to be used
 * @return {String}                 A valid URL
 */
function getUrlFromInput(input, searchEngineUri) {
  var urlValidate = document.createElement('input');
  urlValidate.setAttribute('type', 'url');
  urlValidate.setAttribute('value', input);

  if (!urlValidate.validity.valid) {
    var uri = searchEngineUri.replace('{searchTerms}', input);
    return uri;
  }

  return input;
}

然后可以在主 app.js 中添加代码来处理提交按钮。此代码检查是否存在当前活动的Tab对象,如果存在,则加载 URL 或如果 URL 未更改则重新加载 URL。如果不存在,则创建该对象并加载 URL。

/**
 * Check the input and browse the address with a Tab object on url submit.
 */
window.addEventListener('submit', function (e) {
  e.preventDefault();

  if (!currentUrlInput.trim()) {
    return;
  }

  if (frameContainer.classList.contains('empty')) {
    frameContainer.classList.remove('empty');
  }

  var url = getUrlFromInput(currentUrlInput.trim(), searchEngineUri);

  if (!currentTab) {
    currentTab = new Tab(url);
    frameContainer.appendChild(currentTab.iframe);
  } else if (currentUrlInput === currentTab.title) {
    currentTab.reload();
  } else {
    currentTab.goToUrl(url);
  }
});

currentTab.reload函数使用Browser API提供的iframe.reload函数来重新加载页面。以下代码添加到 tab.js 文件中以处理重新加载。

/**
 * Reload the current page.
 */
Tab.prototype.reload = function _reload () {
  this.iframe.reload();
};

启用/禁用后退和前进按钮

最后,可以使用 浏览器 API 来检查页面是否可以在导航历史记录中后退或前进。iframe.getCanGoBack方法返回一个DOMRequest对象,该对象在其onsuccess回调中指示页面是否可以后退。我们使用Promise来包装此函数,以便更轻松地访问。此代码添加到 tab.js 文件中。

/**
 * Check if the iframe can go backward in the navigation history.
 *
 * @return {Promise} Resolve with true if it can go backward.
 */
Tab.prototype.getCanGoBack = function _getCanGoBack () {
  var self = this;

  return new Promise(function (resolve, reject) {
    var request = self.iframe.getCanGoBack();

    request.onsuccess = function () {
      if (this.result) {
        resolve(true);
      } else {
        resolve(false);
      }
    };
  });
};

当页面加载时,将在 app.js 文件中执行检查。当 iframe 接收mozbrowserloadend事件时,就会发生这种情况。

/**
 * Enable/disable goback and goforward buttons accordingly when the
 * currentTab is loaded.
 */
window.addEventListener('tab:loadend', function (e) {
  if (currentTab === e.detail) {
    currentTab.getCanGoBack().then(function(canGoBack) {
      gobackButton.disabled = !canGoBack;
    });

    currentTab.getCanGoForward().then(function(canGoForward) {
      goforwardButton.disabled = !canGoForward;
    });
  }
});

运行应用程序

现在可以使用 Firefox OS 模拟器之一来测试示例浏览器应用程序。

Firefox_WebIDE_installing_simulator

在 WebIDE 的右上角,单击**选择运行时**以选择一个 Firefox OS 手机或模拟器

Firefox_WebIDE_choose_simulator

选择并启动 Firefox OS 运行时后,您可以通过单击 WebIDE 中的播放按钮来安装并运行该应用程序。有关构建、调试和部署 Firefox OS 应用程序的更多信息,请参阅 WebIDE 文档。

mybrowser finished

最后的想法

您可以向这个简单的浏览器添加更多功能以使其真正有用,包括书签、设置、搜索建议和共享。随意扩展此代码并享受使用乐趣。让我们知道您的想法,并务必传播 Firefox OS 的信息!

其他信息

https://hacks.mozilla.ac.cn/2014/08/building-the-firefox-browser-for-firefox-os/
https://mdn.org.cn/en-US/docs/Web/API/Using_the_Browser_API

关于 Luca Greco

Luca Greco 的更多文章…


2 评论

  1. Brett Zamir

    这真的很棒。是否有机会看到这种方式最终在桌面(无需模拟器)上被激活,以便像“超级全屏模式”一样,可以安装这种浏览器 UI 来替换 Firefox 的 UI,并在打开 URL 时(即使仅通过特权安装和切换默认浏览器的选项——类似于“http”处理程序)?

    对于一个正常的网站来说,如果使用浏览器 iframe 来提供独立的导航控件,例如,在每节经文旁边显示维基页面(包含注释和相关外部链接),如果用户点击其中一个注释维基页面中的链接,他们可以在该单个 iframe 中独立地进行后退和前进,同时仍然可以看到经文和其他注释维基页面?

    2015 年 3 月 20 日 于 21:26

    1. voracity

      不确定这是否完全回答了你的第一个问题,但 Mozilla 正在开发一个(非常)实验性的基于 HTML 的桌面浏览器 UI:https://github.com/mozilla/browser.html。我的(可能不准确)理解是它可能被用作 Servo 的界面。

      2015 年 3 月 21 日 于 18:21

本文评论已关闭。