Mozilla

Articles

Sort by:

View:

  1. Creating a mobile app from a simple HTML site: Part 2

    Or: Making our simple app work for others

    In the first part of this series, which began late last year, we worked through the process of developing a school planner app. At this point (see the final code from Part 1) we’ve got multiple school plans displayed at once, and we’ve got Web, iOS and Android support via Cordova.

    Stage4 Result Screenshot<br />

    Let’s imagine that others have seen the benefits and would like to use our app as well. Creating an app tailored to them should be possible by simply replacing one file (let’s name it www/app_data/plans.json) with their family’s data and making a few other tweaks. This is where we will focus in the beginning of this part.

    Our eventual goal is to create an app to display data (school plans) stored on a server, so every user can view their own data from any computer. In this tutorial, the server part will be minimized to avoid distraction from the main goal — the database of plans is going to be written into a JSON file. If you wish to extend it into SaaS please do so, and let us know in the comments section below.

    What will be built

    A mobile application which will:

    1. Display school plan(s).
    2. Work offline.
    3. Work on many platforms.
    4. Use school plans stored on a server/in a JSON file (our goal for next parts of the series).

    Prerequisites

    If you haven’t worked through the first article then we suggest you do so now. You should at least go through the part 1 prerequisites and make sure you are familiar with them.

    Then follow the steps below, updating your code example from the previous article. If you don’t have this code example, (i.e., if you’ve not gone through the previous article), follow the instructions on how to load any stage in this tutorial. Use stage4 as a starting point.

    You should also make sure you have NodeJS installed.

    Adding an icon

    In Part 1, I omitted the icon, so Cordova added a default one. Let’s make the app look more professional with a custom icon. I’ve downloaded a backpack icon from findicon.com resized it and copied to www/img. In the app directory (school-plan/ — created after running cordova create) — edit the config.xml file and add the following:

    <icon src="www/img/backpack-128.png" />

    If you wish you can add more specific information – for example, more image sizes for each desired platform. You can find more information in Cordova docs. Here is the special definition for Firefox OS:

    <platform name="firefoxos">
    	<icon width="128" height="128" src="www/img/backpack-128.png" />
    	<icon width="64" height="64" src="www/img/backpack-64.png" />
    	<icon width="32" height="32" src="www/img/backpack-32.png" />
    </platform>
    

    Due to an error in the Firefox OS part of Cordova, please also create the www/icon directory using the following command.

    mkdir www/icon

    Modifying the data code

    In this stage we will modify the school plan app to work with data instead of plain HTML (see finished stage 5 code on GitHub.)

    Before we had all school plan’s data hard coded into index.html. Now we will separate the data from its visual representation. To do so, we will remove the old data from www/index.html, leaving only the minimal structure behind.

    Empty both the <brick-tabbar> and <brick-deck> elements in your index.html file, so they are left looking like so:

    <brick-tabbar id="plan-group-menu" selected-index="0">
    </brick-tabbar>
    <brick-deck id="plan-group" selected-index="0">
    </brick-deck>
    

    The most common data structure used in JavaScript projects is JSON; we will therefore add our data in this format. For now it’s OK to distribute the school plan together with the app (later on we will probably serve it from a server instead, for greater flexibility). Our JSON contains an Array of plans. Plan is an Object containing title, id, optional active field and week. The latter is itself an Array of lesson plans.

    Note: use jsHint to check the quality of your code.

    [
        {
          "title": "Name of the plan",
          "id": "id-of-the-plan",
          "active": 1,  // should the plan be active now?
          "week": [
            [
              "first hour of Monday",
              "second hour",
              "third",
              // ... and so on
            [],  // no activities on Tuesday
            ], [
              "",
              "",
              "Wednesday is starting on third hour",
              "fourth"
            ]
        }
    ]
    

    You can find a sample file on Github. Copy this into your project at www/app_data/plans.json.

    Now we need to read this JSON file from the app’s directory.

    As the cards and tabs do not exist at the moment of loading the JavaScript file we should now remove the part where these were linked together from www/js/index.js. Go to this file, find the assignTabs() function, and remove it completely, along with the call to it just below.

    The application will show nothing without the data. Data needs to be loaded just after the app is ready — find the onDeviceReady method and just inside it at the top enter the following lines of code:

    var request = new XMLHttpRequest();
    request.onload = app.renderData;
    request.open("get", "app_data/plans.json", true);
    request.send();
    

    Note: Previously I planned to use Cordova’s FileSystem plugin, but it only made the code more complicated.

    After the request successfully returns our JSON, it is passed to app.renderData (seen below). It parses the text in JSON format to JavaScript and sends data to app.createUI so the necessary DOM elements will be created to form the UI.

    Add the following code block below the onDeviceReady method:

    renderData: function() {
        var plans = JSON.parse(this.responseText);
        app.createUI(plans);
    },
    

    To create the UI we will need weekday names. The best option is to use Cordova’s Globalization plugin. To add this plugin to the application simply run the following command in your terminal, making sure you are inside your root school-plan directory:

    cordova plugin add org.apache.cordova.globalization
    

    Next, add the createUI() method we referenced earlier underneath the renderData() method. It looks like so:

    createUI: function(plans) {
        var deck = document.getElementById('plan-group');
        var tabbar = document.getElementById('plan-group-menu');
        navigator.globalization.getDateNames(function(dayOfWeek) {
            // render UI in the callback
        }, function() {}, {type: 'narrow', item: 'days'});
    },

    Week days are retrieved using the navigator.globalization.getDateNames method. dayOfWeek will hold an Array of week day names, for example (Polish in my case) — ['Pn', 'Wt', 'Śr', 'Cz', 'Pt', 'So', 'Nd']. If you’d like to use full day names, just change type: 'narrow' to type: 'wide'.

    Now we need to create DOM elements for individual plans. This time brick-tabbar-tab and brick-card elements are created using JavaScript. Tab refers to the corresponding card using its target parameter. It has the same value as the card’s id in each case. Brick will parse this value and create a tab.targetElement, which will link to the card element. Inside the callback of getDateNames, enter the following code (in place of the “// render UI in the callback” comment seen above):

    for (var i = 0; i < plans.length; i++) {
        var plan = plans[i];
    
        // create card
        var card = document.createElement('brick-card');
        card.setAttribute('id', plan.id);
        deck.appendChild(card);
    
        //create tab
        var tab = document.createElement('brick-tabbar-tab');
        tab.setAttribute('target', plan.id);
        tab.appendChild(document.createTextNode(plan.title));
        tabbar.appendChild(tab);
    
        // link card to tab
        card.tabElement = tab;
        card.addEventListener('show', function() {
            this.tabElement.select();
        });
    
        // create plan table
        var table = document.createElement('table');
    }
    

    Unlike when writing plain HTML, we will create the table body now and then the header. This is because table.insertRow() either creates a new tbody and tr inside or adds a row to any existing HTMLTableSectionElement (thead if already created). We could also call table.tBodies(0) instead but it would complicate the code.

    We don’t want to display days which don’t have any lessons. Let’s copy the plan with only non-empty days to new cleanPlan array. Place this code after the line that creates the <table> element (see above listing):

    var numberOfDays = plan.week.length;
    var cleanPlan = [];
    for (j = 0; j < numberOfDays; j++) {
        if (plan.week[j].length > 0) {
            cleanPlan.push(plan.week[j]);
        }
    }
    

    There is a problem that needs to be solved before we can create the other DOM elements (<tr> and <td>) — we’re representing the plan in a JSON file structured as humans understand it — hours inside days. Unfortunately tables in HTML are created row by row (days inside hours), which means that the array representing the plan needs to be flipped over.

    For example our array stored in JSON will look like this: (dXhY represents day X and hour Y):

    d1h1 d1h2 d1h3 ...
    d2h1 d2h2 d2h3 ...
    d3h1 d3h2 d3h3 ...
    ...
    

    But our <table> structure will look like this:

    d1h1 d2h1 d3h1 ...
    d1h2 d2h2 d3h2 ...
    d1h3 d2h3 d3h3 ...
    ...
    

    Add the following code block right after the last one, to start performing this data transformation for us:

    var daysInHours = [];
    for (j = 0; j < cleanPlan.length; j++) {
        for (var k = 0; k < cleanPlan[j].length; k++) {
            if (!daysInHours[k]) {
                daysInHours[k] = [];
            }
            daysInHours[k][j] = cleanPlan[j][k];
        }
    }
    

    The most important line above is the daysInHours[k][j] = cleanPlan[j][k]; where the indexes are reversed — the kj element of one array becomes the jk element of the other. d3h2 takes the place of d2h3 and vice versa.

    The daysInHours array should now hold the plan prepared for the UI. Now we can iterate over it to render the plan into the HTML table. There is an important thing to note here — table.insertRow needs to use the (otherwise optional) index set to -1, as by default Android inserts the row on top of the table.

    Add the following block right below the previous one:

    for (var j = 0; j < daysInHours.length; j++) {
        var tr = table.insertRow(-1);
        var td = tr.insertCell(-1);
        td.appendChild(document.createTextNode(j + 1));
        for (var k = 0; k < cleanPlan.length; k++) {
            var td = tr.insertCell(-1);
            if (daysInHours[j][k]) {
                td.appendChild(document.createTextNode(daysInHours[j][k]));
            }
        }
    }
    

    We iterate over all the hours (index j). The first <tr> is created at the bottom of the array, then the <td> with the textNode containing that row number. After that we iterate over the days inside the hour (index k) and create more cells — if there is a plan for this hour and day a textNode is created.

    You might be surprised to see this code using cleanPlan.length instead of daysInHours[j].length. This is because we need to create a cell on each day even if no lesson is planned, otherwise we will end up with a broken table structure like this:

    short row issue

    Now we’re ready to create a header with days. Add the following code block right below the previous one:

    var thead = table.createTHead();
    var tr = thead.insertRow();
    var th_empty = document.createElement('th');
    tr.appendChild(th_empty);
    var weekDayNumber;
    for (var j = 0; j < numberOfDays; j++) {
        var weekDayNumber = (j + 1) % 7;
        if (plan.week[j].length > 0) {
            var th = document.createElement('th');
            th.appendChild(document.createTextNode(dayOfWeek.value[weekDayNumber]));
            tr.appendChild(th);
        }
    }
    

    First, an empty header cell is created for the column, containing the corresponding hour of the day. Then we iterate over the days and create a new <th> with a day name only if there is a plan for the current day (in the same manner as for cleanPlan before.)

    Next we need a line to place the created <table> inside the brick-card element — add the following line below the previous block:

    card.appendChild(table);

    When all tabs are ready, we’ll want the first tab to be selected when the app is loaded. To do this we’ll use the following block — add this just below the previous line:

    if (plan.active) {
        selectTab(deck, tab);
    }
    

    selectTab is a helper function created outside of the app object. It is using polling on activeTab.targetElement to detect if Brick had already linked tab with cards, as seen below. Add this block at the very bottom of your index.js file:

    function selectTab(deck, activeTab) {
        function selectActiveTab() {
            if (!activeTab.targetElement) {
                return window.setTimeout(selectActiveTab, 100);
            }
            deck.showCard(activeTab.targetElement);
        }
        selectActiveTab();
    }
    

    Testing time

    At this moment the app should work exactly the same as on Stage 4 in Part 1. The only difference may be in the localized names of the days in the plan’s header.

    When you want to test your app, type in the following terminal commands (from the root directory of your school-plan app):

    cordova prepare
    cordova serve
    

    This will make the app and its different platforms available to you at localhost:8000.

    If you also want to test the app in Firefox OS, you’ll need to open WebIDE in Firefox, go to Open App > Open Packaged App, and from the file chooser select to open the school-plan/platforms/firefoxos/www directory as a packaged app. From here you can choose to load the app in a simulator or a real Firefox OS device. See the MDN WebIDE page for more details.

    Note: If you’d like to tailor the application for different people who want to use it, at this stage you can do so by replacing the www/app_data/plans.json file with different information for each individual user.

  2. Drag Elements, Console History, and more – Firefox Developer Edition 39

    Quite a few big new features, improvements, and bug fixes made their way into Firefox Developer Edition 39. Update your Firefox Developer Edition, or Nightly builds to try them out!

    Inspector

    The Inspector now allows you to move elements around via drag and drop. Click and hold on an element and then drag it to where you want it to go. This feature was added by contributor Mahdi Dibaiee.

    Back in Firefox 33, a tooltip was added to the rule view to allow editing curves for cubic bezier CSS animations. In Developer Edition 39, we’ve greatly enhanced the tooltip’s UX by adding various standard curves you can try right away, as well as cleaning up the overall appearance. This enhancement was added by new contributor John Giannakos.

    cubic

    The CSS animations panel we debuted in Developer Edition 37 now includes a time machine. You can rewind, fast forward, and set the current time of your animations.

    Console

    Previously, when the DevTools console closed, your past Console history was lost. Now, Console history is persisted across sessions. The recent commands you’ve entered will remain accessible in the next toolbox you open, whether it’s in another tab or after restarting Firefox. Additionally, we’ve added a clearHistory console command to reset the stored list of commands.

    The shorthand $_ has been added as an alias for the last result evaluated in the Console. If you evaluated an expression without storing the result to a variable (for example), you can use this as a quick way to grab the last result.

    We now format pseudo-array-like objects as if they were arrays in the Console output. This makes a pseudo-array-like object easier to reason about and inspect, just like a real array. This feature was added by contributor Johan K. Jensen.

    pseudo-array

    WebIDE and Mobile

    WiFi debugging for Firefox OS has landed. WiFi debugging allows WebIDE to connect to your Firefox OS device via your local WiFi network instead of a USB cable. We’ll discuss this feature in more detail in a future post.

    WebIDE gained support for Cordova-based projects. If you’re working on a mobile app project using Cordova, WebIDE now knows how to build the project for devices it supports without any extra configuration.

    Other Changes

    • Attribute changes only flash the changed attribute in the Markup View, instead of the whole element.
    • Canvas Debugger now supports setTimeout for animations.
    • Inline box model highlighting.
    • Browser Toolbox can now be opened from a shortcut: Cmd-Opt-Shit-I / Ctrl-Alt-Shift-I.
    • Network Monitor now shows the remote server’s IP address and port.
    • When an element’s highlighted in the Inspector, you can now use the arrow keys to highlight the current element’s parent (left key), or its first child, or its next sibling if it has no children, or the next node in the tree if it has no siblings (right key). This is especially useful when an element and its parent occupy the same space on the screen, making it difficult to select one of them using only the mouse.

    For an even more complete list, check out all 200 bugs resolved during the Firefox 39 development cycle.

    Thanks to all the new developers who made their first DevTools contribution this release:

    • Anush
    • Brandon Max
    • Geoffroy Planquart
    • Johan K. Jensen
    • John Giannakos
    • Mahdi Dibaiee
    • Nounours Heureux
    • Wickie Lee
    • Willian Gustavo Veiga

    Do you have feedback, bug reports, feature requests, or questions? As always, you can comment here, add/vote for ideas on UserVoice, or get in touch with the team at @FirefoxDevTools on Twitter.

  3. Mobile game development with the Device Orientation and Vibration APIs

    The market for casual mobile gaming is keeping pace with the growing market for smartphones. There are Web tools that can help web developers like you build games that compete with native games. You’ll need great execution to stand out from the crowd – using the JavaScript APIs correctly can help. For game development, you’ll want to understand the Device Orientation API and the Vibration API.

    Continued…

  4. Trainspotting: Firefox 37, Developer Edition and More

    Welcome to Trainspotting, a new series on Mozilla Hacks designed to help the busy Web developer keep up with what’s new, what’s changed and what is coming soon in all of the Firefoxes, the Web platform, and the tools for building the Web!

    Mozilla develops Gecko and Firefox on a “train model” – we branch the code and ship a release on a time-based schedule (every six weeks). If a feature is not finished, it’s reverted or disabled and has to, as we say, ride the next train. This means we ship new features, performance improvements, and bug fixes to users every six weeks, instead of having to wait up to 18 months.

    Trainspotting will publish every six weeks when we branch the code – and we’ll point out any changes that might require action or cause compatibility issues, as well as new features that Web developers would want to take advantage of.

    Firefox 37

    For a detailed list of all noted changes in the release, check out the official release notes for Firefox 37. Below is a list of notable features, links to documentation, and anything that requires action by developers or site operators.

    Desktop Features

    • A subset of the Media Source Extensions (MSE) API has been implemented and is enabled to allow native playback of HTML5 video on websites such as YouTube. You can read about the various MSE APIs and their usage on MDN.
    • Bing search now uses HTTPS. Hurray for private and secure searching!
    • Heartbeat is a new feedback feature in Firefox desktop: Each day a random subset of Firefox users will be shown a notification bar with an opportunity to provide feedback on their experience. You can see screenshots and read more about how it works on the Heartbeat project page.

    Firefox for Android

    This time around, Firefox for Android is getting a security and stability release. The biggest user-facing changes—improved performance of file downloads, and the addition of some new locales: Albanian, Burmese, Lower Sorbian, Songhai, Upper Sorbian, Uzbek. Read the full list of security fixes for Firefox Android 37 and the full release notes.

    HTML5 & Web Platform

    There are a bunch of new Web platform features that you can now use in production content in Firefox 37, and here are a few examples:

    Keep reading the Firefox 37 for Developers article on MDN for a detailed look at all the rest.

    Developer Tools

    If you’re using Firefox Developer Edition these additions won’t be news, but if you’ve been doing your development in the release build, there are a few new features to note:

    Security

    Firefox 37 has a bunch of security changes. Most of these do not require any action, however if you are a site operator you should definitely look to see if any of these changes impact you. Big thanks to David Keeler, security engineer for Firefox, for his help deciphering this section of the release notes.

    • We removed support for DSA in certificates and TLS, because we found that almost nobody was using these. If you’re a site operator and your certificate was signed with a DSA algorithm, contact your CA and get a new certificate. You can check with `openssl x509 -in {certificate file} -text -noout` and search for “Signature Algorithm.” If you do have one of these certificates and do not change it, users will see an override-able error.
    • HTTP/2 AltSvc is temporarily disabled due to a bug. We implemented HTTP/2 AltSvc support for opportunistic encryption. This feature allows encryption over TLS for unauthenticated connections that would otherwise be clear text. Configuration is very simple, and Patrick McManus has written instructions for how to set it up on your server.
    • We have disabled insecure TLS version fallback. If a secure site isn’t working, you can try setting the “security.tls.version.fallback-limit” preference in about:config to 1 and see if it works then. If you see this anywhere, please file a Tech Evangelism bug, noting the URL of the site, so we can work with the operators to update it. Site operators should make sure their servers aren’t TLS-intolerant, which you can do with the SSL Labs tool.
    • Users can now report SSL connection problems for a variety of non-certificate-related errors. For example, if a user encounters a non-override-able TLS error, they can now send a report to Mozilla directly from the error page. The information in the report consists of the domain you were trying to reach, the certificates the server sent, the time, which error was encountered, and some user agent information. We use this information to work with site operators to fix their configurations, and to improve our software that detects these issues, so please do send reports. Check out a screenshot of what this looks like.
    • TLS False Start optimization now requires a cipher suite using AEAD construction. If you’re running a server and false start isn’t working as expected, try using an AEAD cipher suite. Learn more about AEAD at Wikipedia and in RFC 5288. The only AEAD cipher suite that Firefox supports at this time is AES-GCM.
    • We now log usage of weak ciphers to the web console. For example, if you visit a site with a SHA-1 certificate with the web console open you’ll now see a message like, “This site makes use of a SHA-1 Certificate; it’s recommended you use certificates with signature algorithms that use hash functions stronger than SHA-1. Learn more…” If you run a site and your certificate was signed with SHA-1, get a new certificate from your CA.

    Firefox Beta

    In six short weeks Beta will be available for general release, becoming Firefox 38. Here’s some of what’s coming your way:

    • All Firefox preferences are now in a new tab-based UI.
    • Improved page-load times with speculative connection warmup.
    • MSE support on Mac OS X.
    • EME support for encrypted media playback.
    • Web platform features: WebSockets in Web Workers, KeyboardEvent.code, and the BroadcastChannel API.

    Read the Firefox 38 Beta release notes for the full list.

    Firefox Developer Edition

    This release of Firefox Developer edition (which will be Firefox 39) is frankly kind of ridiculous. The developer tools are getting precariously close to something indistinguishable from magic. Huge props to the developer tools team for what you’re about to see.

    • Developer Tools: Wi-Fi debugging of Firefox OS devices from WebIDE, drag and drop of nodes in the Inspector’s markup view, Web Console input history persistence, localhost works with WebSocket connections when you’re offline, and the cubic bezier tooltip now shows a gallery of pre-sets you can choose from to make your CSS animations super slick.
    • Web platform features include the “switch” role in Aria 1.1, CSS scroll snap points, Cache API, Fetch API, <link rel=”preconnect”> and more.

    Read the full release notes for this release of Firefox Developer Edition.

    Nightly

    The nightly branch of Firefox is where a lot of features are in active development. It’s a place where you can test experimental Web APIs and see user-facing browser features that are not yet ready for hundreds of millions of users. You might see a crash or two, you might lose you session data, but you also might experience the vision and wonder of what the future holds. It’s the Mos Eisley of browsers – a dangerous place, but we stick around for the action.

    There are a number of features that have landed and are shipping nightly builds either enabled by default or by pref:

    • E10s – Web content runs in a separate process from the browser UI.
    • Partial implementation of Service Workers.
    • Shumway – Flash implemented in JavaScript is enabled for some sites.

    Thus concludes the first edition of Trainspotting. Let us know what you think in the comments, and what you’d like to see more of!

  5. Peering Through the WebRTC Fog with SocketPeer

    WebRTC allows browsers to do things they never could before, but a soup of unfamiliar terminology and the complexity of the API makes for a steep learning curve. After spending several weeks neck-deep in example code and cargo-culting several libraries, I have emerged with a workable understanding and a nifty library that helps hide some of the complexity of WebRTC for its simplest use case of two way peer-to-peer communication.

    But before I start talking about the library, it helps to understand a bit of what it abstracts away.

    Alice and Bob (and ICE and NAT and STUN and SDP)

    When two devices want to speak to each other, we need a way for them to exchange contact information such as their IP addresses. In the same way DNS servers help browsers locate remote machines, we can set up a mutually known server to help potential peers locate each other. In WebRTC this is known as a signalling server.

    In our simple case, each peer contacts the signalling server. The server provides the peers with each other’s addresses, and then the peers can begin exchanging data.

    The plot thickens

    Today’s Internet doesn’t quite work that way. Most computers online don’t have unique IP addresses; they share a public address with all the other devices on their local network. Most local networks also have firewalls that protect machines from unwanted inbound traffic. Swapping IP addresses isn’t enough.

    ICE the wound

    The telecommunications industry has been dealing with this problem for a while now*. There are a number of different ways a connection can pass back through a firewall and router to an internal network. ICE is a kitchen-sink protocol that uses the tried-and-true technique “Try Everything and See What Works™.” A server, called a STUN server, uses the ICE protocol to find out and inform a peer of all the possible ways it can be reached from the public Internet. These potential connection methods are called ICE candidates.

    When a machine receives a candidate, it needs to communicate that to its peer. Here the signalling server comes to the rescue again — relaying each peer’s candidates to the other.

    Call and response

    When a device wants to establish a connection, it generates a message called an offer and sends it to a peer using the signalling server. The offer, encoded in the SDP format, consists of information about the device — such as which protocols it knows how to speak and which communication methods it has. When the remote peer receives the offer, it responds with its own SDP message called an answer. Then, the two potential peers send each other possible ICE candidates. Once the peers find a connection method that works for both of them, the connection is established!

    Let’s take it from the top

    Let’s review the connection process, incorporating all the techniques from above:

    1. A device uses a signalling server to relay an SDP offer to a potential peer. In the meantime, it asks an ICE server to find out how it can be reached from the public Internet.
    2. The potential peer replies with its own connection offer, and also gathers ICE candidates.
    3. The peers use the signalling server to exchange ICE candidates, attempting to use them to open a connection.
    4. Once each peer finds a working candidate, the connection is established.

    Let’s give them something to talk about with

    Once a PeerConnection is established, it can be used to carry audio and video MediaStreams as well as a more general DataChannel. People are excited about browser-to-browser video conferencing (with good reason!), but WebRTC isn’t just about voice and video. RTCDataChannel provides a general data pipeline that works similarly to a WebSocket. It’s immensely powerful and useful, but under-promoted.

    Boilerplate the ocean

    WebRTC is a low level, nuts-and-bolts API. It’s made of multiple small, composable APIs that offer a large amount of flexibility and extensibility. The downside of this is that accomplishing even a basic task can require significant boilerplate code. The notion is that JS libraries and frameworks will step in to abstract away this complexity, and indeed, there are a large number of WebRTC libraries out there already.

    Many of these libraries fall short due to a lack of signalling server and fallback support for browsers with no WebRTC support. To address this, Chris Van and I built SocketPeer.

    Using SocketPeer

    SocketPeer is, as its name suggests, a combination of WebSockets and RTCPeerConnection. This node.js library abstracts away the common pattern of using WebSockets as a signalling server to instantiate a DataChannel over WebRTC. The WebSocket server will also serve as a fallback message relay if the peers cannot establish a peer-to-peer connection.

    Server code

    // In your server module alongside existing application code.
    var SocketPeer = require('socketpeer');
    new SocketPeer();

    I’ll admit that’s a bit facile, but SocketPeer is designed to simplify the process of running a signalling server — at its simplest, it does everything for you. The server is fairly configurable, but by default it takes care of itself, by:

    • Creating an http.Server if one is not supplied.
    • Attaching logic to serve the client-side socketpeer.js library.
    • Starting a WebSocket server if one is not supplied.
    • Starting to listen on a default port.

    If all you need is a basic signalling server, then this is all the code you need. But let’s say you want to integrate SocketPeer with an existing Express app:

    // `app` is an existing Express Application
    var server = http.createServer(app);
    new SocketPeer({
      httpServer: server
    });
    server.listen(/* port */);

    Client code

    As described above, when SocketPeer is running on a server it will serve the client library at a known URL. By default, this can be found at /socketpeer/socketpeer.js. The client library exposes an event interface and a series of events to keep you informed of the state of the connection. The three most important events are:

    • connect – Fires when peers are paired. Data can be exchanged using WebSockets at this point.
    • upgrade – Fires when an RTCPeerConnection is established. Data can now be exchanged peer-to-peer.
    • data – Fires when data arrives from the remote peer.

    The last important piece of information is called pairCode. This is an arbitrary string used by the two ends of a peer-to-peer connection to identify each other. It can be any mutually agreed-upon value.

    var peer = new SocketPeer({
      pairCode: 'foobar'
    });
     
    peer.on('connect', function () {
      // connected via WebSockets
      peer.send('Hello via WebSockets.');
    });
     
    peer.on('upgrade', function () {
      // now using RTCPeerConnection
      peer.send('Hello via WebRTC!');
    });
     
    peer.on('data', function (data) {
      // do something awesome
    });

    Chat is the new todo

    What’s sample code without a working demo? I built a super simple example of peer-to-peer chat using SocketPeer. Try it out, and check out the code on GitHub to see how it works.

    Depiction of the SocketPeer demo chat app.

    The library is still a work in progress, but all the core functionality is in place and tested. Please try it out and let us know of any issues or feedback on the API.

    * The author of this post is not a telecommunications engineer. He read a lot of Wikipedia and skimmed a few RFCs so you don’t have to.

  6. What do you want from your DevTools?

    Every tool starts with an idea: “if I had this, then I could do that faster, better, or more easily.” The Firefox Developer Tools are built atop hundreds of those ideas, and many of them come from people in the web development community just like you.

    We collect those ideas in our UserVoice forum, and use your votes to help prioritize our work.

    Last April, @hopefulcyborg requested the ability to “Connect Firefox Developer Tools to [Insert Browser Here].” The following November, we launched Firefox Developer Edition which includes a preliminary version of just that: the ability to debug Chrome and Safari, even on mobile, from Firefox on your desktop. We called it Valence.

    Good ideas don’t have to be big or even particularly innovative as long as they help you accomplish your goals more effectively. For instance, all of the following features also came directly from feedback on our UserVoice forum:

    The color picker introduced in Firefox 31

    The color picker introduced in Firefox 31

    Though somewhat small in scope, each of those ideas made the DevTools more efficient, more helpful, and more usable. And that’s our mission: we make Firefox for you, and we want you to know that we’re listening. If you have ideas for the Firefox Developer Tools, submit them on UserVoice. If you don’t have ideas, vote on the ones that are already there. The right people will see it, and it will help us build the best tools for you.

    If you want to go a step further and contribute to the tools yourself, we’re always happy to help people get involved. After all, Mozilla was founded on the open source ideal of empowering communities to collaborate and improve the Internet for all of us.

    These are your tools. How could they be better?

  7. WebRTC in Firefox 38: Multistream and renegotiation

    Building on the JSEP (Javascript Session Establishment Protocol) engine rewrite introduced in 37, Firefox 38 now has support for multistream (multiple tracks of the same type in a single PeerConnection), and renegotiation (multiple offer/answer exchanges in a single PeerConnection). As usual with such things, there are caveats and limitations, but the functionality seems to be pretty solid.

    Multistream and renegotiation features

    Why are these things useful, you ask? For example, now you can handle a group video call with a single PeerConnection (multistream), and do things like add/remove these streams on the fly (renegotiation). You can also add screensharing to an existing video call without needing a separate PeerConnection. Here are some advantages of this new functionality:

    • Simplifies your job as an app-writer
    • Requires fewer rounds of ICE (Interactive Connectivity Establishment – the protocol for establishing connection between the browsers), and reduces call establishment time
    • Requires fewer ports, both on the browser and on TURN relays (if using bundle, which is enabled by default)

    Now, there are very few WebRTC services that use multistream (the way it is currently specified, see below) or renegotiation. This means that real-world testing of these features is extremely limited, and there will probably be bugs. If you are working with these features, and are having difficulty, do not hesitate to ask questions in IRC at irc.mozilla.org on #media, since this helps us find these bugs.

    Also, it is important to note that Google Chrome’s current implementation of multistream is not going to be interoperable; this is because Chrome has not yet implemented the specification for multistream (called “unified plan” – check on their progress in the Google Chromium Bug tracker). Instead they are still using an older Google proposal (called “plan B”). These two approaches are mutually incompatible.

    On a related note, if you maintain or use a WebRTC gateway that supports multistream, odds are good that it uses “plan B” as well, and will need to be updated. This is a good time to start implementing unified plan support. (Check the Appendix below for examples.)

    Building a simple WebRTC video call page

    So let’s start with a concrete example. We are going to build a simple WebRTC video call page that allows the user to add screen sharing during the call. As we are going to dive deep quickly you might want to check out our earlier Hacks article, WebRTC and the Early API, to learn the basics.

    First we need two PeerConnections:

    pc1 = new mozRTCPeerConnection();
    pc2 = new mozRTCPeerConnection();

    Then we request access to camera and microphone and attach the resulting stream to the first PeerConnection:

    let videoConstraints = {audio: true, video: true};
    navigator.mediaDevices.getUserMedia(videoConstraints)
      .then(stream1) {
        pc1.addStream(stream1);
      });

    To keep things simple we want to be able to run the call just on one machine. But most computers today don’t have two cameras and/or microphones available. And just having a one-way call is not very exciting. So let’s use a built-in testing feature of Firefox for the other direction:

    let fakeVideoConstraints = {video: true, fake: true };
    navigator.mediaDevices.getUserMedia(fakeVideoConstraints)
      .then(stream2) {
        pc2.addStream(stream2);
      });

    Note: You’ll want to call this part from within the success callback of the first getUserMedia() call so that you don’t have to track with boolean flags if both getUserMedia() calls succeeded before you proceed to the next step.
    Firefox also has a built-in fake audio source (which you can turn on like this {audio: true, fake: true}). But listening to an 8kHz tone is not as pleasant as looking at the changing color of the fake video source.

    Now we have all the pieces ready to create the initial offer:

    pc1.createOffer().then(step1, failed);

    Now the WebRTC typical offer – answer flow follows:

    function step1(offer) {
      pc1_offer = offer;
      pc1.setLocalDescription(offer).then(step2, failed);
    }
     
    function step2() {
      pc2.setRemoteDescription(pc1_offer).then(step3, failed);
    }

    For this example we take a shortcut: Instead of passing the signaling message through an actual signaling relay, we simply pass the information into both PeerConnections as they are both locally available on the same page. Refer to our previous hacks article WebRTC and the Early API for a solution which actually uses FireBase as relay instead to connect two browsers.

    function step3() {
      pc2.createAnswer().then(step4, failed);
    }
     
    function step4(answer) {
      pc2_answer = answer;
      pc2.setLocalDescription(answer).then(step5, failed);
    }
     
    function step5() {
      pc1.setRemoteDescription(pc2_answer).then(step6, failed);
    }
     
    function step6() {
      log("Signaling is done");
    }

    The one remaining piece is to connect the remote videos once we receive them.

    pc1.onaddstream = function(obj) {
      pc1video.mozSrcObject = obj.stream;
    }

    Add a similar clone of this for our PeerConnection 2. Keep in mind that these callback functions are super trivial — they assume we only ever receive a single stream and only have a single video player to connect it. The example will get a little more complicated once we add the screen sharing.

    With this we should be able to establish a simple call with audio and video from the real devices getting sent from PeerConnection 1 to PeerConnection 2 and in the opposite direction a fake video stream that shows slowly changing colors.

    Implementing screen sharing

    Now let’s get to the real meat and add screen sharing to the already established call.

    function screenShare() {
      let screenConstraints = {video: {mediaSource: "screen"}};
     
      navigator.mediaDevices.getUserMedia(screenConstraints)
        .then(stream) {
          stream.getTracks().forEach(track) {
            screenStream = stream;
            screenSenders.push(pc1.addTrack(track, stream));
          });
        });
    }

    Two things are required to get screen sharing working:

    1. Only pages loaded over HTTPS are allowed to request screen sharing.
    2. You need to append your domain to the user preference  media.getusermedia.screensharing.allowed_domains in about:config to whitelist it for screen sharing.

    For the screenConstraints you can also use ‘window‘ or ‘application‘ instead of ‘screen‘ if you want to share less than the whole screen.
    We are using getTracks() here to fetch and store the video track out of the stream we get from the getUserMedia call, because we need to remember the track later when we want to be able to remove screen sharing from the call. Alternatively, in this case you could use the addStream() function used before to add new streams to a PeerConnection. But the addTrack() function gives you more flexibility if you want to handle video and audio tracks differently, for instance. In that case, you can fetch these tracks separately via the getAudioTracks() and getVideoTracks() functions instead of using the getTracks() function.

    Once you add a stream or track to an established PeerConnection this needs to be signaled to the other side of the connection. To kick that off, the onnegotiationneeded callback will be invoked. So your callback should be setup before adding a track or stream. The beauty here — from this point on we can simply re-use our signaling call chain. So the resulting screen share function looks like this:

    function screenShare() {
      let screenConstraints = {video: {mediaSource: "screen"}};
     
      pc1.onnegotiationneeded = function (event) {
        pc1.createOffer(step1, failed);
      };
     
      navigator.mediaDevices.getUserMedia(screenConstraints)
        .then(stream) {
          stream.getTracks().forEach(track) {
            screenStream = stream;
            screenSenders.push(pc1.addTrack(track, stream));
          });
        });
    }

    Now the receiving side also needs to learn that the stream from the screen sharing was successfully established. We need to slightly modify our initial onaddstream function for that:

    pc2.onaddstream = function(obj) {
      var stream = obj.stream;
      if (stream.getAudioTracks().length == 0) {
        pc3video.mozSrcObject = obj.stream;
      } else {
        pc2video.mozSrcObject = obj.stream;
      }
    }

    The important thing to note here: With multistream and renegotiation onaddstream can and will be called multiple times. In our little example onaddstream is called the first time we establish the connection and PeerConnection 2 starts receiving the audio and video from the real devices. And then it is called a second time when the video stream from the screen sharing is added.
    We are simply assuming here that the screen share will have no audio track in it to distinguish the two cases. There are probably cleaner ways to do this.

    Please refer to the Appendix for a bit more detail on what happens here under the hood.

    As the user probably does not want to share his/her screen until the end of the call let’s add a function to remove it as well.

    function stopScreenShare() {
      screenStream.stop();
      screenSenders.forEach(sender) {
        pc1.removeTrack(sender);
      });
    }

    We are holding on to a reference to the original stream to be able to call stop() on it to release the getUserMedia permission we got from the user. The addTrack() call in our screenShare() function returned us an RTCRtpSender object, which we are storing so we can hand it to the removeTrack() function.

    All of the code combined with some extra syntactic sugar can be found on our MultiStream test page.

    If you are going to build something which allows both ends of the call to add screen share, a more realistic scenario than our demo, you will need to handle special cases. For example, multiple users might accidentally try to add another stream (e.g. the screen share) exactly at the same time and you may end up with a new corner-case for renegotiation called “glare.” This is what happens when both ends of the WebRTC session decide to send new offers at the same time. We do not yet support the “rollback” session description type that can be used to recover from glare (see Jsep draft and the Firefox bug). Probably the best interim solution to prevent glare is to announce via your signaling channel that the user did something which is going to kick off another round of renegotiation. Then, wait for the okay from the far end before you call createOffer() locally.

    Appendix

    This is an example renegotiation offer SDP from Firefox 39 when adding the screen share:

    v=0
    o=mozilla...THIS_IS_SDPARTA-39.0a1 7832380118043521940 1 IN IP4 0.0.0.0
    s=-
    t=0 0
    a=fingerprint:sha-256 4B:31:DA:18:68:AA:76:A9:C9:A7:45:4D:3A:B3:61:E9:A9:5F:DE:63:3A:98:7C:E5:34:E4:A5:B6:95:C6:F2:E1
    a=group:BUNDLE sdparta_0 sdparta_1 sdparta_2
    a=ice-options:trickle
    a=msid-semantic:WMS *
    m=audio 9 RTP/SAVPF 109 9 0 8
    c=IN IP4 0.0.0.0
    a=candidate:0 1 UDP 2130379007 10.252.26.177 62583 typ host
    a=candidate:1 1 UDP 1694236671 63.245.221.32 54687 typ srflx raddr 10.252.26.177 rport 62583
    a=sendrecv
    a=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level
    a=ice-pwd:3aefa1a552633717497bdff7158dd4a1
    a=ice-ufrag:730b2351
    a=mid:sdparta_0
    a=msid:{d57d3917-64e9-4f49-adfb-b049d165c312} {920e9ffc-728e-0d40-a1b9-ebd0025c860a}
    a=rtcp-mux
    a=rtpmap:109 opus/48000/2
    a=rtpmap:9 G722/8000/1
    a=rtpmap:0 PCMU/8000
    a=rtpmap:8 PCMA/8000
    a=setup:actpass
    a=ssrc:323910839 cname:{72b9ff9f-4d8a-5244-b19a-bd9b47251770}
    m=video 9 RTP/SAVPF 120
    c=IN IP4 0.0.0.0
    a=candidate:0 1 UDP 2130379007 10.252.26.177 62583 typ host
    a=candidate:1 1 UDP 1694236671 63.245.221.32 54687 typ srflx raddr 10.252.26.177 rport 62583
    a=sendrecv
    a=fmtp:120 max-fs=12288;max-fr=60
    a=ice-pwd:3aefa1a552633717497bdff7158dd4a1
    a=ice-ufrag:730b2351
    a=mid:sdparta_1
    a=msid:{d57d3917-64e9-4f49-adfb-b049d165c312} {35eeb34f-f89c-3946-8e5e-2d5abd38c5a5}
    a=rtcp-fb:120 nack
    a=rtcp-fb:120 nack pli
    a=rtcp-fb:120 ccm fir
    a=rtcp-mux
    a=rtpmap:120 VP8/90000
    a=setup:actpass
    a=ssrc:2917595157 cname:{72b9ff9f-4d8a-5244-b19a-bd9b47251770}
    m=video 9 RTP/SAVPF 120
    c=IN IP4 0.0.0.0
    a=sendrecv
    a=fmtp:120 max-fs=12288;max-fr=60
    a=ice-pwd:3aefa1a552633717497bdff7158dd4a1
    a=ice-ufrag:730b2351
    a=mid:sdparta_2
    a=msid:{3a2bfe17-c65d-364a-af14-415d90bb9f52} {aa7a4ca4-189b-504a-9748-5c22bc7a6c4f}
    a=rtcp-fb:120 nack
    a=rtcp-fb:120 nack pli
    a=rtcp-fb:120 ccm fir
    a=rtcp-mux
    a=rtpmap:120 VP8/90000
    a=setup:actpass
    a=ssrc:2325911938 cname:{72b9ff9f-4d8a-5244-b19a-bd9b47251770}

    Note that each track gets its own m-section, denoted by the msid attribute.

    As you can see from the BUNDLE attribute, Firefox offers to put the new video stream, with its different msid value, into the same bundled transport. That means if the answerer agrees we can start sending the video stream over the already established transport. We don’t have to go through another ICE and DTLS round. And in case of TURN servers we save another relay resource.

    Hypothetically, this is what the previous offer would look like if it used plan B (as Chrome does):

    v=0
    o=mozilla...THIS_IS_SDPARTA-39.0a1 7832380118043521940 1 IN IP4 0.0.0.0
    s=-
    t=0 0
    a=fingerprint:sha-256 4B:31:DA:18:68:AA:76:A9:C9:A7:45:4D:3A:B3:61:E9:A9:5F:DE:63:3A:98:7C:E5:34:E4:A5:B6:95:C6:F2:E1
    a=group:BUNDLE sdparta_0 sdparta_1
    a=ice-options:trickle
    a=msid-semantic:WMS *
    m=audio 9 RTP/SAVPF 109 9 0 8
    c=IN IP4 0.0.0.0
    a=candidate:0 1 UDP 2130379007 10.252.26.177 62583 typ host
    a=candidate:1 1 UDP 1694236671 63.245.221.32 54687 typ srflx raddr 10.252.26.177 rport 62583
    a=sendrecv
    a=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level
    a=ice-pwd:3aefa1a552633717497bdff7158dd4a1
    a=ice-ufrag:730b2351
    a=mid:sdparta_0
    a=rtcp-mux
    a=rtpmap:109 opus/48000/2
    a=rtpmap:9 G722/8000/1
    a=rtpmap:0 PCMU/8000
    a=rtpmap:8 PCMA/8000
    a=setup:actpass
    a=ssrc:323910839 msid:{d57d3917-64e9-4f49-adfb-b049d165c312} {920e9ffc-728e-0d40-a1b9-ebd0025c860a}
    a=ssrc:323910839 cname:{72b9ff9f-4d8a-5244-b19a-bd9b47251770}
    m=video 9 RTP/SAVPF 120
    c=IN IP4 0.0.0.0
    a=candidate:0 1 UDP 2130379007 10.252.26.177 62583 typ host
    a=candidate:1 1 UDP 1694236671 63.245.221.32 54687 typ srflx raddr 10.252.26.177 rport 62583
    a=sendrecv
    a=fmtp:120 max-fs=12288;max-fr=60
    a=ice-pwd:3aefa1a552633717497bdff7158dd4a1
    a=ice-ufrag:730b2351
    a=mid:sdparta_1
    a=rtcp-fb:120 nack
    a=rtcp-fb:120 nack pli
    a=rtcp-fb:120 ccm fir
    a=rtcp-mux
    a=rtpmap:120 VP8/90000
    a=setup:actpass
    a=ssrc:2917595157 msid:{d57d3917-64e9-4f49-adfb-b049d165c312} {35eeb34f-f89c-3946-8e5e-2d5abd38c5a5}
    a=ssrc:2917595157 cname:{72b9ff9f-4d8a-5244-b19a-bd9b47251770}
    a=ssrc:2325911938 msid:{3a2bfe17-c65d-364a-af14-415d90bb9f52} {aa7a4ca4-189b-504a-9748-5c22bc7a6c4f}
    a=ssrc:2325911938 cname:{72b9ff9f-4d8a-5244-b19a-bd9b47251770}

    Note that there is only one video m-section, with two different msids, which are part of the ssrc attributes rather than in their own a lines (these are called “source-level” attributes). Continued…

  8. Understanding the CSS box model for inline elements

    In a web page, every element is rendered as a rectangular box. The box model describes how the element’s content, padding, border, and margin determine the space occupied by the element and its relation to other elements in the page.

    Depending on the element’s display property, its box may be one of two types: a block box or an inline box. The box model is applied differently to these two types. In this article we’ll see how the box model is applied to inline boxes, and how a new feature in the Firefox Developer Tools can help you visualize the box model for inline elements.

    Inline boxes and line boxes

    Inline boxes are laid out horizontally in a box called a line box:

    2-inline-boxes

    If there isn’t enough horizontal space to fit all elements into a single line, another line box is created under the first one. A single inline element may then be split across lines:

    line-boxes

    The box model for inline boxes

    When an inline box is split across more than one line, it’s still logically a single box. This means that any horizontal padding, border, or margin is only applied to the start of the first line occupied by the box, and the end of the last line.

    For example, in the screenshot below, the highlighted span is split across 2 lines. Its horizontal padding doesn’t apply to the end of the first line or the beginning of the second line:

    horizontal-padding

    Also, any vertical padding, border, or margin applied to an element will not push away elements above or below it:

    vertical-padding

    However, note that vertical padding and border are still applied, and the padding still pushes out the border:

    vertical-border-padding

    If you need to adjust the height between lines, use line-height instead.

    Inspecting inline elements with devtools

    When debugging layout issues, it’s important to have tools that give you the complete picture. One of these tools is the highlighter, which all browsers include in their devtools. You can use it to select elements for closer inspection, but it also gives you information about layout.

    layout-info

    In the example above, the highlighter in Firefox 39 is used to highlight an inline element split across several lines.

    The highlighter displays guides to help you align elements, gives the complete dimensions of the node, and displays an overlay showing the box model.

    From Firefox 39 onwards, the box model overlay for split inline elements shows each individual line occupied by the element. In this example the content region is displayed in light blue and is split over four lines. A right padding is also defined for the node, and the highlighter shows the padding region in purple.

    Highlighting each individual line box is important for understanding how the box model is applied to inline elements, and also helps you check the space between lines and the vertical alignment of inline boxes.

     

  9. How to make a browser app for Firefox OS

    Firefox OS is an operating system built on top of the Firefox web browser engine, which is called Gecko. A browser app on Firefox OS provides a user interface written with HTML5 technology and manages web page browsing using the Browser API. It also manages tabbing, browsing history, bookmarks, and so on depending on the implementation.

    While Firefox OS already includes a browser, you can use the browser API to create your own browser or add browser functionality to your app. This article shows you how to build a browser app for Firefox OS devices. Following the steps, you’ll get a basic Firefox OS browser app with an address bar and back/forward buttons to browse web pages.

    The source code can be downloaded at https://github.com/begeeben/firefox-os-browser-sample.

    Prerequisites: WebIDE

    The WebIDE is available with Firefox 34 or later. A Firefox OS device is not required to develop, build or test a browser app. By using the WebIDE, we can easily bootstrap a web app, make HTML/CSS/JS modifications and run the app on one of the Firefox OS simulators. To open the WebIDE within Firefox, select Tools > Web Developer > WebIDE from the top menu:

    open webide

    Create an app from the template

    First, bootstrap a web app using the WebIDE empty template. The app type needs to be privileged so the browser permission can be set. This permission is required in order to use the Browser API. The Browser API provides additional methods and events to manage iframes.

    new priviledged app

    Below is a screenshot of what the app should look like. Next we’ll start to add code to make a simple browser app.

    mybrowser empty

    Edit the manifest.webapp

    Let’s start to make some code changes. The template has already set the app type to privileged in the manifest.webapp.

      "type": "privileged",

    To allow our app to use the Browser API, we need to specify the ‘browser‘ permission.

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

    HTML structure

    The browser app we’re building will have a toolbar on top and a div as the iframe container which displays ‘Hello myBrowser!’ by default. The browser iframe will be created later using JavaScript. We could have added the iframe to the HTML, but in this example a new browser iframe will be created dynamically.

    <!-- browser toolbar -->
    <div id="toolbar">
        <button id="goback-button" type="button" disabled><</button>
        <button id="goforward-button" type="button" disabled>></button>
        <!-- url bar displays page url or page title -->
        <form id="url-bar">
            <input id="url-input" type="text"></input>
        </form>
        <button id="url-button" form="url-bar">GO</button>
    </div>
    <!-- the container for the browser frame -->
    <div id="frame-container" class="empty">
        <div id="empty-frame">
            <h1>Hello myBrowser!</h1>
        </div>
    </div>

    Below is a screenshot of the simple browser app:

    browser startup

    Manage the browser iframe

    The browser app has to handle mozbrowser events which are accessible when the app has the browser permission. In order to separate the UI and mozbrowser event handling and preserve the flexibility of supporting multiple tabs in the future, we have added a tab.js file. Following is the code in tab.js which creates an iframe with the ‘mozbrowser‘ attribute to enable the use of the Browser 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;
    }

    The mozallowfullscreen attribute enables the embedded web page of the iframe to use fullscreen mode. A web page can request fullscreen mode by calling Element.mozRequestFullscreen().

    The ‘remote‘ attribute separates the embedded iframe into another child process. This is needed for security reasons to prevent malicious web sites from compromising the browser app. Currently, the attribute does nothing in this sample browser app because Nested OOP has not been implemented. See Bug 1020135 - (nested-oop) [meta] Allow nested oop <iframe mozbrowser>.

    Next the ‘Tab‘ object constructor is added to tab.js. This constructor handles browser iframe creation and browser event handling. When a Tab is constructed, it creates a browser iframe and attaches the mozbrowser event listeners to it:

    /**
     * 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);
    };

    There are additional mozbrowser events that are not used in this simple browser app. The events used in the sample provide access to useful information about the iframe, and our browser app uses them to provide web page details, like the title, URL, loading progress, contextmenu, etc.

    For example, to display the page title on the toolbar, we use the mozbrowsertitlechange event to retrieve the title of the web page. Once the title is updated, a CustomEvent 'tab:titlechange' with the Tab itself as the detail is dispatched to notify other components to update the title.

    Tab.prototype.mozbrowsertitlechange = function _mozbrowsertitlechange (e) {
      if (e.detail) {
        this.title = e.detail;
      }
     
      var event = new CustomEvent('tab:titlechange', { detail: this });
      window.dispatchEvent(event);
    };

    Code can now be added to the main app.js to handle this custom event, which updates the title.

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

    Browse a web page on submit event

    Using the address bar as a search bar is a common and useful practice among browsers. When a user submits the input, first we check if it is a valid URL. If not, the input is regarded as a search term and it will be appended to a search engine URI. Note that the validation of the URL can be handled in multiple ways. The sample app uses an input element with its type attribute set to URL. A URL object could also have been used with the URL string passed as the first parameter to the constructor. A DOM exception will be thrown if the URL is not valid. This exception can then be caught and the URL string assumed to be a search term.

    /**
     * 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;
    }

    Code can then be added to the main app.js to handle the submit button. This code checks to see if a currently active Tab object exists, and either loads the URL or reloads the URL if it has not changed. If one does not exist, it is created and the URL is loaded.

    /**
     * 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);
      }
    });

    The currentTab.reload function uses the iframe.reload function provided by the Browser API to reload the page. The code below is added to the tab.js file to handle the reload.

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

    Enable/disable back and forward buttons

    Finally the Browser API can be used to check if the page can go backward or forward in the navigation history. The iframe.getCanGoBack method returns a DOMRequest object which indicates if the page can go backward in its onsuccess callback. We use a Promise to wrap this function for easier access. This code is added to the tab.js file.

    /**
     * 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);
          }
        };
      });
    };

    The check is performed in the app.js file when a page is loaded. This occurs when the iframe receives a mozbrowserloadend event.

    /**
     * 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;
        });
      }
    });

    Run the app

    One of the Firefox OS simulators can now be used to test the sample browser app.

    Firefox_WebIDE_installing_simulator

    On the top right of the WebIDE, click Select Runtime to choose a Firefox OS phone or simulator:

    Firefox_WebIDE_choose_simulator

    Once a Firefox OS runtime is selected and launched, you can install and run the app by clicking the play button within the WebIDE. See the WebIDE documentation for more information on building, debugging, and deploying Firefox OS apps.

    mybrowser finished

    Final thoughts

    There’s more functionality that can be added to this simple little browser to make it really useful including bookmarks, settings, search suggestions, and sharing. Feel free to expand on this code and have fun with it. Let us know what you think and be sure to spread the word about Firefox OS!

    Additional information

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