Merge inbound to m-c.
authorRyan VanderMeulen <ryanvm@gmail.com>
Fri, 18 Oct 2013 15:08:39 -0400
changeset 166119 81bd105755580575648c985c1151cc6afa74cc17
parent 166062 51dee39976fb76a469a558ee8f6cf5559192dfc2 (current diff)
parent 166118 48932e8aa16206d69a1b0f738fcce733c8b3599a (diff)
child 166120 3daff401c7ab231fbd2a4137cbf2fd8ffc7fcf04
push id428
push userbbajaj@mozilla.com
push dateTue, 28 Jan 2014 00:16:25 +0000
treeherdermozilla-release@cd72a7ff3a75 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone27.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge inbound to m-c.
dom/interfaces/contacts/moz.build
dom/interfaces/contacts/nsIContactProperties.idl
dom/interfaces/contacts/nsIDOMContactManager.idl
dom/system/gonk/RILContentHelper.js
--- a/b2g/app/Makefile.in
+++ b/b2g/app/Makefile.in
@@ -19,17 +19,17 @@ LIBS += \
   -lEGL \
   -lhardware_legacy \
   -lhardware \
   -lcutils \
   $(DEPTH)/media/libpng/$(LIB_PREFIX)mozpng.$(LIB_SUFFIX) \
   $(DEPTH)/widget/gonk/libdisplay/$(LIB_PREFIX)display.$(LIB_SUFFIX) \
   $(MOZ_ZLIB_LIBS) \
   $(NULL)
-ifeq (18,$(ANDROID_VERSION))
+ifeq ($(ANDROID_VERSION),$(findstring $(ANDROID_VERSION),17 18))
 LIBS += \
   -lgui \
   -lsuspend \
   $(NULL)
 endif
 OS_LDFLAGS += -Wl,--export-dynamic
 LOCAL_INCLUDES += -I$(topsrcdir)/widget/gonk/libdisplay
 endif
--- a/browser/base/content/browser-plugins.js
+++ b/browser/base/content/browser-plugins.js
@@ -706,16 +706,25 @@ var gPluginHandler = {
       }
       else {
         url = Services.urlFormatter.formatURLPref("plugins.clickToActivateInfo.url");
       }
       pluginInfo.detailsLink = url;
 
       centerActions.push(pluginInfo);
     }
+
+    if (centerActions.length == 0) {
+      // TODO: this is a temporary band-aid to avoid broken doorhangers
+      // until bug 926605 is landed.
+      notification.options.centerActions = [];
+      setTimeout(() => PopupNotifications.remove(notification), 0);
+      return;
+    }
+
     centerActions.sort(function(a, b) {
       return a.pluginName.localeCompare(b.pluginName);
     });
 
     notification.options.centerActions = centerActions;
 
     // Histograms always start at 0, even though our data starts at 1
     let histogramCount = centerActions.length - 1;
--- a/browser/base/content/test/general/browser.ini
+++ b/browser/base/content/test/general/browser.ini
@@ -54,16 +54,17 @@ support-files =
   plugin_bug752516.html
   plugin_bug787619.html
   plugin_bug797677.html
   plugin_bug820497.html
   plugin_clickToPlayAllow.html
   plugin_clickToPlayDeny.html
   plugin_data_url.html
   plugin_hidden_to_visible.html
+  plugin_iframe.html
   plugin_small.html
   plugin_test.html
   plugin_test2.html
   plugin_test3.html
   plugin_two_types.html
   plugin_unknown.html
   print_postdata.sjs
   redirect_bug623155.sjs
@@ -74,16 +75,17 @@ support-files =
   test_bug839103.html
   test_wyciwyg_copying.html
   title_test.svg
   video.ogg
   zoom_test.html
 
 [browser_CTP_data_urls.js]
 [browser_CTP_drag_drop.js]
+[browser_CTP_iframe.js]
 [browser_CTP_nonplugins.js]
 [browser_CTP_resize.js]
 [browser_URLBarSetURI.js]
 [browser_aboutHealthReport.js]
 [browser_aboutHome.js]
 [browser_aboutSyncProgress.js]
 [browser_addKeywordSearch.js]
 [browser_addon_bar_aomlistener.js]
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/general/browser_CTP_iframe.js
@@ -0,0 +1,135 @@
+var rootDir = getRootDirectory(gTestPath);
+const gTestRoot = rootDir;
+const gHttpTestRoot = rootDir.replace("chrome://mochitests/content/", "http://127.0.0.1:8888/");
+
+var gTestBrowser = null;
+var gNextTest = null;
+var gPluginHost = Components.classes["@mozilla.org/plugin/host;1"].getService(Components.interfaces.nsIPluginHost);
+var gPageLoads = 0;
+
+Components.utils.import("resource://gre/modules/Services.jsm");
+
+// This listens for the next opened tab and checks it is of the right url.
+// opencallback is called when the new tab is fully loaded
+// closecallback is called when the tab is closed
+function TabOpenListener(url, opencallback, closecallback) {
+  this.url = url;
+  this.opencallback = opencallback;
+  this.closecallback = closecallback;
+
+  gBrowser.tabContainer.addEventListener("TabOpen", this, false);
+}
+
+TabOpenListener.prototype = {
+  url: null,
+  opencallback: null,
+  closecallback: null,
+  tab: null,
+  browser: null,
+
+  handleEvent: function(event) {
+    if (event.type == "TabOpen") {
+      gBrowser.tabContainer.removeEventListener("TabOpen", this, false);
+      this.tab = event.originalTarget;
+      this.browser = this.tab.linkedBrowser;
+      gBrowser.addEventListener("pageshow", this, false);
+    } else if (event.type == "pageshow") {
+      if (event.target.location.href != this.url)
+        return;
+      gBrowser.removeEventListener("pageshow", this, false);
+      this.tab.addEventListener("TabClose", this, false);
+      var url = this.browser.contentDocument.location.href;
+      is(url, this.url, "Should have opened the correct tab");
+      this.opencallback(this.tab, this.browser.contentWindow);
+    } else if (event.type == "TabClose") {
+      if (event.originalTarget != this.tab)
+        return;
+      this.tab.removeEventListener("TabClose", this, false);
+      this.opencallback = null;
+      this.tab = null;
+      this.browser = null;
+      // Let the window close complete
+      executeSoon(this.closecallback);
+      this.closecallback = null;
+    }
+  }
+};
+
+function test() {
+  waitForExplicitFinish();
+  registerCleanupFunction(function() {
+    clearAllPluginPermissions();
+    Services.prefs.clearUserPref("extensions.blocklist.suppressUI");
+  });
+  Services.prefs.setBoolPref("extensions.blocklist.suppressUI", true);
+
+  var newTab = gBrowser.addTab();
+  gBrowser.selectedTab = newTab;
+  gTestBrowser = gBrowser.selectedBrowser;
+  gTestBrowser.addEventListener("load", pageLoad, true);
+
+  Services.prefs.setBoolPref("plugins.click_to_play", true);
+  setTestPluginEnabledState(Ci.nsIPluginTag.STATE_CLICKTOPLAY);
+
+  prepareTest(runAfterPluginBindingAttached(test1), gHttpTestRoot + "plugin_iframe.html");
+}
+
+function finishTest() {
+  clearAllPluginPermissions();
+  gTestBrowser.removeEventListener("load", pageLoad, true);
+  gBrowser.removeCurrentTab();
+  window.focus();
+  finish();
+}
+
+function pageLoad() {
+  // Wait for the iframe to be loaded as well.
+  if (gPageLoads++ < 1)
+    return;
+
+  // The plugin events are async dispatched and can come after the load event
+  // This just allows the events to fire before we then go on to test the states.
+  executeSoon(gNextTest);
+}
+
+function prepareTest(nextTest, url) {
+  gNextTest = nextTest;
+  gTestBrowser.contentWindow.location = url;
+}
+
+// Due to layout being async, "PluginBindAttached" may trigger later.
+// This wraps a function to force a layout flush, thus triggering it,
+// and schedules the function execution so they're definitely executed
+// afterwards.
+function runAfterPluginBindingAttached(func) {
+  return function() {
+    let doc = gTestBrowser.contentDocument.getElementById('frame').contentDocument;
+    let elems = doc.getElementsByTagName('embed');
+    if (elems.length < 1) {
+      elems = doc.getElementsByTagName('object');
+    }
+    elems[0].clientTop;
+    executeSoon(func);
+  };
+}
+
+// Test that we don't show a doorhanger after removing the last plugin
+// when the plugin was in an iframe.
+
+function test1() {
+  let popupNotification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
+  ok(popupNotification, "Test 1, Should have a click-to-play notification");
+
+  let frame = gTestBrowser.contentDocument.getElementById("frame");
+  frame.parentElement.removeChild(frame);
+
+  let condition = () => {
+    let notification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
+    if (notification) {
+      notification.reshow();
+    }
+    return !notification;
+  }
+
+  waitForCondition(condition, finishTest, "Test1, Waited too long for notification too be removed");
+}
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/general/plugin_iframe.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+</head>
+<body>
+<iframe src="plugin_test.html" id="frame" width="300" height="300">
+  This should load plugin_test.html
+</iframe>
+</body>
+</html>
--- a/browser/extensions/pdfjs/README.mozilla
+++ b/browser/extensions/pdfjs/README.mozilla
@@ -1,4 +1,4 @@
 This is the pdf.js project output, https://github.com/mozilla/pdf.js
 
-Current extension version is: 0.8.558
+Current extension version is: 0.8.629
 
--- a/browser/extensions/pdfjs/content/build/pdf.js
+++ b/browser/extensions/pdfjs/content/build/pdf.js
@@ -15,18 +15,18 @@
  * limitations under the License.
  */
 
 // Initializing PDFJS global object (if still undefined)
 if (typeof PDFJS === 'undefined') {
   (typeof window !== 'undefined' ? window : this).PDFJS = {};
 }
 
-PDFJS.version = '0.8.558';
-PDFJS.build = 'ea50c07';
+PDFJS.version = '0.8.629';
+PDFJS.build = 'b16b3be';
 
 (function pdfjsWrapper() {
   // Use strict in our context only - users might not want it
   'use strict';
 
 /* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
 /* Copyright 2012 Mozilla Foundation
@@ -1226,16 +1226,21 @@ var ColorSpace = (function ColorSpaceClo
 
     switch (name) {
       case 'DeviceGrayCS':
         return this.singletons.gray;
       case 'DeviceRgbCS':
         return this.singletons.rgb;
       case 'DeviceCmykCS':
         return this.singletons.cmyk;
+      case 'CalGrayCS':
+        var whitePoint = IR[1].WhitePoint;
+        var blackPoint = IR[1].BlackPoint;
+        var gamma = IR[1].Gamma;
+        return new CalGrayCS(whitePoint, blackPoint, gamma);
       case 'PatternCS':
         var basePatternCS = IR[1];
         if (basePatternCS)
           basePatternCS = ColorSpace.fromIR(basePatternCS);
         return new PatternCS(basePatternCS);
       case 'IndexedCS':
         var baseIndexedCS = IR[1];
         var hiVal = IR[2];
@@ -1301,17 +1306,18 @@ var ColorSpace = (function ColorSpaceClo
           return 'DeviceGrayCS';
         case 'DeviceRGB':
         case 'RGB':
           return 'DeviceRgbCS';
         case 'DeviceCMYK':
         case 'CMYK':
           return 'DeviceCmykCS';
         case 'CalGray':
-          return 'DeviceGrayCS';
+          var params = cs[1].getAll();
+          return ['CalGrayCS', params];
         case 'CalRGB':
           return 'DeviceRgbCS';
         case 'ICCBased':
           var stream = xref.fetchIfRef(cs[1]);
           var dict = stream.dict;
           var numComps = dict.get('N');
           if (numComps == 1)
             return 'DeviceGrayCS';
@@ -1722,16 +1728,123 @@ var DeviceCmykCS = (function DeviceCmykC
     },
     usesZeroToOneRange: true
   };
 
   return DeviceCmykCS;
 })();
 
 //
+// CalGrayCS: Based on "PDF Reference, Sixth Ed", p.245
+//
+var CalGrayCS = (function CalGrayCSClosure() {
+  function CalGrayCS(whitePoint, blackPoint, gamma) {
+    this.name = 'CalGray';
+    this.numComps = 3;
+    this.defaultColor = new Float32Array([0, 0, 0]);
+
+    if (!whitePoint) {
+      error('WhitePoint missing - required for color space CalGray');
+    }
+    blackPoint = blackPoint || [0, 0, 0];
+    gamma = gamma || 1;
+
+    // Translate arguments to spec variables.
+    this.XW = whitePoint[0];
+    this.YW = whitePoint[1];
+    this.ZW = whitePoint[2];
+
+    this.XB = blackPoint[0];
+    this.YB = blackPoint[1];
+    this.ZB = blackPoint[2];
+
+    this.G = gamma;
+
+    // Validate variables as per spec.
+    if (this.XW < 0 || this.ZW < 0 || this.YW !== 1) {
+      error('Invalid WhitePoint components for ' + this.name +
+            ', no fallback available');
+    }
+
+    if (this.XB < 0 || this.YB < 0 || this.ZB < 0) {
+      info('Invalid BlackPoint for ' + this.name + ', falling back to default');
+      this.XB = this.YB = this.ZB = 0;
+    }
+
+    if (this.XB !== 0 || this.YB !== 0 || this.ZB !== 0) {
+      TODO(this.name + ', BlackPoint: XB: ' + this.XB + ', YB: ' + this.YB +
+           ', ZB: ' + this.ZB + ', only default values are supported.');
+    }
+
+    if (this.G < 1) {
+      info('Invalid Gamma: ' + this.G + ' for ' + this.name +
+           ', falling back to default');
+      this.G = 1;
+    }
+  }
+
+  CalGrayCS.prototype = {
+    getRgb: function CalGrayCS_getRgb(src, srcOffset) {
+      var rgb = new Uint8Array(3);
+      this.getRgbItem(src, srcOffset, rgb, 0);
+      return rgb;
+    },
+    getRgbItem: function CalGrayCS_getRgbItem(src, srcOffset,
+                                              dest, destOffset) {
+      // A represents a gray component of a calibrated gray space.
+      // A <---> AG in the spec
+      var A = src[srcOffset];
+      var AG = Math.pow(A, this.G);
+
+      // Computes intermediate variables M, L, N as per spec.
+      // Except if other than default BlackPoint values are used.
+      var M = this.XW * AG;
+      var L = this.YW * AG;
+      var N = this.ZW * AG;
+
+      // Decode XYZ, as per spec.
+      var X = M;
+      var Y = L;
+      var Z = N;
+
+      // http://www.poynton.com/notes/colour_and_gamma/ColorFAQ.html, Ch 4.
+      // This yields values in range [0, 100].
+      var Lstar = Math.max(116 * Math.pow(Y, 1 / 3) - 16, 0);
+
+      // Convert values to rgb range [0, 255].
+      dest[destOffset] = Lstar * 255 / 100;
+      dest[destOffset + 1] = Lstar * 255 / 100;
+      dest[destOffset + 2] = Lstar * 255 / 100;
+    },
+    getRgbBuffer: function CalGrayCS_getRgbBuffer(src, srcOffset, count,
+                                                  dest, destOffset, bits) {
+      // TODO: This part is copied from DeviceGray. Make this utility function.
+      var scale = 255 / ((1 << bits) - 1);
+      var j = srcOffset, q = destOffset;
+      for (var i = 0; i < count; ++i) {
+        var c = (scale * src[j++]) | 0;
+        dest[q++] = c;
+        dest[q++] = c;
+        dest[q++] = c;
+      }
+    },
+    getOutputLength: function CalGrayCS_getOutputLength(inputLength) {
+      return inputLength * 3;
+    },
+    isPassthrough: ColorSpace.prototype.isPassthrough,
+    createRgbBuffer: ColorSpace.prototype.createRgbBuffer,
+    isDefaultDecode: function CalGrayCS_isDefaultDecode(decodeMap) {
+      return ColorSpace.isDefaultDecode(decodeMap, this.numComps);
+    },
+    usesZeroToOneRange: true
+  };
+  return CalGrayCS;
+})();
+
+//
 // LabCS: Based on "PDF Reference, Sixth Ed", p.250
 //
 var LabCS = (function LabCSClosure() {
   function LabCS(whitePoint, blackPoint, range) {
     this.name = 'Lab';
     this.numComps = 3;
     this.defaultColor = new Float32Array([0, 0, 0]);
 
@@ -1863,16 +1976,17 @@ var LabCS = (function LabCSClosure() {
       return true;
     },
     usesZeroToOneRange: false
   };
   return LabCS;
 })();
 
 
+
 var PatternType = {
   AXIAL: 2,
   RADIAL: 3
 };
 
 var Pattern = (function PatternClosure() {
   // Constructor should define this.getPattern
   function Pattern() {
@@ -3826,18 +3940,64 @@ PDFJS.maxImageSize = PDFJS.maxImageSize 
 
 /**
  * By default fonts are converted to OpenType fonts and loaded via font face
  * rules. If disabled, the font will be rendered using a built in font renderer
  * that constructs the glyphs with primitive path commands.
  * @var {Boolean}
  */
 PDFJS.disableFontFace = PDFJS.disableFontFace === undefined ?
-                        false :
-                        PDFJS.disableFontFace;
+                        false : PDFJS.disableFontFace;
+
+/**
+ * Path for image resources, mainly for annotation icons. Include trailing
+ * slash.
+ * @var {String}
+ */
+PDFJS.imageResourcesPath = PDFJS.imageResourcesPath === undefined ?
+                           '' : PDFJS.imageResourcesPath;
+
+/**
+ * Disable the web worker and run all code on the main thread. This will happen
+ * automatically if the browser doesn't support workers or sending typed arrays
+ * to workers.
+ * @var {Boolean}
+ */
+PDFJS.disableWorker = PDFJS.disableWorker === undefined ?
+                      false : PDFJS.disableWorker;
+
+/**
+ * Path and filename of the worker file. Required when the worker is enabled.
+ * @var {String}
+ */
+PDFJS.workerSrc = PDFJS.workerSrc === undefined ? null : PDFJS.workerSrc;
+
+/**
+ * Disable range request loading of PDF files. When enabled and if the server
+ * supports partial content requests then the PDF will be fetched in chunks.
+ * Enabled (false) by default.
+ * @var {Boolean}
+ */
+PDFJS.disableRange = PDFJS.disableRange === undefined ?
+                     false : PDFJS.disableRange;
+
+/**
+ * Disable pre-fetching of PDF file data. When range requests are enabled PDF.js
+ * will automatically keep fetching more data even if it isn't needed to display
+ * the current page. This default behavior can be disabled.
+ * @var {Boolean}
+ */
+PDFJS.disableAutoFetch = PDFJS.disableAutoFetch === undefined ?
+                         false : PDFJS.disableAutoFetch;
+
+/**
+ * Enables special hooks for debugging PDF.js.
+ * @var {Boolean}
+ */
+PDFJS.pdfBug = PDFJS.pdfBug === undefined ? false : PDFJS.pdfBug;
 
 /**
  * This is the main entry point for loading a PDF and interacting with it.
  * NOTE: If a URL is used to fetch the PDF data a standard XMLHttpRequest(XHR)
  * is used, which means it must follow the same origin rules that any XHR does
  * e.g. No cross domain requests without CORS.
  *
  * @param {string|TypedAray|object} source Can be an url to where a PDF is
@@ -4269,17 +4429,17 @@ var WorkerTransport = (function WorkerTr
 
     // If worker support isn't disabled explicit and the browser has worker
     // support, create a new web worker and test if it/the browser fullfills
     // all requirements to run parts of pdf.js in a web worker.
     // Right now, the requirement is, that an Uint8Array is still an Uint8Array
     // as it arrives on the worker. Chrome added this with version 15.
     if (!globalScope.PDFJS.disableWorker && typeof Worker !== 'undefined') {
       var workerSrc = PDFJS.workerSrc;
-      if (typeof workerSrc === 'undefined') {
+      if (!workerSrc) {
         error('No PDFJS.workerSrc specified');
       }
 
       try {
         // Some versions of FF can't create a worker on localhost, see:
         // https://bugzilla.mozilla.org/show_bug.cgi?id=683280
         var worker = new Worker(workerSrc);
         var messageHandler = new MessageHandler('main', worker);
@@ -6613,17 +6773,18 @@ var CanvasGraphics = (function CanvasGra
 
       var currentTransform = ctx.mozCurrentTransformInverse;
       var a = currentTransform[0], b = currentTransform[1];
       var widthScale = Math.max(Math.sqrt(a * a + b * b), 1);
       var c = currentTransform[2], d = currentTransform[3];
       var heightScale = Math.max(Math.sqrt(c * c + d * d), 1);
 
       var imgToPaint;
-      if (imgData instanceof HTMLElement) {
+      // instanceof HTMLElement does not work in jsdom node.js module
+      if (imgData instanceof HTMLElement || !imgData.data) {
         imgToPaint = imgData;
       } else {
         var tmpCanvas = CachedCanvases.getCanvas('inlineImage', width, height);
         var tmpCtx = tmpCanvas.context;
         putBinaryImageData(tmpCtx, imgData);
         imgToPaint = tmpCanvas.canvas;
       }
 
--- a/browser/extensions/pdfjs/content/build/pdf.worker.js
+++ b/browser/extensions/pdfjs/content/build/pdf.worker.js
@@ -15,18 +15,18 @@
  * limitations under the License.
  */
 
 // Initializing PDFJS global object (if still undefined)
 if (typeof PDFJS === 'undefined') {
   (typeof window !== 'undefined' ? window : this).PDFJS = {};
 }
 
-PDFJS.version = '0.8.558';
-PDFJS.build = 'ea50c07';
+PDFJS.version = '0.8.629';
+PDFJS.build = 'b16b3be';
 
 (function pdfjsWrapper() {
   // Use strict in our context only - users might not want it
   'use strict';
 
 /* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
 /* Copyright 2012 Mozilla Foundation
@@ -4165,32 +4165,26 @@ var PDFDocument = (function PDFDocumentC
               info('Bad value in document info for "' + key + '"');
             }
           }
         }
       }
       return shadow(this, 'documentInfo', docInfo);
     },
     get fingerprint() {
-      var xref = this.xref, fileID;
+      var xref = this.xref, hash, fileID = '';
+
       if (xref.trailer.has('ID')) {
-        fileID = '';
-        var id = xref.trailer.get('ID')[0];
-        id.split('').forEach(function(el) {
-          fileID += Number(el.charCodeAt(0)).toString(16);
-        });
-      } else {
-        // If we got no fileID, then we generate one,
-        // from the first 100 bytes of PDF
-        var data = this.stream.bytes.subarray(0, 100);
-        var hash = calculateMD5(data, 0, data.length);
-        fileID = '';
-        for (var i = 0, length = hash.length; i < length; i++) {
-          fileID += Number(hash[i]).toString(16);
-        }
+        hash = stringToBytes(xref.trailer.get('ID')[0]);
+      } else {
+        hash = calculateMD5(this.stream.bytes.subarray(0, 100), 0, 100);
+      }
+
+      for (var i = 0, n = hash.length; i < n; i++) {
+        fileID += hash[i].toString(16);
       }
 
       return shadow(this, 'fingerprint', fileID);
     },
 
     traversePages: function PDFDocument_traversePages() {
       this.catalog.traversePages();
     },
@@ -12568,16 +12562,21 @@ var ColorSpace = (function ColorSpaceClo
 
     switch (name) {
       case 'DeviceGrayCS':
         return this.singletons.gray;
       case 'DeviceRgbCS':
         return this.singletons.rgb;
       case 'DeviceCmykCS':
         return this.singletons.cmyk;
+      case 'CalGrayCS':
+        var whitePoint = IR[1].WhitePoint;
+        var blackPoint = IR[1].BlackPoint;
+        var gamma = IR[1].Gamma;
+        return new CalGrayCS(whitePoint, blackPoint, gamma);
       case 'PatternCS':
         var basePatternCS = IR[1];
         if (basePatternCS)
           basePatternCS = ColorSpace.fromIR(basePatternCS);
         return new PatternCS(basePatternCS);
       case 'IndexedCS':
         var baseIndexedCS = IR[1];
         var hiVal = IR[2];
@@ -12643,17 +12642,18 @@ var ColorSpace = (function ColorSpaceClo
           return 'DeviceGrayCS';
         case 'DeviceRGB':
         case 'RGB':
           return 'DeviceRgbCS';
         case 'DeviceCMYK':
         case 'CMYK':
           return 'DeviceCmykCS';
         case 'CalGray':
-          return 'DeviceGrayCS';
+          var params = cs[1].getAll();
+          return ['CalGrayCS', params];
         case 'CalRGB':
           return 'DeviceRgbCS';
         case 'ICCBased':
           var stream = xref.fetchIfRef(cs[1]);
           var dict = stream.dict;
           var numComps = dict.get('N');
           if (numComps == 1)
             return 'DeviceGrayCS';
@@ -13064,16 +13064,123 @@ var DeviceCmykCS = (function DeviceCmykC
     },
     usesZeroToOneRange: true
   };
 
   return DeviceCmykCS;
 })();
 
 //
+// CalGrayCS: Based on "PDF Reference, Sixth Ed", p.245
+//
+var CalGrayCS = (function CalGrayCSClosure() {
+  function CalGrayCS(whitePoint, blackPoint, gamma) {
+    this.name = 'CalGray';
+    this.numComps = 3;
+    this.defaultColor = new Float32Array([0, 0, 0]);
+
+    if (!whitePoint) {
+      error('WhitePoint missing - required for color space CalGray');
+    }
+    blackPoint = blackPoint || [0, 0, 0];
+    gamma = gamma || 1;
+
+    // Translate arguments to spec variables.
+    this.XW = whitePoint[0];
+    this.YW = whitePoint[1];
+    this.ZW = whitePoint[2];
+
+    this.XB = blackPoint[0];
+    this.YB = blackPoint[1];
+    this.ZB = blackPoint[2];
+
+    this.G = gamma;
+
+    // Validate variables as per spec.
+    if (this.XW < 0 || this.ZW < 0 || this.YW !== 1) {
+      error('Invalid WhitePoint components for ' + this.name +
+            ', no fallback available');
+    }
+
+    if (this.XB < 0 || this.YB < 0 || this.ZB < 0) {
+      info('Invalid BlackPoint for ' + this.name + ', falling back to default');
+      this.XB = this.YB = this.ZB = 0;
+    }
+
+    if (this.XB !== 0 || this.YB !== 0 || this.ZB !== 0) {
+      TODO(this.name + ', BlackPoint: XB: ' + this.XB + ', YB: ' + this.YB +
+           ', ZB: ' + this.ZB + ', only default values are supported.');
+    }
+
+    if (this.G < 1) {
+      info('Invalid Gamma: ' + this.G + ' for ' + this.name +
+           ', falling back to default');
+      this.G = 1;
+    }
+  }
+
+  CalGrayCS.prototype = {
+    getRgb: function CalGrayCS_getRgb(src, srcOffset) {
+      var rgb = new Uint8Array(3);
+      this.getRgbItem(src, srcOffset, rgb, 0);
+      return rgb;
+    },
+    getRgbItem: function CalGrayCS_getRgbItem(src, srcOffset,
+                                              dest, destOffset) {
+      // A represents a gray component of a calibrated gray space.
+      // A <---> AG in the spec
+      var A = src[srcOffset];
+      var AG = Math.pow(A, this.G);
+
+      // Computes intermediate variables M, L, N as per spec.
+      // Except if other than default BlackPoint values are used.
+      var M = this.XW * AG;
+      var L = this.YW * AG;
+      var N = this.ZW * AG;
+
+      // Decode XYZ, as per spec.
+      var X = M;
+      var Y = L;
+      var Z = N;
+
+      // http://www.poynton.com/notes/colour_and_gamma/ColorFAQ.html, Ch 4.
+      // This yields values in range [0, 100].
+      var Lstar = Math.max(116 * Math.pow(Y, 1 / 3) - 16, 0);
+
+      // Convert values to rgb range [0, 255].
+      dest[destOffset] = Lstar * 255 / 100;
+      dest[destOffset + 1] = Lstar * 255 / 100;
+      dest[destOffset + 2] = Lstar * 255 / 100;
+    },
+    getRgbBuffer: function CalGrayCS_getRgbBuffer(src, srcOffset, count,
+                                                  dest, destOffset, bits) {
+      // TODO: This part is copied from DeviceGray. Make this utility function.
+      var scale = 255 / ((1 << bits) - 1);
+      var j = srcOffset, q = destOffset;
+      for (var i = 0; i < count; ++i) {
+        var c = (scale * src[j++]) | 0;
+        dest[q++] = c;
+        dest[q++] = c;
+        dest[q++] = c;
+      }
+    },
+    getOutputLength: function CalGrayCS_getOutputLength(inputLength) {
+      return inputLength * 3;
+    },
+    isPassthrough: ColorSpace.prototype.isPassthrough,
+    createRgbBuffer: ColorSpace.prototype.createRgbBuffer,
+    isDefaultDecode: function CalGrayCS_isDefaultDecode(decodeMap) {
+      return ColorSpace.isDefaultDecode(decodeMap, this.numComps);
+    },
+    usesZeroToOneRange: true
+  };
+  return CalGrayCS;
+})();
+
+//
 // LabCS: Based on "PDF Reference, Sixth Ed", p.250
 //
 var LabCS = (function LabCSClosure() {
   function LabCS(whitePoint, blackPoint, range) {
     this.name = 'Lab';
     this.numComps = 3;
     this.defaultColor = new Float32Array([0, 0, 0]);
 
@@ -13205,16 +13312,17 @@ var LabCS = (function LabCSClosure() {
       return true;
     },
     usesZeroToOneRange: false
   };
   return LabCS;
 })();
 
 
+
 var ARCFourCipher = (function ARCFourCipherClosure() {
   function ARCFourCipher(key) {
     this.a = 0;
     this.b = 0;
     var s = new Uint8Array(256);
     var i, j = 0, tmp, keyLength = key.length;
     for (i = 0; i < 256; ++i)
       s[i] = i;
@@ -20946,28 +21054,32 @@ var CFFFont = (function CFFFontClosure()
       var cff = this.cff;
       var charsets = cff.charset.charset;
       var encoding = cff.encoding ? cff.encoding.encoding : null;
       var charstrings = [];
       var unicodeUsed = [];
       var unassignedUnicodeItems = [];
       var inverseEncoding = [];
       var gidStart = 0;
-      // Even though the CFF font may not actually be a CID font is could have
-      // CID information in the font descriptor.
-      if (this.properties.cidSystemInfo) {
-        // According to section 9.7.4.2 if the font is actually a CID font then
-        // we should use the charset to map CIDs to GIDs. If it is not actually
-        // a CID font then CIDs can be mapped directly to GIDs.
+      // According to section 9.7.4.2 CIDFontType0C glyph selection should be
+      // handled differently.
+      if (this.properties.subtype === 'CIDFontType0C') {
         if (this.cff.isCIDFont) {
+          // If the font is actually a CID font then we should use the charset
+          // to map CIDs to GIDs.
           inverseEncoding = charsets;
         } else {
-          for (var i = 0, ii = charsets.length; i < charsets.length; i++) {
+          // If it is NOT actually a CID font then CIDs should be mapped
+          // directly to GIDs.
+          inverseEncoding = [];
+          for (var i = 0, ii = cff.charStrings.count; i < ii; i++) {
             inverseEncoding.push(i);
           }
+          // Use the identity map for charsets as well.
+          charsets = inverseEncoding;
         }
       } else {
         for (var charcode in encoding) {
           var gid = encoding[charcode];
           if (gid in inverseEncoding) {
             // Glyphs can be multiply-encoded if there was an encoding
             // supplement. Convert to an array and append the charcode.
             var previousCharcode = inverseEncoding[gid];
@@ -35867,16 +35979,29 @@ var JpxImage = (function JpxImageClosure
       function transformCalculate(subbands, u0, v0) {
       var ll = subbands[0];
       for (var i = 1, ii = subbands.length, j = 1; i < ii; i += 3, j++) {
         ll = this.iterate(ll, subbands[i], subbands[i + 1],
                           subbands[i + 2], u0, v0);
       }
       return ll;
     };
+    Transform.prototype.expand = function expand(buffer, bufferPadding, step) {
+        // Section F.3.7 extending... using max extension of 4
+        var i1 = bufferPadding - 1, j1 = bufferPadding + 1;
+        var i2 = bufferPadding + step - 2, j2 = bufferPadding + step;
+        buffer[i1--] = buffer[j1++];
+        buffer[j2++] = buffer[i2--];
+        buffer[i1--] = buffer[j1++];
+        buffer[j2++] = buffer[i2--];
+        buffer[i1--] = buffer[j1++];
+        buffer[j2++] = buffer[i2--];
+        buffer[i1--] = buffer[j1++];
+        buffer[j2++] = buffer[i2--];
+    };
     Transform.prototype.iterate = function Transform_iterate(ll, hl, lh, hh,
                                                             u0, v0) {
       var llWidth = ll.width, llHeight = ll.height, llItems = ll.items;
       var hlWidth = hl.width, hlHeight = hl.height, hlItems = hl.items;
       var lhWidth = lh.width, lhHeight = lh.height, lhItems = lh.items;
       var hhWidth = hh.width, hhHeight = hh.height, hhItems = hh.items;
 
       // Section F.3.3 interleave
@@ -35920,28 +36045,17 @@ var JpxImage = (function JpxImageClosure
           continue;
         }
 
         var k = v * width;
         var l = bufferPadding;
         for (var u = 0; u < width; u++, k++, l++)
           buffer[l] = items[k];
 
-        // Section F.3.7 extending... using max extension of 4
-        var i1 = bufferPadding - 1, j1 = bufferPadding + 1;
-        var i2 = bufferPadding + width - 2, j2 = bufferPadding + width;
-        buffer[i1--] = buffer[j1++];
-        buffer[j2++] = buffer[i2--];
-        buffer[i1--] = buffer[j1++];
-        buffer[j2++] = buffer[i2--];
-        buffer[i1--] = buffer[j1++];
-        buffer[j2++] = buffer[i2--];
-        buffer[i1--] = buffer[j1++];
-        buffer[j2++] = buffer[i2--];
-
+        this.expand(buffer, bufferPadding, width);
         this.filter(buffer, bufferPadding, width, u0, bufferOut);
 
         k = v * width;
         l = bufferPadding;
         for (var u = 0; u < width; u++, k++, l++)
           items[k] = bufferOut[l];
       }
 
@@ -35955,28 +36069,17 @@ var JpxImage = (function JpxImageClosure
           continue;
         }
 
         var k = u;
         var l = bufferPadding;
         for (var v = 0; v < height; v++, k += width, l++)
           buffer[l] = items[k];
 
-        // Section F.3.7 extending... using max extension of 4
-        var i1 = bufferPadding - 1, j1 = bufferPadding + 1;
-        var i2 = bufferPadding + height - 2, j2 = bufferPadding + height;
-        buffer[i1--] = buffer[j1++];
-        buffer[j2++] = buffer[i2--];
-        buffer[i1--] = buffer[j1++];
-        buffer[j2++] = buffer[i2--];
-        buffer[i1--] = buffer[j1++];
-        buffer[j2++] = buffer[i2--];
-        buffer[i1--] = buffer[j1++];
-        buffer[j2++] = buffer[i2--];
-
+        this.expand(buffer, bufferPadding, height);
         this.filter(buffer, bufferPadding, height, v0, bufferOut);
 
         k = u;
         l = bufferPadding;
         for (var v = 0; v < height; v++, k += width, l++)
           items[k] = bufferOut[l];
       }
       return {
@@ -36619,20 +36722,16 @@ var Jbig2Image = (function Jbig2ImageClo
         for (var j = 0; j < width; j++)
           row[j] = defaultPixelValue;
       }
       bitmap.push(row);
     }
 
     var decoder = decodingContext.decoder;
     var contextCache = decodingContext.contextCache;
-
-    if (transposed)
-      error('JBIG2 error: transposed is not supported');
-
     var stripT = -decodeInteger(contextCache, 'IADT', decoder); // 6.4.6
     var firstS = 0;
     var i = 0;
     while (i < numberOfSymbolInstances) {
       var deltaT = decodeInteger(contextCache, 'IADT', decoder); // 6.4.6
       stripT += deltaT;
 
       var deltaFirstS = decodeInteger(contextCache, 'IAFS', decoder); // 6.4.7
@@ -36657,38 +36756,70 @@ var Jbig2Image = (function Jbig2ImageClo
           symbolHeight += rdh;
           symbolBitmap = decodeRefinement(symbolWidth, symbolHeight,
             refinementTemplateIndex, symbolBitmap, (rdw >> 1) + rdx,
             (rdh >> 1) + rdy, false, refinementAt,
             decodingContext);
         }
         var offsetT = t - ((referenceCorner & 1) ? 0 : symbolHeight);
         var offsetS = currentS - ((referenceCorner & 2) ? symbolWidth : 0);
-        for (var t2 = 0; t2 < symbolHeight; t2++) {
-          var row = bitmap[offsetT + t2];
-          if (!row) continue;
-          var symbolRow = symbolBitmap[t2];
-          switch (combinationOperator) {
-            case 0: // OR
-              for (var s2 = 0; s2 < symbolWidth; s2++)
-                row[offsetS + s2] |= symbolRow[s2];
-              break;
-            case 2: // XOR
-              for (var s2 = 0; s2 < symbolWidth; s2++)
-                row[offsetS + s2] ^= symbolRow[s2];
-              break;
-            default:
-              error('JBIG2 error: operator ' + combinationOperator +
-                    ' is not supported');
-          }
-        }
-
-        currentS += symbolWidth - 1;
+        if (transposed) {
+          // Place Symbol Bitmap from T1,S1  
+          for (var s2 = 0; s2 < symbolHeight; s2++) {
+            var row = bitmap[offsetS + s2];
+            if (!row) {
+              continue;
+            }
+            var symbolRow = symbolBitmap[s2];
+            // To ignore Parts of Symbol bitmap which goes
+            // outside bitmap region
+            var maxWidth = Math.min(width - offsetT, symbolWidth);
+            switch (combinationOperator) {
+              case 0: // OR
+                for (var t2 = 0; t2 < maxWidth; t2++) {
+                  row[offsetT + t2] |= symbolRow[t2];
+                }
+                break;
+              case 2: // XOR
+                for (var t2 = 0; t2 < maxWidth; t2++) {
+                  row[offsetT + t2] ^= symbolRow[t2];
+                }
+                break;
+              default:
+                error('JBIG2 error: operator ' + combinationOperator +
+                      ' is not supported');
+            }
+          }
+          currentS += symbolHeight - 1;
+        } else {
+          for (var t2 = 0; t2 < symbolHeight; t2++) {
+            var row = bitmap[offsetT + t2];
+            if (!row) {
+              continue;
+            }
+            var symbolRow = symbolBitmap[t2];
+            switch (combinationOperator) {
+              case 0: // OR
+                for (var s2 = 0; s2 < symbolWidth; s2++) {
+                  row[offsetS + s2] |= symbolRow[s2];
+                }
+                break;
+              case 2: // XOR
+                for (var s2 = 0; s2 < symbolWidth; s2++) {
+                  row[offsetS + s2] ^= symbolRow[s2];
+                }
+                break;
+              default:
+                error('JBIG2 error: operator ' + combinationOperator +
+                      ' is not supported');
+            }
+          }
+          currentS += symbolWidth - 1;
+        }
         i++;
-
         var deltaS = decodeInteger(contextCache, 'IADS', decoder); // 6.4.8
         if (deltaS === null)
           break; // OOB
         currentS += deltaS + dsOffset;
       } while (true);
     }
     return bitmap;
   }
--- a/browser/extensions/pdfjs/content/web/viewer.css
+++ b/browser/extensions/pdfjs/content/web/viewer.css
@@ -15,16 +15,18 @@
 
 * {
   padding: 0;
   margin: 0;
 }
 
 html {
   height: 100%;
+  /* Font size is needed to make the activity bar the corect size. */
+  font-size: 10px;
 }
 
 body {
   height: 100%;
   background-color: #404040;
   background-image: url(images/texture.png);
 }
 
@@ -75,17 +77,26 @@ select {
 :-moz-full-screen a:not(.internalLink) {
   display: none;
 }
 
 :fullscreen a:not(.internalLink) {
   display: none;
 }
 
-#viewerContainer.presentationControls {
+:-moz-full-screen .textLayer > div {
+  cursor: none;
+}
+
+:fullscreen .textLayer > div {
+  cursor: none;
+}
+
+#viewerContainer.presentationControls,
+#viewerContainer.presentationControls .textLayer > div {
   cursor: default;
 }
 
 /* outer/inner center provides horizontal center */
 .outerCenter {
   pointer-events: none;
   position: relative;
 }
@@ -108,16 +119,17 @@ html[dir='ltr'] .innerCenter {
 html[dir='rtl'] .innerCenter {
   float: left;
   left: -50%;
 }
 
 #outerContainer {
   width: 100%;
   height: 100%;
+  position: relative;
 }
 
 #sidebarContainer {
   position: absolute;
   top: 0;
   bottom: 0;
   width: 200px;
   visibility: hidden;
@@ -323,16 +335,17 @@ html[dir='ltr'] .secondaryToolbar {
 html[dir='rtl'] .secondaryToolbar {
   left: 4px;
 }
 
 #secondaryToolbarButtonContainer {
   max-width: 200px;
   max-height: 400px;
   overflow-y: auto;
+  margin-bottom: -4px;
 }
 
 .doorHanger,
 .doorHangerRight {
   border: 1px solid hsla(0,0%,0%,.5);
   border-radius: 2px;
   box-shadow: 0 1px 4px rgba(0, 0, 0, 0.3);
 }
@@ -798,29 +811,34 @@ html[dir="rtl"] .secondaryToolbarButton.
   content: url(images/toolbarButton-openFile.png);
 }
 
 .toolbarButton.download::before,
 .secondaryToolbarButton.download::before {
   content: url(images/toolbarButton-download.png);
 }
 
-.toolbarButton.bookmark {
+.toolbarButton.bookmark,
+.secondaryToolbarButton.bookmark {
   -moz-box-sizing: border-box;
   box-sizing: border-box;
-  margin-top: 3px;
   padding-top: 4px;
+  text-decoration: none;
+}
+.secondaryToolbarButton.bookmark {
+  padding-top: 5px;
 }
 
-#viewBookmark[href='#'] {
+.bookmark[href='#'] {
   opacity: .5;
   pointer-events: none;
 }
 
-.toolbarButton.bookmark::before {
+.toolbarButton.bookmark::before,
+.secondaryToolbarButton.bookmark::before {
   content: url(images/toolbarButton-bookmark.png);
 }
 
 #viewThumbnail.toolbarButton::before {
   content: url(images/toolbarButton-viewThumbnail.png);
 }
 
 html[dir="ltr"] #viewOutline.toolbarButton::before {
@@ -847,19 +865,21 @@ html[dir="rtl"] #viewOutline.toolbarButt
 html[dir="ltr"] .secondaryToolbarButton {
   padding-left: 24px;
   text-align: left;
 }
 html[dir="rtl"] .secondaryToolbarButton {
   padding-right: 24px;
   text-align: right;
 }
-
-#secondaryToolbarButtonContainer :last-child {
-  margin-bottom: 0;
+html[dir="ltr"] .secondaryToolbarButton.bookmark {
+  padding-left: 27px;
+}
+html[dir="rtl"] .secondaryToolbarButton.bookmark {
+  padding-right: 27px;
 }
 
 html[dir="ltr"] .secondaryToolbarButton > span {
   padding-right: 4px;
 }
 html[dir="rtl"] .secondaryToolbarButton > span {
   padding-left: 4px;
 }
@@ -966,16 +986,21 @@ html[dir='rtl'] .verticalToolbarSeparato
   top: 0;
   bottom: 0;
   padding: 10px 40px 0;
   overflow: auto;
 }
 
 .thumbnail {
   float: left;
+  margin-bottom: 5px;
+}
+
+#thumbnailView > a:last-of-type > .thumbnail {
+  margin-bottom: 10px;
 }
 
 .thumbnail:not([data-loaded]) {
   border: 1px dashed rgba(255, 255, 255, 0.5);
   margin-bottom: 10px;
 }
 
 .thumbnailImage {
--- a/browser/extensions/pdfjs/content/web/viewer.html
+++ b/browser/extensions/pdfjs/content/web/viewer.html
@@ -93,31 +93,35 @@ limitations under the License.
             <button id="secondaryPrint" class="secondaryToolbarButton print visibleMediumView" title="Print" tabindex="20" data-l10n-id="print">
               <span data-l10n-id="print_label">Print</span>
             </button>
 
             <button id="secondaryDownload" class="secondaryToolbarButton download visibleMediumView" title="Download" tabindex="21" data-l10n-id="download">
               <span data-l10n-id="download_label">Download</span>
             </button>
 
+            <a href="#" id="secondaryViewBookmark" class="secondaryToolbarButton bookmark visibleSmallView" title="Current view (copy or open in new window)" tabindex="22" data-l10n-id="bookmark">
+              <span data-l10n-id="bookmark_label">Current View</span>
+            </a>
+
             <div class="horizontalToolbarSeparator visibleLargeView"></div>
 
-            <button id="firstPage" class="secondaryToolbarButton firstPage" title="Go to First Page" tabindex="22" data-l10n-id="first_page">
+            <button id="firstPage" class="secondaryToolbarButton firstPage" title="Go to First Page" tabindex="23" data-l10n-id="first_page">
               <span data-l10n-id="first_page_label">Go to First Page</span>
             </button>
-            <button id="lastPage" class="secondaryToolbarButton lastPage" title="Go to Last Page" tabindex="23" data-l10n-id="last_page">
+            <button id="lastPage" class="secondaryToolbarButton lastPage" title="Go to Last Page" tabindex="24" data-l10n-id="last_page">
               <span data-l10n-id="last_page_label">Go to Last Page</span>
             </button>
 
             <div class="horizontalToolbarSeparator"></div>
 
-            <button id="pageRotateCw" class="secondaryToolbarButton rotateCw" title="Rotate Clockwise" tabindex="24" data-l10n-id="page_rotate_cw">
+            <button id="pageRotateCw" class="secondaryToolbarButton rotateCw" title="Rotate Clockwise" tabindex="25" data-l10n-id="page_rotate_cw">
               <span data-l10n-id="page_rotate_cw_label">Rotate Clockwise</span>
             </button>
-            <button id="pageRotateCcw" class="secondaryToolbarButton rotateCcw" title="Rotate Counterclockwise" tabindex="25" data-l10n-id="page_rotate_ccw">
+            <button id="pageRotateCcw" class="secondaryToolbarButton rotateCcw" title="Rotate Counterclockwise" tabindex="26" data-l10n-id="page_rotate_ccw">
               <span data-l10n-id="page_rotate_ccw_label">Rotate Counterclockwise</span>
             </button>
           </div>
         </div>  <!-- secondaryToolbar -->
 
         <div class="toolbar">
           <div id="toolbarContainer">
             <div id="toolbarViewer">
@@ -155,17 +159,19 @@ limitations under the License.
                 <button id="print" class="toolbarButton print hiddenMediumView" title="Print" tabindex="14" data-l10n-id="print">
                   <span data-l10n-id="print_label">Print</span>
                 </button>
 
                 <button id="download" class="toolbarButton download hiddenMediumView" title="Download" tabindex="15" data-l10n-id="download">
                   <span data-l10n-id="download_label">Download</span>
                 </button>
                 <!-- <div class="toolbarButtonSpacer"></div> -->
-                <a href="#" id="viewBookmark" class="toolbarButton bookmark hiddenSmallView" title="Current view (copy or open in new window)" tabindex="16" data-l10n-id="bookmark"><span data-l10n-id="bookmark_label">Current View</span></a>
+                <a href="#" id="viewBookmark" class="toolbarButton bookmark hiddenSmallView" title="Current view (copy or open in new window)" tabindex="16" data-l10n-id="bookmark">
+                  <span data-l10n-id="bookmark_label">Current View</span>
+                </a>
 
                 <div class="verticalToolbarSeparator hiddenSmallView"></div>
                 
                 <button id="secondaryToolbarToggle" class="toolbarButton" title="Tools" tabindex="17" data-l10n-id="tools">
                   <span data-l10n-id="tools_label">Tools</span>
                 </button> 
               </div>
               <div class="outerCenter">
--- a/browser/extensions/pdfjs/content/web/viewer.js
+++ b/browser/extensions/pdfjs/content/web/viewer.js
@@ -12,34 +12,36 @@
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
 /* globals PDFJS, PDFBug, FirefoxCom, Stats, Cache, PDFFindBar, CustomStyle,
            PDFFindController, ProgressBar, TextLayerBuilder, DownloadManager,
            getFileName, getOutputScale, scrollIntoView, getPDFFileNameFromURL,
-           PDFHistory, PageView, ThumbnailView, noContextMenuHandler,
-           SecondaryToolbar, PasswordPrompt */
+           PDFHistory, Settings, PageView, ThumbnailView, noContextMenuHandler,
+           SecondaryToolbar, PasswordPrompt, PresentationMode */
 
 'use strict';
 
 var DEFAULT_URL = 'compressed.tracemonkey-pldi-09.pdf';
 var DEFAULT_SCALE = 'auto';
 var DEFAULT_SCALE_DELTA = 1.1;
 var UNKNOWN_SCALE = 0;
 var CACHE_SIZE = 20;
 var CSS_UNITS = 96.0 / 72.0;
 var SCROLLBAR_PADDING = 40;
 var VERTICAL_PADDING = 5;
 var MIN_SCALE = 0.25;
 var MAX_SCALE = 4.0;
 var SETTINGS_MEMORY = 20;
 var SCALE_SELECT_CONTAINER_PADDING = 8;
 var SCALE_SELECT_PADDING = 22;
+var THUMBNAIL_SCROLL_MARGIN = -19;
+var USE_ONLY_CSS_ZOOM = false;
 var RenderingStates = {
   INITIAL: 0,
   RUNNING: 1,
   PAUSED: 2,
   FINISHED: 3
 };
 var FindStates = {
   FIND_FOUND: 0,
@@ -276,16 +278,18 @@ var Cache = function cacheCache(size) {
     if (data.length > size)
       data.shift().destroy();
   };
 };
 
 
 
 
+
+
 var FirefoxCom = (function FirefoxComClosure() {
   return {
     /**
      * Creates an event that the extension is listening for and will
      * synchronously respond to.
      * NOTE: It is reccomended to use request() instead since one day we may not
      * be able to synchronously reply.
      * @param {String} action The action to trigger.
@@ -364,20 +368,29 @@ var DownloadManager = (function Download
       );
     }
   };
 
   return DownloadManager;
 })();
 
 
-// Settings Manager - This is a utility for saving settings
-// First we see if localStorage is available
-// If not, we use FUEL in FF
-// Use asyncStorage for B2G
+var cache = new Cache(CACHE_SIZE);
+var currentPageNumber = 1;
+
+
+/**
+ * Settings Manager - This is a utility for saving settings.
+ *
+ * The way that settings are stored depends on how PDF.js is built,
+ * for 'node make <flag>' the following cases exist:
+ *  - FIREFOX or MOZCENTRAL - uses about:config.
+ *  - B2G                   - uses asyncStorage.
+ *  - GENERIC or CHROME     - uses localStorage, if it is available.
+ */
 var Settings = (function SettingsClosure() {
 
   function Settings(fingerprint) {
     this.fingerprint = fingerprint;
     this.initializedPromise = new PDFJS.Promise();
 
     var resolvePromise = (function settingsResolvePromise(db) {
       this.initialize(db || '{}');
@@ -387,61 +400,61 @@ var Settings = (function SettingsClosure
 
     resolvePromise(FirefoxCom.requestSync('getDatabase', null));
 
   }
 
   Settings.prototype = {
     initialize: function settingsInitialize(database) {
       database = JSON.parse(database);
-      if (!('files' in database))
+      if (!('files' in database)) {
         database.files = [];
-      if (database.files.length >= SETTINGS_MEMORY)
+      }
+      if (database.files.length >= SETTINGS_MEMORY) {
         database.files.shift();
+      }
       var index;
       for (var i = 0, length = database.files.length; i < length; i++) {
         var branch = database.files[i];
-        if (branch.fingerprint == this.fingerprint) {
+        if (branch.fingerprint === this.fingerprint) {
           index = i;
           break;
         }
       }
-      if (typeof index != 'number')
+      if (typeof index !== 'number') {
         index = database.files.push({fingerprint: this.fingerprint}) - 1;
+      }
       this.file = database.files[index];
       this.database = database;
     },
 
     set: function settingsSet(name, val) {
-      if (!this.initializedPromise.isResolved)
+      if (!this.initializedPromise.isResolved) {
         return;
-
+      }
       var file = this.file;
       file[name] = val;
       var database = JSON.stringify(this.database);
 
 
       FirefoxCom.requestSync('setDatabase', database);
 
     },
 
     get: function settingsGet(name, defaultValue) {
-      if (!this.initializedPromise.isResolved)
+      if (!this.initializedPromise.isResolved) {
         return defaultValue;
-
+      }
       return this.file[name] || defaultValue;
     }
   };
 
   return Settings;
 })();
 
-var cache = new Cache(CACHE_SIZE);
-var currentPageNumber = 1;
-
 
 /* globals PDFFindController, FindStates, mozL10n */
 
 /**
  * Creates a "search bar" given set of DOM elements
  * that act as controls for searching, or for setting
  * search preferences in the UI. This object also sets
  * up the appropriate events for the controls. Actual
@@ -979,17 +992,18 @@ var PDFHistory = {
     } else {
       // This corresponds to the loading of a new document.
       if (state && state.fingerprint &&
           this.fingerprint !== state.fingerprint) {
         // Reinitialize the browsing history when a new document
         // is opened in the web viewer.
         this.reInitialized = true;
       }
-      window.history.replaceState({ fingerprint: this.fingerprint }, '');
+      window.history.replaceState({ fingerprint: this.fingerprint }, '',
+          document.URL);
     }
 
     var self = this;
     window.addEventListener('popstate', function pdfHistoryPopstate(evt) {
       evt.preventDefault();
       evt.stopPropagation();
 
       if (!self.historyUnlocked) {
@@ -1178,17 +1192,17 @@ var PDFHistory = {
     } else if (this.current.page || onlyCheckPage) {
       if (this.previousPage === this.currentPage) {
         return null;
       }
     } else {
       return null;
     }
     var params = { hash: this.currentBookmark, page: this.currentPage };
-    if (PDFView.isPresentationMode) {
+    if (PresentationMode.active) {
       params.hash = null;
     }
     return params;
   },
 
   _stateObj: function pdfHistory_stateObj(params) {
     return { fingerprint: this.fingerprint, uid: this.uid, target: params };
   },
@@ -1205,19 +1219,19 @@ var PDFHistory = {
       var previousParams = this._getPreviousParams();
       if (previousParams) {
         var replacePrevious = (!this.current.dest &&
                                this.current.hash !== this.previousHash);
         this._pushToHistory(previousParams, false, replacePrevious);
       }
     }
     if (overwrite || this.uid === 0) {
-      window.history.replaceState(this._stateObj(params), '');
+      window.history.replaceState(this._stateObj(params), '', document.URL);
     } else {
-      window.history.pushState(this._stateObj(params), '');
+      window.history.pushState(this._stateObj(params), '', document.URL);
     }
     this.currentUid = this.uid++;
     this.current = params;
     this.updatePreviousBookmark = true;
   },
 
   _goTo: function pdfHistory_goTo(state) {
     if (!(this.initialized && this.historyUnlocked &&
@@ -1280,51 +1294,53 @@ var PDFHistory = {
 
 var SecondaryToolbar = {
   opened: false,
   previousContainerHeight: null,
   newContainerHeight: null,
 
   initialize: function secondaryToolbarInitialize(options) {
     this.toolbar = options.toolbar;
-    this.toggleButton = options.toggleButton;
-
     this.buttonContainer = this.toolbar.firstElementChild;
 
     // Define the toolbar buttons.
+    this.toggleButton = options.toggleButton;
     this.presentationMode = options.presentationMode;
     this.openFile = options.openFile;
     this.print = options.print;
     this.download = options.download;
     this.firstPage = options.firstPage;
     this.lastPage = options.lastPage;
     this.pageRotateCw = options.pageRotateCw;
     this.pageRotateCcw = options.pageRotateCcw;
 
     // Attach the event listeners.
-    this.toggleButton.addEventListener('click', this.toggle.bind(this));
-
-    this.presentationMode.addEventListener('click',
-      this.presentationModeClick.bind(this));
-    this.openFile.addEventListener('click', this.openFileClick.bind(this));
-    this.print.addEventListener('click', this.printClick.bind(this));
-    this.download.addEventListener('click', this.downloadClick.bind(this));
-
-    this.firstPage.addEventListener('click', this.firstPageClick.bind(this));
-    this.lastPage.addEventListener('click', this.lastPageClick.bind(this));
-
-    this.pageRotateCw.addEventListener('click',
-      this.pageRotateCwClick.bind(this));
-    this.pageRotateCcw.addEventListener('click',
-      this.pageRotateCcwClick.bind(this));
+    var elements = [
+      { element: this.toggleButton, handler: this.toggle },
+      { element: this.presentationMode, handler: this.presentationModeClick },
+      { element: this.openFile, handler: this.openFileClick },
+      { element: this.print, handler: this.printClick },
+      { element: this.download, handler: this.downloadClick },
+      { element: this.firstPage, handler: this.firstPageClick },
+      { element: this.lastPage, handler: this.lastPageClick },
+      { element: this.pageRotateCw, handler: this.pageRotateCwClick },
+      { element: this.pageRotateCcw, handler: this.pageRotateCcwClick }
+    ];
+
+    for (var item in elements) {
+      var element = elements[item].element;
+      if (element) {
+        element.addEventListener('click', elements[item].handler.bind(this));
+      }
+    }
   },
 
   // Event handling functions.
   presentationModeClick: function secondaryToolbarPresentationModeClick(evt) {
-    PDFView.presentationMode();
+    PresentationMode.request();
     this.close();
   },
 
   openFileClick: function secondaryToolbarOpenFileClick(evt) {
     document.getElementById('fileInput').click();
     this.close(evt.target);
   },
 
@@ -1473,32 +1489,203 @@ var PasswordPrompt = {
     if (password && password.length > 0) {
       this.hide();
       return this.updatePassword(password);
     }
   }
 };
 
 
+var DELAY_BEFORE_HIDING_CONTROLS = 3000; // in ms
+var SELECTOR = 'presentationControls';
+
+var PresentationMode = {
+  active: false,
+  args: null,
+  contextMenuOpen: false,
+
+  initialize: function presentationModeInitialize(options) {
+    this.container = options.container;
+    this.secondaryToolbar = options.secondaryToolbar;
+
+    this.firstPage = options.firstPage;
+    this.lastPage = options.lastPage;
+    this.pageRotateCw = options.pageRotateCw;
+    this.pageRotateCcw = options.pageRotateCcw;
+
+    this.firstPage.addEventListener('click', function() {
+      this.contextMenuOpen = false;
+      this.secondaryToolbar.firstPageClick();
+    }.bind(this));
+    this.lastPage.addEventListener('click', function() {
+      this.contextMenuOpen = false;
+      this.secondaryToolbar.lastPageClick();
+    }.bind(this));
+
+    this.pageRotateCw.addEventListener('click', function() {
+      this.contextMenuOpen = false;
+      this.secondaryToolbar.pageRotateCwClick();
+    }.bind(this));
+    this.pageRotateCcw.addEventListener('click', function() {
+      this.contextMenuOpen = false;
+      this.secondaryToolbar.pageRotateCcwClick();
+    }.bind(this));
+  },
+
+  get isFullscreen() {
+    return (document.fullscreenElement ||
+            document.mozFullScreen ||
+            document.webkitIsFullScreen ||
+            document.msFullscreenElement);
+  },
+
+  request: function presentationModeRequest() {
+    if (!PDFView.supportsFullscreen || this.isFullscreen) {
+      return false;
+    }
+
+    if (this.container.requestFullscreen) {
+      this.container.requestFullscreen();
+    } else if (this.container.mozRequestFullScreen) {
+      this.container.mozRequestFullScreen();
+    } else if (this.container.webkitRequestFullScreen) {
+      this.container.webkitRequestFullScreen(Element.ALLOW_KEYBOARD_INPUT);
+    } else if (this.container.msRequestFullscreen) {
+      this.container.msRequestFullscreen();
+    } else {
+      return false;
+    }
+
+    this.args = {
+      page: PDFView.page,
+      previousScale: PDFView.currentScaleValue
+    };
+
+    return true;
+  },
+
+  enter: function presentationModeEnter() {
+    this.active = true;
+
+    PDFView.page = this.args.page;
+    PDFView.setScale('page-fit', true);
+
+    window.addEventListener('mousemove', this.mouseMove, false);
+    window.addEventListener('mousedown', this.mouseDown, false);
+    window.addEventListener('contextmenu', this.contextMenu, false);
+
+    this.showControls();
+    this.contextMenuOpen = false;
+    this.container.setAttribute('contextmenu', 'viewerContextMenu');
+  },
+
+  exit: function presentationModeExit() {
+    this.active = false;
+
+    var page = PDFView.page;
+    PDFView.setScale(this.args.previousScale);
+    PDFView.page = page;
+
+    window.removeEventListener('mousemove', this.mouseMove, false);
+    window.removeEventListener('mousedown', this.mouseDown, false);
+    window.removeEventListener('contextmenu', this.contextMenu, false);
+
+    this.hideControls();
+    this.args = null;
+    PDFView.clearMouseScrollState();
+    this.container.removeAttribute('contextmenu');
+    this.contextMenuOpen = false;
+
+    // Ensure that the thumbnail of the current page is visible
+    // when exiting presentation mode.
+    scrollIntoView(document.getElementById('thumbnailContainer' + page));
+  },
+
+  showControls: function presentationModeShowControls() {
+    if (this.controlsTimeout) {
+      clearTimeout(this.controlsTimeout);
+    } else {
+      this.container.classList.add(SELECTOR);
+    }
+    this.controlsTimeout = setTimeout(function hideControlsTimeout() {
+      this.container.classList.remove(SELECTOR);
+      delete this.controlsTimeout;
+    }.bind(this), DELAY_BEFORE_HIDING_CONTROLS);
+  },
+
+  hideControls: function presentationModeHideControls() {
+    if (!this.controlsTimeout) {
+      return;
+    }
+    this.container.classList.remove(SELECTOR);
+    clearTimeout(this.controlsTimeout);
+    delete this.controlsTimeout;
+  },
+
+  mouseMove: function presentationModeMouseMove(evt) {
+    PresentationMode.showControls();
+  },
+
+  mouseDown: function presentationModeMouseDown(evt) {
+    var self = PresentationMode;
+    if (self.contextMenuOpen) {
+      self.contextMenuOpen = false;
+      evt.preventDefault();
+      return;
+    }
+
+    if (evt.button === 0) {
+      // Enable clicking of links in presentation mode. Please note:
+      // Only links pointing to destinations in the current PDF document work.
+      var isInternalLink = (evt.target.href &&
+                            evt.target.classList.contains('internalLink'));
+      if (!isInternalLink) {
+        // Unless an internal link was clicked, advance one page.
+        evt.preventDefault();
+        PDFView.page += (evt.shiftKey ? -1 : 1);
+      }
+    }
+  },
+
+  contextMenu: function presentationModeContextMenu(evt) {
+    PresentationMode.contextMenuOpen = true;
+  }
+};
+
+(function presentationModeClosure() {
+  function presentationModeChange(e) {
+    if (PresentationMode.isFullscreen) {
+      PresentationMode.enter();
+    } else {
+      PresentationMode.exit();
+    }
+  }
+
+  window.addEventListener('fullscreenchange', presentationModeChange, false);
+  window.addEventListener('mozfullscreenchange', presentationModeChange, false);
+  window.addEventListener('webkitfullscreenchange', presentationModeChange,
+                          false);
+  window.addEventListener('MSFullscreenChange', presentationModeChange, false);
+})();
+
+
 var PDFView = {
   pages: [],
   thumbnails: [],
   currentScale: UNKNOWN_SCALE,
   currentScaleValue: null,
   initialBookmark: document.location.hash.substring(1),
   container: null,
   thumbnailContainer: null,
   initialized: false,
   fellback: false,
   pdfDocument: null,
   sidebarOpen: false,
   pageViewScroll: null,
   thumbnailViewScroll: null,
-  isPresentationMode: false,
-  presentationModeArgs: null,
   pageRotation: 0,
   mouseScrollTimeStamp: 0,
   mouseScrollDelta: 0,
   lastScroll: 0,
   previousPageNumber: 1,
   isViewerEmbedded: (window.parent !== window),
 
   // called once when the document is loaded
@@ -1509,16 +1696,33 @@ var PDFView = {
     this.watchScroll(container, this.pageViewScroll, updateViewarea);
 
     var thumbnailContainer = this.thumbnailContainer =
                              document.getElementById('thumbnailView');
     this.thumbnailViewScroll = {};
     this.watchScroll(thumbnailContainer, this.thumbnailViewScroll,
                      this.renderHighestPriority.bind(this));
 
+    PDFFindBar.initialize({
+      bar: document.getElementById('findbar'),
+      toggleButton: document.getElementById('viewFind'),
+      findField: document.getElementById('findInput'),
+      highlightAllCheckbox: document.getElementById('findHighlightAll'),
+      caseSensitiveCheckbox: document.getElementById('findMatchCase'),
+      findMsg: document.getElementById('findMsg'),
+      findStatusIcon: document.getElementById('findStatusIcon'),
+      findPreviousButton: document.getElementById('findPrevious'),
+      findNextButton: document.getElementById('findNext')
+    });
+
+    PDFFindController.initialize({
+      pdfPageSource: this,
+      integratedFind: this.supportsIntegratedFind
+    });
+
     SecondaryToolbar.initialize({
       toolbar: document.getElementById('secondaryToolbar'),
       toggleButton: document.getElementById('secondaryToolbarToggle'),
       presentationMode: document.getElementById('secondaryPresentationMode'),
       openFile: document.getElementById('secondaryOpenFile'),
       print: document.getElementById('secondaryPrint'),
       download: document.getElementById('secondaryDownload'),
       firstPage: document.getElementById('firstPage'),
@@ -1530,31 +1734,23 @@ var PDFView = {
     PasswordPrompt.initialize({
       overlayContainer: document.getElementById('overlayContainer'),
       passwordField: document.getElementById('password'),
       passwordText: document.getElementById('passwordText'),
       passwordSubmit: document.getElementById('passwordSubmit'),
       passwordCancel: document.getElementById('passwordCancel')
     });
 
-    PDFFindBar.initialize({
-      bar: document.getElementById('findbar'),
-      toggleButton: document.getElementById('viewFind'),
-      findField: document.getElementById('findInput'),
-      highlightAllCheckbox: document.getElementById('findHighlightAll'),
-      caseSensitiveCheckbox: document.getElementById('findMatchCase'),
-      findMsg: document.getElementById('findMsg'),
-      findStatusIcon: document.getElementById('findStatusIcon'),
-      findPreviousButton: document.getElementById('findPrevious'),
-      findNextButton: document.getElementById('findNext')
-    });
-
-    PDFFindController.initialize({
-      pdfPageSource: this,
-      integratedFind: this.supportsIntegratedFind
+    PresentationMode.initialize({
+      container: container,
+      secondaryToolbar: SecondaryToolbar,
+      firstPage: document.getElementById('contextFirstPage'),
+      lastPage: document.getElementById('contextLastPage'),
+      pageRotateCw: document.getElementById('contextPageRotateCw'),
+      pageRotateCcw: document.getElementById('contextPageRotateCcw')
     });
 
     this.initialized = true;
     container.addEventListener('scroll', function() {
       self.lastScroll = Date.now();
     }, false);
   },
 
@@ -1575,96 +1771,97 @@ var PDFView = {
       else if (currentY < lastY)
         state.down = false;
       // else do nothing and use previous value
       state.lastY = currentY;
       callback();
     }, true);
   },
 
-  setScale: function pdfViewSetScale(val, resetAutoSettings, noScroll) {
-    if (val == this.currentScale)
+  setScale: function pdfViewSetScale(value, resetAutoSettings, noScroll) {
+    if (value === 'custom') {
       return;
-
+    }
     var pages = this.pages;
-    for (var i = 0; i < pages.length; i++)
-      pages[i].update(val * CSS_UNITS);
-
-    if (!noScroll && this.currentScale != val)
-      this.pages[this.page - 1].scrollIntoView();
-    this.currentScale = val;
+    var currentPage = pages[this.page - 1];
+    var number = parseFloat(value);
+    var scale;
+
+    if (number) {
+      scale = number;
+      resetAutoSettings = true;
+    } else {
+      if (!currentPage) {
+        return;
+      }
+      var pageWidthScale = (this.container.clientWidth - SCROLLBAR_PADDING) /
+                            currentPage.width * currentPage.scale;
+      var pageHeightScale = (this.container.clientHeight - VERTICAL_PADDING) /
+                             currentPage.height * currentPage.scale;
+      switch (value) {
+        case 'page-actual':
+          scale = 1;
+          break;
+        case 'page-width':
+          scale = pageWidthScale;
+          break;
+        case 'page-height':
+          scale = pageHeightScale;
+          break;
+        case 'page-fit':
+          scale = Math.min(pageWidthScale, pageHeightScale);
+          break;
+        case 'auto':
+          scale = Math.min(1.0, pageWidthScale);
+          break;
+      }
+    }
+    this.currentScaleValue = value;
+
+    if (scale === this.currentScale) {
+      return;
+    }
+    for (var i = 0, ii = pages.length; i < ii; i++) {
+      pages[i].update(scale);
+    }
+    this.currentScale = scale;
+
+    if (!noScroll) {
+      currentPage.scrollIntoView();
+    }
 
     var event = document.createEvent('UIEvents');
     event.initUIEvent('scalechange', false, false, window, 0);
-    event.scale = val;
+    event.scale = scale;
     event.resetAutoSettings = resetAutoSettings;
     window.dispatchEvent(event);
-  },
-
-  parseScale: function pdfViewParseScale(value, resetAutoSettings, noScroll) {
-    if ('custom' == value)
-      return;
-
-    var scale = parseFloat(value);
-    this.currentScaleValue = value;
-    if (scale) {
-      this.setScale(scale, true, noScroll);
-      return;
-    }
-
-    var container = this.container;
-    var currentPage = this.pages[this.page - 1];
-    if (!currentPage) {
-      return;
+
+    if (!number) {
+      selectScaleOption(value);
     }
-
-    var pageWidthScale = (container.clientWidth - SCROLLBAR_PADDING) /
-                          currentPage.width * currentPage.scale / CSS_UNITS;
-    var pageHeightScale = (container.clientHeight - VERTICAL_PADDING) /
-                           currentPage.height * currentPage.scale / CSS_UNITS;
-    switch (value) {
-      case 'page-actual':
-        scale = 1;
-        break;
-      case 'page-width':
-        scale = pageWidthScale;
-        break;
-      case 'page-height':
-        scale = pageHeightScale;
-        break;
-      case 'page-fit':
-        scale = Math.min(pageWidthScale, pageHeightScale);
-        break;
-      case 'auto':
-        scale = Math.min(1.0, pageWidthScale);
-        break;
-    }
-    this.setScale(scale, resetAutoSettings, noScroll);
-
-    selectScaleOption(value);
   },
 
   zoomIn: function pdfViewZoomIn(ticks) {
     var newScale = this.currentScale;
     do {
       newScale = (newScale * DEFAULT_SCALE_DELTA).toFixed(2);
       newScale = Math.ceil(newScale * 10) / 10;
       newScale = Math.min(MAX_SCALE, newScale);
     } while (--ticks && newScale < MAX_SCALE);
-    this.parseScale(newScale, true);
+    this.setScale(newScale, true);
   },
 
   zoomOut: function pdfViewZoomOut(ticks) {
     var newScale = this.currentScale;
     do {
       newScale = (newScale / DEFAULT_SCALE_DELTA).toFixed(2);
       newScale = Math.floor(newScale * 10) / 10;
       newScale = Math.max(MIN_SCALE, newScale);
     } while (--ticks && newScale > MIN_SCALE);
-    this.parseScale(newScale, true);
+    this.setScale(newScale, true);
   },
 
   set page(val) {
     var pages = this.pages;
     var input = document.getElementById('pageNumber');
     var event = document.createEvent('UIEvents');
     event.initUIEvent('pagechange', false, false, window, 0);
 
@@ -2026,25 +2223,24 @@ var PDFView = {
         }
         return pdfOpenParams;
       }
     }
     return '';
   },
 
   /**
-   * For the firefox extension we prefix the full url on anchor links so they
-   * don't come up as resource:// urls and so open in new tab/window works.
-   * @param {String} anchor The anchor hash include the #.
+   * Prefix the full url on anchor links to make sure that links are resolved
+   * relative to the current URL instead of the one defined in <base href>.
+   * @param {String} anchor The anchor hash, including the #.
    */
   getAnchorUrl: function getAnchorUrl(anchor) {
     return this.url.split('#')[0] + anchor;
   },
 
-
   /**
    * Show the error box.
    * @param {String} message A message that is human readable.
    * @param {Object} moreInfo (optional) Further information about the error
    *                            that is more technical.  Should have a 'message'
    *                            and optionally a 'stack' property.
    */
   error: function pdfViewError(message, moreInfo) {
@@ -2141,17 +2337,17 @@ var PDFView = {
     var pagesPromise = this.pagesPromise = new PDFJS.Promise();
     var self = this;
 
     var firstPagePromise = pdfDocument.getPage(1);
 
     // Fetch a single page so we can get a viewport that will be the default
     // viewport for all pages
     firstPagePromise.then(function(pdfPage) {
-      var viewport = pdfPage.getViewport(scale || 1.0);
+      var viewport = pdfPage.getViewport((scale || 1.0) * CSS_UNITS);
       var pagePromises = [];
       for (var pageNum = 1; pageNum <= pagesCount; ++pageNum) {
         var viewportClone = viewport.clone();
         var pageView = new PageView(container, pageNum, scale,
                                     self.navigateTo.bind(self),
                                     viewportClone);
         var thumbnailView = new ThumbnailView(thumbsView, pageNum,
                                               viewportClone);
@@ -2333,24 +2529,24 @@ var PDFView = {
       PDFHistory.initialDestination = null;
     } else if (this.initialBookmark) {
       this.setHash(this.initialBookmark);
       PDFHistory.push({ hash: this.initialBookmark }, !!this.initialBookmark);
       this.initialBookmark = null;
     } else if (storedHash) {
       this.setHash(storedHash);
     } else if (scale) {
-      this.parseScale(scale, true);
+      this.setScale(scale, true);
       this.page = 1;
     }
 
     if (PDFView.currentScale === UNKNOWN_SCALE) {
       // Scale was not initialized: invalid bookmark or scale was not specified.
       // Setting the default one.
-      this.parseScale(DEFAULT_SCALE, true);
+      this.setScale(DEFAULT_SCALE, true);
     }
   },
 
   renderHighestPriority: function pdfViewRenderHighestPriority() {
     // Pages have a higher priority than thumbnails, so check them first.
     var visiblePages = this.getVisiblePages();
     var pageView = this.getHighestPriority(visiblePages, this.pages,
                                            this.pageViewScroll.down);
@@ -2521,17 +2717,17 @@ var PDFView = {
         if (outlineButton.getAttribute('disabled'))
           return;
         break;
     }
   },
 
   getVisiblePages: function pdfViewGetVisiblePages() {
     return this.getVisibleElements(this.container, this.pages,
-                                   !this.isPresentationMode);
+                                   !PresentationMode.active);
   },
 
   getVisibleThumbs: function pdfViewGetVisibleThumbs() {
     return this.getVisibleElements(this.thumbnailContainer, this.thumbnails);
   },
 
   // Generic helper to find out what elements are visible within a scroll pane.
   getVisibleElements: function pdfViewGetVisibleElements(
@@ -2627,116 +2823,31 @@ var PDFView = {
   },
 
   afterPrint: function pdfViewSetupAfterPrint() {
     var div = document.getElementById('printContainer');
     while (div.hasChildNodes())
       div.removeChild(div.lastChild);
   },
 
-  presentationMode: function pdfViewPresentationMode() {
-    if (!this.supportsFullscreen) {
-      return false;
-    }
-    var isPresentationMode = document.fullscreenElement ||
-                             document.mozFullScreen ||
-                             document.webkitIsFullScreen ||
-                             document.msFullscreenElement;
-
-    if (isPresentationMode) {
-      return false;
-    }
-
-    var wrapper = document.getElementById('viewerContainer');
-    if (document.documentElement.requestFullscreen) {
-      wrapper.requestFullscreen();
-    } else if (document.documentElement.mozRequestFullScreen) {
-      wrapper.mozRequestFullScreen();
-    } else if (document.documentElement.webkitRequestFullScreen) {
-      wrapper.webkitRequestFullScreen(Element.ALLOW_KEYBOARD_INPUT);
-    } else if (document.documentElement.msRequestFullscreen) {
-      wrapper.msRequestFullscreen();
-    } else {
-      return false;
-    }
-
-    this.presentationModeArgs = {
-      page: this.page,
-      previousScale: this.currentScaleValue
-    };
-
-    return true;
-  },
-
-  enterPresentationMode: function pdfViewEnterPresentationMode() {
-    this.isPresentationMode = true;
-    this.page = this.presentationModeArgs.page;
-    this.parseScale('page-fit', true);
-    this.showPresentationControls();
-
-    var viewer = document.getElementById('viewer');
-    viewer.setAttribute('contextmenu', 'viewerContextMenu');
-  },
-
-  exitPresentationMode: function pdfViewExitPresentationMode() {
-    this.isPresentationMode = false;
-    this.parseScale(this.presentationModeArgs.previousScale);
-    this.page = this.page;
-    this.clearMouseScrollState();
-    this.hidePresentationControls();
-    this.presentationModeArgs = null;
-
-    var viewer = document.getElementById('viewer');
-    viewer.removeAttribute('contextmenu');
-
-    // Ensure that the thumbnail of the current page is visible
-    // when exiting presentation mode.
-    scrollIntoView(document.getElementById('thumbnailContainer' + this.page));
-  },
-
-  showPresentationControls: function pdfViewShowPresentationControls() {
-    var DELAY_BEFORE_HIDING_CONTROLS = 3000;
-    var wrapper = document.getElementById('viewerContainer');
-    if (this.presentationControlsTimeout) {
-      clearTimeout(this.presentationControlsTimeout);
-    } else {
-      wrapper.classList.add('presentationControls');
-    }
-    this.presentationControlsTimeout = setTimeout(function hideControls() {
-      wrapper.classList.remove('presentationControls');
-      delete PDFView.presentationControlsTimeout;
-    }, DELAY_BEFORE_HIDING_CONTROLS);
-  },
-
-  hidePresentationControls: function pdfViewShowPresentationControls() {
-    if (!this.presentationControlsTimeout) {
-      return;
-    }
-    clearTimeout(this.presentationControlsTimeout);
-    delete this.presentationControlsTimeout;
-
-    var wrapper = document.getElementById('viewerContainer');
-    wrapper.classList.remove('presentationControls');
-  },
-
   rotatePages: function pdfViewPageRotation(delta) {
 
     this.pageRotation = (this.pageRotation + 360 + delta) % 360;
 
     for (var i = 0, l = this.pages.length; i < l; i++) {
       var page = this.pages[i];
       page.update(page.scale, this.pageRotation);
     }
 
     for (var i = 0, l = this.thumbnails.length; i < l; i++) {
       var thumb = this.thumbnails[i];
       thumb.update(this.pageRotation);
     }
 
-    this.parseScale(this.currentScaleValue, true);
+    this.setScale(this.currentScaleValue, true);
 
     this.renderHighestPriority();
 
     var currentPage = this.pages[this.page - 1];
     if (!currentPage) {
       return;
     }
 
@@ -2823,16 +2934,18 @@ var PageView = function pageView(contain
   this.pdfPageRotate = defaultViewport.rotate;
 
   this.renderingState = RenderingStates.INITIAL;
   this.resume = null;
 
   this.textContent = null;
   this.textLayer = null;
 
+  this.zoomLayer = null;
+
   this.annotationLayer = null;
 
   var anchor = document.createElement('a');
   anchor.name = '' + this.id;
 
   var div = this.el = document.createElement('div');
   div.id = 'pageContainer' + this.id;
   div.className = 'page';
@@ -2840,64 +2953,147 @@ var PageView = function pageView(contain
   div.style.height = Math.floor(this.viewport.height) + 'px';
 
   container.appendChild(anchor);
   container.appendChild(div);
 
   this.setPdfPage = function pageViewSetPdfPage(pdfPage) {
     this.pdfPage = pdfPage;
     this.pdfPageRotate = pdfPage.rotate;
-    this.viewport = pdfPage.getViewport(this.scale);
+    this.viewport = pdfPage.getViewport(this.scale * CSS_UNITS);
     this.stats = pdfPage.stats;
-    this.update();
+    this.reset();
   };
 
   this.destroy = function pageViewDestroy() {
-    this.update();
+    this.zoomLayer = null;
+    this.reset();
     if (this.pdfPage) {
       this.pdfPage.destroy();
     }
   };
 
-  this.update = function pageViewUpdate(scale, rotation) {
+  this.reset = function pageViewReset() {
     if (this.renderTask) {
       this.renderTask.cancel();
     }
     this.resume = null;
     this.renderingState = RenderingStates.INITIAL;
 
-    if (typeof rotation !== 'undefined') {
-      this.rotation = rotation;
-    }
-
-    this.scale = scale || this.scale;
-
-    var totalRotation = (this.rotation + this.pdfPageRotate) % 360;
-    this.viewport = this.viewport.clone({
-      scale: this.scale,
-      rotation: totalRotation
-    });
-
     div.style.width = Math.floor(this.viewport.width) + 'px';
     div.style.height = Math.floor(this.viewport.height) + 'px';
 
-    while (div.hasChildNodes()) {
-      div.removeChild(div.lastChild);
+    var childNodes = div.childNodes;
+    for (var i = div.childNodes.length - 1; i >= 0; i--) {
+      var node = childNodes[i];
+      if (this.zoomLayer && this.zoomLayer === node) {
+        continue;
+      }
+      div.removeChild(node);
     }
     div.removeAttribute('data-loaded');
 
     this.annotationLayer = null;
 
     delete this.canvas;
 
     this.loadingIconDiv = document.createElement('div');
     this.loadingIconDiv.className = 'loadingIcon';
     div.appendChild(this.loadingIconDiv);
   };
 
+  this.update = function pageViewUpdate(scale, rotation) {
+    this.scale = scale || this.scale;
+
+    if (typeof rotation !== 'undefined') {
+      this.rotation = rotation;
+    }
+
+    var totalRotation = (this.rotation + this.pdfPageRotate) % 360;
+    this.viewport = this.viewport.clone({
+      scale: this.scale * CSS_UNITS,
+      rotation: totalRotation
+    });
+
+    if (USE_ONLY_CSS_ZOOM && this.canvas) {
+      this.cssTransform(this.canvas);
+      return;
+    } else if (this.canvas && !this.zoomLayer) {
+      this.zoomLayer = this.canvas.parentNode;
+      this.zoomLayer.style.position = 'absolute';
+    }
+    if (this.zoomLayer) {
+      this.cssTransform(this.zoomLayer.firstChild);
+    }
+    this.reset();
+  };
+
+  this.cssTransform = function pageCssTransform(canvas) {
+    // Scale canvas, canvas wrapper, and page container.
+    var width = this.viewport.width;
+    var height = this.viewport.height;
+    canvas.style.width = canvas.parentNode.style.width = div.style.width =
+        Math.floor(width) + 'px';
+    canvas.style.height = canvas.parentNode.style.height = div.style.height =
+        Math.floor(height) + 'px';
+    // The canvas may have been originally rotated, so rotate relative to that.
+    var relativeRotation = this.viewport.rotation - canvas._viewport.rotation;
+    var absRotation = Math.abs(relativeRotation);
+    var scaleX = 1, scaleY = 1;
+    if (absRotation === 90 || absRotation === 270) {
+      // Scale x and y because of the rotation.
+      scaleX = height / width;
+      scaleY = width / height;
+    }
+    var cssTransform = 'rotate(' + relativeRotation + 'deg) ' +
+                       'scale(' + scaleX + ',' + scaleY + ')';
+    CustomStyle.setProp('transform', canvas, cssTransform);
+
+    if (this.textLayer) {
+      // Rotating the text layer is more complicated since the divs inside the
+      // the text layer are rotated.
+      // TODO: This could probably be simplified by drawing the text layer in
+      // one orientation then rotating overall.
+      var textRelativeRotation = this.viewport.rotation -
+                                 this.textLayer.viewport.rotation;
+      var textAbsRotation = Math.abs(textRelativeRotation);
+      var scale = (width / canvas.width);
+      if (textAbsRotation === 90 || textAbsRotation === 270) {
+        scale = width / canvas.height;
+      }
+      var textLayerDiv = this.textLayer.textLayerDiv;
+      var transX, transY;
+      switch (textAbsRotation) {
+        case 0:
+          transX = transY = 0;
+          break;
+        case 90:
+          transX = 0;
+          transY = '-' + textLayerDiv.style.height;
+          break;
+        case 180:
+          transX = '-' + textLayerDiv.style.width;
+          transY = '-' + textLayerDiv.style.height;
+          break;
+        case 270:
+          transX = '-' + textLayerDiv.style.width;
+          transY = 0;
+          break;
+        default:
+          console.error('Bad rotation value.');
+          break;
+      }
+      CustomStyle.setProp('transform', textLayerDiv,
+                          'rotate(' + textAbsRotation + 'deg) ' +
+                            'scale(' + scale + ', ' + scale + ') ' +
+                            'translate(' + transX + ', ' + transY + ')');
+      CustomStyle.setProp('transformOrigin', textLayerDiv, '0% 0%');
+    }
+  };
+
   Object.defineProperty(this, 'width', {
     get: function PageView_getWidth() {
       return this.viewport.width;
     },
     enumerable: true
   });
 
   Object.defineProperty(this, 'height', {
@@ -2914,17 +3110,19 @@ var PageView = function pageView(contain
     function bindLink(link, dest) {
       link.href = PDFView.getDestinationHash(dest);
       link.onclick = function pageViewSetupLinksOnclick() {
         if (dest) {
           PDFView.navigateTo(dest);
         }
         return false;
       };
-      link.className = 'internalLink';
+      if (dest) {
+        link.className = 'internalLink';
+      }
     }
 
     function bindNamedAction(link, action) {
       link.href = PDFView.getAnchorUrl('');
       link.onclick = function pageViewSetupNamedActionOnClick() {
         // See PDF reference, table 8.45 - Named action
         switch (action) {
           case 'GoToPage':
@@ -3025,17 +3223,17 @@ var PageView = function pageView(contain
     });
   }
 
   this.getPagePoint = function pageViewGetPagePoint(x, y) {
     return this.viewport.convertToPdfPoint(x, y);
   };
 
   this.scrollIntoView = function pageViewScrollIntoView(dest) {
-    if (PDFView.isPresentationMode) { // Avoid breaking presentation mode.
+    if (PresentationMode.active) { // Avoid breaking presentation mode.
       dest = null;
     }
     if (!dest) {
       scrollIntoView(div);
       return;
     }
 
     var x = 0, y = 0;
@@ -3078,19 +3276,19 @@ var PageView = function pageView(contain
           height / CSS_UNITS;
         scale = Math.min(widthScale, heightScale);
         break;
       default:
         return;
     }
 
     if (scale && scale !== PDFView.currentScale) {
-      PDFView.parseScale(scale, true, true);
+      PDFView.setScale(scale, true, true);
     } else if (PDFView.currentScale === UNKNOWN_SCALE) {
-      PDFView.parseScale(DEFAULT_SCALE, true, true);
+      PDFView.setScale(DEFAULT_SCALE, true, true);
     }
 
     if (scale === 'page-fit' && !dest[4]) {
       scrollIntoView(div);
       return;
     }
 
     var boundingRect = [
@@ -3147,36 +3345,47 @@ var PageView = function pageView(contain
     canvasWrapper.appendChild(canvas);
     div.appendChild(canvasWrapper);
     this.canvas = canvas;
 
     var scale = this.scale;
     var ctx = canvas.getContext('2d');
     var outputScale = getOutputScale(ctx);
 
-    canvas.width = Math.floor(viewport.width) * outputScale.sx;
-    canvas.height = Math.floor(viewport.height) * outputScale.sy;
+    if (USE_ONLY_CSS_ZOOM) {
+      var actualSizeViewport = viewport.clone({ scale: CSS_UNITS });
+      // Use a scale that will make the canvas be the original intended size
+      // of the page.
+      outputScale.sx *= actualSizeViewport.width / viewport.width;
+      outputScale.sy *= actualSizeViewport.height / viewport.height;
+      outputScale.scaled = true;
+    }
+
+    canvas.width = Math.floor(viewport.width * outputScale.sx);
+    canvas.height = Math.floor(viewport.height * outputScale.sy);
     canvas.style.width = Math.floor(viewport.width) + 'px';
     canvas.style.height = Math.floor(viewport.height) + 'px';
+    // Add the viewport so it's known what it was originally drawn with.
+    canvas._viewport = viewport;
 
     var textLayerDiv = null;
     if (!PDFJS.disableTextLayer) {
       textLayerDiv = document.createElement('div');
       textLayerDiv.className = 'textLayer';
       textLayerDiv.style.width = canvas.width + 'px';
       textLayerDiv.style.height = canvas.height + 'px';
       div.appendChild(textLayerDiv);
     }
     var textLayer = this.textLayer =
       textLayerDiv ? new TextLayerBuilder({
         textLayerDiv: textLayerDiv,
         pageIndex: this.id - 1,
         lastScrollSource: PDFView,
         viewport: this.viewport,
-        isViewerInPresentationMode: PDFView.isPresentationMode
+        isViewerInPresentationMode: PresentationMode.active
       }) : null;
     // TODO(mack): use data attributes to store these
     ctx._scaleX = outputScale.sx;
     ctx._scaleY = outputScale.sy;
     if (outputScale.scaled) {
       ctx.scale(outputScale.sx, outputScale.sy);
     }
     if (outputScale.scaled && textLayerDiv) {
@@ -3206,16 +3415,21 @@ var PageView = function pageView(contain
 
       self.renderingState = RenderingStates.FINISHED;
 
       if (self.loadingIconDiv) {
         div.removeChild(self.loadingIconDiv);
         delete self.loadingIconDiv;
       }
 
+      if (self.zoomLayer) {
+        div.removeChild(self.zoomLayer);
+        self.zoomLayer = null;
+      }
+
       if (checkIfDocumentFontsUsed && PDFView.pdfDocument.embeddedFontsUsed &&
           PDFJS.disableFontFace) {
         console.error(mozL10n.get('web_fonts_disabled', null,
           'Web fonts are disabled: unable to use embedded PDF fonts.'));
         PDFView.fallback();
       }
       if (self.textLayer && self.textLayer.textDivs &&
           self.textLayer.textDivs.length > 0 &&
@@ -3293,28 +3507,32 @@ var PageView = function pageView(contain
 
   this.beforePrint = function pageViewBeforePrint() {
     var pdfPage = this.pdfPage;
 
     var viewport = pdfPage.getViewport(1);
     // Use the same hack we use for high dpi displays for printing to get better
     // output until bug 811002 is fixed in FF.
     var PRINT_OUTPUT_SCALE = 2;
-    var canvas = this.canvas = document.createElement('canvas');
+    var canvas = document.createElement('canvas');
     canvas.width = Math.floor(viewport.width) * PRINT_OUTPUT_SCALE;
     canvas.height = Math.floor(viewport.height) * PRINT_OUTPUT_SCALE;
     canvas.style.width = (PRINT_OUTPUT_SCALE * viewport.width) + 'pt';
     canvas.style.height = (PRINT_OUTPUT_SCALE * viewport.height) + 'pt';
     var cssScale = 'scale(' + (1 / PRINT_OUTPUT_SCALE) + ', ' +
                               (1 / PRINT_OUTPUT_SCALE) + ')';
     CustomStyle.setProp('transform' , canvas, cssScale);
     CustomStyle.setProp('transformOrigin' , canvas, '0% 0%');
 
     var printContainer = document.getElementById('printContainer');
-    printContainer.appendChild(canvas);
+    var canvasWrapper = document.createElement('div');
+    canvasWrapper.style.width = viewport.width + 'pt';
+    canvasWrapper.style.height = viewport.height + 'pt';
+    canvasWrapper.appendChild(canvas);
+    printContainer.appendChild(canvasWrapper);
 
     var self = this;
     canvas.mozPrintCallback = function(obj) {
       var ctx = obj.context;
 
       ctx.save();
       ctx.fillStyle = 'rgb(255, 255, 255)';
       ctx.fillRect(0, 0, canvas.width, canvas.height);
@@ -3939,16 +4157,17 @@ var DocumentOutlineView = function docum
         queue.push({parent: itemsDiv, items: item.items});
       }
 
       levelData.parent.appendChild(div);
     }
   }
 };
 
+
 document.addEventListener('DOMContentLoaded', function webViewerLoad(evt) {
   PDFView.initialize();
 
   var file = window.location.href.split('#')[0];
 
 
   document.getElementById('openFile').setAttribute('hidden', 'true');
   document.getElementById('secondaryOpenFile').setAttribute('hidden', 'true');
@@ -3972,16 +4191,20 @@ document.addEventListener('DOMContentLoa
   if ('disableFontFace' in hashParams) {
     PDFJS.disableFontFace = (hashParams['disableFontFace'] === 'true');
   }
 
   if ('disableHistory' in hashParams) {
     PDFJS.disableHistory = (hashParams['disableHistory'] === 'true');
   }
 
+  if ('useOnlyCssZoom' in hashParams) {
+    USE_ONLY_CSS_ZOOM = (hashParams['useOnlyCssZoom'] === 'true');
+  }
+
   if (!PDFView.supportsDocumentFonts) {
     PDFJS.disableFontFace = true;
   }
 
   if ('textLayer' in hashParams) {
     switch (hashParams['textLayer']) {
       case 'off':
         PDFJS.disableTextLayer = true;
@@ -4091,43 +4314,31 @@ document.addEventListener('DOMContentLoa
 
       if (this.value !== (this.value | 0).toString()) {
         this.value = PDFView.page;
       }
     });
 
   document.getElementById('scaleSelect').addEventListener('change',
     function() {
-      PDFView.parseScale(this.value);
+      PDFView.setScale(this.value);
     });
 
   document.getElementById('presentationMode').addEventListener('click',
     SecondaryToolbar.presentationModeClick.bind(SecondaryToolbar));
 
   document.getElementById('openFile').addEventListener('click',
     SecondaryToolbar.openFileClick.bind(SecondaryToolbar));
 
   document.getElementById('print').addEventListener('click',
     SecondaryToolbar.printClick.bind(SecondaryToolbar));
 
   document.getElementById('download').addEventListener('click',
     SecondaryToolbar.downloadClick.bind(SecondaryToolbar));
 
-  document.getElementById('contextFirstPage').addEventListener('click',
-    SecondaryToolbar.firstPageClick.bind(SecondaryToolbar));
-
-  document.getElementById('contextLastPage').addEventListener('click',
-    SecondaryToolbar.lastPageClick.bind(SecondaryToolbar));
-
-  document.getElementById('contextPageRotateCw').addEventListener('click',
-    SecondaryToolbar.pageRotateCwClick.bind(SecondaryToolbar));
-
-  document.getElementById('contextPageRotateCcw').addEventListener('click',
-    SecondaryToolbar.pageRotateCcwClick.bind(SecondaryToolbar));
-
   PDFView.setTitleUsingUrl(file);
   PDFView.initPassiveLoading();
   return;
 
   PDFView.open(file, 0);
 }, true);
 
 function updateViewarea() {
@@ -4184,27 +4395,28 @@ function updateViewarea() {
     store.set('exists', true);
     store.set('page', pageNumber);
     store.set('zoom', normalizedScaleValue);
     store.set('scrollLeft', Math.round(topLeft[0]));
     store.set('scrollTop', Math.round(topLeft[1]));
   });
   var href = PDFView.getAnchorUrl(pdfOpenParams);
   document.getElementById('viewBookmark').href = href;
+  document.getElementById('secondaryViewBookmark').href = href;
 
   // Update the current bookmark in the browsing history.
   PDFHistory.updateCurrentBookmark(pdfOpenParams, pageNumber);
 }
 
 window.addEventListener('resize', function webViewerResize(evt) {
   if (PDFView.initialized &&
       (document.getElementById('pageWidthOption').selected ||
        document.getElementById('pageFitOption').selected ||
        document.getElementById('pageAutoOption').selected)) {
-    PDFView.parseScale(document.getElementById('scaleSelect').value);
+    PDFView.setScale(document.getElementById('scaleSelect').value);
   }
   updateViewarea();
 
   // Set the 'max-height' CSS property of the secondary toolbar.
   SecondaryToolbar.setMaxHeight(PDFView.container);
 });
 
 window.addEventListener('hashchange', function webViewerHashchange(evt) {
@@ -4227,16 +4439,18 @@ window.addEventListener('change', functi
   };
 
   var file = files[0];
   fileReader.readAsArrayBuffer(file);
   PDFView.setTitleUsingUrl(file.name);
 
   // URL does not reflect proper document location - hiding some icons.
   document.getElementById('viewBookmark').setAttribute('hidden', 'true');
+  document.getElementById('secondaryViewBookmark').
+    setAttribute('hidden', 'true');
   document.getElementById('download').setAttribute('hidden', 'true');
   document.getElementById('secondaryDownload').setAttribute('hidden', 'true');
 }, true);
 
 function selectScaleOption(value) {
   var options = document.getElementById('scaleSelect').options;
   var predefinedValueFound = false;
   for (var i = 0; i < options.length; i++) {
@@ -4297,75 +4511,54 @@ window.addEventListener('scalechange', f
   updateViewarea();
 }, true);
 
 window.addEventListener('pagechange', function pagechange(evt) {
   var page = evt.pageNumber;
   if (PDFView.previousPageNumber !== page) {
     document.getElementById('pageNumber').value = page;
     var selected = document.querySelector('.thumbnail.selected');
-    if (selected)
+    if (selected) {
       selected.classList.remove('selected');
+    }
     var thumbnail = document.getElementById('thumbnailContainer' + page);
     thumbnail.classList.add('selected');
     var visibleThumbs = PDFView.getVisibleThumbs();
     var numVisibleThumbs = visibleThumbs.views.length;
-    // If the thumbnail isn't currently visible scroll it into view.
+
+    // If the thumbnail isn't currently visible, scroll it into view.
     if (numVisibleThumbs > 0) {
       var first = visibleThumbs.first.id;
       // Account for only one thumbnail being visible.
-      var last = numVisibleThumbs > 1 ?
-                  visibleThumbs.last.id : first;
-      if (page <= first || page >= last)
-        scrollIntoView(thumbnail);
+      var last = (numVisibleThumbs > 1 ? visibleThumbs.last.id : first);
+      if (page <= first || page >= last) {
+        scrollIntoView(thumbnail, { top: THUMBNAIL_SCROLL_MARGIN });
+      }
     }
-
   }
   document.getElementById('previous').disabled = (page <= 1);
   document.getElementById('next').disabled = (page >= PDFView.pages.length);
 }, true);
 
 // Firefox specific event, so that we can prevent browser from zooming
 window.addEventListener('DOMMouseScroll', function(evt) {
   if (evt.ctrlKey) {
     evt.preventDefault();
 
     var ticks = evt.detail;
     var direction = (ticks > 0) ? 'zoomOut' : 'zoomIn';
     PDFView[direction](Math.abs(ticks));
-  } else if (PDFView.isPresentationMode) {
+  } else if (PresentationMode.active) {
     var FIREFOX_DELTA_FACTOR = -40;
     PDFView.mouseScroll(evt.detail * FIREFOX_DELTA_FACTOR);
   }
 }, false);
 
-window.addEventListener('mousemove', function mousemove(evt) {
-  if (PDFView.isPresentationMode) {
-    PDFView.showPresentationControls();
-  }
-}, false);
-
-window.addEventListener('mousedown', function mousedown(evt) {
-  if (PDFView.isPresentationMode && evt.button === 0) {
-    // Enable clicking of links in presentation mode.
-    // Note: Only links that point to the currently loaded PDF document works.
-    var targetHref = evt.target.href;
-    var internalLink = targetHref && (targetHref.replace(/#.*$/, '') ===
-                                      window.location.href.replace(/#.*$/, ''));
-    if (!internalLink) {
-      // Unless an internal link was clicked, advance a page in presentation
-      // mode.
-      evt.preventDefault();
-      PDFView.page++;
-    }
-  }
-}, false);
-
 window.addEventListener('click', function click(evt) {
-  if (!PDFView.isPresentationMode) {
+  if (!PresentationMode.active) {
     if (SecondaryToolbar.isOpen && PDFView.container.contains(evt.target)) {
       SecondaryToolbar.close();
     }
   } else if (evt.button === 0) {
     // Necessary since preventDefault() in 'mousedown' won't stop
     // the event propagation in all circumstances in presentation mode.
     evt.preventDefault();
   }
@@ -4412,29 +4605,28 @@ window.addEventListener('keydown', funct
         PDFView.zoomOut();
         handled = true;
         break;
       case 48: // '0'
       case 96: // '0' on Numpad of Swedish keyboard
         // keeping it unhandled (to restore page zoom to 100%)
         setTimeout(function () {
           // ... and resetting the scale after browser adjusts its scale
-          PDFView.parseScale(DEFAULT_SCALE, true);
+          PDFView.setScale(DEFAULT_SCALE, true);
         });
         handled = false;
         break;
     }
   }
 
   // CTRL+ALT or Option+Command
   if (cmd === 3 || cmd === 10) {
     switch (evt.keyCode) {
       case 80: // p
-        PDFView.presentationMode();
-        SecondaryToolbar.close();
+        SecondaryToolbar.presentationModeClick();
         handled = true;
         break;
     }
   }
 
   if (handled) {
     evt.preventDefault();
     return;
@@ -4448,27 +4640,27 @@ window.addEventListener('keydown', funct
                      curElement.tagName.toUpperCase() === 'SELECT')) {
     // Make sure that the secondary toolbar is closed when Escape is pressed.
     if (evt.keyCode !== 27) { // 'Esc'
       return;
     }
   }
   var controlsElement = document.getElementById('toolbar');
   while (curElement) {
-    if (curElement === controlsElement && !PDFView.isPresentationMode)
+    if (curElement === controlsElement && !PresentationMode.active)
       return; // ignoring if the 'toolbar' element is focused
     curElement = curElement.parentNode;
   }
 
   if (cmd === 0) { // no control key pressed at all.
     switch (evt.keyCode) {
       case 38: // up arrow
       case 33: // pg up
       case 8: // backspace
-        if (!PDFView.isPresentationMode &&
+        if (!PresentationMode.active &&
             PDFView.currentScaleValue !== 'page-fit') {
           break;
         }
         /* in presentation mode */
         /* falls through */
       case 37: // left arrow
         // horizontal scrolling using arrow keys
         if (PDFView.isHorizontalScrollbarEnabled) {
@@ -4488,17 +4680,17 @@ window.addEventListener('keydown', funct
         if (!PDFView.supportsIntegratedFind && PDFFindBar.opened) {
           PDFFindBar.close();
           handled = true;
         }
         break;
       case 40: // down arrow
       case 34: // pg down
       case 32: // spacebar
-        if (!PDFView.isPresentationMode &&
+        if (!PresentationMode.active &&
             PDFView.currentScaleValue !== 'page-fit') {
           break;
         }
         /* falls through */
       case 39: // right arrow
         // horizontal scrolling using arrow keys
         if (PDFView.isHorizontalScrollbarEnabled) {
           break;
@@ -4506,61 +4698,61 @@ window.addEventListener('keydown', funct
         /* falls through */
       case 74: // 'j'
       case 78: // 'n'
         PDFView.page++;
         handled = true;
         break;
 
       case 36: // home
-        if (PDFView.isPresentationMode) {
+        if (PresentationMode.active) {
           PDFView.page = 1;
           handled = true;
         }
         break;
       case 35: // end
-        if (PDFView.isPresentationMode) {
+        if (PresentationMode.active) {
           PDFView.page = PDFView.pdfDocument.numPages;
           handled = true;
         }
         break;
 
       case 82: // 'r'
         PDFView.rotatePages(90);
         break;
     }
   }
 
   if (cmd === 4) { // shift-key
     switch (evt.keyCode) {
       case 32: // spacebar
-        if (!PDFView.isPresentationMode &&
+        if (!PresentationMode.active &&
             PDFView.currentScaleValue !== 'page-fit') {
           break;
         }
         PDFView.page--;
         handled = true;
         break;
 
       case 82: // 'r'
         PDFView.rotatePages(-90);
         break;
     }
   }
 
   if (cmd === 2) { // alt-key
     switch (evt.keyCode) {
       case 37: // left arrow
-        if (PDFView.isPresentationMode) {
+        if (PresentationMode.active) {
           PDFHistory.back();
           handled = true;
         }
         break;
       case 39: // right arrow
-        if (PDFView.isPresentationMode) {
+        if (PresentationMode.active) {
           PDFHistory.forward();
           handled = true;
         }
         break;
     }
   }
 
   if (handled) {
@@ -4572,37 +4764,16 @@ window.addEventListener('keydown', funct
 window.addEventListener('beforeprint', function beforePrint(evt) {
   PDFView.beforePrint();
 });
 
 window.addEventListener('afterprint', function afterPrint(evt) {
   PDFView.afterPrint();
 });
 
-(function presentationModeClosure() {
-  function presentationModeChange(e) {
-    var isPresentationMode = document.fullscreenElement ||
-                             document.mozFullScreen ||
-                             document.webkitIsFullScreen ||
-                             document.msFullscreenElement;
-
-    if (isPresentationMode) {
-      PDFView.enterPresentationMode();
-    } else {
-      PDFView.exitPresentationMode();
-    }
-  }
-
-  window.addEventListener('fullscreenchange', presentationModeChange, false);
-  window.addEventListener('mozfullscreenchange', presentationModeChange, false);
-  window.addEventListener('webkitfullscreenchange', presentationModeChange,
-                          false);
-  window.addEventListener('MSFullscreenChange', presentationModeChange, false);
-})();
-
 (function animationStartedClosure() {
   // The offsetParent is not set until the pdf.js iframe or object is visible.
   // Waiting for first animation.
   var requestAnimationFrame = window.requestAnimationFrame ||
                               window.mozRequestAnimationFrame ||
                               window.webkitRequestAnimationFrame ||
                               window.oRequestAnimationFrame ||
                               window.msRequestAnimationFrame ||
--- a/browser/installer/package-manifest.in
+++ b/browser/installer/package-manifest.in
@@ -182,17 +182,16 @@
 @BINPATH@/components/dom_icc.xpt
 @BINPATH@/components/dom_wappush.xpt
 #endif
 #ifdef MOZ_B2G_BT
 @BINPATH@/components/dom_bluetooth.xpt
 #endif
 @BINPATH@/components/dom_camera.xpt
 @BINPATH@/components/dom_canvas.xpt
-@BINPATH@/components/dom_contacts.xpt
 @BINPATH@/components/dom_alarm.xpt
 @BINPATH@/components/dom_core.xpt
 @BINPATH@/components/dom_css.xpt
 @BINPATH@/components/dom_devicestorage.xpt
 @BINPATH@/components/dom_events.xpt
 @BINPATH@/components/dom_file.xpt
 @BINPATH@/components/dom_geolocation.xpt
 @BINPATH@/components/dom_media.xpt
--- a/configure.in
+++ b/configure.in
@@ -223,17 +223,17 @@ if test -n "$gonkdir" ; then
         GONK_INCLUDES="-I$gonkdir/frameworks/base/opengl/include -I$gonkdir/frameworks/base/native/include -I$gonkdir/frameworks/base/include -I$gonkdir/frameworks/base/services/camera -I$gonkdir/frameworks/base/include/media/stagefright -I$gonkdir/frameworks/base/include/media/stagefright/openmax -I$gonkdir/frameworks/base/media/libstagefright/rtsp -I$gonkdir/frameworks/base/media/libstagefright/include -I$gonkdir/external/dbus -I$gonkdir/external/bluetooth/bluez/lib -I$gonkdir/dalvik/libnativehelper/include/nativehelper"
         MOZ_B2G_BT=1
         MOZ_B2G_BT_BLUEZ=1
         MOZ_B2G_CAMERA=1
         MOZ_OMX_DECODER=1
         AC_SUBST(MOZ_OMX_DECODER)
         MOZ_RTSP=1
         ;;
-    18)
+    17|18)
         GONK_INCLUDES="-I$gonkdir/frameworks/native/include -I$gonkdir/frameworks/av/include -I$gonkdir/frameworks/av/include/media -I$gonkdir/frameworks/av/include/camera -I$gonkdir/frameworks/native/include/media/openmax -I$gonkdir/frameworks/av/media/libstagefright/include"
         if test -d "$gonkdir/external/bluetooth/bluez"; then
             GONK_INCLUDES="$GONK_INCLUDES -I$gonkdir/external/dbus -I$gonkdir/external/bluetooth/bluez/lib"
             MOZ_B2G_BT=1
             MOZ_B2G_BT_BLUEZ=1
         fi
         if test -d "$gonkdir/external/bluetooth/bluedroid"; then
             MOZ_B2G_BT=1
--- a/content/base/public/nsContentUtils.h
+++ b/content/base/public/nsContentUtils.h
@@ -1364,23 +1364,16 @@ public:
   static nsIWidget* GetTopLevelWidget(nsIWidget* aWidget);
 
   /**
    * Return the localized ellipsis for UI.
    */
   static const nsDependentString GetLocalizedEllipsis();
 
   /**
-   * The routine GetNativeEvent returns the result of
-   * aDOMEvent->GetInternalNSEvent().
-   * XXX Is this necessary?
-   */
-  static mozilla::WidgetEvent* GetNativeEvent(nsIDOMEvent* aDOMEvent);
-
-  /**
    * Get the candidates for accelkeys for aDOMKeyEvent.
    *
    * @param aDOMKeyEvent [in] the key event for accelkey handling.
    * @param aCandidates [out] the candidate shortcut key combination list.
    *                          the first item is most preferred.
    */
   static void GetAccelKeyCandidates(nsIDOMKeyEvent* aDOMKeyEvent,
                                     nsTArray<nsShortcutCandidate>& aCandidates);
--- a/content/base/src/Element.cpp
+++ b/content/base/src/Element.cpp
@@ -2163,28 +2163,28 @@ Element::PreHandleEventForLinks(nsEventC
 
   // We do the status bar updates in PreHandleEvent so that the status bar gets
   // updated even if the event is consumed before we have a chance to set it.
   switch (aVisitor.mEvent->message) {
   // Set the status bar similarly for mouseover and focus
   case NS_MOUSE_ENTER_SYNTH:
     aVisitor.mEventStatus = nsEventStatus_eConsumeNoDefault;
     // FALL THROUGH
-  case NS_FOCUS_CONTENT:
-    if (aVisitor.mEvent->eventStructType != NS_FOCUS_EVENT ||
-        !static_cast<InternalFocusEvent*>(aVisitor.mEvent)->isRefocus) {
+  case NS_FOCUS_CONTENT: {
+    InternalFocusEvent* focusEvent = aVisitor.mEvent->AsFocusEvent();
+    if (!focusEvent || !focusEvent->isRefocus) {
       nsAutoString target;
       GetLinkTarget(target);
       nsContentUtils::TriggerLink(this, aVisitor.mPresContext, absURI, target,
                                   false, true, true);
       // Make sure any ancestor links don't also TriggerLink
       aVisitor.mEvent->mFlags.mMultipleActionsPrevented = true;
     }
     break;
-
+  }
   case NS_MOUSE_EXIT_SYNTH:
     aVisitor.mEventStatus = nsEventStatus_eConsumeNoDefault;
     // FALL THROUGH
   case NS_BLUR_CONTENT:
     rv = LeaveLink(aVisitor.mPresContext);
     if (NS_SUCCEEDED(rv)) {
       aVisitor.mEvent->mFlags.mMultipleActionsPrevented = true;
     }
@@ -2244,18 +2244,17 @@ Element::PostHandleEventForLinks(nsEvent
             aVisitor.mPresContext->EventStateManager(), this);
         }
       }
     }
     break;
 
   case NS_MOUSE_CLICK:
     if (aVisitor.mEvent->IsLeftClickEvent()) {
-      WidgetInputEvent* inputEvent =
-        static_cast<WidgetInputEvent*>(aVisitor.mEvent);
+      WidgetInputEvent* inputEvent = aVisitor.mEvent->AsInputEvent();
       if (inputEvent->IsControl() || inputEvent->IsMeta() ||
           inputEvent->IsAlt() ||inputEvent->IsShift()) {
         break;
       }
 
       // The default action is simply to dispatch DOMActivate
       nsCOMPtr<nsIPresShell> shell = aVisitor.mPresContext->GetPresShell();
       if (shell) {
@@ -2282,26 +2281,23 @@ Element::PostHandleEventForLinks(nsEvent
                                     aVisitor.mEvent->mFlags.mIsTrusted);
         aVisitor.mEventStatus = nsEventStatus_eConsumeNoDefault;
       }
     }
     break;
 
   case NS_KEY_PRESS:
     {
-      if (aVisitor.mEvent->eventStructType == NS_KEY_EVENT) {
-        WidgetKeyboardEvent* keyEvent =
-          static_cast<WidgetKeyboardEvent*>(aVisitor.mEvent);
-        if (keyEvent->keyCode == NS_VK_RETURN) {
-          nsEventStatus status = nsEventStatus_eIgnore;
-          rv = DispatchClickEvent(aVisitor.mPresContext, keyEvent, this,
-                                  false, nullptr, &status);
-          if (NS_SUCCEEDED(rv)) {
-            aVisitor.mEventStatus = nsEventStatus_eConsumeNoDefault;
-          }
+      WidgetKeyboardEvent* keyEvent = aVisitor.mEvent->AsKeyboardEvent();
+      if (keyEvent && keyEvent->keyCode == NS_VK_RETURN) {
+        nsEventStatus status = nsEventStatus_eIgnore;
+        rv = DispatchClickEvent(aVisitor.mPresContext, keyEvent, this,
+                                false, nullptr, &status);
+        if (NS_SUCCEEDED(rv)) {
+          aVisitor.mEventStatus = nsEventStatus_eConsumeNoDefault;
         }
       }
     }
     break;
 
   default:
     // switch not in sync with the optimization switch earlier in this function
     NS_NOTREACHED("switch statements not in sync");
--- a/content/base/src/nsContentUtils.cpp
+++ b/content/base/src/nsContentUtils.cpp
@@ -4552,23 +4552,16 @@ nsContentUtils::GetLocalizedEllipsis()
                           uint32_t(ArrayLength(sBuf) - 1));
     CopyUnicodeTo(tmp, 0, sBuf, len);
     if (!sBuf[0])
       sBuf[0] = PRUnichar(0x2026);
   }
   return nsDependentString(sBuf);
 }
 
-//static
-WidgetEvent*
-nsContentUtils::GetNativeEvent(nsIDOMEvent* aDOMEvent)
-{
-  return aDOMEvent ? aDOMEvent->GetInternalNSEvent() : nullptr;
-}
-
 static bool
 HasASCIIDigit(const nsTArray<nsShortcutCandidate>& aCandidates)
 {
   for (uint32_t i = 0; i < aCandidates.Length(); ++i) {
     uint32_t ch = aCandidates[i].mCharCode;
     if (ch >= '0' && ch <= '9')
       return true;
   }
@@ -4599,17 +4592,17 @@ nsContentUtils::GetAccelKeyCandidates(ns
 
   nsAutoString eventType;
   aDOMKeyEvent->GetType(eventType);
   // Don't process if aDOMKeyEvent is not a keypress event.
   if (!eventType.EqualsLiteral("keypress"))
     return;
 
   WidgetKeyboardEvent* nativeKeyEvent =
-    static_cast<WidgetKeyboardEvent*>(GetNativeEvent(aDOMKeyEvent));
+    aDOMKeyEvent->GetInternalNSEvent()->AsKeyboardEvent();
   if (nativeKeyEvent) {
     NS_ASSERTION(nativeKeyEvent->eventStructType == NS_KEY_EVENT,
                  "wrong type of native event");
     // nsShortcutCandidate::mCharCode is a candidate charCode.
     // nsShoftcutCandidate::mIgnoreShift means the mCharCode should be tried to
     // execute a command with/without shift key state. If this is TRUE, the
     // shifted key state should be ignored. Otherwise, don't ignore the state.
     // the priority of the charCodes are (shift key is not pressed):
--- a/content/events/public/MutationEvent.h
+++ b/content/events/public/MutationEvent.h
@@ -11,16 +11,18 @@
 #include "nsIAtom.h"
 #include "nsIDOMNode.h"
 
 namespace mozilla {
 
 class InternalMutationEvent : public WidgetEvent
 {
 public:
+  virtual InternalMutationEvent* AsMutationEvent() MOZ_OVERRIDE { return this; }
+
   InternalMutationEvent(bool aIsTrusted, uint32_t aMessage) :
     WidgetEvent(aIsTrusted, aMessage, NS_MUTATION_EVENT),
     mAttrChange(0)
   {
     mFlags.mCancelable = false;
   }
 
   nsCOMPtr<nsIDOMNode> mRelatedNode;
--- a/content/events/src/DOMWheelEvent.cpp
+++ b/content/events/src/DOMWheelEvent.cpp
@@ -24,26 +24,16 @@ DOMWheelEvent::DOMWheelEvent(EventTarget
     mEventIsInternal = true;
     mEvent->time = PR_Now();
     mEvent->refPoint.x = mEvent->refPoint.y = 0;
     static_cast<WidgetWheelEvent*>(mEvent)->inputSource =
       nsIDOMMouseEvent::MOZ_SOURCE_UNKNOWN;
   }
 }
 
-DOMWheelEvent::~DOMWheelEvent()
-{
-  if (mEventIsInternal && mEvent) {
-    MOZ_ASSERT(mEvent->eventStructType == NS_WHEEL_EVENT,
-               "The mEvent must be WidgetWheelEvent");
-    delete static_cast<WidgetWheelEvent*>(mEvent);
-    mEvent = nullptr;
-  }
-}
-
 NS_IMPL_ADDREF_INHERITED(DOMWheelEvent, nsDOMMouseEvent)
 NS_IMPL_RELEASE_INHERITED(DOMWheelEvent, nsDOMMouseEvent)
 
 NS_INTERFACE_MAP_BEGIN(DOMWheelEvent)
   NS_INTERFACE_MAP_ENTRY(nsIDOMWheelEvent)
 NS_INTERFACE_MAP_END_INHERITING(nsDOMMouseEvent)
 
 NS_IMETHODIMP
--- a/content/events/src/DOMWheelEvent.h
+++ b/content/events/src/DOMWheelEvent.h
@@ -17,17 +17,16 @@ namespace dom {
 
 class DOMWheelEvent : public nsDOMMouseEvent,
                       public nsIDOMWheelEvent
 {
 public:
   DOMWheelEvent(mozilla::dom::EventTarget* aOwner,
                 nsPresContext* aPresContext,
                 WidgetWheelEvent* aWheelEvent);
-  virtual ~DOMWheelEvent();
 
   NS_DECL_ISUPPORTS_INHERITED
 
   // nsIDOMWheelEvent Interface
   NS_DECL_NSIDOMWHEELEVENT
   
   // Forward to base class
   NS_FORWARD_TO_NSDOMMOUSEEVENT
--- a/content/events/src/TextComposition.cpp
+++ b/content/events/src/TextComposition.cpp
@@ -46,17 +46,17 @@ TextComposition::MatchesNativeContext(ns
 }
 
 void
 TextComposition::DispatchEvent(WidgetGUIEvent* aEvent,
                                nsEventStatus* aStatus,
                                nsDispatchingCallback* aCallBack)
 {
   if (aEvent->message == NS_COMPOSITION_UPDATE) {
-    mLastData = static_cast<WidgetCompositionEvent*>(aEvent)->data;
+    mLastData = aEvent->AsCompositionEvent()->data;
   }
 
   nsEventDispatcher::Dispatch(mNode, mPresContext,
                               aEvent, nullptr, aStatus, aCallBack);
 }
 
 void
 TextComposition::DispatchCompsotionEventRunnable(uint32_t aEventMessage,
--- a/content/events/src/nsDOMAnimationEvent.cpp
+++ b/content/events/src/nsDOMAnimationEvent.cpp
@@ -21,24 +21,16 @@ nsDOMAnimationEvent::nsDOMAnimationEvent
     mEventIsInternal = false;
   }
   else {
     mEventIsInternal = true;
     mEvent->time = PR_Now();
   }
 }
 
-nsDOMAnimationEvent::~nsDOMAnimationEvent()
-{
-  if (mEventIsInternal) {
-    delete AnimationEvent();
-    mEvent = nullptr;
-  }
-}
-
 NS_INTERFACE_MAP_BEGIN(nsDOMAnimationEvent)
   NS_INTERFACE_MAP_ENTRY(nsIDOMAnimationEvent)
 NS_INTERFACE_MAP_END_INHERITING(nsDOMEvent)
 
 NS_IMPL_ADDREF_INHERITED(nsDOMAnimationEvent, nsDOMEvent)
 NS_IMPL_RELEASE_INHERITED(nsDOMAnimationEvent, nsDOMEvent)
 
 //static
@@ -49,42 +41,49 @@ nsDOMAnimationEvent::Constructor(const m
                                  mozilla::ErrorResult& aRv)
 {
   nsCOMPtr<mozilla::dom::EventTarget> t = do_QueryInterface(aGlobal.GetAsSupports());
   nsRefPtr<nsDOMAnimationEvent> e = new nsDOMAnimationEvent(t, nullptr, nullptr);
   bool trusted = e->Init(t);
 
   aRv = e->InitEvent(aType, aParam.mBubbles, aParam.mCancelable);
 
-  e->AnimationEvent()->animationName = aParam.mAnimationName;
-  e->AnimationEvent()->elapsedTime = aParam.mElapsedTime;
-  e->AnimationEvent()->pseudoElement = aParam.mPseudoElement;
+  InternalAnimationEvent* internalEvent = e->mEvent->AsAnimationEvent();
+  internalEvent->animationName = aParam.mAnimationName;
+  internalEvent->elapsedTime = aParam.mElapsedTime;
+  internalEvent->pseudoElement = aParam.mPseudoElement;
 
   e->SetTrusted(trusted);
   return e.forget();
 }
 
 NS_IMETHODIMP
 nsDOMAnimationEvent::GetAnimationName(nsAString & aAnimationName)
 {
-  aAnimationName = AnimationEvent()->animationName;
+  aAnimationName = mEvent->AsAnimationEvent()->animationName;
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsDOMAnimationEvent::GetElapsedTime(float *aElapsedTime)
 {
   *aElapsedTime = ElapsedTime();
   return NS_OK;
 }
 
+float
+nsDOMAnimationEvent::ElapsedTime()
+{
+  return mEvent->AsAnimationEvent()->elapsedTime;
+}
+
 NS_IMETHODIMP
 nsDOMAnimationEvent::GetPseudoElement(nsAString& aPseudoElement)
 {
-  aPseudoElement = AnimationEvent()->pseudoElement;
+  aPseudoElement = mEvent->AsAnimationEvent()->pseudoElement;
   return NS_OK;
 }
 
 nsresult
 NS_NewDOMAnimationEvent(nsIDOMEvent **aInstancePtrResult,
                         mozilla::dom::EventTarget* aOwner,
                         nsPresContext *aPresContext,
                         InternalAnimationEvent *aEvent)
--- a/content/events/src/nsDOMAnimationEvent.h
+++ b/content/events/src/nsDOMAnimationEvent.h
@@ -2,29 +2,28 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 #ifndef nsDOMAnimationEvent_h_
 #define nsDOMAnimationEvent_h_
 
 #include "nsDOMEvent.h"
 #include "nsIDOMAnimationEvent.h"
-#include "mozilla/ContentEvents.h"
+#include "mozilla/EventForwards.h"
 #include "mozilla/dom/AnimationEventBinding.h"
 
 class nsAString;
 
 class nsDOMAnimationEvent : public nsDOMEvent,
                             public nsIDOMAnimationEvent
 {
 public:
   nsDOMAnimationEvent(mozilla::dom::EventTarget* aOwner,
                       nsPresContext *aPresContext,
                       mozilla::InternalAnimationEvent* aEvent);
-  ~nsDOMAnimationEvent();
 
   NS_DECL_ISUPPORTS_INHERITED
   NS_FORWARD_TO_NSDOMEVENT
   NS_DECL_NSIDOMANIMATIONEVENT
 
   static already_AddRefed<nsDOMAnimationEvent>
   Constructor(const mozilla::dom::GlobalObject& aGlobal,
               const nsAString& aType,
@@ -36,22 +35,12 @@ public:
   {
     return mozilla::dom::AnimationEventBinding::Wrap(aCx, aScope, this);
   }
 
   // xpidl implementation
   // GetAnimationName(nsAString& aAnimationName);
   // GetPseudoElement(nsAString& aPseudoElement);
 
-  float ElapsedTime()
-  {
-    return AnimationEvent()->elapsedTime;
-  }
-
-private:
-  mozilla::InternalAnimationEvent* AnimationEvent() {
-    NS_ABORT_IF_FALSE(mEvent->eventStructType == NS_ANIMATION_EVENT,
-                      "unexpected struct type");
-    return static_cast<mozilla::InternalAnimationEvent*>(mEvent);
-  }
+  float ElapsedTime();
 };
 
 #endif /* !defined(nsDOMAnimationEvent_h_) */
--- a/content/events/src/nsDOMClipboardEvent.cpp
+++ b/content/events/src/nsDOMClipboardEvent.cpp
@@ -19,40 +19,33 @@ nsDOMClipboardEvent::nsDOMClipboardEvent
   if (aEvent) {
     mEventIsInternal = false;
   } else {
     mEventIsInternal = true;
     mEvent->time = PR_Now();
   }
 }
 
-nsDOMClipboardEvent::~nsDOMClipboardEvent()
-{
-  if (mEventIsInternal && mEvent->eventStructType == NS_CLIPBOARD_EVENT) {
-    delete static_cast<InternalClipboardEvent*>(mEvent);
-    mEvent = nullptr;
-  }
-}
-
 NS_INTERFACE_MAP_BEGIN(nsDOMClipboardEvent)
   NS_INTERFACE_MAP_ENTRY(nsIDOMClipboardEvent)
 NS_INTERFACE_MAP_END_INHERITING(nsDOMEvent)
 
 NS_IMPL_ADDREF_INHERITED(nsDOMClipboardEvent, nsDOMEvent)
 NS_IMPL_RELEASE_INHERITED(nsDOMClipboardEvent, nsDOMEvent)
 
 nsresult
-nsDOMClipboardEvent::InitClipboardEvent(const nsAString & aType, bool aCanBubble, bool aCancelable,
-                                        nsIDOMDataTransfer* clipboardData)
+nsDOMClipboardEvent::InitClipboardEvent(const nsAString& aType,
+                                        bool aCanBubble,
+                                        bool aCancelable,
+                                        nsIDOMDataTransfer* aClipboardData)
 {
   nsresult rv = nsDOMEvent::InitEvent(aType, aCanBubble, aCancelable);
   NS_ENSURE_SUCCESS(rv, rv);
 
-  InternalClipboardEvent* event = static_cast<InternalClipboardEvent*>(mEvent);
-  event->clipboardData = clipboardData;
+  mEvent->AsClipboardEvent()->clipboardData = aClipboardData;
 
   return NS_OK;
 }
 
 already_AddRefed<nsDOMClipboardEvent>
 nsDOMClipboardEvent::Constructor(const mozilla::dom::GlobalObject& aGlobal,
                                  const nsAString& aType,
                                  const mozilla::dom::ClipboardEventInit& aParam,
@@ -60,18 +53,17 @@ nsDOMClipboardEvent::Constructor(const m
 {
   nsCOMPtr<mozilla::dom::EventTarget> t = do_QueryInterface(aGlobal.GetAsSupports());
   nsRefPtr<nsDOMClipboardEvent> e =
     new nsDOMClipboardEvent(t, nullptr, nullptr);
   bool trusted = e->Init(t);
 
   nsRefPtr<nsDOMDataTransfer> clipboardData;
   if (e->mEventIsInternal) {
-    InternalClipboardEvent* event =
-      static_cast<InternalClipboardEvent*>(e->mEvent);
+    InternalClipboardEvent* event = e->mEvent->AsClipboardEvent();
     if (event) {
       // Always create a clipboardData for the copy event. If this is changed to
       // support other types of events, make sure that read/write privileges are
       // checked properly within nsDOMDataTransfer.
       clipboardData = new nsDOMDataTransfer(NS_COPY, false, -1);
       clipboardData->SetData(aParam.mDataType, aParam.mData);
     }
   }
@@ -87,17 +79,17 @@ nsDOMClipboardEvent::GetClipboardData(ns
 {
   NS_IF_ADDREF(*aClipboardData = GetClipboardData());
   return NS_OK;
 }
 
 nsIDOMDataTransfer*
 nsDOMClipboardEvent::GetClipboardData()
 {
-  InternalClipboardEvent* event = static_cast<InternalClipboardEvent*>(mEvent);
+  InternalClipboardEvent* event = mEvent->AsClipboardEvent();
 
   if (!event->clipboardData) {
     if (mEventIsInternal) {
       event->clipboardData = new nsDOMDataTransfer(NS_COPY, false, -1);
     } else {
       event->clipboardData =
         new nsDOMDataTransfer(event->message, event->message == NS_PASTE, nsIClipboard::kGlobalClipboard);
     }
--- a/content/events/src/nsDOMClipboardEvent.h
+++ b/content/events/src/nsDOMClipboardEvent.h
@@ -13,17 +13,16 @@
 
 class nsDOMClipboardEvent : public nsDOMEvent,
                             public nsIDOMClipboardEvent
 {
 public:
   nsDOMClipboardEvent(mozilla::dom::EventTarget* aOwner,
                       nsPresContext* aPresContext,
                       mozilla::InternalClipboardEvent* aEvent);
-  virtual ~nsDOMClipboardEvent();
 
   NS_DECL_ISUPPORTS_INHERITED
 
   NS_DECL_NSIDOMCLIPBOARDEVENT
 
   // Forward to base class
   NS_FORWARD_TO_NSDOMEVENT
 
--- a/content/events/src/nsDOMCommandEvent.cpp
+++ b/content/events/src/nsDOMCommandEvent.cpp
@@ -18,35 +18,27 @@ nsDOMCommandEvent::nsDOMCommandEvent(moz
   mEvent->time = PR_Now();
   if (aEvent) {
     mEventIsInternal = false;
   } else {
     mEventIsInternal = true;
   }
 }
 
-nsDOMCommandEvent::~nsDOMCommandEvent()
-{
-  if (mEventIsInternal && mEvent->eventStructType == NS_COMMAND_EVENT) {
-    delete static_cast<WidgetCommandEvent*>(mEvent);
-    mEvent = nullptr;
-  }
-}
-
 NS_INTERFACE_MAP_BEGIN(nsDOMCommandEvent)
   NS_INTERFACE_MAP_ENTRY(nsIDOMCommandEvent)
 NS_INTERFACE_MAP_END_INHERITING(nsDOMEvent)
 
 NS_IMPL_ADDREF_INHERITED(nsDOMCommandEvent, nsDOMEvent)
 NS_IMPL_RELEASE_INHERITED(nsDOMCommandEvent, nsDOMEvent)
 
 NS_IMETHODIMP
 nsDOMCommandEvent::GetCommand(nsAString& aCommand)
 {
-  nsIAtom* command = static_cast<WidgetCommandEvent*>(mEvent)->command;
+  nsIAtom* command = mEvent->AsCommandEvent()->command;
   if (command) {
     command->ToString(aCommand);
   } else {
     aCommand.Truncate();
   }
   return NS_OK;
 }
 
@@ -54,17 +46,17 @@ NS_IMETHODIMP
 nsDOMCommandEvent::InitCommandEvent(const nsAString& aTypeArg,
                                     bool aCanBubbleArg,
                                     bool aCancelableArg,
                                     const nsAString& aCommand)
 {
   nsresult rv = nsDOMEvent::InitEvent(aTypeArg, aCanBubbleArg, aCancelableArg);
   NS_ENSURE_SUCCESS(rv, rv);
 
-  static_cast<WidgetCommandEvent*>(mEvent)->command = do_GetAtom(aCommand);
+  mEvent->AsCommandEvent()->command = do_GetAtom(aCommand);
   return NS_OK;
 }
 
 nsresult NS_NewDOMCommandEvent(nsIDOMEvent** aInstancePtrResult,
                                mozilla::dom::EventTarget* aOwner,
                                nsPresContext* aPresContext,
                                WidgetCommandEvent* aEvent)
 {
--- a/content/events/src/nsDOMCommandEvent.h
+++ b/content/events/src/nsDOMCommandEvent.h
@@ -13,17 +13,16 @@
 
 class nsDOMCommandEvent : public nsDOMEvent,
                           public nsIDOMCommandEvent
 {
 public:
   nsDOMCommandEvent(mozilla::dom::EventTarget* aOwner,
                     nsPresContext* aPresContext,
                     mozilla::WidgetCommandEvent* aEvent);
-  virtual ~nsDOMCommandEvent();
 
   NS_DECL_ISUPPORTS_INHERITED
 
   NS_DECL_NSIDOMCOMMANDEVENT
 
   // Forward to base class
   NS_FORWARD_TO_NSDOMEVENT
 
--- a/content/events/src/nsDOMCompositionEvent.cpp
+++ b/content/events/src/nsDOMCompositionEvent.cpp
@@ -26,28 +26,20 @@ nsDOMCompositionEvent::nsDOMCompositionE
     mEvent->time = PR_Now();
 
     // XXX compositionstart is cancelable in draft of DOM3 Events.
     //     However, it doesn't make sence for us, we cannot cancel composition
     //     when we sends compositionstart event.
     mEvent->mFlags.mCancelable = false;
   }
 
-  mData = static_cast<WidgetCompositionEvent*>(mEvent)->data;
+  mData = mEvent->AsCompositionEvent()->data;
   // TODO: Native event should have locale information.
 }
 
-nsDOMCompositionEvent::~nsDOMCompositionEvent()
-{
-  if (mEventIsInternal) {
-    delete static_cast<WidgetCompositionEvent*>(mEvent);
-    mEvent = nullptr;
-  }
-}
-
 NS_IMPL_ADDREF_INHERITED(nsDOMCompositionEvent, nsDOMUIEvent)
 NS_IMPL_RELEASE_INHERITED(nsDOMCompositionEvent, nsDOMUIEvent)
 
 NS_INTERFACE_MAP_BEGIN(nsDOMCompositionEvent)
   NS_INTERFACE_MAP_ENTRY(nsIDOMCompositionEvent)
 NS_INTERFACE_MAP_END_INHERITING(nsDOMUIEvent)
 
 NS_IMETHODIMP
--- a/content/events/src/nsDOMCompositionEvent.h
+++ b/content/events/src/nsDOMCompositionEvent.h
@@ -5,26 +5,25 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef nsDOMCompositionEvent_h__
 #define nsDOMCompositionEvent_h__
 
 #include "nsDOMUIEvent.h"
 #include "nsIDOMCompositionEvent.h"
 #include "mozilla/dom/CompositionEventBinding.h"
-#include "mozilla/TextEvents.h"
+#include "mozilla/EventForwards.h"
 
 class nsDOMCompositionEvent : public nsDOMUIEvent,
                               public nsIDOMCompositionEvent
 {
 public:
   nsDOMCompositionEvent(mozilla::dom::EventTarget* aOwner,
                         nsPresContext* aPresContext,
                         mozilla::WidgetCompositionEvent* aEvent);
-  virtual ~nsDOMCompositionEvent();
 
   NS_DECL_ISUPPORTS_INHERITED
   NS_FORWARD_TO_NSDOMUIEVENT
   NS_DECL_NSIDOMCOMPOSITIONEVENT
 
   virtual JSObject* WrapObject(JSContext* aCx,
                                JS::Handle<JSObject*> aScope) MOZ_OVERRIDE
   {
--- a/content/events/src/nsDOMDragEvent.cpp
+++ b/content/events/src/nsDOMDragEvent.cpp
@@ -8,41 +8,32 @@
 #include "nsIDOMDataTransfer.h"
 #include "prtime.h"
 #include "mozilla/MouseEvents.h"
 
 using namespace mozilla;
 
 nsDOMDragEvent::nsDOMDragEvent(mozilla::dom::EventTarget* aOwner,
                                nsPresContext* aPresContext,
-                               WidgetInputEvent* aEvent)
+                               WidgetDragEvent* aEvent)
   : nsDOMMouseEvent(aOwner, aPresContext, aEvent ? aEvent :
                     new WidgetDragEvent(false, 0, nullptr))
 {
   if (aEvent) {
     mEventIsInternal = false;
   }
   else {
     mEventIsInternal = true;
     mEvent->time = PR_Now();
     mEvent->refPoint.x = mEvent->refPoint.y = 0;
     static_cast<WidgetMouseEvent*>(mEvent)->inputSource =
       nsIDOMMouseEvent::MOZ_SOURCE_UNKNOWN;
   }
 }
 
-nsDOMDragEvent::~nsDOMDragEvent()
-{
-  if (mEventIsInternal) {
-    if (mEvent->eventStructType == NS_DRAG_EVENT)
-      delete static_cast<WidgetDragEvent*>(mEvent);
-    mEvent = nullptr;
-  }
-}
-
 NS_IMPL_ADDREF_INHERITED(nsDOMDragEvent, nsDOMMouseEvent)
 NS_IMPL_RELEASE_INHERITED(nsDOMDragEvent, nsDOMMouseEvent)
 
 NS_INTERFACE_MAP_BEGIN(nsDOMDragEvent)
   NS_INTERFACE_MAP_ENTRY(nsIDOMDragEvent)
 NS_INTERFACE_MAP_END_INHERITING(nsDOMMouseEvent)
 
 NS_IMETHODIMP
@@ -58,18 +49,17 @@ nsDOMDragEvent::InitDragEvent(const nsAS
 {
   nsresult rv = nsDOMMouseEvent::InitMouseEvent(aType, aCanBubble, aCancelable,
                   aView, aDetail, aScreenX, aScreenY, aClientX, aClientY,
                   aCtrlKey, aAltKey, aShiftKey, aMetaKey, aButton,
                   aRelatedTarget);
   NS_ENSURE_SUCCESS(rv, rv);
 
   if (mEventIsInternal && mEvent) {
-    WidgetDragEvent* dragEvent = static_cast<WidgetDragEvent*>(mEvent);
-    dragEvent->dataTransfer = aDataTransfer;
+    mEvent->AsDragEvent()->dataTransfer = aDataTransfer;
   }
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsDOMDragEvent::GetDataTransfer(nsIDOMDataTransfer** aDataTransfer)
 {
@@ -84,17 +74,17 @@ nsDOMDragEvent::GetDataTransfer()
   // with the drag. It is initialized when an attempt is made to retrieve it
   // rather that when the event is created to avoid duplicating the data when
   // no listener ever uses it.
   if (!mEvent || mEvent->eventStructType != NS_DRAG_EVENT) {
     NS_WARNING("Tried to get dataTransfer from non-drag event!");
     return nullptr;
   }
 
-  WidgetDragEvent* dragEvent = static_cast<WidgetDragEvent*>(mEvent);
+  WidgetDragEvent* dragEvent = mEvent->AsDragEvent();
   // for synthetic events, just use the supplied data transfer object even if null
   if (!mEventIsInternal) {
     nsresult rv = nsContentUtils::SetDataTransferInEvent(dragEvent);
     NS_ENSURE_SUCCESS(rv, nullptr);
   }
 
   return dragEvent->dataTransfer;
 }
--- a/content/events/src/nsDOMDragEvent.h
+++ b/content/events/src/nsDOMDragEvent.h
@@ -12,18 +12,17 @@
 #include "mozilla/EventForwards.h"
 
 class nsDOMDragEvent : public nsDOMMouseEvent,
                        public nsIDOMDragEvent
 {
 public:
   nsDOMDragEvent(mozilla::dom::EventTarget* aOwner,
                  nsPresContext* aPresContext,
-                 mozilla::WidgetInputEvent* aEvent);
-  virtual ~nsDOMDragEvent();
+                 mozilla::WidgetDragEvent* aEvent);
 
   NS_DECL_ISUPPORTS_INHERITED
 
   NS_DECL_NSIDOMDRAGEVENT
   
   NS_FORWARD_TO_NSDOMMOUSEEVENT
 
   virtual JSObject* WrapObject(JSContext* aCx,
--- a/content/events/src/nsDOMEvent.cpp
+++ b/content/events/src/nsDOMEvent.cpp
@@ -138,34 +138,32 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(ns
     tmp->mEvent->target = nullptr;
     tmp->mEvent->currentTarget = nullptr;
     tmp->mEvent->originalTarget = nullptr;
     switch (tmp->mEvent->eventStructType) {
       case NS_MOUSE_EVENT:
       case NS_MOUSE_SCROLL_EVENT:
       case NS_WHEEL_EVENT:
       case NS_SIMPLE_GESTURE_EVENT:
-        static_cast<WidgetMouseEventBase*>(tmp->mEvent)->relatedTarget =
-          nullptr;
+        tmp->mEvent->AsMouseEventBase()->relatedTarget = nullptr;
         break;
-      case NS_DRAG_EVENT:
-        static_cast<WidgetDragEvent*>(tmp->mEvent)->dataTransfer = nullptr;
-        static_cast<WidgetMouseEventBase*>(tmp->mEvent)->relatedTarget =
-          nullptr;
+      case NS_DRAG_EVENT: {
+        WidgetDragEvent* dragEvent = tmp->mEvent->AsDragEvent();
+        dragEvent->dataTransfer = nullptr;
+        dragEvent->relatedTarget = nullptr;
         break;
+      }
       case NS_CLIPBOARD_EVENT:
-        static_cast<InternalClipboardEvent*>(tmp->mEvent)->clipboardData =
-          nullptr;
+        tmp->mEvent->AsClipboardEvent()->clipboardData = nullptr;
         break;
       case NS_MUTATION_EVENT:
-        static_cast<InternalMutationEvent*>(tmp->mEvent)->mRelatedNode =
-          nullptr;
+        tmp->mEvent->AsMutationEvent()->mRelatedNode = nullptr;
         break;
       case NS_FOCUS_EVENT:
-        static_cast<InternalFocusEvent*>(tmp->mEvent)->relatedTarget = nullptr;
+        tmp->mEvent->AsFocusEvent()->relatedTarget = nullptr;
         break;
       default:
         break;
     }
   }
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mPresContext);
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mExplicitOriginalTarget);
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mOwner);
@@ -178,41 +176,37 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(
     NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mEvent->currentTarget)
     NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mEvent->originalTarget)
     switch (tmp->mEvent->eventStructType) {
       case NS_MOUSE_EVENT:
       case NS_MOUSE_SCROLL_EVENT:
       case NS_WHEEL_EVENT:
       case NS_SIMPLE_GESTURE_EVENT:
         NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mEvent->relatedTarget");
-        cb.NoteXPCOMChild(
-          static_cast<WidgetMouseEventBase*>(tmp->mEvent)->relatedTarget);
+        cb.NoteXPCOMChild(tmp->mEvent->AsMouseEventBase()->relatedTarget);
         break;
-      case NS_DRAG_EVENT:
+      case NS_DRAG_EVENT: {
+        WidgetDragEvent* dragEvent = tmp->mEvent->AsDragEvent();
         NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mEvent->dataTransfer");
-        cb.NoteXPCOMChild(
-          static_cast<WidgetDragEvent*>(tmp->mEvent)->dataTransfer);
+        cb.NoteXPCOMChild(dragEvent->dataTransfer);
         NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mEvent->relatedTarget");
-        cb.NoteXPCOMChild(
-          static_cast<WidgetMouseEventBase*>(tmp->mEvent)->relatedTarget);
+        cb.NoteXPCOMChild(dragEvent->relatedTarget);
         break;
+      }
       case NS_CLIPBOARD_EVENT:
         NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mEvent->clipboardData");
-        cb.NoteXPCOMChild(
-          static_cast<InternalClipboardEvent*>(tmp->mEvent)->clipboardData);
+        cb.NoteXPCOMChild(tmp->mEvent->AsClipboardEvent()->clipboardData);
         break;
       case NS_MUTATION_EVENT:
         NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mEvent->mRelatedNode");
-        cb.NoteXPCOMChild(
-          static_cast<InternalMutationEvent*>(tmp->mEvent)->mRelatedNode);
+        cb.NoteXPCOMChild(tmp->mEvent->AsMutationEvent()->mRelatedNode);
         break;
       case NS_FOCUS_EVENT:
         NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mEvent->relatedTarget");
-        cb.NoteXPCOMChild(
-          static_cast<InternalFocusEvent*>(tmp->mEvent)->relatedTarget);
+        cb.NoteXPCOMChild(tmp->mEvent->AsFocusEvent()->relatedTarget);
         break;
       default:
         break;
     }
   }
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPresContext)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mExplicitOriginalTarget)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOwner)
@@ -527,26 +521,25 @@ nsDOMEvent::DuplicatePrivateData()
       // Not copying widget, it is a weak reference.
       WidgetGUIEvent* guiEvent = new WidgetGUIEvent(false, msg, nullptr);
       guiEvent->AssignGUIEventData(*oldGUIEvent, true);
       newEvent = guiEvent;
       break;
     }
     case NS_INPUT_EVENT:
     {
-      WidgetInputEvent* oldInputEvent = static_cast<WidgetInputEvent*>(mEvent);
+      WidgetInputEvent* oldInputEvent = mEvent->AsInputEvent();
       WidgetInputEvent* inputEvent = new WidgetInputEvent(false, msg, nullptr);
       inputEvent->AssignInputEventData(*oldInputEvent, true);
       newEvent = inputEvent;
       break;
     }
     case NS_KEY_EVENT:
     {
-      WidgetKeyboardEvent* oldKeyEvent =
-        static_cast<WidgetKeyboardEvent*>(mEvent);
+      WidgetKeyboardEvent* oldKeyEvent = mEvent->AsKeyboardEvent();
       WidgetKeyboardEvent* keyEvent =
         new WidgetKeyboardEvent(false, msg, nullptr);
       keyEvent->AssignKeyEventData(*oldKeyEvent, true);
       newEvent = keyEvent;
       break;
     }
     case NS_MOUSE_EVENT:
     {
@@ -554,139 +547,133 @@ nsDOMEvent::DuplicatePrivateData()
       WidgetMouseEvent* mouseEvent =
         new WidgetMouseEvent(false, msg, nullptr, oldMouseEvent->reason);
       mouseEvent->AssignMouseEventData(*oldMouseEvent, true);
       newEvent = mouseEvent;
       break;
     }
     case NS_DRAG_EVENT:
     {
-      WidgetDragEvent* oldDragEvent = static_cast<WidgetDragEvent*>(mEvent);
+      WidgetDragEvent* oldDragEvent = mEvent->AsDragEvent();
       WidgetDragEvent* dragEvent = new WidgetDragEvent(false, msg, nullptr);
       dragEvent->AssignDragEventData(*oldDragEvent, true);
       newEvent = dragEvent;
       break;
     }
     case NS_CLIPBOARD_EVENT:
     {
-      InternalClipboardEvent* oldClipboardEvent =
-        static_cast<InternalClipboardEvent*>(mEvent);
+      InternalClipboardEvent* oldClipboardEvent = mEvent->AsClipboardEvent();
       InternalClipboardEvent* clipboardEvent =
         new InternalClipboardEvent(false, msg);
       clipboardEvent->AssignClipboardEventData(*oldClipboardEvent, true);
       newEvent = clipboardEvent;
       break;
     }
     case NS_SCRIPT_ERROR_EVENT:
     {
       InternalScriptErrorEvent* oldScriptErrorEvent =
-        static_cast<InternalScriptErrorEvent*>(mEvent);
+        mEvent->AsScriptErrorEvent();
       InternalScriptErrorEvent* scriptErrorEvent =
         new InternalScriptErrorEvent(false, msg);
       scriptErrorEvent->AssignScriptErrorEventData(*oldScriptErrorEvent, true);
       newEvent = scriptErrorEvent;
       break;
     }
     case NS_TEXT_EVENT:
     {
-      WidgetTextEvent* oldTextEvent = static_cast<WidgetTextEvent*>(mEvent);
+      WidgetTextEvent* oldTextEvent = mEvent->AsTextEvent();
       WidgetTextEvent* textEvent = new WidgetTextEvent(false, msg, nullptr);
       textEvent->AssignTextEventData(*oldTextEvent, true);
       newEvent = textEvent;
       break;
     }
     case NS_COMPOSITION_EVENT:
     {
       WidgetCompositionEvent* compositionEvent =
         new WidgetCompositionEvent(false, msg, nullptr);
       WidgetCompositionEvent* oldCompositionEvent =
-        static_cast<WidgetCompositionEvent*>(mEvent);
+        mEvent->AsCompositionEvent();
       compositionEvent->AssignCompositionEventData(*oldCompositionEvent, true);
       newEvent = compositionEvent;
       break;
     }
     case NS_MOUSE_SCROLL_EVENT:
     {
       WidgetMouseScrollEvent* oldMouseScrollEvent =
-        static_cast<WidgetMouseScrollEvent*>(mEvent);
+        mEvent->AsMouseScrollEvent();
       WidgetMouseScrollEvent* mouseScrollEvent =
         new WidgetMouseScrollEvent(false, msg, nullptr);
       mouseScrollEvent->AssignMouseScrollEventData(*oldMouseScrollEvent, true);
       newEvent = mouseScrollEvent;
       break;
     }
     case NS_WHEEL_EVENT:
     {
       WidgetWheelEvent* oldWheelEvent = static_cast<WidgetWheelEvent*>(mEvent);
       WidgetWheelEvent* wheelEvent = new WidgetWheelEvent(false, msg, nullptr);
       wheelEvent->AssignWheelEventData(*oldWheelEvent, true);
       newEvent = wheelEvent;
       break;
     }
     case NS_SCROLLPORT_EVENT:
     {
-      InternalScrollPortEvent* oldScrollPortEvent =
-        static_cast<InternalScrollPortEvent*>(mEvent);
+      InternalScrollPortEvent* oldScrollPortEvent = mEvent->AsScrollPortEvent();
       InternalScrollPortEvent* scrollPortEvent =
         new InternalScrollPortEvent(false, msg, nullptr);
       scrollPortEvent->AssignScrollPortEventData(*oldScrollPortEvent, true);
       newEvent = scrollPortEvent;
       break;
     }
     case NS_SCROLLAREA_EVENT:
     {
-      InternalScrollAreaEvent* oldScrollAreaEvent =
-        static_cast<InternalScrollAreaEvent*>(mEvent);
+      InternalScrollAreaEvent* oldScrollAreaEvent = mEvent->AsScrollAreaEvent();
       InternalScrollAreaEvent* scrollAreaEvent = 
         new InternalScrollAreaEvent(false, msg, nullptr);
       scrollAreaEvent->AssignScrollAreaEventData(*oldScrollAreaEvent, true);
       newEvent = scrollAreaEvent;
       break;
     }
     case NS_MUTATION_EVENT:
     {
       InternalMutationEvent* mutationEvent =
         new InternalMutationEvent(false, msg);
-      InternalMutationEvent* oldMutationEvent =
-        static_cast<InternalMutationEvent*>(mEvent);
+      InternalMutationEvent* oldMutationEvent = mEvent->AsMutationEvent();
       mutationEvent->AssignMutationEventData(*oldMutationEvent, true);
       newEvent = mutationEvent;
       break;
     }
     case NS_FORM_EVENT:
     {
-      InternalFormEvent* oldFormEvent = static_cast<InternalFormEvent*>(mEvent);
+      InternalFormEvent* oldFormEvent = mEvent->AsFormEvent();
       InternalFormEvent* formEvent = new InternalFormEvent(false, msg);
       formEvent->AssignFormEventData(*oldFormEvent, true);
       newEvent = formEvent;
       break;
     }
     case NS_FOCUS_EVENT:
     {
       InternalFocusEvent* newFocusEvent = new InternalFocusEvent(false, msg);
-      InternalFocusEvent* oldFocusEvent =
-        static_cast<InternalFocusEvent*>(mEvent);
+      InternalFocusEvent* oldFocusEvent = mEvent->AsFocusEvent();
       newFocusEvent->AssignFocusEventData(*oldFocusEvent, true);
       newEvent = newFocusEvent;
       break;
     }
     case NS_COMMAND_EVENT:
     {
-      WidgetCommandEvent* oldCommandEvent =
-        static_cast<WidgetCommandEvent*>(mEvent);
+      WidgetCommandEvent* oldCommandEvent = mEvent->AsCommandEvent();
       WidgetCommandEvent* commandEvent =
         new WidgetCommandEvent(false, mEvent->userType,
                                oldCommandEvent->command, nullptr);
       commandEvent->AssignCommandEventData(*oldCommandEvent, true);
       newEvent = commandEvent;
       break;
     }
     case NS_UI_EVENT:
     {
-      InternalUIEvent* oldUIEvent = static_cast<InternalUIEvent*>(mEvent);
+      InternalUIEvent* oldUIEvent = mEvent->AsUIEvent();
       InternalUIEvent* uiEvent =
         new InternalUIEvent(false, msg, oldUIEvent->detail);
       uiEvent->AssignUIEventData(*oldUIEvent, true);
       newEvent = uiEvent;
       break;
     }
     case NS_SVGZOOM_EVENT:
     {
@@ -694,63 +681,61 @@ nsDOMEvent::DuplicatePrivateData()
       WidgetGUIEvent* guiEvent = new WidgetGUIEvent(false, msg, nullptr);
       guiEvent->eventStructType = NS_SVGZOOM_EVENT;
       guiEvent->AssignGUIEventData(*oldGUIEvent, true);
       newEvent = guiEvent;
       break;
     }
     case NS_SMIL_TIME_EVENT:
     {
-      InternalUIEvent* oldUIEvent = static_cast<InternalUIEvent*>(mEvent);
+      InternalUIEvent* oldUIEvent = mEvent->AsUIEvent();
       InternalUIEvent* uiEvent = new InternalUIEvent(false, msg, 0);
       uiEvent->eventStructType = NS_SMIL_TIME_EVENT;
       uiEvent->AssignUIEventData(*oldUIEvent, true);
       newEvent = uiEvent;
       break;
     }
     case NS_SIMPLE_GESTURE_EVENT:
     {
       WidgetSimpleGestureEvent* oldSimpleGestureEvent =
-        static_cast<WidgetSimpleGestureEvent*>(mEvent);
+        mEvent->AsSimpleGestureEvent();
       WidgetSimpleGestureEvent* simpleGestureEvent = 
         new WidgetSimpleGestureEvent(false, msg, nullptr, 0, 0.0);
       simpleGestureEvent->
         AssignSimpleGestureEventData(*oldSimpleGestureEvent, true);
       newEvent = simpleGestureEvent;
       break;
     }
     case NS_TRANSITION_EVENT:
     {
-      InternalTransitionEvent* oldTransitionEvent =
-        static_cast<InternalTransitionEvent*>(mEvent);
+      InternalTransitionEvent* oldTransitionEvent = mEvent->AsTransitionEvent();
       InternalTransitionEvent* transitionEvent =
          new InternalTransitionEvent(false, msg,
                                      oldTransitionEvent->propertyName,
                                      oldTransitionEvent->elapsedTime,
                                      oldTransitionEvent->pseudoElement);
       transitionEvent->AssignTransitionEventData(*oldTransitionEvent, true);
       newEvent = transitionEvent;
       break;
     }
     case NS_ANIMATION_EVENT:
     {
-      InternalAnimationEvent* oldAnimationEvent =
-        static_cast<InternalAnimationEvent*>(mEvent);
+      InternalAnimationEvent* oldAnimationEvent = mEvent->AsAnimationEvent();
       InternalAnimationEvent* animationEvent =
         new InternalAnimationEvent(false, msg,
                                    oldAnimationEvent->animationName,
                                    oldAnimationEvent->elapsedTime,
                                    oldAnimationEvent->pseudoElement);
       animationEvent->AssignAnimationEventData(*oldAnimationEvent, true);
       newEvent = animationEvent;
       break;
     }
     case NS_TOUCH_EVENT:
     {
-      WidgetTouchEvent* oldTouchEvent = static_cast<WidgetTouchEvent*>(mEvent);
+      WidgetTouchEvent* oldTouchEvent = mEvent->AsTouchEvent();
       WidgetTouchEvent* touchEvent = new WidgetTouchEvent(false, oldTouchEvent);
       touchEvent->AssignTouchEventData(*oldTouchEvent, true);
       newEvent = touchEvent;
       break;
     }
     default:
     {
       NS_WARNING("Unknown event type!!!");
@@ -894,17 +879,17 @@ nsDOMEvent::GetEventPopupControlState(Wi
       case NS_XUL_COMMAND:
         abuse = openControlled;
         break;
       }
     }
     break;
   case NS_KEY_EVENT :
     if (aEvent->mFlags.mIsTrusted) {
-      uint32_t key = static_cast<WidgetKeyboardEvent*>(aEvent)->keyCode;
+      uint32_t key = aEvent->AsKeyboardEvent()->keyCode;
       switch(aEvent->message) {
       case NS_KEY_PRESS :
         // return key on focused button. see note at NS_MOUSE_CLICK.
         if (key == nsIDOMKeyEvent::DOM_VK_RETURN)
           abuse = openAllowed;
         else if (::PopupAllowedForEvent("keypress"))
           abuse = openControlled;
         break;
--- a/content/events/src/nsDOMFocusEvent.cpp
+++ b/content/events/src/nsDOMFocusEvent.cpp
@@ -22,50 +22,42 @@ nsDOMFocusEvent::nsDOMFocusEvent(mozilla
   if (aEvent) {
     mEventIsInternal = false;
   } else {
     mEventIsInternal = true;
     mEvent->time = PR_Now();
   }
 }
 
-nsDOMFocusEvent::~nsDOMFocusEvent()
-{
-  if (mEventIsInternal && mEvent) {
-    delete static_cast<InternalFocusEvent*>(mEvent);
-    mEvent = nullptr;
-  }
-}
-
 /* readonly attribute nsIDOMEventTarget relatedTarget; */
 NS_IMETHODIMP
 nsDOMFocusEvent::GetRelatedTarget(nsIDOMEventTarget** aRelatedTarget)
 {
   NS_ENSURE_ARG_POINTER(aRelatedTarget);
   NS_IF_ADDREF(*aRelatedTarget = GetRelatedTarget());
   return NS_OK;
 }
 
 mozilla::dom::EventTarget*
 nsDOMFocusEvent::GetRelatedTarget()
 {
-  return static_cast<InternalFocusEvent*>(mEvent)->relatedTarget;
+  return mEvent->AsFocusEvent()->relatedTarget;
 }
 
 nsresult
 nsDOMFocusEvent::InitFocusEvent(const nsAString& aType,
                                 bool aCanBubble,
                                 bool aCancelable,
                                 nsIDOMWindow* aView,
                                 int32_t aDetail,
                                 mozilla::dom::EventTarget* aRelatedTarget)
 {
   nsresult rv = nsDOMUIEvent::InitUIEvent(aType, aCanBubble, aCancelable, aView, aDetail);
   NS_ENSURE_SUCCESS(rv, rv);
-  static_cast<InternalFocusEvent*>(mEvent)->relatedTarget = aRelatedTarget;
+  mEvent->AsFocusEvent()->relatedTarget = aRelatedTarget;
   return NS_OK;
 }
 
 already_AddRefed<nsDOMFocusEvent>
 nsDOMFocusEvent::Constructor(const mozilla::dom::GlobalObject& aGlobal,
                              const nsAString& aType,
                              const mozilla::dom::FocusEventInit& aParam,
                              mozilla::ErrorResult& aRv)
--- a/content/events/src/nsDOMFocusEvent.h
+++ b/content/events/src/nsDOMFocusEvent.h
@@ -38,12 +38,11 @@ public:
                                                        mozilla::ErrorResult& aRv);
 protected:
   nsresult InitFocusEvent(const nsAString& aType,
                           bool aCanBubble,
                           bool aCancelable,
                           nsIDOMWindow* aView,
                           int32_t aDetail,
                           mozilla::dom::EventTarget* aRelatedTarget);
-  ~nsDOMFocusEvent();
 };
 
 #endif /* !defined(nsDOMFocusEvent_h_) */
--- a/content/events/src/nsDOMKeyboardEvent.cpp
+++ b/content/events/src/nsDOMKeyboardEvent.cpp
@@ -21,55 +21,71 @@ nsDOMKeyboardEvent::nsDOMKeyboardEvent(m
     mEventIsInternal = false;
   }
   else {
     mEventIsInternal = true;
     mEvent->time = PR_Now();
   }
 }
 
-nsDOMKeyboardEvent::~nsDOMKeyboardEvent()
-{
-  if (mEventIsInternal) {
-    delete static_cast<WidgetKeyboardEvent*>(mEvent);
-    mEvent = nullptr;
-  }
-}
-
 NS_IMPL_ADDREF_INHERITED(nsDOMKeyboardEvent, nsDOMUIEvent)
 NS_IMPL_RELEASE_INHERITED(nsDOMKeyboardEvent, nsDOMUIEvent)
 
 NS_INTERFACE_MAP_BEGIN(nsDOMKeyboardEvent)
   NS_INTERFACE_MAP_ENTRY(nsIDOMKeyEvent)
 NS_INTERFACE_MAP_END_INHERITING(nsDOMUIEvent)
 
+bool
+nsDOMKeyboardEvent::AltKey()
+{
+  return mEvent->AsKeyboardEvent()->IsAlt();
+}
+
 NS_IMETHODIMP
 nsDOMKeyboardEvent::GetAltKey(bool* aIsDown)
 {
   NS_ENSURE_ARG_POINTER(aIsDown);
   *aIsDown = AltKey();
   return NS_OK;
 }
 
+bool
+nsDOMKeyboardEvent::CtrlKey()
+{
+  return mEvent->AsKeyboardEvent()->IsControl();
+}
+
 NS_IMETHODIMP
 nsDOMKeyboardEvent::GetCtrlKey(bool* aIsDown)
 {
   NS_ENSURE_ARG_POINTER(aIsDown);
   *aIsDown = CtrlKey();
   return NS_OK;
 }
 
+bool
+nsDOMKeyboardEvent::ShiftKey()
+{
+  return mEvent->AsKeyboardEvent()->IsShift();
+}
+
 NS_IMETHODIMP
 nsDOMKeyboardEvent::GetShiftKey(bool* aIsDown)
 {
   NS_ENSURE_ARG_POINTER(aIsDown);
   *aIsDown = ShiftKey();
   return NS_OK;
 }
 
+bool
+nsDOMKeyboardEvent::MetaKey()
+{
+  return mEvent->AsKeyboardEvent()->IsMeta();
+}
+
 NS_IMETHODIMP
 nsDOMKeyboardEvent::GetMetaKey(bool* aIsDown)
 {
   NS_ENSURE_ARG_POINTER(aIsDown);
   *aIsDown = MetaKey();
   return NS_OK;
 }
 
@@ -82,17 +98,17 @@ nsDOMKeyboardEvent::GetModifierState(con
   *aState = GetModifierState(aKey);
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsDOMKeyboardEvent::GetKey(nsAString& aKeyName)
 {
   if (!mEventIsInternal) {
-    static_cast<WidgetKeyboardEvent*>(mEvent)->GetDOMKeyName(aKeyName);
+    mEvent->AsKeyboardEvent()->GetDOMKeyName(aKeyName);
   }
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsDOMKeyboardEvent::GetCharCode(uint32_t* aCharCode)
 {
   NS_ENSURE_ARG_POINTER(aCharCode);
@@ -103,17 +119,17 @@ nsDOMKeyboardEvent::GetCharCode(uint32_t
 uint32_t
 nsDOMKeyboardEvent::CharCode()
 {
   switch (mEvent->message) {
   case NS_KEY_UP:
   case NS_KEY_DOWN:
     return 0;
   case NS_KEY_PRESS:
-    return static_cast<WidgetKeyboardEvent*>(mEvent)->charCode;
+    return mEvent->AsKeyboardEvent()->charCode;
   }
   return 0;
 }
 
 NS_IMETHODIMP
 nsDOMKeyboardEvent::GetKeyCode(uint32_t* aKeyCode)
 {
   NS_ENSURE_ARG_POINTER(aKeyCode);
@@ -123,33 +139,33 @@ nsDOMKeyboardEvent::GetKeyCode(uint32_t*
 
 uint32_t
 nsDOMKeyboardEvent::KeyCode()
 {
   switch (mEvent->message) {
   case NS_KEY_UP:
   case NS_KEY_PRESS:
   case NS_KEY_DOWN:
-    return static_cast<WidgetKeyboardEvent*>(mEvent)->keyCode;
+    return mEvent->AsKeyboardEvent()->keyCode;
   }
   return 0;
 }
 
 uint32_t
 nsDOMKeyboardEvent::Which()
 {
   switch (mEvent->message) {
     case NS_KEY_UP:
     case NS_KEY_DOWN:
       return KeyCode();
     case NS_KEY_PRESS:
       //Special case for 4xp bug 62878.  Try to make value of which
       //more closely mirror the values that 4.x gave for RETURN and BACKSPACE
       {
-        uint32_t keyCode = static_cast<WidgetKeyboardEvent*>(mEvent)->keyCode;
+        uint32_t keyCode = mEvent->AsKeyboardEvent()->keyCode;
         if (keyCode == NS_VK_RETURN || keyCode == NS_VK_BACK) {
           return keyCode;
         }
         return CharCode();
       }
   }
 
   return 0;
@@ -159,26 +175,32 @@ NS_IMETHODIMP
 nsDOMKeyboardEvent::GetLocation(uint32_t* aLocation)
 {
   NS_ENSURE_ARG_POINTER(aLocation);
 
   *aLocation = Location();
   return NS_OK;
 }
 
+uint32_t
+nsDOMKeyboardEvent::Location()
+{
+  return mEvent->AsKeyboardEvent()->location;
+}
+
 NS_IMETHODIMP
 nsDOMKeyboardEvent::InitKeyEvent(const nsAString& aType, bool aCanBubble, bool aCancelable,
                                  nsIDOMWindow* aView, bool aCtrlKey, bool aAltKey,
                                  bool aShiftKey, bool aMetaKey,
                                  uint32_t aKeyCode, uint32_t aCharCode)
 {
   nsresult rv = nsDOMUIEvent::InitUIEvent(aType, aCanBubble, aCancelable, aView, 0);
   NS_ENSURE_SUCCESS(rv, rv);
 
-  WidgetKeyboardEvent* keyEvent = static_cast<WidgetKeyboardEvent*>(mEvent);
+  WidgetKeyboardEvent* keyEvent = mEvent->AsKeyboardEvent();
   keyEvent->InitBasicModifiers(aCtrlKey, aAltKey, aShiftKey, aMetaKey);
   keyEvent->keyCode = aKeyCode;
   keyEvent->charCode = aCharCode;
 
   return NS_OK;
 }
 
 nsresult NS_NewDOMKeyboardEvent(nsIDOMEvent** aInstancePtrResult,
--- a/content/events/src/nsDOMKeyboardEvent.h
+++ b/content/events/src/nsDOMKeyboardEvent.h
@@ -5,74 +5,53 @@
 
 #ifndef nsDOMKeyboardEvent_h__
 #define nsDOMKeyboardEvent_h__
 
 #include "nsIDOMKeyEvent.h"
 #include "nsDOMUIEvent.h"
 #include "mozilla/EventForwards.h"
 #include "mozilla/dom/KeyboardEventBinding.h"
-#include "mozilla/TextEvents.h"
 
 class nsDOMKeyboardEvent : public nsDOMUIEvent,
                            public nsIDOMKeyEvent
 {
 public:
   nsDOMKeyboardEvent(mozilla::dom::EventTarget* aOwner,
                      nsPresContext* aPresContext,
                      mozilla::WidgetKeyboardEvent* aEvent);
-  virtual ~nsDOMKeyboardEvent();
 
   NS_DECL_ISUPPORTS_INHERITED
 
   // nsIDOMKeyEvent Interface
   NS_DECL_NSIDOMKEYEVENT
 
   // Forward to base class
   NS_FORWARD_TO_NSDOMUIEVENT
 
   virtual JSObject* WrapObject(JSContext* aCx,
                                JS::Handle<JSObject*> aScope) MOZ_OVERRIDE
   {
     return mozilla::dom::KeyboardEventBinding::Wrap(aCx, aScope, this);
   }
 
-  bool AltKey()
-  {
-    return static_cast<mozilla::WidgetInputEvent*>(mEvent)->IsAlt();
-  }
-
-  bool CtrlKey()
-  {
-    return static_cast<mozilla::WidgetInputEvent*>(mEvent)->IsControl();
-  }
-
-  bool ShiftKey()
-  {
-    return static_cast<mozilla::WidgetInputEvent*>(mEvent)->IsShift();
-  }
-
-  bool MetaKey()
-  {
-    return static_cast<mozilla::WidgetInputEvent*>(mEvent)->IsMeta();
-  }
+  bool AltKey();
+  bool CtrlKey();
+  bool ShiftKey();
+  bool MetaKey();
 
   bool GetModifierState(const nsAString& aKey)
   {
     return GetModifierStateInternal(aKey);
   }
 
   uint32_t CharCode();
   uint32_t KeyCode();
   virtual uint32_t Which() MOZ_OVERRIDE;
-
-  uint32_t Location()
-  {
-    return static_cast<mozilla::WidgetKeyboardEvent*>(mEvent)->location;
-  }
+  uint32_t Location();
 
   void InitKeyEvent(const nsAString& aType, bool aCanBubble, bool aCancelable,
                     nsIDOMWindow* aView, bool aCtrlKey, bool aAltKey,
                     bool aShiftKey, bool aMetaKey,
                     uint32_t aKeyCode, uint32_t aCharCode,
                     mozilla::ErrorResult& aRv)
   {
     aRv = InitKeyEvent(aType, aCanBubble, aCancelable, aView,
--- a/content/events/src/nsDOMMouseEvent.cpp
+++ b/content/events/src/nsDOMMouseEvent.cpp
@@ -8,17 +8,17 @@
 #include "nsContentUtils.h"
 #include "prtime.h"
 #include "mozilla/MouseEvents.h"
 
 using namespace mozilla;
 
 nsDOMMouseEvent::nsDOMMouseEvent(mozilla::dom::EventTarget* aOwner,
                                  nsPresContext* aPresContext,
-                                 WidgetInputEvent* aEvent)
+                                 WidgetMouseEventBase* aEvent)
   : nsDOMUIEvent(aOwner, aPresContext, aEvent ? aEvent :
                  new WidgetMouseEvent(false, 0, nullptr,
                                       WidgetMouseEvent::eReal))
 {
   // There's no way to make this class' ctor allocate an WidgetMouseScrollEvent.
   // It's not that important, though, since a scroll event is not a real
   // DOM event.
   
@@ -41,32 +41,16 @@ nsDOMMouseEvent::nsDOMMouseEvent(mozilla
                    "Don't dispatch DOM events from synthesized mouse events");
       mDetail = static_cast<WidgetMouseEvent*>(mEvent)->clickCount;
       break;
     default:
       break;
   }
 }
 
-nsDOMMouseEvent::~nsDOMMouseEvent()
-{
-  if (mEventIsInternal && mEvent) {
-    switch (mEvent->eventStructType)
-    {
-      case NS_MOUSE_EVENT:
-        delete static_cast<WidgetMouseEvent*>(mEvent);
-        break;
-      default:
-        delete mEvent;
-        break;
-    }
-    mEvent = nullptr;
-  }
-}
-
 NS_IMPL_ADDREF_INHERITED(nsDOMMouseEvent, nsDOMUIEvent)
 NS_IMPL_RELEASE_INHERITED(nsDOMMouseEvent, nsDOMUIEvent)
 
 NS_INTERFACE_MAP_BEGIN(nsDOMMouseEvent)
   NS_INTERFACE_MAP_ENTRY(nsIDOMMouseEvent)
 NS_INTERFACE_MAP_END_INHERITING(nsDOMUIEvent)
 
 NS_IMETHODIMP
@@ -74,39 +58,36 @@ nsDOMMouseEvent::InitMouseEvent(const ns
                                 nsIDOMWindow* aView, int32_t aDetail, int32_t aScreenX, 
                                 int32_t aScreenY, int32_t aClientX, int32_t aClientY, 
                                 bool aCtrlKey, bool aAltKey, bool aShiftKey, 
                                 bool aMetaKey, uint16_t aButton, nsIDOMEventTarget *aRelatedTarget)
 {
   nsresult rv = nsDOMUIEvent::InitUIEvent(aType, aCanBubble, aCancelable, aView, aDetail);
   NS_ENSURE_SUCCESS(rv, rv);
 
-  switch(mEvent->eventStructType)
-  {
+  switch(mEvent->eventStructType) {
     case NS_MOUSE_EVENT:
     case NS_MOUSE_SCROLL_EVENT:
     case NS_WHEEL_EVENT:
     case NS_DRAG_EVENT:
-    case NS_SIMPLE_GESTURE_EVENT:
-    {
-       static_cast<WidgetMouseEventBase*>(mEvent)->relatedTarget =
-         aRelatedTarget;
-       static_cast<WidgetMouseEventBase*>(mEvent)->button = aButton;
-       WidgetInputEvent* inputEvent = static_cast<WidgetInputEvent*>(mEvent);
-       inputEvent->InitBasicModifiers(aCtrlKey, aAltKey, aShiftKey, aMetaKey);
-       mClientPoint.x = aClientX;
-       mClientPoint.y = aClientY;
-       inputEvent->refPoint.x = aScreenX;
-       inputEvent->refPoint.y = aScreenY;
+    case NS_SIMPLE_GESTURE_EVENT: {
+      WidgetMouseEventBase* mouseEventBase = mEvent->AsMouseEventBase();
+      mouseEventBase->relatedTarget = aRelatedTarget;
+      mouseEventBase->button = aButton;
+      mouseEventBase->InitBasicModifiers(aCtrlKey, aAltKey, aShiftKey, aMetaKey);
+      mClientPoint.x = aClientX;
+      mClientPoint.y = aClientY;
+      mouseEventBase->refPoint.x = aScreenX;
+      mouseEventBase->refPoint.y = aScreenY;
 
-       if (mEvent->eventStructType == NS_MOUSE_EVENT) {
-         WidgetMouseEvent* mouseEvent = static_cast<WidgetMouseEvent*>(mEvent);
-         mouseEvent->clickCount = aDetail;
-       }
-       break;
+      if (mEvent->eventStructType == NS_MOUSE_EVENT) {
+        WidgetMouseEvent* mouseEvent = static_cast<WidgetMouseEvent*>(mEvent);
+        mouseEvent->clickCount = aDetail;
+      }
+      break;
     }
     default:
        break;
   }
 
   return NS_OK;
 }   
 
@@ -136,17 +117,17 @@ nsDOMMouseEvent::InitMouseEvent(const ns
   NS_ENSURE_SUCCESS(rv, rv);
 
   switch(mEvent->eventStructType) {
     case NS_MOUSE_EVENT:
     case NS_MOUSE_SCROLL_EVENT:
     case NS_WHEEL_EVENT:
     case NS_DRAG_EVENT:
     case NS_SIMPLE_GESTURE_EVENT:
-      static_cast<WidgetInputEvent*>(mEvent)->modifiers = modifiers;
+      mEvent->AsInputEvent()->modifiers = modifiers;
       return NS_OK;
     default:
       MOZ_CRASH("There is no space to store the modifiers");
   }
 }
 
 already_AddRefed<nsDOMMouseEvent>
 nsDOMMouseEvent::Constructor(const mozilla::dom::GlobalObject& aGlobal,
@@ -166,17 +147,17 @@ nsDOMMouseEvent::Constructor(const mozil
   e->SetTrusted(trusted);
 
   switch (e->mEvent->eventStructType) {
     case NS_MOUSE_EVENT:
     case NS_MOUSE_SCROLL_EVENT:
     case NS_WHEEL_EVENT:
     case NS_DRAG_EVENT:
     case NS_SIMPLE_GESTURE_EVENT:
-      static_cast<WidgetMouseEventBase*>(e->mEvent)->buttons = aParam.mButtons;
+      e->mEvent->AsMouseEventBase()->buttons = aParam.mButtons;
       break;
     default:
       break;
   }
 
   return e.forget();
 }
 
@@ -189,18 +170,19 @@ nsDOMMouseEvent::InitNSMouseEvent(const 
                                   float aPressure, uint16_t aInputSource)
 {
   nsresult rv = nsDOMMouseEvent::InitMouseEvent(aType, aCanBubble, aCancelable,
                                                 aView, aDetail, aScreenX, aScreenY,
                                                 aClientX, aClientY, aCtrlKey, aAltKey, aShiftKey,
                                                 aMetaKey, aButton, aRelatedTarget);
   NS_ENSURE_SUCCESS(rv, rv);
 
-  static_cast<WidgetMouseEventBase*>(mEvent)->pressure = aPressure;
-  static_cast<WidgetMouseEventBase*>(mEvent)->inputSource = aInputSource;
+  WidgetMouseEventBase* mouseEventBase = mEvent->AsMouseEventBase();
+  mouseEventBase->pressure = aPressure;
+  mouseEventBase->inputSource = aInputSource;
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsDOMMouseEvent::GetButton(uint16_t* aButton)
 {
   NS_ENSURE_ARG_POINTER(aButton);
   *aButton = Button();
@@ -212,17 +194,17 @@ nsDOMMouseEvent::Button()
 {
   switch(mEvent->eventStructType)
   {
     case NS_MOUSE_EVENT:
     case NS_MOUSE_SCROLL_EVENT:
     case NS_WHEEL_EVENT:
     case NS_DRAG_EVENT:
     case NS_SIMPLE_GESTURE_EVENT:
-      return static_cast<WidgetMouseEventBase*>(mEvent)->button;
+      return mEvent->AsMouseEventBase()->button;
     default:
       NS_WARNING("Tried to get mouse button for non-mouse event!");
       return WidgetMouseEvent::eLeftButton;
   }
 }
 
 NS_IMETHODIMP
 nsDOMMouseEvent::GetButtons(uint16_t* aButtons)
@@ -237,17 +219,17 @@ nsDOMMouseEvent::Buttons()
 {
   switch(mEvent->eventStructType)
   {
     case NS_MOUSE_EVENT:
     case NS_MOUSE_SCROLL_EVENT:
     case NS_WHEEL_EVENT:
     case NS_DRAG_EVENT:
     case NS_SIMPLE_GESTURE_EVENT:
-      return static_cast<WidgetMouseEventBase*>(mEvent)->buttons;
+      return mEvent->AsMouseEventBase()->buttons;
     default:
       MOZ_CRASH("Tried to get mouse buttons for non-mouse event!");
   }
 }
 
 NS_IMETHODIMP
 nsDOMMouseEvent::GetRelatedTarget(nsIDOMEventTarget** aRelatedTarget)
 {
@@ -262,18 +244,18 @@ nsDOMMouseEvent::GetRelatedTarget()
   nsCOMPtr<mozilla::dom::EventTarget> relatedTarget;
   switch(mEvent->eventStructType)
   {
     case NS_MOUSE_EVENT:
     case NS_MOUSE_SCROLL_EVENT:
     case NS_WHEEL_EVENT:
     case NS_DRAG_EVENT:
     case NS_SIMPLE_GESTURE_EVENT:
-      relatedTarget = do_QueryInterface(
-        static_cast<WidgetMouseEventBase*>(mEvent)->relatedTarget);
+      relatedTarget =
+        do_QueryInterface(mEvent->AsMouseEventBase()->relatedTarget);
       break;
     default:
       break;
   }
 
   if (relatedTarget) {
     nsCOMPtr<nsIContent> content = do_QueryInterface(relatedTarget);
     if (content && content->ChromeOnlyAccess() &&
@@ -367,40 +349,64 @@ int32_t
 nsDOMMouseEvent::ClientY()
 {
   return nsDOMEvent::GetClientCoords(mPresContext,
                                      mEvent,
                                      mEvent->refPoint,
                                      mClientPoint).y;
 }
 
+bool
+nsDOMMouseEvent::AltKey()
+{
+  return mEvent->AsInputEvent()->IsAlt();
+}
+
 NS_IMETHODIMP
 nsDOMMouseEvent::GetAltKey(bool* aIsDown)
 {
   NS_ENSURE_ARG_POINTER(aIsDown);
   *aIsDown = AltKey();
   return NS_OK;
 }
 
+bool
+nsDOMMouseEvent::CtrlKey()
+{
+  return mEvent->AsInputEvent()->IsControl();
+}
+
 NS_IMETHODIMP
 nsDOMMouseEvent::GetCtrlKey(bool* aIsDown)
 {
   NS_ENSURE_ARG_POINTER(aIsDown);
   *aIsDown = CtrlKey();
   return NS_OK;
 }
 
+bool
+nsDOMMouseEvent::ShiftKey()
+{
+  return mEvent->AsInputEvent()->IsShift();
+}
+
 NS_IMETHODIMP
 nsDOMMouseEvent::GetShiftKey(bool* aIsDown)
 {
   NS_ENSURE_ARG_POINTER(aIsDown);
   *aIsDown = ShiftKey();
   return NS_OK;
 }
 
+bool
+nsDOMMouseEvent::MetaKey()
+{
+  return mEvent->AsInputEvent()->IsMeta();
+}
+
 NS_IMETHODIMP
 nsDOMMouseEvent::GetMetaKey(bool* aIsDown)
 {
   NS_ENSURE_ARG_POINTER(aIsDown);
   *aIsDown = MetaKey();
   return NS_OK;
 }
 
@@ -409,32 +415,44 @@ nsDOMMouseEvent::GetModifierState(const 
                                   bool* aState)
 {
   NS_ENSURE_ARG_POINTER(aState);
 
   *aState = GetModifierState(aKey);
   return NS_OK;
 }
 
+float
+nsDOMMouseEvent::MozPressure() const
+{
+  return mEvent->AsMouseEventBase()->pressure;
+}
+
 NS_IMETHODIMP
 nsDOMMouseEvent::GetMozPressure(float* aPressure)
 {
   NS_ENSURE_ARG_POINTER(aPressure);
   *aPressure = MozPressure();
   return NS_OK;
 }
 
+uint16_t
+nsDOMMouseEvent::MozInputSource() const
+{
+  return mEvent->AsMouseEventBase()->inputSource;
+}
+
 NS_IMETHODIMP
 nsDOMMouseEvent::GetMozInputSource(uint16_t* aInputSource)
 {
   NS_ENSURE_ARG_POINTER(aInputSource);
   *aInputSource = MozInputSource();
   return NS_OK;
 }
 
 nsresult NS_NewDOMMouseEvent(nsIDOMEvent** aInstancePtrResult,
                              mozilla::dom::EventTarget* aOwner,
                              nsPresContext* aPresContext,
-                             WidgetInputEvent* aEvent)
+                             WidgetMouseEvent* aEvent)
 {
   nsDOMMouseEvent* it = new nsDOMMouseEvent(aOwner, aPresContext, aEvent);
   return CallQueryInterface(it, aInstancePtrResult);
 }
--- a/content/events/src/nsDOMMouseEvent.h
+++ b/content/events/src/nsDOMMouseEvent.h
@@ -4,26 +4,25 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef nsDOMMouseEvent_h__
 #define nsDOMMouseEvent_h__
 
 #include "nsIDOMMouseEvent.h"
 #include "nsDOMUIEvent.h"
 #include "mozilla/dom/MouseEventBinding.h"
-#include "mozilla/MouseEvents.h"
+#include "mozilla/EventForwards.h"
 
 class nsDOMMouseEvent : public nsDOMUIEvent,
                         public nsIDOMMouseEvent
 {
 public:
   nsDOMMouseEvent(mozilla::dom::EventTarget* aOwner,
                   nsPresContext* aPresContext,
-                  mozilla::WidgetInputEvent* aEvent);
-  virtual ~nsDOMMouseEvent();
+                  mozilla::WidgetMouseEventBase* aEvent);
 
   NS_DECL_ISUPPORTS_INHERITED
 
   // nsIDOMMouseEvent Interface
   NS_DECL_NSIDOMMOUSEEVENT
 
   // Forward to base class
   NS_FORWARD_TO_NSDOMUIEVENT
@@ -39,32 +38,20 @@ public:
   {
     return Button() + 1;
   }
 
   int32_t ScreenX();
   int32_t ScreenY();
   int32_t ClientX();
   int32_t ClientY();
-  bool CtrlKey()
-  {
-    return static_cast<mozilla::WidgetInputEvent*>(mEvent)->IsControl();
-  }
-  bool ShiftKey()
-  {
-    return static_cast<mozilla::WidgetInputEvent*>(mEvent)->IsShift();
-  }
-  bool AltKey()
-  {
-    return static_cast<mozilla::WidgetInputEvent*>(mEvent)->IsAlt();
-  }
-  bool MetaKey()
-  {
-    return static_cast<mozilla::WidgetInputEvent*>(mEvent)->IsMeta();
-  }
+  bool CtrlKey();
+  bool ShiftKey();
+  bool AltKey();
+  bool MetaKey();
   uint16_t Button();
   uint16_t Buttons();
   already_AddRefed<mozilla::dom::EventTarget> GetRelatedTarget();
   void InitMouseEvent(const nsAString & aType, bool aCanBubble, bool aCancelable,
                       nsIDOMWindow* aView, int32_t aDetail, int32_t aScreenX,
                       int32_t aScreenY, int32_t aClientX, int32_t aClientY,
                       bool aCtrlKey, bool aAltKey, bool aShiftKey,
                       bool aMetaKey, uint16_t aButton,
@@ -88,24 +75,18 @@ public:
   int32_t MozMovementX()
   {
     return GetMovementPoint().x;
   }
   int32_t MozMovementY()
   {
     return GetMovementPoint().y;
   }
-  float MozPressure() const
-  {
-    return static_cast<mozilla::WidgetMouseEventBase*>(mEvent)->pressure;
-  }
-  uint16_t MozInputSource() const
-  {
-    return static_cast<mozilla::WidgetMouseEventBase*>(mEvent)->inputSource;
-  }
+  float MozPressure() const;
+  uint16_t MozInputSource() const;
   void InitNSMouseEvent(const nsAString & aType, bool aCanBubble, bool aCancelable,
                         nsIDOMWindow *aView, int32_t aDetail, int32_t aScreenX,
                         int32_t aScreenY, int32_t aClientX, int32_t aClientY,
                         bool aCtrlKey, bool aAltKey, bool aShiftKey,
                         bool aMetaKey, uint16_t aButton,
                         mozilla::dom::EventTarget *aRelatedTarget,
                         float aPressure, uint16_t aInputSource,
                         mozilla::ErrorResult& aRv)
--- a/content/events/src/nsDOMMouseScrollEvent.cpp
+++ b/content/events/src/nsDOMMouseScrollEvent.cpp
@@ -6,50 +6,32 @@
 #include "nsDOMMouseScrollEvent.h"
 #include "prtime.h"
 #include "mozilla/MouseEvents.h"
 
 using namespace mozilla;
 
 nsDOMMouseScrollEvent::nsDOMMouseScrollEvent(mozilla::dom::EventTarget* aOwner,
                                              nsPresContext* aPresContext,
-                                             WidgetInputEvent* aEvent)
+                                             WidgetMouseScrollEvent* aEvent)
   : nsDOMMouseEvent(aOwner, aPresContext,
                     aEvent ? aEvent :
                              new WidgetMouseScrollEvent(false, 0, nullptr))
 {
   if (aEvent) {
     mEventIsInternal = false;
   } else {
     mEventIsInternal = true;
     mEvent->time = PR_Now();
     mEvent->refPoint.x = mEvent->refPoint.y = 0;
     static_cast<WidgetMouseEventBase*>(mEvent)->inputSource =
       nsIDOMMouseEvent::MOZ_SOURCE_UNKNOWN;
   }
 
-  if(mEvent->eventStructType == NS_MOUSE_SCROLL_EVENT) {
-    mDetail = static_cast<WidgetMouseScrollEvent*>(mEvent)->delta;
-  }
-}
-
-nsDOMMouseScrollEvent::~nsDOMMouseScrollEvent()
-{
-  if (mEventIsInternal && mEvent) {
-    switch (mEvent->eventStructType)
-    {
-      case NS_MOUSE_SCROLL_EVENT:
-        delete static_cast<WidgetMouseScrollEvent*>(mEvent);
-        break;
-      default:
-        delete mEvent;
-        break;
-    }
-    mEvent = nullptr;
-  }
+  mDetail = mEvent->AsMouseScrollEvent()->delta;
 }
 
 NS_IMPL_ADDREF_INHERITED(nsDOMMouseScrollEvent, nsDOMMouseEvent)
 NS_IMPL_RELEASE_INHERITED(nsDOMMouseScrollEvent, nsDOMMouseEvent)
 
 NS_INTERFACE_MAP_BEGIN(nsDOMMouseScrollEvent)
   NS_INTERFACE_MAP_ENTRY(nsIDOMMouseScrollEvent)
 NS_INTERFACE_MAP_END_INHERITING(nsDOMMouseEvent)
@@ -61,46 +43,38 @@ nsDOMMouseScrollEvent::InitMouseScrollEv
                                 bool aCtrlKey, bool aAltKey, bool aShiftKey, 
                                 bool aMetaKey, uint16_t aButton, nsIDOMEventTarget *aRelatedTarget,
                                 int32_t aAxis)
 {
   nsresult rv = nsDOMMouseEvent::InitMouseEvent(aType, aCanBubble, aCancelable, aView, aDetail,
                                                 aScreenX, aScreenY, aClientX, aClientY, aCtrlKey,
                                                 aAltKey, aShiftKey, aMetaKey, aButton, aRelatedTarget);
   NS_ENSURE_SUCCESS(rv, rv);
-  
-  if (mEvent->eventStructType == NS_MOUSE_SCROLL_EVENT) {
-    static_cast<WidgetMouseScrollEvent*>(mEvent)->isHorizontal =
-                                                (aAxis == HORIZONTAL_AXIS);
-  }
-
+  mEvent->AsMouseScrollEvent()->isHorizontal = (aAxis == HORIZONTAL_AXIS);
   return NS_OK;
 }
 
 
 NS_IMETHODIMP
 nsDOMMouseScrollEvent::GetAxis(int32_t* aResult)
 {
   NS_ENSURE_ARG_POINTER(aResult);
   *aResult = Axis();
   return NS_OK;
 }
 
 int32_t
 nsDOMMouseScrollEvent::Axis()
 {
-  if (mEvent->eventStructType == NS_MOUSE_SCROLL_EVENT) {
-    return static_cast<WidgetMouseScrollEvent*>(mEvent)->isHorizontal ?
-             static_cast<int32_t>(HORIZONTAL_AXIS) :
-             static_cast<int32_t>(VERTICAL_AXIS);
-  }
-  return 0;
+  return mEvent->AsMouseScrollEvent()->isHorizontal ?
+           static_cast<int32_t>(HORIZONTAL_AXIS) :
+           static_cast<int32_t>(VERTICAL_AXIS);
 }
 
 nsresult NS_NewDOMMouseScrollEvent(nsIDOMEvent** aInstancePtrResult,
                                    mozilla::dom::EventTarget* aOwner,
                                    nsPresContext* aPresContext,
-                                   WidgetInputEvent* aEvent) 
+                                   WidgetMouseScrollEvent* aEvent) 
 {
   nsDOMMouseScrollEvent* it =
     new nsDOMMouseScrollEvent(aOwner, aPresContext, aEvent);
   return CallQueryInterface(it, aInstancePtrResult);
 }
--- a/content/events/src/nsDOMMouseScrollEvent.h
+++ b/content/events/src/nsDOMMouseScrollEvent.h
@@ -11,18 +11,17 @@
 #include "mozilla/dom/MouseScrollEventBinding.h"
 
 class nsDOMMouseScrollEvent : public nsDOMMouseEvent,
                               public nsIDOMMouseScrollEvent
 {
 public:
   nsDOMMouseScrollEvent(mozilla::dom::EventTarget* aOwner,
                         nsPresContext* aPresContext,
-                        mozilla::WidgetInputEvent* aEvent);
-  virtual ~nsDOMMouseScrollEvent();
+                        mozilla::WidgetMouseScrollEvent* aEvent);
 
   NS_DECL_ISUPPORTS_INHERITED
 
   // nsIDOMMouseScrollEvent Interface
   NS_DECL_NSIDOMMOUSESCROLLEVENT
   
   // Forward to base class
   NS_FORWARD_TO_NSDOMMOUSEEVENT
--- a/content/events/src/nsDOMMutationEvent.cpp
+++ b/content/events/src/nsDOMMutationEvent.cpp
@@ -15,97 +15,87 @@ nsDOMMutationEvent::nsDOMMutationEvent(m
                                        nsPresContext* aPresContext,
                                        InternalMutationEvent* aEvent)
   : nsDOMEvent(aOwner, aPresContext,
                aEvent ? aEvent : new InternalMutationEvent(false, 0))
 {
   mEventIsInternal = (aEvent == nullptr);
 }
 
-nsDOMMutationEvent::~nsDOMMutationEvent()
-{
-  if (mEventIsInternal) {
-    InternalMutationEvent* mutation =
-      static_cast<InternalMutationEvent*>(mEvent);
-    delete mutation;
-    mEvent = nullptr;
-  }
-}
-
 NS_INTERFACE_MAP_BEGIN(nsDOMMutationEvent)
   NS_INTERFACE_MAP_ENTRY(nsIDOMMutationEvent)
 NS_INTERFACE_MAP_END_INHERITING(nsDOMEvent)
 
 NS_IMPL_ADDREF_INHERITED(nsDOMMutationEvent, nsDOMEvent)
 NS_IMPL_RELEASE_INHERITED(nsDOMMutationEvent, nsDOMEvent)
 
 already_AddRefed<nsINode>
 nsDOMMutationEvent::GetRelatedNode()
 {
-  nsCOMPtr<nsINode> n = do_QueryInterface(
-    static_cast<InternalMutationEvent*>(mEvent)->mRelatedNode);
+  nsCOMPtr<nsINode> n =
+    do_QueryInterface(mEvent->AsMutationEvent()->mRelatedNode);
   return n.forget();
 }
 
 NS_IMETHODIMP
 nsDOMMutationEvent::GetRelatedNode(nsIDOMNode** aRelatedNode)
 {
   nsCOMPtr<nsINode> relatedNode = GetRelatedNode();
   nsCOMPtr<nsIDOMNode> relatedDOMNode = relatedNode ? relatedNode->AsDOMNode() : nullptr;
   relatedDOMNode.forget(aRelatedNode);
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsDOMMutationEvent::GetPrevValue(nsAString& aPrevValue)
 {
-  InternalMutationEvent* mutation = static_cast<InternalMutationEvent*>(mEvent);
+  InternalMutationEvent* mutation = mEvent->AsMutationEvent();
   if (mutation->mPrevAttrValue)
     mutation->mPrevAttrValue->ToString(aPrevValue);
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsDOMMutationEvent::GetNewValue(nsAString& aNewValue)
 {
-  InternalMutationEvent* mutation = static_cast<InternalMutationEvent*>(mEvent);
+  InternalMutationEvent* mutation = mEvent->AsMutationEvent();
   if (mutation->mNewAttrValue)
       mutation->mNewAttrValue->ToString(aNewValue);
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsDOMMutationEvent::GetAttrName(nsAString& aAttrName)
 {
-  InternalMutationEvent* mutation = static_cast<InternalMutationEvent*>(mEvent);
+  InternalMutationEvent* mutation = mEvent->AsMutationEvent();
   if (mutation->mAttrName)
       mutation->mAttrName->ToString(aAttrName);
   return NS_OK;
 }
 
 uint16_t
 nsDOMMutationEvent::AttrChange()
 {
-  return static_cast<InternalMutationEvent*>(mEvent)->mAttrChange;
+  return mEvent->AsMutationEvent()->mAttrChange;
 }
 
 NS_IMETHODIMP
 nsDOMMutationEvent::GetAttrChange(uint16_t* aAttrChange)
 {
   *aAttrChange = AttrChange();
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsDOMMutationEvent::InitMutationEvent(const nsAString& aTypeArg, bool aCanBubbleArg, bool aCancelableArg, nsIDOMNode* aRelatedNodeArg, const nsAString& aPrevValueArg, const nsAString& aNewValueArg, const nsAString& aAttrNameArg, uint16_t aAttrChangeArg)
 {
   nsresult rv = nsDOMEvent::InitEvent(aTypeArg, aCanBubbleArg, aCancelableArg);
   NS_ENSURE_SUCCESS(rv, rv);
-  
-  InternalMutationEvent* mutation = static_cast<InternalMutationEvent*>(mEvent);
+
+  InternalMutationEvent* mutation = mEvent->AsMutationEvent();
   mutation->mRelatedNode = aRelatedNodeArg;
   if (!aPrevValueArg.IsEmpty())
     mutation->mPrevAttrValue = do_GetAtom(aPrevValueArg);
   if (!aNewValueArg.IsEmpty())
     mutation->mNewAttrValue = do_GetAtom(aNewValueArg);
   if (!aAttrNameArg.IsEmpty()) {
     mutation->mAttrName = do_GetAtom(aAttrNameArg);
   }
--- a/content/events/src/nsDOMMutationEvent.h
+++ b/content/events/src/nsDOMMutationEvent.h
@@ -15,18 +15,16 @@
 class nsDOMMutationEvent : public nsDOMEvent,
                            public nsIDOMMutationEvent
 {
 public:
   nsDOMMutationEvent(mozilla::dom::EventTarget* aOwner,
                      nsPresContext* aPresContext,
                      mozilla::InternalMutationEvent* aEvent);
 
-  virtual ~nsDOMMutationEvent();
-
   NS_DECL_ISUPPORTS_INHERITED
 
   NS_DECL_NSIDOMMUTATIONEVENT
 
   // Forward to base class
   NS_FORWARD_TO_NSDOMEVENT
 
   virtual JSObject* WrapObject(JSContext* aCx,
--- a/content/events/src/nsDOMScrollAreaEvent.cpp
+++ b/content/events/src/nsDOMScrollAreaEvent.cpp
@@ -16,26 +16,16 @@ nsDOMScrollAreaEvent::nsDOMScrollAreaEve
                                            nsPresContext *aPresContext,
                                            InternalScrollAreaEvent* aEvent)
   : nsDOMUIEvent(aOwner, aPresContext, aEvent)
   , mClientArea(nullptr)
 {
   mClientArea.SetLayoutRect(aEvent ? aEvent->mArea : nsRect());
 }
 
-nsDOMScrollAreaEvent::~nsDOMScrollAreaEvent()
-{
-  if (mEventIsInternal && mEvent) {
-    if (mEvent->eventStructType == NS_SCROLLAREA_EVENT) {
-      delete static_cast<InternalScrollAreaEvent*>(mEvent);
-      mEvent = nullptr;
-    }
-  }
-}
-
 NS_IMPL_ADDREF_INHERITED(nsDOMScrollAreaEvent, nsDOMUIEvent)
 NS_IMPL_RELEASE_INHERITED(nsDOMScrollAreaEvent, nsDOMUIEvent)
 
 NS_INTERFACE_MAP_BEGIN(nsDOMScrollAreaEvent)
   NS_INTERFACE_MAP_ENTRY(nsIDOMScrollAreaEvent)
 NS_INTERFACE_MAP_END_INHERITING(nsDOMUIEvent)
 
 
--- a/content/events/src/nsDOMScrollAreaEvent.h
+++ b/content/events/src/nsDOMScrollAreaEvent.h
@@ -18,17 +18,16 @@ class nsDOMScrollAreaEvent : public nsDO
                              public nsIDOMScrollAreaEvent
 {
   typedef mozilla::dom::DOMRect DOMRect;
 
 public:
   nsDOMScrollAreaEvent(mozilla::dom::EventTarget* aOwner,
                        nsPresContext *aPresContext,
                        mozilla::InternalScrollAreaEvent* aEvent);
-  virtual ~nsDOMScrollAreaEvent();
 
   NS_DECL_ISUPPORTS_INHERITED
 
   NS_DECL_NSIDOMSCROLLAREAEVENT
 
   NS_FORWARD_NSIDOMUIEVENT(nsDOMUIEvent::)
 
   NS_FORWARD_TO_NSDOMEVENT_NO_SERIALIZATION_NO_DUPLICATION
--- a/content/events/src/nsDOMSimpleGestureEvent.cpp
+++ b/content/events/src/nsDOMSimpleGestureEvent.cpp
@@ -25,68 +25,82 @@ nsDOMSimpleGestureEvent::nsDOMSimpleGest
     mEventIsInternal = true;
     mEvent->time = PR_Now();
     mEvent->refPoint.x = mEvent->refPoint.y = 0;
     static_cast<WidgetMouseEventBase*>(mEvent)->inputSource =
       nsIDOMMouseEvent::MOZ_SOURCE_UNKNOWN;
   }
 }
 
-nsDOMSimpleGestureEvent::~nsDOMSimpleGestureEvent()
-{
-  if (mEventIsInternal) {
-    delete static_cast<WidgetSimpleGestureEvent*>(mEvent);
-    mEvent = nullptr;
-  }
-}
-
 NS_IMPL_ADDREF_INHERITED(nsDOMSimpleGestureEvent, nsDOMUIEvent)
 NS_IMPL_RELEASE_INHERITED(nsDOMSimpleGestureEvent, nsDOMUIEvent)
 
 NS_INTERFACE_MAP_BEGIN(nsDOMSimpleGestureEvent)
   NS_INTERFACE_MAP_ENTRY(nsIDOMSimpleGestureEvent)
 NS_INTERFACE_MAP_END_INHERITING(nsDOMMouseEvent)
 
 /* attribute unsigned long allowedDirections; */
+uint32_t
+nsDOMSimpleGestureEvent::AllowedDirections()
+{
+  return mEvent->AsSimpleGestureEvent()->allowedDirections;
+}
+
 NS_IMETHODIMP
 nsDOMSimpleGestureEvent::GetAllowedDirections(uint32_t *aAllowedDirections)
 {
   NS_ENSURE_ARG_POINTER(aAllowedDirections);
-  *aAllowedDirections =
-    static_cast<WidgetSimpleGestureEvent*>(mEvent)->allowedDirections;
+  *aAllowedDirections = AllowedDirections();
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsDOMSimpleGestureEvent::SetAllowedDirections(uint32_t aAllowedDirections)
 {
-  static_cast<WidgetSimpleGestureEvent*>(mEvent)->allowedDirections =
-    aAllowedDirections;
+  mEvent->AsSimpleGestureEvent()->allowedDirections = aAllowedDirections;
   return NS_OK;
 }
 
 /* readonly attribute unsigned long direction; */
+uint32_t
+nsDOMSimpleGestureEvent::Direction()
+{
+  return mEvent->AsSimpleGestureEvent()->direction;
+}
+
 NS_IMETHODIMP
 nsDOMSimpleGestureEvent::GetDirection(uint32_t *aDirection)
 {
   NS_ENSURE_ARG_POINTER(aDirection);
   *aDirection = Direction();
   return NS_OK;
 }
 
 /* readonly attribute float delta; */
+double
+nsDOMSimpleGestureEvent::Delta()
+{
+  return mEvent->AsSimpleGestureEvent()->delta;
+}
+
 NS_IMETHODIMP
 nsDOMSimpleGestureEvent::GetDelta(double *aDelta)
 {
   NS_ENSURE_ARG_POINTER(aDelta);
   *aDelta = Delta();
   return NS_OK;
 }
 
 /* readonly attribute unsigned long clickCount; */
+uint32_t
+nsDOMSimpleGestureEvent::ClickCount()
+{
+  return mEvent->AsSimpleGestureEvent()->clickCount;
+}
+
 NS_IMETHODIMP
 nsDOMSimpleGestureEvent::GetClickCount(uint32_t *aClickCount)
 {
   NS_ENSURE_ARG_POINTER(aClickCount);
   *aClickCount = ClickCount();
   return NS_OK;
 }
 
@@ -123,18 +137,17 @@ nsDOMSimpleGestureEvent::InitSimpleGestu
                                                 aCtrlKeyArg,
                                                 aAltKeyArg,
                                                 aShiftKeyArg,
                                                 aMetaKeyArg,
                                                 aButton,
                                                 aRelatedTarget);
   NS_ENSURE_SUCCESS(rv, rv);
 
-  WidgetSimpleGestureEvent* simpleGestureEvent =
-    static_cast<WidgetSimpleGestureEvent*>(mEvent);
+  WidgetSimpleGestureEvent* simpleGestureEvent = mEvent->AsSimpleGestureEvent();
   simpleGestureEvent->allowedDirections = aAllowedDirectionsArg;
   simpleGestureEvent->direction = aDirectionArg;
   simpleGestureEvent->delta = aDeltaArg;
   simpleGestureEvent->clickCount = aClickCountArg;
 
   return NS_OK;
 }
 
--- a/content/events/src/nsDOMSimpleGestureEvent.h
+++ b/content/events/src/nsDOMSimpleGestureEvent.h
@@ -2,62 +2,45 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef nsDOMSimpleGestureEvent_h__
 #define nsDOMSimpleGestureEvent_h__
 
 #include "nsIDOMSimpleGestureEvent.h"
 #include "nsDOMMouseEvent.h"
-#include "mozilla/TouchEvents.h"
+#include "mozilla/EventForwards.h"
 #include "mozilla/dom/SimpleGestureEventBinding.h"
 
 class nsPresContext;
 
 class nsDOMSimpleGestureEvent : public nsDOMMouseEvent,
                                 public nsIDOMSimpleGestureEvent
 {
 public:
   nsDOMSimpleGestureEvent(mozilla::dom::EventTarget* aOwner,
                           nsPresContext*, mozilla::WidgetSimpleGestureEvent*);
-  virtual ~nsDOMSimpleGestureEvent();
 
   NS_DECL_ISUPPORTS_INHERITED
 
   NS_DECL_NSIDOMSIMPLEGESTUREEVENT
 
   // Forward to base class
   NS_FORWARD_TO_NSDOMMOUSEEVENT
 
   virtual JSObject* WrapObject(JSContext* aCx,
 			       JS::Handle<JSObject*> aScope) MOZ_OVERRIDE
   {
     return mozilla::dom::SimpleGestureEventBinding::Wrap(aCx, aScope, this);
   }
 
-  uint32_t AllowedDirections()
-  {
-    return static_cast<mozilla::WidgetSimpleGestureEvent*>(mEvent)->
-             allowedDirections;
-  }
-
-  uint32_t Direction()
-  {
-    return static_cast<mozilla::WidgetSimpleGestureEvent*>(mEvent)->direction;
-  }
-
-  double Delta()
-  {
-    return static_cast<mozilla::WidgetSimpleGestureEvent*>(mEvent)->delta;
-  }
-
-  uint32_t ClickCount()
-  {
-    return static_cast<mozilla::WidgetSimpleGestureEvent*>(mEvent)->clickCount;
-  }
+  uint32_t AllowedDirections();
+  uint32_t Direction();
+  double Delta();
+  uint32_t ClickCount();
 
   void InitSimpleGestureEvent(const nsAString& aType,
                               bool aCanBubble,
                               bool aCancelable,
                               nsIDOMWindow* aView,
                               int32_t aDetail,
                               int32_t aScreenX,
                               int32_t aScreenY,
--- a/content/events/src/nsDOMTextEvent.cpp
+++ b/content/events/src/nsDOMTextEvent.cpp
@@ -25,17 +25,17 @@ nsDOMTextEvent::nsDOMTextEvent(mozilla::
   else {
     mEventIsInternal = true;
     mEvent->time = PR_Now();
   }
 
   //
   // extract the IME composition string
   //
-  WidgetTextEvent *te = static_cast<WidgetTextEvent*>(mEvent);
+  WidgetTextEvent* te = mEvent->AsTextEvent();
   mText = te->theText;
 
   //
   // build the range list -- ranges need to be DOM-ified since the
   // IME transaction will hold a ref, the widget representation
   // isn't persistent
   //
   mTextRange = new nsPrivateTextRangeList(te->rangeCount);
--- a/content/events/src/nsDOMTouchEvent.cpp
+++ b/content/events/src/nsDOMTouchEvent.cpp
@@ -66,24 +66,16 @@ nsDOMTouchEvent::nsDOMTouchEvent(mozilla
       touch->InitializePoints(mPresContext, aEvent);
     }
   } else {
     mEventIsInternal = true;
     mEvent->time = PR_Now();
   }
 }
 
-nsDOMTouchEvent::~nsDOMTouchEvent()
-{
-  if (mEventIsInternal && mEvent) {
-    delete static_cast<WidgetTouchEvent*>(mEvent);
-    mEvent = nullptr;
-  }
-}
-
 NS_IMPL_CYCLE_COLLECTION_INHERITED_3(nsDOMTouchEvent, nsDOMUIEvent,
                                      mTouches,
                                      mTargetTouches,
                                      mChangedTouches)
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(nsDOMTouchEvent)
 NS_INTERFACE_MAP_END_INHERITING(nsDOMUIEvent)
 
@@ -110,28 +102,28 @@ nsDOMTouchEvent::InitTouchEvent(const ns
                                   aCanBubble,
                                   aCancelable,
                                   aView,
                                   aDetail);
   if (aRv.Failed()) {
     return;
   }
 
-  static_cast<WidgetInputEvent*>(mEvent)->
-    InitBasicModifiers(aCtrlKey, aAltKey, aShiftKey, aMetaKey);
+  mEvent->AsInputEvent()->InitBasicModifiers(aCtrlKey, aAltKey,
+                                             aShiftKey, aMetaKey);
   mTouches = aTouches;
   mTargetTouches = aTargetTouches;
   mChangedTouches = aChangedTouches;
 }
 
 nsDOMTouchList*
 nsDOMTouchEvent::Touches()
 {
   if (!mTouches) {
-    WidgetTouchEvent* touchEvent = static_cast<WidgetTouchEvent*>(mEvent);
+    WidgetTouchEvent* touchEvent = mEvent->AsTouchEvent();
     if (mEvent->message == NS_TOUCH_END || mEvent->message == NS_TOUCH_CANCEL) {
       // for touchend events, remove any changed touches from the touches array
       nsTArray< nsRefPtr<Touch> > unchangedTouches;
       const nsTArray< nsRefPtr<Touch> >& touches = touchEvent->touches;
       for (uint32_t i = 0; i < touches.Length(); ++i) {
         if (!touches[i]->mChanged) {
           unchangedTouches.AppendElement(touches[i]);
         }
@@ -144,17 +136,17 @@ nsDOMTouchEvent::Touches()
   return mTouches;
 }
 
 nsDOMTouchList*
 nsDOMTouchEvent::TargetTouches()
 {
   if (!mTargetTouches) {
     nsTArray< nsRefPtr<Touch> > targetTouches;
-    WidgetTouchEvent* touchEvent = static_cast<WidgetTouchEvent*>(mEvent);
+    WidgetTouchEvent* touchEvent = mEvent->AsTouchEvent();
     const nsTArray< nsRefPtr<Touch> >& touches = touchEvent->touches;
     for (uint32_t i = 0; i < touches.Length(); ++i) {
       // for touchend/cancel events, don't append to the target list if this is a
       // touch that is ending
       if ((mEvent->message != NS_TOUCH_END &&
            mEvent->message != NS_TOUCH_CANCEL) || !touches[i]->mChanged) {
         if (touches[i]->mTarget == mEvent->originalTarget) {
           targetTouches.AppendElement(touches[i]);
@@ -166,17 +158,17 @@ nsDOMTouchEvent::TargetTouches()
   return mTargetTouches;
 }
 
 nsDOMTouchList*
 nsDOMTouchEvent::ChangedTouches()
 {
   if (!mChangedTouches) {
     nsTArray< nsRefPtr<Touch> > changedTouches;
-    WidgetTouchEvent* touchEvent = static_cast<WidgetTouchEvent*>(mEvent);
+    WidgetTouchEvent* touchEvent = mEvent->AsTouchEvent();
     const nsTArray< nsRefPtr<Touch> >& touches = touchEvent->touches;
     for (uint32_t i = 0; i < touches.Length(); ++i) {
       if (touches[i]->mChanged) {
         changedTouches.AppendElement(touches[i]);
       }
     }
     mChangedTouches = new nsDOMTouchList(ToSupports(this), changedTouches);
   }
@@ -216,16 +208,40 @@ nsDOMTouchEvent::PrefEnabled()
     }
   }
   if (prefValue) {
     nsContentUtils::InitializeTouchEventTable();
   }
   return prefValue;
 }
 
+bool
+nsDOMTouchEvent::AltKey()
+{
+  return mEvent->AsTouchEvent()->IsAlt();
+}
+
+bool
+nsDOMTouchEvent::MetaKey()
+{
+  return mEvent->AsTouchEvent()->IsMeta();
+}
+
+bool
+nsDOMTouchEvent::CtrlKey()
+{
+  return mEvent->AsTouchEvent()->IsControl();
+}
+
+bool
+nsDOMTouchEvent::ShiftKey()
+{
+  return mEvent->AsTouchEvent()->IsShift();
+}
+
 nsresult
 NS_NewDOMTouchEvent(nsIDOMEvent** aInstancePtrResult,
                     mozilla::dom::EventTarget* aOwner,
                     nsPresContext* aPresContext,
                     WidgetTouchEvent* aEvent)
 {
   nsDOMTouchEvent* it = new nsDOMTouchEvent(aOwner, aPresContext, aEvent);
   return CallQueryInterface(it, aInstancePtrResult);
--- a/content/events/src/nsDOMTouchEvent.h
+++ b/content/events/src/nsDOMTouchEvent.h
@@ -3,18 +3,19 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 #ifndef nsDOMTouchEvent_h_
 #define nsDOMTouchEvent_h_
 
 #include "nsDOMUIEvent.h"
 #include "nsTArray.h"
 #include "mozilla/Attributes.h"
-#include "mozilla/TouchEvents.h"
+#include "mozilla/EventForwards.h"
 #include "nsJSEnvironment.h"
+#include "mozilla/dom/Touch.h"
 #include "mozilla/dom/TouchEventBinding.h"
 #include "nsWrapperCache.h"
 
 
 class nsAString;
 
 class nsDOMTouchList MOZ_FINAL : public nsISupports
                                , public nsWrapperCache
@@ -79,50 +80,34 @@ protected:
 };
 
 class nsDOMTouchEvent : public nsDOMUIEvent
 {
 public:
   nsDOMTouchEvent(mozilla::dom::EventTarget* aOwner,
                   nsPresContext* aPresContext,
                   mozilla::WidgetTouchEvent* aEvent);
-  virtual ~nsDOMTouchEvent();
 
   NS_DECL_ISUPPORTS_INHERITED
   NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(nsDOMTouchEvent, nsDOMUIEvent)
 
   virtual JSObject* WrapObject(JSContext* aCx,
                                JS::Handle<JSObject*> aScope) MOZ_OVERRIDE
   {
     return mozilla::dom::TouchEventBinding::Wrap(aCx, aScope, this);
   }
 
   nsDOMTouchList* Touches();
   nsDOMTouchList* TargetTouches();
   nsDOMTouchList* ChangedTouches();
 
-  bool AltKey()
-  {
-    return static_cast<mozilla::WidgetInputEvent*>(mEvent)->IsAlt();
-  }
-
-  bool MetaKey()
-  {
-    return static_cast<mozilla::WidgetInputEvent*>(mEvent)->IsMeta();
-  }
-
-  bool CtrlKey()
-  {
-    return static_cast<mozilla::WidgetInputEvent*>(mEvent)->IsControl();
-  }
-
-  bool ShiftKey()
-  {
-    return static_cast<mozilla::WidgetInputEvent*>(mEvent)->IsShift();
-  }
+  bool AltKey();
+  bool MetaKey();
+  bool CtrlKey();
+  bool ShiftKey();
 
   void InitTouchEvent(const nsAString& aType,
                       bool aCanBubble,
                       bool aCancelable,
                       nsIDOMWindow* aView,
                       int32_t aDetail,
                       bool aCtrlKey,
                       bool aAltKey,
--- a/content/events/src/nsDOMTransitionEvent.cpp
+++ b/content/events/src/nsDOMTransitionEvent.cpp
@@ -21,24 +21,16 @@ nsDOMTransitionEvent::nsDOMTransitionEve
     mEventIsInternal = false;
   }
   else {
     mEventIsInternal = true;
     mEvent->time = PR_Now();
   }
 }
 
-nsDOMTransitionEvent::~nsDOMTransitionEvent()
-{
-  if (mEventIsInternal) {
-    delete TransitionEvent();
-    mEvent = nullptr;
-  }
-}
-
 NS_INTERFACE_MAP_BEGIN(nsDOMTransitionEvent)
   NS_INTERFACE_MAP_ENTRY(nsIDOMTransitionEvent)
 NS_INTERFACE_MAP_END_INHERITING(nsDOMEvent)
 
 NS_IMPL_ADDREF_INHERITED(nsDOMTransitionEvent, nsDOMEvent)
 NS_IMPL_RELEASE_INHERITED(nsDOMTransitionEvent, nsDOMEvent)
 
 //static
@@ -49,42 +41,49 @@ nsDOMTransitionEvent::Constructor(const 
                                   mozilla::ErrorResult& aRv)
 {
   nsCOMPtr<mozilla::dom::EventTarget> t = do_QueryInterface(aGlobal.GetAsSupports());
   nsRefPtr<nsDOMTransitionEvent> e = new nsDOMTransitionEvent(t, nullptr, nullptr);
   bool trusted = e->Init(t);
 
   aRv = e->InitEvent(aType, aParam.mBubbles, aParam.mCancelable);
 
-  e->TransitionEvent()->propertyName = aParam.mPropertyName;
-  e->TransitionEvent()->elapsedTime = aParam.mElapsedTime;
-  e->TransitionEvent()->pseudoElement = aParam.mPseudoElement;
+  InternalTransitionEvent* internalEvent = e->mEvent->AsTransitionEvent();
+  internalEvent->propertyName = aParam.mPropertyName;
+  internalEvent->elapsedTime = aParam.mElapsedTime;
+  internalEvent->pseudoElement = aParam.mPseudoElement;
 
   e->SetTrusted(trusted);
   return e.forget();
 }
 
 NS_IMETHODIMP
 nsDOMTransitionEvent::GetPropertyName(nsAString & aPropertyName)
 {
-  aPropertyName = TransitionEvent()->propertyName;
+  aPropertyName = mEvent->AsTransitionEvent()->propertyName;
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsDOMTransitionEvent::GetElapsedTime(float *aElapsedTime)
 {
   *aElapsedTime = ElapsedTime();
   return NS_OK;
 }
 
+float
+nsDOMTransitionEvent::ElapsedTime()
+{
+  return mEvent->AsTransitionEvent()->elapsedTime;
+}
+
 NS_IMETHODIMP
 nsDOMTransitionEvent::GetPseudoElement(nsAString& aPseudoElement)
 {
-  aPseudoElement = TransitionEvent()->pseudoElement;
+  aPseudoElement = mEvent->AsTransitionEvent()->pseudoElement;
   return NS_OK;
 }
 
 nsresult
 NS_NewDOMTransitionEvent(nsIDOMEvent **aInstancePtrResult,
                          mozilla::dom::EventTarget* aOwner,
                          nsPresContext *aPresContext,
                          InternalTransitionEvent* aEvent)
--- a/content/events/src/nsDOMTransitionEvent.h
+++ b/content/events/src/nsDOMTransitionEvent.h
@@ -2,29 +2,28 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 #ifndef nsDOMTransitionEvent_h_
 #define nsDOMTransitionEvent_h_
 
 #include "nsDOMEvent.h"
 #include "nsIDOMTransitionEvent.h"
-#include "mozilla/ContentEvents.h"
+#include "mozilla/EventForwards.h"
 #include "mozilla/dom/TransitionEventBinding.h"
 
 class nsAString;
 
 class nsDOMTransitionEvent : public nsDOMEvent,
                              public nsIDOMTransitionEvent
 {
 public:
   nsDOMTransitionEvent(mozilla::dom::EventTarget* aOwner,
                        nsPresContext *aPresContext,
                        mozilla::InternalTransitionEvent* aEvent);
-  ~nsDOMTransitionEvent();
 
   NS_DECL_ISUPPORTS_INHERITED
   NS_FORWARD_TO_NSDOMEVENT
   NS_DECL_NSIDOMTRANSITIONEVENT
 
   static already_AddRefed<nsDOMTransitionEvent>
   Constructor(const mozilla::dom::GlobalObject& aGlobal,
               const nsAString& aType,
@@ -36,22 +35,12 @@ public:
   {
     return mozilla::dom::TransitionEventBinding::Wrap(aCx, aScope, this);
   }
 
   // xpidl implementation
   // GetPropertyName(nsAString& aPropertyName)
   // GetPseudoElement(nsAString& aPreudoElement)
 
-  float ElapsedTime()
-  {
-    return TransitionEvent()->elapsedTime;
-  }
-
-private:
-  mozilla::InternalTransitionEvent* TransitionEvent() {
-    NS_ABORT_IF_FALSE(mEvent->eventStructType == NS_TRANSITION_EVENT,
-                      "unexpected struct type");
-    return static_cast<mozilla::InternalTransitionEvent*>(mEvent);
-  }
+  float ElapsedTime();
 };
 
 #endif /* !defined(nsDOMTransitionEvent_h_) */
--- a/content/events/src/nsDOMUIEvent.cpp
+++ b/content/events/src/nsDOMUIEvent.cpp
@@ -39,25 +39,23 @@ nsDOMUIEvent::nsDOMUIEvent(mozilla::dom:
   }
   
   // Fill mDetail and mView according to the mEvent (widget-generated
   // event) we've got
   switch(mEvent->eventStructType)
   {
     case NS_UI_EVENT:
     {
-      InternalUIEvent *event = static_cast<InternalUIEvent*>(mEvent);
-      mDetail = event->detail;
+      mDetail = mEvent->AsUIEvent()->detail;
       break;
     }
 
     case NS_SCROLLPORT_EVENT:
     {
-      InternalScrollPortEvent* scrollEvent =
-        static_cast<InternalScrollPortEvent*>(mEvent);
+      InternalScrollPortEvent* scrollEvent = mEvent->AsScrollPortEvent();
       mDetail = (int32_t)scrollEvent->orient;
       break;
     }
 
     default:
       mDetail = 0;
       break;
   }
@@ -340,26 +338,22 @@ nsDOMUIEvent::GetIsChar(bool* aIsChar)
 {
   *aIsChar = IsChar();
   return NS_OK;
 }
 
 bool
 nsDOMUIEvent::IsChar() const
 {
-  switch (mEvent->eventStructType)
-  {
-    case NS_KEY_EVENT:
-      return static_cast<WidgetKeyboardEvent*>(mEvent)->isChar;
-    case NS_TEXT_EVENT:
-      return static_cast<WidgetTextEvent*>(mEvent)->isChar;
-    default:
-      return false;
+  WidgetKeyboardEvent* keyEvent = mEvent->AsKeyboardEvent();
+  if (keyEvent) {
+    return keyEvent->isChar;
   }
-  MOZ_CRASH("Switch handles all cases.");
+  WidgetTextEvent* textEvent = mEvent->AsTextEvent();
+  return textEvent ? textEvent->isChar : false;
 }
 
 NS_IMETHODIMP
 nsDOMUIEvent::DuplicatePrivateData()
 {
   mClientPoint = nsDOMEvent::GetClientCoords(mPresContext,
                                              mEvent,
                                              mEvent->refPoint,
@@ -457,20 +451,18 @@ nsDOMUIEvent::ComputeModifierState(const
   }
 
   return modifiers;
 }
 
 bool
 nsDOMUIEvent::GetModifierStateInternal(const nsAString& aKey)
 {
-  if (!mEvent->IsInputDerivedEvent()) {
-    MOZ_CRASH("mEvent must be WidgetInputEvent or derived class");
-  }
-  WidgetInputEvent* inputEvent = static_cast<WidgetInputEvent*>(mEvent);
+  WidgetInputEvent* inputEvent = mEvent->AsInputEvent();
+  MOZ_ASSERT(inputEvent, "mEvent must be WidgetInputEvent or derived class");
   if (aKey.EqualsLiteral(NS_DOM_KEYNAME_SHIFT)) {
     return inputEvent->IsShift();
   }
   if (aKey.EqualsLiteral(NS_DOM_KEYNAME_CONTROL)) {
     return inputEvent->IsControl();
   }
   if (aKey.EqualsLiteral(NS_DOM_KEYNAME_META)) {
     return inputEvent->IsMeta();
--- a/content/events/src/nsDOMXULCommandEvent.cpp
+++ b/content/events/src/nsDOMXULCommandEvent.cpp
@@ -29,40 +29,64 @@ NS_IMPL_RELEASE_INHERITED(nsDOMXULComman
 
 NS_IMPL_CYCLE_COLLECTION_INHERITED_1(nsDOMXULCommandEvent, nsDOMUIEvent,
                                      mSourceEvent)
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(nsDOMXULCommandEvent)
   NS_INTERFACE_MAP_ENTRY(nsIDOMXULCommandEvent)
 NS_INTERFACE_MAP_END_INHERITING(nsDOMUIEvent)
 
+bool
+nsDOMXULCommandEvent::AltKey()
+{
+  return mEvent->AsInputEvent()->IsAlt();
+}
+
 NS_IMETHODIMP
 nsDOMXULCommandEvent::GetAltKey(bool* aIsDown)
 {
   NS_ENSURE_ARG_POINTER(aIsDown);
   *aIsDown = AltKey();
   return NS_OK;
 }
 
+bool
+nsDOMXULCommandEvent::CtrlKey()
+{
+  return mEvent->AsInputEvent()->IsControl();
+}
+
 NS_IMETHODIMP
 nsDOMXULCommandEvent::GetCtrlKey(bool* aIsDown)
 {
   NS_ENSURE_ARG_POINTER(aIsDown);
   *aIsDown = CtrlKey();
   return NS_OK;
 }
 
+bool
+nsDOMXULCommandEvent::ShiftKey()
+{
+  return mEvent->AsInputEvent()->IsShift();
+}
+
 NS_IMETHODIMP
 nsDOMXULCommandEvent::GetShiftKey(bool* aIsDown)
 {
   NS_ENSURE_ARG_POINTER(aIsDown);
   *aIsDown = ShiftKey();
   return NS_OK;
 }
 
+bool
+nsDOMXULCommandEvent::MetaKey()
+{
+  return mEvent->AsInputEvent()->IsMeta();
+}
+
 NS_IMETHODIMP
 nsDOMXULCommandEvent::GetMetaKey(bool* aIsDown)
 {
   NS_ENSURE_ARG_POINTER(aIsDown);
   *aIsDown = MetaKey();
   return NS_OK;
 }
 
@@ -82,17 +106,18 @@ nsDOMXULCommandEvent::InitCommandEvent(c
                                        bool aCtrlKey, bool aAltKey,
                                        bool aShiftKey, bool aMetaKey,
                                        nsIDOMEvent* aSourceEvent)
 {
   nsresult rv = nsDOMUIEvent::InitUIEvent(aType, aCanBubble, aCancelable,
                                           aView, aDetail);
   NS_ENSURE_SUCCESS(rv, rv);
 
-  Event()->InitBasicModifiers(aCtrlKey, aAltKey, aShiftKey, aMetaKey);
+  mEvent->AsInputEvent()->InitBasicModifiers(aCtrlKey, aAltKey,
+                                             aShiftKey, aMetaKey);
   mSourceEvent = aSourceEvent;
 
   return NS_OK;
 }
 
 
 nsresult NS_NewDOMXULCommandEvent(nsIDOMEvent** aInstancePtrResult,
                                   mozilla::dom::EventTarget* aOwner,
--- a/content/events/src/nsDOMXULCommandEvent.h
+++ b/content/events/src/nsDOMXULCommandEvent.h
@@ -29,35 +29,20 @@ public:
   NS_FORWARD_TO_NSDOMUIEVENT
 
   virtual JSObject* WrapObject(JSContext* aCx,
                                JS::Handle<JSObject*> aScope) MOZ_OVERRIDE
   {
     return mozilla::dom::XULCommandEventBinding::Wrap(aCx, aScope, this);
   }
 
-  bool AltKey()
-  {
-    return Event()->IsAlt();
-  }
-
-  bool CtrlKey()
-  {
-    return Event()->IsControl();
-  }
-
-  bool ShiftKey()
-  {
-    return Event()->IsShift();
-  }
-
-  bool MetaKey()
-  {
-    return Event()->IsMeta();
-  }
+  bool AltKey();
+  bool CtrlKey();
+  bool ShiftKey();
+  bool MetaKey();
 
   already_AddRefed<nsDOMEvent> GetSourceEvent()
   {
     nsRefPtr<nsDOMEvent> e =
       mSourceEvent ? mSourceEvent->InternalDOMEvent() : nullptr;
     return e.forget();
   }
 
@@ -71,17 +56,12 @@ public:
                         mozilla::ErrorResult& aRv)
   {
     aRv = InitCommandEvent(aType, aCanBubble, aCancelable, aView, aDetail,
                            aCtrlKey, aAltKey, aShiftKey, aMetaKey,
                            aSourceEvent);
   }
 
 protected:
-  // Convenience accessor for the event
-  mozilla::WidgetInputEvent* Event() {
-    return static_cast<mozilla::WidgetInputEvent*>(mEvent);
-  }
-
   nsCOMPtr<nsIDOMEvent> mSourceEvent;
 };
 
 #endif  // nsDOMXULCommandEvent_h_
--- a/content/events/src/nsEventDispatcher.cpp
+++ b/content/events/src/nsEventDispatcher.cpp
@@ -687,74 +687,73 @@ nsEventDispatcher::CreateEvent(mozilla::
                                nsIDOMEvent** aDOMEvent)
 {
   *aDOMEvent = nullptr;
 
   if (aEvent) {
     switch(aEvent->eventStructType) {
     case NS_MUTATION_EVENT:
       return NS_NewDOMMutationEvent(aDOMEvent, aOwner, aPresContext,
-               static_cast<InternalMutationEvent*>(aEvent));
+                                    aEvent->AsMutationEvent());
     case NS_GUI_EVENT:
     case NS_SCROLLPORT_EVENT:
     case NS_UI_EVENT:
       return NS_NewDOMUIEvent(aDOMEvent, aOwner, aPresContext,
                               static_cast<WidgetGUIEvent*>(aEvent));
     case NS_SCROLLAREA_EVENT:
       return NS_NewDOMScrollAreaEvent(aDOMEvent, aOwner, aPresContext,
-               static_cast<InternalScrollAreaEvent*>(aEvent));
+                                      aEvent->AsScrollAreaEvent());
     case NS_KEY_EVENT:
       return NS_NewDOMKeyboardEvent(aDOMEvent, aOwner, aPresContext,
-                                    static_cast<WidgetKeyboardEvent*>(aEvent));
+                                    aEvent->AsKeyboardEvent());
     case NS_COMPOSITION_EVENT:
-      return NS_NewDOMCompositionEvent(
-        aDOMEvent, aOwner,
-        aPresContext, static_cast<WidgetCompositionEvent*>(aEvent));
+      return NS_NewDOMCompositionEvent(aDOMEvent, aOwner, aPresContext,
+                                       aEvent->AsCompositionEvent());
     case NS_MOUSE_EVENT:
       return NS_NewDOMMouseEvent(aDOMEvent, aOwner, aPresContext,
-                                 static_cast<WidgetInputEvent*>(aEvent));
+                                 aEvent->AsMouseEvent());
     case NS_FOCUS_EVENT:
       return NS_NewDOMFocusEvent(aDOMEvent, aOwner, aPresContext,
-                                 static_cast<InternalFocusEvent*>(aEvent));
+                                 aEvent->AsFocusEvent());
     case NS_MOUSE_SCROLL_EVENT:
       return NS_NewDOMMouseScrollEvent(aDOMEvent, aOwner, aPresContext,
-                                       static_cast<WidgetInputEvent*>(aEvent));
+                                       aEvent->AsMouseScrollEvent());
     case NS_WHEEL_EVENT:
       return NS_NewDOMWheelEvent(aDOMEvent, aOwner, aPresContext,
                                  static_cast<WidgetWheelEvent*>(aEvent));
     case NS_DRAG_EVENT:
       return NS_NewDOMDragEvent(aDOMEvent, aOwner, aPresContext,
-                                static_cast<WidgetDragEvent*>(aEvent));
+                                aEvent->AsDragEvent());
     case NS_TEXT_EVENT:
       return NS_NewDOMTextEvent(aDOMEvent, aOwner, aPresContext,
-                                static_cast<WidgetTextEvent*>(aEvent));
+                                aEvent->AsTextEvent());
     case NS_CLIPBOARD_EVENT:
       return NS_NewDOMClipboardEvent(aDOMEvent, aOwner, aPresContext,
-               static_cast<InternalClipboardEvent*>(aEvent));
+                                     aEvent->AsClipboardEvent());
     case NS_SVGZOOM_EVENT:
       return NS_NewDOMSVGZoomEvent(aDOMEvent, aOwner, aPresContext,
                                    static_cast<WidgetGUIEvent*>(aEvent));
     case NS_SMIL_TIME_EVENT:
       return NS_NewDOMTimeEvent(aDOMEvent, aOwner, aPresContext, aEvent);
 
     case NS_COMMAND_EVENT:
       return NS_NewDOMCommandEvent(aDOMEvent, aOwner, aPresContext,
-                                   static_cast<WidgetCommandEvent*>(aEvent));
+                                   aEvent->AsCommandEvent());
     case NS_SIMPLE_GESTURE_EVENT:
       return NS_NewDOMSimpleGestureEvent(aDOMEvent, aOwner, aPresContext,
-               static_cast<WidgetSimpleGestureEvent*>(aEvent));
+                                         aEvent->AsSimpleGestureEvent());
     case NS_TOUCH_EVENT:
       return NS_NewDOMTouchEvent(aDOMEvent, aOwner, aPresContext,
-                                 static_cast<WidgetTouchEvent*>(aEvent));
+                                 aEvent->AsTouchEvent());
     case NS_TRANSITION_EVENT:
       return NS_NewDOMTransitionEvent(aDOMEvent, aOwner, aPresContext,
-               static_cast<InternalTransitionEvent*>(aEvent));
+                                      aEvent->AsTransitionEvent());
     case NS_ANIMATION_EVENT:
       return NS_NewDOMAnimationEvent(aDOMEvent, aOwner, aPresContext,
-               static_cast<InternalAnimationEvent*>(aEvent));
+                                     aEvent->AsAnimationEvent());
     default:
       // For all other types of events, create a vanilla event object.
       return NS_NewDOMEvent(aDOMEvent, aOwner, aPresContext, aEvent);
     }
   }
 
   // And if we didn't get an event, check the type argument.
 
--- a/content/events/src/nsEventStateManager.cpp
+++ b/content/events/src/nsEventStateManager.cpp
@@ -1140,18 +1140,17 @@ nsEventStateManager::PreHandleEvent(nsPr
     // NS_DRAGDROP_DROP is fired before NS_DRAGDROP_DRAGDROP so send
     // the enter/exit events before NS_DRAGDROP_DROP.
     GenerateDragDropEnterExit(aPresContext,
                               static_cast<WidgetGUIEvent*>(aEvent));
     break;
 
   case NS_KEY_PRESS:
     {
-
-      WidgetKeyboardEvent* keyEvent = static_cast<WidgetKeyboardEvent*>(aEvent);
+      WidgetKeyboardEvent* keyEvent = aEvent->AsKeyboardEvent();
 
       int32_t modifierMask = 0;
       if (keyEvent->IsShift())
         modifierMask |= NS_MODIFIER_SHIFT;
       if (keyEvent->IsControl())
         modifierMask |= NS_MODIFIER_CONTROL;
       if (keyEvent->IsAlt())
         modifierMask |= NS_MODIFIER_ALT;
@@ -1205,139 +1204,132 @@ nsEventStateManager::PreHandleEvent(nsPr
       // on some platforms which might dispatch wheel events which don't have
       // lineOrPageDelta values.  And also, if delta values are customized by
       // prefs, this recomputes them.
       DeltaAccumulator::GetInstance()->
         InitLineOrPageDelta(aTargetFrame, this, wheelEvent);
     }
     break;
   case NS_QUERY_SELECTED_TEXT:
-    DoQuerySelectedText(static_cast<WidgetQueryContentEvent*>(aEvent));
+    DoQuerySelectedText(aEvent->AsQueryContentEvent());
     break;
   case NS_QUERY_TEXT_CONTENT:
     {
       if (RemoteQueryContentEvent(aEvent))
         break;
       nsContentEventHandler handler(mPresContext);
-      handler.OnQueryTextContent(static_cast<WidgetQueryContentEvent*>(aEvent));
+      handler.OnQueryTextContent(aEvent->AsQueryContentEvent());
     }
     break;
   case NS_QUERY_CARET_RECT:
     {
       // XXX remote event
       nsContentEventHandler handler(mPresContext);
-      handler.OnQueryCaretRect(static_cast<WidgetQueryContentEvent*>(aEvent));
+      handler.OnQueryCaretRect(aEvent->AsQueryContentEvent());
     }
     break;
   case NS_QUERY_TEXT_RECT:
     {
       // XXX remote event
       nsContentEventHandler handler(mPresContext);
-      handler.OnQueryTextRect(static_cast<WidgetQueryContentEvent*>(aEvent));
+      handler.OnQueryTextRect(aEvent->AsQueryContentEvent());
     }
     break;
   case NS_QUERY_EDITOR_RECT:
     {
       // XXX remote event
       nsContentEventHandler handler(mPresContext);
-      handler.OnQueryEditorRect(static_cast<WidgetQueryContentEvent*>(aEvent));
+      handler.OnQueryEditorRect(aEvent->AsQueryContentEvent());
     }
     break;
   case NS_QUERY_CONTENT_STATE:
     {
       // XXX remote event
       nsContentEventHandler handler(mPresContext);
-      handler.OnQueryContentState(static_cast<WidgetQueryContentEvent*>(aEvent));
+      handler.OnQueryContentState(aEvent->AsQueryContentEvent());
     }
     break;
   case NS_QUERY_SELECTION_AS_TRANSFERABLE:
     {
       // XXX remote event
       nsContentEventHandler handler(mPresContext);
-      handler.OnQuerySelectionAsTransferable(
-        static_cast<WidgetQueryContentEvent*>(aEvent));
+      handler.OnQuerySelectionAsTransferable(aEvent->AsQueryContentEvent());
     }
     break;
   case NS_QUERY_CHARACTER_AT_POINT:
     {
       // XXX remote event
       nsContentEventHandler handler(mPresContext);
-      handler.OnQueryCharacterAtPoint(
-        static_cast<WidgetQueryContentEvent*>(aEvent));
+      handler.OnQueryCharacterAtPoint(aEvent->AsQueryContentEvent());
     }
     break;
   case NS_QUERY_DOM_WIDGET_HITTEST:
     {
       // XXX remote event
       nsContentEventHandler handler(mPresContext);
-      handler.OnQueryDOMWidgetHittest(
-        static_cast<WidgetQueryContentEvent*>(aEvent));
+      handler.OnQueryDOMWidgetHittest(aEvent->AsQueryContentEvent());
     }
     break;
   case NS_SELECTION_SET:
     {
-      WidgetSelectionEvent *selectionEvent =
-          static_cast<WidgetSelectionEvent*>(aEvent);
+      WidgetSelectionEvent* selectionEvent = aEvent->AsSelectionEvent();
       if (IsTargetCrossProcess(selectionEvent)) {
         // Will not be handled locally, remote the event
         if (GetCrossProcessTarget()->SendSelectionEvent(*selectionEvent))
           selectionEvent->mSucceeded = true;
         break;
       }
       nsContentEventHandler handler(mPresContext);
-      handler.OnSelectionEvent(static_cast<WidgetSelectionEvent*>(aEvent));
+      handler.OnSelectionEvent(selectionEvent);
     }
     break;
   case NS_CONTENT_COMMAND_CUT:
   case NS_CONTENT_COMMAND_COPY:
   case NS_CONTENT_COMMAND_PASTE:
   case NS_CONTENT_COMMAND_DELETE:
   case NS_CONTENT_COMMAND_UNDO:
   case NS_CONTENT_COMMAND_REDO:
   case NS_CONTENT_COMMAND_PASTE_TRANSFERABLE:
     {
-      DoContentCommandEvent(static_cast<WidgetContentCommandEvent*>(aEvent));
+      DoContentCommandEvent(aEvent->AsContentCommandEvent());
     }
     break;
   case NS_CONTENT_COMMAND_SCROLL:
     {
-      DoContentCommandScrollEvent(
-        static_cast<WidgetContentCommandEvent*>(aEvent));
+      DoContentCommandScrollEvent(aEvent->AsContentCommandEvent());
     }
     break;
   case NS_TEXT_TEXT:
     {
-      WidgetTextEvent *textEvent = static_cast<WidgetTextEvent*>(aEvent);
+      WidgetTextEvent *textEvent = aEvent->AsTextEvent();
       if (IsTargetCrossProcess(textEvent)) {
         // Will not be handled locally, remote the event
         if (GetCrossProcessTarget()->SendTextEvent(*textEvent)) {
           // Cancel local dispatching
           aEvent->mFlags.mPropagationStopped = true;
         }
       }
     }
     break;
   case NS_COMPOSITION_START:
     if (aEvent->mFlags.mIsTrusted) {
       // If the event is trusted event, set the selected text to data of
       // composition event.
-      WidgetCompositionEvent *compositionEvent =
-        static_cast<WidgetCompositionEvent*>(aEvent);
+      WidgetCompositionEvent* compositionEvent = aEvent->AsCompositionEvent();
       WidgetQueryContentEvent selectedText(true, NS_QUERY_SELECTED_TEXT,
                                            compositionEvent->widget);
       DoQuerySelectedText(&selectedText);
       NS_ASSERTION(selectedText.mSucceeded, "Failed to get selected text");
       compositionEvent->data = selectedText.mReply.mString;
     }
     // through to compositionend handling
   case NS_COMPOSITION_UPDATE:
   case NS_COMPOSITION_END:
     {
-      WidgetCompositionEvent *compositionEvent =
-          static_cast<WidgetCompositionEvent*>(aEvent);
+      WidgetCompositionEvent* compositionEvent = aEvent->AsCompositionEvent();
       if (IsTargetCrossProcess(compositionEvent)) {
         // Will not be handled locally, remote the event
         if (GetCrossProcessTarget()->SendCompositionEvent(*compositionEvent)) {
           // Cancel local dispatching
           aEvent->mFlags.mPropagationStopped = true;
         }
       }
     }
@@ -1604,29 +1596,27 @@ nsEventStateManager::DispatchCrossProces
   }
 
   switch (aEvent->eventStructType) {
   case NS_MOUSE_EVENT: {
     WidgetMouseEvent* mouseEvent = static_cast<WidgetMouseEvent*>(aEvent);
     return remote->SendRealMouseEvent(*mouseEvent);
   }
   case NS_KEY_EVENT: {
-    WidgetKeyboardEvent* keyEvent = static_cast<WidgetKeyboardEvent*>(aEvent);
-    return remote->SendRealKeyEvent(*keyEvent);
+    return remote->SendRealKeyEvent(*aEvent->AsKeyboardEvent());
   }
   case NS_WHEEL_EVENT: {
     WidgetWheelEvent* wheelEvent = static_cast<WidgetWheelEvent*>(aEvent);
     return remote->SendMouseWheelEvent(*wheelEvent);
   }
   case NS_TOUCH_EVENT: {
     // Let the child process synthesize a mouse event if needed, and
     // ensure we don't synthesize one in this process.
     *aStatus = nsEventStatus_eConsumeNoDefault;
-    WidgetTouchEvent* touchEvent = static_cast<WidgetTouchEvent*>(aEvent);
-    return remote->SendRealTouchEvent(*touchEvent);
+    return remote->SendRealTouchEvent(*aEvent->AsTouchEvent());
   }
   default: {
     MOZ_CRASH("Attempt to send non-whitelisted event?");
   }
   }
 }
 
 bool
@@ -1732,18 +1722,18 @@ nsEventStateManager::HandleCrossProcessE
   } else {
     // This is a touch event with possibly multiple touch points.
     // Each touch point may have its own target.  So iterate through
     // all of them and collect the unique set of targets for event
     // forwarding.
     //
     // This loop is similar to the one used in
     // PresShell::DispatchTouchEvent().
-    WidgetTouchEvent* touchEvent = static_cast<WidgetTouchEvent*>(aEvent);
-    const nsTArray< nsRefPtr<Touch> >& touches = touchEvent->touches;
+    const nsTArray< nsRefPtr<Touch> >& touches =
+      aEvent->AsTouchEvent()->touches;
     for (uint32_t i = 0; i < touches.Length(); ++i) {
       Touch* touch = touches[i];
       // NB: the |mChanged| check is an optimization, subprocesses can
       // compute this for themselves.  If the touch hasn't changed, we
       // may be able to avoid forwarding the event entirely (which is
       // not free).
       if (!touch || !touch->mChanged) {
         continue;
@@ -3478,19 +3468,19 @@ nsEventStateManager::PostHandleEvent(nsP
           break;
       }
       *aStatus = nsEventStatus_eConsumeNoDefault;
     }
     break;
 
   case NS_GESTURENOTIFY_EVENT_START:
     {
-      if (nsEventStatus_eConsumeNoDefault != *aStatus)
-        DecideGestureEvent(static_cast<WidgetGestureNotifyEvent*>(aEvent),
-                           mCurrentTarget);
+      if (nsEventStatus_eConsumeNoDefault != *aStatus) {
+        DecideGestureEvent(aEvent->AsGestureNotifyEvent(), mCurrentTarget);
+      }
     }
     break;
 
   case NS_DRAGDROP_ENTER:
   case NS_DRAGDROP_OVER:
     {
       NS_ASSERTION(aEvent->eventStructType == NS_DRAG_EVENT, "Expected a drag event");
 
@@ -3506,17 +3496,17 @@ nsEventStateManager::PostHandleEvent(nsP
       bool isChromeDoc = nsContentUtils::IsChromeDoc(mDocument);
 
       // the initial dataTransfer is the one from the dragstart event that
       // was set on the dragSession when the drag began.
       nsCOMPtr<nsIDOMDataTransfer> dataTransfer;
       nsCOMPtr<nsIDOMDataTransfer> initialDataTransfer;
       dragSession->GetDataTransfer(getter_AddRefs(initialDataTransfer));
 
-      WidgetDragEvent *dragEvent = static_cast<WidgetDragEvent*>(aEvent);
+      WidgetDragEvent *dragEvent = aEvent->AsDragEvent();
 
       // collect any changes to moz cursor settings stored in the event's
       // data transfer.
       UpdateDragDataTransfer(dragEvent);
 
       // cancelling a dragenter or dragover event means that a drop should be
       // allowed, so update the dropEffect and the canDrop state to indicate
       // that a drag is allowed. If the event isn't cancelled, a drop won't be
@@ -3637,17 +3627,17 @@ nsEventStateManager::PostHandleEvent(nsP
                               static_cast<WidgetGUIEvent*>(aEvent));
     break;
 
   case NS_KEY_UP:
     break;
 
   case NS_KEY_PRESS:
     if (nsEventStatus_eConsumeNoDefault != *aStatus) {
-      WidgetKeyboardEvent* keyEvent = static_cast<WidgetKeyboardEvent*>(aEvent);
+      WidgetKeyboardEvent* keyEvent = aEvent->AsKeyboardEvent();
       //This is to prevent keyboard scrolling while alt modifier in use.
       if (!keyEvent->IsAlt()) {
         switch(keyEvent->keyCode) {
           case NS_VK_TAB:
           case NS_VK_F6:
             // Handling the tab event after it was sent to content is bad,
             // because to the FocusManager the remote-browser looks like one
             // element, so we would just move the focus to the next element
@@ -3704,18 +3694,17 @@ nsEventStateManager::PostHandleEvent(nsP
   mCurrentTargetContent = nullptr;
 
   return ret;
 }
 
 bool
 nsEventStateManager::RemoteQueryContentEvent(WidgetEvent* aEvent)
 {
-  WidgetQueryContentEvent *queryEvent =
-      static_cast<WidgetQueryContentEvent*>(aEvent);
+  WidgetQueryContentEvent* queryEvent = aEvent->AsQueryContentEvent();
   if (!IsTargetCrossProcess(queryEvent)) {
     return false;
   }
   // Will not be handled locally, remote the event
   GetCrossProcessTarget()->HandleQueryContentEvent(*queryEvent);
   return true;
 }
 
--- a/content/html/content/src/HTMLButtonElement.cpp
+++ b/content/html/content/src/HTMLButtonElement.cpp
@@ -255,18 +255,17 @@ HTMLButtonElement::PostHandleEvent(nsEve
 
   if (nsEventStatus_eIgnore == aVisitor.mEventStatus) {
     switch (aVisitor.mEvent->message) {
       case NS_KEY_PRESS:
       case NS_KEY_UP:
         {
           // For backwards compat, trigger buttons with space or enter
           // (bug 25300)
-          WidgetKeyboardEvent* keyEvent =
-            static_cast<WidgetKeyboardEvent*>(aVisitor.mEvent);
+          WidgetKeyboardEvent* keyEvent = aVisitor.mEvent->AsKeyboardEvent();
           if ((keyEvent->keyCode == NS_VK_RETURN &&
                NS_KEY_PRESS == aVisitor.mEvent->message) ||
               (keyEvent->keyCode == NS_VK_SPACE &&
                NS_KEY_UP == aVisitor.mEvent->message)) {
             nsEventStatus status = nsEventStatus_eIgnore;
 
             WidgetMouseEvent event(aVisitor.mEvent->mFlags.mIsTrusted,
                                    NS_MOUSE_CLICK, nullptr,
--- a/content/html/content/src/HTMLFormElement.cpp
+++ b/content/html/content/src/HTMLFormElement.cpp
@@ -681,26 +681,24 @@ nsresult
 HTMLFormElement::BuildSubmission(nsFormSubmission** aFormSubmission, 
                                  WidgetEvent* aEvent)
 {
   NS_ASSERTION(!mPendingSubmission, "tried to build two submissions!");
 
   // Get the originating frame (failure is non-fatal)
   nsGenericHTMLElement* originatingElement = nullptr;
   if (aEvent) {
-    if (NS_FORM_EVENT == aEvent->eventStructType) {
-      nsIContent* originator =
-        static_cast<InternalFormEvent*>(aEvent)->originator;
+    InternalFormEvent* formEvent = aEvent->AsFormEvent();
+    if (formEvent) {
+      nsIContent* originator = formEvent->originator;
       if (originator) {
         if (!originator->IsHTML()) {
           return NS_ERROR_UNEXPECTED;
         }
-        originatingElement =
-          static_cast<nsGenericHTMLElement*>(
-            static_cast<InternalFormEvent*>(aEvent)->originator);
+        originatingElement = static_cast<nsGenericHTMLElement*>(originator);
       }
     }
   }
 
   nsresult rv;
 
   //
   // Get the submission object
--- a/content/html/content/src/HTMLInputElement.cpp
+++ b/content/html/content/src/HTMLInputElement.cpp
@@ -3482,17 +3482,17 @@ HTMLInputElement::PostHandleEvent(nsEven
         case NS_FOCUS_CONTENT:
         {
           // see if we should select the contents of the textbox. This happens
           // for text and password fields when the field was focused by the
           // keyboard or a navigation, the platform allows it, and it wasn't
           // just because we raised a window.
           nsIFocusManager* fm = nsFocusManager::GetFocusManager();
           if (fm && IsSingleLineTextControl(false) &&
-              !(static_cast<InternalFocusEvent*>(aVisitor.mEvent))->fromRaise &&
+              !aVisitor.mEvent->AsFocusEvent()->fromRaise &&
               SelectTextFieldOnFocus()) {
             nsIDocument* document = GetCurrentDoc();
             if (document) {
               uint32_t lastFocusMethod;
               fm->GetLastFocusMethod(document->GetWindow(), &lastFocusMethod);
               if (lastFocusMethod &
                   (nsIFocusManager::FLAG_BYKEY | nsIFocusManager::FLAG_BYMOVEFOCUS)) {
                 nsRefPtr<nsPresContext> presContext = GetPresContext();
@@ -3505,19 +3505,17 @@ HTMLInputElement::PostHandleEvent(nsEven
           break;
         }
 
         case NS_KEY_PRESS:
         case NS_KEY_UP:
         {
           // For backwards compat, trigger checks/radios/buttons with
           // space or enter (bug 25300)
-          WidgetKeyboardEvent* keyEvent =
-            static_cast<WidgetKeyboardEvent*>(aVisitor.mEvent);
-
+          WidgetKeyboardEvent* keyEvent = aVisitor.mEvent->AsKeyboardEvent();
           if ((aVisitor.mEvent->message == NS_KEY_PRESS &&
                keyEvent->keyCode == NS_VK_RETURN) ||
               (aVisitor.mEvent->message == NS_KEY_UP &&
                keyEvent->keyCode == NS_VK_SPACE)) {
             switch(mType) {
               case NS_FORM_INPUT_CHECKBOX:
               case NS_FORM_INPUT_RADIO:
               {
@@ -3786,36 +3784,33 @@ HTMLInputElement::PostHandleEventForRang
     case NS_MOUSE_BUTTON_DOWN:
     case NS_TOUCH_START: {
       if (mIsDraggingRange) {
         break;
       }
       if (nsIPresShell::GetCapturingContent()) {
         break; // don't start drag if someone else is already capturing
       }
-      WidgetInputEvent* inputEvent =
-        static_cast<WidgetInputEvent*>(aVisitor.mEvent);
+      WidgetInputEvent* inputEvent = aVisitor.mEvent->AsInputEvent();
       if (inputEvent->IsShift() || inputEvent->IsControl() ||
           inputEvent->IsAlt() || inputEvent->IsMeta() ||
           inputEvent->IsAltGraph() || inputEvent->IsFn() ||
           inputEvent->IsOS()) {
         break; // ignore
       }
       if (aVisitor.mEvent->message == NS_MOUSE_BUTTON_DOWN) {
         WidgetMouseEvent* mouseEvent =
           static_cast<WidgetMouseEvent*>(aVisitor.mEvent);
         if (mouseEvent->buttons == WidgetMouseEvent::eLeftButtonFlag) {
           StartRangeThumbDrag(inputEvent);
         } else if (mIsDraggingRange) {
           CancelRangeThumbDrag();
         }
       } else {
-        WidgetTouchEvent* touchEvent =
-          static_cast<WidgetTouchEvent*>(aVisitor.mEvent);
-        if (touchEvent->touches.Length() == 1) {
+        if (aVisitor.mEvent->AsTouchEvent()->touches.Length() == 1) {
           StartRangeThumbDrag(inputEvent);
         } else if (mIsDraggingRange) {
           CancelRangeThumbDrag();
         }
       }
       aVisitor.mEvent->mFlags.mMultipleActionsPrevented = true;
     } break;
 
@@ -3825,38 +3820,36 @@ HTMLInputElement::PostHandleEventForRang
         break;
       }
       if (nsIPresShell::GetCapturingContent() != this) {
         // Someone else grabbed capture.
         CancelRangeThumbDrag();
         break;
       }
       SetValueOfRangeForUserEvent(
-        rangeFrame->GetValueAtEventPoint(
-          static_cast<WidgetInputEvent*>(aVisitor.mEvent)));
+        rangeFrame->GetValueAtEventPoint(aVisitor.mEvent->AsInputEvent()));
       aVisitor.mEvent->mFlags.mMultipleActionsPrevented = true;
       break;
 
     case NS_MOUSE_BUTTON_UP:
     case NS_TOUCH_END:
       if (!mIsDraggingRange) {
         break;
       }
       // We don't check to see whether we are the capturing content here and
       // call CancelRangeThumbDrag() if that is the case. We just finish off
       // the drag and set our final value (unless someone has called
       // preventDefault() and prevents us getting here).
-      FinishRangeThumbDrag(static_cast<WidgetInputEvent*>(aVisitor.mEvent));
+      FinishRangeThumbDrag(aVisitor.mEvent->AsInputEvent());
       aVisitor.mEvent->mFlags.mMultipleActionsPrevented = true;
       break;
 
     case NS_KEY_PRESS:
       if (mIsDraggingRange &&
-          static_cast<WidgetKeyboardEvent*>(aVisitor.mEvent)->keyCode ==
-            NS_VK_ESCAPE) {
+          aVisitor.mEvent->AsKeyboardEvent()->keyCode == NS_VK_ESCAPE) {
         CancelRangeThumbDrag();
       }
       break;
 
     case NS_TOUCH_CANCEL:
       if (mIsDraggingRange) {
         CancelRangeThumbDrag();
       }
--- a/content/html/content/src/HTMLLabelElement.cpp
+++ b/content/html/content/src/HTMLLabelElement.cpp
@@ -192,17 +192,17 @@ HTMLLabelElement::PostHandleEvent(nsEven
           //    sensible, we might send more events through like
           //    this.)  See bug 7554, bug 49897, and bug 96813.
           nsEventStatus status = aVisitor.mEventStatus;
           // Ok to use aVisitor.mEvent as parameter because DispatchClickEvent
           // will actually create a new event.
           EventFlags eventFlags;
           eventFlags.mMultipleActionsPrevented = true;
           DispatchClickEvent(aVisitor.mPresContext,
-                             static_cast<WidgetInputEvent*>(aVisitor.mEvent),
+                             aVisitor.mEvent->AsInputEvent(),
                              content, false, &eventFlags, &status);
           // Do we care about the status this returned?  I don't think we do...
           // Don't run another <label> off of this click
           aVisitor.mEvent->mFlags.mMultipleActionsPrevented = true;
         }
         break;
     }
     mHandlingEvent = false;
--- a/content/html/content/src/nsTextEditorState.cpp
+++ b/content/html/content/src/nsTextEditorState.cpp
@@ -848,18 +848,18 @@ nsTextInputListener::HandleEvent(nsIDOME
   bool isTrusted = false;
   rv = aEvent->GetIsTrusted(&isTrusted);
   NS_ENSURE_SUCCESS(rv, rv);
   if (!isTrusted) {
     return NS_OK;
   }
 
   WidgetKeyboardEvent* keyEvent =
-    static_cast<WidgetKeyboardEvent*>(aEvent->GetInternalNSEvent());
-  if (keyEvent->eventStructType != NS_KEY_EVENT) {
+    aEvent->GetInternalNSEvent()->AsKeyboardEvent();
+  if (!keyEvent) {
     return NS_ERROR_UNEXPECTED;
   }
 
   nsINativeKeyBindings *bindings = GetKeyBindings();
   if (bindings) {
     bool handled = false;
     switch (keyEvent->message) {
       case NS_KEY_DOWN:
--- a/content/media/gstreamer/GStreamerLoader.cpp
+++ b/content/media/gstreamer/GStreamerLoader.cpp
@@ -52,23 +52,32 @@ load_gstreamer()
   typedef typeof(::gst_version) VersionFuncType;
   if (VersionFuncType *versionFunc = (VersionFuncType*)dlsym(RTLD_DEFAULT, "gst_version")) {
     versionFunc(&major, &minor, &micro, &nano);
   }
 
   if (major == GST_VERSION_MAJOR && minor == GST_VERSION_MINOR) {
     gstreamerLib = RTLD_DEFAULT;
   } else {
+#ifdef __OpenBSD__
+    gstreamerLib = dlopen("libgstreamer-0.10.so", RTLD_NOW | RTLD_LOCAL);
+#else
     gstreamerLib = dlopen("libgstreamer-0.10.so.0", RTLD_NOW | RTLD_LOCAL);
+#endif
   }
 
   void *handles[] = {
     gstreamerLib,
+#ifdef __OpenBSD__
+    dlopen("libgstapp-0.10.so", RTLD_NOW | RTLD_LOCAL),
+    dlopen("libgstvideo-0.10.so", RTLD_NOW | RTLD_LOCAL)
+#else
     dlopen("libgstapp-0.10.so.0", RTLD_NOW | RTLD_LOCAL),
     dlopen("libgstvideo-0.10.so.0", RTLD_NOW | RTLD_LOCAL)
+#endif
   };
 
   for (size_t i = 0; i < sizeof(handles) / sizeof(handles[0]); i++) {
     if (!handles[i]) {
       goto fail;
     }
   }
 
--- a/content/media/omx/OmxDecoder.cpp
+++ b/content/media/omx/OmxDecoder.cpp
@@ -462,17 +462,17 @@ bool OmxDecoder::AllocateMediaResources(
   // it can't connect.
   OMXClient client;
   DebugOnly<status_t> err = client.connect();
   NS_ASSERTION(err == OK, "Failed to connect to OMX in mediaserver.");
   sp<IOMX> omx = client.interface();
 
   if ((mVideoTrack != nullptr) && (mVideoSource == nullptr)) {
     mNativeWindow = new GonkNativeWindow();
-#if defined(MOZ_WIDGET_GONK) && ANDROID_VERSION >= 18
+#if defined(MOZ_WIDGET_GONK) && ANDROID_VERSION >= 17
     mNativeWindowClient = new GonkNativeWindowClient(mNativeWindow->getBufferQueue());
 #else
     mNativeWindowClient = new GonkNativeWindowClient(mNativeWindow);
 #endif
 
     // Experience with OMX codecs is that only the HW decoders are
     // worth bothering with, at least on the platforms where this code
     // is currently used, and for formats this code is currently used
--- a/content/smil/nsDOMTimeEvent.cpp
+++ b/content/smil/nsDOMTimeEvent.cpp
@@ -21,18 +21,17 @@ nsDOMTimeEvent::nsDOMTimeEvent(mozilla::
   if (aEvent) {
     mEventIsInternal = false;
   } else {
     mEventIsInternal = true;
     mEvent->eventStructType = NS_SMIL_TIME_EVENT;
   }
 
   if (mEvent->eventStructType == NS_SMIL_TIME_EVENT) {
-    InternalUIEvent* event = static_cast<InternalUIEvent*>(mEvent);
-    mDetail = event->detail;
+    mDetail = mEvent->AsUIEvent()->detail;
   }
 
   mEvent->mFlags.mBubbles = false;
   mEvent->mFlags.mCancelable = false;
 
   if (mPresContext) {
     nsCOMPtr<nsISupports> container = mPresContext->GetContainer();
     if (container) {
--- a/content/xbl/src/nsXBLPrototypeHandler.cpp
+++ b/content/xbl/src/nsXBLPrototypeHandler.cpp
@@ -905,19 +905,18 @@ nsXBLPrototypeHandler::ReportKeyConflict
                                   params, ArrayLength(params),
                                   nullptr, EmptyString(), mLineNumber);
 }
 
 bool
 nsXBLPrototypeHandler::ModifiersMatchMask(nsIDOMUIEvent* aEvent,
                                           bool aIgnoreShiftKey)
 {
-  WidgetEvent* event = aEvent->GetInternalNSEvent();
-  NS_ENSURE_TRUE(event && event->IsInputDerivedEvent(), false);
-  WidgetInputEvent* inputEvent = static_cast<WidgetInputEvent*>(event);
+  WidgetInputEvent* inputEvent = aEvent->GetInternalNSEvent()->AsInputEvent();
+  NS_ENSURE_TRUE(inputEvent, false);
 
   if (mKeyMask & cMetaMask) {
     if (inputEvent->IsMeta() != ((mKeyMask & cMeta) != 0)) {
       return false;
     }
   }
 
   if (mKeyMask & cOSMask) {
--- a/content/xbl/src/nsXBLWindowKeyHandler.cpp
+++ b/content/xbl/src/nsXBLWindowKeyHandler.cpp
@@ -356,20 +356,19 @@ nsXBLWindowKeyHandler::WalkHandlers(nsID
     // get the DOM window we're attached to
     nsCOMPtr<nsIControllers> controllers;
     nsCOMPtr<nsPIWindowRoot> root = do_QueryInterface(mTarget);
     if (root) {
       root->GetControllers(getter_AddRefs(controllers));
     }
 
     WidgetKeyboardEvent* keyEvent =
-      static_cast<WidgetKeyboardEvent*>(aKeyEvent->GetInternalNSEvent());
-    MOZ_ASSERT(keyEvent->eventStructType == NS_KEY_EVENT,
+      aKeyEvent->GetInternalNSEvent()->AsKeyboardEvent();
+    MOZ_ASSERT(keyEvent,
                "DOM key event's internal event must be WidgetKeyboardEvent");
-
     bool handled = false;
     switch (keyEvent->message) {
       case NS_KEY_PRESS:
         handled = sNativeEditorBindings->KeyPress(*keyEvent,
                                                   DoCommandCallback,
                                                   controllers);
         break;
       case NS_KEY_UP:
--- a/content/xul/content/src/nsXULElement.cpp
+++ b/content/xul/content/src/nsXULElement.cpp
@@ -1185,18 +1185,17 @@ nsXULElement::PreHandleEvent(nsEventChai
                         do_QueryInterface(domEvent);
                     if (commandEvent) {
                         commandEvent->GetSourceEvent(getter_AddRefs(domEvent));
                     } else {
                         domEvent = nullptr;
                     }
                 }
 
-                WidgetInputEvent* orig =
-                    static_cast<WidgetInputEvent*>(aVisitor.mEvent);
+                WidgetInputEvent* orig = aVisitor.mEvent->AsInputEvent();
                 nsContentUtils::DispatchXULCommand(
                   commandContent,
                   aVisitor.mEvent->mFlags.mIsTrusted,
                   aVisitor.mDOMEvent,
                   nullptr,
                   orig->IsControl(),
                   orig->IsAlt(),
                   orig->IsShift(),
--- a/dom/base/DOMRequestHelper.jsm
+++ b/dom/base/DOMRequestHelper.jsm
@@ -47,17 +47,18 @@ this.DOMRequestIpcHelper = function DOMR
 }
 
 DOMRequestIpcHelper.prototype = {
   /**
    * An object which "inherits" from DOMRequestIpcHelper, declares its own
    * queryInterface method and adds at least one weak listener to the Message
    * Manager MUST implement Ci.nsISupportsWeakReference.
    */
-  QueryInterface: XPCOMUtils.generateQI([Ci.nsISupportsWeakReference]),
+  QueryInterface: XPCOMUtils.generateQI([Ci.nsISupportsWeakReference,
+                                         Ci.nsIObserver]),
 
    /**
    *  'aMessages' is expected to be an array of either:
    *  - objects of this form:
    *    {
    *      name: "messageName",
    *      strongRef: false
    *    }
@@ -149,33 +150,46 @@ DOMRequestIpcHelper.prototype = {
     this._window = aWindow;
     if (this._window) {
       // We don't use this.innerWindowID, but other classes rely on it.
       let util = this._window.QueryInterface(Ci.nsIInterfaceRequestor)
                              .getInterface(Ci.nsIDOMWindowUtils);
       this.innerWindowID = util.currentInnerWindowID;
     }
 
+    this._destroyed = false;
+
     Services.obs.addObserver(this, "inner-window-destroyed", false);
   },
 
   destroyDOMRequestHelper: function() {
+    if (this._destroyed) {
+      return;
+    }
+
+    this._destroyed = true;
+
     Services.obs.removeObserver(this, "inner-window-destroyed");
 
     if (this._listeners) {
       Object.keys(this._listeners).forEach((aName) => {
         this._listeners[aName] ? cpmm.removeMessageListener(aName, this)
                                : cpmm.removeWeakMessageListener(aName, this);
         delete this._listeners[aName];
       });
     }
 
     this._listeners = null;
     this._requests = null;
     this._window = null;
+
+    // Objects inheriting from DOMRequestIPCHelper may have an uninit function.
+    if (this.uninit) {
+      this.uninit();
+    }
   },
 
   observe: function(aSubject, aTopic, aData) {
     if (aTopic !== "inner-window-destroyed") {
       return;
     }
 
     let wId = aSubject.QueryInterface(Ci.nsISupportsPRUint64).data;
--- a/dom/base/URL.cpp
+++ b/dom/base/URL.cpp
@@ -41,17 +41,18 @@ URL::Constructor(const GlobalObject& aGl
     aRv.Throw(rv);
     return nullptr;
   }
 
   nsCOMPtr<nsIURI> uri;
   rv = ioService->NewURI(NS_ConvertUTF16toUTF8(aUrl), nullptr, aBase.GetURI(),
                          getter_AddRefs(uri));
   if (NS_FAILED(rv)) {
-    aRv.Throw(NS_ERROR_DOM_TYPE_ERR);
+    nsAutoString label(aUrl);
+    aRv.ThrowTypeError(MSG_INVALID_URL, &label);
     return nullptr;
   }
 
   nsRefPtr<URL> url = new URL(uri);
   return url.forget();
 }
 
 /* static */ already_AddRefed<URL>
@@ -64,25 +65,27 @@ URL::Constructor(const GlobalObject& aGl
     aRv.Throw(rv);
     return nullptr;
   }
 
   nsCOMPtr<nsIURI> baseUri;
   rv = ioService->NewURI(NS_ConvertUTF16toUTF8(aBase), nullptr, nullptr,
                          getter_AddRefs(baseUri));
   if (NS_FAILED(rv)) {
-    aRv.Throw(NS_ERROR_DOM_TYPE_ERR);
+    nsAutoString label(aBase);
+    aRv.ThrowTypeError(MSG_INVALID_URL, &label);
     return nullptr;
   }
 
   nsCOMPtr<nsIURI> uri;
   rv = ioService->NewURI(NS_ConvertUTF16toUTF8(aUrl), nullptr, baseUri,
                          getter_AddRefs(uri));
   if (NS_FAILED(rv)) {
-    aRv.Throw(NS_ERROR_DOM_TYPE_ERR);
+    nsAutoString label(aUrl);
+    aRv.ThrowTypeError(MSG_INVALID_URL, &label);
     return nullptr;
   }
 
   nsRefPtr<URL> url = new URL(uri);
   return url.forget();
 }
 
 void
@@ -198,17 +201,18 @@ URL::SetHref(const nsAString& aHref, Err
   if (NS_FAILED(rv)) {
     aRv.Throw(rv);
     return;
   }
 
   nsCOMPtr<nsIURI> uri;
   rv = ioService->NewURI(href, nullptr, nullptr, getter_AddRefs(uri));
   if (NS_FAILED(rv)) {
-    aRv.Throw(NS_ERROR_DOM_TYPE_ERR);
+    nsAutoString label(aHref);
+    aRv.ThrowTypeError(MSG_INVALID_URL, &label);
     return;
   }
 
   aRv = mURI->SetSpec(href);
 }
 
 void
 URL::GetOrigin(nsString& aOrigin) const
--- a/dom/base/test/test_urlExceptions.html
+++ b/dom/base/test/test_urlExceptions.html
@@ -19,39 +19,39 @@ https://bugzilla.mozilla.org/show_bug.cg
 <pre id="test">
 </pre>
   <script type="application/javascript">
 
   // URL.href throws
   var url = new URL('http://www.example.com');
   ok(url, "URL created");
 
-  var status = false;
   try {
     url.href = '42';
+    ok(false, "url.href = 42 should throw");
   } catch(e) {
-    status = true;
+    ok(true, "url.href = 42 should throw");
+    ok(e instanceof TypeError, "error type typeError");
   }
-  ok(status, "url.href = 42 should throw");
 
   url.href = 'http://www.example.org';
   ok(true, "url.href should not throw");
 
-  status = false
   try {
     new URL('42');
+    ok(false, "new URL(42) should throw");
   } catch(e) {
-    status = true;
+    ok(true, "new URL(42) should throw");
+    ok(e instanceof TypeError, "error type typeError");
   }
-  ok(status, "new URL(42) should throw");
 
-  status = false
   try {
     new URL('http://www.example.com', '42');
+    ok(false, "new URL(something, 42) should throw");
   } catch(e) {
-    status = true;
+    ok(true, "new URL(something, 42) should throw");
+    ok(e instanceof TypeError, "error type typeError");
   }
-  ok(status, "new URL(something, 42) should throw");
 
   </script>
 </body>
 </html>
 
--- a/dom/bindings/Errors.msg
+++ b/dom/bindings/Errors.msg
@@ -42,8 +42,9 @@ MSG_DEF(MSG_ENCODING_NOT_SUPPORTED, 1, "
 MSG_DEF(MSG_DOM_ENCODING_NOT_UTF, 0, "The encoding must be utf-8, utf-16, or utf-16be.")
 MSG_DEF(MSG_NOT_FINITE, 1, "{0} is not a finite floating-point value.")
 MSG_DEF(MSG_INVALID_VERSION, 0, "0 (Zero) is not a valid database version.")
 MSG_DEF(MSG_INVALID_BYTESTRING, 2, "Cannot convert string to ByteString because the character"
         " at index {0} has value {1} which is greater than 255.")
 MSG_DEF(MSG_NOT_DATE, 1, "{0} is not a date.")
 MSG_DEF(MSG_INVALID_ADVANCE_COUNT, 0, "0 (Zero) is not a valid advance count.")
 MSG_DEF(MSG_DEFINEPROPERTY_ON_GSP, 0, "Not allowed to define a property on the named properties object.")
+MSG_DEF(MSG_INVALID_URL, 1, "{0} is not a valid URL.")
--- a/dom/bindings/parser/WebIDL.py
+++ b/dom/bindings/parser/WebIDL.py
@@ -4775,17 +4775,18 @@ class Parser(Tokenizer):
     def p_ExtendedAttributeNamedArgList(self, p):
         """
             ExtendedAttributeNamedArgList : IDENTIFIER EQUALS IDENTIFIER LPAREN ArgumentList RPAREN
         """
         p[0] = (p[1], p[3], p[5])
 
     def p_error(self, p):
         if not p:
-            raise WebIDLError("Syntax Error at end of file. Possibly due to missing semicolon(;), braces(}) or both", [])
+            raise WebIDLError("Syntax Error at end of file. Possibly due to missing semicolon(;), braces(}) or both",
+                              [self._filename])
         else:
             raise WebIDLError("invalid syntax", [Location(self.lexer, p.lineno, p.lexpos, self._filename)])
 
     def __init__(self, outputdir='', lexer=None):
         Tokenizer.__init__(self, outputdir, lexer)
         self.parser = yacc.yacc(module=self,
                                 outputdir=outputdir,
                                 tabmodule='webidlyacc',
--- a/dom/camera/GonkCameraHwMgr.cpp
+++ b/dom/camera/GonkCameraHwMgr.cpp
@@ -178,17 +178,17 @@ GonkCameraHardware::Init()
   DOM_CAMERA_LOGI("Sensor orientation: base=%d, offset=%d, final=%d\n", info.orientation, offset, mSensorOrientation);
 
   // Disable shutter sound in android CameraService because gaia camera app will play it
   mCamera->sendCommand(CAMERA_CMD_ENABLE_SHUTTER_SOUND, 0, 0);
 
   mNativeWindow = new GonkNativeWindow();
   mNativeWindow->setNewFrameCallback(this);
   mCamera->setListener(this);
-#if defined(MOZ_WIDGET_GONK) && ANDROID_VERSION >= 18
+#if defined(MOZ_WIDGET_GONK) && ANDROID_VERSION >= 17
   mCamera->setPreviewTexture(mNativeWindow->getBufferQueue());
 #else
   mCamera->setPreviewTexture(mNativeWindow);
 #endif
   mInitialized = true;
 }
 
 sp<GonkCameraHardware>
--- a/dom/camera/GonkCameraSource.cpp
+++ b/dom/camera/GonkCameraSource.cpp
@@ -125,17 +125,17 @@ static int32_t getColorFormat(const char
 
     if (!strcmp(colorFormat, CameraParameters::PIXEL_FORMAT_RGB565)) {
        return OMX_COLOR_Format16bitRGB565;
     }
 
     if (!strcmp(colorFormat, "OMX_TI_COLOR_FormatYUV420PackedSemiPlanar")) {
        return OMX_TI_COLOR_FormatYUV420PackedSemiPlanar;
     }
-#if defined(MOZ_WIDGET_GONK) && ANDROID_VERSION >= 18
+#if defined(MOZ_WIDGET_GONK) && ANDROID_VERSION >= 17
     if (!strcmp(colorFormat, CameraParameters::PIXEL_FORMAT_ANDROID_OPAQUE)) {
         return OMX_COLOR_FormatAndroidOpaque;
     }
 #endif
     CS_LOGE("Uknown color format (%s), please add it to "
          "GonkCameraSource::getColorFormat", colorFormat);
 
     CHECK(!"Unknown color format");
@@ -537,17 +537,17 @@ status_t GonkCameraSource::start(MetaDat
 
     mStartTimeUs = 0;
     mNumInputBuffers = 0;
     if (meta) {
         int64_t startTimeUs;
         if (meta->findInt64(kKeyTime, &startTimeUs)) {
             mStartTimeUs = startTimeUs;
         }
-#if defined(MOZ_WIDGET_GONK) && ANDROID_VERSION >= 18
+#if defined(MOZ_WIDGET_GONK) && ANDROID_VERSION >= 17
         int32_t nBuffers;
         if (meta->findInt32(kKeyNumBuffers, &nBuffers)) {
             CHECK_GT(nBuffers, 0);
             mNumInputBuffers = nBuffers;
         }
 #endif
     }
 
--- a/dom/contacts/ContactManager.js
+++ b/dom/contacts/ContactManager.js
@@ -10,471 +10,280 @@ function debug(s) { dump("-*- ContactMan
 const Cc = Components.classes;
 const Ci = Components.interfaces;
 const Cu = Components.utils;
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/DOMRequestHelper.jsm");
 
+XPCOMUtils.defineLazyServiceGetter(Services, "DOMRequest",
+                                   "@mozilla.org/dom/dom-request-service;1",
+                                   "nsIDOMRequestService");
+
+XPCOMUtils.defineLazyServiceGetter(this, "pm",
+                                   "@mozilla.org/permissionmanager;1",
+                                   "nsIPermissionManager");
+
 XPCOMUtils.defineLazyServiceGetter(this, "cpmm",
                                    "@mozilla.org/childprocessmessagemanager;1",
                                    "nsIMessageSender");
 
 const CONTACTS_SENDMORE_MINIMUM = 5;
 
-function stringOrBust(aObj) {
-  if (typeof aObj != "string") {
-    if (DEBUG) debug("Field is not a string and was ignored.");
-    return undefined;
-  } else {
-    return aObj;
-  }
-}
-
-function sanitizeStringArray(aArray) {
-  if (!Array.isArray(aArray)) {
-    aArray = [aArray];
-  }
-  return aArray.map(stringOrBust).filter(function(el) { return el != undefined; });
-}
-
-const nsIClassInfo            = Ci.nsIClassInfo;
-const CONTACTPROPERTIES_CID   = Components.ID("{35ad8a4e-9486-44b6-883d-550f14635e49}");
-const nsIContactProperties    = Ci.nsIContactProperties;
+function ContactAddressImpl() { }
 
-// ContactProperties is not directly instantiated. It is used as interface.
-
-function ContactProperties(aProp) { if (DEBUG) debug("ContactProperties Constructor"); }
-
-ContactProperties.prototype = {
-
-  classID : CONTACTPROPERTIES_CID,
-  classInfo : XPCOMUtils.generateCI({classID: CONTACTPROPERTIES_CID,
-                                     contractID:"@mozilla.org/contactProperties;1",
-                                     classDescription: "ContactProperties",
-                                     interfaces: [nsIContactProperties],
-                                     flags: nsIClassInfo.DOM_OBJECT}),
-
-  QueryInterface : XPCOMUtils.generateQI([nsIContactProperties])
-}
-
-//ContactAddress
-
-const CONTACTADDRESS_CONTRACTID = "@mozilla.org/contactAddress;1";
-const CONTACTADDRESS_CID        = Components.ID("{9cbfa81c-bcab-4ca9-b0d2-f4318f295e33}");
-const nsIContactAddress         = Components.interfaces.nsIContactAddress;
+ContactAddressImpl.prototype = {
+  // This function is meant to be called via bindings code for type checking,
+  // don't call it directly. Instead, create a content object and call initialize
+  // on that.
+  initialize: function(aType, aStreetAddress, aLocality, aRegion, aPostalCode, aCountryName, aPref) {
+    this.type = aType;
+    this.streetAddress = aStreetAddress;
+    this.locality = aLocality;
+    this.region = aRegion;
+    this.postalCode = aPostalCode;
+    this.countryName = aCountryName;
+    this.pref = aPref;
+  },
 
-function ContactAddress(aType, aStreetAddress, aLocality, aRegion, aPostalCode, aCountryName, aPref) {
-  this.type = sanitizeStringArray(aType);
-  this.streetAddress = stringOrBust(aStreetAddress);
-  this.locality = stringOrBust(aLocality);
-  this.region = stringOrBust(aRegion);
-  this.postalCode = stringOrBust(aPostalCode);
-  this.countryName = stringOrBust(aCountryName);
-  this.pref = aPref;
-};
-
-ContactAddress.prototype = {
-  __exposedProps__: {
-                      type: 'rw',
-                      streetAddress: 'rw',
-                      locality: 'rw',
-                      region: 'rw',
-                      postalCode: 'rw',
-                      countryName: 'rw',
-                      pref: 'rw'
-                     },
+  toJSON: function(excludeExposedProps) {
+    let json = {
+      type: this.type,
+      streetAddress: this.streetAddress,
+      locality: this.locality,
+      region: this.region,
+      postalCode: this.postalCode,
+      countryName: this.countryName,
+      pref: this.pref,
+    };
+    if (!excludeExposedProps) {
+      json.__exposedProps__ = {
+        type: "rw",
+        streetAddress: "rw",
+        locality: "rw",
+        region: "rw",
+        postalCode: "rw",
+        countryName: "rw",
+        pref: "rw",
+      };
+    }
+    return json;
+  },
 
-  classID : CONTACTADDRESS_CID,
-  classInfo : XPCOMUtils.generateCI({classID: CONTACTADDRESS_CID,
-                                     contractID: CONTACTADDRESS_CONTRACTID,
-                                     classDescription: "ContactAddress",
-                                     interfaces: [nsIContactAddress],
-                                     flags: nsIClassInfo.DOM_OBJECT}),
-
-  QueryInterface : XPCOMUtils.generateQI([nsIContactAddress])
-}
-
-//ContactField
-
-const CONTACTFIELD_CONTRACTID = "@mozilla.org/contactField;1";
-const CONTACTFIELD_CID        = Components.ID("{ad19a543-69e4-44f0-adfa-37c011556bc1}");
-const nsIContactField         = Components.interfaces.nsIContactField;
-
-function ContactField(aType, aValue, aPref) {
-  this.type = sanitizeStringArray(aType);
-  this.value = stringOrBust(aValue);
-  this.pref = aPref;
+  classID: Components.ID("{9cbfa81c-bcab-4ca9-b0d2-f4318f295e33}"),
+  contractID: "@mozilla.org/contactAddress;1",
+  QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports]),
 };
 
-ContactField.prototype = {
-  __exposedProps__: {
-                      type: 'rw',
-                      value: 'rw',
-                      pref: 'rw'
-                     },
+function ContactFieldImpl() { }
 
-  classID : CONTACTFIELD_CID,
-  classInfo : XPCOMUtils.generateCI({classID: CONTACTFIELD_CID,
-                                     contractID: CONTACTFIELD_CONTRACTID,
-                                     classDescription: "ContactField",
-                                     interfaces: [nsIContactField],
-                                     flags: nsIClassInfo.DOM_OBJECT}),
+ContactFieldImpl.prototype = {
+  // This function is meant to be called via bindings code for type checking,
+  // don't call it directly. Instead, create a content object and call initialize
+  // on that.
+  initialize: function(aType, aValue, aPref) {
+    this.type = aType;
+    this.value = aValue;
+    this.pref = aPref;
+  },
 
-  QueryInterface : XPCOMUtils.generateQI([nsIContactField])
-}
-
-//ContactTelField
+  toJSON: function(excludeExposedProps) {
+    let json = {
+      type: this.type,
+      value: this.value,
+      pref: this.pref,
+    };
+    if (!excludeExposedProps) {
+      json.__exposedProps__ = {
+        type: "rw",
+        value: "rw",
+        pref: "rw",
+      };
+    }
+    return json;
+  },
 
-const CONTACTTELFIELD_CONTRACTID = "@mozilla.org/contactTelField;1";
-const CONTACTTELFIELD_CID        = Components.ID("{4d42c5a9-ea5d-4102-80c3-40cc986367ca}");
-const nsIContactTelField         = Components.interfaces.nsIContactTelField;
-
-function ContactTelField(aType, aValue, aCarrier, aPref) {
-  this.type = sanitizeStringArray(aType);
-  this.value = stringOrBust(aValue);
-  this.carrier = stringOrBust(aCarrier);
-  this.pref = aPref;
+  classID: Components.ID("{ad19a543-69e4-44f0-adfa-37c011556bc1}"),
+  contractID: "@mozilla.org/contactField;1",
+  QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports]),
 };
 
-ContactTelField.prototype = {
-  __exposedProps__: {
-                      type: 'rw',
-                      value: 'rw',
-                      carrier: 'rw',
-                      pref: 'rw'
-                     },
+function ContactTelFieldImpl() { }
 
-  classID : CONTACTTELFIELD_CID,
-  classInfo : XPCOMUtils.generateCI({classID: CONTACTTELFIELD_CID,
-                                     contractID: CONTACTTELFIELD_CONTRACTID,
-                                     classDescription: "ContactTelField",
-                                     interfaces: [nsIContactTelField],
-                                     flags: nsIClassInfo.DOM_OBJECT}),
+ContactTelFieldImpl.prototype = {
+  // This function is meant to be called via bindings code for type checking,
+  // don't call it directly. Instead, create a content object and call initialize
+  // on that.
+  initialize: function(aType, aValue, aCarrier, aPref) {
+    this.type = aType;
+    this.value = aValue;
+    this.carrier = aCarrier;
+    this.pref = aPref;
+  },
 
-  QueryInterface : XPCOMUtils.generateQI([nsIContactTelField])
-}
-
-//ContactFindSortOptions
-
-const CONTACTFINDSORTOPTIONS_CONTRACTID = "@mozilla.org/contactFindSortOptions;1"
-const CONTACTFINDSORTOPTIONS_CID        = Components.ID("{0a5b1fab-70da-46dd-b902-619904d920c2}");
-const nsIContactFindSortOptions         = Ci.nsIContactFindSortOptions;
+  toJSON: function(excludeExposedProps) {
+    let json = {
+      type: this.type,
+      value: this.value,
+      carrier: this.carrier,
+      pref: this.pref,
+    };
+    if (!excludeExposedProps) {
+      json.__exposedProps__ = {
+        type: "rw",
+        value: "rw",
+        carrier: "rw",
+        pref: "rw",
+      };
+    }
+    return json;
+  },
 
-function ContactFindSortOptions () { }
-
-ContactFindSortOptions.prototype = {
-  classID: CONTACTFINDSORTOPTIONS_CID,
-  classInfo: XPCOMUtils.generateCI({classID: CONTACTFINDSORTOPTIONS_CID,
-                                    contractID: CONTACTFINDSORTOPTIONS_CONTRACTID,
-                                    classDescription: "ContactFindSortOptions",
-                                    interfaces: [nsIContactFindSortOptions],
-                                    flags: nsIClassInfo.DOM_OBJECT}),
-  QueryInterface: XPCOMUtils.generateQI([nsIContactFindSortOptions])
+  classID: Components.ID("{4d42c5a9-ea5d-4102-80c3-40cc986367ca}"),
+  contractID: "@mozilla.org/contactTelField;1",
+  QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports]),
 };
 
-//ContactFindOptions
-
-const CONTACTFINDOPTIONS_CONTRACTID = "@mozilla.org/contactFindOptions;1";
-const CONTACTFINDOPTIONS_CID        = Components.ID("{28ce07d0-45d9-4b7a-8843-521df4edd8bc}");
-const nsIContactFindOptions         = Components.interfaces.nsIContactFindOptions;
-
-function ContactFindOptions() { };
-
-ContactFindOptions.prototype = {
-
-  classID : CONTACTFINDOPTIONS_CID,
-  classInfo : XPCOMUtils.generateCI({classID: CONTACTFINDOPTIONS_CID,
-                                     contractID: CONTACTFINDOPTIONS_CONTRACTID,
-                                     classDescription: "ContactFindOptions",
-                                     interfaces: [nsIContactFindSortOptions,
-                                                  nsIContactFindOptions],
-                                     flags: nsIClassInfo.DOM_OBJECT}),
+function validateArrayField(data, createCb) {
+  // We use an array-like Proxy to validate data set by content, since we don't
+  // have WebIDL arrays yet. See bug 851726.
 
-  QueryInterface : XPCOMUtils.generateQI([nsIContactFindSortOptions,
-                                          nsIContactFindOptions])
-}
-
-//Contact
-
-const CONTACT_CONTRACTID = "@mozilla.org/contact;1";
-const CONTACT_CID        = Components.ID("{72a5ee28-81d8-4af8-90b3-ae935396cc66}");
-const nsIDOMContact      = Components.interfaces.nsIDOMContact;
-
-function checkBlobArray(aBlob) {
-  if (Array.isArray(aBlob)) {
-    for (let i = 0; i < aBlob.length; i++) {
-      if (typeof aBlob != 'object') {
-        return null;
+  // ArrayPropertyExposedPropsProxy is used to return "rw" for any valid index
+  // and "length" in __exposedProps__.
+  const ArrayPropertyExposedPropsProxy = new Proxy({}, {
+    get: function(target, name) {
+      // Test for index access
+      if (String(name >>> 0) === name) {
+        return "rw";
       }
-      if (!(aBlob[i] instanceof Components.interfaces.nsIDOMBlob)) {
-        return null;
+      if (name === "length") {
+        return "r";
       }
     }
-    return aBlob;
-  }
-  return null;
-}
+  });
 
-function isVanillaObj(aObj) {
-  return Object.prototype.toString.call(aObj) == "[object Object]";
-}
+  const ArrayPropertyHandler = {
+    set: function(target, name, val, receiver) {
+      // Test for index access
+      if (String(name >>> 0) === name) {
+        target[name] = createCb(val);
+      }
+    },
+    get: function(target, name) {
+      if (name === "__exposedProps__") {
+        return ArrayPropertyExposedPropsProxy;
+      }
+      return target[name];
+    }
+  };
 
-function validateArrayField(data, createCb) {
   if (data) {
     data = Array.isArray(data) ? data : [data];
     let filtered = [];
     for (let i = 0, n = data.length; i < n; ++i) {
-      let obj = data[i];
-      if (obj && isVanillaObj(obj)) {
-        filtered.push(createCb(obj));
-      }
+      filtered.push(createCb(data[i]));
     }
-    return filtered;
+    if (filtered.length === 0) {
+      return undefined;
+    }
+    return new Proxy(filtered, ArrayPropertyHandler);
   }
   return undefined;
 }
 
-function Contact() { };
+// We need this to create a copy of the mozContact object in ContactManager.save
+// Keep in sync with the interfaces.
+const PROPERTIES = [
+  "name", "honorificPrefix", "givenName", "additionalName", "familyName",
+  "honorificSuffix", "nickname", "photo", "category", "org", "jobTitle",
+  "bday", "note", "anniversary", "sex", "genderIdentity", "key"
+];
+const ADDRESS_PROPERTIES = ["adr"];
+const FIELD_PROPERTIES = ["email", "url", "impp"];
+const TELFIELD_PROPERTIES = ["tel"];
+
+function Contact() { }
 
 Contact.prototype = {
-  __exposedProps__: {
-                      id: 'rw',
-                      updated: 'rw',
-                      published:  'rw',
-                      name: 'rw',
-                      honorificPrefix: 'rw',
-                      givenName: 'rw',
-                      additionalName: 'rw',
-                      familyName: 'rw',
-                      honorificSuffix: 'rw',
-                      nickname: 'rw',
-                      email: 'rw',
-                      photo: 'rw',
-                      url: 'rw',
-                      category: 'rw',
-                      adr: 'rw',
-                      tel: 'rw',
-                      org: 'rw',
-                      jobTitle: 'rw',
-                      bday: 'rw',
-                      note: 'rw',
-                      impp: 'rw',
-                      anniversary: 'rw',
-                      sex: 'rw',
-                      genderIdentity: 'rw',
-                      key: 'rw',
-                     },
-
-  set name(aName) {
-    this._name = sanitizeStringArray(aName);
-  },
-
-  get name() {
-    return this._name;
-  },
-
-  set honorificPrefix(aHonorificPrefix) {
-    this._honorificPrefix = sanitizeStringArray(aHonorificPrefix);
-  },
-
-  get honorificPrefix() {
-    return this._honorificPrefix;
-  },
-
-  set givenName(aGivenName) {
-    this._givenName = sanitizeStringArray(aGivenName);
-  },
-
-  get givenName() {
-    return this._givenName;
-  },
-
-  set additionalName(aAdditionalName) {
-    this._additionalName = sanitizeStringArray(aAdditionalName);
-  },
-
-  get additionalName() {
-    return this._additionalName;
-  },
-
-  set familyName(aFamilyName) {
-    this._familyName = sanitizeStringArray(aFamilyName);
-  },
-
-  get familyName() {
-    return this._familyName;
-  },
-
-  set honorificSuffix(aHonorificSuffix) {
-    this._honorificSuffix = sanitizeStringArray(aHonorificSuffix);
-  },
-
-  get honorificSuffix() {
-    return this._honorificSuffix;
-  },
-
-  set nickname(aNickname) {
-    this._nickname = sanitizeStringArray(aNickname);
-  },
-
-  get nickname() {
-    return this._nickname;
-  },
-
-  set photo(aPhoto) {
-    this._photo = checkBlobArray(aPhoto);
-  },
-
-  get photo() {
-    return this._photo;
-  },
-
-  set category(aCategory) {
-    this._category = sanitizeStringArray(aCategory);
-  },
-
-  get category() {
-    return this._category;
-  },
-
+  // We need to create the content interfaces in these setters, otherwise when
+  // we return these objects (e.g. from a find call), the values in the array
+  // will be COW's, and content cannot see the properties.
   set email(aEmail) {
-    this._email = validateArrayField(aEmail, function(email) {
-      return new ContactField(email.type, email.value, email.pref);
-    });
+    this._email = aEmail;
   },
 
   get email() {
+    this._email = validateArrayField(this._email, function(email) {
+      let obj = this._window.ContactField._create(this._window, new ContactFieldImpl());
+      obj.initialize(email.type, email.value, email.pref);
+      return obj;
+    }.bind(this));
     return this._email;
   },
 
   set adr(aAdr) {
-    this._adr = validateArrayField(aAdr, function(adr) {
-      return new ContactAddress(adr.type, adr.streetAddress, adr.locality,
-                                adr.region, adr.postalCode, adr.countryName,
-                                adr.pref);
-    });
+    this._adr = aAdr;
   },
 
   get adr() {
+    this._adr = validateArrayField(this._adr, function(adr) {
+      let obj = this._window.ContactAddress._create(this._window, new ContactAddressImpl());
+      obj.initialize(adr.type, adr.streetAddress, adr.locality,
+                     adr.region, adr.postalCode, adr.countryName,
+                     adr.pref);
+      return obj;
+    }.bind(this));
     return this._adr;
   },
 
   set tel(aTel) {
-    this._tel = validateArrayField(aTel, function(tel) {
-      return new ContactTelField(tel.type, tel.value, tel.carrier, tel.pref);
-    });
+    this._tel = aTel;
   },
 
   get tel() {
+    this._tel = validateArrayField(this._tel, function(tel) {
+      let obj = this._window.ContactTelField._create(this._window, new ContactTelFieldImpl());
+      obj.initialize(tel.type, tel.value, tel.carrier, tel.pref);
+      return obj;
+    }.bind(this));
     return this._tel;
   },
 
   set impp(aImpp) {
-    this._impp = validateArrayField(aImpp, function(impp) {
-      return new ContactField(impp.type, impp.value, impp.pref);
-    });
+    this._impp = aImpp;
   },
 
   get impp() {
+    this._impp = validateArrayField(this._impp, function(impp) {
+      let obj = this._window.ContactField._create(this._window, new ContactFieldImpl());
+      obj.initialize(impp.type, impp.value, impp.pref);
+      return obj;
+    }.bind(this));
     return this._impp;
   },
 
   set url(aUrl) {
-    this._url = validateArrayField(aUrl, function(url) {
-      return new ContactField(url.type, url.value, url.pref);
-    });
+    this._url = aUrl;
   },
 
   get url() {
+    this._url = validateArrayField(this._url, function(url) {
+      let obj = this._window.ContactField._create(this._window, new ContactFieldImpl());
+      obj.initialize(url.type, url.value, url.pref);
+      return obj;
+    }.bind(this));
     return this._url;
   },
 
-  set org(aOrg) {
-    this._org = sanitizeStringArray(aOrg);
-  },
-
-  get org() {
-    return this._org;
-  },
-
-  set jobTitle(aJobTitle) {
-    this._jobTitle = sanitizeStringArray(aJobTitle);
-  },
-
-  get jobTitle() {
-    return this._jobTitle;
-  },
-
-  set note(aNote) {
-    this._note = sanitizeStringArray(aNote);
-  },
-
-  get note() {
-    return this._note;
-  },
-
-  set bday(aBday) {
-    if (aBday && aBday.constructor.name === "Date") {
-      this._bday = aBday;
-    } else if (typeof aBday === "string" || typeof aBday === "number") {
-      this._bday = new Date(aBday);
-    }
-  },
-
-  get bday() {
-    return this._bday;
+  init: function(aWindow) {
+    this._window = aWindow;
   },
 
-  set anniversary(aAnniversary) {
-    if (aAnniversary && aAnniversary.constructor.name === "Date") {
-      this._anniversary = aAnniversary;
-    } else if (typeof aAnniversary === "string" || typeof aAnniversary === "number") {
-      this._anniversary = new Date(aAnniversary);
-    }
-  },
-
-  get anniversary() {
-    return this._anniversary;
-  },
-
-  set sex(aSex) {
-    if (aSex !== "undefined") {
-      this._sex = aSex;
-    } else {
-      this._sex = null;
-    }
-  },
-
-  get sex() {
-    return this._sex;
-  },
-
-  set genderIdentity(aGenderIdentity) {
-    if (aGenderIdentity !== "undefined") {
-      this._genderIdentity = aGenderIdentity;
-    } else {
-      this._genderIdentity = null;
-    }
-  },
-
-  get genderIdentity() {
-    return this._genderIdentity;
-  },
-
-  set key(aKey) {
-    this._key = sanitizeStringArray(aKey);
-  },
-
-  get key() {
-    return this._key;
-  },
-
-  init: function init(aProp) {
+  __init: function(aProp) {
     this.name =            aProp.name;
     this.honorificPrefix = aProp.honorificPrefix;
     this.givenName =       aProp.givenName;
     this.additionalName =  aProp.additionalName;
     this.familyName =      aProp.familyName;
     this.honorificSuffix = aProp.honorificSuffix;
     this.nickname =        aProp.nickname;
     this.email =           aProp.email;
@@ -489,86 +298,109 @@ Contact.prototype = {
     this.note =            aProp.note;
     this.impp =            aProp.impp;
     this.anniversary =     aProp.anniversary;
     this.sex =             aProp.sex;
     this.genderIdentity =  aProp.genderIdentity;
     this.key =             aProp.key;
   },
 
-  get published () {
-    return this._published;
-  },
-
-  set published(aPublished) {
-    this._published = aPublished;
-  },
-
-  get updated () {
-    return this._updated;
-  },
-
-  set updated(aUpdated) {
-    this._updated = aUpdated;
+  setMetadata: function(aId, aPublished, aUpdated) {
+    this.id = aId;
+    if (aPublished) {
+      this.published = aPublished;
+    }
+    if (aUpdated) {
+      this.updated = aUpdated;
+    }
   },
 
-  classID : CONTACT_CID,
-  classInfo : XPCOMUtils.generateCI({classID: CONTACT_CID,
-                                     contractID: CONTACT_CONTRACTID,
-                                     classDescription: "Contact",
-                                     interfaces: [nsIDOMContact, nsIContactProperties],
-                                     flags: nsIClassInfo.DOM_OBJECT}),
+  toJSON: function() {
+    return {
+      id:              this.id,
+      published:       this.published,
+      updated:         this.updated,
 
-  QueryInterface : XPCOMUtils.generateQI([nsIDOMContact, nsIContactProperties])
-}
+      name:            this.name,
+      honorificPrefix: this.honorificPrefix,
+      givenName:       this.givenName,
+      additionalName:  this.additionalName,
+      familyName:      this.familyName,
+      honorificSuffix: this.honorificSuffix,
+      nickname:        this.nickname,
+      category:        this.category,
+      org:             this.org,
+      jobTitle:        this.jobTitle,
+      note:            this.note,
+      sex:             this.sex,
+      genderIdentity:  this.genderIdentity,
+      email:           this.email,
+      photo:           this.photo,
+      adr:             this.adr,
+      url:             this.url,
+      tel:             this.tel,
+      bday:            this.bday,
+      impp:            this.impp,
+      anniversary:     this.anniversary,
+      key:             this.key,
 
-// ContactManager
+      __exposedProps__: {
+        id:              "rw",
+        published:       "rw",
+        updated:         "rw",
+        name:            "rw",
+        honorificPrefix: "rw",
+        givenName:       "rw",
+        additionalName:  "rw",
+        familyName:      "rw",
+        honorificSuffix: "rw",
+        nickname:        "rw",
+        category:        "rw",
+        org:             "rw",
+        jobTitle:        "rw",
+        note:            "rw",
+        sex:             "rw",
+        genderIdentity:  "rw",
+        email:           "rw",
+        photo:           "rw",
+        adr:             "rw",
+        url:             "rw",
+        tel:             "rw",
+        bday:            "rw",
+        impp:            "rw",
+        anniversary:     "rw",
+        key:             "rw",
+      }
+    };
+  },
 
-const CONTACTMANAGER_CONTRACTID = "@mozilla.org/contactManager;1";
-const CONTACTMANAGER_CID        = Components.ID("{8beb3a66-d70a-4111-b216-b8e995ad3aff}");
-const nsIDOMContactManager      = Components.interfaces.nsIDOMContactManager;
+  classID: Components.ID("{72a5ee28-81d8-4af8-90b3-ae935396cc66}"),
+  contractID: "@mozilla.org/contact;1",
+  QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports,
+                                         Ci.nsIDOMGlobalPropertyInitializer]),
+};
 
-function ContactManager()
-{
-  if (DEBUG) debug("Constructor");
-}
+function ContactManager() { }
 
 ContactManager.prototype = {
   __proto__: DOMRequestIpcHelper.prototype,
-  _oncontactchange: null,
+  hasListenPermission: false,
   _cachedContacts: [] ,
 
-  set oncontactchange(aCallback) {
-    if (DEBUG) debug("set oncontactchange");
-    let allowCallback = function() {
-      if (!this._oncontactchange) {
-        cpmm.sendAsyncMessage("Contacts:RegisterForMessages");
-      }
-      this._oncontactchange = aCallback;
-    }.bind(this);
-    let cancelCallback = function() {
-      throw Components.results.NS_ERROR_FAILURE;
-    }
-    this.askPermission("listen", null, allowCallback, cancelCallback);
+  set oncontactchange(aHandler) {
+    this.__DOM_IMPL__.setEventHandler("oncontactchange", aHandler);
   },
 
   get oncontactchange() {
-    return this._oncontactchange;
+    return this.__DOM_IMPL__.getEventHandler("oncontactchange");
   },
 
-  _setMetaData: function(aNewContact, aRecord) {
-    aNewContact.id = aRecord.id;
-    aNewContact.published = aRecord.published;
-    aNewContact.updated = aRecord.updated;
-  },
-
-  _convertContact: function CM_convertContact(aContact) {
-    let newContact = new Contact();
-    newContact.init(aContact.properties);
-    this._setMetaData(newContact, aContact);
+  _convertContact: function(aContact) {
+    let newContact = new this._window.mozContact(aContact.properties);
+    newContact.setMetadata(aContact.id, aContact.published, aContact.updated);
     return newContact;
   },
 
   _convertContacts: function(aContacts) {
     let contacts = [];
     for (let i in aContacts) {
       contacts.push(this._convertContact(aContacts[i]));
     }
@@ -668,23 +500,21 @@ ContactManager.prototype = {
           req.allow();
         } else {
           req.cancel();
         }
         break;
       case "Contact:Changed":
         // Fire oncontactchange event
         if (DEBUG) debug("Contacts:ContactChanged: " + msg.contactID + ", " + msg.reason);
-        if (this._oncontactchange) {
-          let event = new this._window.MozContactChangeEvent("contactchanged", {
-            contactID: msg.contactID,
-            reason: msg.reason
-          });
-          this._oncontactchange.handleEvent(event);
-        }
+        let event = new this._window.MozContactChangeEvent("contactchange", {
+          contactID: msg.contactID,
+          reason: msg.reason
+        });
+        this.dispatchEvent(event);
         break;
       case "Contacts:Revision":
         if (DEBUG) debug("new revision: " + msg.revision);
         req = this.getRequest(msg.requestID);
         if (req) {
           Services.DOMRequest.fireSuccess(req.request, msg.revision);
         }
         break;
@@ -696,16 +526,22 @@ ContactManager.prototype = {
         }
         break;
       default:
         if (DEBUG) debug("Wrong message: " + aMessage.name);
     }
     this.removeRequest(msg.requestID);
   },
 
+  dispatchEvent: function(event) {
+    if (this.hasListenPermission) {
+      this.__DOM_IMPL__.dispatchEvent(event);
+    }
+  },
+
   askPermission: function (aAccess, aRequest, aAllowCallback, aCancelCallback) {
     if (DEBUG) debug("askPermission for contacts");
     let access;
     switch(aAccess) {
       case "create":
         access = "create";
         break;
       case "update":
@@ -753,62 +589,84 @@ ContactManager.prototype = {
       origin: principal.origin,
       appID: principal.appId,
       browserFlag: principal.isInBrowserElement,
       windowID: this._window.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils).outerWindowID
     });
   },
 
   save: function save(aContact) {
-    if (DEBUG) debug("save: " + JSON.stringify(aContact) + " :" + aContact.id);
-    let newContact = {};
-    newContact.properties = {
-      name:            [],
-      honorificPrefix: [],
-      givenName:       [],
-      additionalName:  [],
-      familyName:      [],
-      honorificSuffix: [],
-      nickname:        [],
-      email:           [],
-      photo:           [],
-      url:             [],
-      category:        [],
-      adr:             [],
-      tel:             [],
-      org:             [],
-      jobTitle:        [],
-      bday:            null,
-      note:            [],
-      impp:            [],
-      anniversary:     null,
-      sex:             null,
-      genderIdentity:  null,
-      key:             [],
-    };
-    for (let field in newContact.properties) {
-      newContact.properties[field] = aContact[field];
+    // We have to do a deep copy of the contact manually here because
+    // nsFrameMessageManager doesn't know how to create a structured clone of a
+    // mozContact object.
+    let newContact = {properties: {}};
+
+    for (let field of PROPERTIES) {
+      if (aContact[field]) {
+        newContact.properties[field] = aContact[field];
+      }
+    }
+
+    for (let prop of ADDRESS_PROPERTIES) {
+      if (aContact[prop]) {
+        newContact.properties[prop] = [];
+        for (let i of aContact[prop]) {
+          if (i) {
+            let json = ContactAddressImpl.prototype.toJSON.apply(i, [true]);
+            newContact.properties[prop].push(json);
+          }
+        }
+      }
     }
+
+    for (let prop of FIELD_PROPERTIES) {
+      if (aContact[prop]) {
+        newContact.properties[prop] = [];
+        for (let i of aContact[prop]) {
+          if (i) {
+            let json = ContactFieldImpl.prototype.toJSON.apply(i, [true]);
+            newContact.properties[prop].push(json);
+          }
+        }
+      }
+    }
+
+    for (let prop of TELFIELD_PROPERTIES) {
+      if (aContact[prop]) {
+        newContact.properties[prop] = [];
+        for (let i of aContact[prop]) {
+          if (i) {
+            let json = ContactTelFieldImpl.prototype.toJSON.apply(i, [true]);
+            newContact.properties[prop].push(json);
+          }
+        }
+      }
+    }
+
     let request = this.createRequest();
     let requestID = this.getRequestId({request: request, reason: reason});
 
     let reason;
     if (aContact.id == "undefined") {
       // for example {25c00f01-90e5-c545-b4d4-21E2ddbab9e0} becomes
       // 25c00f0190e5c545b4d421E2ddbab9e0
-      aContact.id = this._getRandomId().replace('-', '', 'g').replace('{', '').replace('}', '');
+      aContact.id = this._getRandomId().replace(/[{}-]/g, "");
       // Cache the contact so that its ID may be updated later if necessary
       this._cachedContacts[requestID] = aContact;
       reason = "create";
     } else {
       reason = "update";
     }
 
-    this._setMetaData(newContact, aContact);
+    newContact.id = aContact.id;
+    newContact.published = aContact.published;
+    newContact.updated = aContact.updated;
+
     if (DEBUG) debug("send: " + JSON.stringify(newContact));
+
     let options = { contact: newContact, reason: reason };
     let allowCallback = function() {
       cpmm.sendAsyncMessage("Contact:Save", {requestID: requestID, options: options});
     }.bind(this)
     this.askPermission(reason, request, allowCallback);
     return request;
   },
 
@@ -874,29 +732,28 @@ ContactManager.prototype = {
     if (!aRecord || !aRecord.id) {
       Services.DOMRequest.fireErrorAsync(request, true);
       return request;
     }
 
     let options = { id: aRecord.id };
     let allowCallback = function() {
       cpmm.sendAsyncMessage("Contact:Remove", {requestID: this.getRequestId({request: request, reason: "remove"}), options: options});
-    }.bind(this)
+    }.bind(this);
     this.askPermission("remove", request, allowCallback);
     return request;
   },
 
   clear: function() {
     if (DEBUG) debug("clear");
-    let request;
-    request = this.createRequest();
+    let request = this.createRequest();
     let options = {};
     let allowCallback = function() {
       cpmm.sendAsyncMessage("Contacts:Clear", {requestID: this.getRequestId({request: request, reason: "remove"}), options: options});
-    }.bind(this)
+    }.bind(this);
     this.askPermission("remove", request, allowCallback);
     return request;
   },
 
   getRevision: function() {
     let request = this.createRequest();
 
     let allowCallback = function() {
@@ -926,40 +783,37 @@ ContactManager.prototype = {
       Services.DOMRequest.fireError(request);
     };
 
     this.askPermission("count", request, allowCallback, cancelCallback);
     return request;
   },
 
   init: function(aWindow) {
+    // DOMRequestIpcHelper.initHelper sets this._window
     this.initDOMRequestHelper(aWindow, ["Contacts:Find:Return:OK", "Contacts:Find:Return:KO",
                               "Contacts:Clear:Return:OK", "Contacts:Clear:Return:KO",
                               "Contact:Save:Return:OK", "Contact:Save:Return:KO",
                               "Contact:Remove:Return:OK", "Contact:Remove:Return:KO",
                               "Contact:Changed",
                               "PermissionPromptHelper:AskPermission:OK",
                               "Contacts:GetAll:Next", "Contacts:GetAll:Return:KO",
                               "Contacts:Count",
                               "Contacts:Revision", "Contacts:GetRevision:Return:KO",]);
-  },
+
 
-  // Called from DOMRequestIpcHelper
-  uninit: function uninit() {
-    if (DEBUG) debug("uninit call");
-    if (this._oncontactchange)
-      this._oncontactchange = null;
+    let allowCallback = function() {
+      cpmm.sendAsyncMessage("Contacts:RegisterForMessages");
+      this.hasListenPermission = true;
+    }.bind(this);
+
+    this.askPermission("listen", null, allowCallback);
   },
 
-  classID : CONTACTMANAGER_CID,
-  QueryInterface : XPCOMUtils.generateQI([nsIDOMContactManager,
-                                          Ci.nsIDOMGlobalPropertyInitializer,
-                                          Ci.nsISupportsWeakReference]),
+  classID: Components.ID("{8beb3a66-d70a-4111-b216-b8e995ad3aff}"),
+  contractID: "@mozilla.org/contactManager;1",
+  QueryInterface: XPCOMUtils.generateQI([Ci.nsISupportsWeakReference,
+                                         Ci.nsIDOMGlobalPropertyInitializer]),
+};
 
-  classInfo : XPCOMUtils.generateCI({classID: CONTACTMANAGER_CID,
-                                     contractID: CONTACTMANAGER_CONTRACTID,
-                                     classDescription: "ContactManager",
-                                     interfaces: [nsIDOMContactManager],
-                                     flags: nsIClassInfo.DOM_OBJECT})
-}
-
-this.NSGetFactory = XPCOMUtils.generateNSGetFactory(
-                       [Contact, ContactManager, ContactProperties, ContactAddress, ContactField, ContactTelField, ContactFindSortOptions, ContactFindOptions])
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory([
+  Contact, ContactManager, ContactFieldImpl, ContactAddressImpl, ContactTelFieldImpl
+]);
--- a/dom/contacts/ContactManager.manifest
+++ b/dom/contacts/ContactManager.manifest
@@ -1,25 +1,14 @@
-component {35ad8a4e-9486-44b6-883d-550f14635e49} ContactManager.js
-contract @mozilla.org/contactProperties;1 {35ad8a4e-9486-44b6-883d-550f14635e49}
-
 component {9cbfa81c-bcab-4ca9-b0d2-f4318f295e33} ContactManager.js
 contract @mozilla.org/contactAddress;1 {9cbfa81c-bcab-4ca9-b0d2-f4318f295e33}
 
 component {ad19a543-69e4-44f0-adfa-37c011556bc1} ContactManager.js
 contract @mozilla.org/contactField;1 {ad19a543-69e4-44f0-adfa-37c011556bc1}
 
 component {4d42c5a9-ea5d-4102-80c3-40cc986367ca} ContactManager.js
 contract @mozilla.org/contactTelField;1 {4d42c5a9-ea5d-4102-80c3-40cc986367ca}
 
-component {0a5b1fab-70da-46dd-b902-619904d920c2} ContactManager.js
-contract @mozilla.org/contactFindSortOptions;1 {0a5b1fab-70da-46dd-b902-619904d920c2}
-
-component {28ce07d0-45d9-4b7a-8843-521df4edd8bc} ContactManager.js
-contract @mozilla.org/contactFindOptions;1 {28ce07d0-45d9-4b7a-8843-521df4edd8bc}
-
 component {72a5ee28-81d8-4af8-90b3-ae935396cc66} ContactManager.js
 contract @mozilla.org/contact;1 {72a5ee28-81d8-4af8-90b3-ae935396cc66}
-category JavaScript-global-constructor mozContact @mozilla.org/contact;1
 
 component {8beb3a66-d70a-4111-b216-b8e995ad3aff} ContactManager.js
 contract @mozilla.org/contactManager;1 {8beb3a66-d70a-4111-b216-b8e995ad3aff}
-category JavaScript-navigator-property mozContacts @mozilla.org/contactManager;1
--- a/dom/contacts/fallback/ContactDB.jsm
+++ b/dom/contacts/fallback/ContactDB.jsm
@@ -15,34 +15,38 @@ const Cu = Components.utils;
 const Cc = Components.classes;
 const Ci = Components.interfaces;
 
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/IndexedDBHelper.jsm");
 Cu.import("resource://gre/modules/PhoneNumberUtils.jsm");
 
 const DB_NAME = "contacts";
-const DB_VERSION = 14;
+const DB_VERSION = 16;
 const STORE_NAME = "contacts";
 const SAVED_GETALL_STORE_NAME = "getallcache";
 const CHUNK_SIZE = 20;
 const REVISION_STORE = "revision";
 const REVISION_KEY = "revision";
 
-function exportContact(aRecord) {
-  let contact = {};
-  contact.properties = aRecord.properties;
+function optionalDate(aValue) {
+  if (aValue) {
+    if (!(aValue instanceof Date)) {
+      return new Date(aValue);
+    }
+    return aValue;
+  }
+  return undefined;
+}
 
-  for (let field in aRecord.properties)
-    contact.properties[field] = aRecord.properties[field];
-
-  contact.updated = aRecord.updated;
-  contact.published = aRecord.published;
-  contact.id = aRecord.id;
-  return contact;
+function exportContact(aRecord) {
+  if (aRecord) {
+    delete aRecord.search;
+  }
+  return aRecord;
 }
 
 function ContactDispatcher(aContacts, aFullContacts, aCallback, aNewTxn, aClearDispatcher, aFailureCb) {
   let nextIndex = 0;
 
   let sendChunk;
   let count = 0;
   if (aFullContacts) {
@@ -145,19 +149,17 @@ ContactDB.prototype = {
       }
 
       let idService = Cc["@mozilla.org/uuid-generator;1"].getService(Ci.nsIUUIDGenerator);
       objectStore = aTransaction.objectStore(STORE_NAME);
 
       for (let i = 0; i < contacts.length; i++) {
         let contact = {};
         contact.properties = contacts[i];
-        contact.id = idService.generateUUID().toString().replace('-', '', 'g')
-                                                        .replace('{', '')
-                                                        .replace('}', '');
+        contact.id = idService.generateUUID().toString().replace(/[{}-]/g, "");
         contact = this.makeImport(contact);
         this.updateRecordMetadata(contact);
         if (DEBUG) debug("import: " + JSON.stringify(contact));
         objectStore.put(contact);
       }
     }.bind(this);
 
     function createFinalSchema() {
@@ -539,21 +541,90 @@ ContactDB.prototype = {
 
           let cursor = event.target.result;
           if (cursor) {
             let modified = removeEmptyStrings(cursor.value.search.parsedTel);
             let modified2 = removeEmptyStrings(cursor.value.search.tel);
             if (modified || modified2) {
               cursor.update(cursor.value);
             }
+            cursor.continue();
           } else {
             next();
           }
         };
       },
+      function upgrade14to15() {
+        if (DEBUG) debug("Fix array properties saved as scalars");
+        if (!objectStore) {
+         objectStore = aTransaction.objectStore(STORE_NAME);
+        }
+        const ARRAY_PROPERTIES = ["photo", "adr", "email", "url", "impp", "tel",
+                                 "name", "honorificPrefix", "givenName",
+                                 "additionalName", "familyName", "honorificSuffix",
+                                 "nickname", "category", "org", "jobTitle",
+                                 "note", "key"];
+        const PROPERTIES_WITH_TYPE = ["adr", "email", "url", "impp", "tel"];
+        objectStore.openCursor().onsuccess = function(event) {
+          let cursor = event.target.result;
+          let changed = false;
+          if (cursor) {
+            let props = cursor.value.properties;
+            for (let prop of ARRAY_PROPERTIES) {
+              if (props[prop]) {
+                if (!Array.isArray(props[prop])) {
+                  cursor.value.properties[prop] = [props[prop]];
+                  changed = true;
+                }
+                if (PROPERTIES_WITH_TYPE.indexOf(prop) !== -1) {
+                  let subprop = cursor.value.properties[prop];
+                  for (let i = 0; i < subprop.length; ++i) {
+                    if (!Array.isArray(subprop[i].type)) {
+                      cursor.value.properties[prop][i].type = [subprop[i].type];
+                      changed = true;
+                    }
+                  }
+                }
+              }
+            }
+            if (changed) {
+              cursor.update(cursor.value);
+            }
+            cursor.continue();
+          } else {
+           next();
+          }
+        };
+      },
+      function upgrade15to16() {
+        if (DEBUG) debug("Fix Date properties");
+        if (!objectStore) {
+         objectStore = aTransaction.objectStore(STORE_NAME);
+        }
+        const DATE_PROPERTIES = ["bday", "anniversary"];
+        objectStore.openCursor().onsuccess = function(event) {
+          let cursor = event.target.result;
+          let changed = false;
+          if (cursor) {
+            let props = cursor.value.properties;
+            for (let prop of DATE_PROPERTIES) {
+              if (props[prop] && !(props[prop] instanceof Date)) {
+                cursor.value.properties[prop] = new Date(props[prop]);
+                changed = true;
+              }
+            }
+            if (changed) {
+              cursor.update(cursor.value);
+            }
+            cursor.continue();
+          } else {
+           next();
+          }
+        };
+      },
     ];
 
     let index = aOldVersion;
     let outer = this;
     function next() {
       if (index == aNewVersion) {
         outer.incrementRevision(aTransaction);
         return;
@@ -571,41 +642,17 @@ ContactDB.prototype = {
     if (aNewVersion > steps.length) {
       dump("Contacts DB upgrade error!");
       aTransaction.abort();
     }
     next();
   },
 
   makeImport: function makeImport(aContact) {
-    let contact = {};
-    contact.properties = {
-      name:            [],
-      honorificPrefix: [],
-      givenName:       [],
-      additionalName:  [],
-      familyName:      [],
-      honorificSuffix: [],
-      nickname:        [],
-      email:           [],
-      photo:           [],
-      url:             [],
-      category:        [],
-      adr:             [],
-      tel:             [],
-      org:             [],
-      jobTitle:        [],
-      bday:            null,
-      note:            [],
-      impp:            [],
-      anniversary:     null,
-      sex:             null,
-      genderIdentity:  null,
-      key:             [],
-    };
+    let contact = {properties: {}};
 
     contact.search = {
       givenName:       [],
       familyName:      [],
       email:           [],
       category:        [],
       tel:             [],
       exactTel:        [],
@@ -682,17 +729,16 @@ ContactDB.prototype = {
               if (typeof val == "string") {
                 contact.search[field].push(val.toLowerCase());
               }
             }
           }
         }
       }
     }
-    if (DEBUG) debug("contact:" + JSON.stringify(contact));
 
     contact.updated = aContact.updated;
     contact.published = aContact.published;
     contact.id = aContact.id;
 
     return contact;
   },
 
--- a/dom/contacts/moz.build
+++ b/dom/contacts/moz.build
@@ -1,16 +1,16 @@
 # -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
 # vim: set filetype=python:
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
-# Android only supports the Contacts API on the Nightly channel.
-if CONFIG['MOZ_WIDGET_TOOLKIT'] != 'android' or CONFIG['NIGHTLY_BUILD']:
+# Disable the tests on Android for now (bug 927869)
+if CONFIG['MOZ_WIDGET_TOOLKIT'] != 'android':
     TEST_DIRS += ['tests']
 
 EXTRA_COMPONENTS += [
     'ContactManager.js',
     'ContactManager.manifest',
 ]
 
 EXTRA_JS_MODULES += [
--- a/dom/contacts/tests/test_contacts_basics.html
+++ b/dom/contacts/tests/test_contacts_basics.html
@@ -56,87 +56,88 @@ var c3 = {
 
 var c4 = {
   name: ["c c", "a a", "c c"],
   familyName: ["c","a","c"],
   givenName: ["c","a","c"],
 };
 
 var c5 = {
-  nickname: "empty"
+  familyName: [],
+  givenName: [],
 };
 
 var c6 = {
-  name: "e",
+  name: ["e"],
   familyName: ["e","e","e"],
   givenName: ["e","e","e"],
 };
 
 var c7 = {
-  name: "e",
+  name: ["e"],
   familyName: ["e","e","e"],
   givenName: ["e","e","e"],
 };
 
 var c8 = {
-  name: "e",
+  name: ["e"],
   familyName: ["e","e","e"],
   givenName: ["e","e","e"],
 };
 
 var adr1 = {
-  type: "work",
+  type: ["work"],
   streetAddress: "street 1",
   locality: "locality 1",
   region: "region 1",
   postalCode: "postal code 1",
   countryName: "country 1"
 };
 
 var adr2 = {
-  type: "home, fax",
+  type: ["home, fax"],
   streetAddress: "street2",
   locality: "locality2",
   region: "region2",
   postalCode: "postal code2",
   countryName: "country2"
 };
 
 var properties1 = {
   name: ["Test1 TestFamilyName", "Test2 Wagner"],
   familyName: ["TestFamilyName","Wagner"],
   givenName: ["Test1","Test2"],
-  nickname: "nicktest",
+  nickname: ["nicktest"],
   tel: [{type: ["work"], value: "123456", carrier: "testCarrier"} , {type: ["home", "fax"], value: "+55 (31) 9876-3456"}, {type: ["home"], value: "+49 451 491934"}],
-  adr: adr1,
+  adr: [adr1],
   email: [{type: ["work"], value: "x@y.com"}],
 };
 
 var properties2 = {
   name: ["dummyHonorificPrefix dummyGivenName dummyFamilyName dummyHonorificSuffix", "dummyHonorificPrefix2"],
-  familyName: "dummyFamilyName",
-  givenName: "dummyGivenName",
+  familyName: ["dummyFamilyName"],
+  givenName: ["dummyGivenName"],
   honorificPrefix: ["dummyHonorificPrefix","dummyHonorificPrefix2"],
-  honorificSuffix: "dummyHonorificSuffix",
-  additionalName: "dummyadditionalName",
-  nickname: "dummyNickname",
+  honorificSuffix: ["dummyHonorificSuffix"],
+  additionalName: ["dummyadditionalName"],
+  nickname: ["dummyNickname"],
   tel: [{type: ["test"], value: "7932012345", carrier: "myCarrier", pref: 1},{type: ["home", "custom"], value: "7932012346", pref: 0}],
   email: [{type: ["test"], value: "a@b.c"}, {value: "b@c.d", pref: 1}],
   adr: [adr1, adr2],
   impp: [{type: ["aim"], value:"im1", pref: 1}, {value: "im2"}],
   org: ["org1", "org2"],
   jobTitle: ["boss", "superboss"],
-  note: "test note",
+  note: ["test note"],
   category: ["cat1", "cat2"],
   url: [{type: ["work", "work2"], value: "www.1.com", pref: 1}, {value:"www2.com"}],
   bday: new Date("1980, 12, 01"),
   anniversary: new Date("2000, 12, 01"),
   sex: "male",
   genderIdentity: "test",
-  key: "ERPJ394GJJWEVJ0349GJ09W3H4FG0WFW80VHW3408GH30WGH348G3H"
+  key: ["ERPJ394GJJWEVJ0349GJ09W3H4FG0WFW80VHW3408GH30WGH348G3H"]
 };
 
 var sample_id1;
 var sample_id2;
 
 var createResult1;
 var createResult2;
 
@@ -161,104 +162,131 @@ function onFailure() {
   next();
 }
 
 function checkStr(str1, str2, msg) {
   if (str1 ^ str2) {
     ok(false, "Expected both strings to be either present or absent");
     return;
   }
+  if (!str1 || str1 == "null") {
+    str1 = null;
+  }
+  if (!str2 || str2 == "null") {
+    str2 = null;
+  }
   is(str1, str2, msg);
 }
 
 function checkStrArray(str1, str2, msg) {
-  // comparing /[null(,null)+]/ and undefined should pass
-  function nonNull(e) {
-    return e != null;
+  function normalize_falsy(v) {
+    if (!v || v == "null" || v == "undefined") {
+      return "";
+    }
+    return v;
+  }
+  function optArray(val) {
+    return Array.isArray(val) ? val : [val];
   }
-  if ((Array.isArray(str1) && str1.filter(nonNull).length == 0 && str2 == undefined)
-     ||(Array.isArray(str2) && str2.filter(nonNull).length == 0 && str1 == undefined)) {
-    ok(true, msg);
-  } else if (str1) {
-    is(JSON.stringify(typeof str1 == "string" ? [str1] : str1), JSON.stringify(typeof str2 == "string" ? [str2] : str2), msg);
+  str1 = optArray(str1).map(normalize_falsy).filter(v => v != "");
+  str2 = optArray(str2).map(normalize_falsy).filter(v => v != "");
+  ise(JSON.stringify(str1), JSON.stringify(str2), msg);
+}
+
+function checkPref(pref1, pref2) {
+  // If on Android treat one preference as 0 and the other as undefined as matching
+  if (isAndroid) {
+    if ((!pref1 && pref2 == undefined) || (pref1 == undefined && !pref2)) {
+      pref1 = false;
+      pref2 = false;
+    }
   }
+  ise(!!pref1, !!pref2, "Same pref");
 }
 
 function checkAddress(adr1, adr2) {
   if (adr1 ^ adr2) {
     ok(false, "Expected both adrs to be either present or absent");
     return;
   }
   checkStrArray(adr1.type, adr2.type, "Same type");
-  checkStrArray(adr1.streetAddress, adr2.streetAddress, "Same streetAddress");
-  checkStrArray(adr1.locality, adr2.locality, "Same locality");
-  checkStrArray(adr1.region, adr2.region, "Same region");
-  checkStrArray(adr1.postalCode, adr2.postalCode, "Same postalCode");
-  checkStrArray(adr1.countryName, adr2.countryName, "Same countryName");
+  checkStr(adr1.streetAddress, adr2.streetAddress, "Same streetAddress");
+  checkStr(adr1.locality, adr2.locality, "Same locality");
+  checkStr(adr1.region, adr2.region, "Same region");
+  checkStr(adr1.postalCode, adr2.postalCode, "Same postalCode");
+  checkStr(adr1.countryName, adr2.countryName, "Same countryName");
   checkPref(adr1.pref, adr2.pref);
 }
 
-function checkTel(tel1, tel2) {
-  if (tel1 ^ tel2) {
-    ok(false, "Expected both tels to be either present or absent");
-    return;
-  }
-  checkStrArray(tel1.type, tel2.type, "Same type");
-  checkStrArray(tel1.value, tel2.value, "Same value");
-  checkStrArray(tel1.carrier, tel2.carrier, "Same carrier");
-  checkPref(tel1.pref, tel2.pref);
-}
-
 function checkField(field1, field2) {
   if (field1 ^ field2) {
     ok(false, "Expected both fields to be either present or absent");
     return;
   }
   checkStrArray(field1.type, field2.type, "Same type");
-  checkStrArray(field1.value, field2.value, "Same value");
+  checkStr(field1.value, field2.value, "Same value");
   checkPref(field1.pref, field2.pref);
 }
 
-function checkPref(pref1, pref2) {
-  // If on Android treat one preference as 0 and the other as undefined as matching
-  if (isAndroid) {
-    if ((pref1 == 0 && pref2 == undefined) || (pref1 == undefined && pref2 == 0)) {
-      pref1 = 0;
-      pref2 = 0;
-    }
+function checkTel(tel1, tel2) {
+  if (tel1 ^ tel2) {
+    ok(false, "Expected both tels to be either present or absent");
+    return;
   }
-  is(pref1, pref2, "Same pref");
+  checkField(tel1, tel2);
+  checkStr(tel1.carrier, tel2.carrier, "Same carrier");
 }
 
 function checkCategory(category1, category2) {
   // Android adds contacts to the a default category. This should be removed from the
   // results before comparing them
   if (isAndroid) {
     category1 = removeAndroidDefaultCategory(category1);
     category2 = removeAndroidDefaultCategory(category2);
   }
   checkStrArray(category1, category2, "Same Category")
 }
 
 function removeAndroidDefaultCategory(category) {
-  if (category == undefined) {
-    return;
+  if (!category) {
+    return category;
   }
 
-  for (var i = 0; i < category.length; i++) {
+  var result = [];
+
+  for (var i of category) {
     // Some devices may return the full group name (prefixed with "System Group: ")
-    if (category[i] == "My Contacts" || category[i] == "System Group: My Contacts") {
-      category.splice(i, 1);
+    if (i != "My Contacts" && i != "System Group: My Contacts") {
+      result.push(i);
     }
   }
 
-  return category;
+  return result;
+}
+
+function checkArrayField(array1, array2, func, msg) {
+  if (!!array1 ^ !!array2) {
+    ok(false, "Expected both arrays to be either present or absent");
+    return;
+  }
+  if (!array1 && !array2)  {
+    ok(true, msg);
+    return;
+  }
+  ise(array1.length, array2.length, "Same length");
+  for (var i = 0; i < array1.length; ++i) {
+    func(array1[i], array2[i], msg);
+  }
 }
 
 function checkContacts(contact1, contact2) {
+  if (!!contact1 ^ !!contact2) {
+    ok(false, "Expected both contacts to be either present or absent");
+    return;
+  }
   checkStrArray(contact1.name, contact2.name, "Same name");
   checkStrArray(contact1.honorificPrefix, contact2.honorificPrefix, "Same honorificPrefix");
   checkStrArray(contact1.givenName, contact2.givenName, "Same givenName");
   checkStrArray(contact1.additionalName, contact2.additionalName, "Same additionalName");
   checkStrArray(contact1.familyName, contact2.familyName, "Same familyName");
   checkStrArray(contact1.honorificSuffix, contact2.honorificSuffix, "Same honorificSuffix");
   checkStrArray(contact1.nickname, contact2.nickname, "Same nickname");
   checkCategory(contact1.category, contact2.category);
@@ -266,38 +294,32 @@ function checkContacts(contact1, contact
   checkStrArray(contact1.jobTitle, contact2.jobTitle, "Same jobTitle");
   is(contact1.bday ? contact1.bday.valueOf() : null, contact2.bday ? contact2.bday.valueOf() : null, "Same birthday");
   checkStrArray(contact1.note, contact2.note, "Same note");
   is(contact1.anniversary ? contact1.anniversary.valueOf() : null , contact2.anniversary ? contact2.anniversary.valueOf() : null, "Same anniversary");
   checkStr(contact1.sex, contact2.sex, "Same sex");
   checkStr(contact1.genderIdentity, contact2.genderIdentity, "Same genderIdentity");
   checkStrArray(contact1.key, contact2.key, "Same key");
 
-  for (var i in contact1.email) {
-    checkField(contact1.email[i], contact2.email[i]);
-  }
-  for (var i in contact1.adr) {
-    checkAddress(contact1.adr[i], contact2.adr[i]);
-  }
-  for (var i in contact1.tel) {
-    checkTel(contact1.tel[i], contact2.tel[i]);
-  }
-  for (var i in contact1.url) {
-    checkField(contact1.url[i], contact2.url[i]);
-  }
-  for (var i in contact1.impp) {
-    checkField(contact1.impp[i], contact2.impp[i]);
-  }
+  checkArrayField(contact1.adr, contact2.adr, checkAddress, "Same adr");
+  checkArrayField(contact1.tel, contact2.tel, checkTel, "Same tel");
+  checkArrayField(contact1.email, contact2.email, checkField, "Same email");
+  checkArrayField(contact1.url, contact2.url, checkField, "Same url");
+  checkArrayField(contact1.impp, contact2.impp, checkField, "Same impp");
 }
 
 var req;
 var index = 0;
 
 var initialRev;
 
+var defaultOptions = {
+  sortBy: "givenName",
+};
+
 function checkRevision(revision, msg, then) {
   var revReq = mozContacts.getRevision();
   revReq.onsuccess = function(e) {
     is(e.target.result, initialRev+revision, msg);
     then();
   };
   // The revision function isn't supported on Android so treat on failure as success
   if (isAndroid) {
@@ -314,18 +336,16 @@ function checkCount(count, msg, then) {
   request.onsuccess = function(e) {
     is(e.target.result, count, msg);
     then();
   };
   request.onerror = onFailure;
 }
 
 var mozContacts = window.navigator.mozContacts;
-ok(mozContacts, "mozContacts exists");
-ok("mozContact" in window, "mozContact exists");
 var steps = [
   function() {
     req = mozContacts.getRevision();
     req.onsuccess = function(e) {
       initialRev = e.target.result;
       next();
     };
 
@@ -349,65 +369,63 @@ var steps = [
           checkRevision(1, "Revision was incremented on clear", next);
         });
       };
       req.onerror = onFailure;
     });
   },
   function () {
     ok(true, "Retrieving all contacts");
-    req = mozContacts.find({});
+    req = mozContacts.find(defaultOptions);
     req.onsuccess = function () {
       is(req.result.length, 0, "Empty database.");
       checkRevision(1, "Revision was not incremented on find", next);
     };
     req.onerror = onFailure;
   },
   function () {
     ok(true, "Adding empty contact");
-    createResult1 = new mozContact();
-    createResult1.init({});
+    createResult1 = new mozContact({});
     req = navigator.mozContacts.save(createResult1);
     req.onsuccess = function () {
       ok(createResult1.id, "The contact now has an ID.");
       sample_id1 = createResult1.id;
       checkCount(1, "1 contact after adding empty contact", function() {
         checkRevision(2, "Revision was incremented on save", next);
       });
     };
     req.onerror = onFailure;
   },
   function () {
     ok(true, "Retrieving all contacts");
-    req = mozContacts.find({});
+    req = mozContacts.find(defaultOptions);
     req.onsuccess = function () {
       is(req.result.length, 1, "One contact.");
       findResult1 = req.result[0];
       next();
     };
     req.onerror = onFailure;
   },
   function () {
     ok(true, "Deleting empty contact");
     req = navigator.mozContacts.remove(findResult1);
     req.onsuccess = function () {
-      var req2 = mozContacts.find({});
+      var req2 = mozContacts.find(defaultOptions);
       req2.onsuccess = function () {
         is(req2.result.length, 0, "Empty Database.");
         clearTemps();
         checkRevision(3, "Revision was incremented on remove", next);
       }
       req2.onerror = onFailure;
     }
     req.onerror = onFailure;
   },
   function () {
     ok(true, "Adding a new contact1");
-    createResult1 = new mozContact();
-    createResult1.init(properties1);
+    createResult1 = new mozContact(properties1);
 
     mozContacts.oncontactchange = function(event) {
       is(event.contactID, createResult1.id, "Same contactID");
       is(event.reason, "create", "Same reason");
       next();
     }
 
     req = navigator.mozContacts.save(createResult1);
@@ -482,18 +500,17 @@ var steps = [
     req.onerror = onFailure;
   },
   function () {
     ok(true, "Adding a new contact");
     mozContacts.oncontactchange = function(event) {
        is(event.contactID, createResult2.id, "Same contactID");
        is(event.reason, "create", "Same reason");
      }
-    createResult2 = new mozContact();
-    createResult2.init({name: "newName"});
+    createResult2 = new mozContact({name: ["newName"]});
     req = navigator.mozContacts.save(createResult2);
     req.onsuccess = function () {
       ok(createResult2.id, "The contact now has an ID.");
       next();
     };
     req.onerror = onFailure;
   },
   function () {
@@ -568,18 +585,17 @@ var steps = [
     req.onsuccess = function () {
       ok(true, "Deleted the database");
       next();
     };
     req.onerror = onFailure;
   },
   function () {
     ok(true, "Adding a new contact with properties1");
-    createResult1 = new mozContact();
-    createResult1.init(properties1);
+    createResult1 = new mozContact(properties1);
     mozContacts.oncontactchange = null;
     req = navigator.mozContacts.save(createResult1);
     req.onsuccess = function () {
       ok(createResult1.id, "The contact now has an ID.");
       sample_id1 = createResult1.id;
       checkContacts(createResult1, properties1);
       next();
     };
@@ -783,37 +799,39 @@ var steps = [
       ok(findResult1.id == sample_id1, "Same ID");
       checkContacts(createResult1, properties1);
       next();
     };
     req.onerror = onFailure;
   },
   function () {
     ok(true, "Retrieving all contacts");
-    req = mozContacts.find({});
+    req = mozContacts.find(defaultOptions);
     req.onsuccess = function() {
       is(req.result.length, 1, "Found exactly 1 contact.");
       findResult1 = req.result[0];
       ok(findResult1.id == sample_id1, "Same ID");
       checkContacts(createResult1, findResult1);
-      ok(findResult1.updated, "Has updated field");
-      ok(findResult1.published, "Has published field");
+      if (!isAndroid) {
+        ok(findResult1.updated, "Has updated field");
+        ok(findResult1.published, "Has published field");
+      }
       next();
     }
     req.onerror = onFailure;
   },
   function () {
     ok(true, "Modifying contact1");
     if (!findResult1) {
       SpecialPowers.executeSoon(next);
     } else {
       findResult1.impp = properties1.impp = [{value:"phil impp"}];
       req = navigator.mozContacts.save(findResult1);
       req.onsuccess = function () {
-        var req2 = mozContacts.find({});
+        var req2 = mozContacts.find(defaultOptions);
         req2.onsuccess = function() {
           is(req2.result.length, 1, "Found exactly 1 contact.");
           findResult2 = req2.result[0];
           ok(findResult2.id == sample_id1, "Same ID");
           checkContacts(findResult2, properties1);
           is(findResult2.impp.length, 1, "Found exactly 1 IMS info.");
           next();
         };
@@ -867,17 +885,17 @@ var steps = [
   function () {
     ok(true, "Modifying contact2");
     if (!findResult1) {
       SpecialPowers.executeSoon(next);
     } else {
       findResult1.impp = properties1.impp = [{value: "phil impp"}];
       req = mozContacts.save(findResult1);
       req.onsuccess = function () {
-        var req2 = mozContacts.find({});
+        var req2 = mozContacts.find(defaultOptions);
         req2.onsuccess = function () {
           is(req2.result.length, 1, "Found exactly 1 contact.");
           findResult1 = req2.result[0];
           ok(findResult1.id == sample_id1, "Same ID");
           checkContacts(findResult1, properties1);
           is(findResult1.impp.length, 1, "Found exactly 1 IMS info.");
           next();
         }
@@ -932,19 +950,18 @@ var steps = [
     req.onerror = onFailure;
   },
   function () {
     ok(true, "Modifying contact3");
     if (!findResult1) {
       SpecialPowers.executeSoon(next);
     } else {
       findResult1.email = [{value: properties1.nickname}];
-      findResult1.nickname = "TEST";
-      var newContact = new mozContact();
-      newContact.init(findResult1);
+      findResult1.nickname = ["TEST"];
+      var newContact = new mozContact(findResult1);
       req = mozContacts.save(newContact);
       req.onsuccess = function () {
         var options = {filterBy: ["email", "givenName"],
                        filterOp: "startsWith",
                        filterValue: properties1.givenName[0]};
         // One contact has it in nickname and the other in email
         var req2 = mozContacts.find(options);
         req2.onsuccess = function () {
@@ -956,17 +973,17 @@ var steps = [
       };
       req.onerror = onFailure;
     }
   },
   function () {
     ok(true, "Deleting contact" + findResult1);
     req = mozContacts.remove(findResult1);
     req.onsuccess = function () {
-      var req2 = mozContacts.find({});
+      var req2 = mozContacts.find(defaultOptions);
       req2.onsuccess = function () {
         is(req2.result.length, 1, "One contact left.");
         findResult1 = req2.result[0];
         next();
       }
       req2.onerror = onFailure;
     }
     req.onerror = onFailure;
@@ -977,44 +994,42 @@ var steps = [
     req.onsuccess =  function () {
       clearTemps();
       next();
     };
     req.onerror = onFailure;
   },
   function () {
     ok(true, "Adding a new contact");
-    createResult1 = new mozContact();
-    createResult1.init(properties1);
+    createResult1 = new mozContact(properties1);
     req = mozContacts.save(createResult1)
     req.onsuccess = function () {
       ok(createResult1.id, "The contact now has an ID.");
       sample_id1 = createResult1.id;
       next();
     }
     req.onerror = onFailure;
   },
   function () {
     ok(true, "Adding a new contact2");
-    createResult2 = new mozContact();
-    createResult2.init(properties2);
+    createResult2 = new mozContact(properties2);
     req = mozContacts.save(createResult2);
     req.onsuccess = function () {
       ok(createResult2.id, "The contact now has an ID.");
       sample_id2 = createResult2.id;
       next();
     };
     req.onerror = onFailure;
   },
   function () {
     ok(true, "Retrieving all contacts");
-    req = mozContacts.find({sortBy: 'FamilyName',})
+    req = mozContacts.find({sortBy: "familyName"});
     req.onsuccess = function () {
       is(req.result.length, 2, "Found exactly 2 contact.");
-      checkContacts(properties2, req.result[1]);
+      checkContacts(req.result[1], properties1);
       next();
     }
     req.onerror = onFailure;
   },
   function () {
     console.log("Searching contacts by query1");
     var options = {filterBy: ["givenName", "email"],
                    filterOp: "startsWith",
@@ -1081,37 +1096,35 @@ var steps = [
       clearTemps();
       next();
     }
     req.onerror = onFailure;
   },
   function () {
     ok(true, "Adding 20 contacts");
     for (var i=0; i<19; i++) {
-      createResult1 = new mozContact();
-      createResult1.init(properties1);
+      createResult1 = new mozContact(properties1);
       req = mozContacts.save(createResult1);
       req.onsuccess = function () {
         ok(createResult1.id, "The contact now has an ID.");
       };
       req.onerror = onFailure;
     };
-    createResult1 = new mozContact();
-    createResult1.init(properties1);
+    createResult1 = new mozContact(properties1);
     req = mozContacts.save(createResult1);
     req.onsuccess = function () {
       ok(createResult1.id, "The contact now has an ID.");
       checkStrArray(createResult1.name, properties1.name, "Same Name");
       checkCount(20, "20 contacts in DB", next);
     };
     req.onerror = onFailure;
   },
   function () {
     ok(true, "Retrieving all contacts");
-    req = mozContacts.find({});
+    req = mozContacts.find(defaultOptions);
     req.onsuccess = function () {
       is(req.result.length, 20, "20 Entries.");
       next();
     }
     req.onerror = onFailure;
   },
   function () {
     ok(true, "Retrieving all contacts with limit 10");
@@ -1147,82 +1160,83 @@ var steps = [
       next();
     }
     req.onerror = onFailure;
   },
   function () {
     ok(true, "Retrieving all contacts3");
     var options = {filterBy: ["givenName", "tel", "email"],
                    filterOp: "startsWith",
-                   filterValue: properties1.givenName[0].substring(0, 4),
-                   filterLimit: 15 };
+                   filterValue: properties1.givenName[0].substring(0, 4)};
     req = mozContacts.find(options);
     req.onsuccess = function () {
-      is(req.result.length, 15, "15 Entries.");
+      is(req.result.length, 20, "20 Entries.");
       checkContacts(createResult1, req.result[10]);
       next();
     }
     req.onerror = onFailure;
   },
   function () {
     ok(true, "Deleting database");
     req = mozContacts.clear();
     req.onsuccess = function () {
       clearTemps();
       next();
     }
     req.onerror = onFailure;
   },
   function () {
     ok(true, "Testing clone contact");
-    createResult1 = new mozContact();
-    createResult1.init(properties1);
+    createResult1 = new mozContact(properties1);
     req = mozContacts.save(createResult1);
     req.onsuccess = function () {
       ok(createResult1.id, "The contact now has an ID.");
       checkStrArray(createResult1.name, properties1.name, "Same Name");
       next();
     }
     req.onerror = onFailure;
   },
   function() {
     ok(true, "Testing clone contact2");
     var cloned = new mozContact(createResult1);
     ok(cloned.id != createResult1.id, "Cloned contact has new ID");
-    cloned.email = {value: "new email!"};
-    cloned.givenName = "Tom";
+    cloned.email = [{value: "new email!"}];
+    cloned.givenName = ["Tom"];
     req = mozContacts.save(cloned);
     req.onsuccess = function () {
       ok(cloned.id, "The contact now has an ID.");
-      ok(cloned.email[0].value == "new email!", "Same Email");
-      ok(createResult1.email != cloned.email, "Clone has different email");
-      ok(cloned.givenName == "Tom", "New Name");
+      is(cloned.email[0].value, "new email!", "Same Email");
+      isnot(createResult1.email[0].value, cloned.email[0].value, "Clone has different email");
+      is(cloned.givenName, "Tom", "New Name");
       next();
     }
     req.onerror = onFailure;
   },
   function () {
     ok(true, "Retrieving all contacts");
     var options = {filterBy: ["givenName"],
                    filterOp: "startsWith",
                    filterValue: properties2.givenName[0].substring(0, 4)};
-    req = mozContacts.find({});
+    req = mozContacts.find(defaultOptions);
     req.onsuccess = function () {
       is(req.result.length, 2, "2 Entries.");
       next();
     }
     req.onerror = onFailure;
   },
   function () {
     ok(true, "Search with redundant fields should only return 1 contact");
-    createResult1 = new mozContact();
-    createResult1.init({name: "XXX", givenName: "XXX", email: [{value: "XXX"}], tel: {value: "XXX"}});
+    createResult1 = new mozContact({name: ["XXX"],
+                                    givenName: ["XXX"],
+                                    email: [{value: "XXX"}],
+                                    tel: [{value: "XXX"}]
+                                   });
     req = mozContacts.save(createResult1);
     req.onsuccess = function() {
-      var options = {filterBy: [],
+      var options = {filterBy: ["givenName", "familyName"],
                      filterOp: "equals",
                      filterValue: "XXX"};
       var req2 = mozContacts.find(options);
       req2.onsuccess = function() {
         is(req2.result.length, 1, "1 Entry");
         next();
       }
       req2.onerror = onFailure;
@@ -1235,54 +1249,50 @@ var steps = [
     req.onsuccess = function () {
       ok(true, "Deleted the database");
       next();
     }
     req.onerror = onFailure;
   },
   function () {
     ok(true, "Test sorting");
-    createResult1 = new mozContact();
-    createResult1.init(c3);
+    createResult1 = new mozContact(c3);
     req = navigator.mozContacts.save(createResult1);
     req.onsuccess = function () {
       ok(createResult1.id, "The contact now has an ID.");
       checkContacts(c3, createResult1);
       next();
     };
     req.onerror = onFailure;
   },
   function () {
     ok(true, "Test sorting");
-    createResult1 = new mozContact();
-    createResult1.init(c2);
+    createResult1 = new mozContact(c2);
     req = navigator.mozContacts.save(createResult1);
     req.onsuccess = function () {
       ok(createResult1.id, "The contact now has an ID.");
       checkContacts(c2, createResult1);
       next();
     };
     req.onerror = onFailure;
   },
   function () {
     ok(true, "Test sorting");
-    createResult1 = new mozContact();
-    createResult1.init(c4);
+    createResult1 = new mozContact(c4);
     req = navigator.mozContacts.save(createResult1);
     req.onsuccess = function () {
       ok(createResult1.id, "The contact now has an ID.");
       checkContacts(c4, createResult1);
       next();
     };
     req.onerror = onFailure;
   },
   function () {
     ok(true, "Test sorting");
-    createResult1 = new mozContact();
-    createResult1.init(c1);
+    createResult1 = new mozContact(c1);
     req = navigator.mozContacts.save(createResult1);
     req.onsuccess = function () {
       ok(createResult1.id, "The contact now has an ID.");
       checkContacts(c1, createResult1);
       next();
     };
     req.onerror = onFailure;
   },
@@ -1313,18 +1323,17 @@ var steps = [
       checkContacts(req.result[2], c2);
       checkContacts(req.result[3], c1);
       next();
     };
     req.onerror = onFailure;
   },
   function () {
     ok(true, "Test sorting");
-    createResult1 = new mozContact();
-    createResult1.init(c5);
+    createResult1 = new mozContact(c5);
     req = navigator.mozContacts.save(createResult1);
     req.onsuccess = function () {
       ok(createResult1.id, "The contact now has an ID.");
       checkContacts(c5, createResult1);
       next();
     };
     req.onerror = onFailure;
   },
@@ -1341,27 +1350,26 @@ var steps = [
       checkContacts(req.result[3], c3);
       checkContacts(req.result[4], c4);
       next();
     };
     req.onerror = onFailure;
   },
   function () {
     ok(true, "Don't allow to add custom fields");
-    createResult1 = new mozContact();
-    createResult1.init({givenName: "customTest", yyy: "XXX"});
+    createResult1 = new mozContact({givenName: ["customTest"], yyy: "XXX"});
     req = mozContacts.save(createResult1);
     req.onsuccess = function() {
-      var options = {filterBy: [],
+      var options = {filterBy: ["givenName"],
                      filterOp: "equals",
                      filterValue: "customTest"};
       var req2 = mozContacts.find(options);
       req2.onsuccess = function() {
         is(req2.result.length, 1, "1 Entry");
-        checkStrArray(req2.result.givenName, "customTest", "same name");
+        checkStrArray(req2.result[0].givenName, ["customTest"], "same name");
         ok(req2.result.yyy === undefined, "custom property undefined");
         next();
       }
       req2.onerror = onFailure;
     }
     req.onerror = onFailure;
   },
   function () {
@@ -1370,42 +1378,39 @@ var steps = [
     req.onsuccess = function () {
       ok(true, "Deleted the database");
       next();
     }
     req.onerror = onFailure;
   },
   function () {
     ok(true, "Test sorting");
-    createResult1 = new mozContact();
-    createResult1.init(c7);
+    createResult1 = new mozContact(c7);
     req = navigator.mozContacts.save(createResult1);
     req.onsuccess = function () {
       ok(createResult1.id, "The contact now has an ID.");
       checkContacts(c7, createResult1);
       next();
     };
     req.onerror = onFailure;
   },
   function () {
     ok(true, "Test sorting");
-    createResult1 = new mozContact();
-    createResult1.init(c6);
+    createResult1 = new mozContact(c6);
     req = navigator.mozContacts.save(createResult1);
     req.onsuccess = function () {
       ok(createResult1.id, "The contact now has an ID.");
       checkContacts(c6, createResult1);
       next();
     };
     req.onerror = onFailure;
   },
   function () {
     ok(true, "Test sorting");
-    createResult1 = new mozContact();
-    createResult1.init(c8);
+    createResult1 = new mozContact(c8);
     req = navigator.mozContacts.save(createResult1);
     req.onsuccess = function () {
       ok(createResult1.id, "The contact now has an ID.");
       checkContacts(c8, createResult1);
       next();
     };
     req.onerror = onFailure;
   },
@@ -1425,27 +1430,26 @@ var steps = [
       ok(req.result[0].published < req.result[1].published, "Right sorting order");
       ok(req.result[1].published < req.result[2].published, "Right sorting order");
       next();
     };
     req.onerror = onFailure;
   },
   function () {
     ok(true, "Deleting database");
-    req = mozContacts.clear()
+    req = mozContacts.clear();
     req.onsuccess = function () {
       ok(true, "Deleted the database");
       next();
     }
     req.onerror = onFailure;
   },
   function () {
     ok(true, "Adding a new contact with properties2");
-    createResult2 = new mozContact();
-    createResult2.init(properties2);
+    createResult2 = new mozContact(properties2);
     req = mozContacts.save(createResult2);
     req.onsuccess = function () {
       ok(createResult2.id, "The contact now has an ID.");
       sample_id2 = createResult2.id;
       next();
     };
     req.onerror = onFailure;
   },
@@ -1480,19 +1484,18 @@ var steps = [
     req = mozContacts.clear()
     req.onsuccess = function () {
       ok(true, "Deleted the database");
       next();
     }
     req.onerror = onFailure;
   },
   function () {
-    ok(true, "Adding empty contact");
-    createResult1 = new mozContact();
-    createResult1.init({name: "5", givenName: "5"});
+    ok(true, "Adding contact for category search");
+    createResult1 = new mozContact({name: ["5"], givenName: ["5"]});
     req = navigator.mozContacts.save(createResult1);
     req.onsuccess = function () {
       ok(createResult1.id, "The contact now has an ID.");
       sample_id1 = createResult1.id;
       next();
     };
     req.onerror = onFailure;
   },
@@ -1517,30 +1520,20 @@ var steps = [
       next();
     }
     req.onerror = onFailure;
   },
   function () {
     ok(true, "Adding contact with invalid data");
     var input = document.createElement("input");
     var obj = {
-        name: [1, 2],
-        familyName: 3,
-        givenName: 4,
         honorificPrefix: [],
-        honorificSuffix: {foo: "bar"},
-        additionalName: 7,
-        nickname: [8, 9],
-        org: [10, 11],
-        jobTitle: [12, 13],
-        note: 14,
-        category: [15, 16],
+        honorificSuffix: [{foo: "bar"}],
         sex: 17,
         genderIdentity: 18,
-        key: 4,
         email: input,
         adr: input,
         tel: input,
         impp: input,
         url: input
     };
     obj.honorificPrefix.__defineGetter__('0',(function() {
       var c = 0;
@@ -1548,54 +1541,51 @@ var steps = [
         if (c == 0) {
           c++;
           return "string";
         } else {
           return {foo:"bar"};
         }
       }
     })());
-    createResult1 = new mozContact();
-    createResult1.init(obj);
+    createResult1 = new mozContact(obj);
     req = mozContacts.save(createResult1);
     req.onsuccess = function () {
       checkContacts(createResult1, {
-        honorificPrefix: "string",
+        honorificPrefix: ["string"],
+        honorificSuffix: ["[object Object]"],
         sex: "17",
         genderIdentity: "18"
       });
       next();
     };
   },
   function () {
     ok(true, "Adding contact with no number but carrier");
-    createResult1 = new mozContact();
-    createResult1.init({ tel: [{type: ["home"], carrier: "myCarrier"} ] });
+    createResult1 = new mozContact({ tel: [{type: ["home"], carrier: "myCarrier"} ] });
     req = navigator.mozContacts.save(createResult1);
     req.onsuccess = function () {
       ok(createResult1.id, "The contact now has an ID.");
       next();
     };
     req.onerror = onFailure;
   },
   function () {
     ok(true, "Adding contact with email but no value");
-    createResult1 = new mozContact();
-    createResult1.init({ email: [{type: ["home"]}] });
+    createResult1 = new mozContact({ email: [{type: ["home"]}] });
     req = navigator.mozContacts.save(createResult1);
     req.onsuccess = function () {
       ok(createResult1.id, "The contact now has an ID.");
       next();
     };
     req.onerror = onFailure;
   },
   function () {
     ok(true, "Testing numbersOnly search 1");
-    createResult1 = new mozContact();
-    createResult1.init({ name: ["aaaaaaaaa"], givenName: ["aaaaaaaaa"], tel: [{ value: "1234567890"}]});
+    createResult1 = new mozContact({ name: ["aaaaaaaaa"], givenName: ["aaaaaaaaa"], tel: [{ value: "1234567890"}]});
     req = navigator.mozContacts.save(createResult1);
     req.onsuccess = function () {
       ok(createResult1.id, "The contact now has an ID.");
       next();
     };
     req.onerror = onFailure;
   },
   function () {
@@ -1665,27 +1655,20 @@ var steps = [
     req.onsuccess = function () {
       ok(true, "Deleted the database");
       next();
     }
     req.onerror = onFailure;
   },
   function() {
     ok(true, "Test setting array properties to scalar values")
-    const DOMStrings = ["name","honorificPrefix","givenName","additionalName",
-                        "familyName", "honorificSuffix","nickname","category",
-                        "org","jobTitle","note"];
     const FIELDS = ["email","url","adr","tel","impp"];
     createResult1 = new mozContact();
-    for (var prop of DOMStrings) {
-      createResult1[prop] = "foo";
-      ok(Array.isArray(createResult1[prop]), prop + " is array");
-    }
     for (var prop of FIELDS) {
-      createResult1[prop] = {type: "foo"};
+      createResult1[prop] = {type: ["foo"]};
       ok(Array.isArray(createResult1[prop]), prop + " is array");
     }
     next();
   },
   function () {
     ok(true, "all done!\n");
     clearTemps();
 
--- a/dom/contacts/tests/test_contacts_blobs.html
+++ b/dom/contacts/tests/test_contacts_blobs.html
@@ -85,24 +85,24 @@ function verifyBuffers(buffer1, buffer2,
   if (isLast)
     next();
 }
 
 var randomBlob = getRandomBlob(1024);
 var randomBlob2 = getRandomBlob(1024);
 
 var properties1 = {
-  name: "xTestname1",
-  givenName: "xTestname1",
+  name: ["xTestname1"],
+  givenName: ["xTestname1"],
   photo: [randomBlob]
 };
 
 var properties2 = {
-  name: "yTestname2",
-  givenName: "yTestname2",
+  name: ["yTestname2"],
+  givenName: ["yTestname2"],
   photo: [randomBlob, randomBlob2]
 };
 
 var sample_id1;
 var createResult1;
 var findResult1;
 
 function onUnwantedSuccess() {
@@ -172,115 +172,61 @@ var steps = [
     req.onsuccess = function () {
       ok(true, "Deleted the database");
       next();
     };
     req.onerror = onFailure;
   },
   function () {
     ok(true, "Adding contact with photo");
-    createResult1 = new mozContact();
-    createResult1.init(properties1);
-    req = navigator.mozContacts.save(createResult1);
-    req.onsuccess = function () {
-      ok(createResult1.id, "The contact now has an ID.");
-      sample_id1 = createResult1.id;
-      next();
-    };
-    req.onerror = onFailure;
-  },
-  function () {
-    ok(true, "Retrieving by substring");
-    var options = {filterBy: ["givenName"],
-                   filterOp: "startsWith",
-                   filterValue: properties1.givenName.substring(0,3)};
-    req = mozContacts.find(options);
-    req.onsuccess = function () {
-      ok(req.result.length == 1, "Found exactly 1 contact.");
-      findResult1 = req.result[0];
-      ok(findResult1.id == sample_id1, "Same ID");
-      verifyBlobArray(createResult1.photo, properties1.photo);
-    };
-    req.onerror = onFailure;
-  },
-  function () {
-    ok(true, "Adding contact with 2 photos");
-    createResult1 = new mozContact();
-    createResult1.init(properties2);
+    createResult1 = new mozContact(properties1);
     req = navigator.mozContacts.save(createResult1);
     req.onsuccess = function () {
       ok(createResult1.id, "The contact now has an ID.");
       sample_id1 = createResult1.id;
       next();
     };
     req.onerror = onFailure;
   },
   function () {
     ok(true, "Retrieving by substring");
     var options = {filterBy: ["givenName"],
                    filterOp: "startsWith",
-                   filterValue: properties2.givenName.substring(0,3)};
+                   filterValue: properties1.givenName[0].substring(0,3)};
     req = mozContacts.find(options);
     req.onsuccess = function () {
       ok(req.result.length == 1, "Found exactly 1 contact.");
       findResult1 = req.result[0];
       ok(findResult1.id == sample_id1, "Same ID");
-      verifyBlobArray(createResult1.photo, properties2.photo);
+      verifyBlobArray(createResult1.photo, properties1.photo);
     };
     req.onerror = onFailure;
   },
   function () {
-    ok(true, "Adding photo as String");
-    createResult1 = new mozContact();
-    createResult1.init({givenName: "asdf", photo: ["xyz"]});
+    ok(true, "Adding contact with 2 photos");
+    createResult1 = new mozContact(properties2);
     req = navigator.mozContacts.save(createResult1);
     req.onsuccess = function () {
       ok(createResult1.id, "The contact now has an ID.");
       sample_id1 = createResult1.id;
-      is(createResult1.photo, null, "No photo")
-      next();
-    };
-    req.onerror = onFailure;
-  },
-  function () {
-    ok(true, "Adding photo as String");
-    createResult1 = new mozContact();
-    createResult1.init({givenName: "jkl", photo: "xyz"});
-    req = navigator.mozContacts.save(createResult1);
-    req.onsuccess = function () {
-      ok(createResult1.id, "The contact now has an ID.");
-      is(createResult1.photo, null, "No photo")
       next();
     };
     req.onerror = onFailure;
   },
   function () {
     ok(true, "Retrieving by substring");
     var options = {filterBy: ["givenName"],
                    filterOp: "startsWith",
-                   filterValue: "asdf"};
+                   filterValue: properties2.givenName[0].substring(0,3)};
     req = mozContacts.find(options);
     req.onsuccess = function () {
       ok(req.result.length == 1, "Found exactly 1 contact.");
       findResult1 = req.result[0];
       ok(findResult1.id == sample_id1, "Same ID");
-      is(findResult1.photo, null, "No photo");
-      next();
-    };
-    req.onerror = onFailure;
-  },
-  function () {
-    ok(true, "Adding photo as Object");
-    createResult1 = new mozContact();
-    createResult1.init({photo: [{}]});
-    req = navigator.mozContacts.save(createResult1);
-    req.onsuccess = function () {
-      ok(createResult1.id, "The contact now has an ID.");
-      is(createResult1.photo, null, "No photo")
-      next();
+      verifyBlobArray(createResult1.photo, properties2.photo);
     };
     req.onerror = onFailure;
   },
   function () {
     ok(true, "Deleting database");
     req = mozContacts.clear()
     req.onsuccess = function () {
       ok(true, "Deleted the database");
--- a/dom/contacts/tests/test_contacts_getall.html
+++ b/dom/contacts/tests/test_contacts_getall.html
@@ -31,182 +31,216 @@ SpecialPowers.addPermission("contacts-re
 SpecialPowers.addPermission("contacts-create", true, document);
 
 var isAndroid = (navigator.userAgent.indexOf("Android") !== -1);
 var androidVersion = SpecialPowers.Cc['@mozilla.org/system-info;1']
                                   .getService(SpecialPowers.Ci.nsIPropertyBag2)
                                   .getProperty('version');
 
 let adr1 = {
-  type: "work",
+  type: ["work"],
   streetAddress: "street 1",
   locality: "locality 1",
   region: "region 1",
   postalCode: "postal code 1",
   countryName: "country 1"
 };
 
 let properties1 = {
   name: ["Testname1 TestFamilyName"],
   familyName: ["TestFamilyName","Wagner"],
   givenName: ["Test1","Test2"],
-  nickname: "nicktest",
+  nickname: ["nicktest"],
   tel: [{type: ["work"], value: "123456", carrier: "testCarrier"} , {type: ["home", "fax"], value: "+9-876-5432"}],
-  adr: adr1,
+  adr: [adr1],
   email: [{type: ["work"], value: "x@y.com"}]
 };
 
 function onFailure() {
   ok(false, "in on Failure!");
   next();
 }
-
 function checkStr(str1, str2, msg) {
-  // comparing /[null(,null)+]/ and undefined should pass
-  function nonNull(e) {
-    return e != null;
+  if (str1 ^ str2) {
+    ok(false, "Expected both strings to be either present or absent");
+    return;
   }
-  if ((Array.isArray(str1) && str1.filter(nonNull).length == 0 && str2 == undefined)
-     ||(Array.isArray(str2) && str2.filter(nonNull).length == 0 && str1 == undefined)) {
-    ok(true, msg);
-  } else if (str1) {
-    is(JSON.stringify(typeof str1 == "string" ? [str1] : str1), JSON.stringify(typeof str2 == "string" ? [str2] : str2), msg);
+  is(str1, str2, msg);
+}
+
+function checkStrArray(str1, str2, msg) {
+  function normalize_falsy(k, v) {
+    if (!v || v == "null" || v == "undefined") {
+      return "";
+    }
+    return v;
   }
+  ise(JSON.stringify(str1, normalize_falsy), JSON.stringify(str2, normalize_falsy), msg);
+}
+
+function checkPref(pref1, pref2) {
+  // If on Android treat one preference as 0 and the other as undefined as matching
+  if (isAndroid) {
+    if ((!pref1 && pref2 == undefined) || (pref1 == undefined && !pref2)) {
+      pref1 = false;
+      pref2 = false;
+    }
+  }
+  ise(!!pref1, !!pref2, "Same pref");
 }
 
 function checkAddress(adr1, adr2) {
-  checkStr(adr1.type, adr2.type, "Same type");
-  checkStr(adr1.streetAddress, adr2.streetAddress, "Same streetAddress");
-  checkStr(adr1.locality, adr2.locality, "Same locality");
-  checkStr(adr1.region, adr2.region, "Same region");
-  checkStr(adr1.postalCode, adr2.postalCode, "Same postalCode");
-  checkStr(adr1.countryName, adr2.countryName, "Same countryName");
+  if (adr1 ^ adr2) {
+    ok(false, "Expected both adrs to be either present or absent");
+    return;
+  }
+  checkStrArray(adr1.type, adr2.type, "Same type");
+  checkStrArray(adr1.streetAddress, adr2.streetAddress, "Same streetAddress");
+  checkStrArray(adr1.locality, adr2.locality, "Same locality");
+  checkStrArray(adr1.region, adr2.region, "Same region");
+  checkStrArray(adr1.postalCode, adr2.postalCode, "Same postalCode");
+  checkStrArray(adr1.countryName, adr2.countryName, "Same countryName");
+  checkPref(adr1.pref, adr2.pref);
+}
+
+function checkField(field1, field2) {
+  if (field1 ^ field2) {
+    ok(false, "Expected both fields to be either present or absent");
+    return;
+  }
+  checkStrArray(field1.type, field2.type, "Same type");
+  checkStrArray(field1.value, field2.value, "Same value");
+  checkPref(field1.pref, field2.pref);
 }
 
 function checkTel(tel1, tel2) {
-  checkStr(tel1.type, tel2.type, "Same type");
-  checkStr(tel1.value, tel2.value, "Same value");
-  checkStr(tel1.carrier, tel2.carrier, "Same carrier");
+  if (tel1 ^ tel2) {
+    ok(false, "Expected both tels to be either present or absent");
+    return;
+  }
+  checkField(tel1, tel2);
+  checkStrArray(tel1.carrier, tel2.carrier, "Same carrier");
+}
+
+function checkCategory(category1, category2) {
+  // Android adds contacts to the a default category. This should be removed from the
+  // results before comparing them
+  if (isAndroid) {
+    category1 = removeAndroidDefaultCategory(category1);
+    category2 = removeAndroidDefaultCategory(category2);
+  }
+  checkStrArray(category1, category2, "Same Category")
 }
 
-function checkField(field1, field2) {
-  checkStr(field1.type, field2.type, "Same type");
-  checkStr(field1.value, field2.value, "Same value");
+function removeAndroidDefaultCategory(category) {
+  if (!category) {
+    return category;
+  }
+
+  var result = [];
+
+  for (var i of category) {
+    // Some devices may return the full group name (prefixed with "System Group: ")
+    if (i != "My Contacts" && i != "System Group: My Contacts") {
+      result.push(i);
+    }
+  }
+
+  return result;
+}
+
+function checkArrayField(array1, array2, func, msg) {
+  if (!!array1 ^ !!array2) {
+    ok(false, "Expected both arrays to be either present or absent");
+    return;
+  }
+  if (!array1 && !array2)  {
+    ok(true, msg);
+    return;
+  }
+  ise(array1.length, array2.length, "Same length");
+  for (var i = 0; i < array1.length; ++i) {
+    func(array1[i], array2[i], msg);
+  }
 }
 
 function checkContacts(contact1, contact2) {
-  checkStr(contact1.name, contact2.name, "Same name");
-  checkStr(contact1.honorificPrefix, contact2.honorificPrefix, "Same honorificPrefix");
-  checkStr(contact1.givenName, contact2.givenName, "Same givenName");
-  checkStr(contact1.additionalName, contact2.additionalName, "Same additionalName");
-  checkStr(contact1.familyName, contact2.familyName, "Same familyName");
-  checkStr(contact1.honorificSuffix, contact2.honorificSuffix, "Same honorificSuffix");
-  checkStr(contact1.nickname, contact2.nickname, "Same nickname");
-  checkStr(contact1.category, contact2.category, "Same category");
-  checkStr(contact1.org, contact2.org, "Same org");
-  checkStr(contact1.jobTitle, contact2.jobTitle, "Same jobTitle");
-  is(contact1.bday ? contact1.bday.valueOf() : null, contact2.bday ? contact2.bday.valueOf() : null, "Same birthday");
-  checkStr(contact1.note, contact2.note, "Same note");
-  is(contact1.anniversary ? contact1.anniversary.valueOf() : null , contact2.anniversary ? contact2.anniversary.valueOf() : null, "Same anniversary");
-  is(contact1.sex, contact2.sex, "Same sex");
-  is(contact1.genderIdentity, contact2.genderIdentity, "Same genderIdentity");
-
-  for (let i in contact1.email) {
-    if (contact1.email) {
-      ok(contact2.email != null, "conatct2.email exists");
-    }
-    if (contact2.email) {
-      ok(contact1.email != null, "conatct1.email exists");
-    }
-    checkField(contact1.email[i], contact2.email[i]);
+  if (!!contact1 ^ !!contact2) {
+    ok(false, "Expected both contacts to be either present or absent");
+    return;
   }
-  for (let i in contact1.adr) {
-    if (contact1.adr) {
-      ok(contact2.adr != null, "conatct2.adr exists");
-    }
-    if (contact2.adr) {
-      ok(contact1.adr != null, "conatct1.adr exists");
-    }
-    checkAddress(contact1.adr[i], contact2.adr[i]);
-  }
-  for (let i in contact1.tel) {
-    if (contact1.tel) {
-      ok(contact2.tel != null, "conatct2.tel exists");
-    }
-    if (contact2.tel) {
-      ok(contact1.tel != null, "conatct1.tel exists");
-    }
-    checkTel(contact1.tel[i], contact2.tel[i]);
-  }
-  for (let i in contact1.url) {
-    if (contact1.url) {
-      ok(contact2.url != null, "conatct2.url exists");
-    }
-    if (contact2.url) {
-      ok(contact1.url != null, "conatct1.url exists");
-    }
-    checkField(contact1.url[i], contact2.url[i]);
-  }
-  for (let i in contact1.impp) {
-    if (contact1.impp) {
-      ok(contact2.impp != null, "conatct2.impp exists");
-    }
-    if (contact2.impp) {
-      ok(contact1.impp != null, "conatct1.impp exists");
-    }
-    checkField(contact1.impp[i], contact2.impp[i]);
-  }
+  checkStrArray(contact1.name, contact2.name, "Same name");
+  checkStrArray(contact1.honorificPrefix, contact2.honorificPrefix, "Same honorificPrefix");
+  checkStrArray(contact1.givenName, contact2.givenName, "Same givenName");
+  checkStrArray(contact1.additionalName, contact2.additionalName, "Same additionalName");
+  checkStrArray(contact1.familyName, contact2.familyName, "Same familyName");
+  checkStrArray(contact1.honorificSuffix, contact2.honorificSuffix, "Same honorificSuffix");
+  checkStrArray(contact1.nickname, contact2.nickname, "Same nickname");
+  checkCategory(contact1.category, contact2.category);
+  checkStrArray(contact1.org, contact2.org, "Same org");
+  checkStrArray(contact1.jobTitle, contact2.jobTitle, "Same jobTitle");
+  is(contact1.bday ? contact1.bday.valueOf() : null, contact2.bday ? contact2.bday.valueOf() : null, "Same birthday");
+  checkStrArray(contact1.note, contact2.note, "Same note");
+  is(contact1.anniversary ? contact1.anniversary.valueOf() : null , contact2.anniversary ? contact2.anniversary.valueOf() : null, "Same anniversary");
+  checkStr(contact1.sex, contact2.sex, "Same sex");
+  checkStr(contact1.genderIdentity, contact2.genderIdentity, "Same genderIdentity");
+  checkStrArray(contact1.key, contact2.key, "Same key");
+
+  checkArrayField(contact1.adr, contact2.adr, checkAddress, "Same adr");
+  checkArrayField(contact1.tel, contact2.tel, checkTel, "Same tel");
+  checkArrayField(contact1.email, contact2.email, checkField, "Same email");
+  checkArrayField(contact1.url, contact2.url, checkField, "Same url");
+  checkArrayField(contact1.impp, contact2.impp, checkField, "Same impp");
 }
 
 function clearDatabase() {
   ok(true, "Clearing database");
   req = mozContacts.clear();
   req.onsuccess = function() {
     ok(true, "Cleared the database");
     next();
   };
   req.onerror = onFailure;
 }
 
 function addContacts() {
   ok(true, "Adding 40 contacts");
   for (let i = 0; i < 39; ++i) {
-    createResult1 = new mozContact();
     properties1.familyName[0] = "Testname" + (i < 10 ? "0" + i : i);
-    properties1.name = properties1.givenName[0] + " " + properties1.familyName[0];
-    createResult1.init(properties1);
+    properties1.name = [properties1.givenName[0] + " " + properties1.familyName[0]];
+    createResult1 = new mozContact(properties1);
     req = mozContacts.save(createResult1);
     req.onsuccess = function() {
       ok(createResult1.id, "The contact now has an ID.");
     };
     req.onerror = onFailure;
   };
-  createResult1 = new mozContact();
   properties1.familyName[0] = "Testname39";
-  properties1.name = properties1.givenName[0] + " Testname39";
-  createResult1.init(properties1);
+  properties1.name = [properties1.givenName[0] + " Testname39"];
+  createResult1 = new mozContact(properties1);
   req = mozContacts.save(createResult1);
   req.onsuccess = function() {
     ok(createResult1.id, "The contact now has an ID.");
-    ok(createResult1.name == properties1.name, "Same Name");
+    checkStrArray(createResult1.name, properties1.name, "Same Name");
     next();
   };
   req.onerror = onFailure;
 }
 
 let createResult1;
 
 let index = 0;
 let req;
 let mozContacts = window.navigator.mozContacts;
 
 function getOne(msg) {
   return function() {
     ok(true, msg || "Retrieving one contact with getAll");
     req = mozContacts.getAll({});
+
     let count = 0;
     req.onsuccess = function(event) {
       ok(true, "on success");
       if (req.result) {
         ok(true, "result is valid");
         count++;
         req.continue();
       } else {
@@ -241,21 +275,24 @@ function getAll(msg) {
         next();
       }
     };
     req.onerror = onFailure;
   }
 }
 
 let steps = [
+  function start() {
+    SpecialPowers.Cc["@mozilla.org/tools/profiler;1"].getService(SpecialPowers.Ci.nsIProfiler).AddMarker("GETALL_START");
+    next();
+  },
   clearDatabase,
   function() {
     // add a contact
-    createResult1 = new mozContact();
-    createResult1.init({});
+    createResult1 = new mozContact({});
     req = navigator.mozContacts.save(createResult1);
     req.onsuccess = function() {
       next();
     };
     req.onerror = onFailure;
   },
 
   getOne(),
@@ -436,16 +473,17 @@ let steps = [
       next();
     }
   },
 
   clearDatabase,
 
   function() {
     ok(true, "all done!\n");
+    SpecialPowers.Cc["@mozilla.org/tools/profiler;1"].getService(SpecialPowers.Ci.nsIProfiler).AddMarker("GETALL_END");
     SimpleTest.finish();
   }
 ];
 
 function next() {
   ok(true, "Begin!");
   if (index >= steps.length) {
     ok(false, "Shouldn't get here!");
--- a/dom/contacts/tests/test_contacts_international.html
+++ b/dom/contacts/tests/test_contacts_international.html
@@ -46,33 +46,33 @@ var number1 = {
 };
 
 var number2 = {
   local: "7932012346",
   international: "+557932012346"
 };
 
 var properties1 = {
-  name: "Testname1",
+  name: ["Testname1"],
   tel: [{type: ["work"], value: number1.local, carrier: "testCarrier"} , {type: ["home", "fax"], value: number2.local}],
 };
 
 var shortNumber = "888";
 var properties2 = {
-  name: "Testname2",
+  name: ["Testname2"],
   tel: [{type: ["work"], value: shortNumber, carrier: "testCarrier"}]
 };
 
 var number3 = {
   international1: "0041557932012345",
   international2: "+557932012345"
 };
 
 var properties3 = {
-  name: "Testname2",
+  name: ["Testname2"],
   tel: [{value: number3.international2}]
 };
 
 var req;
 var index = 0;
 var createResult1;
 var findResult1;
 var sample_id1;
@@ -86,30 +86,28 @@ var steps = [
     req.onsuccess = function () {
       ok(true, "Deleted the database");
       next();
     };
     req.onerror = onFailure;
   },
   function () {
     ok(true, "Adding a new contact1");
-    createResult1 = new mozContact();
-    createResult1.init(properties1);
+    createResult1 = new mozContact(properties1);
     req = navigator.mozContacts.save(createResult1);
     req.onsuccess = function () {
       ok(createResult1.id, "The contact now has an ID.");
       sample_id1 = createResult1.id;
       next();
     };
     req.onerror = onFailure;
   },
   function () {
     ok(true, "Adding a new contact2");
-    var createResult2 = new mozContact();
-    createResult2.init(properties2);
+    var createResult2 = new mozContact(properties2);
     req = navigator.mozContacts.save(createResult2);
     req.onsuccess = function () {
       ok(createResult2.id, "The contact now has an ID.");
       next();
     };
     req.onerror = onFailure;
   },
   function () {
@@ -244,18 +242,17 @@ var steps = [
     req.onsuccess = function () {
       ok(true, "Deleted the database");
       next();
     }
     req.onerror = onFailure;
   },
   function () {
     ok(true, "Adding a new contact with country code");
-    createResult1 = new mozContact();
-    createResult1.init(properties3);
+    createResult1 = new mozContact(properties3);
     req = navigator.mozContacts.save(createResult1);
     req.onsuccess = function () {
       ok(createResult1.id, "The contact now has an ID.");
       sample_id1 = createResult1.id;
       next();
     };
     req.onerror = onFailure;
   },
--- a/dom/contacts/tests/test_contacts_substringmatching.html
+++ b/dom/contacts/tests/test_contacts_substringmatching.html
@@ -77,18 +77,17 @@ var steps = [
     req.onsuccess = function () {
       ok(true, "Deleted the database");
       next();
     }
     req.onerror = onFailure;
   },
   function () {
     ok(true, "Adding contact");
-    createResult1 = new mozContact();
-    createResult1.init(prop);
+    createResult1 = new mozContact(prop);
     req = navigator.mozContacts.save(createResult1);
     req.onsuccess = function () {
       ok(createResult1.id, "The contact now has an ID.");
       sample_id1 = createResult1.id;
       next();
     };
     req.onerror = onFailure;
   },
@@ -161,18 +160,17 @@ var steps = [
     req.onsuccess = function () {
       is(req.result.length, 1, "Found exactly 1 contacts.");
       next();
     };
     req.onerror = onFailure;
   },
   function () {
     ok(true, "Adding contact");
-    createResult1 = new mozContact();
-    createResult1.init(prop2);
+    createResult1 = new mozContact(prop2);
     req = navigator.mozContacts.save(createResult1);
     req.onsuccess = function () {
       ok(createResult1.id, "The contact now has an ID.");
       sample_id1 = createResult1.id;
       next();
     };
     req.onerror = onFailure;
   },
@@ -254,18 +252,17 @@ var steps = [
     req.onsuccess = function () {
       ok(true, "Deleted the database");
       next();
     }
     req.onerror = onFailure;
   },
   function () {
     ok(true, "Adding contact");
-    createResult1 = new mozContact();
-    createResult1.init(prop3);
+    createResult1 = new mozContact(prop3);
     req = navigator.mozContacts.save(createResult1);
     req.onsuccess = function () {
       ok(createResult1.id, "The contact now has an ID.");
       sample_id1 = createResult1.id;
       next();
     };
     req.onerror = onFailure;
   },
@@ -284,18 +281,17 @@ var steps = [
       };
       req.onerror = onFailure;
     } else {
       SpecialPowers.executeSoon(next);
     }
   },
   function () {
     ok(true, "Adding contact");
-    createResult1 = new mozContact();
-    createResult1.init(prop4);
+    createResult1 = new mozContact(prop4);
     req = navigator.mozContacts.save(createResult1);
     req.onsuccess = function () {
       ok(createResult1.id, "The contact now has an ID.");
       sample_id1 = createResult1.id;
       next();
     };
     req.onerror = onFailure;
   },
--- a/dom/contacts/tests/test_contacts_substringmatchingVE.html
+++ b/dom/contacts/tests/test_contacts_substringmatchingVE.html
@@ -68,18 +68,17 @@ var steps = [
     req.onsuccess = function () {
       ok(true, "Deleted the database");
       next();
     }
     req.onerror = onFailure;
   },
   function () {
     ok(true, "Adding contact");
-    createResult1 = new mozContact();
-    createResult1.init(prop);
+    createResult1 = new mozContact(prop);
     req = navigator.mozContacts.save(createResult1);
     req.onsuccess = function () {
       ok(createResult1.id, "The contact now has an ID.");
       sample_id1 = createResult1.id;
       next();
     };
     req.onerror = onFailure;
   },
@@ -107,18 +106,17 @@ var steps = [
       ok(findResult1.id == sample_id1, "Same ID");
       is(findResult1.tel[1].value, "7704143727591", "Same Value");
       next();
     };
     req.onerror = onFailure;
   },
   function () {
     ok(true, "Adding contact");
-    createResult1 = new mozContact();
-    createResult1.init(prop2);
+    createResult1 = new mozContact(prop2);
     req = navigator.mozContacts.save(createResult1);
     req.onsuccess = function () {
       ok(createResult1.id, "The contact now has an ID.");
       sample_id1 = createResult1.id;
       next();
     };
     req.onerror = onFailure;
   },
--- a/dom/icc/interfaces/nsIDOMIccManager.idl
+++ b/dom/icc/interfaces/nsIDOMIccManager.idl
@@ -492,17 +492,17 @@ interface nsIDOMMozIccManager : nsIDOMEv
    *        - 'adn': Abbreviated Dialling Number
    *        - 'fdn': Fixed Dialling Number
    * @param contact
    *        The contact will be updated in ICC
    * @param [optional] pin2
    *        PIN2 is only required for 'fdn'.
    */
   nsIDOMDOMRequest updateContact(in DOMString contactType,
-                                 in nsIDOMContact contact,
+                                 in nsISupports contact,
                                  [optional] in DOMString pin2);
 
   // End of UICC Phonebook Interfaces.
 
   // UICC Secure Element Interfaces
 
   /**
    * A secure element is a smart card chip that can hold
--- a/dom/icc/interfaces/nsIIccProvider.idl
+++ b/dom/icc/interfaces/nsIIccProvider.idl
@@ -67,17 +67,17 @@ interface nsIIccProvider : nsISupports
   /**
    * Phonebook interfaces.
    */
   nsIDOMDOMRequest readContacts(in nsIDOMWindow window,
                                 in DOMString contactType);
 
   nsIDOMDOMRequest updateContact(in nsIDOMWindow window,
                                  in DOMString contactType,
-                                 in nsIDOMContact contact,
+                                 in nsISupports contact,
                                  in DOMString pin2);
 
   /**
    * Secure Card Icc communication channel
    */
   nsIDOMDOMRequest iccOpenChannel(in nsIDOMWindow window,
                                   in DOMString aid);
 
--- a/dom/icc/src/IccManager.cpp
+++ b/dom/icc/src/IccManager.cpp
@@ -229,17 +229,17 @@ IccManager::ReadContacts(const nsAString
     return NS_ERROR_FAILURE;
   }
 
   return mProvider->ReadContacts(GetOwner(), aContactType, aRequest);
 }
 
 NS_IMETHODIMP
 IccManager::UpdateContact(const nsAString& aContactType,
-                          nsIDOMContact* aContact,
+                          nsISupports* aContact,
                           const nsAString& aPin2,
                           nsIDOMDOMRequest** aRequest)
 {
   if (!mProvider) {
     return NS_ERROR_FAILURE;
   }
 
   return mProvider->UpdateContact(GetOwner(), aContactType, aContact, aPin2, aRequest);
--- a/dom/icc/tests/marionette/test_icc_contact.js
+++ b/dom/icc/tests/marionette/test_icc_contact.js
@@ -10,46 +10,44 @@ ok(icc instanceof MozIccManager, "icc is
 
 function testReadContacts(type) {
   let request = icc.readContacts(type);
   request.onsuccess = function onsuccess() {
     let contacts = request.result;
 
     is(Array.isArray(contacts), true);
 
-    is(contacts[0].name, "Mozilla");
+    is(contacts[0].name[0], "Mozilla");
     is(contacts[0].tel[0].value, "15555218201");
     is(contacts[0].id, "890141032111185107201");
 
-    is(contacts[1].name, "Saßê黃");
+    is(contacts[1].name[0], "Saßê黃");
     is(contacts[1].tel[0].value, "15555218202");
     is(contacts[1].id, "890141032111185107202");
 
-    is(contacts[2].name, "Fire 火");
+    is(contacts[2].name[0], "Fire 火");
     is(contacts[2].tel[0].value, "15555218203");
     is(contacts[2].id, "890141032111185107203");
 
-    is(contacts[3].name, "Huang 黃");
+    is(contacts[3].name[0], "Huang 黃");
     is(contacts[3].tel[0].value, "15555218204");
     is(contacts[3].id, "890141032111185107204");
 
     runNextTest();
   };
 
   request.onerror = function onerror() {
     ok(false, "Cannot get " + type + " contacts");
     runNextTest();
   };
 };
 
 function testAddContact(type, pin2) {
-  let contact = new mozContact();
-
-  contact.init({
-    name: "add",
+  let contact = new mozContact({
+    name: ["add"],
     tel: [{value: "0912345678"}],
     email:[]
   });
 
   let updateRequest = icc.updateContact(type, contact, pin2);
 
   updateRequest.onsuccess = function onsuccess() {
     // Get ICC contact for checking new contact
@@ -57,17 +55,17 @@ function testAddContact(type, pin2) {
     let getRequest = icc.readContacts(type);
 
     getRequest.onsuccess = function onsuccess() {
       let contacts = getRequest.result;
 
       // There are 4 SIM contacts which are harded in emulator
       is(contacts.length, 5);
 
-      is(contacts[4].name, "add");
+      is(contacts[4].name[0], "add");
       is(contacts[4].tel[0].value, "0912345678");
 
       runNextTest();
     };
 
     getRequest.onerror = function onerror() {
       ok(false, "Cannot get " + type + " contacts: " + getRequest.error.name);
       runNextTest();
deleted file mode 100644
--- a/dom/interfaces/contacts/moz.build
+++ /dev/null
@@ -1,15 +0,0 @@
-# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
-# vim: set filetype=python:
-# This Source Code Form is subject to the terms of the Mozilla Public
-# License, v. 2.0. If a copy of the MPL was not distributed with this
-# file, You can obtain one at http://mozilla.org/MPL/2.0/.
-
-XPIDL_SOURCES += [
-    'nsIContactProperties.idl',
-    'nsIDOMContactManager.idl',
-]
-
-XPIDL_MODULE = 'dom_contacts'
-
-MODULE = 'dom'
-
deleted file mode 100644
--- a/dom/interfaces/contacts/nsIContactProperties.idl
+++ /dev/null
@@ -1,74 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this file,
- * You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-#include "domstubs.idl"
-
-[scriptable, uuid(9cbfa81c-bcab-4ca9-b0d2-f4318f295e33)]
-interface nsIContactAddress : nsISupports
-{
-  attribute DOMString type;
-  attribute boolean   pref; // false = no pref, true = preferred (vCard3 TYPE:PREF; vCard4 PREF:1)
-  attribute DOMString streetAddress;
-  attribute DOMString locality;
-  attribute DOMString region;
-  attribute DOMString postalCode;
-  attribute DOMString countryName;
-};
-
-[scriptable, uuid(ad19a543-69e4-44f0-adfa-37c011556bc1)]
-interface nsIContactField : nsISupports
-{
-  attribute jsval     type; // DOMString[], "home", "work", etc.
-  attribute DOMString value;
-  attribute boolean   pref; // false = no pref, true = preferred (vCard3 TYPE:PREF; vCard4 PREF:1)
-};
-
-[scriptable, uuid(4d42c5a9-ea5d-4102-80c3-40cc986367ca)]
-interface nsIContactTelField : nsIContactField
-{
-  attribute DOMString carrier;
-};
-
-[scriptable, uuid(0a5b1fab-70da-46dd-b902-619904d920c2)]
-interface nsIContactFindSortOptions : nsISupports
-{
-  attribute DOMString sortBy;       // "givenName" or "familyName"
-  attribute DOMString sortOrder;    // e.g. "descending"
-};
-
-[scriptable, uuid(28ce07d0-45d9-4b7a-8843-521df4edd8bc)]
-interface nsIContactFindOptions : nsIContactFindSortOptions
-{
-  attribute DOMString filterValue;  // e.g. "Tom"
-  attribute DOMString filterOp;     // e.g. "startsWith"
-  attribute jsval filterBy;         // DOMString[], e.g. ["givenName", "nickname"]
-  attribute unsigned long filterLimit;
-};
-
-[scriptable, uuid(35ad8a4e-9486-44b6-883d-550f14635e49)]
-interface nsIContactProperties : nsISupports
-{
-  attribute jsval         name;               // DOMString[]
-  attribute jsval         honorificPrefix;    // DOMString[]
-  attribute jsval         givenName;          // DOMString[]
-  attribute jsval         additionalName;     // DOMString[]
-  attribute jsval         familyName;         // DOMString[]
-  attribute jsval         honorificSuffix;    // DOMString[]
-  attribute jsval         nickname;           // DOMString[]
-  attribute jsval         email;              // ContactField[]
-  attribute jsval         photo;              // nsIDOMBlob[]
-  attribute jsval         url;                // ContactField[]
-  attribute jsval         category;           // DOMString[]
-  attribute jsval         adr;                // ContactAddress[]
-  attribute jsval         tel;                // ContactTelField[]
-  attribute jsval         org;                // DOMString[]
-  attribute jsval         jobTitle;           // DOMString[]
-  attribute jsval         bday;               // Date
-  attribute jsval         note;               // DOMString[]
-  attribute jsval         impp;               // ContactField[]
-  attribute jsval         anniversary;        // Date
-  attribute DOMString     sex;                // DOMString
-  attribute DOMString     genderIdentity;     // DOMString
-  attribute jsval         key;                // DOMString[]
-};
deleted file mode 100644
--- a/dom/interfaces/contacts/nsIDOMContactManager.idl
+++ /dev/null
@@ -1,41 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this file,
- * You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-#include "domstubs.idl"
-#include "nsIContactProperties.idl"
-#include "nsIDOMEventTarget.idl"
-
-interface nsIArray;
-interface nsIDOMDOMRequest;
-interface nsIDOMDOMCursor;
-
-[scriptable, uuid(72a5ee28-81d8-4af8-90b3-ae935396cc66)]
-interface nsIDOMContact : nsIContactProperties
-{
-  attribute DOMString id;
-  readonly attribute jsval     published;
-  readonly attribute jsval     updated;
-
-  void init(in nsIContactProperties properties);  // Workaround BUG 723206
-};
-
-[scriptable, uuid(8beb3a66-d70a-4111-b216-b8e995ad3aff)]
-interface nsIDOMContactManager : nsISupports
-{
-  nsIDOMDOMRequest find(in nsIContactFindOptions options);
-
-  nsIDOMDOMCursor getAll(in nsIContactFindSortOptions options);
-
-  nsIDOMDOMRequest clear();
-
-  nsIDOMDOMRequest save(in nsIDOMContact contact);
-
-  nsIDOMDOMRequest remove(in nsIDOMContact contact);
-
-  attribute nsIDOMEventListener oncontactchange;
-
-  nsIDOMDOMRequest getRevision();
-
-  nsIDOMDOMRequest getCount();
-};
--- a/dom/interfaces/events/nsIDOMEvent.idl
+++ b/dom/interfaces/events/nsIDOMEvent.idl
@@ -237,27 +237,27 @@ nsresult
 NS_NewDOMUIEvent(nsIDOMEvent** aInstancePtrResult,
                  mozilla::dom::EventTarget* aOwner,
                  nsPresContext* aPresContext,
                  mozilla::WidgetGUIEvent* aEvent);
 nsresult
 NS_NewDOMMouseEvent(nsIDOMEvent** aInstancePtrResult,
                     mozilla::dom::EventTarget* aOwner,
                     nsPresContext* aPresContext,
-                    mozilla::WidgetInputEvent* aEvent);
+                    mozilla::WidgetMouseEvent* aEvent);
 nsresult
 NS_NewDOMFocusEvent(nsIDOMEvent** aInstancePtrResult,
                     mozilla::dom::EventTarget* aOwner,
                     nsPresContext* aPresContext,
                     mozilla::InternalFocusEvent* aEvent);
 nsresult
 NS_NewDOMMouseScrollEvent(nsIDOMEvent** aInstancePtrResult,
                           mozilla::dom::EventTarget* aOwner,
                           nsPresContext* aPresContext,
-                          mozilla::WidgetInputEvent* aEvent);
+                          mozilla::WidgetMouseScrollEvent* aEvent);
 nsresult
 NS_NewDOMWheelEvent(nsIDOMEvent** aInstancePtrResult,
                     mozilla::dom::EventTarget* aOwner,
                     nsPresContext* aPresContext,
                     mozilla::WidgetWheelEvent* aEvent);
 nsresult
 NS_NewDOMDragEvent(nsIDOMEvent** aInstancePtrResult,
                    mozilla::dom::EventTarget* aOwner,
--- a/dom/ipc/TabParent.cpp
+++ b/dom/ipc/TabParent.cpp
@@ -621,20 +621,20 @@ TabParent::MapEventCoordinatesForChildPr
 void
 TabParent::MapEventCoordinatesForChildProcess(
   const LayoutDeviceIntPoint& aOffset, WidgetEvent* aEvent)
 {
   if (aEvent->eventStructType != NS_TOUCH_EVENT) {
     aEvent->refPoint = aOffset;
   } else {
     aEvent->refPoint = LayoutDeviceIntPoint();
-    WidgetTouchEvent* touchEvent = static_cast<WidgetTouchEvent*>(aEvent);
     // Then offset all the touch points by that distance, to put them
     // in the space where top-left is 0,0.
-    const nsTArray< nsRefPtr<Touch> >& touches = touchEvent->touches;
+    const nsTArray< nsRefPtr<Touch> >& touches =
+      aEvent->AsTouchEvent()->touches;
     for (uint32_t i = 0; i < touches.Length(); ++i) {
       Touch* touch = touches[i];
       if (touch) {
         touch->mRefPoint += LayoutDeviceIntPoint::ToUntyped(aOffset);
       }
     }
   }
 }
@@ -736,17 +736,17 @@ TabParent::TryCapture(const WidgetGUIEve
 {
   MOZ_ASSERT(sEventCapturer == this && mEventCaptureDepth > 0);
 
   if (aEvent.eventStructType != NS_TOUCH_EVENT) {
     // Only capture of touch events is implemented, for now.
     return false;
   }
 
-  WidgetTouchEvent event(static_cast<const WidgetTouchEvent&>(aEvent));
+  WidgetTouchEvent event(*aEvent.AsTouchEvent());
 
   bool isTouchPointUp = (event.message == NS_TOUCH_END ||
                          event.message == NS_TOUCH_CANCEL);
   if (event.message == NS_TOUCH_START || isTouchPointUp) {
     // Let the DOM see touch start/end events so that its touch-point
     // state stays consistent.
     if (isTouchPointUp && 0 == --mEventCaptureDepth) {
       // All event series are un-captured, don't try to catch any
--- a/dom/messages/SystemMessageManager.js
+++ b/dom/messages/SystemMessageManager.js
@@ -279,16 +279,19 @@ SystemMessageManager.prototype = {
 
     debug("done");
   },
 
   observe: function sysMessMgr_observe(aSubject, aTopic, aData) {
     if (aTopic === kSystemMessageInternalReady) {
       this._registerManifest();
     }
+
+    // Call the DOMRequestIpcHelper.observe method.
+    this.__proto__.__proto__.observe.call(this, aSubject, aTopic, aData);
   },
 
   _registerManifest: function sysMessMgr_registerManifest() {
     if (this._isInBrowserElement) {
       debug("the app loaded in the browser doesn't need to register " +
             "the manifest for listening to the system messages");
       return;
     }
--- a/dom/moz.build
+++ b/dom/moz.build
@@ -2,17 +2,16 @@
 # vim: set filetype=python:
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 interfaces = [
     'base',
     'canvas',
-    'contacts',
     'core',
     'html',
     'events',
     'devicestorage',
     'settings',
     'stylesheets',
     'sidebar',
     'css',
--- a/dom/plugins/base/nsPluginInstanceOwner.cpp
+++ b/dom/plugins/base/nsPluginInstanceOwner.cpp
@@ -2330,18 +2330,17 @@ nsEventStatus nsPluginInstanceOwner::Pro
               break;
             case NS_KEY_UP:
               event.type = KeyRelease;
               break;
             }
 #endif
 
 #ifdef MOZ_WIDGET_QT
-          const WidgetKeyboardEvent& keyEvent =
-            static_cast<const WidgetKeyboardEvent&>(anEvent);
+          const WidgetKeyboardEvent& keyEvent = *anEvent.AsKeyboardEvent();
 
           memset( &event, 0, sizeof(event) );
           event.time = anEvent.time;
 
           QWidget* qWidget = static_cast<QWidget*>(widget->GetNativeData(NS_NATIVE_WINDOW));
 
           if (qWidget)
 #if defined(Q_WS_X11)
@@ -2492,18 +2491,17 @@ nsEventStatus nsPluginInstanceOwner::Pro
             }
             break;
           }
       }
       break;
 
     case NS_KEY_EVENT:
      {
-       const WidgetKeyboardEvent& keyEvent =
-         static_cast<const WidgetKeyboardEvent&>(anEvent);
+       const WidgetKeyboardEvent& keyEvent = *anEvent.AsKeyboardEvent();
        LOG("Firing NS_KEY_EVENT %d %d\n", keyEvent.keyCode, keyEvent.charCode);
        // pluginEvent is initialized by nsWindow::InitKeyEvent().
        ANPEvent* pluginEvent = reinterpret_cast<ANPEvent*>(keyEvent.pluginEvent);
        if (pluginEvent) {
          MOZ_ASSERT(pluginEvent->inSize == sizeof(ANPEvent));
          MOZ_ASSERT(pluginEvent->eventType == kKey_ANPEventType);
          mInstance->HandleEvent(pluginEvent, nullptr, NS_PLUGIN_CALL_SAFE_TO_REENTER_GECKO);
        }
--- a/dom/src/events/nsJSEventListener.cpp
+++ b/dom/src/events/nsJSEventListener.cpp
@@ -171,21 +171,19 @@ nsJSEventListener::HandleEvent(nsIDOMEve
 
     nsString errorMsg, file;
     EventOrString msgOrEvent;
     Optional<nsAString> fileName;
     Optional<uint32_t> lineNumber;
     Optional<uint32_t> columnNumber;
 
     NS_ENSURE_TRUE(aEvent, NS_ERROR_UNEXPECTED);
-    WidgetEvent* event = aEvent->GetInternalNSEvent();
-    if (event->message == NS_LOAD_ERROR &&
-        event->eventStructType == NS_SCRIPT_ERROR_EVENT) {
-      InternalScriptErrorEvent *scriptEvent =
-        static_cast<InternalScriptErrorEvent*>(event);
+    InternalScriptErrorEvent* scriptEvent =
+      aEvent->GetInternalNSEvent()->AsScriptErrorEvent();
+    if (scriptEvent && scriptEvent->message == NS_LOAD_ERROR) {
       errorMsg = scriptEvent->errorMsg;
       msgOrEvent.SetAsString() = static_cast<nsAString*>(&errorMsg);
 
       file = scriptEvent->fileName;
       fileName = &file;
 
       lineNumber.Construct();
       lineNumber.Value() = scriptEvent->lineNr;
--- a/dom/system/gonk/RILContentHelper.js
+++ b/dom/system/gonk/RILContentHelper.js
@@ -1767,30 +1767,29 @@ RILContentHelper.prototype = {
       this.fireRequestError(message.requestId, message.errorMsg);
       return;
     }
 
     let window = this._windowsMap[message.requestId];
     delete this._windowsMap[message.requestId];
     let contacts = message.contacts;
     let result = contacts.map(function(c) {
-      let contact = Cc["@mozilla.org/contact;1"].createInstance(Ci.nsIDOMContact);
       let prop = {name: [c.alphaId], tel: [{value: c.number}]};
 
       if (c.email) {
         prop.email = [{value: c.email}];
       }
 
       // ANR - Additional Number
       let anrLen = c.anr ? c.anr.length : 0;
       for (let i = 0; i < anrLen; i++) {
         prop.tel.push({value: c.anr[i]});
       }
 
-      contact.init(prop);
+      let contact = new window.mozContact(prop);
       contact.id = message.iccid + c.recordId;
       return contact;
     });
 
     this.fireRequestSuccess(message.requestId,
                             ObjectWrapper.wrap(result, window));
   },
 
new file mode 100644
--- /dev/null
+++ b/dom/webidl/Contacts.webidl
@@ -0,0 +1,170 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+[ChromeOnly, Constructor, JSImplementation="@mozilla.org/contactAddress;1"]
+interface ContactAddress {
+  attribute object?    type; // DOMString[]
+  attribute DOMString? streetAddress;
+  attribute DOMString? locality;
+  attribute DOMString? region;
+  attribute DOMString? postalCode;
+  attribute DOMString? countryName;
+  attribute boolean?   pref;
+
+  [ChromeOnly]
+  void initialize(optional sequence<DOMString>? type,
+                  optional DOMString streetAddress,
+                  optional DOMString locality,
+                  optional DOMString region,
+                  optional DOMString postalCode,
+                  optional DOMString countryName,
+                  optional boolean pref);
+};
+
+dictionary ContactAddressInit {
+  sequence<DOMString>? type;
+  DOMString?           streetAddress;
+  DOMString?           locality;
+  DOMString?           region;
+  DOMString?           postalCode;
+  DOMString?           countryName;
+  boolean?             pref;
+};
+
+
+[ChromeOnly, Constructor, JSImplementation="@mozilla.org/contactField;1"]
+interface ContactField {
+  attribute object?    type; // DOMString[]
+  attribute DOMString? value;
+  attribute boolean?   pref;
+
+  [ChromeOnly]
+  void initialize(optional sequence<DOMString>? type,
+                  optional DOMString value,
+                  optional boolean pref);
+};
+
+dictionary ContactFieldInit {
+  sequence<DOMString>? type;
+  DOMString?           value;
+  boolean?             pref;
+};
+
+
+[ChromeOnly, Constructor, JSImplementation="@mozilla.org/contactTelField;1"]
+interface ContactTelField : ContactField {
+  attribute DOMString? carrier;
+
+  [ChromeOnly]
+  void initialize(optional sequence<DOMString>? type,
+                  optional DOMString value,
+                  optional DOMString? carrier,
+                  optional boolean pref);
+};
+
+dictionary ContactTelFieldInit : ContactFieldInit {
+  DOMString? carrier;
+};
+
+
+dictionary ContactProperties {
+  Date?                          bday;
+  Date?                          anniversary;
+
+  DOMString?                     sex;
+  DOMString?                     genderIdentity;
+
+  sequence<Blob>?                photo;
+
+  sequence<ContactAddressInit>?  adr;
+
+  sequence<ContactFieldInit>?    email;
+  sequence<ContactFieldInit>?    url;
+  sequence<ContactFieldInit>?    impp;
+
+  sequence<ContactTelFieldInit>? tel;
+
+  sequence<DOMString>?           name;
+  sequence<DOMString>?           honorificPrefix;
+  sequence<DOMString>?           givenName;
+  sequence<DOMString>?           additionalName;
+  sequence<DOMString>?           familyName;
+  sequence<DOMString>?           honorificSuffix;
+  sequence<DOMString>?           nickname;
+  sequence<DOMString>?           category;
+  sequence<DOMString>?           org;
+  sequence<DOMString>?           jobTitle;
+  sequence<DOMString>?           note;
+  sequence<DOMString>?           key;
+};
+
+[Constructor(optional ContactProperties properties),
+ JSImplementation="@mozilla.org/contact;1"]
+interface mozContact {
+           attribute DOMString    id;
+  readonly attribute Date?        published;
+  readonly attribute Date?        updated;
+
+           attribute Date?        bday;
+           attribute Date?        anniversary;
+
+           attribute DOMString?   sex;
+           attribute DOMString?   genderIdentity;
+
+           attribute object?      photo;
+
+           attribute object?      adr;
+
+           attribute object?      email;
+           attribute object?      url;
+           attribute object?      impp;
+
+           attribute object?      tel;
+
+           attribute object?      name;
+           attribute object?      honorificPrefix;
+           attribute object?      givenName;
+           attribute object?      additionalName;
+           attribute object?      familyName;
+           attribute object?      honorificSuffix;
+           attribute object?      nickname;
+           attribute object?      category;
+           attribute object?      org;
+           attribute object?      jobTitle;
+           attribute object?      note;
+           attribute object?      key;
+
+  [ChromeOnly]
+  void setMetadata(DOMString id, Date? published, Date? updated);
+
+  jsonifier;
+};
+
+dictionary ContactFindSortOptions {
+  DOMString sortBy;                    // "givenName" or "familyName"
+  DOMString sortOrder = "ascending";   // e.g. "descending"
+};
+
+dictionary ContactFindOptions : ContactFindSortOptions {
+  DOMString      filterValue;  // e.g. "Tom"
+  DOMString      filterOp;     // e.g. "startsWith"
+  any            filterBy;     // e.g. ["givenName", "nickname"]
+  unsigned long  filterLimit = 0;
+};
+
+[NoInterfaceObject, NavigatorProperty="mozContacts",
+ JSImplementation="@mozilla.org/contactManager;1"]
+interface ContactManager : EventTarget {
+  DOMRequest find(optional ContactFindOptions options);
+  DOMCursor  getAll(optional ContactFindSortOptions options);
+  DOMRequest clear();
+  DOMRequest save(mozContact contact);
+  DOMRequest remove(mozContact contact);
+  DOMRequest getRevision();
+  DOMRequest getCount();
+
+  attribute  EventHandler oncontactchange;
+};
--- a/dom/webidl/moz.build
+++ b/dom/webidl/moz.build
@@ -54,16 +54,17 @@ WEBIDL_FILES = [
     'ChannelMergerNode.webidl',
     'ChannelSplitterNode.webidl',
     'CharacterData.webidl',
     'ChildNode.webidl',
     'ClipboardEvent.webidl',
     'CommandEvent.webidl',
     'Comment.webidl',
     'CompositionEvent.webidl',
+    'Contacts.webidl',
     'ConvolverNode.webidl',
     'Coordinates.webidl',
     'DOMCursor.webidl',
     'DOMError.webidl',
     'DOMException.webidl',
     'DOMImplementation.webidl',
     'DOMMMIError.webidl',
     'DOMParser.webidl',
--- a/editor/libeditor/base/nsEditor.cpp
+++ b/editor/libeditor/base/nsEditor.cpp
@@ -4804,17 +4804,18 @@ nsEditor::HandleKeyPressEvent(nsIDOMKeyE
 {
   // NOTE: When you change this method, you should also change:
   //   * editor/libeditor/text/tests/test_texteditor_keyevent_handling.html
   //   * editor/libeditor/html/tests/test_htmleditor_keyevent_handling.html
   //
   // And also when you add new key handling, you need to change the subclass's
   // HandleKeyPressEvent()'s switch statement.
 
-  WidgetKeyboardEvent* nativeKeyEvent = GetNativeKeyEvent(aKeyEvent);
+  WidgetKeyboardEvent* nativeKeyEvent =
+    aKeyEvent->GetInternalNSEvent()->AsKeyboardEvent();
   NS_ENSURE_TRUE(nativeKeyEvent, NS_ERROR_UNEXPECTED);
   NS_ASSERTION(nativeKeyEvent->message == NS_KEY_PRESS,
                "HandleKeyPressEvent gets non-keypress event");
 
   // if we are readonly or disabled, then do nothing.
   if (IsReadonly() || IsDisabled()) {
     // consume backspace for disabled and readonly textfields, to prevent
     // back in history, which could be confusing to users
@@ -5157,26 +5158,16 @@ nsEditor::IsModifiableNode(nsIDOMNode *a
 }
 
 bool
 nsEditor::IsModifiableNode(nsINode *aNode)
 {
   return true;
 }
 
-WidgetKeyboardEvent*
-nsEditor::GetNativeKeyEvent(nsIDOMKeyEvent* aDOMKeyEvent)
-{
-  NS_ENSURE_TRUE(aDOMKeyEvent, nullptr);
-  WidgetEvent* nativeEvent = aDOMKeyEvent->GetInternalNSEvent();
-  NS_ENSURE_TRUE(nativeEvent, nullptr);
-  NS_ENSURE_TRUE(nativeEvent->eventStructType == NS_KEY_EVENT, nullptr);
-  return static_cast<WidgetKeyboardEvent*>(nativeEvent);
-}
-
 already_AddRefed<nsIContent>
 nsEditor::GetFocusedContent()
 {
   nsCOMPtr<nsIDOMEventTarget> piTarget = GetDOMEventTarget();
   if (!piTarget) {
     return nullptr;
   }
 
--- a/editor/libeditor/base/nsEditor.h
+++ b/editor/libeditor/base/nsEditor.h
@@ -2,17 +2,16 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef __editor_h__
 #define __editor_h__
 
 #include "mozilla/Assertions.h"         // for MOZ_ASSERT, etc.
-#include "mozilla/EventForwards.h"      // for WidgetKeyboardEvent
 #include "mozilla/TypedEnum.h"          // for MOZ_BEGIN_ENUM_CLASS, etc.
 #include "nsAutoPtr.h"                  // for nsRefPtr
 #include "nsCOMArray.h"                 // for nsCOMArray
 #include "nsCOMPtr.h"                   // for already_AddRefed, nsCOMPtr
 #include "nsCycleCollectionParticipant.h"
 #include "nsEditProperty.h"             // for nsEditProperty, etc
 #include "nsIEditor.h"                  // for nsIEditor::EDirection, etc
 #include "nsIEditorIMESupport.h"        // for NS_DECL_NSIEDITORIMESUPPORT, etc
@@ -400,18 +399,16 @@ protected:
   // unregister and release our event listeners
   virtual void RemoveEventListeners();
 
   /**
    * Return true if spellchecking should be enabled for this editor.
    */
   bool GetDesiredSpellCheckState();
 
-  mozilla::WidgetKeyboardEvent* GetNativeKeyEvent(nsIDOMKeyEvent* aDOMKeyEvent);
-
   bool CanEnableSpellCheck()
   {
     // Check for password/readonly/disabled, which are not spellchecked
     // regardless of DOM. Also, check to see if spell check should be skipped or not.
     return !IsPasswordEditor() && !IsReadonly() && !IsDisabled() && !ShouldSkipSpellCheck();
   }
 
 public:
--- a/editor/libeditor/html/nsHTMLEditor.cpp
+++ b/editor/libeditor/html/nsHTMLEditor.cpp
@@ -586,17 +586,18 @@ nsHTMLEditor::HandleKeyPressEvent(nsIDOM
   //   * editor/libeditor/html/tests/test_htmleditor_keyevent_handling.html
 
   if (IsReadonly() || IsDisabled()) {
     // When we're not editable, the events are handled on nsEditor, so, we can
     // bypass nsPlaintextEditor.
     return nsEditor::HandleKeyPressEvent(aKeyEvent);
   }
 
-  WidgetKeyboardEvent* nativeKeyEvent = GetNativeKeyEvent(aKeyEvent);
+  WidgetKeyboardEvent* nativeKeyEvent =
+    aKeyEvent->GetInternalNSEvent()->AsKeyboardEvent();
   NS_ENSURE_TRUE(nativeKeyEvent, NS_ERROR_UNEXPECTED);
   NS_ASSERTION(nativeKeyEvent->message == NS_KEY_PRESS,
                "HandleKeyPressEvent gets non-keypress event");
 
   switch (nativeKeyEvent->keyCode) {
     case nsIDOMKeyEvent::DOM_VK_META:
     case nsIDOMKeyEvent::DOM_VK_WIN:
     case nsIDOMKeyEvent::DOM_VK_SHIFT:
--- a/editor/libeditor/text/nsPlaintextDataTransfer.cpp
+++ b/editor/libeditor/text/nsPlaintextDataTransfer.cpp