• This repository has been archived on 07/Apr/2021
  • Stars
    star
    796
  • Rank 56,896 (Top 2 %)
  • Language
    JavaScript
  • License
    MIT License
  • Created over 9 years ago
  • Updated over 5 years ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

🌲 A web browser built with JavaScript as a Windows app

Logo JavaScript Browser

A web browser built with JavaScript as a Windows app.
http://microsoftedge.github.io/JSBrowser/

badge_windowsstore

JavaScript Browser

This project is a tutorial demonstrating the capabilities of the web platform on Windows 10. The browser is a sample app built around the HTML WebView control, using primarily JavaScript to light up the user interface. Built using Visual Studio 2015, this is a JavaScript Universal Windows Platform (UWP) app.

In addition to JavaScript, HTML and CSS are the other core programming languages used. Some C++ code is also included to enable supplemental features, but is not required to create a simple browser.

Additionally, we’re taking advantage of the new ECMAScript 2015 (ES2015) support in Chakra, the JavaScript engine behind Microsoft Edge and the WebView control. ES2015 allows us to remove much of the scaffolding and boilerplate code, simplifying our implementation significantly. The following ES2015 features were used in the creation of this app: Array.from(), Array.prototype.find(), arrow functions, method properties, const, for-of, let, Map, Object.assign(), Promises, property shorthands, Proxies, spread operator, String.prototype.includes(), String.prototype.startsWith(), Symbols, template strings, and Unicode code point escapes.

User interface

The user interface consists of ten components:

  • Title bar
  • Back button
  • Forward button
  • Refresh button
  • Favicon
  • Address bar
  • Share on Twitter button
  • Favorites button and menu
  • Settings button and menu
  • WebView control

Favorites

Additional functionality

There are several additional features implemented to make the browsing experience more pleasant:

  • Keyboard shortcuts - press F11 to toggle fullscreen mode, ESC to exit fullscreen mode, or Ctrl + L to select the address bar
  • CSS transitions for animating the menus
  • Cache management
  • Favorites management
  • URL input analysis — “bing.com” navigates to http(s)://bing.com, “seahawks” searches Bing
  • Auto-de/select the address bar on blur/focus
  • Responsive design

Harnessing the WebView control

<div class="navbar">
  <!-- ... -->
</div>
<x-ms-webview id="WebView"></x-ms-webview>

Introduced for JavaScript apps in Windows 8.1, the WebView control—sometimes referred to by its tag name, x-ms-webview—allows you to host web content in your Windows app. Available in both HTML and XAML, the x-ms-webview comes with a powerful set of APIs, which overcomes several of the limitations that encumber an iframe, such as framebusting sites and document loading events. Additionally, the x-ms-webview provides new functionality that is not possible with an iframe, such as better access to local content and the ability to take screenshots.

When you use the WebView control, you get the same web platform that powers Microsoft Edge.

WebView flowchart

Developing the browser

We will be using fifteen of the x-ms-webview APIs. All but two of these members handle the page navigation in some capacity. Let’s see how we can hook into these APIs to create each UI component.

Hooking up the back and forward buttons

When you invoke a back button, the browser returns to an earlier page in the browser history, if available. Similarly, when you invoke a forward button, the browser returns to a later page in the browser history, if available. In order to implement this logic, we use the goBack() and goForward() methods, respectively. These functions will automatically navigate to the correct page in the navigation stack.

After every page navigation, we will also update the current state to stop the user from navigating further when they reach either end of the navigation stack. This will disable the back or forward buttons when the canGoBack property or the canGoForward property resolves false, respectively.

// Update the navigation state
this.updateNavState = () => {
  this.backButton.disabled = !this.webview.canGoBack;
  this.forwardButton.disabled = !this.webview.canGoForward;
};

// Listen for the back button to navigate backwards
this.backButton.addEventListener("click", () => this.webview.goBack());

// Listen for the forward button to navigate forwards
this.forwardButton.addEventListener("click", () => this.webview.goForward());

Hooking up the refresh and stop buttons

The refresh and stop buttons are slightly different than the rest of the navbar components in that they take up the same space in the UI. When a page is loading, clicking the button will stop the navigation, hide the progress ring, and display a refresh icon. Conversely, when a page is stagnant, clicking the button will refresh the page and (in another part of the code) display a stop icon. We’ll use the refresh() and stop() methods based on the present conditions.

// Listen for the stop/refresh button to stop navigation/refresh the page
this.stopButton.addEventListener("click", () => {
  if (this.loading) {
    this.webview.stop();
    this.showProgressRing(false);
    this.showRefresh();
  }
  else {
    this.webview.refresh();
  }
});

Hooking up the address bar

At a very high level, implementing the address bar appears quite simple. When a URL is entered in the textbox, pressing Enter will call the navigate() method using the address bar input value as its parameter.

However, today’s modern browsers have increased the amount of functionality for added convenience to the user. This adds some complexity to the implementation, depending on the number of scenarios you intend on accommodating.

const RE_VALIDATE_URL = /^[-:.&#+()[\]$'*;@~!,?%=\/\w]+$/;

// Attempt a function
function attempt(func) {
  try {
    return func();
  }
  catch (e) {
    return e;
  }
}

// Navigate to the specified absolute URL
function navigate(webview, url, silent) {
  let resp = attempt(() => webview.navigate(url));
  // ...
}

// Navigate to the specified location
this.navigateTo = loc => {
  // ...
  // Check if the input value contains illegal characters
  let isUrl = RE_VALIDATE_URL.test(loc);
  if (isUrl && navigate(this.webview, loc, true)) {
    return;
  }
  // ... Fallback logic (e.g. prepending http(s) to the URL, querying Bing.com, etc.)
};

// Listen for the Enter key in the address bar to navigate to the specified URL
this.urlInput.addEventListener("keypress", e => {
  if (e.keyCode === 13) {
    this.navigateTo(urlInput.value);
  }
});

Here are some example scenarios to consider. Say the value "microsoft.com" was entered into the address bar. The URL is not entirely complete. Passing that value into the navigate() method would end unsuccessfully. The browser must acknowledge that the URL is incomplete and determine whether http or https is the correct protocol to prepend. Furthermore, a URL may not have been intended as a URL at all. Say the value “seahawks” was entered into the address bar. Many browsers have their address bar double as a search box. The browser must establish that the value is not a URL, and fall back to querying a search engine for that value.

Displaying the favicon

Acquiring a favicon can be tricky, as there are several ways in which it can be displayed. The easiest route would be to check the root of the website for a file named "favicon.ico". Though, some sites are actually in the subdomain and may have a different favicon. For example, the favicon for “microsoft.com” is different than the favicon for “windows.microsoft.com”. In order to minimize the ambiguity, another route would be to check the markup of the page for a link tag within the document head with a “rel” attribute of “icon” or “shortcut icon”. We use the invokeScriptAsync() method to inject script into the WebView control, which will return a string if successful. Our script will search for all elements in the hosted page with a link tag, check if the ref attribute contains the word “icon”, and, if there is a match, return the value of the “href” attribute back to the app.

// Check if a file exists at the specified URL
function fileExists(url) {
  return new Promise(resolve =>
    Windows.Web.Http.HttpClient()
      .getAsync(new URI(url), Windows.Web.Http.HttpCompletionOption.responseHeadersRead)
      .done(e => resolve(e.isSuccessStatusCode), () => resolve(false))
  );
}

// Show the favicon if available
this.getFavicon = loc => {
  let host = new URI(loc).host;

  // Exit for cached ico location
  // ...

  let protocol = loc.split(":")[0];

  // Hide favicon when the host cannot be resolved or the protocol is not http(s)
  // ...

  loc = `${protocol}://${host}/favicon.ico`;

  // Check if there is a favicon in the root directory
  fileExists(loc).then(exists => {
    if (exists) {
      console.log(`Favicon found: ${loc}`);
      this.favicon.src = loc;
      return;
    }
    // Asynchronously check for a favicon in the web page markup
    console.log("Favicon not found in root. Checking the markup...");
    let script = "Object(Array.from(document.getElementsByTagName('link')).find(link => link.rel.includes('icon'))).href";
    let asyncOp = this.webview.invokeScriptAsync("eval", script);

    asyncOp.oncomplete = e => {
      loc = e.target.result || "";

      if (loc) {
        console.log(`Found favicon in markup: ${loc}`);
        this.favicon.src = loc;
      }
      else {
        this.hideFavicon();
      }
    };
    asyncOp.onerror = e => {
      console.error(`Unable to find favicon in markup: ${e.message}`);
    };
    asyncOp.start();
  });
};

As mentioned earlier, we are making use of the new ES2015 specification throughout our code. You may have noticed the use of arrow notation in many of the previous code samples, among other new JavaScript APIs. The injected script is a great example of the code improvement exhibited by implementing ES2015 features.

// Before (ES < 6):
"(function () {var n = document.getElementsByTagName('link'); for (var i = 0; i < n.length; i++) { if (n[i].rel.indexOf('icon') > -1) { return n[i].href; }}})();"

// After (ES6):
"Object(Array.from(document.getElementsByTagName('link')).find(link => link.rel.includes('icon'))).href"

Adding keyboard shortcuts

Unlike the features we have already covered, implementing keyboard shortcuts requires a small amount of C++ or C# code to create a Windows Runtime (WinRT) component.

Keyboard shortcuts flowchart

In order to recognize the defined hotkeys for particular actions, such as Ctrl + L to select the address bar and F11 to toggle full-screen mode, we need to inject script into the WebView control. This is done using the invokeScriptAsync() method we discussed earlier. However, we need some way to communicate the key codes back to the app layer.

With the addWebAllowedObject() method, we can expose a method for the injected script to pass the hotkeys back to our app logic in JavaScript. Although, in Windows 10, the WebView control is off-thread. We need to create a dispatcher, which will marshal the event through to the UI thread so that the app layer can receive the notification.

KeyHandler::KeyHandler()
{
	// Must run on App UI thread
	m_dispatcher = Windows::UI::Core::CoreWindow::GetForCurrentThread()->Dispatcher;
}

void KeyHandler::setKeyCombination(int keyPress)
{
	m_dispatcher->RunAsync(
		CoreDispatcherPriority::Normal,
		ref new DispatchedHandler([this, keyPress]
	{
		NotifyAppEvent(keyPress);
	}));
}
// Create the C++ Windows Runtime Component
let winRTObject = new NativeListener.KeyHandler();

// Listen for an app notification from the WinRT object
winRTObject.onnotifyappevent = e => this.handleShortcuts(e.target);

// Expose the native WinRT object on the page's global object
this.webview.addWebAllowedObject("NotifyApp", winRTObject);

// ...

// Inject fullscreen mode hot key listener into the WebView with every page load
this.webview.addEventListener("MSWebViewDOMContentLoaded", () => {
    let asyncOp = this.webview.invokeScriptAsync("eval", `
        addEventListener("keydown", e => {
            let k = e.keyCode;
            if (k === ${this.KEYS.ESC} || k === ${this.KEYS.F11} || (e.ctrlKey && k === ${this.KEYS.L})) {
                NotifyApp.setKeyCombination(k);
            }
        });
    `);
    asyncOp.onerror = e => console.error(`Unable to listen for fullscreen hot keys: ${e.message}`);
    asyncOp.start();
});

Customizing the browser

Now that we have incorporated the key WebView APIs, let’s explore how we can customize and polish our user interface.

Branding the title bar

Leveraging Windows Runtime APIs, we can use the ApplicationView.TitleBar property to modify the color palette for all components in the app title bar. In our browser, we modify the colors on app load to match the background color of the navbar. We also modify the colors when either of the menus are open to match the background color of the menu. Each color must be defined as an object of RGBA properties. For convenience, we created a helper function to generate the correct format from a hexadecimal string.

//// browser.js
// Use a proxy to workaround a WinRT issue with Object.assign
this.titleBar = new Proxy(Windows.UI.ViewManagement.ApplicationView.getForCurrentView().titleBar, {
  "get": (target, key) => target[key],
  "set": (target, key, value) => (target[key] = value, true)
});

//// title-bar.js
// Set your default colors
const BRAND = hexStrToRGBA("#3B3B3B");
const GRAY = hexStrToRGBA("#666");
const WHITE = hexStrToRGBA("#FFF");

// Set the default title bar colors
this.setDefaultAppBarColors = () => {
  Object.assign(this.titleBar, {
    "foregroundColor": BRAND,
    "backgroundColor": BRAND,

    "buttonForegroundColor": WHITE,
    "buttonBackgroundColor": BRAND,

    "buttonHoverForegroundColor": WHITE,
    "buttonHoverBackgroundColor": GRAY,

    "buttonPressedForegroundColor": BRAND,
    "buttonPressedBackgroundColor": WHITE,

    "inactiveForegroundColor": BRAND,
    "inactiveBackgroundColor": BRAND,

    "buttonInactiveForegroundColor": GRAY,
    "buttonInactiveBackgroundColor": BRAND,

    "buttonInactiveHoverForegroundColor": WHITE,
    "buttonInactiveHoverBackgroundColor": BRAND,

    "buttonPressedForegroundColor": BRAND,
    "buttonPressedBackgroundColor": BRAND
  });
};

Other functionality

The progress indicator, as well as the settings and favorites menus, leverage CSS transitions for animation. With the former menu, the temporary web data is cleared using the clearTemporaryWebDataAsync() method. With the latter menu, the list of favorites is stored on a JSON file in the root folder of the roaming app data store.

Citations

The JSBrowser logo is based on trees by Nicholas Menghini from the Noun Project.

Code of Conduct

This project has adopted the Microsoft Open Source Code of Conduct. For more information see the Code of Conduct FAQ or contact [email protected] with any additional questions or comments.

More Repositories

1

MSEdge

Microsoft Edge
2,817
star
2

MSEdgeExplainers

Home for explainer documents originated by the Microsoft Edge team
HTML
1,281
star
3

magic-mirror-demo

A ⚡Magic Mirror⚡ powered by a UWP Hosted Web App 🚀
JavaScript
1,232
star
4

static-code-scan

Run this quick static code scan on any URL to check for out-of-date libraries, layout issues and accessibility.
JavaScript
1,047
star
5

WebView2Samples

Microsoft Edge WebView2 samples
C++
834
star
6

WebGL

Microsoft Edge WebGL Implementation
C++
716
star
7

Status

This repository tracks the roadmap for the Microsoft Edge web platform. This data is used on https://status.microsoftedge.com/ to provide implementation status and forward-looking plans for web standards in Edge and other browsers.
JavaScript
535
star
8

Demos-old

Open source and interoperable demos for Microsoft Edge Dev site
JavaScript
437
star
9

WebView2Feedback

Feedback and discussions about Microsoft Edge WebView2
430
star
10

WebView2Browser

A web browser built with the Microsoft Edge WebView2 control.
JavaScript
399
star
11

MicrosoftEdge-Documentation

Microsoft Edge documentation
240
star
12

Demos

Web pages and apps used to demo various DevTools, PWA, WebView, Extensions, and Web Platform features of Microsoft Edge
JavaScript
195
star
13

JsDbg

Debugging extensions for Microsoft Edge and other Chromium-based browsers
JavaScript
177
star
14

DevTools

Feedback and discussions about Microsoft Edge Developer Tools
HTML
152
star
15

Sudoku

In this demo we take the well-known game of Sudoku, and demonstrate how developers can use HTML5 and JavaScript to create an efficient algorithm to solve these puzzles. The algorithms draw heavily on the Chakra engine’s support for ECMAScript 5 standard array operations to rapidly solve many Sudoku games. You can also manually solve Sudoku puzzles.
HTML
148
star
16

MicrosoftEdge-Extensions-Demos

JavaScript
135
star
17

pushnotifications-demo

Demo for cross browsers push notifications with server side code
JavaScript
120
star
18

WebView2Announcements

Subscribe to this repo to be notified of Announcements and changes in Microsoft WebView2 controls.
111
star
19

generator-appx

Yeoman generator for building websites and Windows 10 Apps
JavaScript
90
star
20

webauthnsample

JavaScript
85
star
21

dev.microsoftedge.com-vms

Scripts used to generate the free VMs available at https://dev.microsoftedge.com
PowerShell
80
star
22

edge-launcher

A simple command line exe to launch Microsoft Edge at a URL.
C++
78
star
23

BrowserEfficiencyTest

BrowserEfficiencyTest is a Selenium WebDriver based web browser test automation project.
C#
65
star
24

css-usage

This script is used within our Bing and Interop crawlers to determine the properties used on a page and generalized values that could have been used.
JavaScript
65
star
25

EdgeWebDriver

Feedback and discussions about WebDriver for Microsoft Edge
55
star
26

enterprise-mode-site-list-portal

C#
52
star
27

Gamepad-Sample

A basic sample on how to use the W3C GamePad API across browsers.
JavaScript
50
star
28

pushnotifications-demo-aspnetcore

Demo for cross-browser push notifications with ASP.NET Core server side code
C#
47
star
29

A11y

Automated testing for HTML5Accessibility.com
C#
40
star
30

hwa-cli

Command-Line Interface for converting Windows Hosted Web Apps locally and remotely
TypeScript
31
star
31

webauthn-polyfill

Polyfill that maps the Web Authentication API on top of Edge preliminary implementation.
JavaScript
30
star
32

WebAppsDocs

Windows 10 web apps and frameworks documentation
HTML
30
star
33

Elevator

Elevator is a utility that allows other programs to collect traces through Windows Performance Recorder.
C#
28
star
34

DevToolsSamples

A collection of samples from Edge Developer Tools
JavaScript
22
star
35

webpayments

Polyfill to map Apple Pay APIs to the latest spec for Web Payments.
22
star
36

APICatalogData

This project contains the data for https://aka.ms/apicatalog, a tool to visualize and analyze the API overlap between standards specifications and support across browsers.
16
star
37

edgevr

Edge
HTML
15
star
38

appx-starter

JavaScript
15
star
39

fps-emitter

Measure the FPS of a page and emit it whenever it updates. Designed for the browser.
JavaScript
15
star
40

videotest

Video streaming battery rundown test methodology
CSS
14
star
41

pan-tool

A tool that injects touch input on the screen. It sends pointerdown, pointerupdate and pointerup to simulate pan, scrolling and fling gestures.
C++
14
star
42

wptest

JavaScript
14
star
43

serviceworker-demo

Service Worker demos
JavaScript
13
star
44

WebOnPi

Home application that comes pre-loaded on the #WebOnPi project
C++
13
star
45

hwa-server

Server for deploying HWAs in conjunction with the HWA-CLI tool.
TypeScript
12
star
46

hwa

PowerShell
12
star
47

hwa-starter

Everything you need to get started building a Windows Universal Hosted Web App.
HTML
12
star
48

edge-launch

Launcher for Microsoft Edge
JavaScript
12
star
49

CloudAppX

Cloudy with a chance of AppX
JavaScript
11
star
50

noin

Takes inline scripts and puts them into separate files
JavaScript
11
star
51

DevToolsDeviceList

Repo that contains the list of devices displayed in the emulation tool of the developer tools in Edge.
10
star
52

MicrosoftWebDriver-chocolatey

Microsoft WebDriver chocolatey package so installation is easier
PowerShell
9
star
53

SurfacePresenter

ISurfacePresenter API with D3D9 and D3D10/11, and contrasts with GDI
C++
8
star
54

css-usage-for-crawlers

This is where we will store our crawler specific code.
JavaScript
8
star
55

Sprache

A NuGet package for managing and using the Accept-Language header in C#
C#
7
star
56

generator-lasagnajs

Generator for the LasagnaJS architecture
JavaScript
6
star
57

A11yDevTool

This repo contains a mapping of UIA properties to ARIA properties that is used by the F12 developer tools in Edge.
6
star
58

CloudAppX-cli

JavaScript
6
star
59

timing-allow-origin-test

Demo of Timing-Allow-Origin with multiple origins
JavaScript
5
star
60

.github

Default Community Health Files for the Microsoft Edge organization on GitHub
4
star
61

microsoftedge.github.io

3
star
62

edge-devtools-crash-analyzer-support

Enables un-minification of call stacks in the Microsoft Edge DevTools Crash Analyzer tool
TypeScript
3
star
63

action-issue-to-workitem

GitHub Action that creates a Azure DevOps work item when an Issue is created
JavaScript
2
star
64

TopDeveloperNeeds

Web platform top developer needs dashboard
HTML
1
star