merge m-c to cedar
authorMike de Boer <mdeboer@mozilla.com>
Mon, 24 Oct 2016 17:30:51 +0200
changeset 428745 d6dc1f5435991946aa877bb05402d3de6922ed28
parent 428145 56149f041fc8bccdb6208119940e907fdcf5e041 (current diff)
parent 428736 c845bfd0accb7e0c29b41713255963b08006e701 (diff)
child 428746 471250cacc583496404db5341ecdd1500e112030
child 429193 217ff2dc022c72221ad8d7454803fde332c772c5
push id33412
push usermwein@mozilla.com
push dateMon, 24 Oct 2016 15:46:56 +0000
milestone52.0a1
merge m-c to cedar MozReview-Commit-ID: 9AshC7sXTwn
browser/components/extensions/extensions-browser.manifest
browser/components/extensions/jar.mn
browser/components/extensions/test/browser/browser.ini
devtools/client/animationinspector/test/browser_animation_timeline_pause_button.js
dom/browser-element/mochitest/browserElement_SetNFCFocus.js
dom/browser-element/mochitest/test_browserElement_inproc_SetNFCFocus.html
dom/browser-element/mochitest/test_browserElement_oop_SetNFCFocus.html
dom/mobilemessage/Assertions.cpp
dom/mobilemessage/Constants.cpp
dom/mobilemessage/Constants.h
dom/mobilemessage/DOMMobileMessageError.cpp
dom/mobilemessage/DOMMobileMessageError.h
dom/mobilemessage/DeletedMessageInfo.cpp
dom/mobilemessage/DeletedMessageInfo.h
dom/mobilemessage/MmsMessage.cpp
dom/mobilemessage/MmsMessage.h
dom/mobilemessage/MmsMessageInternal.cpp
dom/mobilemessage/MmsMessageInternal.h
dom/mobilemessage/MobileMessageCallback.cpp
dom/mobilemessage/MobileMessageCallback.h
dom/mobilemessage/MobileMessageCursorCallback.cpp
dom/mobilemessage/MobileMessageCursorCallback.h
dom/mobilemessage/MobileMessageManager.cpp
dom/mobilemessage/MobileMessageManager.h
dom/mobilemessage/MobileMessageService.cpp
dom/mobilemessage/MobileMessageService.h
dom/mobilemessage/MobileMessageThread.cpp
dom/mobilemessage/MobileMessageThread.h
dom/mobilemessage/MobileMessageThreadInternal.cpp
dom/mobilemessage/MobileMessageThreadInternal.h
dom/mobilemessage/SmsMessage.cpp
dom/mobilemessage/SmsMessage.h
dom/mobilemessage/SmsMessageInternal.cpp
dom/mobilemessage/SmsMessageInternal.h
dom/mobilemessage/Types.h
dom/mobilemessage/android/MobileMessageDatabaseService.cpp
dom/mobilemessage/android/MobileMessageDatabaseService.h
dom/mobilemessage/android/SmsManager.cpp
dom/mobilemessage/android/SmsManager.h
dom/mobilemessage/android/SmsService.cpp
dom/mobilemessage/android/SmsService.h
dom/mobilemessage/gonk/MmsPduHelper.jsm
dom/mobilemessage/gonk/MmsService.js
dom/mobilemessage/gonk/MmsService.manifest
dom/mobilemessage/gonk/MobileMessageDB.jsm
dom/mobilemessage/gonk/MobileMessageDatabaseService.js
dom/mobilemessage/gonk/MobileMessageDatabaseService.manifest
dom/mobilemessage/gonk/SmsSegmentHelper.jsm
dom/mobilemessage/gonk/SmsService.js
dom/mobilemessage/gonk/SmsService.manifest
dom/mobilemessage/gonk/WspPduHelper.jsm
dom/mobilemessage/gonk/mms_consts.js
dom/mobilemessage/gonk/wap_consts.js
dom/mobilemessage/interfaces/moz.build
dom/mobilemessage/interfaces/nsIDeletedMessageInfo.idl
dom/mobilemessage/interfaces/nsIGonkMobileMessageDatabaseService.idl
dom/mobilemessage/interfaces/nsIGonkSmsService.idl
dom/mobilemessage/interfaces/nsIMmsMessage.idl
dom/mobilemessage/interfaces/nsIMmsService.idl
dom/mobilemessage/interfaces/nsIMobileMessageCallback.idl
dom/mobilemessage/interfaces/nsIMobileMessageCursorCallback.idl
dom/mobilemessage/interfaces/nsIMobileMessageDatabaseService.idl
dom/mobilemessage/interfaces/nsIMobileMessageService.idl
dom/mobilemessage/interfaces/nsIMobileMessageThread.idl
dom/mobilemessage/interfaces/nsISmsMessage.idl
dom/mobilemessage/interfaces/nsISmsMessenger.idl
dom/mobilemessage/interfaces/nsISmsService.idl
dom/mobilemessage/interfaces/nsIWapPushApplication.idl
dom/mobilemessage/ipc/PMobileMessageCursor.ipdl
dom/mobilemessage/ipc/PSms.ipdl
dom/mobilemessage/ipc/PSmsRequest.ipdl
dom/mobilemessage/ipc/SmsChild.cpp
dom/mobilemessage/ipc/SmsChild.h
dom/mobilemessage/ipc/SmsIPCService.cpp
dom/mobilemessage/ipc/SmsIPCService.h
dom/mobilemessage/ipc/SmsParent.cpp
dom/mobilemessage/ipc/SmsParent.h
dom/mobilemessage/ipc/SmsTypes.ipdlh
dom/mobilemessage/moz.build
dom/mobilemessage/tests/marionette/head.js
dom/mobilemessage/tests/marionette/manifest.ini
dom/mobilemessage/tests/marionette/mmdb_head.js
dom/mobilemessage/tests/marionette/test_between_emulators.py
dom/mobilemessage/tests/marionette/test_bug814761.js
dom/mobilemessage/tests/marionette/test_decode_spanish_fallback.js
dom/mobilemessage/tests/marionette/test_emulator_loopback.js
dom/mobilemessage/tests/marionette/test_error_of_mms_manual_retrieval.js
dom/mobilemessage/tests/marionette/test_error_of_mms_send.js
dom/mobilemessage/tests/marionette/test_error_of_sms_send.js
dom/mobilemessage/tests/marionette/test_filter_date.js
dom/mobilemessage/tests/marionette/test_filter_mixed.js
dom/mobilemessage/tests/marionette/test_filter_number.js
dom/mobilemessage/tests/marionette/test_filter_read.js
dom/mobilemessage/tests/marionette/test_filter_received.js
dom/mobilemessage/tests/marionette/test_filter_sent.js
dom/mobilemessage/tests/marionette/test_filter_unread.js
dom/mobilemessage/tests/marionette/test_getmessage.js
dom/mobilemessage/tests/marionette/test_getmessage_notfound.js
dom/mobilemessage/tests/marionette/test_getmessages.js
dom/mobilemessage/tests/marionette/test_getsegmentinfofortext.js
dom/mobilemessage/tests/marionette/test_getthreads.js
dom/mobilemessage/tests/marionette/test_incoming.js
dom/mobilemessage/tests/marionette/test_incoming_delete.js
dom/mobilemessage/tests/marionette/test_incoming_max_segments.js
dom/mobilemessage/tests/marionette/test_invalid_address.js
dom/mobilemessage/tests/marionette/test_mark_msg_read.js
dom/mobilemessage/tests/marionette/test_mark_msg_read_error.js
dom/mobilemessage/tests/marionette/test_massive_incoming_delete.js
dom/mobilemessage/tests/marionette/test_message_classes.js
dom/mobilemessage/tests/marionette/test_mmdb_foreachmatchedmmsdeliveryinfo.js
dom/mobilemessage/tests/marionette/test_mmdb_full_storage.js
dom/mobilemessage/tests/marionette/test_mmdb_new.js
dom/mobilemessage/tests/marionette/test_mmdb_ports_in_cdma_wappush.js
dom/mobilemessage/tests/marionette/test_mmdb_setmessagedeliverybyid_sms.js
dom/mobilemessage/tests/marionette/test_mmdb_upgradeSchema_22.js
dom/mobilemessage/tests/marionette/test_mmdb_upgradeSchema_current_structure.js
dom/mobilemessage/tests/marionette/test_mmsmessage_attachments.js
dom/mobilemessage/tests/marionette/test_mobilemessage_dsds_default_service_id.js
dom/mobilemessage/tests/marionette/test_mt_sms_concatenation.js
dom/mobilemessage/tests/marionette/test_ondeleted_event.js
dom/mobilemessage/tests/marionette/test_outgoing.js
dom/mobilemessage/tests/marionette/test_outgoing_delete.js
dom/mobilemessage/tests/marionette/test_outgoing_max_segments.js
dom/mobilemessage/tests/marionette/test_outgoing_unstable_voice_connection.js
dom/mobilemessage/tests/marionette/test_phone_number_normalization.js
dom/mobilemessage/tests/marionette/test_replace_short_message_type.js
dom/mobilemessage/tests/marionette/test_segment_info.js
dom/mobilemessage/tests/marionette/test_smsc_address.js
dom/mobilemessage/tests/marionette/test_strict_7bit_encoding.js
dom/mobilemessage/tests/marionette/test_thread_subject.js
dom/mobilemessage/tests/marionette/test_update_gsm_nl_on_mcc_chanages.js
dom/mobilemessage/tests/marionette/test_update_thread_record_in_delete.js
dom/mobilemessage/tests/mochitest/chrome.ini
dom/mobilemessage/tests/mochitest/test_sms_basics.html
dom/mobilemessage/tests/xpcshell/header_helpers.js
dom/mobilemessage/tests/xpcshell/test_mms_pdu_helper.js
dom/mobilemessage/tests/xpcshell/test_mms_service.js
dom/mobilemessage/tests/xpcshell/test_sms_segment_helper.js
dom/mobilemessage/tests/xpcshell/test_smsservice_createsmsmessage.js
dom/mobilemessage/tests/xpcshell/test_wsp_pdu_helper.js
dom/mobilemessage/tests/xpcshell/test_wsp_pdu_helper_header.js
dom/mobilemessage/tests/xpcshell/test_wsp_pdu_helper_numeric.js
dom/mobilemessage/tests/xpcshell/test_wsp_pdu_helper_parameter.js
dom/mobilemessage/tests/xpcshell/test_wsp_pdu_helper_text.js
dom/mobilemessage/tests/xpcshell/xpcshell.ini
dom/nfc/MozIsoDepTech.cpp
dom/nfc/MozIsoDepTech.h
dom/nfc/MozNDEFRecord.cpp
dom/nfc/MozNDEFRecord.h
dom/nfc/MozNfcATech.cpp
dom/nfc/MozNfcATech.h
dom/nfc/NfcContentHelper.js
dom/nfc/NfcContentHelper.manifest
dom/nfc/TagUtils.cpp
dom/nfc/TagUtils.h
dom/nfc/gonk/Nfc.js
dom/nfc/gonk/Nfc.manifest
dom/nfc/gonk/NfcMessageHandler.cpp
dom/nfc/gonk/NfcMessageHandler.h
dom/nfc/gonk/NfcOptions.h
dom/nfc/gonk/NfcService.cpp
dom/nfc/gonk/NfcService.h
dom/nfc/gonk/nfc_consts.js
dom/nfc/gonk/nsINfcService.idl
dom/nfc/gonk/nsINfcSystemMessage.idl
dom/nfc/messages/HCIEventTransactionSystemMessage.manifest
dom/nfc/messages/HCIEventTransactionSystemMessageConfigurator.js
dom/nfc/moz.build
dom/nfc/nsINfcContentHelper.idl
dom/nfc/nsNfc.js
dom/nfc/nsNfc.manifest
dom/nfc/tests/marionette/head.js
dom/nfc/tests/marionette/manifest.ini
dom/nfc/tests/marionette/test_ndef.js
dom/nfc/tests/marionette/test_nfc_checkP2PRegistration.js
dom/nfc/tests/marionette/test_nfc_enabled.js
dom/nfc/tests/marionette/test_nfc_error_messages.js
dom/nfc/tests/marionette/test_nfc_manager_tech_discovered.js
dom/nfc/tests/marionette/test_nfc_manager_tech_discovered_ndef.js
dom/nfc/tests/marionette/test_nfc_manager_tech_lost.js
dom/nfc/tests/marionette/test_nfc_peer.js
dom/nfc/tests/marionette/test_nfc_peer_sendFile.js
dom/nfc/tests/marionette/test_nfc_peer_sendndef.js
dom/nfc/tests/marionette/test_nfc_read_tag.js
dom/nfc/tests/marionette/test_nfc_tag_found.js
dom/nfc/tests/marionette/test_nfc_tag_lost.js
dom/nfc/tests/unit/header_helpers.js
dom/nfc/tests/unit/test_HCIEventTransactionSystemMessageConfigurator.js
dom/nfc/tests/unit/test_Nfc.js
dom/nfc/tests/unit/xpcshell.ini
dom/permission/tests/mochitest-websms.ini
dom/permission/tests/test_sms.html
dom/wappush/gonk/CpPduHelper.jsm
dom/wappush/gonk/SiPduHelper.jsm
dom/wappush/gonk/SlPduHelper.jsm
dom/wappush/gonk/WapPushManager.js
dom/wappush/gonk/WbxmlPduHelper.jsm
dom/wappush/moz.build
dom/wappush/tests/header_helpers.js
dom/wappush/tests/test_cp_pdu_helper.js
dom/wappush/tests/test_si_pdu_helper.js
dom/wappush/tests/test_sl_pdu_helper.js
dom/wappush/tests/xpcshell.ini
dom/webidl/DOMMobileMessageError.webidl
dom/webidl/MmsMessage.webidl
dom/webidl/MobileMessageThread.webidl
dom/webidl/MozIsoDepTech.webidl
dom/webidl/MozMessageDeletedEvent.webidl
dom/webidl/MozMmsEvent.webidl
dom/webidl/MozMobileMessageManager.webidl
dom/webidl/MozNDEFRecord.webidl
dom/webidl/MozNFC.webidl
dom/webidl/MozNFCPeer.webidl
dom/webidl/MozNFCPeerEvent.webidl
dom/webidl/MozNFCTag.webidl
dom/webidl/MozNFCTagEvent.webidl
dom/webidl/MozNfcATech.webidl
dom/webidl/MozSmsEvent.webidl
dom/webidl/NfcOptions.webidl
dom/webidl/SmsMessage.webidl
ipc/nfc/NfcConnector.cpp
ipc/nfc/NfcConnector.h
ipc/nfc/moz.build
js/src/threading/posix/Mutex.cpp
js/src/threading/windows/Mutex.cpp
layout/reftests/webkit-box/webkit-box-anon-flex-items-1.html
layout/style/ServoBindingHelpers.h
media/libcubeb/bug1308418-mutex-copy-ctor.patch
media/libcubeb/wasapi-drift.patch
media/libcubeb/wasapi-stereo-mic.patch
mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoSmsManager.java
mobile/android/geckoview/src/main/java/org/mozilla/gecko/SmsManager.java
security/manager/pki/resources/content/viewCertDetails.js
taskcluster/ci/b2g-device/kind.yml
taskcluster/ci/build/mulet.yml
taskcluster/scripts/builder/build-emulator-x86.sh
taskcluster/scripts/builder/build-emulator.sh
taskcluster/scripts/builder/build-mulet-haz-linux.sh
taskcluster/scripts/builder/build-mulet-linux.sh
taskcluster/scripts/builder/build-simulator.sh
taskcluster/scripts/builder/gaia_props.py
taskcluster/scripts/builder/pull-gaia.sh
taskcluster/scripts/phone-builder/build-dolphin.sh
taskcluster/scripts/phone-builder/build-phone-ota.sh
taskcluster/scripts/phone-builder/build-phone.sh
taskcluster/scripts/phone-builder/post-build.sh
taskcluster/scripts/phone-builder/pre-build.sh
taskcluster/taskgraph/transforms/job/mulet.py
taskcluster/taskgraph/transforms/job/phone_builder.py
testing/docker/b2g-build/Dockerfile
testing/docker/b2g-build/VERSION
testing/docker/b2g-build/bin/repository-url.py
testing/docker/b2g-build/releng.repo
testing/docker/builder/Dockerfile
testing/docker/builder/REGISTRY
testing/docker/builder/VERSION
testing/docker/builder/bin/checkout-gecko
testing/docker/builder/git.env
testing/docker/builder/mulet.env
testing/docker/phone-builder/Dockerfile
testing/docker/phone-builder/bin/validate_task.py
testing/docker/phone-builder/hgrc
testing/docker/phone-builder/tc-vcs-config.yml
testing/docker/phone-builder/tests/invalid_base_repo.yml
testing/docker/phone-builder/tests/invalid_head_repo.yml
testing/docker/phone-builder/tests/public.yml
testing/docker/phone-builder/tests/test_validation.py
testing/docker/phone-builder/tests/valid.yml
testing/web-platform/meta/html/browsers/windows/noreferrer-window-name.html.ini
--- a/.eslintignore
+++ b/.eslintignore
@@ -1,11 +1,8 @@
-# Allow linting of .eslintrc.js files.
-!**/.eslintrc.js
-
 # Always ignore node_modules.
 **/node_modules/**/*.*
 
 # Exclude expected objdirs.
 obj*/**
 
 # We ignore all these directories by default, until we get them enabled.
 # If you are enabling a directory, please add directory specific exclusions
--- a/.taskcluster.yml
+++ b/.taskcluster.yml
@@ -70,28 +70,28 @@ tasks:
       payload:
         env:
           # checkout-gecko uses these to check out the source; the inputs
           # to `mach taskgraph decision` are all on the command line.
           GECKO_BASE_REPOSITORY: 'https://hg.mozilla.org/mozilla-unified'
           GECKO_HEAD_REPOSITORY: '{{{url}}}'
           GECKO_HEAD_REF: '{{revision}}'
           GECKO_HEAD_REV: '{{revision}}'
+          HG_STORE_PATH: /home/worker/checkouts/hg-store
 
         cache:
-          level-{{level}}-hg-shared: /home/worker/hg-shared
           level-{{level}}-checkouts: /home/worker/checkouts
 
         features:
           taskclusterProxy: true
           chainOfTrust: true
 
         # Note: This task is built server side without the context or tooling that
         # exist in tree so we must hard code the version
-        image: 'taskcluster/decision:0.1.6'
+        image: 'taskcluster/decision:0.1.7'
 
         maxRunTime: 1800
 
         # TODO use mozilla-unified for the base repository once the tc-vcs
         # tar.gz archives are created or tc-vcs isn't being used.
         command:
           - /home/worker/bin/run-task
           - '--vcs-checkout=/home/worker/checkouts/gecko'
--- a/accessible/generic/ImageAccessible.cpp
+++ b/accessible/generic/ImageAccessible.cpp
@@ -133,16 +133,18 @@ ImageAccessible::DoAction(uint8_t aIndex
 
   nsIDocument* document = mContent->OwnerDoc();
   nsCOMPtr<nsPIDOMWindowOuter> piWindow = document->GetWindow();
   if (!piWindow)
     return false;
 
   nsCOMPtr<nsPIDOMWindowOuter> tmp;
   return NS_SUCCEEDED(piWindow->Open(spec, EmptyString(), EmptyString(),
+                                     /* aLoadInfo = */ nullptr,
+                                     /* aForceNoOpener = */ false,
                                      getter_AddRefs(tmp)));
 }
 
 ////////////////////////////////////////////////////////////////////////////////
 // ImageAccessible
 
 nsIntPoint
 ImageAccessible::Position(uint32_t aCoordType)
--- a/addon-sdk/moz.build
+++ b/addon-sdk/moz.build
@@ -1,27 +1,24 @@
-# AUTOMATICALLY GENERATED FROM mozbuild.template AND mach.  DO NOT EDIT.
-# 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/.
-
 # -*- Mode: python; 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/.
 
 # Makefile.in uses a misc target through test_addons_TARGET.
 HAS_MISC_RULE = True
 
 BROWSER_CHROME_MANIFESTS += ['test/browser.ini']
 JETPACK_PACKAGE_MANIFESTS += ['source/test/jetpack-package.ini',
                               'source/test/leak/jetpack-package.ini']
 JETPACK_ADDON_MANIFESTS += ['source/test/addons/jetpack-addon.ini']
 
+DIRS += ['source/test/fixtures']
+
 addons = [
     'addon-manager',
     'author-email',
     'child_process',
     'chrome',
     'content-permissions',
     'content-script-messages-latency',
     'contributors',
--- a/addon-sdk/source/lib/toolkit/loader.js
+++ b/addon-sdk/source/lib/toolkit/loader.js
@@ -23,22 +23,29 @@ module.metadata = {
   "stability": "unstable"
 };
 
 const { classes: Cc, Constructor: CC, interfaces: Ci, utils: Cu,
         results: Cr, manager: Cm } = Components;
 const systemPrincipal = CC('@mozilla.org/systemprincipal;1', 'nsIPrincipal')();
 const { loadSubScript } = Cc['@mozilla.org/moz/jssubscript-loader;1'].
                      getService(Ci.mozIJSSubScriptLoader);
-const { notifyObservers } = Cc['@mozilla.org/observer-service;1'].
+const { addObserver, notifyObservers } = Cc['@mozilla.org/observer-service;1'].
                         getService(Ci.nsIObserverService);
 const { XPCOMUtils } = Cu.import("resource://gre/modules/XPCOMUtils.jsm", {});
 const { NetUtil } = Cu.import("resource://gre/modules/NetUtil.jsm", {});
 const { join: pathJoin, normalize, dirname } = Cu.import("resource://gre/modules/osfile/ospath_unix.jsm");
 
+XPCOMUtils.defineLazyServiceGetter(this, "resProto",
+                                   "@mozilla.org/network/protocol;1?name=resource",
+                                   "nsIResProtocolHandler");
+XPCOMUtils.defineLazyServiceGetter(this, "zipCache",
+                                   "@mozilla.org/libjar/zip-reader-cache;1",
+                                   "nsIZipReaderCache");
+
 XPCOMUtils.defineLazyGetter(this, "XulApp", () => {
   let xulappURI = module.uri.replace("toolkit/loader.js",
                                      "sdk/system/xul-app.jsm");
   return Cu.import(xulappURI, {});
 });
 
 // Define some shortcuts.
 const bind = Function.call.bind(Function.bind);
@@ -197,24 +204,150 @@ function serializeStack(frames) {
            frame.fileName + ":" +
            frame.lineNumber + ":" +
            frame.columnNumber + "\n" +
            stack;
   }, "");
 }
 Loader.serializeStack = serializeStack;
 
+class DefaultMap extends Map {
+  constructor(createItem, items = undefined) {
+    super(items);
+
+    this.createItem = createItem;
+  }
+
+  get(key) {
+    if (!this.has(key)) {
+      this.set(key, this.createItem(key));
+    }
+
+    return super.get(key);
+  }
+}
+
+const urlCache = {
+  /**
+   * Returns a list of fully-qualified URLs for entries within the zip
+   * file at the given URI which are either directories or files with a
+   * .js or .json extension.
+   *
+   * @param {nsIJARURI} uri
+   * @param {string} baseURL
+   *        The original base URL, prior to resolution.
+   *
+   * @returns {Set<string>}
+   */
+  getZipFileContents(uri, baseURL) {
+    // Make sure the path has a trailing slash, and strip off the leading
+    // slash, so that we can easily check whether it is a path prefix.
+    let basePath = addTrailingSlash(uri.JAREntry).slice(1);
+    let file = uri.JARFile.QueryInterface(Ci.nsIFileURL).file;
+
+    let enumerator = zipCache.getZip(file).findEntries("(*.js|*.json|*/)");
+
+    let results = new Set();
+    for (let entry of XPCOMUtils.IterStringEnumerator(enumerator)) {
+      if (entry.startsWith(basePath)) {
+        let path = entry.slice(basePath.length);
+
+        results.add(baseURL + path);
+      }
+    }
+
+    return results;
+  },
+
+  zipContentsCache: new DefaultMap(baseURL => {
+    let uri = NetUtil.newURI(baseURL);
+
+    if (baseURL.startsWith("resource:")) {
+      uri = NetUtil.newURI(resProto.resolveURI(uri));
+    }
+
+    if (uri instanceof Ci.nsIJARURI) {
+      return urlCache.getZipFileContents(uri, baseURL);
+    }
+
+    return null;
+  }),
+
+  filesCache: new DefaultMap(url => {
+    try {
+      let uri = NetUtil.newURI(url).QueryInterface(Ci.nsIFileURL);
+
+      return uri.file.exists();
+    } catch (e) {
+      return false;
+    }
+  }),
+
+  QueryInterface: XPCOMUtils.generateQI([Ci.nsISupportsWeakReference]),
+
+  observe() {
+    // Clear any module resolution caches when the startup cache is flushed,
+    // since it probably means we're loading new copies of extensions.
+    this.zipContentsCache.clear();
+    this.filesCache.clear();
+  },
+
+  /**
+   * Returns the base URL for the given URL, if one can be determined. For
+   * a resource: URL, this is the root of the resource package. For a jar:
+   * URL, it is the root of the JAR file. Otherwise, null is returned.
+   *
+   * @param {string} url
+   * @returns {string?}
+   */
+  getBaseURL(url) {
+    // By using simple string matching for the common case of resource: URLs
+    // backed by jar: URLs, we can avoid creating any nsIURI objects for the
+    // common case where the JAR contents are already cached.
+    if (url.startsWith("resource://")) {
+      return /^resource:\/\/[^\/]+\//.exec(url)[0];
+    }
+
+    let uri = NetUtil.newURI(url);
+    if (uri instanceof Ci.nsIJARURI) {
+      return `jar:${uri.JARFile.spec}!/`;
+    }
+
+    return null;
+  },
+
+  /**
+   * Returns true if the target of the given URL exists as a local file,
+   * or as an entry in a local zip file.
+   *
+   * @param {string} url
+   * @returns {boolean}
+   */
+  exists(url) {
+    if (!/\.(?:js|json)$/.test(url)) {
+      url = addTrailingSlash(url);
+    }
+
+    let baseURL = this.getBaseURL(url);
+    let scripts = baseURL && this.zipContentsCache.get(baseURL);
+    if (scripts) {
+      return scripts.has(url);
+    }
+
+    return this.filesCache.get(url);
+  },
+}
+addObserver(urlCache, "startupcache-invalidate", true);
+
 function readURI(uri) {
   let nsURI = NetUtil.newURI(uri);
   if (nsURI.scheme == "resource") {
     // Resolve to a real URI, this will catch any obvious bad paths without
     // logging assertions in debug builds, see bug 1135219
-    let proto = Cc["@mozilla.org/network/protocol;1?name=resource"].
-                getService(Ci.nsIResProtocolHandler);
-    uri = proto.resolveURI(nsURI);
+    uri = resProto.resolveURI(nsURI);
   }
 
   let stream = NetUtil.newChannel({
     uri: NetUtil.newURI(uri, 'UTF-8'),
     loadUsingSystemPrincipal: true}
   ).open2();
   let count = stream.available();
   let data = NetUtil.readInputStreamToString(stream, count, {
@@ -222,27 +355,25 @@ function readURI(uri) {
   });
 
   stream.close();
 
   return data;
 }
 
 // Combines all arguments into a resolved, normalized path
-function join(...paths) {
-  let joined = pathJoin(...paths);
-  let resolved = normalize(joined);
+function join(base, ...paths) {
+  // If this is an absolute URL, we need to normalize only the path portion,
+  // or we wind up stripping too many slashes and producing invalid URLs.
+  let match = /^((?:resource|file|chrome)\:\/\/[^\/]*|jar:[^!]+!)(.*)/.exec(base);
+  if (match) {
+    return match[1] + normalize(pathJoin(match[2], ...paths));
+  }
 
-  // OS.File `normalize` strips out any additional slashes breaking URIs like
-  // `resource://`, `resource:///`, `chrome://` or `file:///`, so we work
-  // around this putting back the slashes originally given, for such schemes.
-  let re = /^(resource|file|chrome)(\:\/{1,3})([^\/])/;
-  let matches = joined.match(re);
-
-  return resolved.replace(re, (...args) => args[1] + matches[2] + args[3]);
+  return normalize(pathJoin(base, ...paths));
 }
 Loader.join = join;
 
 // Function takes set of options and returns a JS sandbox. Function may be
 // passed set of options:
 //  - `name`: A string value which identifies the sandbox in about:memory. Will
 //    throw exception if omitted.
 // - `principal`: String URI or `nsIPrincipal` for the sandbox. Defaults to
@@ -455,141 +586,120 @@ const resolve = iced(function resolve(id
 
   return resolved;
 });
 Loader.resolve = resolve;
 
 // Attempts to load `path` and then `path.js`
 // Returns `path` with valid file, or `undefined` otherwise
 function resolveAsFile(path) {
-  let found;
+  // Append '.js' to path name unless it's another support filetype
+  path = normalizeExt(path);
+  if (urlCache.exists(path)) {
+    return path;
+  }
 
-  // As per node's loader spec,
-  // we first should try and load 'path' (with no extension)
-  // before trying 'path.js'. We will not support this feature
-  // due to performance, but may add it if necessary for adoption.
-  try {
-    // Append '.js' to path name unless it's another support filetype
-    path = normalizeExt(path);
-    readURI(path);
-    found = path;
-  } catch (e) {}
-
-  return found;
+  return null;
 }
 
 // Attempts to load `path/package.json`'s `main` entry,
 // followed by `path/index.js`, or `undefined` otherwise
 function resolveAsDirectory(path) {
   try {
     // If `path/package.json` exists, parse the `main` entry
     // and attempt to load that
-    let main = getManifestMain(JSON.parse(readURI(path + '/package.json')));
-    if (main != null) {
-      let tmpPath = join(path, main);
-      let found = resolveAsFile(tmpPath);
-      if (found)
+    let manifestPath = addTrailingSlash(path) + 'package.json';
+
+    let main = (urlCache.exists(manifestPath) &&
+                getManifestMain(JSON.parse(readURI(manifestPath))));
+    if (main) {
+      let found = resolveAsFile(join(path, main));
+      if (found) {
         return found
+      }
     }
   } catch (e) {}
 
-  try {
-    let tmpPath = path + '/index.js';
-    readURI(tmpPath);
-    return tmpPath;
-  } catch (e) {}
-
-  return null;
+  return resolveAsFile(addTrailingSlash(path) + 'index.js');
 }
 
 function resolveRelative(rootURI, modulesDir, id) {
   let fullId = join(rootURI, modulesDir, id);
-  let resolvedPath;
 
-  if ((resolvedPath = resolveAsFile(fullId)))
+  let resolvedPath = (resolveAsFile(fullId) ||
+                      resolveAsDirectory(fullId));
+  if (resolvedPath) {
     return stripBase(rootURI, resolvedPath);
-
-  if ((resolvedPath = resolveAsDirectory(fullId)))
-    return stripBase(rootURI, resolvedPath);
+  }
 
   return null;
 }
 
 // From `resolve` module
 // https://github.com/substack/node-resolve/blob/master/lib/node-modules-paths.js
-function* getNodeModulePaths(start) {
-  // Configurable in node -- do we need this to be configurable?
+function* getNodeModulePaths(rootURI, start) {
   let moduleDir = 'node_modules';
 
   let parts = start.split('/');
   while (parts.length) {
     let leaf = parts.pop();
-    if (leaf !== moduleDir)
-      yield join(...parts, leaf, moduleDir);
+    let path = join(...parts, leaf, moduleDir);
+    if (leaf !== moduleDir && urlCache.exists(join(rootURI, path))) {
+      yield path;
+    }
   }
 
-  yield moduleDir;
+  if (urlCache.exists(join(rootURI, moduleDir))) {
+    yield moduleDir;
+  }
 }
 
 // Node-style module lookup
 // Takes an id and path and attempts to load a file using node's resolving
 // algorithm.
 // `id` should already be resolved relatively at this point.
 // http://nodejs.org/api/modules.html#modules_all_together
 const nodeResolve = iced(function nodeResolve(id, requirer, { rootURI }) {
   // Resolve again
   id = Loader.resolve(id, requirer);
 
   // If this is already an absolute URI then there is no resolution to do
-  if (isAbsoluteURI(id))
+  if (isAbsoluteURI(id)) {
     return null;
+  }
 
   // we assume that extensions are correct, i.e., a directory doesnt't have '.js'
   // and a js file isn't named 'file.json.js'
   let resolvedPath;
 
-  if ((resolvedPath = resolveRelative(rootURI, "", id)))
+  if ((resolvedPath = resolveRelative(rootURI, "", id))) {
     return resolvedPath;
+  }
 
   // If the requirer is an absolute URI then the node module resolution below
   // won't work correctly as we prefix everything with rootURI
-  if (isAbsoluteURI(requirer))
+  if (isAbsoluteURI(requirer)) {
     return null;
+  }
 
   // If manifest has dependencies, attempt to look up node modules
   // in the `dependencies` list
-  for (let modulesDir of getNodeModulePaths(dirname(requirer))) {
-    if ((resolvedPath = resolveRelative(rootURI, modulesDir, id)))
+  for (let modulesDir of getNodeModulePaths(rootURI, dirname(requirer))) {
+    if ((resolvedPath = resolveRelative(rootURI, modulesDir, id))) {
       return resolvedPath;
+    }
   }
 
   // We would not find lookup for things like `sdk/tabs`, as that's part of
   // the alias mapping. If during `generateMap`, the runtime lookup resolves
   // with `resolveURI` -- if during runtime, then `resolve` will throw.
   return null;
 });
 
-// String (`${rootURI}:${requirer}:${id}`) -> resolvedPath
-Loader.nodeResolverCache = new Map();
-
-const nodeResolveWithCache = iced(function cacheNodeResolutions(id, requirer, { rootURI }) {
-  // Compute the cache key based on current arguments.
-  let cacheKey = `${rootURI || ""}:${requirer}:${id}`;
-
-  // Try to get the result from the cache.
-  if (Loader.nodeResolverCache.has(cacheKey)) {
-    return Loader.nodeResolverCache.get(cacheKey);
-  }
-
-  // Resolve and cache if it is not in the cache yet.
-  let result = nodeResolve(id, requirer, { rootURI });
-  Loader.nodeResolverCache.set(cacheKey, result);
-  return result;
-});
-Loader.nodeResolve = nodeResolveWithCache;
+Loader.nodeResolve = nodeResolve;
 
 function addTrailingSlash(path) {
   return path.replace(/\/*$/, "/");
 }
 
 const resolveURI = iced(function resolveURI(id, mapping) {
   // Do not resolve if already a resource URI
   if (isAbsoluteURI(id))
@@ -822,19 +932,16 @@ const Module = iced(function Module(id, 
     uri: { value: uri }
   });
 });
 Loader.Module = Module;
 
 // Takes `loader`, and unload `reason` string and notifies all observers that
 // they should cleanup after them-self.
 const unload = iced(function unload(loader, reason) {
-  // Clear the nodeResolverCache when the loader is unloaded.
-  Loader.nodeResolverCache.clear();
-
   // subject is a unique object created per loader instance.
   // This allows any code to cleanup on loader unload regardless of how
   // it was loaded. To handle unload for specific loader subject may be
   // asserted against loader.destructor or require('@loader/unload')
   // Note: We don not destroy loader's module cache or sandboxes map as
   // some modules may do cleanup in subsequent turns of event loop. Destroying
   // cache may cause module identity problems in such cases.
   let subject = { wrappedJSObject: loader.destructor };
new file mode 100644
--- /dev/null
+++ b/addon-sdk/source/test/fixtures/create_xpi.py
@@ -0,0 +1,15 @@
+# -*- Mode: python; 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/.
+
+from os.path import abspath
+
+# TODO replace this script with a direct Python action invocation
+from mozbuild.action.zip import main as create_zip
+
+def main(output, input_dir):
+    output.close()
+
+    return create_zip(['-C', input_dir, abspath(output.name), '**'])
new file mode 100644
--- /dev/null
+++ b/addon-sdk/source/test/fixtures/moz.build
@@ -0,0 +1,22 @@
+# -*- Mode: python; 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/.
+
+fixtures = [
+    'native-addon-test',
+    'native-overrides-test',
+]
+
+output_dir = OBJDIR_FILES._tests.testing.mochitest['jetpack-package']['addon-sdk'].source.test.fixtures
+
+for fixture in fixtures:
+    xpi = '%s.xpi' % fixture
+
+    GENERATED_FILES += [xpi]
+    f = GENERATED_FILES[xpi]
+    f.script = 'create_xpi.py'
+    f.inputs = [fixture]
+
+    output_dir += ['!%s' % xpi]
--- a/addon-sdk/source/test/jetpack-package.ini
+++ b/addon-sdk/source/test/jetpack-package.ini
@@ -1,32 +1,37 @@
 [DEFAULT]
 support-files =
   buffers/**
   commonjs-test-adapter/**
   context-menu/**
   event/**
   fixtures.js
   fixtures/**
+  fixtures/native-addon-test.xpi
+  fixtures/native-overrides-test.xpi
   framescript-manager/**
   framescript-util/**
   lib/**
   loader/**
   modules/**
   page-mod/**
   path/**
   private-browsing/**
   querystring/**
   sidebar/**
   tabs/**
   test-context-menu.html
   traits/**
   util.js
   windows/**
   zip/**
+generated-files =
+  fixtures/native-addon-test.xpi
+  fixtures/native-overrides-test.xpi
 
 [test-addon-bootstrap.js]
 [test-addon-extras.js]
 [test-addon-installer.js]
 [test-addon-window.js]
 [test-api-utils.js]
 [test-array.js]
 [test-base64.js]
--- a/addon-sdk/source/test/test-loader.js
+++ b/addon-sdk/source/test/test-loader.js
@@ -30,18 +30,18 @@ exports['test resolve'] = function (asse
   assert.equal(resolve('./utils/file.js', './'), './utils/file.js');
   assert.equal(resolve('./utils/file.js', './index.js'), './utils/file.js');
 
   assert.equal(resolve('../utils/./file.js', cuddlefish_id), 'sdk/utils/file.js');
   assert.equal(resolve('../utils/file.js', cuddlefish_id), 'sdk/utils/file.js');
   assert.equal(resolve('./utils/file.js', cuddlefish_id), 'sdk/loader/utils/file.js');
 
   assert.equal(resolve('..//index.js', './dir/c.js'), './index.js');
-  assert.equal(resolve('../../gre/modules/XPCOMUtils.jsm', 'resource://thing/utils/file.js'), 'resource://gre/modules/XPCOMUtils.jsm');
-  assert.equal(resolve('../../gre/modules/XPCOMUtils.jsm', 'chrome://thing/utils/file.js'), 'chrome://gre/modules/XPCOMUtils.jsm');
+  assert.equal(resolve('../modules/XPCOMUtils.jsm', 'resource://gre/utils/file.js'), 'resource://gre/modules/XPCOMUtils.jsm');
+  assert.equal(resolve('../modules/XPCOMUtils.jsm', 'chrome://gre/utils/file.js'), 'chrome://gre/modules/XPCOMUtils.jsm');
   assert.equal(resolve('../../a/b/c.json', 'file:///thing/utils/file.js'), 'file:///a/b/c.json');
 
   // Does not change absolute paths
   assert.equal(resolve('resource://gre/modules/file.js', './dir/b.js'),
     'resource://gre/modules/file.js');
   assert.equal(resolve('file:///gre/modules/file.js', './dir/b.js'),
     'file:///gre/modules/file.js');
   assert.equal(resolve('/root.js', './dir/b.js'),
--- a/addon-sdk/source/test/test-native-loader.js
+++ b/addon-sdk/source/test/test-native-loader.js
@@ -3,77 +3,318 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 'use strict';
 
 var {
   Loader, main, unload, parseStack, resolve, nodeResolve
 } = require('toolkit/loader');
 var { readURI } = require('sdk/net/url');
 var { all } = require('sdk/core/promise');
+var { before, after } = require('sdk/test/utils');
 var testOptions = require('@test/options');
 
 var root = module.uri.substr(0, module.uri.lastIndexOf('/'))
 // The following adds Debugger constructor to the global namespace.
-const { Cu } = require('chrome');
+const { Cc, Ci, Cu } = require('chrome');
 const { addDebuggerToGlobal } = Cu.import('resource://gre/modules/jsdebugger.jsm', {});
 addDebuggerToGlobal(this);
 
+const { NetUtil } = Cu.import('resource://gre/modules/NetUtil.jsm', {});
 
-exports['test nodeResolve'] = function (assert) {
-  let rootURI = root + '/fixtures/native-addon-test/';
-  let manifest = {};
-  manifest.dependencies = {};
+const resProto = Cc["@mozilla.org/network/protocol;1?name=resource"]
+        .getService(Ci.nsIResProtocolHandler);
+
+const fileRoot = resProto.resolveURI(NetUtil.newURI(root));
+
+let variants = [
+  {
+    description: "unpacked resource:",
+    getRootURI(fixture) {
+      return `${root}/fixtures/${fixture}/`;
+    },
+  },
+  {
+    description: "unpacked file:",
+    getRootURI(fixture) {
+      return `${fileRoot}/fixtures/${fixture}/`;
+    },
+  },
+  {
+    description: "packed resource:",
+    getRootURI(fixture) {
+      return `resource://${fixture}/`;
+    },
+  },
+  {
+    description: "packed jar:",
+    getRootURI(fixture) {
+      return `jar:${fileRoot}/fixtures/${fixture}.xpi!/`;
+    },
+  },
+];
 
-  // Handles extensions
-  resolveTest('../package.json', './dir/c.js', './package.json');
-  resolveTest('../dir/b.js', './dir/c.js', './dir/b.js');
+let fixtures = [
+  'native-addon-test',
+  'native-overrides-test',
+];
+
+for (let variant of variants) {
+  exports[`test nodeResolve (${variant.description})`] = function (assert) {
+    let rootURI = variant.getRootURI('native-addon-test');
+    let manifest = {};
+    manifest.dependencies = {};
+
+    // Handles extensions
+    resolveTest('../package.json', './dir/c.js', './package.json');
+    resolveTest('../dir/b.js', './dir/c.js', './dir/b.js');
+
+    resolveTest('./dir/b', './index.js', './dir/b.js');
+    resolveTest('../index', './dir/b.js', './index.js');
+    resolveTest('../', './dir/b.js', './index.js');
+    resolveTest('./dir/a', './index.js', './dir/a.js', 'Precedence dir/a.js over dir/a/');
+    resolveTest('../utils', './dir/a.js', './utils/index.js', 'Requiring a directory defaults to dir/index.js');
+    resolveTest('../newmodule', './dir/c.js', './newmodule/lib/file.js', 'Uses package.json main in dir to load appropriate "main"');
+    resolveTest('test-math', './utils/index.js', './node_modules/test-math/index.js',
+      'Dependencies default to their index.js');
+    resolveTest('test-custom-main', './utils/index.js', './node_modules/test-custom-main/lib/custom-entry.js',
+      'Dependencies use "main" entry');
+    resolveTest('test-math/lib/sqrt', './utils/index.js', './node_modules/test-math/lib/sqrt.js',
+      'Dependencies\' files can be consumed via "/"');
+
+    resolveTest('sdk/tabs/utils', './index.js', undefined,
+      'correctly ignores SDK references in paths');
+    resolveTest('fs', './index.js', undefined,
+      'correctly ignores built in node modules in paths');
 
-  resolveTest('./dir/b', './index.js', './dir/b.js');
-  resolveTest('../index', './dir/b.js', './index.js');
-  resolveTest('../', './dir/b.js', './index.js');
-  resolveTest('./dir/a', './index.js', './dir/a.js', 'Precedence dir/a.js over dir/a/');
-  resolveTest('../utils', './dir/a.js', './utils/index.js', 'Requiring a directory defaults to dir/index.js');
-  resolveTest('../newmodule', './dir/c.js', './newmodule/lib/file.js', 'Uses package.json main in dir to load appropriate "main"');
-  resolveTest('test-math', './utils/index.js', './node_modules/test-math/index.js',
-    'Dependencies default to their index.js');
-  resolveTest('test-custom-main', './utils/index.js', './node_modules/test-custom-main/lib/custom-entry.js',
-    'Dependencies use "main" entry');
-  resolveTest('test-math/lib/sqrt', './utils/index.js', './node_modules/test-math/lib/sqrt.js',
-    'Dependencies\' files can be consumed via "/"');
+    resolveTest('test-add', './node_modules/test-math/index.js',
+      './node_modules/test-math/node_modules/test-add/index.js',
+      'Dependencies\' dependencies can be found');
+
+    resolveTest('resource://gre/modules/commonjs/sdk/tabs.js', './index.js', undefined,
+                'correctly ignores absolute URIs.');
+
+    resolveTest('../tabs', 'resource://gre/modules/commonjs/sdk/addon/bootstrap.js', undefined,
+                'correctly ignores attempts to resolve from a module at an absolute URI.');
+
+    resolveTest('sdk/tabs', 'resource://gre/modules/commonjs/sdk/addon/bootstrap.js', undefined,
+                'correctly ignores attempts to resolve from a module at an absolute URI.');
+
+    function resolveTest (id, requirer, expected, msg) {
+      let result = nodeResolve(id, requirer, { manifest: manifest, rootURI: rootURI });
+      assert.equal(result, expected, 'nodeResolve ' + id + ' from ' + requirer + ' ' +msg);
+    }
+  }
+
+  /*
+  // TODO not working in current env
+  exports[`test bundle (${variant.description`] = function (assert, done) {
+    loadAddon('/native-addons/native-addon-test/')
+  };
+  */
+
+  exports[`test native Loader with mappings (${variant.description})`] = function (assert, done) {
+    all([
+      getJSON('/fixtures/native-addon-test/expectedmap.json'),
+      getJSON('/fixtures/native-addon-test/package.json')
+    ]).then(([expectedMap, manifest]) => {
+
+      // Override dummy module and point it to `test-math` to see if the
+      // require is pulling from the mapping
+      expectedMap['./index.js']['./dir/dummy'] = './dir/a.js';
+
+      let rootURI = variant.getRootURI('native-addon-test');
+      let loader = Loader({
+        paths: makePaths(rootURI),
+        rootURI: rootURI,
+        manifest: manifest,
+        requireMap: expectedMap,
+        isNative: true
+      });
+
+      let program = main(loader);
+      assert.equal(program.dummyModule, 'dir/a',
+        'The lookup uses the information given in the mapping');
+
+      testLoader(program, assert);
+      unload(loader);
+      done();
+    }).then(null, (reason) => console.error(reason));
+  };
+
+  exports[`test native Loader overrides (${variant.description})`] = function*(assert) {
+    const expectedKeys = Object.keys(require("sdk/io/fs")).join(", ");
+    const manifest = yield getJSON('/fixtures/native-overrides-test/package.json');
+    const rootURI = variant.getRootURI('native-overrides-test');
+
+    let loader = Loader({
+      paths: makePaths(rootURI),
+      rootURI: rootURI,
+      manifest: manifest,
+      metadata: manifest,
+      isNative: true
+    });
 
-  resolveTest('sdk/tabs/utils', './index.js', undefined,
-    'correctly ignores SDK references in paths');
-  resolveTest('fs', './index.js', undefined,
-    'correctly ignores built in node modules in paths');
+    let program = main(loader);
+    let fooKeys = Object.keys(program.foo).join(", ");
+    let barKeys = Object.keys(program.foo).join(", ");
+    let fsKeys = Object.keys(program.fs).join(", ");
+    let overloadKeys = Object.keys(program.overload.fs).join(", ");
+    let overloadLibKeys = Object.keys(program.overloadLib.fs).join(", ");
+
+    assert.equal(fooKeys, expectedKeys, "foo exports sdk/io/fs");
+    assert.equal(barKeys, expectedKeys, "bar exports sdk/io/fs");
+    assert.equal(fsKeys, expectedKeys, "sdk/io/fs exports sdk/io/fs");
+    assert.equal(overloadKeys, expectedKeys, "overload exports foo which exports sdk/io/fs");
+    assert.equal(overloadLibKeys, expectedKeys, "overload/lib/foo exports foo/lib/foo");
+    assert.equal(program.internal, "test", "internal exports ./lib/internal");
+    assert.equal(program.extra, true, "fs-extra was exported properly");
+
+    assert.equal(program.Tabs, "no tabs exist", "sdk/tabs exports ./lib/tabs from the add-on");
+    assert.equal(program.CoolTabs, "no tabs exist", "sdk/tabs exports ./lib/tabs from the node_modules");
+    assert.equal(program.CoolTabsLib, "a cool tabs implementation", "./lib/tabs true relative path from the node_modules");
+
+    assert.equal(program.ignore, "do not ignore this export", "../ignore override was ignored.");
+
+    unload(loader);
+  };
+
+  exports[`test invalid native Loader overrides cause no errors (${variant.description})`] = function*(assert) {
+    const manifest = yield getJSON('/fixtures/native-overrides-test/package.json');
+    const rootURI = variant.getRootURI('native-overrides-test');
+    const EXPECTED = JSON.stringify({});
 
-  resolveTest('test-add', './node_modules/test-math/index.js',
-    './node_modules/test-math/node_modules/test-add/index.js',
-    'Dependencies\' dependencies can be found');
+    let makeLoader = (rootURI, manifest) => Loader({
+      paths: makePaths(rootURI),
+      rootURI: rootURI,
+      manifest: manifest,
+      metadata: manifest,
+      isNative: true
+    });
+
+    manifest.jetpack.overrides = "string";
+    let loader = makeLoader(rootURI, manifest);
+    assert.equal(JSON.stringify(loader.manifest.jetpack.overrides), EXPECTED,
+                 "setting jetpack.overrides to a string caused no errors making the loader");
+    unload(loader);
+
+    manifest.jetpack.overrides = true;
+    loader = makeLoader(rootURI, manifest);
+    assert.equal(JSON.stringify(loader.manifest.jetpack.overrides), EXPECTED,
+                 "setting jetpack.overrides to a boolean caused no errors making the loader");
+    unload(loader);
+
+    manifest.jetpack.overrides = 5;
+    loader = makeLoader(rootURI, manifest);
+    assert.equal(JSON.stringify(loader.manifest.jetpack.overrides), EXPECTED,
+                 "setting jetpack.overrides to a number caused no errors making the loader");
+    unload(loader);
+
+    manifest.jetpack.overrides = null;
+    loader = makeLoader(rootURI, manifest);
+    assert.equal(JSON.stringify(loader.manifest.jetpack.overrides), EXPECTED,
+                 "setting jetpack.overrides to null caused no errors making the loader");
+    unload(loader);
+  };
+
+  exports[`test invalid native Loader jetpack key cause no errors (${variant.description})`] = function*(assert) {
+    const manifest = yield getJSON('/fixtures/native-overrides-test/package.json');
+    const rootURI = variant.getRootURI('native-overrides-test');
+    const EXPECTED = JSON.stringify({});
 
-  resolveTest('resource://gre/modules/commonjs/sdk/tabs.js', './index.js', undefined,
-              'correctly ignores absolute URIs.');
+    let makeLoader = (rootURI, manifest) => Loader({
+      paths: makePaths(rootURI),
+      rootURI: rootURI,
+      manifest: manifest,
+      metadata: manifest,
+      isNative: true
+    });
+
+    manifest.jetpack = "string";
+    let loader = makeLoader(rootURI, manifest);
+    assert.equal(JSON.stringify(loader.manifest.jetpack.overrides), EXPECTED,
+                 "setting jetpack.overrides to a string caused no errors making the loader");
+    unload(loader);
 
-  resolveTest('../tabs', 'resource://gre/modules/commonjs/sdk/addon/bootstrap.js', undefined,
-              'correctly ignores attempts to resolve from a module at an absolute URI.');
+    manifest.jetpack = true;
+    loader = makeLoader(rootURI, manifest);
+    assert.equal(JSON.stringify(loader.manifest.jetpack.overrides), EXPECTED,
+                 "setting jetpack.overrides to a boolean caused no errors making the loader");
+    unload(loader);
+
+    manifest.jetpack = 5;
+    loader = makeLoader(rootURI, manifest);
+    assert.equal(JSON.stringify(loader.manifest.jetpack.overrides), EXPECTED,
+                 "setting jetpack.overrides to a number caused no errors making the loader");
+    unload(loader);
+
+    manifest.jetpack = null;
+    loader = makeLoader(rootURI, manifest);
+    assert.equal(JSON.stringify(loader.manifest.jetpack.overrides), EXPECTED,
+                 "setting jetpack.overrides to null caused no errors making the loader");
+    unload(loader);
+  };
 
-  resolveTest('sdk/tabs', 'resource://gre/modules/commonjs/sdk/addon/bootstrap.js', undefined,
-              'correctly ignores attempts to resolve from a module at an absolute URI.');
+  exports[`test native Loader without mappings (${variant.description})`] = function (assert, done) {
+    getJSON('/fixtures/native-addon-test/package.json').then(manifest => {
+      let rootURI = variant.getRootURI('native-addon-test');
+      let loader = Loader({
+        paths: makePaths(rootURI),
+        rootURI: rootURI,
+        manifest: manifest,
+        isNative: true
+      });
+
+      let program = main(loader);
+      testLoader(program, assert);
+      unload(loader);
+      done();
+    }).then(null, (reason) => console.error(reason));
+  };
 
-  function resolveTest (id, requirer, expected, msg) {
-    let result = nodeResolve(id, requirer, { manifest: manifest, rootURI: rootURI });
-    assert.equal(result, expected, 'nodeResolve ' + id + ' from ' + requirer + ' ' +msg);
-  }
+  exports[`test require#resolve with relative, dependencies (${variant.description})`] = function(assert, done) {
+    getJSON('/fixtures/native-addon-test/package.json').then(manifest => {
+      let rootURI = variant.getRootURI('native-addon-test');
+      let loader = Loader({
+        paths: makePaths(rootURI),
+        rootURI: rootURI,
+        manifest: manifest,
+        isNative: true
+      });
+
+      let program = main(loader);
+      let fixtureRoot = program.require.resolve("./").replace(/index\.js$/, "");
+
+      assert.equal(variant.getRootURI("native-addon-test"), fixtureRoot, "correct resolution root");
+      assert.equal(program.require.resolve("test-math"), fixtureRoot + "node_modules/test-math/index.js", "works with node_modules");
+      assert.equal(program.require.resolve("./newmodule"), fixtureRoot + "newmodule/lib/file.js", "works with directory mains");
+      assert.equal(program.require.resolve("./dir/a"), fixtureRoot + "dir/a.js", "works with normal relative module lookups");
+      assert.equal(program.require.resolve("modules/Promise.jsm"), "resource://gre/modules/Promise.jsm", "works with path lookups");
+
+      // TODO bug 1050422, handle loading non JS/JSM file paths
+      // assert.equal(program.require.resolve("test-assets/styles.css"), fixtureRoot + "node_modules/test-assets/styles.css",
+      // "works with different file extension lookups in dependencies");
+
+      unload(loader);
+      done();
+    }).then(null, (reason) => console.error(reason));
+  };
 }
 
-/*
-// TODO not working in current env
-exports['test bundle'] = function (assert, done) {
-  loadAddon('/native-addons/native-addon-test/')
-};
-*/
+before(exports, () => {
+  for (let fixture of fixtures) {
+    let url = `jar:${root}/fixtures/${fixture}.xpi!/`;
+
+    resProto.setSubstitution(fixture, NetUtil.newURI(url));
+  }
+});
+
+after(exports, () => {
+  for (let fixture of fixtures)
+    resProto.setSubstitution(fixture, null);
+});
 
 exports['test JSM loading'] = function (assert, done) {
   getJSON('/fixtures/jsm-package/package.json').then(manifest => {
     let rootURI = root + '/fixtures/jsm-package/';
     let loader = Loader({
       paths: makePaths(rootURI),
       rootURI: rootURI,
       manifest: manifest,
@@ -95,203 +336,16 @@ exports['test JSM loading'] = function (
       assert.equal(path, 10, 'JSM files resolved from path work');
       assert.equal(absolute, 20, 'JSM files resolved from full resource:// work');
       assert.equal(jsabsolute, 30, 'JS files resolved from full resource:// work');
     }).then(done, console.error);
 
   }).then(null, console.error);
 };
 
-exports['test native Loader with mappings'] = function (assert, done) {
-  all([
-    getJSON('/fixtures/native-addon-test/expectedmap.json'),
-    getJSON('/fixtures/native-addon-test/package.json')
-  ]).then(([expectedMap, manifest]) => {
-
-    // Override dummy module and point it to `test-math` to see if the
-    // require is pulling from the mapping
-    expectedMap['./index.js']['./dir/dummy'] = './dir/a.js';
-
-    let rootURI = root + '/fixtures/native-addon-test/';
-    let loader = Loader({
-      paths: makePaths(rootURI),
-      rootURI: rootURI,
-      manifest: manifest,
-      requireMap: expectedMap,
-      isNative: true
-    });
-
-    let program = main(loader);
-    assert.equal(program.dummyModule, 'dir/a',
-      'The lookup uses the information given in the mapping');
-
-    testLoader(program, assert);
-    unload(loader);
-    done();
-  }).then(null, (reason) => console.error(reason));
-};
-
-exports['test native Loader overrides'] = function*(assert) {
-  const expectedKeys = Object.keys(require("sdk/io/fs")).join(", ");
-  const manifest = yield getJSON('/fixtures/native-overrides-test/package.json');
-  const rootURI = root + '/fixtures/native-overrides-test/';
-
-  let loader = Loader({
-    paths: makePaths(rootURI),
-    rootURI: rootURI,
-    manifest: manifest,
-    metadata: manifest,
-    isNative: true
-  });
-
-  let program = main(loader);
-  let fooKeys = Object.keys(program.foo).join(", ");
-  let barKeys = Object.keys(program.foo).join(", ");
-  let fsKeys = Object.keys(program.fs).join(", ");
-  let overloadKeys = Object.keys(program.overload.fs).join(", ");
-  let overloadLibKeys = Object.keys(program.overloadLib.fs).join(", ");
-
-  assert.equal(fooKeys, expectedKeys, "foo exports sdk/io/fs");
-  assert.equal(barKeys, expectedKeys, "bar exports sdk/io/fs");
-  assert.equal(fsKeys, expectedKeys, "sdk/io/fs exports sdk/io/fs");
-  assert.equal(overloadKeys, expectedKeys, "overload exports foo which exports sdk/io/fs");
-  assert.equal(overloadLibKeys, expectedKeys, "overload/lib/foo exports foo/lib/foo");
-  assert.equal(program.internal, "test", "internal exports ./lib/internal");
-  assert.equal(program.extra, true, "fs-extra was exported properly");
-
-  assert.equal(program.Tabs, "no tabs exist", "sdk/tabs exports ./lib/tabs from the add-on");
-  assert.equal(program.CoolTabs, "no tabs exist", "sdk/tabs exports ./lib/tabs from the node_modules");
-  assert.equal(program.CoolTabsLib, "a cool tabs implementation", "./lib/tabs true relative path from the node_modules");
-
-  assert.equal(program.ignore, "do not ignore this export", "../ignore override was ignored.");
-
-  unload(loader);
-};
-
-exports['test invalid native Loader overrides cause no errors'] = function*(assert) {
-  const manifest = yield getJSON('/fixtures/native-overrides-test/package.json');
-  const rootURI = root + '/fixtures/native-overrides-test/';
-  const EXPECTED = JSON.stringify({});
-
-  let makeLoader = (rootURI, manifest) => Loader({
-    paths: makePaths(rootURI),
-    rootURI: rootURI,
-    manifest: manifest,
-    metadata: manifest,
-    isNative: true
-  });
-
-  manifest.jetpack.overrides = "string";
-  let loader = makeLoader(rootURI, manifest);
-  assert.equal(JSON.stringify(loader.manifest.jetpack.overrides), EXPECTED,
-               "setting jetpack.overrides to a string caused no errors making the loader");
-  unload(loader);
-
-  manifest.jetpack.overrides = true;
-  loader = makeLoader(rootURI, manifest);
-  assert.equal(JSON.stringify(loader.manifest.jetpack.overrides), EXPECTED,
-               "setting jetpack.overrides to a boolean caused no errors making the loader");
-  unload(loader);
-
-  manifest.jetpack.overrides = 5;
-  loader = makeLoader(rootURI, manifest);
-  assert.equal(JSON.stringify(loader.manifest.jetpack.overrides), EXPECTED,
-               "setting jetpack.overrides to a number caused no errors making the loader");
-  unload(loader);
-
-  manifest.jetpack.overrides = null;
-  loader = makeLoader(rootURI, manifest);
-  assert.equal(JSON.stringify(loader.manifest.jetpack.overrides), EXPECTED,
-               "setting jetpack.overrides to null caused no errors making the loader");
-  unload(loader);
-};
-
-exports['test invalid native Loader jetpack key cause no errors'] = function*(assert) {
-  const manifest = yield getJSON('/fixtures/native-overrides-test/package.json');
-  const rootURI = root + '/fixtures/native-overrides-test/';
-  const EXPECTED = JSON.stringify({});
-
-  let makeLoader = (rootURI, manifest) => Loader({
-    paths: makePaths(rootURI),
-    rootURI: rootURI,
-    manifest: manifest,
-    metadata: manifest,
-    isNative: true
-  });
-
-  manifest.jetpack = "string";
-  let loader = makeLoader(rootURI, manifest);
-  assert.equal(JSON.stringify(loader.manifest.jetpack.overrides), EXPECTED,
-               "setting jetpack.overrides to a string caused no errors making the loader");
-  unload(loader);
-
-  manifest.jetpack = true;
-  loader = makeLoader(rootURI, manifest);
-  assert.equal(JSON.stringify(loader.manifest.jetpack.overrides), EXPECTED,
-               "setting jetpack.overrides to a boolean caused no errors making the loader");
-  unload(loader);
-
-  manifest.jetpack = 5;
-  loader = makeLoader(rootURI, manifest);
-  assert.equal(JSON.stringify(loader.manifest.jetpack.overrides), EXPECTED,
-               "setting jetpack.overrides to a number caused no errors making the loader");
-  unload(loader);
-
-  manifest.jetpack = null;
-  loader = makeLoader(rootURI, manifest);
-  assert.equal(JSON.stringify(loader.manifest.jetpack.overrides), EXPECTED,
-               "setting jetpack.overrides to null caused no errors making the loader");
-  unload(loader);
-};
-
-exports['test native Loader without mappings'] = function (assert, done) {
-  getJSON('/fixtures/native-addon-test/package.json').then(manifest => {
-    let rootURI = root + '/fixtures/native-addon-test/';
-    let loader = Loader({
-      paths: makePaths(rootURI),
-      rootURI: rootURI,
-      manifest: manifest,
-      isNative: true
-    });
-
-    let program = main(loader);
-    testLoader(program, assert);
-    unload(loader);
-    done();
-  }).then(null, (reason) => console.error(reason));
-};
-
-exports["test require#resolve with relative, dependencies"] = function(assert, done) {
-  getJSON('/fixtures/native-addon-test/package.json').then(manifest => {
-    let rootURI = root + '/fixtures/native-addon-test/';
-    let loader = Loader({
-      paths: makePaths(rootURI),
-      rootURI: rootURI,
-      manifest: manifest,
-      isNative: true
-    });
-
-    let program = main(loader);
-    let fixtureRoot = program.require.resolve("./").replace(/native-addon-test\/(.*)/, "") + "native-addon-test/";
-
-    assert.equal(root + "/fixtures/native-addon-test/", fixtureRoot, "correct resolution root");
-    assert.equal(program.require.resolve("test-math"), fixtureRoot + "node_modules/test-math/index.js", "works with node_modules");
-    assert.equal(program.require.resolve("./newmodule"), fixtureRoot + "newmodule/lib/file.js", "works with directory mains");
-    assert.equal(program.require.resolve("./dir/a"), fixtureRoot + "dir/a.js", "works with normal relative module lookups");
-    assert.equal(program.require.resolve("modules/Promise.jsm"), "resource://gre/modules/Promise.jsm", "works with path lookups");
-
-    // TODO bug 1050422, handle loading non JS/JSM file paths
-    // assert.equal(program.require.resolve("test-assets/styles.css"), fixtureRoot + "node_modules/test-assets/styles.css",
-    // "works with different file extension lookups in dependencies");
-
-    unload(loader);
-    done();
-  }).then(null, (reason) => console.error(reason));
-};
-
 function testLoader (program, assert) {
   // Test 'main' entries
   // no relative custom main `lib/index.js`
   assert.equal(program.customMainModule, 'custom entry file',
     'a node_module dependency correctly uses its `main` entry in manifest');
   // relative custom main `./lib/index.js`
   assert.equal(program.customMainModuleRelative, 'custom entry file relative',
     'a node_module dependency correctly uses its `main` entry in manifest with relative ./');
--- a/b2g/app/b2g.js
+++ b/b2g/app/b2g.js
@@ -385,19 +385,16 @@ pref("dom.mozBrowserFramesEnabled", true
 pref("dom.ipc.processCount", 100000);
 
 pref("dom.ipc.browser_frames.oop_by_default", false);
 
 #if !defined(MOZ_MULET) && !defined(MOZ_GRAPHENE)
 pref("dom.meta-viewport.enabled", true);
 #endif
 
-// SMS/MMS
-pref("dom.sms.enabled", true);
-
 //The waiting time in network manager.
 pref("network.gonk.ms-release-mms-connection", 30000);
 
 // Shortnumber matching needed for e.g. Brazil:
 // 03187654321 can be found with 87654321
 pref("dom.phonenumber.substringmatching.BR", 8);
 pref("dom.phonenumber.substringmatching.CO", 10);
 pref("dom.phonenumber.substringmatching.VE", 7);
--- a/b2g/installer/package-manifest.in
+++ b/b2g/installer/package-manifest.in
@@ -154,36 +154,32 @@
 @RESPATH@/components/dom_base.xpt
 @RESPATH@/components/dom_system.xpt
 @RESPATH@/components/dom_workers.xpt
 #ifdef MOZ_WIDGET_GONK
 @RESPATH@/components/dom_wifi.xpt
 @RESPATH@/components/dom_system_gonk.xpt
 #endif
 #ifdef MOZ_B2G_RIL
-@RESPATH@/components/dom_wappush.xpt
 @RESPATH@/components/dom_mobileconnection.xpt
 #endif
 #ifdef MOZ_B2G_BT
 @RESPATH@/components/dom_bluetooth.xpt
 #endif
 @RESPATH@/components/dom_canvas.xpt
 @RESPATH@/components/dom_contacts.xpt
 @RESPATH@/components/dom_core.xpt
 @RESPATH@/components/dom_css.xpt
 @RESPATH@/components/dom_events.xpt
 @RESPATH@/components/dom_geolocation.xpt
 @RESPATH@/components/dom_media.xpt
 @RESPATH@/components/dom_network.xpt
 #ifdef MOZ_SECUREELEMENT
 @RESPATH@/components/dom_secureelement.xpt
 #endif
-#ifdef MOZ_NFC
-@RESPATH@/components/dom_nfc.xpt
-#endif
 @RESPATH@/components/dom_notification.xpt
 @RESPATH@/components/dom_html.xpt
 @RESPATH@/components/dom_offline.xpt
 @RESPATH@/components/dom_json.xpt
 @RESPATH@/components/dom_messages.xpt
 @RESPATH@/components/dom_power.xpt
 @RESPATH@/components/dom_push.xpt
 @RESPATH@/components/dom_quota.xpt
@@ -552,24 +548,16 @@
 @RESPATH@/components/messageWakeupService.manifest
 @RESPATH@/components/SettingsManager.js
 @RESPATH@/components/SettingsManager.manifest
 @RESPATH@/components/SettingsService.js
 @RESPATH@/components/SettingsService.manifest
 @RESPATH@/components/webvtt.xpt
 @RESPATH@/components/WebVTT.manifest
 @RESPATH@/components/WebVTTParserWrapper.js
-#ifdef MOZ_NFC
-@RESPATH@/components/nsNfc.manifest
-@RESPATH@/components/nsNfc.js
-@RESPATH@/components/Nfc.manifest
-@RESPATH@/components/Nfc.js
-@RESPATH@/components/NfcContentHelper.manifest
-@RESPATH@/components/NfcContentHelper.js
-#endif
 #ifdef MOZ_SECUREELEMENT
 @RESPATH@/components/DOMSecureElement.manifest
 @RESPATH@/components/DOMSecureElement.js
 #endif
 @RESPATH@/components/nsINIProcessor.manifest
 @RESPATH@/components/nsINIProcessor.js
 @RESPATH@/components/nsPrompter.manifest
 @RESPATH@/components/nsPrompter.js
--- a/browser/base/content/aboutNetError.xhtml
+++ b/browser/base/content/aboutNetError.xhtml
@@ -96,17 +96,17 @@
 
       function doOverride(buttonEl) {
         var event = new CustomEvent("AboutNetErrorOverride", {bubbles:true});
         document.dispatchEvent(event);
         retryThis(buttonEl);
       }
 
       function toggleDisplay(node) {
-        toggle = {
+        const toggle = {
           "": "block",
           "none": "block",
           "block": "none"
         };
         return (node.style.display = toggle[node.style.display]);
       }
 
       function showCertificateErrorReporting() {
--- a/browser/base/content/abouthome/aboutHome.js
+++ b/browser/base/content/abouthome/aboutHome.js
@@ -1,14 +1,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/. */
 
 "use strict";
 
+/* import-globals-from ../contentSearchUI.js */
+
 // The process of adding a new default snippet involves:
 //   * add a new entity to aboutHome.dtd
 //   * add a <span/> for it in aboutHome.xhtml
 //   * add an entry here in the proper ordering (based on spans)
 // The <a/> part of the snippet will be linked to the corresponding url.
 const DEFAULT_SNIPPETS_URLS = [
   "https://www.mozilla.org/firefox/features/?utm_source=snippet&utm_medium=snippet&utm_campaign=default+feature+snippet"
 , "https://addons.mozilla.org/firefox/?utm_source=snippet&utm_medium=snippet&utm_campaign=addons"
--- a/browser/base/content/nsContextMenu.js
+++ b/browser/base/content/nsContextMenu.js
@@ -363,20 +363,21 @@ nsContextMenu.prototype = {
     this.showItem("context-sharevideo", shareEnabled && this.onVideo);
     this.setItemAttr("context-sharevideo", "disabled", !this.mediaURL || this.mediaURL.startsWith("blob:"));
   },
 
   initSpellingItems: function() {
     var canSpell = InlineSpellCheckerUI.canSpellCheck &&
                    !InlineSpellCheckerUI.initialSpellCheckPending &&
                    this.canSpellCheck;
+    let showDictionaries = canSpell && InlineSpellCheckerUI.enabled;
     var onMisspelling = InlineSpellCheckerUI.overMisspelling;
     var showUndo = canSpell && InlineSpellCheckerUI.canUndo();
     this.showItem("spell-check-enabled", canSpell);
-    this.showItem("spell-separator", canSpell || this.onEditableArea);
+    this.showItem("spell-separator", canSpell);
     document.getElementById("spell-check-enabled")
             .setAttribute("checked", canSpell && InlineSpellCheckerUI.enabled);
 
     this.showItem("spell-add-to-dictionary", onMisspelling);
     this.showItem("spell-undo-add-to-dictionary", showUndo);
 
     // suggestion list
     this.showItem("spell-suggestions-separator", onMisspelling || showUndo);
@@ -387,29 +388,30 @@ nsContextMenu.prototype = {
         InlineSpellCheckerUI.addSuggestionsToMenu(suggestionsSeparator.parentNode,
                                                   suggestionsSeparator, 5);
       this.showItem("spell-no-suggestions", numsug == 0);
     }
     else
       this.showItem("spell-no-suggestions", false);
 
     // dictionary list
-    this.showItem("spell-dictionaries", canSpell && InlineSpellCheckerUI.enabled);
+    this.showItem("spell-dictionaries", showDictionaries);
     if (canSpell) {
       var dictMenu = document.getElementById("spell-dictionaries-menu");
       var dictSep = document.getElementById("spell-language-separator");
       let count = InlineSpellCheckerUI.addDictionaryListToMenu(dictMenu, dictSep);
       this.showItem(dictSep, count > 0);
       this.showItem("spell-add-dictionaries-main", false);
     }
     else if (this.onEditableArea) {
       // when there is no spellchecker but we might be able to spellcheck
       // add the add to dictionaries item. This will ensure that people
       // with no dictionaries will be able to download them
-      this.showItem("spell-add-dictionaries-main", true);
+      this.showItem("spell-language-separator", showDictionaries);
+      this.showItem("spell-add-dictionaries-main", showDictionaries);
     }
     else
       this.showItem("spell-add-dictionaries-main", false);
   },
 
   initClipboardItems: function() {
     // Copy depends on whether there is selected text.
     // Enabling this context menu item is now done through the global
--- a/browser/base/content/tabbrowser.xml
+++ b/browser/base/content/tabbrowser.xml
@@ -5670,17 +5670,17 @@
 
       <field name="_animateElement">
         this.mTabstrip._scrollButtonDown;
       </field>
 
       <method name="_notifyBackgroundTab">
         <parameter name="aTab"/>
         <body><![CDATA[
-          if (aTab.pinned)
+          if (aTab.pinned || aTab.hidden)
             return;
 
           var scrollRect = this.mTabstrip.scrollClientRect;
           var tab = aTab.getBoundingClientRect();
           this.mTabstrip._calcTabMargins(aTab);
 
           // DOMRect left/right properties are immutable.
           tab = {left: tab.left, right: tab.right};
--- a/browser/base/content/test/general/browser_contextmenu.js
+++ b/browser/base/content/test/general/browser_contextmenu.js
@@ -871,18 +871,16 @@ add_task(function* test_input_spell_fals
     ["context-undo",        false,
      "---",                 null,
      "context-cut",         true,
      "context-copy",        true,
      "context-paste",       null, // ignore clipboard state
      "context-delete",      false,
      "---",                 null,
      "context-selectall",   true,
-     "---",                 null,
-     "spell-add-dictionaries-main",  true,
     ]
   );
 });
 
 const remoteClientsFixture = [ { id: 1, name: "Foo"}, { id: 2, name: "Bar"} ];
 
 add_task(function* test_plaintext_sendpagetodevice() {
   if (!gFxAccounts.sendTabToDeviceEnabled) {
--- a/browser/base/content/test/general/browser_selectpopup.js
+++ b/browser/base/content/test/general/browser_selectpopup.js
@@ -37,28 +37,28 @@ const PAGECONTENT_SMALL =
   "  <option value='Three'>Three</option>" +
   "  <option value='Four'>Four</option>" +
   "</select><select id='three'>" +
   "  <option value='Five'>Five</option>" +
   "  <option value='Six'>Six</option>" +
   "</select></body></html>";
 
 const PAGECONTENT_SOMEHIDDEN =
-  "<html>" +
+  "<html><head><style>.hidden { display: none; }</style></head>" +
   "<body><select id='one'>" +
   "  <option value='One' style='display: none;'>OneHidden</option>" +
-  "  <option value='Two' style='display: none;'>TwoHidden</option>" +
+  "  <option value='Two' class='hidden'>TwoHidden</option>" +
   "  <option value='Three'>ThreeVisible</option>" +
   "  <option value='Four'style='display: table;'>FourVisible</option>" +
   "  <option value='Five'>FiveVisible</option>" +
-  "  <optgroup label='GroupHidden' style='display: none;'>" +
+  "  <optgroup label='GroupHidden' class='hidden'>" +
   "    <option value='Four'>Six.OneHidden</option>" +
   "    <option value='Five' style='display: block;'>Six.TwoHidden</option>" +
   "  </optgroup>" +
-  "  <option value='Six'>SevenVisible</option>" +
+  "  <option value='Six' class='hidden' style='display: block;'>SevenVisible</option>" +
   "</select></body></html>";
 
 const PAGECONTENT_TRANSLATED =
   "<html><body>" +
   "<div id='div'>" +
   "<iframe id='frame' width='320' height='295' style='border: none;'" +
   "        src='data:text/html,<select id=select autofocus><option>he he he</option><option>boo boo</option><option>baz baz</option></select>'" +
   "</iframe>" +
--- a/browser/components/contextualidentity/test/browser/.eslintrc.js
+++ b/browser/components/contextualidentity/test/browser/.eslintrc.js
@@ -1,7 +1,11 @@
 "use strict";
 
 module.exports = {
   "extends": [
     "../../../../../testing/mochitest/browser.eslintrc.js"
-  ]
+  ],
+
+  "rules": {
+    "no-undef": 2
+  }
 };
--- a/browser/components/contextualidentity/test/browser/browser_windowName.js
+++ b/browser/components/contextualidentity/test/browser/browser_windowName.js
@@ -35,17 +35,17 @@ add_task(function* test() {
   let browser2 = gBrowser.getBrowserForTab(tab2);
   yield BrowserTestUtils.browserLoaded(browser2);
   yield ContentTask.spawn(browser2, null, function(opts) {
     content.window.name = 'tab-2';
   });
 
   // Let's try to open a window from tab1 with a name 'tab-2'.
   info("Opening a window from the first tab...");
-  yield ContentTask.spawn(browser1, { url: BASE_URI + '?new' }, function(opts) {
+  yield ContentTask.spawn(browser1, { url: BASE_URI + '?new' }, function* (opts) {
     yield (new content.window.wrappedJSObject.Promise(resolve => {
       let w = content.window.wrappedJSObject.open(opts.url, 'tab-2');
       w.onload = function() { resolve(); }
     }));
   });
 
   is(browser1.contentTitle, '?old', "Tab1 title must be 'old'");
   is(browser1.contentPrincipal.userContextId, 1, "Tab1 UCI must be 1");
--- a/browser/components/customizableui/CustomizableWidgets.jsm
+++ b/browser/components/customizableui/CustomizableWidgets.jsm
@@ -1254,18 +1254,19 @@ if (Services.prefs.getBoolPref("privacy.
 }
 
 if (AppConstants.E10S_TESTING_ONLY) {
   if (Services.appinfo.browserTabsRemoteAutostart) {
     CustomizableWidgets.push({
       id: "e10s-button",
       defaultArea: CustomizableUI.AREA_PANEL,
       onBuild: function(aDocument) {
-          node.setAttribute("label", CustomizableUI.getLocalizedProperty(this, "label"));
-          node.setAttribute("tooltiptext", CustomizableUI.getLocalizedProperty(this, "tooltiptext"));
+        let node = aDocument.createElementNS(kNSXUL, "toolbarbutton");
+        node.setAttribute("label", CustomizableUI.getLocalizedProperty(this, "label"));
+        node.setAttribute("tooltiptext", CustomizableUI.getLocalizedProperty(this, "tooltiptext"));
       },
       onCommand: function(aEvent) {
         let win = aEvent.view;
         win.OpenBrowserWindow({remote: false});
       },
     });
   }
 }
--- a/browser/components/customizableui/test/browser_988072_sidebar_events.js
+++ b/browser/components/customizableui/test/browser_988072_sidebar_events.js
@@ -132,261 +132,261 @@ function add_sidebar_task(description, s
     gTestSidebarItem.remove();
     removeWidget();
   });
 }
 
 add_sidebar_task(
   "Check that a sidebar that uses a command event listener works",
 function*() {
-  gTestSidebarItem.addEventListener("command", sawEvent);
+  gTestSidebarItem.addEventListener("command", window.sawEvent);
 }, function*() {
   checkExpectedEvents({ command: 1 });
 });
 
 add_sidebar_task(
   "Check that a sidebar that uses a click event listener works",
 function*() {
-  gTestSidebarItem.addEventListener("click", sawEvent);
+  gTestSidebarItem.addEventListener("click", window.sawEvent);
 }, function*() {
   checkExpectedEvents({ click: 1 });
 });
 
 add_sidebar_task(
   "Check that a sidebar that uses both click and command event listeners works",
 function*() {
-  gTestSidebarItem.addEventListener("command", sawEvent);
-  gTestSidebarItem.addEventListener("click", sawEvent);
+  gTestSidebarItem.addEventListener("command", window.sawEvent);
+  gTestSidebarItem.addEventListener("click", window.sawEvent);
 }, function*() {
   checkExpectedEvents({ command: 1, click: 1 });
 });
 
 add_sidebar_task(
   "Check that a sidebar that uses an oncommand attribute works",
 function*() {
-  gTestSidebarItem.setAttribute("oncommand", "sawEvent(event, true)");
+  gTestSidebarItem.setAttribute("oncommand", "window.sawEvent(event, true)");
 }, function*() {
   checkExpectedEvents({ oncommand: 1 });
 });
 
 add_sidebar_task(
   "Check that a sidebar that uses an onclick attribute works",
 function*() {
-  gTestSidebarItem.setAttribute("onclick", "sawEvent(event, true)");
+  gTestSidebarItem.setAttribute("onclick", "window.sawEvent(event, true)");
 }, function*() {
   checkExpectedEvents({ onclick: 1 });
 });
 
 add_sidebar_task(
   "Check that a sidebar that uses both onclick and oncommand attributes works",
 function*() {
-  gTestSidebarItem.setAttribute("onclick", "sawEvent(event, true)");
-  gTestSidebarItem.setAttribute("oncommand", "sawEvent(event, true)");
+  gTestSidebarItem.setAttribute("onclick", "window.sawEvent(event, true)");
+  gTestSidebarItem.setAttribute("oncommand", "window.sawEvent(event, true)");
 }, function*() {
   checkExpectedEvents({ onclick: 1, oncommand: 1 });
 });
 
 add_sidebar_task(
   "Check that a sidebar that uses an onclick attribute and a command listener works",
 function*() {
-  gTestSidebarItem.setAttribute("onclick", "sawEvent(event, true)");
-  gTestSidebarItem.addEventListener("command", sawEvent);
+  gTestSidebarItem.setAttribute("onclick", "window.sawEvent(event, true)");
+  gTestSidebarItem.addEventListener("command", window.sawEvent);
 }, function*() {
   checkExpectedEvents({ onclick: 1, command: 1 });
 });
 
 add_sidebar_task(
   "Check that a sidebar that uses an oncommand attribute and a click listener works",
 function*() {
-  gTestSidebarItem.setAttribute("oncommand", "sawEvent(event, true)");
-  gTestSidebarItem.addEventListener("click", sawEvent);
+  gTestSidebarItem.setAttribute("oncommand", "window.sawEvent(event, true)");
+  gTestSidebarItem.addEventListener("click", window.sawEvent);
 }, function*() {
   checkExpectedEvents({ click: 1, oncommand: 1 });
 });
 
 add_sidebar_task(
   "A sidebar with both onclick attribute and click listener sees only one event :(",
 function*() {
-  gTestSidebarItem.setAttribute("onclick", "sawEvent(event, true)");
-  gTestSidebarItem.addEventListener("click", sawEvent);
+  gTestSidebarItem.setAttribute("onclick", "window.sawEvent(event, true)");
+  gTestSidebarItem.addEventListener("click", window.sawEvent);
 }, function*() {
   checkExpectedEvents({ onclick: 1 });
 });
 
 add_sidebar_task(
   "A sidebar with both oncommand attribute and command listener sees only one event :(",
 function*() {
-  gTestSidebarItem.setAttribute("oncommand", "sawEvent(event, true)");
-  gTestSidebarItem.addEventListener("command", sawEvent);
+  gTestSidebarItem.setAttribute("oncommand", "window.sawEvent(event, true)");
+  gTestSidebarItem.addEventListener("command", window.sawEvent);
 }, function*() {
   checkExpectedEvents({ oncommand: 1 });
 });
 
 add_sidebar_task(
   "Check that a sidebar that uses a broadcaster with an oncommand attribute works",
 function*() {
   let broadcaster = document.createElement("broadcaster");
   broadcaster.setAttribute("id", "testbroadcaster");
-  broadcaster.setAttribute("oncommand", "sawEvent(event, true)");
+  broadcaster.setAttribute("oncommand", "window.sawEvent(event, true)");
   broadcaster.setAttribute("label", "Test Sidebar");
   document.getElementById("mainBroadcasterSet").appendChild(broadcaster);
 
   gTestSidebarItem.setAttribute("observes", "testbroadcaster");
 }, function*() {
   checkExpectedEvents({ oncommand: 1 });
   document.getElementById("testbroadcaster").remove();
 });
 
 add_sidebar_task(
   "Check that a sidebar that uses a broadcaster with an onclick attribute works",
 function*() {
   let broadcaster = document.createElement("broadcaster");
   broadcaster.setAttribute("id", "testbroadcaster");
-  broadcaster.setAttribute("onclick", "sawEvent(event, true)");
+  broadcaster.setAttribute("onclick", "window.sawEvent(event, true)");
   broadcaster.setAttribute("label", "Test Sidebar");
   document.getElementById("mainBroadcasterSet").appendChild(broadcaster);
 
   gTestSidebarItem.setAttribute("observes", "testbroadcaster");
 }, function*() {
   checkExpectedEvents({ onclick: 1 });
   document.getElementById("testbroadcaster").remove();
 });
 
 add_sidebar_task(
   "Check that a sidebar that uses a broadcaster with both onclick and oncommand attributes works",
 function*() {
   let broadcaster = document.createElement("broadcaster");
   broadcaster.setAttribute("id", "testbroadcaster");
-  broadcaster.setAttribute("onclick", "sawEvent(event, true)");
-  broadcaster.setAttribute("oncommand", "sawEvent(event, true)");
+  broadcaster.setAttribute("onclick", "window.sawEvent(event, true)");
+  broadcaster.setAttribute("oncommand", "window.sawEvent(event, true)");
   broadcaster.setAttribute("label", "Test Sidebar");
   document.getElementById("mainBroadcasterSet").appendChild(broadcaster);
 
   gTestSidebarItem.setAttribute("observes", "testbroadcaster");
 }, function*() {
   checkExpectedEvents({ onclick: 1, oncommand: 1 });
   document.getElementById("testbroadcaster").remove();
 });
 
 add_sidebar_task(
   "Check that a sidebar with a click listener and a broadcaster with an oncommand attribute works",
 function*() {
   let broadcaster = document.createElement("broadcaster");
   broadcaster.setAttribute("id", "testbroadcaster");
-  broadcaster.setAttribute("oncommand", "sawEvent(event, true)");
+  broadcaster.setAttribute("oncommand", "window.sawEvent(event, true)");
   broadcaster.setAttribute("label", "Test Sidebar");
   document.getElementById("mainBroadcasterSet").appendChild(broadcaster);
 
   gTestSidebarItem.setAttribute("observes", "testbroadcaster");
-  gTestSidebarItem.addEventListener("click", sawEvent);
+  gTestSidebarItem.addEventListener("click", window.sawEvent);
 }, function*() {
   checkExpectedEvents({ click: 1, oncommand: 1 });
   document.getElementById("testbroadcaster").remove();
 });
 
 add_sidebar_task(
   "Check that a sidebar with a command listener and a broadcaster with an onclick attribute works",
 function*() {
   let broadcaster = document.createElement("broadcaster");
   broadcaster.setAttribute("id", "testbroadcaster");
-  broadcaster.setAttribute("onclick", "sawEvent(event, true)");
+  broadcaster.setAttribute("onclick", "window.sawEvent(event, true)");
   broadcaster.setAttribute("label", "Test Sidebar");
   document.getElementById("mainBroadcasterSet").appendChild(broadcaster);
 
   gTestSidebarItem.setAttribute("observes", "testbroadcaster");
-  gTestSidebarItem.addEventListener("command", sawEvent);
+  gTestSidebarItem.addEventListener("command", window.sawEvent);
 }, function*() {
   checkExpectedEvents({ onclick: 1, command: 1 });
   document.getElementById("testbroadcaster").remove();
 });
 
 add_sidebar_task(
   "Check that a sidebar with a click listener and a broadcaster with an onclick " +
   "attribute only sees one event :(",
 function*() {
   let broadcaster = document.createElement("broadcaster");
   broadcaster.setAttribute("id", "testbroadcaster");
-  broadcaster.setAttribute("onclick", "sawEvent(event, true)");
+  broadcaster.setAttribute("onclick", "window.sawEvent(event, true)");
   broadcaster.setAttribute("label", "Test Sidebar");
   document.getElementById("mainBroadcasterSet").appendChild(broadcaster);
 
   gTestSidebarItem.setAttribute("observes", "testbroadcaster");
-  gTestSidebarItem.addEventListener("click", sawEvent);
+  gTestSidebarItem.addEventListener("click", window.sawEvent);
 }, function*() {
   checkExpectedEvents({ onclick: 1 });
   document.getElementById("testbroadcaster").remove();
 });
 
 add_sidebar_task(
   "Check that a sidebar with a command listener and a broadcaster with an oncommand " +
   "attribute only sees one event :(",
 function*() {
   let broadcaster = document.createElement("broadcaster");
   broadcaster.setAttribute("id", "testbroadcaster");
-  broadcaster.setAttribute("oncommand", "sawEvent(event, true)");
+  broadcaster.setAttribute("oncommand", "window.sawEvent(event, true)");
   broadcaster.setAttribute("label", "Test Sidebar");
   document.getElementById("mainBroadcasterSet").appendChild(broadcaster);
 
   gTestSidebarItem.setAttribute("observes", "testbroadcaster");
-  gTestSidebarItem.addEventListener("command", sawEvent);
+  gTestSidebarItem.addEventListener("command", window.sawEvent);
 }, function*() {
   checkExpectedEvents({ oncommand: 1 });
   document.getElementById("testbroadcaster").remove();
 });
 
 add_sidebar_task(
   "Check that a sidebar that uses a command element with a command event listener works",
 function*() {
   let command = document.createElement("command");
   command.setAttribute("id", "testcommand");
   document.getElementById("mainCommandSet").appendChild(command);
-  command.addEventListener("command", sawEvent);
+  command.addEventListener("command", window.sawEvent);
 
   gTestSidebarItem.setAttribute("command", "testcommand");
 }, function*() {
   checkExpectedEvents({ command: 1 });
   document.getElementById("testcommand").remove();
 });
 
 add_sidebar_task(
   "Check that a sidebar that uses a command element with an oncommand attribute works",
 function*() {
   let command = document.createElement("command");
   command.setAttribute("id", "testcommand");
-  command.setAttribute("oncommand", "sawEvent(event, true)");
+  command.setAttribute("oncommand", "window.sawEvent(event, true)");
   document.getElementById("mainCommandSet").appendChild(command);
 
   gTestSidebarItem.setAttribute("command", "testcommand");
 }, function*() {
   checkExpectedEvents({ oncommand: 1 });
   document.getElementById("testcommand").remove();
 });
 
 add_sidebar_task("Check that a sidebar that uses a command element with a " +
   "command event listener and oncommand attribute works",
 function*() {
   let command = document.createElement("command");
   command.setAttribute("id", "testcommand");
-  command.setAttribute("oncommand", "sawEvent(event, true)");
+  command.setAttribute("oncommand", "window.sawEvent(event, true)");
   document.getElementById("mainCommandSet").appendChild(command);
-  command.addEventListener("command", sawEvent);
+  command.addEventListener("command", window.sawEvent);
 
   gTestSidebarItem.setAttribute("command", "testcommand");
 }, function*() {
   checkExpectedEvents({ command: 1, oncommand: 1 });
   document.getElementById("testcommand").remove();
 });
 
 add_sidebar_task(
   "A sidebar with a command element will still see click events",
 function*() {
   let command = document.createElement("command");
   command.setAttribute("id", "testcommand");
-  command.setAttribute("oncommand", "sawEvent(event, true)");
+  command.setAttribute("oncommand", "window.sawEvent(event, true)");
   document.getElementById("mainCommandSet").appendChild(command);
-  command.addEventListener("command", sawEvent);
+  command.addEventListener("command", window.sawEvent);
 
   gTestSidebarItem.setAttribute("command", "testcommand");
-  gTestSidebarItem.addEventListener("click", sawEvent);
+  gTestSidebarItem.addEventListener("click", window.sawEvent);
 }, function*() {
   checkExpectedEvents({ click: 1, command: 1, oncommand: 1 });
   document.getElementById("testcommand").remove();
 });
--- a/browser/components/extensions/ext-browserAction.js
+++ b/browser/components/extensions/ext-browserAction.js
@@ -21,16 +21,25 @@ var {
   EventManager,
   IconDetails,
 } = ExtensionUtils;
 
 const POPUP_PRELOAD_TIMEOUT_MS = 200;
 
 const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
 
+function isAncestorOrSelf(target, node) {
+  for (; node; node = node.parentNode) {
+    if (node === target) {
+      return true;
+    }
+  }
+  return false;
+}
+
 // WeakMap[Extension -> BrowserAction]
 var browserActionMap = new WeakMap();
 
 // Responsible for the browser_action section of the manifest as well
 // as the associated popup.
 function BrowserAction(options, extension) {
   this.extension = extension;
 
@@ -82,16 +91,18 @@ BrowserAction.prototype = {
         view.setAttribute("flex", "1");
 
         document.getElementById("PanelUI-multiView").appendChild(view);
       },
 
       onDestroyed: document => {
         let view = document.getElementById(this.viewId);
         if (view) {
+          this.clearPopup();
+          CustomizableUI.hidePanelForNode(view);
           view.remove();
         }
       },
 
       onCreated: node => {
         node.classList.add("badged-button");
         node.classList.add("webextension-browser-action");
         node.setAttribute("constrain-size", "true");
@@ -193,17 +204,18 @@ BrowserAction.prototype = {
         break;
 
       case "mouseup":
         if (event.button == 0) {
           this.clearPopupTimeout();
           // If we have a pending pre-loaded popup, cancel it after we've waited
           // long enough that we can be relatively certain it won't be opening.
           if (this.pendingPopup) {
-            if (event.target === this.widget.forWindow(window).node) {
+            let {node} = this.widget.forWindow(window);
+            if (isAncestorOrSelf(node, event.originalTarget)) {
               this.pendingPopupTimeout = setTimeout(() => this.clearPopup(),
                                                     POPUP_PRELOAD_TIMEOUT_MS);
             } else {
               this.clearPopup();
             }
           }
         }
         break;
@@ -442,19 +454,21 @@ extensions.registerSchemaAPI("browserAct
 
         let title = BrowserAction.for(extension).getProperty(tab, "title");
         return Promise.resolve(title);
       },
 
       setIcon: function(details) {
         let tab = details.tabId !== null ? TabManager.getTab(details.tabId, context) : null;
 
+        // Note: the caller in the child process has already normalized
+        // `details` to not contain an `imageData` property, so the icon can
+        // safely be normalized here without errors.
         let icon = IconDetails.normalize(details, extension, context);
         BrowserAction.for(extension).setProperty(tab, "icon", icon);
-        return Promise.resolve();
       },
 
       setBadgeText: function(details) {
         let tab = details.tabId !== null ? TabManager.getTab(details.tabId, context) : null;
 
         BrowserAction.for(extension).setProperty(tab, "badgeText", details.text);
       },
 
new file mode 100644
--- /dev/null
+++ b/browser/components/extensions/ext-c-browserAction.js
@@ -0,0 +1,26 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+"use strict";
+
+Cu.import("resource://gre/modules/ExtensionUtils.jsm");
+var {
+  IconDetails,
+} = ExtensionUtils;
+
+extensions.registerSchemaAPI("browserAction", "addon_child", context => {
+  return {
+    browserAction: {
+      setIcon: function(details) {
+        // This needs to run in the addon process because normalization requires
+        // the use of <canvas>.
+        let normalizedDetails = {
+          tabId: details.tabId,
+          path: IconDetails.normalize(details, context.extension, context),
+        };
+        return context.childManager.callParentAsyncFunction("browserAction.setIcon", [
+          normalizedDetails,
+        ]);
+      },
+    },
+  };
+});
new file mode 100644
--- /dev/null
+++ b/browser/components/extensions/ext-c-contextMenus.js
@@ -0,0 +1,158 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+"use strict";
+
+// If id is not specified for an item we use an integer.
+// This ID need only be unique within a single addon. Since all addon code that
+// can use this API runs in the same process, this local variable suffices.
+var gNextMenuItemID = 0;
+
+// Map[Extension -> Map[string or id, ContextMenusClickPropHandler]]
+var gPropHandlers = new Map();
+
+// The contextMenus API supports an "onclick" attribute in the create/update
+// methods to register a callback. This class manages these onclick properties.
+class ContextMenusClickPropHandler {
+  constructor(context) {
+    this.context = context;
+    // Map[string or integer -> callback]
+    this.onclickMap = new Map();
+    this.dispatchEvent = this.dispatchEvent.bind(this);
+  }
+
+  // A listener on contextMenus.onClicked that forwards the event to the only
+  // listener, if any.
+  dispatchEvent(info, tab) {
+    let onclick = this.onclickMap.get(info.menuItemId);
+    if (onclick) {
+      // No need for runSafe or anything because we are already being run inside
+      // an event handler -- the event is just being forwarded to the actual
+      // handler.
+      onclick(info, tab);
+    }
+  }
+
+  // Sets the `onclick` handler for the given menu item.
+  // The `onclick` function MUST be owned by `this.context`.
+  setListener(id, onclick) {
+    if (this.onclickMap.size === 0) {
+      this.context.childManager.getParentEvent("contextMenus.onClicked").addListener(this.dispatchEvent);
+      this.context.callOnClose(this);
+    }
+    this.onclickMap.set(id, onclick);
+
+    let propHandlerMap = gPropHandlers.get(this.context.extension);
+    if (!propHandlerMap) {
+      propHandlerMap = new Map();
+    } else {
+      // If the current callback was created in a different context, remove it
+      // from the other context.
+      let propHandler = propHandlerMap.get(id);
+      if (propHandler && propHandler !== this) {
+        propHandler.unsetListener(id);
+      }
+    }
+    propHandlerMap.set(id, this);
+    gPropHandlers.set(this.context.extension, propHandlerMap);
+  }
+
+  // Deletes the `onclick` handler for the given menu item.
+  // The `onclick` function MUST be owned by `this.context`.
+  unsetListener(id) {
+    if (!this.onclickMap.delete(id)) {
+      return;
+    }
+    if (this.onclickMap.size === 0) {
+      this.context.childManager.getParentEvent("contextMenus.onClicked").removeListener(this.dispatchEvent);
+      this.context.forgetOnClose(this);
+    }
+    let propHandlerMap = gPropHandlers.get(this.context.extension);
+    propHandlerMap.delete(id);
+    if (propHandlerMap.size === 0) {
+      gPropHandlers.delete(this.context.extension);
+    }
+  }
+
+  // Deletes the `onclick` handler for the given menu item, if any, regardless
+  // of the context where it was created.
+  unsetListenerFromAnyContext(id) {
+    let propHandlerMap = gPropHandlers.get(this.context.extension);
+    let propHandler = propHandlerMap && propHandlerMap.get(id);
+    if (propHandler) {
+      propHandler.unsetListener(id);
+    }
+  }
+
+  // Remove all `onclick` handlers of the extension.
+  deleteAllListenersFromExtension() {
+    let propHandlerMap = gPropHandlers.get(this.context.extension);
+    if (propHandlerMap) {
+      for (let [id, propHandler] of propHandlerMap) {
+        propHandler.unsetListener(id);
+      }
+    }
+  }
+
+  // Removes all `onclick` handlers from this context.
+  close() {
+    for (let id of this.onclickMap.keys()) {
+      this.unsetListener(id);
+    }
+  }
+}
+
+extensions.registerSchemaAPI("contextMenus", "addon_child", context => {
+  let onClickedProp = new ContextMenusClickPropHandler(context);
+
+  return {
+    contextMenus: {
+      create(createProperties, callback) {
+        if (createProperties.id === null) {
+          createProperties.id = ++gNextMenuItemID;
+        }
+        let {onclick} = createProperties;
+        delete createProperties.onclick;
+        context.childManager.callParentAsyncFunction("contextMenus.createInternal", [
+          createProperties,
+        ]).then(() => {
+          if (onclick) {
+            onClickedProp.setListener(createProperties.id, onclick);
+          }
+          if (callback) {
+            callback();
+          }
+        });
+        return createProperties.id;
+      },
+
+      update(id, updateProperties) {
+        let {onclick} = updateProperties;
+        delete updateProperties.onclick;
+        return context.childManager.callParentAsyncFunction("contextMenus.update", [
+          id,
+          updateProperties,
+        ]).then(() => {
+          if (onclick) {
+            onClickedProp.setListener(id, onclick);
+          } else if (onclick === null) {
+            onClickedProp.unsetListenerFromAnyContext(id);
+          }
+          // else onclick is not set so it should not be changed.
+        });
+      },
+
+      remove(id) {
+        onClickedProp.unsetListenerFromAnyContext(id);
+        return context.childManager.callParentAsyncFunction("contextMenus.remove", [
+          id,
+        ]);
+      },
+
+      removeAll() {
+        onClickedProp.deleteAllListenersFromExtension();
+
+        return context.childManager.callParentAsyncFunction("contextMenus.removeAll", []);
+      },
+    },
+  };
+});
new file mode 100644
--- /dev/null
+++ b/browser/components/extensions/ext-c-pageAction.js
@@ -0,0 +1,26 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+"use strict";
+
+Cu.import("resource://gre/modules/ExtensionUtils.jsm");
+var {
+  IconDetails,
+} = ExtensionUtils;
+
+extensions.registerSchemaAPI("pageAction", "addon_child", context => {
+  return {
+    pageAction: {
+      setIcon: function(details) {
+        // This needs to run in the addon process because normalization requires
+        // the use of <canvas>.
+        let normalizedDetails = {
+          tabId: details.tabId,
+          path: IconDetails.normalize(details, context.extension, context),
+        };
+        return context.childManager.callParentAsyncFunction("pageAction.setIcon", [
+          normalizedDetails,
+        ]);
+      },
+    },
+  };
+});
--- a/browser/components/extensions/ext-contextMenus.js
+++ b/browser/components/extensions/ext-contextMenus.js
@@ -5,17 +5,16 @@
 Cu.import("resource://gre/modules/ExtensionUtils.jsm");
 Cu.import("resource://gre/modules/MatchPattern.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 
 var {
   EventManager,
   IconDetails,
-  runSafe,
 } = ExtensionUtils;
 
 // Map[Extension -> Map[ID -> MenuItem]]
 // Note: we want to enumerate all the menu items so
 // this cannot be a weak map.
 var gContextMenuMap = new Map();
 
 // Map[Extension -> MenuItem]
@@ -188,19 +187,16 @@ var gMenuBuilder = {
         item.checked = true;
       }
 
       item.tabManager.addActiveTabPermission();
 
       let tab = item.tabManager.convert(contextData.tab);
       let info = item.getClickInfo(contextData, wasChecked);
       item.extension.emit("webext-contextmenu-menuitem-click", info, tab);
-      if (item.onclick) {
-        runSafe(item.extContext, item.onclick, info, tab);
-      }
     });
 
     return element;
   },
 
   handleEvent: function(event) {
     if (this.xulMenu != event.target || event.type != "popuphidden") {
       return;
@@ -254,19 +250,18 @@ function getContexts(contextData) {
 
   if (contextData.onAudio) {
     contexts.add("audio");
   }
 
   return contexts;
 }
 
-function MenuItem(extension, extContext, createProperties, isRoot = false) {
+function MenuItem(extension, createProperties, isRoot = false) {
   this.extension = extension;
-  this.extContext = extContext;
   this.children = [];
   this.parent = null;
   this.tabManager = TabManager.for(extension);
 
   this.setDefaults();
   this.setProps(createProperties);
   if (!this.hasOwnProperty("_id")) {
     this.id = gNextMenuItemID++;
@@ -370,17 +365,17 @@ MenuItem.prototype = {
     }
     this.children.splice(idx, 1);
     child.parent = null;
   },
 
   get root() {
     let extension = this.extension;
     if (!gRootItems.has(extension)) {
-      let root = new MenuItem(extension, this.context,
+      let root = new MenuItem(extension,
                               {title: extension.name},
                               /* isRoot = */ true);
       gRootItems.set(extension, root);
     }
 
     return gRootItems.get(extension);
   },
 
@@ -490,47 +485,43 @@ extensions.on("shutdown", (type, extensi
   }
 });
 /* eslint-enable mozilla/balanced-listeners */
 
 extensions.registerSchemaAPI("contextMenus", "addon_parent", context => {
   let {extension} = context;
   return {
     contextMenus: {
-      create: function(createProperties, callback) {
-        let menuItem = new MenuItem(extension, context, createProperties);
+      createInternal: function(createProperties) {
+        // Note that the id is required by the schema. If the addon did not set
+        // it, the implementation of contextMenus.create in the child should
+        // have added it.
+        let menuItem = new MenuItem(extension, createProperties);
         gContextMenuMap.get(extension).set(menuItem.id, menuItem);
-        if (callback) {
-          runSafe(context, callback);
-        }
-        return menuItem.id;
       },
 
       update: function(id, updateProperties) {
         let menuItem = gContextMenuMap.get(extension).get(id);
         if (menuItem) {
           menuItem.setProps(updateProperties);
         }
-        return Promise.resolve();
       },
 
       remove: function(id) {
         let menuItem = gContextMenuMap.get(extension).get(id);
         if (menuItem) {
           menuItem.remove();
         }
-        return Promise.resolve();
       },
 
       removeAll: function() {
         let root = gRootItems.get(extension);
         if (root) {
           root.remove();
         }
-        return Promise.resolve();
       },
 
       onClicked: new EventManager(context, "contextMenus.onClicked", fire => {
         let listener = (event, info, tab) => {
           fire(info, tab);
         };
 
         extension.on("webext-contextmenu-menuitem-click", listener);
--- a/browser/components/extensions/ext-pageAction.js
+++ b/browser/components/extensions/ext-pageAction.js
@@ -231,23 +231,21 @@ extensions.registerSchemaAPI("pageAction
         return () => {
           pageAction.off("click", listener);
         };
       }).api(),
 
       show(tabId) {
         let tab = TabManager.getTab(tabId, context);
         PageAction.for(extension).setProperty(tab, "show", true);
-        return Promise.resolve();
       },
 
       hide(tabId) {
         let tab = TabManager.getTab(tabId, context);
         PageAction.for(extension).setProperty(tab, "show", false);
-        return Promise.resolve();
       },
 
       setTitle(details) {
         let tab = TabManager.getTab(details.tabId, context);
 
         // Clear the tab-specific title when given a null string.
         PageAction.for(extension).setProperty(tab, "title", details.title || null);
       },
@@ -257,19 +255,21 @@ extensions.registerSchemaAPI("pageAction
 
         let title = PageAction.for(extension).getProperty(tab, "title");
         return Promise.resolve(title);
       },
 
       setIcon(details) {
         let tab = TabManager.getTab(details.tabId, context);
 
+        // Note: the caller in the child process has already normalized
+        // `details` to not contain an `imageData` property, so the icon can
+        // safely be normalized here without errors.
         let icon = IconDetails.normalize(details, extension, context);
         PageAction.for(extension).setProperty(tab, "icon", icon);
-        return Promise.resolve();
       },
 
       setPopup(details) {
         let tab = TabManager.getTab(details.tabId, context);
 
         // Note: Chrome resolves arguments to setIcon relative to the calling
         // context, but resolves arguments to setPopup relative to the extension
         // root.
--- a/browser/components/extensions/ext-tabs.js
+++ b/browser/components/extensions/ext-tabs.js
@@ -48,72 +48,50 @@ function getSender(extension, target, se
       }
     }
   }
 }
 
 // Used by Extension.jsm
 global.tabGetSender = getSender;
 
-function getDocShellOwner(docShell) {
-  let browser = docShell.chromeEventHandler;
-
-  let xulWindow = browser.ownerGlobal;
-
-  let {gBrowser} = xulWindow;
-  if (gBrowser) {
-    let tab = gBrowser.getTabForBrowser(browser);
-
-    return {xulWindow, tab};
-  }
-
-  return {};
-}
-
 /* eslint-disable mozilla/balanced-listeners */
-// This listener fires whenever an extension page opens in a tab
-// (either initiated by the extension or the user). Its job is to fill
-// in some tab-specific details and keep data around about the
-// ExtensionContext.
-extensions.on("page-load", (type, context, params, sender) => {
-  if (params.type == "tab" || params.type == "popup") {
-    let {xulWindow, tab} = getDocShellOwner(params.docShell);
-
-    // FIXME: Handle tabs being moved between windows.
-    context.windowId = WindowManager.getId(xulWindow);
-    if (tab) {
-      sender.tabId = TabManager.getId(tab);
-      context.tabId = TabManager.getId(tab);
-    }
-  }
-});
 
 extensions.on("page-shutdown", (type, context) => {
-  if (context.type == "tab") {
-    let {xulWindow, tab} = getDocShellOwner(context.docShell);
-    if (tab) {
-      xulWindow.gBrowser.removeTab(tab);
+  if (context.viewType == "tab") {
+    if (context.extension.id !== context.xulBrowser.contentPrincipal.addonId) {
+      // Only close extension tabs.
+      // This check prevents about:addons from closing when it contains a
+      // WebExtension as an embedded inline options page.
+      return;
+    }
+    let {gBrowser} = context.xulBrowser.ownerGlobal;
+    if (gBrowser) {
+      let tab = gBrowser.getTabForBrowser(context.xulBrowser);
+      if (tab) {
+        gBrowser.removeTab(tab);
+      }
     }
   }
 });
 
 extensions.on("fill-browser-data", (type, browser, data, result) => {
   let tabId = TabManager.getBrowserId(browser);
   if (tabId == -1) {
     result.cancel = true;
     return;
   }
 
   data.tabId = tabId;
 });
 /* eslint-enable mozilla/balanced-listeners */
 
 global.currentWindow = function(context) {
-  let {xulWindow} = getDocShellOwner(context.docShell);
-  if (xulWindow) {
+  let {xulWindow} = context;
+  if (xulWindow && context.viewType != "background") {
     return xulWindow;
   }
   return WindowManager.topWindow;
 };
 
 let tabListener = {
   init() {
     if (this.initialized) {
@@ -130,23 +108,16 @@ let tabListener = {
     WindowListManager.addOpenListener(this.handleWindowOpen);
     WindowListManager.addCloseListener(this.handleWindowClose);
 
     EventEmitter.decorate(this);
 
     this.initialized = true;
   },
 
-  destroy() {
-    AllWindowEvents.removeListener("TabClose", this);
-    AllWindowEvents.removeListener("TabOpen", this);
-    WindowListManager.removeOpenListener(this.handleWindowOpen);
-    WindowListManager.removeCloseListener(this.handleWindowClose);
-  },
-
   handleEvent(event) {
     switch (event.type) {
       case "TabOpen":
         if (event.detail.adoptedTab) {
           this.adoptedTabs.set(event.detail.adoptedTab, event.target);
         }
 
         // We need to delay sending this event until the next tick, since the
@@ -233,17 +204,27 @@ let tabListener = {
   emitCreated(tab) {
     this.emit("tab-created", {tab});
   },
 
   emitRemoved(tab, isWindowClosing) {
     let windowId = WindowManager.getId(tab.ownerGlobal);
     let tabId = TabManager.getId(tab);
 
-    this.emit("tab-removed", {tab, tabId, windowId, isWindowClosing});
+    // When addons run in-process, `window.close()` is synchronous. Most other
+    // addon-invoked calls are asynchronous since they go through a proxy
+    // context via the message manager. This includes event registrations such
+    // as `tabs.onRemoved.addListener`.
+    // So, even if `window.close()` were to be called (in-process) after calling
+    // `tabs.onRemoved.addListener`, then the tab would be closed before the
+    // event listener is registered. To make sure that the event listener is
+    // notified, we dispatch `tabs.onRemoved` asynchronously.
+    Services.tm.mainThread.dispatch(() => {
+      this.emit("tab-removed", {tab, tabId, windowId, isWindowClosing});
+    }, Ci.nsIThread.DISPATCH_NORMAL);
   },
 
   tabReadyInitialized: false,
   tabReadyPromises: new WeakMap(),
   initializingTabs: new WeakSet(),
 
   initTabReady() {
     if (!this.tabReadyInitialized) {
@@ -287,33 +268,38 @@ let tabListener = {
       } else {
         this.tabReadyPromises.set(tab, deferred);
       }
     }
     return deferred.promise;
   },
 };
 
+/* eslint-disable mozilla/balanced-listeners */
+extensions.on("startup", () => {
+  tabListener.init();
+});
+/* eslint-enable mozilla/balanced-listeners */
+
 extensions.registerSchemaAPI("tabs", "addon_parent", context => {
   let {extension} = context;
   let self = {
     tabs: {
       onActivated: new WindowEventManager(context, "tabs.onActivated", "TabSelect", (fire, event) => {
         let tab = event.originalTarget;
         let tabId = TabManager.getId(tab);
         let windowId = WindowManager.getId(tab.ownerGlobal);
         fire({tabId, windowId});
       }).api(),
 
       onCreated: new EventManager(context, "tabs.onCreated", fire => {
         let listener = (eventName, event) => {
           fire(TabManager.convert(extension, event.tab));
         };
 
-        tabListener.init();
         tabListener.on("tab-created", listener);
         return () => {
           tabListener.off("tab-created", listener);
         };
       }).api(),
 
       /**
        * Since multiple tabs currently can't be highlighted, onHighlighted
@@ -328,41 +314,38 @@ extensions.registerSchemaAPI("tabs", "ad
         fire({tabIds, windowId});
       }).api(),
 
       onAttached: new EventManager(context, "tabs.onAttached", fire => {
         let listener = (eventName, event) => {
           fire(event.tabId, {newWindowId: event.newWindowId, newPosition: event.newPosition});
         };
 
-        tabListener.init();
         tabListener.on("tab-attached", listener);
         return () => {
           tabListener.off("tab-attached", listener);
         };
       }).api(),
 
       onDetached: new EventManager(context, "tabs.onDetached", fire => {
         let listener = (eventName, event) => {
           fire(event.tabId, {oldWindowId: event.oldWindowId, oldPosition: event.oldPosition});
         };
 
-        tabListener.init();
         tabListener.on("tab-detached", listener);
         return () => {
           tabListener.off("tab-detached", listener);
         };
       }).api(),
 
       onRemoved: new EventManager(context, "tabs.onRemoved", fire => {
         let listener = (eventName, event) => {
           fire(event.tabId, {windowId: event.windowId, isWindowClosing: event.isWindowClosing});
         };
 
-        tabListener.init();
         tabListener.on("tab-removed", listener);
         return () => {
           tabListener.off("tab-removed", listener);
         };
       }).api(),
 
       onReplaced: ignoreEvent(context, "tabs.onReplaced"),
 
@@ -857,19 +840,19 @@ extensions.registerSchemaAPI("tabs", "ad
         let tabsMoved = [];
         if (!Array.isArray(tabIds)) {
           tabIds = [tabIds];
         }
 
         let destinationWindow = null;
         if (moveProperties.windowId !== null) {
           destinationWindow = WindowManager.getWindow(moveProperties.windowId, context);
-          // Ignore invalid window.
+          // Fail on an invalid window.
           if (!destinationWindow) {
-            return;
+            return Promise.reject({message: `Invalid window ID: ${moveProperties.windowId}`});
           }
         }
 
         /*
           Indexes are maintained on a per window basis so that a call to
             move([tabA, tabB], {index: 0})
               -> tabA to 0, tabB to 1 if tabA and tabB are in the same window
             move([tabA, tabB], {index: 0})
@@ -1054,17 +1037,16 @@ extensions.registerSchemaAPI("tabs", "ad
               tabId,
               oldZoomFactor,
               newZoomFactor,
               zoomSettings: self.tabs._getZoomSettings(tabId),
             });
           }
         };
 
-        tabListener.init();
         tabListener.on("tab-attached", tabCreated);
         tabListener.on("tab-created", tabCreated);
 
         AllWindowEvents.addListener("FullZoomChange", zoomListener);
         AllWindowEvents.addListener("TextZoomChange", zoomListener);
         return () => {
           tabListener.off("tab-attached", tabCreated);
           tabListener.off("tab-created", tabCreated);
--- a/browser/components/extensions/ext-utils.js
+++ b/browser/components/extensions/ext-utils.js
@@ -23,22 +23,20 @@ XPCOMUtils.defineLazyGetter(this, "color
 
 Cu.import("resource://gre/modules/ExtensionUtils.jsm");
 Cu.import("resource://gre/modules/AppConstants.jsm");
 
 const POPUP_LOAD_TIMEOUT_MS = 200;
 
 const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
 
-// Minimum time between two resizes.
-const RESIZE_TIMEOUT = 100;
-
 var {
   DefaultWeakMap,
   EventManager,
+  promiseEvent,
 } = ExtensionUtils;
 
 // This file provides some useful code for the |tabs| and |windows|
 // modules. All of the code is installed on |global|, which is a scope
 // shared among the different ext-*.js scripts.
 
 global.makeWidgetId = id => {
   id = id.toLowerCase();
@@ -54,67 +52,55 @@ function promisePopupShown(popup) {
       popup.addEventListener("popupshown", function onPopupShown(event) {
         popup.removeEventListener("popupshown", onPopupShown);
         resolve();
       });
     }
   });
 }
 
-XPCOMUtils.defineLazyGetter(this, "stylesheets", () => {
-  let styleSheetURI = NetUtil.newURI("chrome://browser/content/extension.css");
-  let styleSheet = styleSheetService.preloadSheet(styleSheetURI,
-                                                  styleSheetService.AGENT_SHEET);
-  let stylesheets = [styleSheet];
+XPCOMUtils.defineLazyGetter(this, "popupStylesheets", () => {
+  let stylesheets = ["chrome://browser/content/extension.css"];
 
   if (AppConstants.platform === "macosx") {
-    styleSheetURI = NetUtil.newURI("chrome://browser/content/extension-mac.css");
-    let macStyleSheet = styleSheetService.preloadSheet(styleSheetURI,
-                                                       styleSheetService.AGENT_SHEET);
-    stylesheets.push(macStyleSheet);
+    stylesheets.push("chrome://browser/content/extension-mac.css");
   }
   return stylesheets;
 });
 
 XPCOMUtils.defineLazyGetter(this, "standaloneStylesheets", () => {
   let stylesheets = [];
 
   if (AppConstants.platform === "macosx") {
-    let styleSheetURI = NetUtil.newURI("chrome://browser/content/extension-mac-panel.css");
-    let macStyleSheet = styleSheetService.preloadSheet(styleSheetURI,
-                                                       styleSheetService.AGENT_SHEET);
-    stylesheets.push(macStyleSheet);
+    stylesheets.push("chrome://browser/content/extension-mac-panel.css");
   }
   if (AppConstants.platform === "win") {
-    let styleSheetURI = NetUtil.newURI("chrome://browser/content/extension-win-panel.css");
-    let winStyleSheet = styleSheetService.preloadSheet(styleSheetURI,
-                                                       styleSheetService.AGENT_SHEET);
-    stylesheets.push(winStyleSheet);
+    stylesheets.push("chrome://browser/content/extension-win-panel.css");
   }
   return stylesheets;
 });
 
 /* eslint-disable mozilla/balanced-listeners */
 extensions.on("page-shutdown", (type, context) => {
-  if (context.type == "popup" && context.active) {
-    context.contentWindow.close();
+  if (context.viewType == "popup" && context.active) {
+    // TODO(robwu): This is not webext-oop compatible.
+    context.xulBrowser.contentWindow.close();
   }
 });
 /* eslint-enable mozilla/balanced-listeners */
 
 class BasePopup {
   constructor(extension, viewNode, popupURL, browserStyle, fixedWidth = false) {
     this.extension = extension;
     this.popupURL = popupURL;
     this.viewNode = viewNode;
     this.browserStyle = browserStyle;
     this.window = viewNode.ownerGlobal;
     this.destroyed = false;
     this.fixedWidth = fixedWidth;
-    this.ignoreResizes = true;
 
     this.contentReady = new Promise(resolve => {
       this._resolveContentReady = resolve;
     });
 
     this.viewNode.addEventListener(this.DESTROY_EVENT, this);
 
     let doc = viewNode.ownerDocument;
@@ -152,304 +138,234 @@ class BasePopup {
       BasePopup.instances.get(this.window).delete(this.extension);
 
       this.browser = null;
       this.viewNode = null;
     });
   }
 
   destroyBrowser(browser) {
-    browser.removeEventListener("DOMWindowCreated", this, true);
-    browser.removeEventListener("load", this, true);
-    browser.removeEventListener("DOMContentLoaded", this, true);
-    browser.removeEventListener("DOMTitleChanged", this, true);
-    browser.removeEventListener("DOMWindowClose", this, true);
-    browser.removeEventListener("MozScrolledAreaChanged", this, true);
+    let mm = browser.messageManager;
+    // If the browser has already been removed from the document, because the
+    // popup was closed externally, there will be no message manager here.
+    if (mm) {
+      mm.removeMessageListener("DOMTitleChanged", this);
+      mm.removeMessageListener("Extension:BrowserBackgroundChanged", this);
+      mm.removeMessageListener("Extension:BrowserContentLoaded", this);
+      mm.removeMessageListener("Extension:BrowserResized", this);
+      mm.removeMessageListener("Extension:DOMWindowClose", this);
+    }
   }
 
   // Returns the name of the event fired on `viewNode` when the popup is being
   // destroyed. This must be implemented by every subclass.
   get DESTROY_EVENT() {
     throw new Error("Not implemented");
   }
 
+  get STYLESHEETS() {
+    let sheets = [];
+
+    if (this.browserStyle) {
+      sheets.push(...popupStylesheets);
+    }
+    if (!this.fixedWidth) {
+      sheets.push(...standaloneStylesheets);
+    }
+
+    return sheets;
+  }
+
   get panel() {
     let panel = this.viewNode;
     while (panel && panel.localName != "panel") {
       panel = panel.parentNode;
     }
     return panel;
   }
 
+  receiveMessage({name, data}) {
+    switch (name) {
+      case "DOMTitleChanged":
+        this.viewNode.setAttribute("aria-label", this.browser.contentTitle);
+        break;
+
+      case "Extension:BrowserBackgroundChanged":
+        this.setBackground(data.background);
+        break;
+
+      case "Extension:BrowserContentLoaded":
+        this.browserLoadedDeferred.resolve();
+        break;
+
+      case "Extension:BrowserResized":
+        this._resolveContentReady();
+        if (this.ignoreResizes) {
+          this.dimensions = data;
+        } else {
+          this.resizeBrowser(data);
+        }
+        break;
+
+      case "Extension:DOMWindowClose":
+        this.closePopup();
+        break;
+    }
+  }
+
   handleEvent(event) {
     switch (event.type) {
       case this.DESTROY_EVENT:
         this.destroy();
         break;
-
-      case "DOMWindowCreated":
-        if (event.target === this.browser.contentDocument) {
-          let winUtils = this.browser.contentWindow
-                             .QueryInterface(Ci.nsIInterfaceRequestor)
-                             .getInterface(Ci.nsIDOMWindowUtils);
-
-          if (this.browserStyle) {
-            for (let stylesheet of stylesheets) {
-              winUtils.addSheet(stylesheet, winUtils.AGENT_SHEET);
-            }
-          }
-          if (!this.fixedWidth) {
-            for (let stylesheet of standaloneStylesheets) {
-              winUtils.addSheet(stylesheet, winUtils.AGENT_SHEET);
-            }
-          }
-        }
-        break;
-
-      case "DOMWindowClose":
-        if (event.target === this.browser.contentWindow) {
-          event.preventDefault();
-          this.closePopup();
-        }
-        break;
-
-      case "DOMTitleChanged":
-        this.viewNode.setAttribute("aria-label", this.browser.contentTitle);
-        break;
-
-      case "DOMContentLoaded":
-        this.browserLoadedDeferred.resolve();
-        this.resizeBrowser(true);
-        break;
-
-      case "load":
-        // We use a capturing listener, so we get this event earlier than any
-        // load listeners in the content page. Resizing after a timeout ensures
-        // that we calculate the size after the entire event cycle has completed
-        // (unless someone spins the event loop, anyway), and hopefully after
-        // the content has made any modifications.
-        Promise.resolve().then(() => {
-          this.resizeBrowser(true);
-        });
-
-        // Mutation observer to make sure the panel shrinks when the content does.
-        new this.browser.contentWindow.MutationObserver(this.resizeBrowser.bind(this)).observe(
-          this.browser.contentDocument.documentElement, {
-            attributes: true,
-            characterData: true,
-            childList: true,
-            subtree: true,
-          });
-        break;
-
-      case "MozScrolledAreaChanged":
-        this.resizeBrowser();
-        break;
     }
   }
 
   createBrowser(viewNode, popupURL = null) {
     let document = viewNode.ownerDocument;
     this.browser = document.createElementNS(XUL_NS, "browser");
     this.browser.setAttribute("type", "content");
     this.browser.setAttribute("disableglobalhistory", "true");
     this.browser.setAttribute("transparent", "true");
     this.browser.setAttribute("class", "webextension-popup-browser");
-    this.browser.setAttribute("webextension-view-type", "popup");
     this.browser.setAttribute("tooltip", "aHTMLTooltip");
 
     // We only need flex sizing for the sake of the slide-in sub-views of the
     // main menu panel, so that the browser occupies the full width of the view,
     // and also takes up any extra height that's available to it.
     this.browser.setAttribute("flex", "1");
 
     // Note: When using noautohide panels, the popup manager will add width and
     // height attributes to the panel, breaking our resize code, if the browser
     // starts out smaller than 30px by 10px. This isn't an issue now, but it
     // will be if and when we popup debugging.
 
     viewNode.appendChild(this.browser);
 
+    extensions.emit("extension-browser-inserted", this.browser);
+    let windowId = WindowManager.getId(this.browser.ownerGlobal);
+    this.browser.messageManager.sendAsyncMessage("Extension:InitExtensionView", {
+      viewType: "popup",
+      windowId,
+    });
+    // TODO(robwu): Rework this to use the Extension:ExtensionViewLoaded message
+    // to detect loads and so on. And definitely move this content logic inside
+    // a file in the child process.
+
     let initBrowser = browser => {
-      browser.addEventListener("DOMWindowCreated", this, true);
-      browser.addEventListener("load", this, true);
-      browser.addEventListener("DOMContentLoaded", this, true);
-      browser.addEventListener("DOMTitleChanged", this, true);
-      browser.addEventListener("DOMWindowClose", this, true);
-      browser.addEventListener("MozScrolledAreaChanged", this, true);
+      let mm = browser.messageManager;
+      mm.addMessageListener("DOMTitleChanged", this);
+      mm.addMessageListener("Extension:BrowserBackgroundChanged", this);
+      mm.addMessageListener("Extension:BrowserContentLoaded", this);
+      mm.addMessageListener("Extension:BrowserResized", this);
+      mm.addMessageListener("Extension:DOMWindowClose", this, true);
     };
 
     if (!popupURL) {
       initBrowser(this.browser);
       return this.browser;
     }
 
-    return new Promise(resolve => {
-      // The first load event is for about:blank.
-      // We can't finish setting up the browser until the binding has fully
-      // initialized. Waiting for the first load event guarantees that it has.
-      let loadListener = event => {
-        this.browser.removeEventListener("load", loadListener, true);
-        resolve();
-      };
-      this.browser.addEventListener("load", loadListener, true);
-    }).then(() => {
+    return promiseEvent(this.browser, "load").then(() => {
       initBrowser(this.browser);
 
-      let {contentWindow} = this.browser;
+      let mm = this.browser.messageManager;
+
+      mm.loadFrameScript(
+        "chrome://extensions/content/ext-browser-content.js", false);
 
-      contentWindow.QueryInterface(Ci.nsIInterfaceRequestor)
-                   .getInterface(Ci.nsIDOMWindowUtils)
-                   .allowScriptsToClose();
+      mm.sendAsyncMessage("Extension:InitBrowser", {
+        allowScriptsToClose: true,
+        fixedWidth: this.fixedWidth,
+        maxWidth: 800,
+        maxHeight: 600,
+        stylesheets: this.STYLESHEETS,
+      });
 
       this.browser.setAttribute("src", popupURL);
     });
   }
 
-  // Resizes the browser to match the preferred size of the content (debounced).
-  resizeBrowser(ignoreThrottling = false) {
-    if (this.ignoreResizes) {
-      return;
-    }
-
-    if (ignoreThrottling && this.resizeTimeout) {
-      this.window.clearTimeout(this.resizeTimeout);
-      this.resizeTimeout = null;
-    }
-
-    if (this.resizeTimeout == null) {
-      this.resizeTimeout = this.window.setTimeout(() => {
-        try {
-          this._resizeBrowser();
-        } finally {
-          this.resizeTimeout = null;
-        }
-      }, RESIZE_TIMEOUT);
-
-      this._resizeBrowser();
-    }
-  }
-
-  _resizeBrowser() {
-    let doc = this.browser && this.browser.contentDocument;
-    if (!doc || !doc.documentElement) {
-      return;
-    }
-
-    let root = doc.documentElement;
-    let body = doc.body;
-    if (!body || doc.compatMode == "BackCompat") {
-      // In quirks mode, the root element is used as the scroll frame, and the
-      // body lies about its scroll geometry, and returns the values for the
-      // root instead.
-      body = root;
-    }
-
-
+  resizeBrowser({width, height, detail}) {
     if (this.fixedWidth) {
-      // If we're in a fixed-width area (namely a slide-in subview of the main
-      // menu panel), we need to calculate the view height based on the
-      // preferred height of the content document's root scrollable element at the
-      // current width, rather than the complete preferred dimensions of the
-      // content window.
-
-      // Compensate for any offsets (margin, padding, ...) between the scroll
-      // area of the body and the outer height of the document.
-      let getHeight = elem => elem.getBoundingClientRect(elem).height;
-      let bodyPadding = getHeight(root) - getHeight(body);
-
-      let height = Math.ceil(body.scrollHeight + bodyPadding);
-
       // Figure out how much extra space we have on the side of the panel
       // opposite the arrow.
       let side = this.panel.getAttribute("side") == "top" ? "bottom" : "top";
       let maxHeight = this.viewHeight + this.extraHeight[side];
 
       height = Math.min(height, maxHeight);
       this.browser.style.height = `${height}px`;
 
       // Set a maximum height on the <panelview> element to our preferred
       // maximum height, so that the PanelUI resizing code can make an accurate
       // calculation. If we don't do this, the flex sizing logic will prevent us
       // from ever reporting a preferred size smaller than the height currently
       // available to us in the panel.
       height = Math.max(height, this.viewHeight);
       this.viewNode.style.maxHeight = `${height}px`;
     } else {
-      // Copy the background color of the document's body to the panel if it's
-      // fully opaque.
-      let panelBackground = "";
-      let panelArrow = "";
-
-      let background = doc.defaultView.getComputedStyle(body).backgroundColor;
-      if (background != "transparent") {
-        let bgColor = colorUtils.colorToRGBA(background);
-        if (bgColor.a == 1) {
-          panelBackground = background;
-          let borderColor = this.borderColor || background;
-
-          panelArrow = `url("data:image/svg+xml,${encodeURIComponent(`<?xml version="1.0" encoding="UTF-8"?>
-            <svg xmlns="http://www.w3.org/2000/svg" width="20" height="10">
-              <path d="M 0,10 L 10,0 20,10 z" fill="${borderColor}"/>
-              <path d="M 1,10 L 10,1 19,10 z" fill="${background}"/>
-            </svg>
-          `)}")`;
-        }
-      }
-
-      this.panel.style.setProperty("--arrowpanel-background", panelBackground);
-      this.panel.style.setProperty("--panel-arrow-image-vertical", panelArrow);
-
-
-      // Adjust the size of the browser based on its content's preferred size.
-      let {contentViewer} = this.browser.docShell;
-      let ratio = this.window.devicePixelRatio;
-
-      let w = {}, h = {};
-      contentViewer.getContentSizeConstrained(800 * ratio, 600 * ratio, w, h);
-      let width = Math.ceil(w.value / ratio);
-      let height = Math.ceil(h.value / ratio);
-
       this.browser.style.width = `${width}px`;
       this.browser.style.height = `${height}px`;
     }
 
-    let event = new this.window.CustomEvent("WebExtPopupResized");
+    let event = new this.window.CustomEvent("WebExtPopupResized", {detail});
     this.browser.dispatchEvent(event);
+  }
 
-    this._resolveContentReady();
+  setBackground(background) {
+    let panelBackground = "";
+    let panelArrow = "";
+
+    if (background) {
+      let borderColor = this.borderColor || background;
+
+      panelBackground = background;
+      panelArrow = `url("data:image/svg+xml,${encodeURIComponent(`<?xml version="1.0" encoding="UTF-8"?>
+        <svg xmlns="http://www.w3.org/2000/svg" width="20" height="10">
+          <path d="M 0,10 L 10,0 20,10 z" fill="${borderColor}"/>
+          <path d="M 1,10 L 10,1 19,10 z" fill="${background}"/>
+        </svg>
+      `)}")`;
+    }
+
+    this.panel.style.setProperty("--arrowpanel-background", panelBackground);
+    this.panel.style.setProperty("--panel-arrow-image-vertical", panelArrow);
   }
 }
 
 /**
  * A map of active popups for a given browser window.
  *
  * WeakMap[window -> WeakMap[Extension -> BasePopup]]
  */
 BasePopup.instances = new DefaultWeakMap(() => new WeakMap());
 
-global.PanelPopup = class PanelPopup extends BasePopup {
+class PanelPopup extends BasePopup {
   constructor(extension, imageNode, popupURL, browserStyle) {
     let document = imageNode.ownerDocument;
 
     let panel = document.createElement("panel");
     panel.setAttribute("id", makeWidgetId(extension.id) + "-panel");
     panel.setAttribute("class", "browser-extension-panel");
     panel.setAttribute("tabspecific", "true");
     panel.setAttribute("type", "arrow");
     panel.setAttribute("role", "group");
 
     document.getElementById("mainPopupSet").appendChild(panel);
 
     super(extension, panel, popupURL, browserStyle);
 
-    this.ignoreResizes = false;
-
     this.contentReady.then(() => {
       panel.openPopup(imageNode, "bottomcenter topright", 0, 0, false, false);
+
+      let event = new this.window.CustomEvent("WebExtPopupLoaded", {
+        bubbles: true,
+        detail: {extension},
+      });
+      this.browser.dispatchEvent(event);
     });
   }
 
   get DESTROY_EVENT() {
     return "popuphidden";
   }
 
   destroy() {
@@ -460,31 +376,33 @@ global.PanelPopup = class PanelPopup ext
   closePopup() {
     promisePopupShown(this.viewNode).then(() => {
       // Make sure we're not already destroyed.
       if (this.viewNode) {
         this.viewNode.hidePopup();
       }
     });
   }
-};
+}
 
-global.ViewPopup = class ViewPopup extends BasePopup {
+class ViewPopup extends BasePopup {
   constructor(extension, window, popupURL, browserStyle, fixedWidth) {
     let document = window.document;
 
     // Create a temporary panel to hold the browser while it pre-loads its
     // content. This panel will never be shown, but the browser's docShell will
     // be swapped with the browser in the real panel when it's ready.
     let panel = document.createElement("panel");
     panel.setAttribute("type", "arrow");
     document.getElementById("mainPopupSet").appendChild(panel);
 
     super(extension, panel, popupURL, browserStyle, fixedWidth);
 
+    this.ignoreResizes = true;
+
     this.attached = false;
     this.tempPanel = panel;
 
     this.browser.classList.add("webextension-preload-browser");
   }
 
   /**
    * Attaches the pre-loaded browser to the given view node, and reserves a
@@ -512,16 +430,20 @@ global.ViewPopup = class ViewPopup exten
         Promise.race([
           // This promise may be rejected if the popup calls window.close()
           // before it has fully loaded.
           this.browserLoaded.catch(() => {}),
           new Promise(resolve => setTimeout(resolve, POPUP_LOAD_TIMEOUT_MS)),
         ]),
       ]);
 
+      if (!this.destroyed && !this.panel) {
+        this.destroy();
+      }
+
       if (this.destroyed) {
         return false;
       }
 
       this.attached = true;
 
       // Store the initial height of the view, so that we never resize menu panel
       // sub-views smaller than the initial height of the menu.
@@ -544,21 +466,29 @@ global.ViewPopup = class ViewPopup exten
       // Create a new browser in the real popup.
       let browser = this.browser;
       this.createBrowser(this.viewNode);
 
       this.browser.swapDocShells(browser);
       this.destroyBrowser(browser);
 
       this.ignoreResizes = false;
-      this.resizeBrowser(true);
+      if (this.dimensions) {
+        this.resizeBrowser(this.dimensions);
+      }
 
       this.tempPanel.remove();
       this.tempPanel = null;
 
+      let event = new this.window.CustomEvent("WebExtPopupLoaded", {
+        bubbles: true,
+        detail: {extension: this.extension},
+      });
+      this.browser.dispatchEvent(event);
+
       return true;
     }.bind(this));
   }
 
   destroy() {
     return super.destroy().then(() => {
       if (this.tempPanel) {
         this.tempPanel.remove();
@@ -573,17 +503,19 @@ global.ViewPopup = class ViewPopup exten
 
   closePopup() {
     if (this.attached) {
       CustomizableUI.hidePanelForNode(this.viewNode);
     } else {
       this.destroy();
     }
   }
-};
+}
+
+Object.assign(global, {PanelPopup, ViewPopup});
 
 // Manages tab-specific context data, and dispatching tab select events
 // across all windows.
 global.TabContext = function TabContext(getDefaults, extension) {
   this.extension = extension;
   this.getDefaults = getDefaults;
 
   this.tabData = new WeakMap();
@@ -732,16 +664,38 @@ ExtensionTabManager.prototype = {
 
   getTabs(window) {
     return Array.from(window.gBrowser.tabs)
                 .filter(tab => !tab.closing)
                 .map(tab => this.convert(tab));
   },
 };
 
+// Sends the tab and windowId upon request. This is primarily used to support
+// the synchronous `browser.extension.getViews` API.
+let onGetTabAndWindowId = {
+  receiveMessage({name, target, sync}) {
+    let {gBrowser} = target.ownerGlobal;
+    let tab = gBrowser && gBrowser.getTabForBrowser(target);
+    if (tab) {
+      let reply = {
+        tabId: TabManager.getId(tab),
+        windowId: WindowManager.getId(tab.ownerGlobal),
+      };
+      if (sync) {
+        return reply;
+      }
+      target.messageManager.sendAsyncMessage("Extension:SetTabAndWindowId", reply);
+    }
+  },
+};
+/* eslint-disable mozilla/balanced-listeners */
+Services.mm.addMessageListener("Extension:GetTabAndWindowId", onGetTabAndWindowId);
+/* eslint-enable mozilla/balanced-listeners */
+
 
 // Manages global mappings between XUL tabs and extension tab IDs.
 global.TabManager = {
   _tabs: new WeakMap(),
   _nextId: 1,
   _initialized: false,
 
   // We begin listening for TabOpen and TabClose events once we've started
@@ -760,26 +714,35 @@ global.TabManager = {
   },
 
   handleEvent(event) {
     if (event.type == "TabOpen") {
       let {adoptedTab} = event.detail;
       if (adoptedTab) {
         // This tab is being created to adopt a tab from a different window.
         // Copy the ID from the old tab to the new.
-        this._tabs.set(event.target, this.getId(adoptedTab));
+        let tab = event.target;
+        this._tabs.set(tab, this.getId(adoptedTab));
+
+        tab.linkedBrowser.messageManager.sendAsyncMessage("Extension:SetTabAndWindowId", {
+          windowId: WindowManager.getId(tab.ownerGlobal),
+        });
       }
     } else if (event.type == "TabClose") {
       let {adoptedBy} = event.detail;
       if (adoptedBy) {
         // This tab is being closed because it was adopted by a new window.
         // Copy its ID to the new tab, in case it was created as the first tab
         // of a new window, and did not have an `adoptedTab` detail when it was
         // opened.
         this._tabs.set(adoptedBy, this.getId(event.target));
+
+        adoptedBy.linkedBrowser.messageManager.sendAsyncMessage("Extension:SetTabAndWindowId", {
+          windowId: WindowManager.getId(adoptedBy),
+        });
       }
     }
   },
 
   handleWindowOpen(window) {
     if (window.arguments && window.arguments[0] instanceof window.XULElement) {
       // If the first window argument is a XUL element, it means the
       // window is about to adopt a tab from another window to replace its
--- a/browser/components/extensions/ext-windows.js
+++ b/browser/components/extensions/ext-windows.js
@@ -34,22 +34,26 @@ extensions.registerSchemaAPI("windows", 
         fire(WindowManager.getId(window));
       }).api(),
 
       onFocusChanged: new EventManager(context, "windows.onFocusChanged", fire => {
         // Keep track of the last windowId used to fire an onFocusChanged event
         let lastOnFocusChangedWindowId;
 
         let listener = event => {
-          let window = WindowManager.topWindow;
-          let windowId = window ? WindowManager.getId(window) : WindowManager.WINDOW_ID_NONE;
-          if (windowId !== lastOnFocusChangedWindowId) {
-            fire(windowId);
-            lastOnFocusChangedWindowId = windowId;
-          }
+          // Wait a tick to avoid firing a superfluous WINDOW_ID_NONE
+          // event when switching focus between two Firefox windows.
+          Promise.resolve().then(() => {
+            let window = Services.focus.activeWindow;
+            let windowId = window ? WindowManager.getId(window) : WindowManager.WINDOW_ID_NONE;
+            if (windowId !== lastOnFocusChangedWindowId) {
+              fire(windowId);
+              lastOnFocusChangedWindowId = windowId;
+            }
+          });
         };
         AllWindowEvents.addListener("focus", listener);
         AllWindowEvents.addListener("blur", listener);
         return () => {
           AllWindowEvents.removeListener("focus", listener);
           AllWindowEvents.removeListener("blur", listener);
         };
       }).api(),
@@ -108,29 +112,29 @@ extensions.registerSchemaAPI("windows", 
           // Private browsing tabs can only be moved to private browsing
           // windows.
           let incognito = PrivateBrowsingUtils.isBrowserPrivate(tab.linkedBrowser);
           if (createData.incognito !== null && createData.incognito != incognito) {
             return Promise.reject({message: "`incognito` property must match the incognito state of tab"});
           }
           createData.incognito = incognito;
 
-          args.appendElement(tab, /*weak =*/ false);
+          args.appendElement(tab, /* weak = */ false);
         } else if (createData.url !== null) {
           if (Array.isArray(createData.url)) {
             let array = Cc["@mozilla.org/supports-array;1"].createInstance(Ci.nsISupportsArray);
             for (let url of createData.url) {
               array.AppendElement(mkstr(url));
             }
-            args.appendElement(array, /*weak =*/ false);
+            args.appendElement(array, /* weak = */ false);
           } else {
-            args.appendElement(mkstr(createData.url), /*weak =*/ false);
+            args.appendElement(mkstr(createData.url), /* weak = */ false);
           }
         } else {
-          args.appendElement(mkstr(aboutNewTabService.newTabURL), /*weak =*/ false);
+          args.appendElement(mkstr(aboutNewTabService.newTabURL), /* weak = */ false);
         }
 
         let features = ["chrome"];
 
         if (createData.type === null || createData.type == "normal") {
           features.push("dialog=no", "all");
         } else {
           // All other types create "popup"-type windows by default.
--- a/browser/components/extensions/extensions-browser.manifest
+++ b/browser/components/extensions/extensions-browser.manifest
@@ -7,16 +7,19 @@ category webextension-scripts desktop-ru
 category webextension-scripts history chrome://browser/content/ext-history.js
 category webextension-scripts pageAction chrome://browser/content/ext-pageAction.js
 category webextension-scripts tabs chrome://browser/content/ext-tabs.js
 category webextension-scripts theme chrome://browser/content/ext-theme.js
 category webextension-scripts utils chrome://browser/content/ext-utils.js
 category webextension-scripts windows chrome://browser/content/ext-windows.js
 
 # scripts that must run in the same process as addon code.
+category webextension-scripts-addon browserAction chrome://browser/content/ext-c-browserAction.js
+category webextension-scripts-addon contextMenus chrome://browser/content/ext-c-contextMenus.js
+category webextension-scripts-addon pageAction chrome://browser/content/ext-c-pageAction.js
 category webextension-scripts-addon tabs chrome://browser/content/ext-c-tabs.js
 
 # schemas
 category webextension-schemas bookmarks chrome://browser/content/schemas/bookmarks.json
 category webextension-schemas browser_action chrome://browser/content/schemas/browser_action.json
 category webextension-schemas commands chrome://browser/content/schemas/commands.json
 category webextension-schemas context_menus chrome://browser/content/schemas/context_menus.json
 category webextension-schemas context_menus_internal chrome://browser/content/schemas/context_menus_internal.json
--- a/browser/components/extensions/jar.mn
+++ b/browser/components/extensions/jar.mn
@@ -18,9 +18,12 @@ browser.jar:
     content/browser/ext-contextMenus.js
     content/browser/ext-desktop-runtime.js
     content/browser/ext-history.js
     content/browser/ext-pageAction.js
     content/browser/ext-tabs.js
     content/browser/ext-theme.js
     content/browser/ext-utils.js
     content/browser/ext-windows.js
+    content/browser/ext-c-browserAction.js
+    content/browser/ext-c-contextMenus.js
+    content/browser/ext-c-pageAction.js
     content/browser/ext-c-tabs.js
--- a/browser/components/extensions/schemas/browser_action.json
+++ b/browser/components/extensions/schemas/browser_action.json
@@ -63,31 +63,38 @@
         "description": "Pixel data for an image. Must be an ImageData object (for example, from a <code>canvas</code> element)."
       }
     ],
     "functions": [
       {
         "name": "setTitle",
         "type": "function",
         "description": "Sets the title of the browser action. This shows up in the tooltip.",
+        "async": "callback",
         "parameters": [
           {
             "name": "details",
             "type": "object",
             "properties": {
               "title": {
                 "type": "string",
                 "description": "The string the browser action should display when moused over."
               },
               "tabId": {
                 "type": "integer",
                 "optional": true,
                 "description": "Limits the change to when a particular tab is selected. Automatically resets when the tab is closed."
               }
             }
+          },
+          {
+            "type": "function",
+            "name": "callback",
+            "optional": true,
+            "parameters": []
           }
         ]
       },
       {
         "name": "getTitle",
         "type": "function",
         "description": "Gets the title of the browser action.",
         "async": "callback",
@@ -161,32 +168,39 @@
             "parameters": []
           }
         ]
       },
       {
         "name": "setPopup",
         "type": "function",
         "description": "Sets the html document to be opened as a popup when the user clicks on the browser action's icon.",
+        "async": "callback",
         "parameters": [
           {
             "name": "details",
             "type": "object",
             "properties": {
               "tabId": {
                 "type": "integer",
                 "optional": true,
                 "minimum": 0,
                 "description": "Limits the change to when a particular tab is selected. Automatically resets when the tab is closed."
               },
               "popup": {
                 "type": "string",
                 "description": "The html file to show in a popup.  If set to the empty string (''), no popup is shown."
               }
             }
+          },
+          {
+            "type": "function",
+            "name": "callback",
+            "optional": true,
+            "parameters": []
           }
         ]
       },
       {
         "name": "getPopup",
         "type": "function",
         "description": "Gets the html document set as the popup for this browser action.",
         "async": "callback",
@@ -213,31 +227,38 @@
             ]
           }
         ]
       },
       {
         "name": "setBadgeText",
         "type": "function",
         "description": "Sets the badge text for the browser action. The badge is displayed on top of the icon.",
+        "async": "callback",
         "parameters": [
           {
             "name": "details",
             "type": "object",
             "properties": {
               "text": {
                 "type": "string",
                 "description": "Any number of characters can be passed, but only about four can fit in the space."
               },
               "tabId": {
                 "type": "integer",
                 "optional": true,
                 "description": "Limits the change to when a particular tab is selected. Automatically resets when the tab is closed."
               }
             }
+          },
+          {
+            "type": "function",
+            "name": "callback",
+            "optional": true,
+            "parameters": []
           }
         ]
       },
       {
         "name": "getBadgeText",
         "type": "function",
         "description": "Gets the badge text of the browser action. If no tab is specified, the non-tab-specific badge text is returned.",
         "async": "callback",
@@ -264,16 +285,17 @@
             ]
           }
         ]
       },
       {
         "name": "setBadgeBackgroundColor",
         "type": "function",
         "description": "Sets the background color for the badge.",
+        "async": "callback",
         "parameters": [
           {
             "name": "details",
             "type": "object",
             "properties": {
               "color": {
                 "description": "An array of four integers in the range [0,255] that make up the RGBA color of the badge. For example, opaque red is <code>[255, 0, 0, 255]</code>. Can also be a string with a CSS value, with opaque red being <code>#FF0000</code> or <code>#F00</code>.",
                 "choices": [
@@ -282,16 +304,22 @@
                 ]
               },
               "tabId": {
                 "type": "integer",
                 "optional": true,
                 "description": "Limits the change to when a particular tab is selected. Automatically resets when the tab is closed."
               }
             }
+          },
+          {
+            "type": "function",
+            "name": "callback",
+            "optional": true,
+            "parameters": []
           }
         ]
       },
       {
         "name": "getBadgeBackgroundColor",
         "type": "function",
         "description": "Gets the background color of the browser action.",
         "async": "callback",
@@ -318,37 +346,51 @@
             ]
           }
         ]
       },
       {
         "name": "enable",
         "type": "function",
         "description": "Enables the browser action for a tab. By default, browser actions are enabled.",
+        "async": "callback",
         "parameters": [
           {
             "type": "integer",
             "optional": true,
             "name": "tabId",
             "minimum": 0,
             "description": "The id of the tab for which you want to modify the browser action."
+          },
+          {
+            "type": "function",
+            "name": "callback",
+            "optional": true,
+            "parameters": []
           }
         ]
       },
       {
         "name": "disable",
         "type": "function",
         "description": "Disables the browser action for a tab.",
+        "async": "callback",
         "parameters": [
           {
             "type": "integer",
             "optional": true,
             "name": "tabId",
             "minimum": 0,
             "description": "The id of the tab for which you want to modify the browser action."
+          },
+          {
+            "type": "function",
+            "name": "callback",
+            "optional": true,
+            "parameters": []
           }
         ]
       },
       {
         "name": "openPopup",
         "type": "function",
         "description": "Opens the extension popup window in the active window but does not grant tab permissions.",
         "unsupported": true,
--- a/browser/components/extensions/schemas/context_menus.json
+++ b/browser/components/extensions/schemas/context_menus.json
@@ -202,16 +202,84 @@
             "name": "callback",
             "optional": true,
             "description": "Called when the item has been created in the browser. If there were any problems creating the item, details will be available in $(ref:runtime.lastError).",
             "parameters": []
           }
         ]
       },
       {
+        "name": "createInternal",
+        "type": "function",
+        "allowedContexts": ["addon_parent_only"],
+        "async": "callback",
+        "description": "Identical to contextMenus.create, except: the 'id' field is required and allows an integer, 'onclick' is not allowed, and the method is async (and the return value is not a menu item ID).",
+        "parameters": [
+          {
+            "type": "object",
+            "name": "createProperties",
+            "properties": {
+              "type": {
+                "$ref": "ItemType",
+                "optional": true
+              },
+              "id": {
+                "choices": [
+                  { "type": "integer" },
+                  { "type": "string" }
+                ]
+              },
+              "title": {
+                "type": "string",
+                "optional": true
+              },
+              "checked": {
+                "type": "boolean",
+                "optional": true
+              },
+              "contexts": {
+                "type": "array",
+                "items": {
+                  "$ref": "ContextType"
+                },
+                "minItems": 1,
+                "optional": true
+              },
+              "parentId": {
+                "choices": [
+                  { "type": "integer" },
+                  { "type": "string" }
+                ],
+                "optional": true
+              },
+              "documentUrlPatterns": {
+                "type": "array",
+                "items": {"type": "string"},
+                "optional": true
+              },
+              "targetUrlPatterns": {
+                "type": "array",
+                "items": {"type": "string"},
+                "optional": true
+              },
+              "enabled": {
+                "type": "boolean",
+                "optional": true
+              }
+            }
+          },
+          {
+            "type": "function",
+            "name": "callback",
+            "optional": true,
+            "parameters": []
+          }
+        ]
+      },
+      {
         "name": "update",
         "type": "function",
         "description": "Updates a previously created context menu item.",
         "async": "callback",
         "parameters": [
           {
             "choices": [
               { "type": "integer" },
@@ -242,17 +310,17 @@
                 "items": {
                   "$ref": "ContextType"
                 },
                 "minItems": 1,
                 "optional": true
               },
               "onclick": {
                 "type": "function",
-                "optional": true,
+                "optional": "omit-key-if-missing",
                 "parameters": [
                   {
                     "name": "info",
                     "$ref": "contextMenusInternal.OnClickData"
                   },
                   {
                     "name": "tab",
                     "$ref": "tabs.Tab",
--- a/browser/components/extensions/schemas/context_menus_internal.json
+++ b/browser/components/extensions/schemas/context_menus_internal.json
@@ -68,31 +68,11 @@
           },
           "checked": {
             "type": "boolean",
             "optional": true,
             "description": "A flag indicating the state of a checkbox or radio item after it is clicked."
           }
         }
       }
-    ],
-    "events": [
-      {
-        "name": "onClicked",
-        "type": "function",
-        "description": "Fired when a context menu item is clicked.",
-        "parameters": [
-          {
-            "name": "info",
-            "$ref": "OnClickData",
-            "description": "Information about the item clicked and the context where the click happened."
-          },
-          {
-            "name": "tab",
-            "$ref": "tabs.Tab",
-            "description": "The details of the tab where the click took place. If the click did not take place in a tab, this parameter will be missing.",
-            "optional": true
-          }
-        ]
-      }
     ]
   }
 ]
--- a/browser/components/extensions/schemas/page_action.json
+++ b/browser/components/extensions/schemas/page_action.json
@@ -51,29 +51,41 @@
         "additionalProperties": { "type": "any" },
         "description": "Pixel data for an image. Must be an ImageData object (for example, from a <code>canvas</code> element)."
       }
     ],
     "functions": [
       {
         "name": "show",
         "type": "function",
-        "async": true,
+        "async": "callback",
         "description": "Shows the page action. The page action is shown whenever the tab is selected.",
         "parameters": [
-          {"type": "integer", "name": "tabId", "minimum": 0, "description": "The id of the tab for which you want to modify the page action."}
+          {"type": "integer", "name": "tabId", "minimum": 0, "description": "The id of the tab for which you want to modify the page action."},
+          {
+            "type": "function",
+            "name": "callback",
+            "optional": true,
+            "parameters": []
+          }
         ]
       },
       {
         "name": "hide",
         "type": "function",
-        "async": true,
+        "async": "callback",
         "description": "Hides the page action.",
         "parameters": [
-          {"type": "integer", "name": "tabId", "minimum": 0, "description": "The id of the tab for which you want to modify the page action."}
+          {"type": "integer", "name": "tabId", "minimum": 0, "description": "The id of the tab for which you want to modify the page action."},
+          {
+            "type": "function",
+            "name": "callback",
+            "optional": true,
+            "parameters": []
+          }
         ]
       },
       {
         "name": "setTitle",
         "type": "function",
         "description": "Sets the title of the page action. This is displayed in a tooltip over the page action.",
         "parameters": [
           {
--- a/browser/components/extensions/schemas/tabs.json
+++ b/browser/components/extensions/schemas/tabs.json
@@ -250,17 +250,17 @@
             ]
           }
         ]
       },
       {
         "name": "sendMessage",
         "type": "function",
         "description": "Sends a single message to the content script(s) in the specified tab, with an optional callback to run when a response is sent back.  The $(ref:runtime.onMessage) event is fired in each content script running in the specified tab for the current extension.",
-        "async": "sendResponse",
+        "async": "responseCallback",
         "parameters": [
           {
             "type": "integer",
             "name": "tabId",
             "minimum": 0
           },
           {
             "type": "any",
--- a/browser/components/extensions/test/browser/browser.ini
+++ b/browser/components/extensions/test/browser/browser.ini
@@ -30,16 +30,17 @@ tags = webextensions
 [browser_ext_commands_execute_browser_action.js]
 [browser_ext_commands_execute_page_action.js]
 [browser_ext_commands_getAll.js]
 [browser_ext_commands_onCommand.js]
 [browser_ext_contentscript_connect.js]
 [browser_ext_contextMenus.js]
 [browser_ext_contextMenus_checkboxes.js]
 [browser_ext_contextMenus_icons.js]
+[browser_ext_contextMenus_onclick.js]
 [browser_ext_contextMenus_radioGroups.js]
 [browser_ext_contextMenus_uninstall.js]
 [browser_ext_contextMenus_urlPatterns.js]
 [browser_ext_currentWindow.js]
 [browser_ext_getViews.js]
 [browser_ext_incognito_popup.js]
 [browser_ext_lastError.js]
 [browser_ext_legacy_extension_context_contentscript.js]
--- a/browser/components/extensions/test/browser/browser_ext_browserAction_pageAction_icon.js
+++ b/browser/components/extensions/test/browser/browser_ext_browserAction_pageAction_icon.js
@@ -179,20 +179,22 @@ add_task(function* testDetailsObjects() 
         browser.test.fail("expecting 'setIcon' message");
       }
 
       let details = iconDetails[test.index];
 
       let detailString = JSON.stringify(details);
       browser.test.log(`Setting browerAction/pageAction to ${detailString} expecting URLs ${JSON.stringify(details.resolutions)}`);
 
-      browser.browserAction.setIcon(Object.assign({tabId}, details.details));
-      browser.pageAction.setIcon(Object.assign({tabId}, details.details));
-
-      browser.test.sendMessage("iconSet");
+      Promise.all([
+        browser.browserAction.setIcon(Object.assign({tabId}, details.details)),
+        browser.pageAction.setIcon(Object.assign({tabId}, details.details)),
+      ]).then(() => {
+        browser.test.sendMessage("iconSet");
+      });
     });
 
     // Generate a list of tests and resolutions to send back to the test
     // context.
     //
     // This process is a bit convoluted, because the outer test context needs
     // to handle checking the button nodes and changing the screen resolution,
     // but it can't pass us icon definitions with ImageData objects. This
--- a/browser/components/extensions/test/browser/browser_ext_browserAction_popup_resize.js
+++ b/browser/components/extensions/test/browser/browser_ext_browserAction_popup_resize.js
@@ -1,78 +1,65 @@
 /* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set sts=2 sw=2 et tw=80: */
 "use strict";
 
 function* openPanel(extension, win = window) {
   clickBrowserAction(extension, win);
 
-  return yield awaitExtensionPanel(extension, win);
-}
-
-function* awaitResize(browser) {
-  // Debouncing code makes this a bit racy.
-  // Try to skip the first, early resize, and catch the resize event we're
-  // looking for, but don't wait longer than a few seconds.
-
-  return Promise.race([
-    BrowserTestUtils.waitForEvent(browser, "WebExtPopupResized")
-      .then(() => BrowserTestUtils.waitForEvent(browser, "WebExtPopupResized")),
-    new Promise(resolve => setTimeout(resolve, 5000)),
-  ]);
+  return yield awaitExtensionPanel(extension, win, false);
 }
 
 add_task(function* testBrowserActionPopupResize() {
   let extension = ExtensionTestUtils.loadExtension({
     manifest: {
       "browser_action": {
         "default_popup": "popup.html",
         "browser_style": true,
       },
     },
 
     files: {
-      "popup.html": '<html><head><meta charset="utf-8"></head></html>',
+      "popup.html": '<!DOCTYPE html><html><head><meta charset="utf-8"></head></html>',
     },
   });
 
   yield extension.startup();
 
-  clickBrowserAction(extension, window);
+  let browser = yield openPanel(extension);
 
-  let browser = yield openPanel(extension);
-  let panelWindow = browser.contentWindow;
-  let panelBody = panelWindow.document.body;
+  function* checkSize(expected) {
+    let dims = yield promiseContentDimensions(browser);
 
-  function checkSize(expected) {
-    is(panelWindow.innerHeight, expected, `Panel window should be ${expected}px tall`);
-    is(panelBody.clientHeight, panelBody.scrollHeight,
+    is(dims.window.innerHeight, expected, `Panel window should be ${expected}px tall`);
+    is(dims.body.clientHeight, dims.body.scrollHeight,
       "Panel body should be tall enough to fit its contents");
 
     // Tolerate if it is 1px too wide, as that may happen with the current resizing method.
-    ok(Math.abs(panelWindow.innerWidth - expected) <= 1, `Panel window should be ${expected}px wide`);
-    is(panelBody.clientWidth, panelBody.scrollWidth,
+    ok(Math.abs(dims.window.innerWidth - expected) <= 1, `Panel window should be ${expected}px wide`);
+    is(dims.body.clientWidth, dims.body.scrollWidth,
       "Panel body should be wide enough to fit its contents");
   }
 
+  /* eslint-disable mozilla/no-cpows-in-tests */
   function setSize(size) {
-    panelBody.style.height = `${size}px`;
-    panelBody.style.width = `${size}px`;
+    content.document.body.style.height = `${size}px`;
+    content.document.body.style.width = `${size}px`;
   }
+  /* eslint-enable mozilla/no-cpows-in-tests */
 
   let sizes = [
     200,
     400,
     300,
   ];
 
   for (let size of sizes) {
-    setSize(size);
-    yield awaitResize(browser);
-    checkSize(size);
+    yield alterContent(browser, setSize, size);
+    yield checkSize(size);
   }
 
   yield closeBrowserAction(extension);
   yield extension.unload();
 });
 
 function* testPopupSize(standardsMode, browserWin = window, arrowSide = "top") {
   let docType = standardsMode ? "<!DOCTYPE html>" : "";
@@ -117,144 +104,156 @@ function* testPopupSize(standardsMode, b
             <span></span>
           </body>
         </html>`,
     },
   });
 
   yield extension.startup();
 
+  /* eslint-disable mozilla/no-cpows-in-tests */
+
   if (arrowSide == "top") {
     // Test the standalone panel for a toolbar button.
     let browser = yield openPanel(extension, browserWin);
-    let win = browser.contentWindow;
-    let body = win.document.body;
 
-    let isStandards = win.document.compatMode != "BackCompat";
-    is(isStandards, standardsMode, "Document has the expected compat mode");
+    let dims = yield promiseContentDimensions(browser);
+
+    is(dims.isStandards, standardsMode, "Document has the expected compat mode");
 
-    let {innerWidth, innerHeight} = win;
+    let {innerWidth, innerHeight} = dims.window;
 
-    body.classList.add("bigger");
-    yield awaitResize(browser);
+    dims = yield alterContent(browser, () => {
+      content.document.body.classList.add("bigger");
+    });
 
+    let win = dims.window;
     is(win.innerHeight, innerHeight, "Window height should not change");
     ok(win.innerWidth > innerWidth, `Window width should increase (${win.innerWidth} > ${innerWidth})`);
 
 
-    body.classList.remove("bigger");
-    yield awaitResize(browser);
+    dims = yield alterContent(browser, () => {
+      content.document.body.classList.remove("bigger");
+    });
 
+    win = dims.window;
     is(win.innerHeight, innerHeight, "Window height should not change");
 
     // The getContentSize calculation is not always reliable to single-pixel
     // precision.
     ok(Math.abs(win.innerWidth - innerWidth) <= 1,
        `Window width should return to approximately its original value (${win.innerWidth} ~= ${innerWidth})`);
 
     yield closeBrowserAction(extension, browserWin);
   }
 
 
   // Test the PanelUI panel for a menu panel button.
   let widget = getBrowserActionWidget(extension);
   CustomizableUI.addWidgetToArea(widget.id, CustomizableUI.AREA_PANEL);
 
   let browser = yield openPanel(extension, browserWin);
-  let win = browser.contentWindow;
-  let body = win.document.body;
 
   let {panel} = browserWin.PanelUI;
   let origPanelRect = panel.getBoundingClientRect();
 
   // Check that the panel is still positioned as expected.
   let checkPanelPosition = () => {
     is(panel.getAttribute("side"), arrowSide, "Panel arrow is positioned as expected");
 
     let panelRect = panel.getBoundingClientRect();
     if (arrowSide == "top") {
       ok(panelRect.top, origPanelRect.top, "Panel has not moved downwards");
       ok(panelRect.bottom >= origPanelRect.bottom, `Panel has not shrunk from original size (${panelRect.bottom} >= ${origPanelRect.bottom})`);
 
-      let screenBottom = browserWin.screen.availTop + win.screen.availHeight;
+      let screenBottom = browserWin.screen.availTop + browserWin.screen.availHeight;
       let panelBottom = browserWin.mozInnerScreenY + panelRect.bottom;
       ok(panelBottom <= screenBottom, `Bottom of popup should be on-screen. (${panelBottom} <= ${screenBottom})`);
     } else {
       ok(panelRect.bottom, origPanelRect.bottom, "Panel has not moved upwards");
       ok(panelRect.top <= origPanelRect.top, `Panel has not shrunk from original size (${panelRect.top} <= ${origPanelRect.top})`);
 
       let panelTop = browserWin.mozInnerScreenY + panelRect.top;
       ok(panelTop >= browserWin.screen.availTop, `Top of popup should be on-screen. (${panelTop} >= ${browserWin.screen.availTop})`);
     }
   };
 
-
-  let isStandards = win.document.compatMode != "BackCompat";
-  is(isStandards, standardsMode, "Document has the expected compat mode");
+  yield awaitBrowserLoaded(browser);
 
   // Wait long enough to make sure the initial resize debouncing timer has
   // expired.
   yield new Promise(resolve => setTimeout(resolve, 100));
 
+  let dims = yield promiseContentDimensions(browser);
+
+  is(dims.isStandards, standardsMode, "Document has the expected compat mode");
+
   // If the browser's preferred height is smaller than the initial height of the
   // panel, then it will still take up the full available vertical space. Even
   // so, we need to check that we've gotten the preferred height calculation
   // correct, so check that explicitly.
   let getHeight = () => parseFloat(browser.style.height);
 
-  let {innerWidth, innerHeight} = win;
+  let {innerWidth, innerHeight} = dims.window;
   let height = getHeight();
 
 
+  let setClass = className => {
+    content.document.body.className = className;
+  };
+
   info("Increase body children's width. " +
        "Expect them to wrap, and the frame to grow vertically rather than widen.");
-  body.className = "big";
-  yield awaitResize(browser);
+
+  dims = yield alterContent(browser, setClass, "big");
+  let win = dims.window;
 
   ok(getHeight() > height, `Browser height should increase (${getHeight()} > ${height})`);
 
   is(win.innerWidth, innerWidth, "Window width should not change");
   ok(win.innerHeight >= innerHeight, `Window height should increase (${win.innerHeight} >= ${innerHeight})`);
   is(win.scrollMaxY, 0, "Document should not be vertically scrollable");
 
   checkPanelPosition();
 
 
   info("Increase body children's width and height. " +
        "Expect them to wrap, and the frame to grow vertically rather than widen.");
-  body.className = "bigger";
-  yield awaitResize(browser);
+
+  dims = yield alterContent(browser, setClass, "bigger");
+  win = dims.window;
 
   ok(getHeight() > height, `Browser height should increase (${getHeight()} > ${height})`);
 
   is(win.innerWidth, innerWidth, "Window width should not change");
   ok(win.innerHeight >= innerHeight, `Window height should increase (${win.innerHeight} >= ${innerHeight})`);
   is(win.scrollMaxY, 0, "Document should not be vertically scrollable");
 
   checkPanelPosition();
 
 
   info("Increase body height beyond the height of the screen. " +
        "Expect the panel to grow to accommodate, but not larger than the height of the screen.");
-  body.className = "huge";
-  yield awaitResize(browser);
+
+  dims = yield alterContent(browser, setClass, "huge");
+  win = dims.window;
 
   ok(getHeight() > height, `Browser height should increase (${getHeight()} > ${height})`);
 
   is(win.innerWidth, innerWidth, "Window width should not change");
   ok(win.innerHeight > innerHeight, `Window height should increase (${win.innerHeight} > ${innerHeight})`);
   ok(win.innerHeight < screen.height, `Window height be less than the screen height (${win.innerHeight} < ${screen.height})`);
   ok(win.scrollMaxY > 0, `Document should be vertically scrollable (${win.scrollMaxY} > 0)`);
 
   checkPanelPosition();
 
 
   info("Restore original styling. Expect original dimensions.");
-  body.className = "";
-  yield awaitResize(browser);
+  dims = yield alterContent(browser, setClass, "");
+  win = dims.window;
 
   is(getHeight(), height, "Browser height should return to its original value");
 
   is(win.innerWidth, innerWidth, "Window width should not change");
   is(win.innerHeight, innerHeight, "Window height should return to its original value");
   is(win.scrollMaxY, 0, "Document should not be vertically scrollable");
 
   checkPanelPosition();
--- a/browser/components/extensions/test/browser/browser_ext_contextMenus.js
+++ b/browser/components/extensions/test/browser/browser_ext_contextMenus.js
@@ -269,33 +269,48 @@ add_task(function* testRemoveAllWithTwoE
   const manifest = {permissions: ["contextMenus"]};
 
   const first = ExtensionTestUtils.loadExtension({manifest, background() {
     browser.contextMenus.create({title: "alpha", contexts: ["all"]});
 
     browser.contextMenus.onClicked.addListener(() => {
       browser.contextMenus.removeAll();
     });
-    browser.test.onMessage.addListener(() => {
+    browser.test.onMessage.addListener(msg => {
+      if (msg == "ping") {
+        browser.test.sendMessage("pong-alpha");
+        return;
+      }
       browser.contextMenus.create({title: "gamma", contexts: ["all"]});
     });
   }});
 
   const second = ExtensionTestUtils.loadExtension({manifest, background() {
     browser.contextMenus.create({title: "beta", contexts: ["all"]});
 
     browser.contextMenus.onClicked.addListener(() => {
       browser.contextMenus.removeAll();
     });
+
+    browser.test.onMessage.addListener(() => {
+      browser.test.sendMessage("pong-beta");
+    });
   }});
 
   yield first.startup();
   yield second.startup();
 
   function* confirmMenuItems(...items) {
+    // Round-trip to extension to make sure that the context menu state has been
+    // updated by the async contextMenus.create / contextMenus.removeAll calls.
+    first.sendMessage("ping");
+    second.sendMessage("ping");
+    yield first.awaitMessage("pong-alpha");
+    yield second.awaitMessage("pong-beta");
+
     const menu = yield openContextMenu();
     for (const id of ["alpha", "beta", "gamma"]) {
       const expected = items.includes(id);
       const found = menu.getElementsByAttribute("label", id);
       is(found.length, expected, `menu item ${id} ${expected ? "" : "not "}found`);
     }
     // Return the first menu item, we need to click it.
     return menu.getElementsByAttribute("label", items[0])[0];
--- a/browser/components/extensions/test/browser/browser_ext_contextMenus_icons.js
+++ b/browser/components/extensions/test/browser/browser_ext_contextMenus_icons.js
@@ -29,16 +29,19 @@ add_task(function* () {
           browser.contextMenus.remove(menuitemId);
         },
       });
 
       browser.contextMenus.create({
         title: "child",
       });
 
+      browser.test.onMessage.addListener(() => {
+        browser.test.sendMessage("pong");
+      });
       browser.test.notifyPass("contextmenus-icons");
     },
   });
 
   let confirmContextMenuIcon = (rootElement) => {
     let expectedURL = new RegExp(String.raw`^moz-extension://[^/]+/extension\.png$`);
     let imageUrl = rootElement.getAttribute("image");
     ok(expectedURL.test(imageUrl), "The context menu should display the extension icon next to the root element");
@@ -50,16 +53,20 @@ add_task(function* () {
   let extensionMenu = yield openExtensionContextMenu();
 
   let contextMenu = document.getElementById("contentAreaContextMenu");
   let topLevelMenuItem = contextMenu.getElementsByAttribute("ext-type", "top-level-menu")[0];
   confirmContextMenuIcon(topLevelMenuItem);
 
   let childToDelete = extensionMenu.getElementsByAttribute("label", "child-to-delete")[0];
   yield closeExtensionContextMenu(childToDelete);
+  // Now perform a roundtrip to the extension process to make sure that the
+  // click event has had a chance to fire.
+  extension.sendMessage("ping");
+  yield extension.awaitMessage("pong");
 
   yield openExtensionContextMenu();
 
   contextMenu = document.getElementById("contentAreaContextMenu");
   topLevelMenuItem = contextMenu.getElementsByAttribute("label", "child")[0];
 
   confirmContextMenuIcon(topLevelMenuItem);
   yield closeContextMenu();
new file mode 100644
--- /dev/null
+++ b/browser/components/extensions/test/browser/browser_ext_contextMenus_onclick.js
@@ -0,0 +1,196 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+"use strict";
+
+// Loaded both as a background script and a tab page.
+function testScript() {
+  let page = location.pathname.includes("tab.html") ? "tab" : "background";
+  let clickCounts = {
+    old: 0,
+    new: 0,
+  };
+  browser.contextMenus.onClicked.addListener(() => {
+    // Async to give other onclick handlers a chance to fire.
+    setTimeout(() => {
+      browser.test.sendMessage("onClicked-fired", page);
+    });
+  });
+  browser.test.onMessage.addListener((toPage, msg) => {
+    if (toPage !== page) {
+      return;
+    }
+    browser.test.log(`Received ${msg} for ${toPage}`);
+    if (msg == "get-click-counts") {
+      browser.test.sendMessage("click-counts", clickCounts);
+    } else if (msg == "clear-click-counts") {
+      clickCounts.old = clickCounts.new = 0;
+      browser.test.sendMessage("next");
+    } else if (msg == "create-with-onclick") {
+      browser.contextMenus.create({
+        id: "iden",
+        title: "tifier",
+        onclick() {
+          ++clickCounts.old;
+          browser.test.log(`onclick fired for original onclick property in ${page}`);
+        },
+      }, () => browser.test.sendMessage("next"));
+    } else if (msg == "create-without-onclick") {
+      browser.contextMenus.create({
+        id: "iden",
+        title: "tifier",
+      }, () => browser.test.sendMessage("next"));
+    } else if (msg == "update-without-onclick") {
+      browser.contextMenus.update("iden", {
+        enabled: true,  // Already enabled, so this does nothing.
+      }, () => browser.test.sendMessage("next"));
+    } else if (msg == "update-with-onclick") {
+      browser.contextMenus.update("iden", {
+        onclick() {
+          ++clickCounts.new;
+          browser.test.log(`onclick fired for updated onclick property in ${page}`);
+        },
+      }, () => browser.test.sendMessage("next"));
+    } else if (msg == "remove") {
+      browser.contextMenus.remove("iden", () => browser.test.sendMessage("next"));
+    } else if (msg == "removeAll") {
+      browser.contextMenus.removeAll(() => browser.test.sendMessage("next"));
+    }
+  });
+
+  if (page == "background") {
+    browser.test.log("Opening tab.html");
+    browser.tabs.create({
+      url: "tab.html",
+      active: false,  // To not interfere with the context menu tests.
+    });
+  } else {
+    // Sanity check - the pages must be in the same process.
+    let pages = browser.extension.getViews();
+    browser.test.assertTrue(pages.includes(window),
+        "Expected this tab to be an extension view");
+    pages = pages.filter(w => w !== window);
+    browser.test.assertEq(pages[0], browser.extension.getBackgroundPage(),
+        "Expected the other page to be a background page");
+    browser.test.sendMessage("tab.html ready");
+  }
+}
+
+add_task(function* () {
+  let tab1 = yield BrowserTestUtils.openNewForegroundTab(gBrowser,
+    "http://mochi.test:8888/browser/browser/components/extensions/test/browser/context.html");
+
+  gBrowser.selectedTab = tab1;
+
+  let extension = ExtensionTestUtils.loadExtension({
+    manifest: {
+      "permissions": ["contextMenus"],
+    },
+    background: testScript,
+    files: {
+      "tab.html": `<!DOCTYPE html><meta charset="utf-8"><script src="tab.js"></script>`,
+      "tab.js": testScript,
+    },
+  });
+  yield extension.startup();
+  yield extension.awaitMessage("tab.html ready");
+
+  function* clickContextMenu() {
+    // Using openContextMenu instead of openExtensionContextMenu because the
+    // test extension has only one context menu item.
+    let extensionMenuRoot = yield openContextMenu();
+    let items = extensionMenuRoot.getElementsByAttribute("label", "tifier");
+    is(items.length, 1, "Expected one context menu item");
+    yield closeExtensionContextMenu(items[0]);
+    // One of them is "tab", the other is "background".
+    info(`onClicked from: ${yield extension.awaitMessage("onClicked-fired")}`);
+    info(`onClicked from: ${yield extension.awaitMessage("onClicked-fired")}`);
+  }
+
+  function* getCounts(page) {
+    extension.sendMessage(page, "get-click-counts");
+    return yield extension.awaitMessage("click-counts");
+  }
+  function* resetCounts() {
+    extension.sendMessage("tab", "clear-click-counts");
+    extension.sendMessage("background", "clear-click-counts");
+    yield extension.awaitMessage("next");
+    yield extension.awaitMessage("next");
+  }
+
+  // During this test, at most one "onclick" attribute is expected at any time.
+  for (let pageOne of ["background", "tab"]) {
+    for (let pageTwo of ["background", "tab"]) {
+      info(`Testing with menu created by ${pageOne} and updated by ${pageTwo}`);
+      extension.sendMessage(pageOne, "create-with-onclick");
+      yield extension.awaitMessage("next");
+
+      // Test that update without onclick attribute does not clear the existing
+      // onclick handler.
+      extension.sendMessage(pageTwo, "update-without-onclick");
+      yield extension.awaitMessage("next");
+      yield clickContextMenu();
+      let clickCounts = yield getCounts(pageOne);
+      is(clickCounts.old, 1, `Original onclick should still be present in ${pageOne}`);
+      is(clickCounts.new, 0, `Not expecting any new handlers in ${pageOne}`);
+      if (pageOne !== pageTwo) {
+        clickCounts = yield getCounts(pageTwo);
+        is(clickCounts.old, 0, `Not expecting any handlers in ${pageTwo}`);
+        is(clickCounts.new, 0, `Not expecting any new handlers in ${pageTwo}`);
+      }
+      yield resetCounts();
+
+      // Test that update with onclick handler in a different page clears the
+      // existing handler and activates the new onclick handler.
+      extension.sendMessage(pageTwo, "update-with-onclick");
+      yield extension.awaitMessage("next");
+      yield clickContextMenu();
+      clickCounts = yield getCounts(pageOne);
+      is(clickCounts.old, 0, `Original onclick should be gone from ${pageOne}`);
+      if (pageOne !== pageTwo) {
+        is(clickCounts.new, 0, `Still not expecting new handlers in ${pageOne}`);
+      }
+      clickCounts = yield getCounts(pageTwo);
+      if (pageOne !== pageTwo) {
+        is(clickCounts.old, 0, `Not expecting an old onclick in ${pageTwo}`);
+      }
+      is(clickCounts.new, 1, `New onclick should be triggered in ${pageTwo}`);
+      yield resetCounts();
+
+      // Test that updating the handler (different again from the last `update`
+      // call, but the same as the `create` call) clears the existing handler
+      // and activates the new onclick handler.
+      extension.sendMessage(pageOne, "update-with-onclick");
+      yield extension.awaitMessage("next");
+      yield clickContextMenu();
+      clickCounts = yield getCounts(pageOne);
+      is(clickCounts.new, 1, `onclick should be triggered in ${pageOne}`);
+      if (pageOne !== pageTwo) {
+        clickCounts = yield getCounts(pageTwo);
+        is(clickCounts.new, 0, `onclick should be gone from ${pageTwo}`);
+      }
+      yield resetCounts();
+
+      // Test that removing the context menu and recreating it with the same ID
+      // (in a different context) does not leave behind any onclick handlers.
+      extension.sendMessage(pageTwo, "remove");
+      yield extension.awaitMessage("next");
+      extension.sendMessage(pageTwo, "create-without-onclick");
+      yield extension.awaitMessage("next");
+      yield clickContextMenu();
+      clickCounts = yield getCounts(pageOne);
+      is(clickCounts.new, 0, `Did not expect any click handlers in ${pageOne}`);
+      if (pageOne !== pageTwo) {
+        clickCounts = yield getCounts(pageTwo);
+        is(clickCounts.new, 0, `Did not expect any click handlers in ${pageTwo}`);
+      }
+      yield resetCounts();
+
+      // Remove context menu for the next iteration of the test. And just to get
+      // more coverage, let's use removeAll instead of remove.
+      extension.sendMessage(pageOne, "removeAll");
+      yield extension.awaitMessage("next");
+    }
+  }
+  yield extension.unload();
+  yield BrowserTestUtils.removeTab(tab1);
+});
--- a/browser/components/extensions/test/browser/browser_ext_pageAction_popup_resize.js
+++ b/browser/components/extensions/test/browser/browser_ext_pageAction_popup_resize.js
@@ -1,33 +1,28 @@
 /* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set sts=2 sw=2 et tw=80: */
 "use strict";
 
-function* awaitResize(browser) {
-  // Debouncing code makes this a bit racy.
-  // Try to skip the first, early resize, and catch the resize event we're
-  // looking for, but don't wait longer than a few seconds.
-
-  return Promise.race([
-    BrowserTestUtils.waitForEvent(browser, "WebExtPopupResized")
-      .then(() => BrowserTestUtils.waitForEvent(browser, "WebExtPopupResized")),
-    new Promise(resolve => setTimeout(resolve, 5000)),
-  ]);
-}
+let delay = ms => new Promise(resolve => {
+  setTimeout(resolve, ms);
+});
 
 add_task(function* testPageActionPopupResize() {
+  let browser;
+
   let extension = ExtensionTestUtils.loadExtension({
     manifest: {
       "page_action": {
         "default_popup": "popup.html",
         "browser_style": true,
       },
     },
     background: function() {
+      /* global browser */
       browser.tabs.query({active: true, currentWindow: true}, tabs => {
         const tabId = tabs[0].id;
 
         browser.pageAction.show(tabId).then(() => {
           browser.test.sendMessage("action-shown");
         });
       });
     },
@@ -37,69 +32,70 @@ add_task(function* testPageActionPopupRe
     },
   });
 
   yield extension.startup();
   yield extension.awaitMessage("action-shown");
 
   clickPageAction(extension, window);
 
-  let {target: panelDocument} = yield BrowserTestUtils.waitForEvent(document, "load", true, (event) => {
-    info(`Loaded ${event.target.location}`);
-    return event.target.location && event.target.location.href.endsWith("popup.html");
-  });
+  browser = yield awaitExtensionPanel(extension);
 
-  let panelWindow = panelDocument.defaultView;
-  let panelBody = panelDocument.body.firstChild;
-  let body = panelDocument.body;
-  let root = panelDocument.documentElement;
+  function* checkSize(expected) {
+    let dims = yield promiseContentDimensions(browser);
+    let {body, root} = dims;
 
-  function checkSize(expected) {
-    is(panelWindow.innerHeight, expected, `Panel window should be ${expected}px tall`);
+    is(dims.window.innerHeight, expected, `Panel window should be ${expected}px tall`);
     is(body.clientHeight, body.scrollHeight,
       "Panel body should be tall enough to fit its contents");
     is(root.clientHeight, root.scrollHeight,
       "Panel root should be tall enough to fit its contents");
 
     // Tolerate if it is 1px too wide, as that may happen with the current resizing method.
-    ok(Math.abs(panelWindow.innerWidth - expected) <= 1, `Panel window should be ${expected}px wide`);
+    ok(Math.abs(dims.window.innerWidth - expected) <= 1, `Panel window should be ${expected}px wide`);
     is(body.clientWidth, body.scrollWidth,
        "Panel body should be wide enough to fit its contents");
   }
 
+  /* eslint-disable mozilla/no-cpows-in-tests */
   function setSize(size) {
-    panelBody.style.height = `${size}px`;
-    panelBody.style.width = `${size}px`;
-
-    return BrowserTestUtils.waitForEvent(panelWindow, "resize");
+    let elem = content.document.body.firstChild;
+    elem.style.height = `${size}px`;
+    elem.style.width = `${size}px`;
   }
+  /* eslint-enable mozilla/no-cpows-in-tests */
 
   let sizes = [
     200,
     400,
     300,
   ];
 
   for (let size of sizes) {
-    yield setSize(size);
-    checkSize(size);
+    yield alterContent(browser, setSize, size);
+    yield checkSize(size);
   }
 
-  yield setSize(1400);
+  yield alterContent(browser, setSize, 1400);
+
+  let dims = yield promiseContentDimensions(browser);
+  let {body, root} = dims;
 
   if (AppConstants.platform == "win") {
-    ok(panelWindow.innerWidth >= 750 && panelWindow.innerWidth <= 800,
-       `Panel window width ${panelWindow.innerWidth} is in acceptable range`);
-  } else {
-    is(panelWindow.innerWidth, 800, "Panel window width");
+    while (dims.window.innerWidth < 800) {
+      yield delay(50);
+      dims = yield promiseContentDimensions(browser);
+    }
   }
+
+  is(dims.window.innerWidth, 800, "Panel window width");
   ok(body.clientWidth <= 800, `Panel body width ${body.clientWidth} is less than 800`);
   is(body.scrollWidth, 1400, "Panel body scroll width");
 
-  is(panelWindow.innerHeight, 600, "Panel window height");
+  is(dims.window.innerHeight, 600, "Panel window height");
   ok(root.clientHeight <= 600, `Panel root height (${root.clientHeight}px) is less than 600px`);
   is(root.scrollHeight, 1400, "Panel root scroll height");
 
   yield extension.unload();
 });
 
 add_task(function* testPageActionPopupReflow() {
   let browser;
@@ -136,35 +132,38 @@ add_task(function* testPageActionPopupRe
 
   yield extension.startup();
   yield extension.awaitMessage("action-shown");
 
   clickPageAction(extension, window);
 
   browser = yield awaitExtensionPanel(extension);
 
-  let win = browser.contentWindow;
-  let body = win.document.body;
-  let root = win.document.documentElement;
+  /* eslint-disable mozilla/no-cpows-in-tests */
+  function setSize(size) {
+    content.document.body.style.fontSize = `${size}px`;
+  }
+  /* eslint-enable mozilla/no-cpows-in-tests */
 
-  function setSize(size) {
-    body.style.fontSize = `${size}px`;
+  let dims = yield alterContent(browser, setSize, 18);
 
-    return awaitResize(browser);
+  if (AppConstants.platform == "win") {
+    while (dims.window.innerWidth < 800) {
+      yield delay(50);
+      dims = yield promiseContentDimensions(browser);
+    }
   }
 
-  yield setSize(18);
-
-  is(win.innerWidth, 800, "Panel window should be 800px wide");
-  is(body.clientWidth, 800, "Panel body should be 800px wide");
-  is(body.clientWidth, body.scrollWidth,
+  is(dims.window.innerWidth, 800, "Panel window should be 800px wide");
+  is(dims.body.clientWidth, 800, "Panel body should be 800px wide");
+  is(dims.body.clientWidth, dims.body.scrollWidth,
      "Panel body should be wide enough to fit its contents");
 
-  ok(win.innerHeight > 36,
-     `Panel window height (${win.innerHeight}px) should be taller than two lines of text.`);
+  ok(dims.window.innerHeight > 36,
+     `Panel window height (${dims.window.innerHeight}px) should be taller than two lines of text.`);
 
-  is(body.clientHeight, body.scrollHeight,
+  is(dims.body.clientHeight, dims.body.scrollHeight,
     "Panel body should be tall enough to fit its contents");
-  is(root.clientHeight, root.scrollHeight,
+  is(dims.root.clientHeight, dims.root.scrollHeight,
     "Panel root should be tall enough to fit its contents");
 
   yield extension.unload();
 });
--- a/browser/components/extensions/test/browser/browser_ext_popup_background.js
+++ b/browser/components/extensions/test/browser/browser_ext_popup_background.js
@@ -1,24 +1,12 @@
 /* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set sts=2 sw=2 et tw=80: */
 "use strict";
 
-function* awaitResize(browser) {
-  // Debouncing code makes this a bit racy.
-  // Try to skip the first, early resize, and catch the resize event we're
-  // looking for, but don't wait longer than a few seconds.
-
-  return Promise.race([
-    BrowserTestUtils.waitForEvent(browser, "WebExtPopupResized")
-      .then(() => BrowserTestUtils.waitForEvent(browser, "WebExtPopupResized")),
-    new Promise(resolve => setTimeout(resolve, 5000)),
-  ]);
-}
-
 add_task(function* testPopupBackground() {
   let extension = ExtensionTestUtils.loadExtension({
     background() {
       browser.tabs.query({active: true, currentWindow: true}, tabs => {
         browser.pageAction.show(tabs[0].id);
       });
     },
 
@@ -74,36 +62,44 @@ add_task(function* testPopupBackground()
       let backgroundIndex = image.lastIndexOf(`fill="${background}"`);
 
       ok(borderIndex >= 0, `Have border fill (index=${borderIndex})`);
       ok(backgroundIndex >= 0, `Have background fill (index=${backgroundIndex})`);
       is(getComputedStyle(arrowContent).backgroundColor, background, "Arrow content should have correct background");
       isnot(borderIndex, backgroundIndex, "Border and background fills are separate elements");
     };
 
-    let win = browser.contentWindow;
-    let body = win.document.body;
+    function getBackground(browser) {
+      return ContentTask.spawn(browser, null, function* () {
+        return content.getComputedStyle(content.document.body)
+                      .backgroundColor;
+      });
+    }
+
+    /* eslint-disable mozilla/no-cpows-in-tests */
+    let setBackground = color => {
+      content.document.body.style.backgroundColor = color;
+    };
+    /* eslint-enable mozilla/no-cpows-in-tests */
 
     yield new Promise(resolve => setTimeout(resolve, 100));
 
     info("Test that initial background color is applied");
 
-    checkArrow(win.getComputedStyle(body).backgroundColor);
+    checkArrow(yield getBackground(browser));
 
     info("Test that dynamically-changed background color is applied");
 
-    body.style.backgroundColor = "black";
-    yield awaitResize(browser);
+    yield alterContent(browser, setBackground, "black");
 
-    checkArrow(win.getComputedStyle(body).backgroundColor);
+    checkArrow(yield getBackground(browser));
 
     info("Test that non-opaque background color results in default styling");
 
-    body.style.backgroundColor = "rgba(1, 2, 3, .9)";
-    yield awaitResize(browser);
+    yield alterContent(browser, setBackground, "rgba(1, 2, 3, .9)");
 
     checkArrow(null);
   }
 
   {
     info("Test stand-alone browserAction popup");
 
     clickBrowserAction(extension);
--- a/browser/components/extensions/test/browser/browser_ext_popup_corners.js
+++ b/browser/components/extensions/test/browser/browser_ext_popup_corners.js
@@ -37,27 +37,34 @@ add_task(function* testPopupBorderRadius
     let panel = getPanelForNode(browser);
     let arrowContent = document.getAnonymousElementByAttribute(panel, "class", "panel-arrowcontent");
 
     let panelStyle = getComputedStyle(arrowContent);
 
     let viewNode = browser.parentNode === panel ? browser : browser.parentNode;
     let viewStyle = getComputedStyle(viewNode);
 
-    let win = browser.contentWindow;
-    let bodyStyle = win.getComputedStyle(win.document.body);
+    let props = ["borderTopLeftRadius", "borderTopRightRadius",
+                 "borderBottomRightRadius", "borderBottomLeftRadius"];
 
-    for (let prop of ["borderTopLeftRadius", "borderTopRightRadius",
-                      "borderBottomRightRadius", "borderBottomLeftRadius"]) {
+    /* eslint-disable mozilla/no-cpows-in-tests */
+    let bodyStyle = yield ContentTask.spawn(browser, props, function* (props) {
+      let bodyStyle = content.getComputedStyle(content.document.body);
+
+      return new Map(props.map(prop => [prop, bodyStyle[prop]]));
+    });
+    /* eslint-enable mozilla/no-cpows-in-tests */
+
+    for (let prop of props) {
       if (standAlone) {
         is(viewStyle[prop], panelStyle[prop], `Panel and view ${prop} should be the same`);
-        is(bodyStyle[prop], panelStyle[prop], `Panel and body ${prop} should be the same`);
+        is(bodyStyle.get(prop), panelStyle[prop], `Panel and body ${prop} should be the same`);
       } else {
         is(viewStyle[prop], "0px", `View node ${prop} should be 0px`);
-        is(bodyStyle[prop], "0px", `Body node ${prop} should be 0px`);
+        is(bodyStyle.get(prop), "0px", `Body node ${prop} should be 0px`);
       }
     }
   }
 
   {
     info("Test stand-alone browserAction popup");
 
     clickBrowserAction(extension);
--- a/browser/components/extensions/test/browser/browser_ext_popup_shutdown.js
+++ b/browser/components/extensions/test/browser/browser_ext_popup_shutdown.js
@@ -1,17 +1,18 @@
 /* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set sts=2 sw=2 et tw=80: */
 "use strict";
 
 let getExtension = () => {
   return ExtensionTestUtils.loadExtension({
     background() {
       browser.tabs.query({active: true, currentWindow: true}, tabs => {
-        browser.pageAction.show(tabs[0].id);
+        browser.pageAction.show(tabs[0].id)
+          .then(() => { browser.test.sendMessage("pageAction ready"); });
       });
     },
 
     manifest: {
       "browser_action": {
         "default_popup": "popup.html",
         "browser_style": false,
       },
@@ -29,46 +30,51 @@ let getExtension = () => {
   });
 };
 
 add_task(function* testStandaloneBrowserAction() {
   info("Test stand-alone browserAction popup");
 
   let extension = getExtension();
   yield extension.startup();
+  yield extension.awaitMessage("pageAction ready");
 
   clickBrowserAction(extension);
   let browser = yield awaitExtensionPanel(extension);
   let panel = getPanelForNode(browser);
 
   yield extension.unload();
 
   is(panel.parentNode, null, "Panel should be removed from the document");
 });
 
 add_task(function* testMenuPanelBrowserAction() {
   let extension = getExtension();
   yield extension.startup();
+  yield extension.awaitMessage("pageAction ready");
 
   let widget = getBrowserActionWidget(extension);
   CustomizableUI.addWidgetToArea(widget.id, CustomizableUI.AREA_PANEL);
 
   clickBrowserAction(extension);
   let browser = yield awaitExtensionPanel(extension);
   let panel = getPanelForNode(browser);
 
   yield extension.unload();
 
   is(panel.state, "closed", "Panel should be closed");
 });
 
 add_task(function* testPageAction() {
   let extension = getExtension();
   yield extension.startup();
+  yield extension.awaitMessage("pageAction ready");
 
   clickPageAction(extension);
   let browser = yield awaitExtensionPanel(extension);
   let panel = getPanelForNode(browser);
 
   yield extension.unload();
 
+  yield new Promise(resolve => setTimeout(resolve, 0));
+
   is(panel.parentNode, null, "Panel should be removed from the document");
 });
--- a/browser/components/extensions/test/browser/browser_ext_runtime_openOptionsPage.js
+++ b/browser/components/extensions/test/browser/browser_ext_runtime_openOptionsPage.js
@@ -25,16 +25,17 @@ function* loadExtension(options) {
         <html>
           <head>
             <meta charset="utf-8">
             <script src="options.js" type="text/javascript"></script>
           </head>
         </html>`,
 
       "options.js": function() {
+        window.iAmOption = true;
         browser.runtime.sendMessage("options.html");
         browser.runtime.onMessage.addListener((msg, sender, respond) => {
           if (msg == "ping") {
             respond("pong");
           }
         });
       },
     },
@@ -91,16 +92,23 @@ add_tasks(function* test_inline_options(
           awaitOptions(),
         ]);
       }).then(([, tab]) => {
         browser.test.assertEq("about:addons", tab.url, "Tab contains AddonManager");
         browser.test.assertTrue(tab.active, "Tab is active");
         browser.test.assertTrue(tab.id != firstTab, "Tab is a new tab");
 
         optionsTab = tab.id;
+        browser.test.assertEq(0, browser.extension.getViews({type: "popup"}).length, "viewType is not popup");
+        browser.test.assertEq(1, browser.extension.getViews({type: "tab"}).length, "viewType is tab");
+        browser.test.assertEq(1, browser.extension.getViews({windowId: tab.windowId}).length, "windowId matches");
+        let views = browser.extension.getViews();
+        browser.test.assertEq(2, views.length, "Expected the options page and the background page");
+        browser.test.assertTrue(views.includes(window), "One of the views is the background page");
+        browser.test.assertTrue(views.some(w => w.iAmOption), "One of the views is the options page");
 
         browser.test.log("Switch tabs.");
         return browser.tabs.update(firstTab, {active: true});
       }).then(() => {
         browser.test.log("Open options page again. Expect tab re-selected, no new load.");
 
         return browser.runtime.openOptionsPage();
       }).then(() => {
@@ -190,16 +198,23 @@ add_tasks(function* test_tab_options(ext
           awaitOptions(),
         ]);
       }).then(([, tab]) => {
         browser.test.assertEq(optionsURL, tab.url, "Tab contains options.html");
         browser.test.assertTrue(tab.active, "Tab is active");
         browser.test.assertTrue(tab.id != firstTab, "Tab is a new tab");
 
         optionsTab = tab.id;
+        browser.test.assertEq(0, browser.extension.getViews({type: "popup"}).length, "viewType is not popup");
+        browser.test.assertEq(1, browser.extension.getViews({type: "tab"}).length, "viewType is tab");
+        browser.test.assertEq(1, browser.extension.getViews({windowId: tab.windowId}).length, "windowId matches");
+        let views = browser.extension.getViews();
+        browser.test.assertEq(2, views.length, "Expected the options page and the background page");
+        browser.test.assertTrue(views.includes(window), "One of the views is the background page");
+        browser.test.assertTrue(views.some(w => w.iAmOption), "One of the views is the options page");
 
         browser.test.log("Switch tabs.");
         return browser.tabs.update(firstTab, {active: true});
       }).then(() => {
         browser.test.log("Open options page again. Expect tab re-selected, no new load.");
 
         return browser.runtime.openOptionsPage();
       }).then(() => {
--- a/browser/components/extensions/test/browser/browser_ext_tabs_executeScript.js
+++ b/browser/components/extensions/test/browser/browser_ext_tabs_executeScript.js
@@ -1,17 +1,41 @@
 /* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set sts=2 sw=2 et tw=80: */
 "use strict";
 
 add_task(function* testExecuteScript() {
   let {MessageChannel} = Cu.import("resource://gre/modules/MessageChannel.jsm", {});
 
-  let messageManagersSize = MessageChannel.messageManagers.size;
-  let responseManagersSize = MessageChannel.responseManagers.size;
+  function countMM(messageManagerMap) {
+    let count = 0;
+    // List of permanent message managers in the main process. We should not
+    // count them in the test because MessageChannel unsubscribes when the
+    // message manager closes, which never happens to these, of course.
+    let globalMMs = [
+      Services.mm,
+      Services.ppmm,
+      Services.ppmm.getChildAt(0),
+    ];
+    for (let mm of messageManagerMap.keys()) {
+      // Sanity check: mm is a message manager.
+      try {
+        mm.QueryInterface(Ci.nsIMessageSender);
+      } catch (e) {
+        mm.QueryInterface(Ci.nsIMessageBroadcaster);
+      }
+      if (!globalMMs.includes(mm)) {
+        ++count;
+      }
+    }
+    return count;
+  }
+
+  let messageManagersSize = countMM(MessageChannel.messageManagers);
+  let responseManagersSize = countMM(MessageChannel.responseManagers);
 
   const BASE = "http://mochi.test:8888/browser/browser/components/extensions/test/browser/";
   const URL = BASE + "file_iframe_document.html";
   let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, URL, true);
 
   function background() {
     browser.tabs.query({active: true, currentWindow: true}).then(tabs => {
       return browser.webNavigation.getAllFrames({tabId: tabs[0].id});
@@ -198,12 +222,12 @@ add_task(function* testExecuteScript() {
   yield extension.awaitFinish("executeScript");
 
   yield extension.unload();
 
   yield BrowserTestUtils.removeTab(tab);
 
   // Make sure that we're not holding on to references to closed message
   // managers.
-  is(MessageChannel.messageManagers.size, messageManagersSize, "Message manager count");
-  is(MessageChannel.responseManagers.size, responseManagersSize, "Response manager count");
+  is(countMM(MessageChannel.messageManagers), messageManagersSize, "Message manager count");
+  is(countMM(MessageChannel.responseManagers), responseManagersSize, "Response manager count");
   is(MessageChannel.pendingResponses.size, 0, "Pending response count");
 });
--- a/browser/components/extensions/test/browser/browser_ext_tabs_getCurrent.js
+++ b/browser/components/extensions/test/browser/browser_ext_tabs_getCurrent.js
@@ -22,17 +22,16 @@ add_task(function* () {
             if (tabId == currentTab.id) {
               browser.tabs.onActivated.removeListener(listener);
 
               browser.tabs.getCurrent(currentTab => {
                 browser.test.assertEq(currentTab.id, tabId, "in active background tab");
                 browser.test.assertEq(currentTab.url, url, "getCurrent in non-active background tab");
 
                 browser.test.sendMessage("tab-finished");
-                browser.tabs.remove(tabId);
               });
             }
           });
           browser.tabs.update(currentTab.id, {active: true});
         });
       },
 
       "popup.js": function() {
@@ -61,10 +60,11 @@ add_task(function* () {
   yield extension.awaitMessage("background-finished");
   yield extension.awaitMessage("tab-finished");
 
   clickBrowserAction(extension);
   yield awaitExtensionPanel(extension);
   yield extension.awaitMessage("popup-finished");
   yield closeBrowserAction(extension);
 
+  // The extension tab is automatically closed when the extension unloads.
   yield extension.unload();
 });
--- a/browser/components/extensions/test/browser/browser_ext_tabs_move.js
+++ b/browser/components/extensions/test/browser/browser_ext_tabs_move.js
@@ -64,25 +64,34 @@ add_task(function* () {
     manifest: {
       "permissions": ["tabs"],
     },
 
     background: function() {
       browser.tabs.query(
         {lastFocusedWindow: true},
         tabs => {
-          let tab = tabs[0];
+          let tab = tabs[1];
           // Assuming that tab.id of 12345 does not exist.
-          browser.tabs.move([12345, tab.id], {index: 0});
-          browser.tabs.query(
-            {lastFocusedWindow: true},
-            tabs => {
-              browser.test.assertEq(tabs[0].url, tab.url, "should be first tab");
-              browser.test.notifyPass("tabs.move.invalid");
-            });
+          browser.tabs.move([tab.id, 12345], {index: 0})
+          .then(
+            tabs => { browser.test.fail("Promise should not resolve"); },
+            e => {
+              browser.test.assertTrue(/Invalid tab/.test(e),
+                                      "Invalid tab should be in error");
+            })
+          .then(
+            browser.tabs.query({lastFocusedWindow: true})
+            .then(
+              (tabs) => {
+                browser.test.assertEq(tabs[1].url, tab.url, "should be second tab");
+                browser.test.notifyPass("tabs.move.invalid");
+              }
+            )
+          );
         });
     },
   });
 
   yield extension.startup();
   yield extension.awaitFinish("tabs.move.invalid");
   yield extension.unload();
 
--- a/browser/components/extensions/test/browser/browser_ext_tabs_move_window.js
+++ b/browser/components/extensions/test/browser/browser_ext_tabs_move_window.js
@@ -22,16 +22,26 @@ add_task(function* () {
 
         browser.tabs.query(
           {url: "<all_urls>"},
           tabs => {
             browser.test.assertEq(tabs[0].url, "http://example.com/");
             browser.test.assertEq(tabs[0].windowId, destination.windowId);
             browser.test.notifyPass("tabs.move.window");
           });
+
+        // Assuming that this windowId does not exist.
+        browser.tabs.move(source.id, {windowId: 123144576, index: 0})
+        .then(
+          tabs => { browser.test.fail("Promise should not resolve"); },
+          e => {
+            browser.test.assertTrue(/Invalid window/.test(e),
+                                    "Invalid window should be in error");
+          }
+        );
       });
     },
   });
 
   yield extension.startup();
   yield extension.awaitFinish("tabs.move.window");
   yield extension.unload();
 
--- a/browser/components/extensions/test/browser/browser_ext_tabs_onUpdated.js
+++ b/browser/components/extensions/test/browser/browser_ext_tabs_onUpdated.js
@@ -185,13 +185,14 @@ add_task(function* test_without_tabs_per
 
           if (changeInfo.status == "complete") {
             browser.tabs.onUpdated.removeListener(onUpdated);
             browser.tabs.remove(tabId);
             browser.test.notifyPass("finish");
           }
         }
       });
+      browser.tabs.reload(tab.id);
     });
   }, false /* withPermissions */);
 });
 
 add_task(forceGC);
--- a/browser/components/extensions/test/browser/browser_ext_windows_create_tabId.js
+++ b/browser/components/extensions/test/browser/browser_ext_windows_create_tabId.js
@@ -108,28 +108,29 @@ add_task(function* testWindowCreate() {
         error => {
           browser.test.assertTrue(/Invalid tab ID: 0/.test(error.message),
                                   "Create call failed as expected");
         }
       );
     }).then(() => {
       browser.test.log("Try to create a window with two URLs");
 
-      return browser.windows.create({url: ["http://example.com/", "http://example.org/"]});
-    }).then(window => {
+      return Promise.all([
+        // tabs.onUpdated can be invoked between the call of windows.create and
+        // the invocation of its callback/promise, so set up the listeners
+        // before creating the window.
+        promiseTabUpdated("http://example.com/"),
+        promiseTabUpdated("http://example.org/"),
+        browser.windows.create({url: ["http://example.com/", "http://example.org/"]}),
+      ]);
+    }).then(([, , window]) => {
       browser.test.assertEq(2, window.tabs.length, "2 tabs were opened in new window");
       browser.test.assertEq("about:blank", window.tabs[0].url, "about:blank, page not loaded yet");
       browser.test.assertEq("about:blank", window.tabs[1].url, "about:blank, page not loaded yet");
 
-      return Promise.all([
-        promiseTabUpdated("http://example.com/"),
-        promiseTabUpdated("http://example.org/"),
-        Promise.resolve(window),
-      ]);
-    }).then(([, , window]) => {
       return browser.windows.get(window.id, {populate: true});
     }).then(window => {
       browser.test.assertEq(2, window.tabs.length, "2 tabs were opened in new window");
       browser.test.assertEq("http://example.com/", window.tabs[0].url, "Correct URL was loaded in tab 1");
       browser.test.assertEq("http://example.org/", window.tabs[1].url, "Correct URL was loaded in tab 2");
       return browser.windows.remove(window.id);
     }).then(() => {
       browser.test.notifyPass("window-create");
--- a/browser/components/extensions/test/browser/browser_ext_windows_events.js
+++ b/browser/components/extensions/test/browser/browser_ext_windows_events.js
@@ -11,20 +11,25 @@ add_task(function* testWindowsEvents() {
 
       browser.test.assertTrue(Number.isInteger(window.id),
                               "Window object's id is an integer");
       browser.test.assertEq("normal", window.type,
                             "Window object returned with the correct type");
       browser.test.sendMessage("window-created", window.id);
     });
 
-    let lastWindowId;
+    let lastWindowId, os;
     browser.windows.onFocusChanged.addListener(function listener(windowId) {
       browser.test.log(`onFocusChange: windowId=${windowId} lastWindowId=${lastWindowId}`);
 
+      if (windowId === browser.windows.WINDOW_ID_NONE && os === "linux") {
+        browser.test.log("Ignoring a superfluous WINDOW_ID_NONE (blur) event on Linux");
+        return;
+      }
+
       browser.test.assertTrue(lastWindowId !== windowId,
                               "onFocusChanged fired once for the given window");
       lastWindowId = windowId;
 
       browser.test.assertTrue(Number.isInteger(windowId),
                               "windowId is an integer");
 
       browser.windows.getLastFocused().then(window => {
@@ -38,17 +43,20 @@ add_task(function* testWindowsEvents() {
       browser.test.log(`onRemoved: windowId=${windowId}`);
 
       browser.test.assertTrue(Number.isInteger(windowId),
                               "windowId is an integer");
       browser.test.sendMessage(`window-removed`, windowId);
       browser.test.notifyPass("windows.events");
     });
 
-    browser.test.sendMessage("ready");
+    browser.runtime.getPlatformInfo(info => {
+      os = info.os;
+      browser.test.sendMessage("ready");
+    });
   }
 
   let extension = ExtensionTestUtils.loadExtension({
     background: `(${background})()`,
   });
 
   yield extension.startup();
   yield extension.awaitMessage("ready");
--- a/browser/components/extensions/test/browser/browser_ext_windows_update.js
+++ b/browser/components/extensions/test/browser/browser_ext_windows_update.js
@@ -63,18 +63,20 @@ add_task(function* testWindowUpdate() {
       let os;
       function checkWindow(expected) {
         return new Promise(resolve => {
           _checkWindowPromise = {resolve};
           browser.test.sendMessage("check-window", expected);
         });
       }
 
+      let currentWindowId;
       function updateWindow(windowId, params, expected) {
         return browser.windows.update(windowId, params).then(window => {
+          browser.test.assertEq(currentWindowId, window.id, "Expected WINDOW_ID_CURRENT to refer to the same window");
           for (let key of Object.keys(params)) {
             if (key == "state" && os == "mac" && params.state == "normal") {
               // OS-X doesn't have a hard distinction between "normal" and
               // "maximized" states.
               browser.test.assertTrue(window.state == "normal" || window.state == "maximized",
                                       `Expected window.state (currently ${window.state}) to be "normal" but will accept "maximized"`);
             } else {
               browser.test.assertEq(params[key], window[key], `Got expected value for window.${key}`);
@@ -83,16 +85,17 @@ add_task(function* testWindowUpdate() {
 
           return checkWindow(expected);
         });
       }
 
       let windowId = browser.windows.WINDOW_ID_CURRENT;
 
       browser.runtime.getPlatformInfo().then(info => { os = info.os; })
+      .then(() => browser.windows.getCurrent().then(window => { currentWindowId = window.id; }))
       .then(() => updateWindow(windowId, {state: "maximized"}, {state: "STATE_MAXIMIZED"}))
       .then(() => updateWindow(windowId, {state: "minimized"}, {state: "STATE_MINIMIZED"}))
       .then(() => updateWindow(windowId, {state: "normal"}, {state: "STATE_NORMAL"}))
       .then(() => updateWindow(windowId, {state: "fullscreen"}, {state: "STATE_FULLSCREEN"}))
       .then(() => updateWindow(windowId, {state: "normal"}, {state: "STATE_NORMAL"}))
       .then(() => {
         browser.test.notifyPass("window-update");
       }).catch(e => {
@@ -104,17 +107,18 @@ add_task(function* testWindowUpdate() {
 
   extension.onMessage("check-window", expected => {
     if (expected.state != null) {
       let {windowState} = window;
       if (window.fullScreen) {
         windowState = window.STATE_FULLSCREEN;
       }
 
-      if (expected.state == "STATE_NORMAL" && AppConstants.platform == "macosx") {
+      // Temporarily accepting STATE_MAXIMIZED on Linux because of bug 1307759.
+      if (expected.state == "STATE_NORMAL" && (AppConstants.platform == "macosx" || AppConstants.platform == "linux")) {
         ok(windowState == window.STATE_NORMAL || windowState == window.STATE_MAXIMIZED,
            `Expected windowState (currently ${windowState}) to be STATE_NORMAL but will accept STATE_MAXIMIZED`);
       } else {
         is(windowState, window[expected.state],
            `Expected window state to be ${expected.state}`);
       }
     }
 
--- a/browser/components/extensions/test/browser/head.js
+++ b/browser/components/extensions/test/browser/head.js
@@ -6,17 +6,18 @@
  *          getBrowserActionWidget
  *          clickBrowserAction clickPageAction
  *          getBrowserActionPopup getPageActionPopup
  *          closeBrowserAction closePageAction
  *          promisePopupShown promisePopupHidden
  *          openContextMenu closeContextMenu
  *          openExtensionContextMenu closeExtensionContextMenu
  *          imageBuffer getListStyleImage getPanelForNode
- *          awaitExtensionPanel
+ *          awaitExtensionPanel awaitPopupResize
+ *          promiseContentDimensions alterContent
  */
 
 var {AppConstants} = Cu.import("resource://gre/modules/AppConstants.jsm");
 var {CustomizableUI} = Cu.import("resource:///modules/CustomizableUI.jsm");
 
 // Bug 1239884: Our tests occasionally hit a long GC pause at unpredictable
 // times in debug builds, which results in intermittent timeouts. Until we have
 // a better solution, we force a GC after certain strategic tests, which tend to
@@ -79,38 +80,77 @@ function promisePopupHidden(popup) {
     let onPopupHidden = event => {
       popup.removeEventListener("popuphidden", onPopupHidden);
       resolve();
     };
     popup.addEventListener("popuphidden", onPopupHidden);
   });
 }
 
+function promiseContentDimensions(browser) {
+  return ContentTask.spawn(browser, null, function* () {
+    function copyProps(obj, props) {
+      let res = {};
+      for (let prop of props) {
+        res[prop] = obj[prop];
+      }
+      return res;
+    }
+
+    return {
+      window: copyProps(content,
+                        ["innerWidth", "innerHeight", "outerWidth", "outerHeight",
+                         "scrollX", "scrollY", "scrollMaxX", "scrollMaxY"]),
+      body: copyProps(content.document.body,
+                      ["clientWidth", "clientHeight", "scrollWidth", "scrollHeight"]),
+      root: copyProps(content.document.documentElement,
+                      ["clientWidth", "clientHeight", "scrollWidth", "scrollHeight"]),
+
+      isStandards: content.document.compatMode !== "BackCompat",
+    };
+  });
+}
+
+function* awaitPopupResize(browser) {
+  return BrowserTestUtils.waitForEvent(browser, "WebExtPopupResized",
+                                       event => event.detail === "delayed");
+}
+
+function alterContent(browser, task, arg = null) {
+  return Promise.all([
+    ContentTask.spawn(browser, arg, task),
+    awaitPopupResize(browser),
+  ]).then(() => {
+    return promiseContentDimensions(browser);
+  });
+}
+
 function getPanelForNode(node) {
   while (node.localName != "panel") {
     node = node.parentNode;
   }
   return node;
 }
 
-var awaitExtensionPanel = Task.async(function* (extension, win = window, filename = "popup.html") {
-  let {target} = yield BrowserTestUtils.waitForEvent(win.document, "load", true, (event) => {
-    return event.target.location && event.target.location.href.endsWith(filename);
-  });
+var awaitBrowserLoaded = browser => ContentTask.spawn(browser, null, () => {
+  if (content.document.readyState !== "complete") {
+    return ContentTaskUtils.waitForEvent(content, "load").then(() => {});
+  }
+});
 
-  let browser = target.defaultView
-                      .QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDocShell)
-                      .chromeEventHandler;
+var awaitExtensionPanel = Task.async(function* (extension, win = window, awaitLoad = true) {
+  let {originalTarget: browser} = yield BrowserTestUtils.waitForEvent(
+    win.document, "WebExtPopupLoaded", true,
+    event => event.detail.extension.id === extension.id);
 
-  if (browser.matches(".webextension-preload-browser")) {
-    let event = yield BrowserTestUtils.waitForEvent(browser, "SwapDocShells");
-    browser = event.detail;
-  }
+  yield Promise.all([
+    promisePopupShown(getPanelForNode(browser)),
 
-  yield promisePopupShown(getPanelForNode(browser));
+    awaitLoad && awaitBrowserLoaded(browser),
+  ]);
 
   return browser;
 });
 
 function getBrowserActionWidget(extension) {
   return CustomizableUI.getWidget(makeWidgetId(extension.id) + "-browser-action");
 }
 
--- a/browser/components/migration/nsIEHistoryEnumerator.cpp
+++ b/browser/components/migration/nsIEHistoryEnumerator.cpp
@@ -76,17 +76,17 @@ nsIEHistoryEnumerator::HasMoreElements(b
     nsresult rv = NS_NewURI(getter_AddRefs(uri), url);
     ::CoTaskMemFree(statURL.pwcsUrl);
     if (NS_FAILED(rv)) {
       // Got a corrupt or invalid URI, continue to the next entry.
       return HasMoreElements(_retval);
     }
   }
 
-  nsDependentString title(statURL.pwcsTitle);
+  nsDependentString title(statURL.pwcsTitle ? statURL.pwcsTitle : L"");
 
   bool lastVisitTimeIsValid;
   PRTime lastVisited = WinMigrationFileTimeToPRTime(&(statURL.ftLastVisited), &lastVisitTimeIsValid);
 
   mCachedNextEntry = do_CreateInstance("@mozilla.org/hash-property-bag;1");
   MOZ_ASSERT(mCachedNextEntry, "Should have instanced a new property bag");
   if (mCachedNextEntry) {
     mCachedNextEntry->SetPropertyAsInterface(NS_LITERAL_STRING("uri"), uri);
--- a/browser/components/originattributes/test/browser/file_sharedworker.js
+++ b/browser/components/originattributes/test/browser/file_sharedworker.js
@@ -1,7 +1,9 @@
 self.randomValue = Math.random();
 
+/* global onconnect:true */
+
 onconnect = function (e) {
   let port = e.ports[0];
   port.postMessage(self.randomValue);
   port.start();
 };
--- a/browser/components/places/tests/chrome/test_0_bug510634.xul
+++ b/browser/components/places/tests/chrome/test_0_bug510634.xul
@@ -38,17 +38,16 @@
      * https://bugzilla.mozilla.org/show_bug.cgi?id=510634
      *
      * Ensures that properties for special queries are set on their tree nodes,
      * even if PlacesUIUtils.leftPaneFolderId was not initialized.
      */
 
     SimpleTest.waitForExplicitFinish();
 
-    // converts nsISupportsArray of atoms to a simple JS-strings array
     function runTest() {
       // We need to cache and restore this getter in order to simulate
       // Bug 510634
       let cachedLeftPaneFolderIdGetter =
         PlacesUIUtils.__lookupGetter__("leftPaneFolderId");
       // Must also cache and restore this getter as it is affected by
       // leftPaneFolderId, from bug 564900.
       let cachedAllBookmarksFolderIdGetter =
--- a/browser/components/preferences/applicationManager.js
+++ b/browser/components/preferences/applicationManager.js
@@ -1,12 +1,14 @@
 // 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/.
 
+/* import-globals-from in-content/applications.js */
+
 var Cc = Components.classes;
 var Ci = Components.interfaces;
 
 var gAppManagerDialog = {
   _removed: [],
 
   init: function appManager_init() {
     this.handlerInfo = window.arguments[0];
--- a/browser/components/preferences/fonts.js
+++ b/browser/components/preferences/fonts.js
@@ -1,13 +1,15 @@
 /* -*- indent-tabs-mode: nil; js-indent-level: 4 -*- */
 /* 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/. */
 
+/* import-globals-from ../../../toolkit/mozapps/preferences/fontbuilder.js */
+
 // browser.display.languageList LOCK ALL when LOCKED
 
 const kDefaultFontType          = "font.default.%LANG%";
 const kFontNameFmtSerif         = "font.name.serif.%LANG%";
 const kFontNameFmtSansSerif     = "font.name.sans-serif.%LANG%";
 const kFontNameFmtMonospace     = "font.name.monospace.%LANG%";
 const kFontNameListFmtSerif     = "font.name-list.serif.%LANG%";
 const kFontNameListFmtSansSerif = "font.name-list.sans-serif.%LANG%";
@@ -96,9 +98,8 @@ var gFontsDialog = {
     let {Services} = Components.utils.import("resource://gre/modules/Services.jsm", {});
     let flags = Services.prompt.BUTTON_POS_1 * Services.prompt.BUTTON_TITLE_CANCEL |
                 Services.prompt.BUTTON_POS_0 * Services.prompt.BUTTON_TITLE_IS_STRING |
                 Services.prompt.BUTTON_POS_1_DEFAULT;
     let buttonChosen = Services.prompt.confirmEx(window, title, warningMessage, flags, confirmLabel, null, "", "", {});
     return buttonChosen == 0;
   },
 };
-
--- a/browser/components/preferences/handlers.xml
+++ b/browser/components/preferences/handlers.xml
@@ -1,13 +1,14 @@
 <?xml version="1.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/. -->
+<!-- import-globals-from in-content/applications.js -->
 
 <!DOCTYPE overlay [
   <!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd">
   <!ENTITY % applicationsDTD SYSTEM "chrome://browser/locale/preferences/applications.dtd">
   %brandDTD;
   %applicationsDTD;
 ]>
 
--- a/browser/components/preferences/in-content/preferences.js
+++ b/browser/components/preferences/in-content/preferences.js
@@ -1,12 +1,24 @@
 /* - 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/. */
 
+// Import globals from the files imported by the .xul files.
+/* import-globals-from subdialogs.js */
+/* import-globals-from advanced.js */
+/* import-globals-from main.js */
+/* import-globals-from search.js */
+/* import-globals-from content.js */
+/* import-globals-from privacy.js */
+/* import-globals-from applications.js */
+/* import-globals-from security.js */
+/* import-globals-from sync.js */
+/* import-globals-from ../../../base/content/utilityOverlay.js */
+
 "use strict";
 
 var Cc = Components.classes;
 var Ci = Components.interfaces;
 var Cu = Components.utils;
 var Cr = Components.results;
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
--- a/browser/components/preferences/in-content/privacy.js
+++ b/browser/components/preferences/in-content/privacy.js
@@ -646,32 +646,32 @@ var gPrivacyPane = {
     gSubDialog.open("chrome://browser/content/preferences/sanitize.xul", "resizable=no");
   },
 
 
   /**
    * Displays a dialog from which individual parts of private data may be
    * cleared.
    */
-  clearPrivateDataNow: function (aClearEverything)
-  {
+  clearPrivateDataNow: function (aClearEverything) {
     var ts = document.getElementById("privacy.sanitize.timeSpan");
     var timeSpanOrig = ts.value;
-    if (aClearEverything)
+
+    if (aClearEverything) {
       ts.value = 0;
+    }
 
-    const Cc = Components.classes, Ci = Components.interfaces;
-    var glue = Cc["@mozilla.org/browser/browserglue;1"]
-                 .getService(Ci.nsIBrowserGlue);
-    glue.sanitize(window);
+    gSubDialog.open("chrome://browser/content/sanitize.xul", "resizable=no", null, () => {
+      // reset the timeSpan pref
+      if (aClearEverything) {
+        ts.value = timeSpanOrig;
+      }
 
-    // reset the timeSpan pref
-    if (aClearEverything)
-      ts.value = timeSpanOrig;
-    Services.obs.notifyObservers(null, "clear-private-data", null);
+      Services.obs.notifyObservers(null, "clear-private-data", null);
+    });
   },
 
   /**
    * Enables or disables the "Settings..." button depending
    * on the privacy.sanitize.sanitizeOnShutdown preference value
    */
   _updateSanitizeSettingsButton: function () {
     var settingsButton = document.getElementById("clearDataSettings");
--- a/browser/components/preferences/in-content/subdialogs.js
+++ b/browser/components/preferences/in-content/subdialogs.js
@@ -168,16 +168,28 @@ var gSubDialog = {
       this.injectXMLStylesheet(styleSheetURL);
     }
 
     // Provide the ability for the dialog to know that it is being loaded "in-content".
     this._frame.contentDocument.documentElement.setAttribute("subdialog", "true");
 
     this._frame.contentWindow.addEventListener("dialogclosing", this);
 
+    let oldResizeBy = this._frame.contentWindow.resizeBy;
+    this._frame.contentWindow.resizeBy = function(resizeByWidth, resizeByHeight) {
+      // Only handle resizeByHeight currently.
+      let frameHeight = gSubDialog._frame.clientHeight;
+      let boxMinHeight = parseFloat(getComputedStyle(gSubDialog._box).minHeight, 10);
+
+      gSubDialog._frame.style.height = (frameHeight + resizeByHeight) + "px";
+      gSubDialog._box.style.minHeight = (boxMinHeight + resizeByHeight) + "px";
+
+      oldResizeBy.call(gSubDialog._frame.contentWindow, resizeByWidth, resizeByHeight);
+    };
+
     // Make window.close calls work like dialog closing.
     let oldClose = this._frame.contentWindow.close;
     this._frame.contentWindow.close = function() {
       var closingEvent = gSubDialog._closingEvent;
       if (!closingEvent) {
         closingEvent = new CustomEvent("dialogclosing", {
           bubbles: true,
           detail: { button: null },
@@ -268,18 +280,20 @@ var gSubDialog = {
     this._frame.style.height = frameHeight;
     this._box.style.minHeight = "calc(" +
                                 (boxVerticalBorder + groupBoxTitleHeight + boxVerticalPadding) +
                                 "px + " + frameMinHeight + ")";
 
     this._overlay.style.visibility = "visible";
     this._overlay.style.opacity = ""; // XXX: focus hack continued from _onContentLoaded
 
-    this._resizeObserver = new MutationObserver(this._onResize);
-    this._resizeObserver.observe(this._box, {attributes: true});
+    if (this._box.getAttribute("resizable") == "true") {
+      this._resizeObserver = new MutationObserver(this._onResize);
+      this._resizeObserver.observe(this._box, {attributes: true});
+    }
 
     this._trapFocus();
   },
 
   _onResize: function(mutations) {
     let frame = gSubDialog._frame;
     // The width and height styles are needed for the initial
     // layout of the frame, but afterward they need to be removed
--- a/browser/components/preferences/in-content/tests/browser.ini
+++ b/browser/components/preferences/in-content/tests/browser.ini
@@ -21,17 +21,16 @@ skip-if = os != "win" # This test tests 
 [browser_connection_bug388287.js]
 [browser_cookies_exceptions.js]
 [browser_defaultbrowser_alwayscheck.js]
 [browser_healthreport.js]
 skip-if = true || !healthreport # Bug 1185403 for the "true"
 [browser_homepages_filter_aboutpreferences.js]
 [browser_notifications_do_not_disturb.js]
 [browser_permissions_urlFieldHidden.js]
-skip-if = true # Bug 1278388
 [browser_proxy_backup.js]
 [browser_privacypane_1.js]
 [browser_privacypane_3.js]
 [browser_privacypane_4.js]
 [browser_privacypane_5.js]
 [browser_privacypane_8.js]
 [browser_sanitizeOnShutdown_prefLocked.js]
 [browser_searchsuggestions.js]
--- a/browser/components/sessionstore/RecentlyClosedTabsAndWindowsMenuUtils.jsm
+++ b/browser/components/sessionstore/RecentlyClosedTabsAndWindowsMenuUtils.jsm
@@ -39,53 +39,22 @@ this.RecentlyClosedTabsAndWindowsMenuUti
   */
   getTabsFragment: function(aWindow, aTagName, aPrefixRestoreAll=false,
                             aRestoreAllLabel="menuRestoreAllTabs.label") {
     let doc = aWindow.document;
     let fragment = doc.createDocumentFragment();
     if (SessionStore.getClosedTabCount(aWindow) != 0) {
       let closedTabs = SessionStore.getClosedTabData(aWindow, false);
       for (let i = 0; i < closedTabs.length; i++) {
-        let element = doc.createElementNS(kNSXUL, aTagName);
-        element.setAttribute("label", closedTabs[i].title);
-        if (closedTabs[i].image) {
-          setImage(aWindow, closedTabs[i], element);
-        }
-        element.setAttribute("value", i);
-        if (aTagName == "menuitem") {
-          element.setAttribute("class", "menuitem-iconic bookmark-item menuitem-with-favicon");
-        }
-        element.setAttribute("oncommand", "undoCloseTab(" + i + ");");
-
-        // Set the targetURI attribute so it will be shown in tooltip and trigger
-        // onLinkHovered. SessionStore uses one-based indexes, so we need to
-        // normalize them.
-        let tabData = closedTabs[i].state;
-        let activeIndex = (tabData.index || tabData.entries.length) - 1;
-        if (activeIndex >= 0 && tabData.entries[activeIndex]) {
-          element.setAttribute("targetURI", tabData.entries[activeIndex].url);
-        }
-
-        element.addEventListener("click", this._undoCloseMiddleClick, false);
-        if (i == 0)
-          element.setAttribute("key", "key_undoCloseTab");
-        fragment.appendChild(element);
+        createEntry(aTagName, false, i, closedTabs[i], doc,
+                    closedTabs[i].title, fragment);
       }
 
-      let restoreAllTabs = doc.createElementNS(kNSXUL, aTagName);
-      restoreAllTabs.classList.add("restoreallitem");
-      restoreAllTabs.setAttribute("label", navigatorBundle.GetStringFromName(aRestoreAllLabel));
-      restoreAllTabs.setAttribute("oncommand",
-              "for (var i = 0; i < " + closedTabs.length + "; i++) undoCloseTab();");
-      if (!aPrefixRestoreAll) {
-        fragment.appendChild(doc.createElementNS(kNSXUL, "menuseparator"));
-        fragment.appendChild(restoreAllTabs);
-      } else {
-        fragment.insertBefore(restoreAllTabs, fragment.firstChild);
-      }
+    createRestoreAllEntry(doc, fragment, aPrefixRestoreAll, false,
+                          aRestoreAllLabel, closedTabs.length, aTagName)
     }
     return fragment;
   },
 
   /**
   * Builds up a document fragment of UI items for the recently closed windows.
   * @param   aWindow
   *          A window that can be used to create the elements and document fragment.
@@ -96,64 +65,39 @@ this.RecentlyClosedTabsAndWindowsMenuUti
   *          If suffixed (the default) a separator will be inserted before it.
   * @param   aRestoreAllLabel (defaults to "menuRestoreAllWindows.label")
   *          Which localizable string to use for the 'restore all windows' item.
   * @returns A document fragment with UI items for each recently closed window.
   */
   getWindowsFragment: function(aWindow, aTagName, aPrefixRestoreAll=false,
                             aRestoreAllLabel="menuRestoreAllWindows.label") {
     let closedWindowData = SessionStore.getClosedWindowData(false);
-    let fragment = aWindow.document.createDocumentFragment();
+    let doc = aWindow.document;
+    let fragment = doc.createDocumentFragment();
     if (closedWindowData.length != 0) {
       let menuLabelString = navigatorBundle.GetStringFromName("menuUndoCloseWindowLabel");
       let menuLabelStringSingleTab =
         navigatorBundle.GetStringFromName("menuUndoCloseWindowSingleTabLabel");
 
-      let doc = aWindow.document;
       for (let i = 0; i < closedWindowData.length; i++) {
         let undoItem = closedWindowData[i];
         let otherTabsCount = undoItem.tabs.length - 1;
         let label = (otherTabsCount == 0) ? menuLabelStringSingleTab
                                           : PluralForm.get(otherTabsCount, menuLabelString);
         let menuLabel = label.replace("#1", undoItem.title)
                              .replace("#2", otherTabsCount);
-        let item = doc.createElementNS(kNSXUL, aTagName);
-        item.setAttribute("label", menuLabel);
         let selectedTab = undoItem.tabs[undoItem.selected - 1];
-        if (selectedTab.image) {
-          setImage(aWindow, selectedTab, item);
-        }
-        if (aTagName == "menuitem") {
-          item.setAttribute("class", "menuitem-iconic bookmark-item menuitem-with-favicon");
-        }
-        item.setAttribute("oncommand", "undoCloseWindow(" + i + ");");
 
-        // Set the targetURI attribute so it will be shown in tooltip.
-        // SessionStore uses one-based indexes, so we need to normalize them.
-        let activeIndex = (selectedTab.index || selectedTab.entries.length) - 1;
-        if (activeIndex >= 0 && selectedTab.entries[activeIndex])
-          item.setAttribute("targetURI", selectedTab.entries[activeIndex].url);
-
-        if (i == 0)
-          item.setAttribute("key", "key_undoCloseWindow");
-        fragment.appendChild(item);
+        createEntry(aTagName, true, i, selectedTab, doc, menuLabel,
+                    fragment);
       }
 
-      // "Open All in Windows"
-      let restoreAllWindows = doc.createElementNS(kNSXUL, aTagName);
-      restoreAllWindows.classList.add("restoreallitem");
-      restoreAllWindows.setAttribute("label", navigatorBundle.GetStringFromName(aRestoreAllLabel));
-      restoreAllWindows.setAttribute("oncommand",
-        "for (var i = 0; i < " + closedWindowData.length + "; i++) undoCloseWindow();");
-      if (!aPrefixRestoreAll) {
-        fragment.appendChild(doc.createElementNS(kNSXUL, "menuseparator"));
-        fragment.appendChild(restoreAllWindows);
-      } else {
-        fragment.insertBefore(restoreAllWindows, fragment.firstChild);
-      }
+      createRestoreAllEntry(doc, fragment, aPrefixRestoreAll, true,
+                            aRestoreAllLabel, closedWindowData.length,
+                            aTagName);
     }
     return fragment;
   },
 
 
   /**
     * Re-open a closed tab and put it to the end of the tab strip.
     * Used for a middle click.
@@ -164,16 +108,107 @@ this.RecentlyClosedTabsAndWindowsMenuUti
     if (aEvent.button != 1)
       return;
 
     aEvent.view.undoCloseTab(aEvent.originalTarget.getAttribute("value"));
     aEvent.view.gBrowser.moveTabToEnd();
   },
 };
 
-function setImage(aWindow, aItem, aElement) {
+function setImage(aItem, aElement) {
   let iconURL = aItem.image;
   // don't initiate a connection just to fetch a favicon (see bug 467828)
   if (/^https?:/.test(iconURL))
     iconURL = "moz-anno:favicon:" + iconURL;
 
   aElement.setAttribute("image", iconURL);
 }
+
+/**
+ * Create a UI entry for a recently closed tab or window.
+ * @param aTagName
+ *        the tag name that will be used when creating the UI entry
+ * @param aIsWindowsFragment
+ *        whether or not this entry will represent a closed window
+ * @param aIndex
+ *        the index of the closed tab
+ * @param aClosedTab
+ *        the closed tab
+ * @param aDocument
+ *        a document that can be used to create the entry
+ * @param aMenuLabel
+ *        the label the created entry will have
+ * @param aFragment
+ *        the fragment the created entry will be in
+ */
+function createEntry(aTagName, aIsWindowsFragment, aIndex, aClosedTab,
+                     aDocument, aMenuLabel, aFragment) {
+  let element = aDocument.createElementNS(kNSXUL, aTagName);
+
+  element.setAttribute("label", aMenuLabel);
+  if (aClosedTab.image) {
+    setImage(aClosedTab, element);
+  }
+  if (!aIsWindowsFragment) {
+    element.setAttribute("value", aIndex);
+  }
+
+  if (aTagName == "menuitem") {
+    element.setAttribute("class", "menuitem-iconic bookmark-item menuitem-with-favicon");
+  }
+
+  element.setAttribute("oncommand", "undoClose" + (aIsWindowsFragment ? "Window" : "Tab") +
+                       "(" + aIndex + ");");
+
+  // Set the targetURI attribute so it will be shown in tooltip.
+  // SessionStore uses one-based indexes, so we need to normalize them.
+  let tabData;
+  tabData = aIsWindowsFragment ? aClosedTab
+                     : aClosedTab.state;
+  let activeIndex = (tabData.index || tabData.entries.length) - 1;
+  if (activeIndex >= 0 && tabData.entries[activeIndex]) {
+    element.setAttribute("targetURI", tabData.entries[activeIndex].url);
+  }
+
+  if (!aIsWindowsFragment) {
+    element.addEventListener("click", this._undoCloseMiddleClick, false);
+  }
+  if (aIndex == 0) {
+    element.setAttribute("key", "key_undoClose" + (aIsWindowsFragment? "Window" : "Tab"));
+  }
+
+  aFragment.appendChild(element);
+}
+
+/**
+ * Create an entry to restore all closed windows or tabs.
+ * @param aDocument
+ *        a document that can be used to create the entry
+ * @param aFragment
+ *        the fragment the created entry will be in
+ * @param aPrefixRestoreAll
+ *        whether the 'restore all windows' item is suffixed or prefixed to the list
+ *        If suffixed a separator will be inserted before it.
+ * @param aIsWindowsFragment
+ *        whether or not this entry will represent a closed window
+ * @param aRestoreAllLabel
+ *        which localizable string to use for the entry
+ * @param aEntryCount
+ *        the number of elements to be restored by this entry
+ * @param aTagName
+ *        the tag name that will be used when creating the UI entry
+ */
+function createRestoreAllEntry(aDocument, aFragment, aPrefixRestoreAll,
+                                aIsWindowsFragment, aRestoreAllLabel,
+                                aEntryCount, aTagName) {
+  let restoreAllElements = aDocument.createElementNS(kNSXUL, aTagName);
+  restoreAllElements.classList.add("restoreallitem");
+  restoreAllElements.setAttribute("label", navigatorBundle.GetStringFromName(aRestoreAllLabel));
+  restoreAllElements.setAttribute("oncommand",
+                                  "for (var i = 0; i < " + aEntryCount + "; i++) undoClose" +
+                                    (aIsWindowsFragment? "Window" : "Tab") + "();");
+  if (aPrefixRestoreAll) {
+    aFragment.insertBefore(restoreAllElements, aFragment.firstChild);
+  } else {
+    aFragment.appendChild(aDocument.createElementNS(kNSXUL, "menuseparator"));
+    aFragment.appendChild(restoreAllElements);
+  }
+}
--- a/browser/components/sessionstore/SessionStore.jsm
+++ b/browser/components/sessionstore/SessionStore.jsm
@@ -126,25 +126,26 @@ const CLOSED_MESSAGES = new Set([
 // These are tab events that we listen to.
 const TAB_EVENTS = [
   "TabOpen", "TabBrowserCreated", "TabClose", "TabSelect", "TabShow", "TabHide", "TabPinned",
   "TabUnpinned"
 ];
 
 const NS_XUL = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
 
-Cu.import("resource://gre/modules/Services.jsm", this);
-Cu.import("resource://gre/modules/XPCOMUtils.jsm", this);
-Cu.import("resource://gre/modules/TelemetryTimestamps.jsm", this);
-Cu.import("resource://gre/modules/TelemetryStopwatch.jsm", this);
-Cu.import("resource://gre/modules/osfile.jsm", this);
 Cu.import("resource://gre/modules/PrivateBrowsingUtils.jsm", this);
 Cu.import("resource://gre/modules/Promise.jsm", this);
+Cu.import("resource://gre/modules/Services.jsm", this);
 Cu.import("resource://gre/modules/Task.jsm", this);
+Cu.import("resource://gre/modules/TelemetryStopwatch.jsm", this);
+Cu.import("resource://gre/modules/TelemetryTimestamps.jsm", this);
+Cu.import("resource://gre/modules/Timer.jsm", this);
+Cu.import("resource://gre/modules/XPCOMUtils.jsm", this);
 Cu.import("resource://gre/modules/debug.js", this);
+Cu.import("resource://gre/modules/osfile.jsm", this);
 
 XPCOMUtils.defineLazyServiceGetter(this, "gSessionStartup",
   "@mozilla.org/browser/sessionstartup;1", "nsISessionStartup");
 XPCOMUtils.defineLazyServiceGetter(this, "gScreenManager",
   "@mozilla.org/gfx/screenmanager;1", "nsIScreenManager");
 XPCOMUtils.defineLazyServiceGetter(this, "Telemetry",
   "@mozilla.org/base/telemetry;1", "nsITelemetry");
 XPCOMUtils.defineLazyModuleGetter(this, "console",
@@ -1457,19 +1458,47 @@ var SessionStoreInternal = {
     let progress = { total: -1, current: -1 };
 
     // We're going down! Switch state so that we treat closing windows and
     // tabs correctly.
     RunState.setQuitting();
 
     if (!syncShutdown) {
       // We've got some time to shut down, so let's do this properly.
+      // To prevent blocker from breaking the 60 sec limit(which will cause a
+      // crash) of async shutdown during flushing all windows, we resolve the
+      // promise passed to blocker once:
+      // 1. the flushing exceed 50 sec, or
+      // 2. 'oop-frameloader-crashed' or 'ipc:content-shutdown' is observed.
+      // Thus, Firefox still can open the last session on next startup.
       AsyncShutdown.quitApplicationGranted.addBlocker(
         "SessionStore: flushing all windows",
-        this.flushAllWindowsAsync(progress),
+        () => {
+          var promises = [];
+          promises.push(this.flushAllWindowsAsync(progress));
+          promises.push(this.looseTimer(50000));
+
+          var promiseOFC = new Promise(resolve => {
+            Services.obs.addObserver(function obs(subject, topic) {
+              Services.obs.removeObserver(obs, topic);
+              resolve();
+            }, "oop-frameloader-crashed", false);
+          });
+          promises.push(promiseOFC);
+
+          var promiseICS = new Promise(resolve => {
+            Services.obs.addObserver(function obs(subject, topic) {
+              Services.obs.removeObserver(obs, topic);
+              resolve();
+            }, "ipc:content-shutdown", false);
+          });
+          promises.push(promiseICS);
+
+          return Promise.race(promises);
+        },
         () => progress);
     } else {
       // We have to shut down NOW, which means we only get to save whatever
       // we already had cached.
     }
   },
 
   /**
@@ -4325,16 +4354,43 @@ var SessionStoreInternal = {
   reportInternalError(data) {
     // For the moment, we only report errors through Telemetry.
     if (data.telemetry) {
       for (let key of Object.keys(data.telemetry)) {
         let histogram = Telemetry.getHistogramById(key);
         histogram.add(data.telemetry[key]);
       }
     }
+  },
+
+  /**
+   * Countdown for a given duration, skipping beats if the computer is too busy,
+   * sleeping or otherwise unavailable.
+   *
+   * @param {number} delay An approximate delay to wait in milliseconds (rounded
+   * up to the closest second).
+   *
+   * @return Promise
+   */
+  looseTimer(delay) {
+    let DELAY_BEAT = 1000;
+    let timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
+    let beats = Math.ceil(delay / DELAY_BEAT);
+    let promise =  new Promise(resolve => {
+      timer.initWithCallback(function() {
+        if (beats <= 0) {
+          resolve();
+        }
+        --beats;
+      }, DELAY_BEAT, Ci.nsITimer.TYPE_REPEATING_PRECISE_CAN_SKIP);
+    });
+    // Ensure that the timer is both canceled once we are done with it
+    // and not garbage-collected until then.
+    promise.then(() => timer.cancel(), () => timer.cancel());
+    return promise;
   }
 };
 
 /**
  * Priority queue that keeps track of a list of tabs to restore and returns
  * the tab we should restore next, based on priority rules. We decide between
  * pinned, visible and hidden tabs in that and FIFO order. Hidden tabs are only
  * restored with restore_hidden_tabs=true.
copy from browser/components/contextualidentity/test/browser/.eslintrc.js
copy to browser/components/syncedtabs/test/browser/.eslintrc.js
new file mode 100644
--- /dev/null
+++ b/browser/components/syncedtabs/test/xpcshell/.eslintrc.js
@@ -0,0 +1,7 @@
+"use strict";
+
+module.exports = {
+  "extends": [
+    "../../../../../testing/xpcshell/xpcshell.eslintrc.js"
+  ]
+};
new file mode 100644
--- /dev/null
+++ b/browser/components/tests/unit/.eslintrc.js
@@ -0,0 +1,7 @@
+"use strict";
+
+module.exports = {
+  "extends": [
+    "../../../../testing/xpcshell/xpcshell.eslintrc.js"
+  ]
+};
--- a/browser/components/tests/unit/test_distribution.js
+++ b/browser/components/tests/unit/test_distribution.js
@@ -4,16 +4,17 @@
 /**
  * Tests that preferences are properly set by distribution.ini
  */
 
 Cu.import("resource://gre/modules/LoadContextInfo.jsm");
 
 // Import common head.
 var commonFile = do_get_file("../../../../toolkit/components/places/tests/head_common.js", false);
+/* import-globals-from ../../../../toolkit/components/places/tests/head_common.js */
 if (commonFile) {
   let uri = Services.io.newFileURI(commonFile);
   Services.scriptloader.loadSubScript(uri.spec, this);
 }
 
 const TOPICDATA_DISTRIBUTION_CUSTOMIZATION = "force-distribution-customization";
 const TOPIC_BROWSERGLUE_TEST = "browser-glue-test";
 
--- a/browser/config/mozconfigs/macosx64/artifact
+++ b/browser/config/mozconfigs/macosx64/artifact
@@ -1,8 +1,10 @@
 MOZ_AUTOMATION_BUILD_SYMBOLS=0
 MOZ_AUTOMATION_L10N_CHECK=0
 
 . "$topsrcdir/build/macosx/mozconfig.common"
 . "$topsrcdir/build/mozconfig.common.override"
 
 ac_add_options --enable-artifact-builds
 ac_add_options --enable-artifact-build-symbols
+unset CC
+unset CXX
--- a/browser/extensions/e10srollout/bootstrap.js
+++ b/browser/extensions/e10srollout/bootstrap.js
@@ -237,17 +237,17 @@ let performLongSpinnerCheck = Task.async
         // The Histogram might not be defined in this ping if no data was recorded for it.
         // In this case, we still add the session length because that was a valid session
         // without a long spinner.
         continue;
       }
 
       let histogram = ping.payload.histograms[LONG_SPINNER_HISTOGRAM];
 
-      for (spinnerTime of Object.keys(histogram.values)) {
+      for (let spinnerTime of Object.keys(histogram.values)) {
         // Only consider spinners that took more than 2 seconds.
         // Note: the first bucket size that fits this criteria is
         // 2297ms. And the largest bucket is 64000ms, meaning that
         // any pause larger than that is only counted as a 64s pause.
         // For reference, the bucket sizes are:
         // 0, 1000, 2297, 5277, 12124, 27856, 64000
         if (spinnerTime >= 2000) {
           totalSpinnerTime += spinnerTime * histogram.values[spinnerTime];
--- a/browser/extensions/formautofill/test/unit/.eslintrc
+++ b/browser/extensions/formautofill/test/unit/.eslintrc
@@ -1,5 +1,5 @@
 {
   "extends": [
-    "../../../../../testing/xpcshell/xpcshell.eslintrc"
+    "../../../../../testing/xpcshell/xpcshell.eslintrc.js"
   ],
 }
--- a/browser/extensions/pdfjs/README.mozilla
+++ b/browser/extensions/pdfjs/README.mozilla
@@ -1,3 +1,3 @@
 This is the pdf.js project output, https://github.com/mozilla/pdf.js
 
-Current extension version is: 1.6.234
+Current extension version is: 1.6.263
new file mode 100644
--- /dev/null
+++ b/browser/extensions/pdfjs/content/PdfJsNetwork.jsm
@@ -0,0 +1,257 @@
+/* Copyright 2012 Mozilla Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * 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 Components, Services */
+
+'use strict';
+
+Components.utils.import('resource://gre/modules/Services.jsm');
+
+var EXPORTED_SYMBOLS = ['NetworkManager'];
+
+function log(aMsg) {
+  var msg = 'network.js: ' + (aMsg.join ? aMsg.join('') : aMsg);
+  Services.console.logStringMessage(msg);
+}
+
+var NetworkManager = (function NetworkManagerClosure() {
+
+  var OK_RESPONSE = 200;
+  var PARTIAL_CONTENT_RESPONSE = 206;
+
+  function NetworkManager(url, args) {
+    this.url = url;
+    args = args || {};
+    this.isHttp = /^https?:/i.test(url);
+    this.httpHeaders = (this.isHttp && args.httpHeaders) || {};
+    this.withCredentials = args.withCredentials || false;
+    this.getXhr = args.getXhr ||
+      function NetworkManager_getXhr() {
+        return new XMLHttpRequest();
+      };
+
+    this.currXhrId = 0;
+    this.pendingRequests = Object.create(null);
+    this.loadedRequests = Object.create(null);
+  }
+
+  function getArrayBuffer(xhr) {
+    var data = xhr.response;
+    if (typeof data !== 'string') {
+      return data;
+    }
+    var length = data.length;
+    var array = new Uint8Array(length);
+    for (var i = 0; i < length; i++) {
+      array[i] = data.charCodeAt(i) & 0xFF;
+    }
+    return array.buffer;
+  }
+
+  NetworkManager.prototype = {
+    requestRange: function NetworkManager_requestRange(begin, end, listeners) {
+      var args = {
+        begin: begin,
+        end: end
+      };
+      for (var prop in listeners) {
+        args[prop] = listeners[prop];
+      }
+      return this.request(args);
+    },
+
+    requestFull: function NetworkManager_requestFull(listeners) {
+      return this.request(listeners);
+    },
+
+    request: function NetworkManager_request(args) {
+      var xhr = this.getXhr();
+      var xhrId = this.currXhrId++;
+      var pendingRequest = this.pendingRequests[xhrId] = {
+        xhr: xhr
+      };
+
+      xhr.open('GET', this.url);
+      xhr.withCredentials = this.withCredentials;
+      for (var property in this.httpHeaders) {
+        var value = this.httpHeaders[property];
+        if (typeof value === 'undefined') {
+          continue;
+        }
+        xhr.setRequestHeader(property, value);
+      }
+      if (this.isHttp && 'begin' in args && 'end' in args) {
+        var rangeStr = args.begin + '-' + (args.end - 1);
+        xhr.setRequestHeader('Range', 'bytes=' + rangeStr);
+        pendingRequest.expectedStatus = 206;
+      } else {
+        pendingRequest.expectedStatus = 200;
+      }
+
+      var useMozChunkedLoading = !!args.onProgressiveData;
+      if (useMozChunkedLoading) {
+        xhr.responseType = 'moz-chunked-arraybuffer';
+        pendingRequest.onProgressiveData = args.onProgressiveData;
+        pendingRequest.mozChunked = true;
+      } else {
+        xhr.responseType = 'arraybuffer';
+      }
+
+      if (args.onError) {
+        xhr.onerror = function(evt) {
+          args.onError(xhr.status);
+        };
+      }
+      xhr.onreadystatechange = this.onStateChange.bind(this, xhrId);
+      xhr.onprogress = this.onProgress.bind(this, xhrId);
+
+      pendingRequest.onHeadersReceived = args.onHeadersReceived;
+      pendingRequest.onDone = args.onDone;
+      pendingRequest.onError = args.onError;
+      pendingRequest.onProgress = args.onProgress;
+
+      xhr.send(null);
+
+      return xhrId;
+    },
+
+    onProgress: function NetworkManager_onProgress(xhrId, evt) {
+      var pendingRequest = this.pendingRequests[xhrId];
+      if (!pendingRequest) {
+        // Maybe abortRequest was called...
+        return;
+      }
+
+      if (pendingRequest.mozChunked) {
+        var chunk = getArrayBuffer(pendingRequest.xhr);
+        pendingRequest.onProgressiveData(chunk);
+      }
+
+      var onProgress = pendingRequest.onProgress;
+      if (onProgress) {
+        onProgress(evt);
+      }
+    },
+
+    onStateChange: function NetworkManager_onStateChange(xhrId, evt) {
+      var pendingRequest = this.pendingRequests[xhrId];
+      if (!pendingRequest) {
+        // Maybe abortRequest was called...
+        return;
+      }
+
+      var xhr = pendingRequest.xhr;
+      if (xhr.readyState >= 2 && pendingRequest.onHeadersReceived) {
+        pendingRequest.onHeadersReceived();
+        delete pendingRequest.onHeadersReceived;
+      }
+
+      if (xhr.readyState !== 4) {
+        return;
+      }
+
+      if (!(xhrId in this.pendingRequests)) {
+        // The XHR request might have been aborted in onHeadersReceived()
+        // callback, in which case we should abort request
+        return;
+      }
+
+      delete this.pendingRequests[xhrId];
+
+      // success status == 0 can be on ftp, file and other protocols
+      if (xhr.status === 0 && this.isHttp) {
+        if (pendingRequest.onError) {
+          pendingRequest.onError(xhr.status);
+        }
+        return;
+      }
+      var xhrStatus = xhr.status || OK_RESPONSE;
+
+      // From http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.35.2:
+      // "A server MAY ignore the Range header". This means it's possible to
+      // get a 200 rather than a 206 response from a range request.
+      var ok_response_on_range_request =
+          xhrStatus === OK_RESPONSE &&
+          pendingRequest.expectedStatus === PARTIAL_CONTENT_RESPONSE;
+
+      if (!ok_response_on_range_request &&
+          xhrStatus !== pendingRequest.expectedStatus) {
+        if (pendingRequest.onError) {
+          pendingRequest.onError(xhr.status);
+        }
+        return;
+      }
+
+      this.loadedRequests[xhrId] = true;
+
+      var chunk = getArrayBuffer(xhr);
+      if (xhrStatus === PARTIAL_CONTENT_RESPONSE) {
+        var rangeHeader = xhr.getResponseHeader('Content-Range');
+        var matches = /bytes (\d+)-(\d+)\/(\d+)/.exec(rangeHeader);
+        var begin = parseInt(matches[1], 10);
+        pendingRequest.onDone({
+          begin: begin,
+          chunk: chunk
+        });
+      } else if (pendingRequest.onProgressiveData) {
+        pendingRequest.onDone(null);
+      } else if (chunk) {
+        pendingRequest.onDone({
+          begin: 0,
+          chunk: chunk
+        });
+      } else if (pendingRequest.onError) {
+        pendingRequest.onError(xhr.status);
+      }
+    },
+
+    hasPendingRequests: function NetworkManager_hasPendingRequests() {
+      for (var xhrId in this.pendingRequests) {
+        return true;
+      }
+      return false;
+    },
+
+    getRequestXhr: function NetworkManager_getXhr(xhrId) {
+      return this.pendingRequests[xhrId].xhr;
+    },
+
+    isStreamingRequest: function NetworkManager_isStreamingRequest(xhrId) {
+      return !!(this.pendingRequests[xhrId].onProgressiveData);
+    },
+
+    isPendingRequest: function NetworkManager_isPendingRequest(xhrId) {
+      return xhrId in this.pendingRequests;
+    },
+
+    isLoadedRequest: function NetworkManager_isLoadedRequest(xhrId) {
+      return xhrId in this.loadedRequests;
+    },
+
+    abortAllRequests: function NetworkManager_abortAllRequests() {
+      for (var xhrId in this.pendingRequests) {
+        this.abortRequest(xhrId | 0);
+      }
+    },
+
+    abortRequest: function NetworkManager_abortRequest(xhrId) {
+      var xhr = this.pendingRequests[xhrId].xhr;
+      delete this.pendingRequests[xhrId];
+      xhr.abort();
+    }
+  };
+
+  return NetworkManager;
+})();
+
--- a/browser/extensions/pdfjs/content/PdfStreamConverter.jsm
+++ b/browser/extensions/pdfjs/content/PdfStreamConverter.jsm
@@ -33,17 +33,17 @@ const PDF_VIEWER_WEB_PAGE = 'resource://
 const MAX_NUMBER_OF_PREFS = 50;
 const MAX_STRING_PREF_LENGTH = 128;
 
 Cu.import('resource://gre/modules/XPCOMUtils.jsm');
 Cu.import('resource://gre/modules/Services.jsm');
 Cu.import('resource://gre/modules/NetUtil.jsm');
 
 XPCOMUtils.defineLazyModuleGetter(this, 'NetworkManager',
-  'resource://pdf.js/network.js');
+  'resource://pdf.js/PdfJsNetwork.jsm');
 
 XPCOMUtils.defineLazyModuleGetter(this, 'PrivateBrowsingUtils',
   'resource://gre/modules/PrivateBrowsingUtils.jsm');
 
 XPCOMUtils.defineLazyModuleGetter(this, 'PdfJsTelemetry',
   'resource://pdf.js/PdfJsTelemetry.jsm');
 
 XPCOMUtils.defineLazyModuleGetter(this, 'PdfjsContentUtils',
--- a/browser/extensions/pdfjs/content/build/pdf.js
+++ b/browser/extensions/pdfjs/content/build/pdf.js
@@ -7,3667 +7,3531 @@
  *     http://www.apache.org/licenses/LICENSE-2.0
  *
  * Unless required by applicable law or agreed to in writing, software
  * 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.
  */
-/* jshint globalstrict: false */
-/* umdutils ignore */
-
 (function (root, factory) {
   'use strict';
   if (typeof define === 'function' && define.amd) {
-define('pdfjs-dist/build/pdf', ['exports'], factory);
+    define('pdfjs-dist/build/pdf', ['exports'], factory);
   } else if (typeof exports !== 'undefined') {
     factory(exports);
   } else {
-factory((root.pdfjsDistBuildPdf = {}));
+    factory(root['pdfjsDistBuildPdf'] = {});
   }
 }(this, function (exports) {
   // Use strict in our context only - users might not want it
   'use strict';
-
-var pdfjsVersion = '1.6.234';
-var pdfjsBuild = 'bc3bceb';
-
-  var pdfjsFilePath =
-    typeof document !== 'undefined' && document.currentScript ?
-      document.currentScript.src : null;
-
+  var pdfjsVersion = '1.6.263';
+  var pdfjsBuild = '7e392c0';
+  var pdfjsFilePath = typeof document !== 'undefined' && document.currentScript ? document.currentScript.src : null;
   var pdfjsLibs = {};
-
   (function pdfjsWrapper() {
-
-
-
-(function (root, factory) {
-  {
-    factory((root.pdfjsSharedUtil = {}));
-  }
-}(this, function (exports) {
-
-var globalScope = (typeof window !== 'undefined') ? window :
-                  (typeof global !== 'undefined') ? global :
-                  (typeof self !== 'undefined') ? self : this;
-
-var FONT_IDENTITY_MATRIX = [0.001, 0, 0, 0.001, 0, 0];
-
-var TextRenderingMode = {
-  FILL: 0,
-  STROKE: 1,
-  FILL_STROKE: 2,
-  INVISIBLE: 3,
-  FILL_ADD_TO_PATH: 4,
-  STROKE_ADD_TO_PATH: 5,
-  FILL_STROKE_ADD_TO_PATH: 6,
-  ADD_TO_PATH: 7,
-  FILL_STROKE_MASK: 3,
-  ADD_TO_PATH_FLAG: 4
-};
-
-var ImageKind = {
-  GRAYSCALE_1BPP: 1,
-  RGB_24BPP: 2,
-  RGBA_32BPP: 3
-};
-
-var AnnotationType = {
-  TEXT: 1,
-  LINK: 2,
-  FREETEXT: 3,
-  LINE: 4,
-  SQUARE: 5,
-  CIRCLE: 6,
-  POLYGON: 7,
-  POLYLINE: 8,
-  HIGHLIGHT: 9,
-  UNDERLINE: 10,
-  SQUIGGLY: 11,
-  STRIKEOUT: 12,
-  STAMP: 13,
-  CARET: 14,
-  INK: 15,
-  POPUP: 16,
-  FILEATTACHMENT: 17,
-  SOUND: 18,
-  MOVIE: 19,
-  WIDGET: 20,
-  SCREEN: 21,
-  PRINTERMARK: 22,
-  TRAPNET: 23,
-  WATERMARK: 24,
-  THREED: 25,
-  REDACT: 26
-};
-
-var AnnotationFlag = {
-  INVISIBLE: 0x01,
-  HIDDEN: 0x02,
-  PRINT: 0x04,
-  NOZOOM: 0x08,
-  NOROTATE: 0x10,
-  NOVIEW: 0x20,
-  READONLY: 0x40,
-  LOCKED: 0x80,
-  TOGGLENOVIEW: 0x100,
-  LOCKEDCONTENTS: 0x200
-};
-
-var AnnotationFieldFlag = {
-  READONLY: 0x0000001,
-  REQUIRED: 0x0000002,
-  NOEXPORT: 0x0000004,
-  MULTILINE: 0x0001000,
-  PASSWORD: 0x0002000,
-  NOTOGGLETOOFF: 0x0004000,
-  RADIO: 0x0008000,
-  PUSHBUTTON: 0x0010000,
-  COMBO: 0x0020000,
-  EDIT: 0x0040000,
-  SORT: 0x0080000,
-  FILESELECT: 0x0100000,
-  MULTISELECT: 0x0200000,
-  DONOTSPELLCHECK: 0x0400000,
-  DONOTSCROLL: 0x0800000,
-  COMB: 0x1000000,
-  RICHTEXT: 0x2000000,
-  RADIOSINUNISON: 0x2000000,
-  COMMITONSELCHANGE: 0x4000000,
-};
-
-var AnnotationBorderStyleType = {
-  SOLID: 1,
-  DASHED: 2,
-  BEVELED: 3,
-  INSET: 4,
-  UNDERLINE: 5
-};
-
-var StreamType = {
-  UNKNOWN: 0,
-  FLATE: 1,
-  LZW: 2,
-  DCT: 3,
-  JPX: 4,
-  JBIG: 5,
-  A85: 6,
-  AHX: 7,
-  CCF: 8,
-  RL: 9
-};
-
-var FontType = {
-  UNKNOWN: 0,
-  TYPE1: 1,
-  TYPE1C: 2,
-  CIDFONTTYPE0: 3,
-  CIDFONTTYPE0C: 4,
-  TRUETYPE: 5,
-  CIDFONTTYPE2: 6,
-  TYPE3: 7,
-  OPENTYPE: 8,
-  TYPE0: 9,
-  MMTYPE1: 10
-};
-
-var VERBOSITY_LEVELS = {
-  errors: 0,
-  warnings: 1,
-  infos: 5
-};
-
-// All the possible operations for an operator list.
-var OPS = {
-  // Intentionally start from 1 so it is easy to spot bad operators that will be
-  // 0's.
-  dependency: 1,
-  setLineWidth: 2,
-  setLineCap: 3,
-  setLineJoin: 4,
-  setMiterLimit: 5,
-  setDash: 6,
-  setRenderingIntent: 7,
-  setFlatness: 8,
-  setGState: 9,
-  save: 10,
-  restore: 11,
-  transform: 12,
-  moveTo: 13,
-  lineTo: 14,
-  curveTo: 15,
-  curveTo2: 16,
-  curveTo3: 17,
-  closePath: 18,
-  rectangle: 19,
-  stroke: 20,
-  closeStroke: 21,
-  fill: 22,
-  eoFill: 23,
-  fillStroke: 24,
-  eoFillStroke: 25,
-  closeFillStroke: 26,
-  closeEOFillStroke: 27,
-  endPath: 28,
-  clip: 29,
-  eoClip: 30,
-  beginText: 31,
-  endText: 32,
-  setCharSpacing: 33,
-  setWordSpacing: 34,
-  setHScale: 35,
-  setLeading: 36,
-  setFont: 37,
-  setTextRenderingMode: 38,
-  setTextRise: 39,
-  moveText: 40,
-  setLeadingMoveText: 41,
-  setTextMatrix: 42,
-  nextLine: 43,
-  showText: 44,
-  showSpacedText: 45,
-  nextLineShowText: 46,
-  nextLineSetSpacingShowText: 47,
-  setCharWidth: 48,
-  setCharWidthAndBounds: 49,
-  setStrokeColorSpace: 50,
-  setFillColorSpace: 51,
-  setStrokeColor: 52,
-  setStrokeColorN: 53,
-  setFillColor: 54,
-  setFillColorN: 55,
-  setStrokeGray: 56,
-  setFillGray: 57,
-  setStrokeRGBColor: 58,
-  setFillRGBColor: 59,
-  setStrokeCMYKColor: 60,
-  setFillCMYKColor: 61,
-  shadingFill: 62,
-  beginInlineImage: 63,
-  beginImageData: 64,
-  endInlineImage: 65,
-  paintXObject: 66,
-  markPoint: 67,
-  markPointProps: 68,
-  beginMarkedContent: 69,
-  beginMarkedContentProps: 70,
-  endMarkedContent: 71,
-  beginCompat: 72,
-  endCompat: 73,
-  paintFormXObjectBegin: 74,
-  paintFormXObjectEnd: 75,
-  beginGroup: 76,
-  endGroup: 77,
-  beginAnnotations: 78,
-  endAnnotations: 79,
-  beginAnnotation: 80,
-  endAnnotation: 81,
-  paintJpegXObject: 82,
-  paintImageMaskXObject: 83,
-  paintImageMaskXObjectGroup: 84,
-  paintImageXObject: 85,
-  paintInlineImageXObject: 86,
-  paintInlineImageXObjectGroup: 87,
-  paintImageXObjectRepeat: 88,
-  paintImageMaskXObjectRepeat: 89,
-  paintSolidColorImageMask: 90,
-  constructPath: 91
-};
-
-var verbosity = VERBOSITY_LEVELS.warnings;
-
-function setVerbosityLevel(level) {
-  verbosity = level;
-}
-
-function getVerbosityLevel() {
-  return verbosity;
-}
-
-// A notice for devs. These are good for things that are helpful to devs, such
-// as warning that Workers were disabled, which is important to devs but not
-// end users.
-function info(msg) {
-  if (verbosity >= VERBOSITY_LEVELS.infos) {
-    console.log('Info: ' + msg);
-  }
-}
-
-// Non-fatal warnings.
-function warn(msg) {
-  if (verbosity >= VERBOSITY_LEVELS.warnings) {
-    console.log('Warning: ' + msg);
-  }
-}
-
-// Deprecated API function -- display regardless of the PDFJS.verbosity setting.
-function deprecated(details) {
-  console.log('Deprecated API usage: ' + details);
-}
-
-// Fatal errors that should trigger the fallback UI and halt execution by
-// throwing an exception.
-function error(msg) {
-  if (verbosity >= VERBOSITY_LEVELS.errors) {
-    console.log('Error: ' + msg);
-    console.log(backtrace());
-  }
-  throw new Error(msg);
-}
-
-function backtrace() {
-  try {
-    throw new Error();
-  } catch (e) {
-    return e.stack ? e.stack.split('\n').slice(2).join('\n') : '';
-  }
-}
-
-function assert(cond, msg) {
-  if (!cond) {
-    error(msg);
-  }
-}
-
-var UNSUPPORTED_FEATURES = {
-  unknown: 'unknown',
-  forms: 'forms',
-  javaScript: 'javaScript',
-  smask: 'smask',
-  shadingPattern: 'shadingPattern',
-  font: 'font'
-};
-
-// Checks if URLs have the same origin. For non-HTTP based URLs, returns false.
-function isSameOrigin(baseUrl, otherUrl) {
-  try {
-    var base = new URL(baseUrl);
-    if (!base.origin || base.origin === 'null') {
-      return false; // non-HTTP url
-    }
-  } catch (e) {
-    return false;
-  }
-
-  var other = new URL(otherUrl, base);
-  return base.origin === other.origin;
-}
-
-// Validates if URL is safe and allowed, e.g. to avoid XSS.
-function isValidUrl(url, allowRelative) {
-  if (!url || typeof url !== 'string') {
-    return false;
-  }
-  // RFC 3986 (http://tools.ietf.org/html/rfc3986#section-3.1)
-  // scheme = ALPHA *( ALPHA / DIGIT / "+" / "-" / "." )
-  var protocol = /^[a-z][a-z0-9+\-.]*(?=:)/i.exec(url);
-  if (!protocol) {
-    return allowRelative;
-  }
-  protocol = protocol[0].toLowerCase();
-  switch (protocol) {
-    case 'http':
-    case 'https':
-    case 'ftp':
-    case 'mailto':
-    case 'tel':
-      return true;
-    default:
-      return false;
-  }
-}
-
-function shadow(obj, prop, value) {
-  Object.defineProperty(obj, prop, { value: value,
-                                     enumerable: true,
-                                     configurable: true,
-                                     writable: false });
-  return value;
-}
-
-function getLookupTableFactory(initializer) {
-  var lookup;
-  return function () {
-    if (initializer) {
-      lookup = Object.create(null);
-      initializer(lookup);
-      initializer = null;
-    }
-    return lookup;
-  };
-}
-
-var PasswordResponses = {
-  NEED_PASSWORD: 1,
-  INCORRECT_PASSWORD: 2
-};
-
-var PasswordException = (function PasswordExceptionClosure() {
-  function PasswordException(msg, code) {
-    this.name = 'PasswordException';
-    this.message = msg;
-    this.code = code;
-  }
-
-  PasswordException.prototype = new Error();
-  PasswordException.constructor = PasswordException;
-
-  return PasswordException;
-})();
-
-var UnknownErrorException = (function UnknownErrorExceptionClosure() {
-  function UnknownErrorException(msg, details) {
-    this.name = 'UnknownErrorException';
-    this.message = msg;
-    this.details = details;
-  }
-
-  UnknownErrorException.prototype = new Error();
-  UnknownErrorException.constructor = UnknownErrorException;
-
-  return UnknownErrorException;
-})();
-
-var InvalidPDFException = (function InvalidPDFExceptionClosure() {
-  function InvalidPDFException(msg) {
-    this.name = 'InvalidPDFException';
-    this.message = msg;
-  }
-
-  InvalidPDFException.prototype = new Error();
-  InvalidPDFException.constructor = InvalidPDFException;
-
-  return InvalidPDFException;
-})();
-
-var MissingPDFException = (function MissingPDFExceptionClosure() {
-  function MissingPDFException(msg) {
-    this.name = 'MissingPDFException';
-    this.message = msg;
-  }
-
-  MissingPDFException.prototype = new Error();
-  MissingPDFException.constructor = MissingPDFException;
-
-  return MissingPDFException;
-})();
-
-var UnexpectedResponseException =
-    (function UnexpectedResponseExceptionClosure() {
-  function UnexpectedResponseException(msg, status) {
-    this.name = 'UnexpectedResponseException';
-    this.message = msg;
-    this.status = status;
-  }
-
-  UnexpectedResponseException.prototype = new Error();
-  UnexpectedResponseException.constructor = UnexpectedResponseException;
-
-  return UnexpectedResponseException;
-})();
-
-var NotImplementedException = (function NotImplementedExceptionClosure() {
-  function NotImplementedException(msg) {
-    this.message = msg;
-  }
-
-  NotImplementedException.prototype = new Error();
-  NotImplementedException.prototype.name = 'NotImplementedException';
-  NotImplementedException.constructor = NotImplementedException;
-
-  return NotImplementedException;
-})();
-
-var MissingDataException = (function MissingDataExceptionClosure() {
-  function MissingDataException(begin, end) {
-    this.begin = begin;
-    this.end = end;
-    this.message = 'Missing data [' + begin + ', ' + end + ')';
-  }
-
-  MissingDataException.prototype = new Error();
-  MissingDataException.prototype.name = 'MissingDataException';
-  MissingDataException.constructor = MissingDataException;
-
-  return MissingDataException;
-})();
-
-var XRefParseException = (function XRefParseExceptionClosure() {
-  function XRefParseException(msg) {
-    this.message = msg;
-  }
-
-  XRefParseException.prototype = new Error();
-  XRefParseException.prototype.name = 'XRefParseException';
-  XRefParseException.constructor = XRefParseException;
-
-  return XRefParseException;
-})();
-
-var NullCharactersRegExp = /\x00/g;
-
-function removeNullCharacters(str) {
-  if (typeof str !== 'string') {
-    warn('The argument for removeNullCharacters must be a string.');
-    return str;
-  }
-  return str.replace(NullCharactersRegExp, '');
-}
-
-function bytesToString(bytes) {
-  assert(bytes !== null && typeof bytes === 'object' &&
-         bytes.length !== undefined, 'Invalid argument for bytesToString');
-  var length = bytes.length;
-  var MAX_ARGUMENT_COUNT = 8192;
-  if (length < MAX_ARGUMENT_COUNT) {
-    return String.fromCharCode.apply(null, bytes);
-  }
-  var strBuf = [];
-  for (var i = 0; i < length; i += MAX_ARGUMENT_COUNT) {
-    var chunkEnd = Math.min(i + MAX_ARGUMENT_COUNT, length);
-    var chunk = bytes.subarray(i, chunkEnd);
-    strBuf.push(String.fromCharCode.apply(null, chunk));
-  }
-  return strBuf.join('');
-}
-
-function stringToBytes(str) {
-  assert(typeof str === 'string', 'Invalid argument for stringToBytes');
-  var length = str.length;
-  var bytes = new Uint8Array(length);
-  for (var i = 0; i < length; ++i) {
-    bytes[i] = str.charCodeAt(i) & 0xFF;
-  }
-  return bytes;
-}
-
-/**
- * Gets length of the array (Array, Uint8Array, or string) in bytes.
- * @param {Array|Uint8Array|string} arr
- * @returns {number}
- */
-function arrayByteLength(arr) {
-  if (arr.length !== undefined) {
-    return arr.length;
-  }
-  assert(arr.byteLength !== undefined);
-  return arr.byteLength;
-}
-
-/**
- * Combines array items (arrays) into single Uint8Array object.
- * @param {Array} arr - the array of the arrays (Array, Uint8Array, or string).
- * @returns {Uint8Array}
- */
-function arraysToBytes(arr) {
-  // Shortcut: if first and only item is Uint8Array, return it.
-  if (arr.length === 1 && (arr[0] instanceof Uint8Array)) {
-    return arr[0];
-  }
-  var resultLength = 0;
-  var i, ii = arr.length;
-  var item, itemLength ;
-  for (i = 0; i < ii; i++) {
-    item = arr[i];
-    itemLength = arrayByteLength(item);
-    resultLength += itemLength;
-  }
-  var pos = 0;
-  var data = new Uint8Array(resultLength);
-  for (i = 0; i < ii; i++) {
-    item = arr[i];
-    if (!(item instanceof Uint8Array)) {
-      if (typeof item === 'string') {
-        item = stringToBytes(item);
-      } else {
-        item = new Uint8Array(item);
+    (function (root, factory) {
+      factory(root.pdfjsSharedUtil = {});
+    }(this, function (exports) {
+      var globalScope = typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : this;
+      var FONT_IDENTITY_MATRIX = [
+        0.001,
+        0,
+        0,
+        0.001,
+        0,
+        0
+      ];
+      var TextRenderingMode = {
+        FILL: 0,
+        STROKE: 1,
+        FILL_STROKE: 2,
+        INVISIBLE: 3,
+        FILL_ADD_TO_PATH: 4,
+        STROKE_ADD_TO_PATH: 5,
+        FILL_STROKE_ADD_TO_PATH: 6,
+        ADD_TO_PATH: 7,
+        FILL_STROKE_MASK: 3,
+        ADD_TO_PATH_FLAG: 4
+      };
+      var ImageKind = {
+        GRAYSCALE_1BPP: 1,
+        RGB_24BPP: 2,
+        RGBA_32BPP: 3
+      };
+      var AnnotationType = {
+        TEXT: 1,
+        LINK: 2,
+        FREETEXT: 3,
+        LINE: 4,
+        SQUARE: 5,
+        CIRCLE: 6,
+        POLYGON: 7,
+        POLYLINE: 8,
+        HIGHLIGHT: 9,
+        UNDERLINE: 10,
+        SQUIGGLY: 11,
+        STRIKEOUT: 12,
+        STAMP: 13,
+        CARET: 14,
+        INK: 15,
+        POPUP: 16,
+        FILEATTACHMENT: 17,
+        SOUND: 18,
+        MOVIE: 19,
+        WIDGET: 20,
+        SCREEN: 21,
+        PRINTERMARK: 22,
+        TRAPNET: 23,
+        WATERMARK: 24,
+        THREED: 25,
+        REDACT: 26
+      };
+      var AnnotationFlag = {
+        INVISIBLE: 0x01,
+        HIDDEN: 0x02,
+        PRINT: 0x04,
+        NOZOOM: 0x08,
+        NOROTATE: 0x10,
+        NOVIEW: 0x20,
+        READONLY: 0x40,
+        LOCKED: 0x80,
+        TOGGLENOVIEW: 0x100,
+        LOCKEDCONTENTS: 0x200
+      };
+      var AnnotationFieldFlag = {
+        READONLY: 0x0000001,
+        REQUIRED: 0x0000002,
+        NOEXPORT: 0x0000004,
+        MULTILINE: 0x0001000,
+        PASSWORD: 0x0002000,
+        NOTOGGLETOOFF: 0x0004000,
+        RADIO: 0x0008000,
+        PUSHBUTTON: 0x0010000,
+        COMBO: 0x0020000,
+        EDIT: 0x0040000,
+        SORT: 0x0080000,
+        FILESELECT: 0x0100000,
+        MULTISELECT: 0x0200000,
+        DONOTSPELLCHECK: 0x0400000,
+        DONOTSCROLL: 0x0800000,
+        COMB: 0x1000000,
+        RICHTEXT: 0x2000000,
+        RADIOSINUNISON: 0x2000000,
+        COMMITONSELCHANGE: 0x4000000
+      };
+      var AnnotationBorderStyleType = {
+        SOLID: 1,
+        DASHED: 2,
+        BEVELED: 3,
+        INSET: 4,
+        UNDERLINE: 5
+      };
+      var StreamType = {
+        UNKNOWN: 0,
+        FLATE: 1,
+        LZW: 2,
+        DCT: 3,
+        JPX: 4,
+        JBIG: 5,
+        A85: 6,
+        AHX: 7,
+        CCF: 8,
+        RL: 9
+      };
+      var FontType = {
+        UNKNOWN: 0,
+        TYPE1: 1,
+        TYPE1C: 2,
+        CIDFONTTYPE0: 3,
+        CIDFONTTYPE0C: 4,
+        TRUETYPE: 5,
+        CIDFONTTYPE2: 6,
+        TYPE3: 7,
+        OPENTYPE: 8,
+        TYPE0: 9,
+        MMTYPE1: 10
+      };
+      var VERBOSITY_LEVELS = {
+        errors: 0,
+        warnings: 1,
+        infos: 5
+      };
+      // All the possible operations for an operator list.
+      var OPS = {
+        // Intentionally start from 1 so it is easy to spot bad operators that will be
+        // 0's.
+        dependency: 1,
+        setLineWidth: 2,
+        setLineCap: 3,
+        setLineJoin: 4,
+        setMiterLimit: 5,
+        setDash: 6,
+        setRenderingIntent: 7,
+        setFlatness: 8,
+        setGState: 9,
+        save: 10,
+        restore: 11,
+        transform: 12,
+        moveTo: 13,
+        lineTo: 14,
+        curveTo: 15,
+        curveTo2: 16,
+        curveTo3: 17,
+        closePath: 18,
+        rectangle: 19,
+        stroke: 20,
+        closeStroke: 21,
+        fill: 22,
+        eoFill: 23,
+        fillStroke: 24,
+        eoFillStroke: 25,
+        closeFillStroke: 26,
+        closeEOFillStroke: 27,
+        endPath: 28,
+        clip: 29,
+        eoClip: 30,
+        beginText: 31,
+        endText: 32,
+        setCharSpacing: 33,
+        setWordSpacing: 34,
+        setHScale: 35,
+        setLeading: 36,
+        setFont: 37,
+        setTextRenderingMode: 38,
+        setTextRise: 39,
+        moveText: 40,
+        setLeadingMoveText: 41,
+        setTextMatrix: 42,
+        nextLine: 43,
+        showText: 44,
+        showSpacedText: 45,
+        nextLineShowText: 46,
+        nextLineSetSpacingShowText: 47,
+        setCharWidth: 48,
+        setCharWidthAndBounds: 49,
+        setStrokeColorSpace: 50,
+        setFillColorSpace: 51,
+        setStrokeColor: 52,
+        setStrokeColorN: 53,
+        setFillColor: 54,
+        setFillColorN: 55,
+        setStrokeGray: 56,
+        setFillGray: 57,
+        setStrokeRGBColor: 58,
+        setFillRGBColor: 59,
+        setStrokeCMYKColor: 60,
+        setFillCMYKColor: 61,
+        shadingFill: 62,
+        beginInlineImage: 63,
+        beginImageData: 64,
+        endInlineImage: 65,
+        paintXObject: 66,
+        markPoint: 67,
+        markPointProps: 68,
+        beginMarkedContent: 69,
+        beginMarkedContentProps: 70,
+        endMarkedContent: 71,
+        beginCompat: 72,
+        endCompat: 73,
+        paintFormXObjectBegin: 74,
+        paintFormXObjectEnd: 75,
+        beginGroup: 76,
+        endGroup: 77,
+        beginAnnotations: 78,
+        endAnnotations: 79,
+        beginAnnotation: 80,
+        endAnnotation: 81,
+        paintJpegXObject: 82,
+        paintImageMaskXObject: 83,
+        paintImageMaskXObjectGroup: 84,
+        paintImageXObject: 85,
+        paintInlineImageXObject: 86,
+        paintInlineImageXObjectGroup: 87,
+        paintImageXObjectRepeat: 88,
+        paintImageMaskXObjectRepeat: 89,
+        paintSolidColorImageMask: 90,
+        constructPath: 91
+      };
+      var verbosity = VERBOSITY_LEVELS.warnings;
+      function setVerbosityLevel(level) {
+        verbosity = level;
+      }
+      function getVerbosityLevel() {
+        return verbosity;
+      }
+      // A notice for devs. These are good for things that are helpful to devs, such
+      // as warning that Workers were disabled, which is important to devs but not
+      // end users.
+      function info(msg) {
+        if (verbosity >= VERBOSITY_LEVELS.infos) {
+          console.log('Info: ' + msg);
+        }
+      }
+      // Non-fatal warnings.
+      function warn(msg) {
+        if (verbosity >= VERBOSITY_LEVELS.warnings) {
+          console.log('Warning: ' + msg);
+        }
+      }
+      // Deprecated API function -- display regardless of the PDFJS.verbosity setting.
+      function deprecated(details) {
+        console.log('Deprecated API usage: ' + details);
+      }
+      // Fatal errors that should trigger the fallback UI and halt execution by
+      // throwing an exception.
+      function error(msg) {
+        if (verbosity >= VERBOSITY_LEVELS.errors) {
+          console.log('Error: ' + msg);
+          console.log(backtrace());
+        }
+        throw new Error(msg);
+      }
+      function backtrace() {
+        try {
+          throw new Error();
+        } catch (e) {
+          return e.stack ? e.stack.split('\n').slice(2).join('\n') : '';
+        }
       }
-    }
-    itemLength = item.byteLength;
-    data.set(item, pos);
-    pos += itemLength;
-  }
-  return data;
-}
-
-function string32(value) {
-  return String.fromCharCode((value >> 24) & 0xff, (value >> 16) & 0xff,
-                             (value >> 8) & 0xff, value & 0xff);
-}
-
-function log2(x) {
-  var n = 1, i = 0;
-  while (x > n) {
-    n <<= 1;
-    i++;
-  }
-  return i;
-}
-
-function readInt8(data, start) {
-  return (data[start] << 24) >> 24;
-}
-
-function readUint16(data, offset) {
-  return (data[offset] << 8) | data[offset + 1];
-}
-
-function readUint32(data, offset) {
-  return ((data[offset] << 24) | (data[offset + 1] << 16) |
-         (data[offset + 2] << 8) | data[offset + 3]) >>> 0;
-}
-
-// Lazy test the endianness of the platform
-// NOTE: This will be 'true' for simulated TypedArrays
-function isLittleEndian() {
-  var buffer8 = new Uint8Array(2);
-  buffer8[0] = 1;
-  var buffer16 = new Uint16Array(buffer8.buffer);
-  return (buffer16[0] === 1);
-}
-
-// Checks if it's possible to eval JS expressions.
-function isEvalSupported() {
-  try {
-    /* jshint evil: true */
-    new Function('');
-    return true;
-  } catch (e) {
-    return false;
-  }
-}
-
-
-var IDENTITY_MATRIX = [1, 0, 0, 1, 0, 0];
-
-var Util = (function UtilClosure() {
-  function Util() {}
-
-  var rgbBuf = ['rgb(', 0, ',', 0, ',', 0, ')'];
-
-  // makeCssRgb() can be called thousands of times. Using |rgbBuf| avoids
-  // creating many intermediate strings.
-  Util.makeCssRgb = function Util_makeCssRgb(r, g, b) {
-    rgbBuf[1] = r;
-    rgbBuf[3] = g;
-    rgbBuf[5] = b;
-    return rgbBuf.join('');
-  };
-
-  // Concatenates two transformation matrices together and returns the result.
-  Util.transform = function Util_transform(m1, m2) {
-    return [
-      m1[0] * m2[0] + m1[2] * m2[1],
-      m1[1] * m2[0] + m1[3] * m2[1],
-      m1[0] * m2[2] + m1[2] * m2[3],
-      m1[1] * m2[2] + m1[3] * m2[3],
-      m1[0] * m2[4] + m1[2] * m2[5] + m1[4],
-      m1[1] * m2[4] + m1[3] * m2[5] + m1[5]
-    ];
-  };
-
-  // For 2d affine transforms
-  Util.applyTransform = function Util_applyTransform(p, m) {
-    var xt = p[0] * m[0] + p[1] * m[2] + m[4];
-    var yt = p[0] * m[1] + p[1] * m[3] + m[5];
-    return [xt, yt];
-  };
-
-  Util.applyInverseTransform = function Util_applyInverseTransform(p, m) {
-    var d = m[0] * m[3] - m[1] * m[2];
-    var xt = (p[0] * m[3] - p[1] * m[2] + m[2] * m[5] - m[4] * m[3]) / d;
-    var yt = (-p[0] * m[1] + p[1] * m[0] + m[4] * m[1] - m[5] * m[0]) / d;
-    return [xt, yt];
-  };
-
-  // Applies the transform to the rectangle and finds the minimum axially
-  // aligned bounding box.
-  Util.getAxialAlignedBoundingBox =
-    function Util_getAxialAlignedBoundingBox(r, m) {
-
-    var p1 = Util.applyTransform(r, m);
-    var p2 = Util.applyTransform(r.slice(2, 4), m);
-    var p3 = Util.applyTransform([r[0], r[3]], m);
-    var p4 = Util.applyTransform([r[2], r[1]], m);
-    return [
-      Math.min(p1[0], p2[0], p3[0], p4[0]),
-      Math.min(p1[1], p2[1], p3[1], p4[1]),
-      Math.max(p1[0], p2[0], p3[0], p4[0]),
-      Math.max(p1[1], p2[1], p3[1], p4[1])
-    ];
-  };
-
-  Util.inverseTransform = function Util_inverseTransform(m) {
-    var d = m[0] * m[3] - m[1] * m[2];
-    return [m[3] / d, -m[1] / d, -m[2] / d, m[0] / d,
-      (m[2] * m[5] - m[4] * m[3]) / d, (m[4] * m[1] - m[5] * m[0]) / d];
-  };
-
-  // Apply a generic 3d matrix M on a 3-vector v:
-  //   | a b c |   | X |
-  //   | d e f | x | Y |
-  //   | g h i |   | Z |
-  // M is assumed to be serialized as [a,b,c,d,e,f,g,h,i],
-  // with v as [X,Y,Z]
-  Util.apply3dTransform = function Util_apply3dTransform(m, v) {
-    return [
-      m[0] * v[0] + m[1] * v[1] + m[2] * v[2],
-      m[3] * v[0] + m[4] * v[1] + m[5] * v[2],
-      m[6] * v[0] + m[7] * v[1] + m[8] * v[2]
-    ];
-  };
-
-  // This calculation uses Singular Value Decomposition.
-  // The SVD can be represented with formula A = USV. We are interested in the
-  // matrix S here because it represents the scale values.
-  Util.singularValueDecompose2dScale =
-    function Util_singularValueDecompose2dScale(m) {
-
-    var transpose = [m[0], m[2], m[1], m[3]];
-
-    // Multiply matrix m with its transpose.
-    var a = m[0] * transpose[0] + m[1] * transpose[2];
-    var b = m[0] * transpose[1] + m[1] * transpose[3];
-    var c = m[2] * transpose[0] + m[3] * transpose[2];
-    var d = m[2] * transpose[1] + m[3] * transpose[3];
-
-    // Solve the second degree polynomial to get roots.
-    var first = (a + d) / 2;
-    var second = Math.sqrt((a + d) * (a + d) - 4 * (a * d - c * b)) / 2;
-    var sx = first + second || 1;
-    var sy = first - second || 1;
-
-    // Scale values are the square roots of the eigenvalues.
-    return [Math.sqrt(sx), Math.sqrt(sy)];
-  };
-
-  // Normalize rectangle rect=[x1, y1, x2, y2] so that (x1,y1) < (x2,y2)
-  // For coordinate systems whose origin lies in the bottom-left, this
-  // means normalization to (BL,TR) ordering. For systems with origin in the
-  // top-left, this means (TL,BR) ordering.
-  Util.normalizeRect = function Util_normalizeRect(rect) {
-    var r = rect.slice(0); // clone rect
-    if (rect[0] > rect[2]) {
-      r[0] = rect[2];
-      r[2] = rect[0];
-    }
-    if (rect[1] > rect[3]) {
-      r[1] = rect[3];
-      r[3] = rect[1];
-    }
-    return r;
-  };
-
-  // Returns a rectangle [x1, y1, x2, y2] corresponding to the
-  // intersection of rect1 and rect2. If no intersection, returns 'false'
-  // The rectangle coordinates of rect1, rect2 should be [x1, y1, x2, y2]
-  Util.intersect = function Util_intersect(rect1, rect2) {
-    function compare(a, b) {
-      return a - b;
-    }
-
-    // Order points along the axes
-    var orderedX = [rect1[0], rect1[2], rect2[0], rect2[2]].sort(compare),
-        orderedY = [rect1[1], rect1[3], rect2[1], rect2[3]].sort(compare),
-        result = [];
-
-    rect1 = Util.normalizeRect(rect1);
-    rect2 = Util.normalizeRect(rect2);
-
-    // X: first and second points belong to different rectangles?
-    if ((orderedX[0] === rect1[0] && orderedX[1] === rect2[0]) ||
-        (orderedX[0] === rect2[0] && orderedX[1] === rect1[0])) {
-      // Intersection must be between second and third points
-      result[0] = orderedX[1];
-      result[2] = orderedX[2];
-    } else {
-      return false;
-    }
-
-    // Y: first and second points belong to different rectangles?
-    if ((orderedY[0] === rect1[1] && orderedY[1] === rect2[1]) ||
-        (orderedY[0] === rect2[1] && orderedY[1] === rect1[1])) {
-      // Intersection must be between second and third points
-      result[1] = orderedY[1];
-      result[3] = orderedY[2];
-    } else {
-      return false;
-    }
-
-    return result;
-  };
-
-  Util.sign = function Util_sign(num) {
-    return num < 0 ? -1 : 1;
-  };
-
-  var ROMAN_NUMBER_MAP = [
-    '', 'C', 'CC', 'CCC', 'CD', 'D', 'DC', 'DCC', 'DCCC', 'CM',
-    '', 'X', 'XX', 'XXX', 'XL', 'L', 'LX', 'LXX', 'LXXX', 'XC',
-    '', 'I', 'II', 'III', 'IV', 'V', 'VI', 'VII', 'VIII', 'IX'
-  ];
-  /**
-   * Converts positive integers to (upper case) Roman numerals.
-   * @param {integer} number - The number that should be converted.
-   * @param {boolean} lowerCase - Indicates if the result should be converted
-   *   to lower case letters. The default is false.
-   * @return {string} The resulting Roman number.
-   */
-  Util.toRoman = function Util_toRoman(number, lowerCase) {
-    assert(isInt(number) && number > 0,
-           'The number should be a positive integer.');
-    var pos, romanBuf = [];
-    // Thousands
-    while (number >= 1000) {
-      number -= 1000;
-      romanBuf.push('M');
-    }
-    // Hundreds
-    pos = (number / 100) | 0;
-    number %= 100;
-    romanBuf.push(ROMAN_NUMBER_MAP[pos]);
-    // Tens
-    pos = (number / 10) | 0;
-    number %= 10;
-    romanBuf.push(ROMAN_NUMBER_MAP[10 + pos]);
-    // Ones
-    romanBuf.push(ROMAN_NUMBER_MAP[20 + number]);
-
-    var romanStr = romanBuf.join('');
-    return (lowerCase ? romanStr.toLowerCase() : romanStr);
-  };
-
-  Util.appendToArray = function Util_appendToArray(arr1, arr2) {
-    Array.prototype.push.apply(arr1, arr2);
-  };
-
-  Util.prependToArray = function Util_prependToArray(arr1, arr2) {
-    Array.prototype.unshift.apply(arr1, arr2);
-  };
-
-  Util.extendObj = function extendObj(obj1, obj2) {
-    for (var key in obj2) {
-      obj1[key] = obj2[key];
-    }
-  };
-
-  Util.getInheritableProperty =
-      function Util_getInheritableProperty(dict, name, getArray) {
-    while (dict && !dict.has(name)) {
-      dict = dict.get('Parent');
-    }
-    if (!dict) {
-      return null;
-    }
-    return getArray ? dict.getArray(name) : dict.get(name);
-  };
-
-  Util.inherit = function Util_inherit(sub, base, prototype) {
-    sub.prototype = Object.create(base.prototype);
-    sub.prototype.constructor = sub;
-    for (var prop in prototype) {
-      sub.prototype[prop] = prototype[prop];
-    }
-  };
-
-  Util.loadScript = function Util_loadScript(src, callback) {
-    var script = document.createElement('script');
-    var loaded = false;
-    script.setAttribute('src', src);
-    if (callback) {
-      script.onload = function() {
-        if (!loaded) {
-          callback();
-        }
-        loaded = true;
+      function assert(cond, msg) {
+        if (!cond) {
+          error(msg);
+        }
+      }
+      var UNSUPPORTED_FEATURES = {
+        unknown: 'unknown',
+        forms: 'forms',
+        javaScript: 'javaScript',
+        smask: 'smask',
+        shadingPattern: 'shadingPattern',
+        font: 'font'
+      };
+      // Checks if URLs have the same origin. For non-HTTP based URLs, returns false.
+      function isSameOrigin(baseUrl, otherUrl) {
+        try {
+          var base = new URL(baseUrl);
+          if (!base.origin || base.origin === 'null') {
+            return false;
+          }
+        } // non-HTTP url
+        catch (e) {
+          return false;
+        }
+        var other = new URL(otherUrl, base);
+        return base.origin === other.origin;
+      }
+      // Checks if URLs use one of the whitelisted protocols, e.g. to avoid XSS.
+      function isValidProtocol(url) {
+        if (!url) {
+          return false;
+        }
+        switch (url.protocol) {
+        case 'http:':
+        case 'https:':
+        case 'ftp:':
+        case 'mailto:':
+        case 'tel:':
+          return true;
+        default:
+          return false;
+        }
+      }
+      /**
+       * Attempts to create a valid absolute URL (utilizing `isValidProtocol`).
+       * @param {URL|string} url - An absolute, or relative, URL.
+       * @param {URL|string} baseUrl - An absolute URL.
+       * @returns Either a valid {URL}, or `null` otherwise.
+       */
+      function createValidAbsoluteUrl(url, baseUrl) {
+        if (!url) {
+          return null;
+        }
+        try {
+          var absoluteUrl = baseUrl ? new URL(url, baseUrl) : new URL(url);
+          if (isValidProtocol(absoluteUrl)) {
+            return absoluteUrl;
+          }
+        } catch (ex) {
+        }
+        return null;
+      }
+      function shadow(obj, prop, value) {
+        Object.defineProperty(obj, prop, {
+          value: value,
+          enumerable: true,
+          configurable: true,
+          writable: false
+        });
+        return value;
+      }
+      function getLookupTableFactory(initializer) {
+        var lookup;
+        return function () {
+          if (initializer) {
+            lookup = Object.create(null);
+            initializer(lookup);
+            initializer = null;
+          }
+          return lookup;
+        };
+      }
+      var PasswordResponses = {
+        NEED_PASSWORD: 1,
+        INCORRECT_PASSWORD: 2
       };
-    }
-    document.getElementsByTagName('head')[0].appendChild(script);
-  };
-
-  return Util;
-})();
-
-/**
- * PDF page viewport created based on scale, rotation and offset.
- * @class
- * @alias PageViewport
- */
-var PageViewport = (function PageViewportClosure() {
-  /**
-   * @constructor
-   * @private
-   * @param viewBox {Array} xMin, yMin, xMax and yMax coordinates.
-   * @param scale {number} scale of the viewport.
-   * @param rotation {number} rotations of the viewport in degrees.
-   * @param offsetX {number} offset X
-   * @param offsetY {number} offset Y
-   * @param dontFlip {boolean} if true, axis Y will not be flipped.
-   */
-  function PageViewport(viewBox, scale, rotation, offsetX, offsetY, dontFlip) {
-    this.viewBox = viewBox;
-    this.scale = scale;
-    this.rotation = rotation;
-    this.offsetX = offsetX;
-    this.offsetY = offsetY;
-
-    // creating transform to convert pdf coordinate system to the normal
-    // canvas like coordinates taking in account scale and rotation
-    var centerX = (viewBox[2] + viewBox[0]) / 2;
-    var centerY = (viewBox[3] + viewBox[1]) / 2;
-    var rotateA, rotateB, rotateC, rotateD;
-    rotation = rotation % 360;
-    rotation = rotation < 0 ? rotation + 360 : rotation;
-    switch (rotation) {
-      case 180:
-        rotateA = -1; rotateB = 0; rotateC = 0; rotateD = 1;
-        break;
-      case 90:
-        rotateA = 0; rotateB = 1; rotateC = 1; rotateD = 0;
-        break;
-      case 270:
-        rotateA = 0; rotateB = -1; rotateC = -1; rotateD = 0;
-        break;
-      //case 0:
-      default:
-        rotateA = 1; rotateB = 0; rotateC = 0; rotateD = -1;
-        break;
-    }
-
-    if (dontFlip) {
-      rotateC = -rotateC; rotateD = -rotateD;
-    }
-
-    var offsetCanvasX, offsetCanvasY;
-    var width, height;
-    if (rotateA === 0) {
-      offsetCanvasX = Math.abs(centerY - viewBox[1]) * scale + offsetX;
-      offsetCanvasY = Math.abs(centerX - viewBox[0]) * scale + offsetY;
-      width = Math.abs(viewBox[3] - viewBox[1]) * scale;
-      height = Math.abs(viewBox[2] - viewBox[0]) * scale;
-    } else {
-      offsetCanvasX = Math.abs(centerX - viewBox[0]) * scale + offsetX;
-      offsetCanvasY = Math.abs(centerY - viewBox[1]) * scale + offsetY;
-      width = Math.abs(viewBox[2] - viewBox[0]) * scale;
-      height = Math.abs(viewBox[3] - viewBox[1]) * scale;
-    }
-    // creating transform for the following operations:
-    // translate(-centerX, -centerY), rotate and flip vertically,
-    // scale, and translate(offsetCanvasX, offsetCanvasY)
-    this.transform = [
-      rotateA * scale,
-      rotateB * scale,
-      rotateC * scale,
-      rotateD * scale,
-      offsetCanvasX - rotateA * scale * centerX - rotateC * scale * centerY,
-      offsetCanvasY - rotateB * scale * centerX - rotateD * scale * centerY
-    ];
-
-    this.width = width;
-    this.height = height;
-    this.fontScale = scale;
-  }
-  PageViewport.prototype = /** @lends PageViewport.prototype */ {
-    /**
-     * Clones viewport with additional properties.
-     * @param args {Object} (optional) If specified, may contain the 'scale' or
-     * 'rotation' properties to override the corresponding properties in
-     * the cloned viewport.
-     * @returns {PageViewport} Cloned viewport.
-     */
-    clone: function PageViewPort_clone(args) {
-      args = args || {};
-      var scale = 'scale' in args ? args.scale : this.scale;
-      var rotation = 'rotation' in args ? args.rotation : this.rotation;
-      return new PageViewport(this.viewBox.slice(), scale, rotation,
-                              this.offsetX, this.offsetY, args.dontFlip);
-    },
-    /**
-     * Converts PDF point to the viewport coordinates. For examples, useful for
-     * converting PDF location into canvas pixel coordinates.
-     * @param x {number} X coordinate.
-     * @param y {number} Y coordinate.
-     * @returns {Object} Object that contains 'x' and 'y' properties of the
-     * point in the viewport coordinate space.
-     * @see {@link convertToPdfPoint}
-     * @see {@link convertToViewportRectangle}
-     */
-    convertToViewportPoint: function PageViewport_convertToViewportPoint(x, y) {
-      return Util.applyTransform([x, y], this.transform);
-    },
-    /**
-     * Converts PDF rectangle to the viewport coordinates.
-     * @param rect {Array} xMin, yMin, xMax and yMax coordinates.
-     * @returns {Array} Contains corresponding coordinates of the rectangle
-     * in the viewport coordinate space.
-     * @see {@link convertToViewportPoint}
-     */
-    convertToViewportRectangle:
-      function PageViewport_convertToViewportRectangle(rect) {
-      var tl = Util.applyTransform([rect[0], rect[1]], this.transform);
-      var br = Util.applyTransform([rect[2], rect[3]], this.transform);
-      return [tl[0], tl[1], br[0], br[1]];
-    },
-    /**
-     * Converts viewport coordinates to the PDF location. For examples, useful
-     * for converting canvas pixel location into PDF one.
-     * @param x {number} X coordinate.
-     * @param y {number} Y coordinate.
-     * @returns {Object} Object that contains 'x' and 'y' properties of the
-     * point in the PDF coordinate space.
-     * @see {@link convertToViewportPoint}
-     */
-    convertToPdfPoint: function PageViewport_convertToPdfPoint(x, y) {
-      return Util.applyInverseTransform([x, y], this.transform);
-    }
-  };
-  return PageViewport;
-})();
-
-var PDFStringTranslateTable = [
-  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
-  0x2D8, 0x2C7, 0x2C6, 0x2D9, 0x2DD, 0x2DB, 0x2DA, 0x2DC, 0, 0, 0, 0, 0, 0, 0,
-  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
-  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
-  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
-  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x2022, 0x2020, 0x2021, 0x2026, 0x2014,
-  0x2013, 0x192, 0x2044, 0x2039, 0x203A, 0x2212, 0x2030, 0x201E, 0x201C,
-  0x201D, 0x2018, 0x2019, 0x201A, 0x2122, 0xFB01, 0xFB02, 0x141, 0x152, 0x160,
-  0x178, 0x17D, 0x131, 0x142, 0x153, 0x161, 0x17E, 0, 0x20AC
-];
-
-function stringToPDFString(str) {
-  var i, n = str.length, strBuf = [];
-  if (str[0] === '\xFE' && str[1] === '\xFF') {
-    // UTF16BE BOM
-    for (i = 2; i < n; i += 2) {
-      strBuf.push(String.fromCharCode(
-        (str.charCodeAt(i) << 8) | str.charCodeAt(i + 1)));
-    }
-  } else {
-    for (i = 0; i < n; ++i) {
-      var code = PDFStringTranslateTable[str.charCodeAt(i)];
-      strBuf.push(code ? String.fromCharCode(code) : str.charAt(i));
-    }
-  }
-  return strBuf.join('');
-}
-
-function stringToUTF8String(str) {
-  return decodeURIComponent(escape(str));
-}
-
-function utf8StringToString(str) {
-  return unescape(encodeURIComponent(str));
-}
-
-function isEmptyObj(obj) {
-  for (var key in obj) {
-    return false;
-  }
-  return true;
-}
-
-function isBool(v) {
-  return typeof v === 'boolean';
-}
-
-function isInt(v) {
-  return typeof v === 'number' && ((v | 0) === v);
-}
-
-function isNum(v) {
-  return typeof v === 'number';
-}
-
-function isString(v) {
-  return typeof v === 'string';
-}
-
-function isArray(v) {
-  return v instanceof Array;
-}
-
-function isArrayBuffer(v) {
-  return typeof v === 'object' && v !== null && v.byteLength !== undefined;
-}
-
-// Checks if ch is one of the following characters: SPACE, TAB, CR or LF.
-function isSpace(ch) {
-  return (ch === 0x20 || ch === 0x09 || ch === 0x0D || ch === 0x0A);
-}
-
-/**
- * Promise Capability object.
- *
- * @typedef {Object} PromiseCapability
- * @property {Promise} promise - A promise object.
- * @property {function} resolve - Fulfills the promise.
- * @property {function} reject - Rejects the promise.
- */
-
-/**
- * Creates a promise capability object.
- * @alias createPromiseCapability
- *
- * @return {PromiseCapability} A capability object contains:
- * - a Promise, resolve and reject methods.
- */
-function createPromiseCapability() {
-  var capability = {};
-  capability.promise = new Promise(function (resolve, reject) {
-    capability.resolve = resolve;
-    capability.reject = reject;
-  });
-  return capability;
-}
-
-/**
- * Polyfill for Promises:
- * The following promise implementation tries to generally implement the
- * Promise/A+ spec. Some notable differences from other promise libraries are:
- * - There currently isn't a separate deferred and promise object.
- * - Unhandled rejections eventually show an error if they aren't handled.
- *
- * Based off of the work in:
- * https://bugzilla.mozilla.org/show_bug.cgi?id=810490
- */
-(function PromiseClosure() {
-  if (globalScope.Promise) {
-    // Promises existing in the DOM/Worker, checking presence of all/resolve
-    if (typeof globalScope.Promise.all !== 'function') {
-      globalScope.Promise.all = function (iterable) {
-        var count = 0, results = [], resolve, reject;
-        var promise = new globalScope.Promise(function (resolve_, reject_) {
-          resolve = resolve_;
-          reject = reject_;
-        });
-        iterable.forEach(function (p, i) {
-          count++;
-          p.then(function (result) {
-            results[i] = result;
-            count--;
-            if (count === 0) {
-              resolve(results);
-            }
-          }, reject);
-        });
-        if (count === 0) {
-          resolve(results);
-        }
-        return promise;
-      };
-    }
-    if (typeof globalScope.Promise.resolve !== 'function') {
-      globalScope.Promise.resolve = function (value) {
-        return new globalScope.Promise(function (resolve) { resolve(value); });
-      };
-    }
-    if (typeof globalScope.Promise.reject !== 'function') {
-      globalScope.Promise.reject = function (reason) {
-        return new globalScope.Promise(function (resolve, reject) {
-          reject(reason);
-        });
-      };
-    }
-    if (typeof globalScope.Promise.prototype.catch !== 'function') {
-      globalScope.Promise.prototype.catch = function (onReject) {
-        return globalScope.Promise.prototype.then(undefined, onReject);
-      };
-    }
-    return;
-  }
-  throw new Error('DOM Promise is not present');
-})();
-
-
-var StatTimer = (function StatTimerClosure() {
-  function rpad(str, pad, length) {
-    while (str.length < length) {
-      str += pad;
-    }
-    return str;
-  }
-  function StatTimer() {
-    this.started = Object.create(null);
-    this.times = [];
-    this.enabled = true;
-  }
-  StatTimer.prototype = {
-    time: function StatTimer_time(name) {
-      if (!this.enabled) {
-        return;
+      var PasswordException = function PasswordExceptionClosure() {
+        function PasswordException(msg, code) {
+          this.name = 'PasswordException';
+          this.message = msg;
+          this.code = code;
+        }
+        PasswordException.prototype = new Error();
+        PasswordException.constructor = PasswordException;
+        return PasswordException;
+      }();
+      var UnknownErrorException = function UnknownErrorExceptionClosure() {
+        function UnknownErrorException(msg, details) {
+          this.name = 'UnknownErrorException';
+          this.message = msg;
+          this.details = details;
+        }
+        UnknownErrorException.prototype = new Error();
+        UnknownErrorException.constructor = UnknownErrorException;
+        return UnknownErrorException;
+      }();
+      var InvalidPDFException = function InvalidPDFExceptionClosure() {
+        function InvalidPDFException(msg) {
+          this.name = 'InvalidPDFException';
+          this.message = msg;
+        }
+        InvalidPDFException.prototype = new Error();
+        InvalidPDFException.constructor = InvalidPDFException;
+        return InvalidPDFException;
+      }();
+      var MissingPDFException = function MissingPDFExceptionClosure() {
+        function MissingPDFException(msg) {
+          this.name = 'MissingPDFException';
+          this.message = msg;
+        }
+        MissingPDFException.prototype = new Error();
+        MissingPDFException.constructor = MissingPDFException;
+        return MissingPDFException;
+      }();
+      var UnexpectedResponseException = function UnexpectedResponseExceptionClosure() {
+        function UnexpectedResponseException(msg, status) {
+          this.name = 'UnexpectedResponseException';
+          this.message = msg;
+          this.status = status;
+        }
+        UnexpectedResponseException.prototype = new Error();
+        UnexpectedResponseException.constructor = UnexpectedResponseException;
+        return UnexpectedResponseException;
+      }();
+      var NotImplementedException = function NotImplementedExceptionClosure() {
+        function NotImplementedException(msg) {
+          this.message = msg;
+        }
+        NotImplementedException.prototype = new Error();
+        NotImplementedException.prototype.name = 'NotImplementedException';
+        NotImplementedException.constructor = NotImplementedException;
+        return NotImplementedException;
+      }();
+      var MissingDataException = function MissingDataExceptionClosure() {
+        function MissingDataException(begin, end) {
+          this.begin = begin;
+          this.end = end;
+          this.message = 'Missing data [' + begin + ', ' + end + ')';
+        }
+        MissingDataException.prototype = new Error();
+        MissingDataException.prototype.name = 'MissingDataException';
+        MissingDataException.constructor = MissingDataException;
+        return MissingDataException;
+      }();
+      var XRefParseException = function XRefParseExceptionClosure() {
+        function XRefParseException(msg) {
+          this.message = msg;
+        }
+        XRefParseException.prototype = new Error();
+        XRefParseException.prototype.name = 'XRefParseException';
+        XRefParseException.constructor = XRefParseException;
+        return XRefParseException;
+      }();
+      var NullCharactersRegExp = /\x00/g;
+      function removeNullCharacters(str) {
+        if (typeof str !== 'string') {
+          warn('The argument for removeNullCharacters must be a string.');
+          return str;
+        }
+        return str.replace(NullCharactersRegExp, '');
       }
-      if (name in this.started) {
-        warn('Timer is already running for ' + name);
+      function bytesToString(bytes) {
+        assert(bytes !== null && typeof bytes === 'object' && bytes.length !== undefined, 'Invalid argument for bytesToString');
+        var length = bytes.length;
+        var MAX_ARGUMENT_COUNT = 8192;
+        if (length < MAX_ARGUMENT_COUNT) {
+          return String.fromCharCode.apply(null, bytes);
+        }
+        var strBuf = [];
+        for (var i = 0; i < length; i += MAX_ARGUMENT_COUNT) {
+          var chunkEnd = Math.min(i + MAX_ARGUMENT_COUNT, length);
+          var chunk = bytes.subarray(i, chunkEnd);
+          strBuf.push(String.fromCharCode.apply(null, chunk));
+        }
+        return strBuf.join('');
       }
-      this.started[name] = Date.now();
-    },
-    timeEnd: function StatTimer_timeEnd(name) {
-      if (!this.enabled) {
-        return;
+      function stringToBytes(str) {
+        assert(typeof str === 'string', 'Invalid argument for stringToBytes');
+        var length = str.length;
+        var bytes = new Uint8Array(length);
+        for (var i = 0; i < length; ++i) {
+          bytes[i] = str.charCodeAt(i) & 0xFF;
+        }
+        return bytes;
       }
-      if (!(name in this.started)) {
-        warn('Timer has not been started for ' + name);
+      /**
+       * Gets length of the array (Array, Uint8Array, or string) in bytes.
+       * @param {Array|Uint8Array|string} arr
+       * @returns {number}
+       */
+      function arrayByteLength(arr) {
+        if (arr.length !== undefined) {
+          return arr.length;
+        }
+        assert(arr.byteLength !== undefined);
+        return arr.byteLength;
       }
-      this.times.push({
-        'name': name,
-        'start': this.started[name],
-        'end': Date.now()
-      });
-      // Remove timer from started so it can be called again.
-      delete this.started[name];
-    },
-    toString: function StatTimer_toString() {
-      var i, ii;
-      var times = this.times;
-      var out = '';
-      // Find the longest name for padding purposes.
-      var longest = 0;
-      for (i = 0, ii = times.length; i < ii; ++i) {
-        var name = times[i]['name'];
-        if (name.length > longest) {
-          longest = name.length;
+      /**
+       * Combines array items (arrays) into single Uint8Array object.
+       * @param {Array} arr - the array of the arrays (Array, Uint8Array, or string).
+       * @returns {Uint8Array}
+       */
+      function arraysToBytes(arr) {
+        // Shortcut: if first and only item is Uint8Array, return it.
+        if (arr.length === 1 && arr[0] instanceof Uint8Array) {
+          return arr[0];
+        }
+        var resultLength = 0;
+        var i, ii = arr.length;
+        var item, itemLength;
+        for (i = 0; i < ii; i++) {
+          item = arr[i];
+          itemLength = arrayByteLength(item);
+          resultLength += itemLength;
+        }
+        var pos = 0;
+        var data = new Uint8Array(resultLength);
+        for (i = 0; i < ii; i++) {
+          item = arr[i];
+          if (!(item instanceof Uint8Array)) {
+            if (typeof item === 'string') {
+              item = stringToBytes(item);
+            } else {
+              item = new Uint8Array(item);
+            }
+          }
+          itemLength = item.byteLength;
+          data.set(item, pos);
+          pos += itemLength;
+        }
+        return data;
+      }
+      function string32(value) {
+        return String.fromCharCode(value >> 24 & 0xff, value >> 16 & 0xff, value >> 8 & 0xff, value & 0xff);
+      }
+      function log2(x) {
+        var n = 1, i = 0;
+        while (x > n) {
+          n <<= 1;
+          i++;
+        }
+        return i;
+      }
+      function readInt8(data, start) {
+        return data[start] << 24 >> 24;
+      }
+      function readUint16(data, offset) {
+        return data[offset] << 8 | data[offset + 1];
+      }
+      function readUint32(data, offset) {
+        return (data[offset] << 24 | data[offset + 1] << 16 | data[offset + 2] << 8 | data[offset + 3]) >>> 0;
+      }
+      // Lazy test the endianness of the platform
+      // NOTE: This will be 'true' for simulated TypedArrays
+      function isLittleEndian() {
+        var buffer8 = new Uint8Array(2);
+        buffer8[0] = 1;
+        var buffer16 = new Uint16Array(buffer8.buffer);
+        return buffer16[0] === 1;
+      }
+      // Checks if it's possible to eval JS expressions.
+      function isEvalSupported() {
+        try {
+          new Function('');
+          return true;
+        } catch (e) {
+          return false;
         }
       }
-      for (i = 0, ii = times.length; i < ii; ++i) {
-        var span = times[i];
-        var duration = span.end - span.start;
-        out += rpad(span['name'], ' ', longest) + ' ' + duration + 'ms\n';
-      }
-      return out;
-    }
-  };
-  return StatTimer;
-})();
-
-var createBlob = function createBlob(data, contentType) {
-  if (typeof Blob !== 'undefined') {
-    return new Blob([data], { type: contentType });
-  }
-  warn('The "Blob" constructor is not supported.');
-};
-
-var createObjectURL = (function createObjectURLClosure() {
-  // Blob/createObjectURL is not available, falling back to data schema.
-  var digits =
-    'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';
-
-  return function createObjectURL(data, contentType, forceDataSchema) {
-    if (!forceDataSchema &&
-        typeof URL !== 'undefined' && URL.createObjectURL) {
-      var blob = createBlob(data, contentType);
-      return URL.createObjectURL(blob);
-    }
-
-    var buffer = 'data:' + contentType + ';base64,';
-    for (var i = 0, ii = data.length; i < ii; i += 3) {
-      var b1 = data[i] & 0xFF;
-      var b2 = data[i + 1] & 0xFF;
-      var b3 = data[i + 2] & 0xFF;
-      var d1 = b1 >> 2, d2 = ((b1 & 3) << 4) | (b2 >> 4);
-      var d3 = i + 1 < ii ? ((b2 & 0xF) << 2) | (b3 >> 6) : 64;
-      var d4 = i + 2 < ii ? (b3 & 0x3F) : 64;
-      buffer += digits[d1] + digits[d2] + digits[d3] + digits[d4];
-    }
-    return buffer;
-  };
-})();
-
-function MessageHandler(sourceName, targetName, comObj) {
-  this.sourceName = sourceName;
-  this.targetName = targetName;
-  this.comObj = comObj;
-  this.callbackIndex = 1;
-  this.postMessageTransfers = true;
-  var callbacksCapabilities = this.callbacksCapabilities = Object.create(null);
-  var ah = this.actionHandler = Object.create(null);
-
-  this._onComObjOnMessage = function messageHandlerComObjOnMessage(event) {
-    var data = event.data;
-    if (data.targetName !== this.sourceName) {
-      return;
-    }
-    if (data.isReply) {
-      var callbackId = data.callbackId;
-      if (data.callbackId in callbacksCapabilities) {
-        var callback = callbacksCapabilities[callbackId];
-        delete callbacksCapabilities[callbackId];
-        if ('error' in data) {
-          callback.reject(data.error);
+      var IDENTITY_MATRIX = [
+        1,
+        0,
+        0,
+        1,
+        0,
+        0
+      ];
+      var Util = function UtilClosure() {
+        function Util() {
+        }
+        var rgbBuf = [
+          'rgb(',
+          0,
+          ',',
+          0,
+          ',',
+          0,
+          ')'
+        ];
+        // makeCssRgb() can be called thousands of times. Using |rgbBuf| avoids
+        // creating many intermediate strings.
+        Util.makeCssRgb = function Util_makeCssRgb(r, g, b) {
+          rgbBuf[1] = r;
+          rgbBuf[3] = g;
+          rgbBuf[5] = b;
+          return rgbBuf.join('');
+        };
+        // Concatenates two transformation matrices together and returns the result.
+        Util.transform = function Util_transform(m1, m2) {
+          return [
+            m1[0] * m2[0] + m1[2] * m2[1],
+            m1[1] * m2[0] + m1[3] * m2[1],
+            m1[0] * m2[2] + m1[2] * m2[3],
+            m1[1] * m2[2] + m1[3] * m2[3],
+            m1[0] * m2[4] + m1[2] * m2[5] + m1[4],
+            m1[1] * m2[4] + m1[3] * m2[5] + m1[5]
+          ];
+        };
+        // For 2d affine transforms
+        Util.applyTransform = function Util_applyTransform(p, m) {
+          var xt = p[0] * m[0] + p[1] * m[2] + m[4];
+          var yt = p[0] * m[1] + p[1] * m[3] + m[5];
+          return [
+            xt,
+            yt
+          ];
+        };
+        Util.applyInverseTransform = function Util_applyInverseTransform(p, m) {
+          var d = m[0] * m[3] - m[1] * m[2];
+          var xt = (p[0] * m[3] - p[1] * m[2] + m[2] * m[5] - m[4] * m[3]) / d;
+          var yt = (-p[0] * m[1] + p[1] * m[0] + m[4] * m[1] - m[5] * m[0]) / d;
+          return [
+            xt,
+            yt
+          ];
+        };
+        // Applies the transform to the rectangle and finds the minimum axially
+        // aligned bounding box.
+        Util.getAxialAlignedBoundingBox = function Util_getAxialAlignedBoundingBox(r, m) {
+          var p1 = Util.applyTransform(r, m);
+          var p2 = Util.applyTransform(r.slice(2, 4), m);
+          var p3 = Util.applyTransform([
+            r[0],
+            r[3]
+          ], m);
+          var p4 = Util.applyTransform([
+            r[2],
+            r[1]
+          ], m);
+          return [
+            Math.min(p1[0], p2[0], p3[0], p4[0]),
+            Math.min(p1[1], p2[1], p3[1], p4[1]),
+            Math.max(p1[0], p2[0], p3[0], p4[0]),
+            Math.max(p1[1], p2[1], p3[1], p4[1])
+          ];
+        };
+        Util.inverseTransform = function Util_inverseTransform(m) {
+          var d = m[0] * m[3] - m[1] * m[2];
+          return [
+            m[3] / d,
+            -m[1] / d,
+            -m[2] / d,
+            m[0] / d,
+            (m[2] * m[5] - m[4] * m[3]) / d,
+            (m[4] * m[1] - m[5] * m[0]) / d
+          ];
+        };
+        // Apply a generic 3d matrix M on a 3-vector v:
+        //   | a b c |   | X |
+        //   | d e f | x | Y |
+        //   | g h i |   | Z |
+        // M is assumed to be serialized as [a,b,c,d,e,f,g,h,i],
+        // with v as [X,Y,Z]
+        Util.apply3dTransform = function Util_apply3dTransform(m, v) {
+          return [
+            m[0] * v[0] + m[1] * v[1] + m[2] * v[2],
+            m[3] * v[0] + m[4] * v[1] + m[5] * v[2],
+            m[6] * v[0] + m[7] * v[1] + m[8] * v[2]
+          ];
+        };
+        // This calculation uses Singular Value Decomposition.
+        // The SVD can be represented with formula A = USV. We are interested in the
+        // matrix S here because it represents the scale values.
+        Util.singularValueDecompose2dScale = function Util_singularValueDecompose2dScale(m) {
+          var transpose = [
+            m[0],
+            m[2],
+            m[1],
+            m[3]
+          ];
+          // Multiply matrix m with its transpose.
+          var a = m[0] * transpose[0] + m[1] * transpose[2];
+          var b = m[0] * transpose[1] + m[1] * transpose[3];
+          var c = m[2] * transpose[0] + m[3] * transpose[2];
+          var d = m[2] * transpose[1] + m[3] * transpose[3];
+          // Solve the second degree polynomial to get roots.
+          var first = (a + d) / 2;
+          var second = Math.sqrt((a + d) * (a + d) - 4 * (a * d - c * b)) / 2;
+          var sx = first + second || 1;
+          var sy = first - second || 1;
+          // Scale values are the square roots of the eigenvalues.
+          return [
+            Math.sqrt(sx),
+            Math.sqrt(sy)
+          ];
+        };
+        // Normalize rectangle rect=[x1, y1, x2, y2] so that (x1,y1) < (x2,y2)
+        // For coordinate systems whose origin lies in the bottom-left, this
+        // means normalization to (BL,TR) ordering. For systems with origin in the
+        // top-left, this means (TL,BR) ordering.
+        Util.normalizeRect = function Util_normalizeRect(rect) {
+          var r = rect.slice(0);
+          // clone rect
+          if (rect[0] > rect[2]) {
+            r[0] = rect[2];
+            r[2] = rect[0];
+          }
+          if (rect[1] > rect[3]) {
+            r[1] = rect[3];
+            r[3] = rect[1];
+          }
+          return r;
+        };
+        // Returns a rectangle [x1, y1, x2, y2] corresponding to the
+        // intersection of rect1 and rect2. If no intersection, returns 'false'
+        // The rectangle coordinates of rect1, rect2 should be [x1, y1, x2, y2]
+        Util.intersect = function Util_intersect(rect1, rect2) {
+          function compare(a, b) {
+            return a - b;
+          }
+          // Order points along the axes
+          var orderedX = [
+              rect1[0],
+              rect1[2],
+              rect2[0],
+              rect2[2]
+            ].sort(compare), orderedY = [
+              rect1[1],
+              rect1[3],
+              rect2[1],
+              rect2[3]
+            ].sort(compare), result = [];
+          rect1 = Util.normalizeRect(rect1);
+          rect2 = Util.normalizeRect(rect2);
+          // X: first and second points belong to different rectangles?
+          if (orderedX[0] === rect1[0] && orderedX[1] === rect2[0] || orderedX[0] === rect2[0] && orderedX[1] === rect1[0]) {
+            // Intersection must be between second and third points
+            result[0] = orderedX[1];
+            result[2] = orderedX[2];
+          } else {
+            return false;
+          }
+          // Y: first and second points belong to different rectangles?
+          if (orderedY[0] === rect1[1] && orderedY[1] === rect2[1] || orderedY[0] === rect2[1] && orderedY[1] === rect1[1]) {
+            // Intersection must be between second and third points
+            result[1] = orderedY[1];
+            result[3] = orderedY[2];
+          } else {
+            return false;
+          }
+          return result;
+        };
+        Util.sign = function Util_sign(num) {
+          return num < 0 ? -1 : 1;
+        };
+        var ROMAN_NUMBER_MAP = [
+          '',
+          'C',
+          'CC',
+          'CCC',
+          'CD',
+          'D',
+          'DC',
+          'DCC',
+          'DCCC',
+          'CM',
+          '',
+          'X',
+          'XX',
+          'XXX',
+          'XL',
+          'L',
+          'LX',
+          'LXX',
+          'LXXX',
+          'XC',
+          '',
+          'I',
+          'II',
+          'III',
+          'IV',
+          'V',
+          'VI',
+          'VII',
+          'VIII',
+          'IX'
+        ];
+        /**
+         * Converts positive integers to (upper case) Roman numerals.
+         * @param {integer} number - The number that should be converted.
+         * @param {boolean} lowerCase - Indicates if the result should be converted
+         *   to lower case letters. The default is false.
+         * @return {string} The resulting Roman number.
+         */
+        Util.toRoman = function Util_toRoman(number, lowerCase) {
+          assert(isInt(number) && number > 0, 'The number should be a positive integer.');
+          var pos, romanBuf = [];
+          // Thousands
+          while (number >= 1000) {
+            number -= 1000;
+            romanBuf.push('M');
+          }
+          // Hundreds
+          pos = number / 100 | 0;
+          number %= 100;
+          romanBuf.push(ROMAN_NUMBER_MAP[pos]);
+          // Tens
+          pos = number / 10 | 0;
+          number %= 10;
+          romanBuf.push(ROMAN_NUMBER_MAP[10 + pos]);
+          // Ones
+          romanBuf.push(ROMAN_NUMBER_MAP[20 + number]);
+          var romanStr = romanBuf.join('');
+          return lowerCase ? romanStr.toLowerCase() : romanStr;
+        };
+        Util.appendToArray = function Util_appendToArray(arr1, arr2) {
+          Array.prototype.push.apply(arr1, arr2);
+        };
+        Util.prependToArray = function Util_prependToArray(arr1, arr2) {
+          Array.prototype.unshift.apply(arr1, arr2);
+        };
+        Util.extendObj = function extendObj(obj1, obj2) {
+          for (var key in obj2) {
+            obj1[key] = obj2[key];
+          }
+        };
+        Util.getInheritableProperty = function Util_getInheritableProperty(dict, name, getArray) {
+          while (dict && !dict.has(name)) {
+            dict = dict.get('Parent');
+          }
+          if (!dict) {
+            return null;
+          }
+          return getArray ? dict.getArray(name) : dict.get(name);
+        };
+        Util.inherit = function Util_inherit(sub, base, prototype) {
+          sub.prototype = Object.create(base.prototype);
+          sub.prototype.constructor = sub;
+          for (var prop in prototype) {
+            sub.prototype[prop] = prototype[prop];
+          }
+        };
+        Util.loadScript = function Util_loadScript(src, callback) {
+          var script = document.createElement('script');
+          var loaded = false;
+          script.setAttribute('src', src);
+          if (callback) {
+            script.onload = function () {
+              if (!loaded) {
+                callback();
+              }
+              loaded = true;
+            };
+          }
+          document.getElementsByTagName('head')[0].appendChild(script);
+        };
+        return Util;
+      }();
+      /**
+       * PDF page viewport created based on scale, rotation and offset.
+       * @class
+       * @alias PageViewport
+       */
+      var PageViewport = function PageViewportClosure() {
+        /**
+         * @constructor
+         * @private
+         * @param viewBox {Array} xMin, yMin, xMax and yMax coordinates.
+         * @param scale {number} scale of the viewport.
+         * @param rotation {number} rotations of the viewport in degrees.
+         * @param offsetX {number} offset X
+         * @param offsetY {number} offset Y
+         * @param dontFlip {boolean} if true, axis Y will not be flipped.
+         */
+        function PageViewport(viewBox, scale, rotation, offsetX, offsetY, dontFlip) {
+          this.viewBox = viewBox;
+          this.scale = scale;
+          this.rotation = rotation;
+          this.offsetX = offsetX;
+          this.offsetY = offsetY;
+          // creating transform to convert pdf coordinate system to the normal
+          // canvas like coordinates taking in account scale and rotation
+          var centerX = (viewBox[2] + viewBox[0]) / 2;
+          var centerY = (viewBox[3] + viewBox[1]) / 2;
+          var rotateA, rotateB, rotateC, rotateD;
+          rotation = rotation % 360;
+          rotation = rotation < 0 ? rotation + 360 : rotation;
+          switch (rotation) {
+          case 180:
+            rotateA = -1;
+            rotateB = 0;
+            rotateC = 0;
+            rotateD = 1;
+            break;
+          case 90:
+            rotateA = 0;
+            rotateB = 1;
+            rotateC = 1;
+            rotateD = 0;
+            break;
+          case 270:
+            rotateA = 0;
+            rotateB = -1;
+            rotateC = -1;
+            rotateD = 0;
+            break;
+          //case 0:
+          default:
+            rotateA = 1;
+            rotateB = 0;
+            rotateC = 0;
+            rotateD = -1;
+            break;
+          }
+