Skip to content

Design Decisions

FlorianDecker edited this page Sep 15, 2015 · 2 revisions

HTML5 compatible JavaScript API

Should the Drag & Drop API be HTML5 compatible? Is it possible?

Unfortunately, we could not make the API for file selection and upload HTML5 File API compatible, mainly because the required interception of JavaScript calls can not be provided without side-effects. This would introduce browser sniffing in the client code (for IE 9 and below), which is an unacceptable solution and therefore we will focus on creating an easily adoptable API for all supported browsers. HTML5 can then be used as an alternative.

Requirements

  • Upload of multiple files
  • Progress and finished events
  • Work-around free (i.e. no iframes, flash, etc.)
  • Simple checks (for file-size, -type, ...)

Details

This example for the file API shows a very basic use case: Selection of multiple files, previews for images, and subsequent upload.

// files property of element objects
// --------------------------------------------

var files = document.getElementById("drop-area").files; 

for (var i = 0, l = files.length; i < l; i++) {
	file = files[i];
	xhr = new XMLHttpRequest();
	
	// Events
	// --------------------------------------------
	// Update progress bar
	xhr.upload.addEventListener("progress", function (evt) {
		if (evt.lengthComputable) {
			progressBar.style.width = (evt.loaded / evt.total) * 100 + "%";
		}
		else {
			   // No data to calculate on
		}
	}, false);
			
	// File uploaded
	xhr.addEventListener("load", function () {
		progressBarContainer.className += " uploaded";
		progressBar.innerHTML = "Uploaded!";
	}, false);

	// Send file
	// --------------------------------------------
	
	xhr.open("post", "upload/upload.php", true);
			
	// Set appropriate headers
	...

	xhr.send(file);
}

However, some critical aspects (read: classes and properties) are missing in earlier browser versions and they would have to be substituted and, making use of JavaScript's Duck Typing approach, mimicked. Since the API adds a files property to every object involved (i.e. the elements dragged upon, file selector inputs, ...) of type FileList, ...

var files = document.getElementById("drop-area").files;  

... this would have been the first entry point: change the element lookup, so it returns a suitable object for the corresponding HTML elements. Unfortunately, after spending hours of searching, no evidence turned up showing any hook (or something similar) to intercept the JavaScript call at any point. Hence, it would require a separate call to window.external anyway, breaking compatibility, or the addition of a "local" library to shadow the function(s) and redirect them accordingly, with unpredictable results.

Before HTML5, no standardized file access was available in JavaScript, as it is mentioned here, meaning that there is also no way of passing streams (input) streams to JavaScript in order to read them (for obvious security reasons).

Solution

Instead of trying to imitate the HTML5 File API, the implementation will focus on an easily adoptable solution using an API implemented in C#. Similar to the HTML5 File API, there will be four events for custom Drag & Drop actions:

  • dragenter
  • dragover
  • dragleave
  • drop

An abstract representation (using a file handle, e.g. a GUID) of files with suitable functions to deal with them in different ways, for example upload them via a PUT request:

.addEventListener("drop", document.getElementById("landing_zone"), function (evt) {
        var fileHandle = evt.DroppedFiles[0];
        uploadService.upload(fileHandle, "PUT", {}); // sample interface: handle, method, headers
}, false); 

Therefore, files will be read and dealt with in C#, while access is still easily possible from within JavaScript. Moreover, this API will can be further extended with custom Plug Ins, hence, many different operations on files are possible without breaking compatibility to standardized file functionality.

IWebBrowser2, commands and how to prevent them

One of the main issues in DesktopGap will be to control the "normal" behavior of web browsers: Back, Forward, Refresh, Print, etc. should not be callable by the user since it is likely to break page flow and cause all sorts of abnormalities. However, after removing the corresponding buttons from the UI, their functionality remained and could still be activated with keyboard shortcuts. This was initially believed to be done by an object passed into the .NET implementation of the web browser (whose hard cast to an internal interface prohibited a simple solution) - however, the keyboard shortcuts turned out to be handled somewhere internally, and are therefore inaccessible to us.

Since there is a huge amount of keyboard shortcuts, which are probably even changing over time, a more stable solution had to be found. Unfortunately, nothing has been found so far to directly inhibit navigation functionality (such as replacing functions as NO-OPs or similar), whereas the following things have (unsuccessfully) been tried:

  • TransparentProxy, with NO-OPs to return instead of the original object
  • Whitelisting keyboard shortcuts
  • Interception of COM commands
  • Internet research

The solution we ultimately chose was actually to whitelist a few shortcuts while disabling all the others. But, as it turns out, the .NET framework keeps an enum for keyboard shortcuts (Windows.Forms.Shortcuts), leading to this:

 public void OnBrowserKeyDown (TridentWebBrowser extendedTridentWebBrowser, KeyEventArgs keyEventArgs)
    {
      var keyCode = keyEventArgs.KeyCode | ModifierKeys;

      if (ModifierKeys == 0 || !Enum.IsDefined (typeof (Shortcut), (Shortcut) keyCode))
        return;

      switch (keyCode)
      {
        case Keys.Control | Keys.A: // Select All
        case Keys.Control | Keys.C: // Copy
        case Keys.Control | Keys.V: // Paste
        case Keys.Control | Keys.X: // Cut
        case Keys.Delete: // Delete
          keyEventArgs.Handled = !_enableWebBrowserEditingShortcuts;
          break;
        default:
          keyEventArgs.Handled = !_enableWebBrowserShortcuts;
          break;
      }
    }

For some reason delete was also considered a shortcut and thus disabled ...