Merge m-c to graphics
authorKartikaya Gupta <kgupta@mozilla.com>
Tue, 09 May 2017 09:16:42 -0400
changeset 359293 2e78cc583b9c6485f677cfc86168e1ac9554637b
parent 359292 b0adf1e0f7ed8a04a90693fc672277016c40b28b (current diff)
parent 357193 b0ff0c5c0a35a1ee9f19cec2bae5cc2cd5772db6 (diff)
child 359294 a3916eccfa832ce5273a5775b408e5ee33bb7d9f
push id31852
push userkwierso@gmail.com
push dateFri, 19 May 2017 21:47:27 +0000
treeherdermozilla-central@979f11deabd0 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone55.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge m-c to graphics MozReview-Commit-ID: ETkjQuNwtc8
browser/components/downloads/test/browser/browser_downloads_panel_footer.js
browser/components/downloads/test/unit/.eslintrc.js
browser/components/downloads/test/unit/head.js
browser/components/downloads/test/unit/test_DownloadsCommon.js
browser/components/downloads/test/unit/xpcshell.ini
dom/base/ScriptSettings.cpp
dom/base/ScriptSettings.h
dom/base/nsIScriptElement.h
dom/base/nsIScriptLoaderObserver.idl
dom/base/nsScriptElement.cpp
dom/base/nsScriptElement.h
dom/base/nsScriptLoader.cpp
dom/base/nsScriptLoader.h
services/cloudsync/CloudSync.jsm
services/cloudsync/CloudSyncAdapters.jsm
services/cloudsync/CloudSyncBookmarks.jsm
services/cloudsync/CloudSyncBookmarksFolderCache.jsm
services/cloudsync/CloudSyncEventSource.jsm
services/cloudsync/CloudSyncLocal.jsm
services/cloudsync/CloudSyncPlacesWrapper.jsm
services/cloudsync/CloudSyncTabs.jsm
services/cloudsync/docs/api.md
services/cloudsync/docs/architecture.rst
services/cloudsync/docs/dataformat.rst
services/cloudsync/docs/example.rst
services/cloudsync/docs/index.rst
services/cloudsync/moz.build
services/cloudsync/tests/mochitest/.eslintrc.js
services/cloudsync/tests/mochitest/browser.ini
services/cloudsync/tests/mochitest/browser_tabEvents.js
services/cloudsync/tests/mochitest/other_window.html
services/cloudsync/tests/xpcshell/.eslintrc.js
services/cloudsync/tests/xpcshell/head.js
services/cloudsync/tests/xpcshell/test_bookmarks.js
services/cloudsync/tests/xpcshell/test_lazyload.js
services/cloudsync/tests/xpcshell/test_module.js
services/cloudsync/tests/xpcshell/test_tabs.js
services/cloudsync/tests/xpcshell/xpcshell.ini
servo/components/style/gecko/generated/gecko_pseudo_element_helper.rs
servo/components/style/gecko_bindings/bindings.rs
servo/components/style/gecko_bindings/structs_debug.rs
servo/components/style/gecko_bindings/structs_release.rs
servo/components/style/gecko_string_cache/atom_macro.rs
third_party/rust/bindgen/src/chooser.rs
third_party/rust/bindgen/tests/headers/16-byte-alignment.h
third_party/rust/bindgen/tests/headers/381-decltype-alias.hpp
third_party/rust/bindgen/tests/headers/accessors.hpp
third_party/rust/bindgen/tests/headers/annotation_hide.hpp
third_party/rust/bindgen/tests/headers/anon_enum.hpp
third_party/rust/bindgen/tests/headers/anon_enum_trait.hpp
third_party/rust/bindgen/tests/headers/anon_enum_whitelist.h
third_party/rust/bindgen/tests/headers/anon_union.hpp
third_party/rust/bindgen/tests/headers/arg_keyword.hpp
third_party/rust/bindgen/tests/headers/auto.hpp
third_party/rust/bindgen/tests/headers/bad-namespace-parenthood-inheritance.hpp
third_party/rust/bindgen/tests/headers/base-to-derived.hpp
third_party/rust/bindgen/tests/headers/bitfield-enum-basic.hpp
third_party/rust/bindgen/tests/headers/bitfield_align.h
third_party/rust/bindgen/tests/headers/bitfield_method_mangling.h
third_party/rust/bindgen/tests/headers/blocks.h
third_party/rust/bindgen/tests/headers/canonical_path_without_namespacing.hpp
third_party/rust/bindgen/tests/headers/class.hpp
third_party/rust/bindgen/tests/headers/class_nested.hpp
third_party/rust/bindgen/tests/headers/class_no_members.hpp
third_party/rust/bindgen/tests/headers/class_static.hpp
third_party/rust/bindgen/tests/headers/class_static_const.hpp
third_party/rust/bindgen/tests/headers/class_use_as.hpp
third_party/rust/bindgen/tests/headers/class_with_dtor.hpp
third_party/rust/bindgen/tests/headers/class_with_inner_struct.hpp
third_party/rust/bindgen/tests/headers/class_with_typedef.hpp
third_party/rust/bindgen/tests/headers/complex.h
third_party/rust/bindgen/tests/headers/complex_global.h
third_party/rust/bindgen/tests/headers/const_array_fn_arg.h
third_party/rust/bindgen/tests/headers/const_bool.hpp
third_party/rust/bindgen/tests/headers/const_enum_unnamed.hpp
third_party/rust/bindgen/tests/headers/const_ptr.hpp
third_party/rust/bindgen/tests/headers/const_resolved_ty.h
third_party/rust/bindgen/tests/headers/const_tparam.hpp
third_party/rust/bindgen/tests/headers/constant-evaluate.h
third_party/rust/bindgen/tests/headers/constant-non-specialized-tp.hpp
third_party/rust/bindgen/tests/headers/constify-all-enums.h
third_party/rust/bindgen/tests/headers/constify-enum.h
third_party/rust/bindgen/tests/headers/constructor-tp.hpp
third_party/rust/bindgen/tests/headers/constructors.hpp
third_party/rust/bindgen/tests/headers/convert-floats.h
third_party/rust/bindgen/tests/headers/crtp.hpp
third_party/rust/bindgen/tests/headers/dash_language.h
third_party/rust/bindgen/tests/headers/decl_extern_int_twice.h
third_party/rust/bindgen/tests/headers/decl_ptr_to_array.h
third_party/rust/bindgen/tests/headers/disable-namespacing.hpp
third_party/rust/bindgen/tests/headers/duplicated-namespaces-definitions.hpp
third_party/rust/bindgen/tests/headers/duplicated-namespaces.hpp
third_party/rust/bindgen/tests/headers/duplicated_constants_in_ns.hpp
third_party/rust/bindgen/tests/headers/elaborated.hpp
third_party/rust/bindgen/tests/headers/empty_template_param_name.hpp
third_party/rust/bindgen/tests/headers/enum.h
third_party/rust/bindgen/tests/headers/enum_alias.hpp
third_party/rust/bindgen/tests/headers/enum_and_vtable_mangling.hpp
third_party/rust/bindgen/tests/headers/enum_dupe.h
third_party/rust/bindgen/tests/headers/enum_explicit_type.hpp
third_party/rust/bindgen/tests/headers/enum_in_template_with_typedef.hpp
third_party/rust/bindgen/tests/headers/enum_negative.h
third_party/rust/bindgen/tests/headers/enum_packed.h
third_party/rust/bindgen/tests/headers/eval-variadic-template-parameter.hpp
third_party/rust/bindgen/tests/headers/extern.hpp
third_party/rust/bindgen/tests/headers/float128.hpp
third_party/rust/bindgen/tests/headers/forward-declaration-autoptr.hpp
third_party/rust/bindgen/tests/headers/forward-inherit-struct-with-fields.hpp
third_party/rust/bindgen/tests/headers/forward-inherit-struct.hpp
third_party/rust/bindgen/tests/headers/forward_declared_complex_types.hpp
third_party/rust/bindgen/tests/headers/forward_declared_struct.h
third_party/rust/bindgen/tests/headers/func_proto.h
third_party/rust/bindgen/tests/headers/func_ptr.h
third_party/rust/bindgen/tests/headers/func_ptr_in_struct.h
third_party/rust/bindgen/tests/headers/func_with_array_arg.h
third_party/rust/bindgen/tests/headers/func_with_func_ptr_arg.h
third_party/rust/bindgen/tests/headers/in_class_typedef.hpp
third_party/rust/bindgen/tests/headers/inherit-namespaced.hpp
third_party/rust/bindgen/tests/headers/inherit_named.hpp
third_party/rust/bindgen/tests/headers/inherit_typedef.hpp
third_party/rust/bindgen/tests/headers/inline-function.h
third_party/rust/bindgen/tests/headers/inline_namespace.hpp
third_party/rust/bindgen/tests/headers/inline_namespace_conservative.hpp
third_party/rust/bindgen/tests/headers/inline_namespace_whitelist.hpp
third_party/rust/bindgen/tests/headers/inner_const.hpp
third_party/rust/bindgen/tests/headers/inner_template_self.hpp
third_party/rust/bindgen/tests/headers/int128_t.h
third_party/rust/bindgen/tests/headers/issue-358.hpp
third_party/rust/bindgen/tests/headers/issue-372.hpp
third_party/rust/bindgen/tests/headers/issue-410.hpp
third_party/rust/bindgen/tests/headers/issue-446.hpp
third_party/rust/bindgen/tests/headers/issue-447.hpp
third_party/rust/bindgen/tests/headers/issue-493.hpp
third_party/rust/bindgen/tests/headers/issue_311.hpp
third_party/rust/bindgen/tests/headers/issue_315.hpp
third_party/rust/bindgen/tests/headers/jsval_layout_opaque.hpp
third_party/rust/bindgen/tests/headers/keywords.h
third_party/rust/bindgen/tests/headers/layout.h
third_party/rust/bindgen/tests/headers/layout_align.h
third_party/rust/bindgen/tests/headers/layout_arp.h
third_party/rust/bindgen/tests/headers/layout_array.h
third_party/rust/bindgen/tests/headers/layout_cmdline_token.h
third_party/rust/bindgen/tests/headers/layout_eth_conf.h
third_party/rust/bindgen/tests/headers/layout_kni_mbuf.h
third_party/rust/bindgen/tests/headers/layout_mbuf.h
third_party/rust/bindgen/tests/headers/macro-expr-basic.h
third_party/rust/bindgen/tests/headers/macro-redef.h
third_party/rust/bindgen/tests/headers/macro_const.h
third_party/rust/bindgen/tests/headers/maddness-is-avoidable.hpp
third_party/rust/bindgen/tests/headers/method-mangling.hpp
third_party/rust/bindgen/tests/headers/module-whitelisted.hpp
third_party/rust/bindgen/tests/headers/msvc-no-usr.hpp
third_party/rust/bindgen/tests/headers/multiple-inherit-empty-correct-layout.hpp
third_party/rust/bindgen/tests/headers/mutable.hpp
third_party/rust/bindgen/tests/headers/namespace.hpp
third_party/rust/bindgen/tests/headers/nested.hpp
third_party/rust/bindgen/tests/headers/nested_vtable.hpp
third_party/rust/bindgen/tests/headers/nested_within_namespace.hpp
third_party/rust/bindgen/tests/headers/no-comments.h
third_party/rust/bindgen/tests/headers/no-derive-debug.h
third_party/rust/bindgen/tests/headers/no-derive-default.h
third_party/rust/bindgen/tests/headers/no-recursive-whitelisting.h
third_party/rust/bindgen/tests/headers/no-std.h
third_party/rust/bindgen/tests/headers/no_copy.hpp
third_party/rust/bindgen/tests/headers/nsStyleAutoArray.hpp
third_party/rust/bindgen/tests/headers/objc_interface.h
third_party/rust/bindgen/tests/headers/objc_interface_type.h
third_party/rust/bindgen/tests/headers/objc_method.h
third_party/rust/bindgen/tests/headers/only_bitfields.hpp
third_party/rust/bindgen/tests/headers/opaque-tracing.hpp
third_party/rust/bindgen/tests/headers/opaque_in_struct.hpp
third_party/rust/bindgen/tests/headers/opaque_pointer.hpp
third_party/rust/bindgen/tests/headers/opaque_typedef.hpp
third_party/rust/bindgen/tests/headers/overflowed_enum.hpp
third_party/rust/bindgen/tests/headers/overloading.hpp
third_party/rust/bindgen/tests/headers/private.hpp
third_party/rust/bindgen/tests/headers/public-dtor.hpp
third_party/rust/bindgen/tests/headers/redeclaration.hpp
third_party/rust/bindgen/tests/headers/ref_argument_array.hpp
third_party/rust/bindgen/tests/headers/reparented_replacement.hpp
third_party/rust/bindgen/tests/headers/replace_template_alias.hpp
third_party/rust/bindgen/tests/headers/replace_use.hpp
third_party/rust/bindgen/tests/headers/replaces_double.hpp
third_party/rust/bindgen/tests/headers/resolved_type_def_function.h
third_party/rust/bindgen/tests/headers/same_struct_name_in_different_namespaces.hpp
third_party/rust/bindgen/tests/headers/size_t_template.hpp
third_party/rust/bindgen/tests/headers/struct_containing_forward_declared_struct.h
third_party/rust/bindgen/tests/headers/struct_typedef.h
third_party/rust/bindgen/tests/headers/struct_typedef_ns.hpp
third_party/rust/bindgen/tests/headers/struct_with_anon_struct.h
third_party/rust/bindgen/tests/headers/struct_with_anon_struct_array.h
third_party/rust/bindgen/tests/headers/struct_with_anon_struct_pointer.h
third_party/rust/bindgen/tests/headers/struct_with_anon_union.h
third_party/rust/bindgen/tests/headers/struct_with_anon_unnamed_struct.h
third_party/rust/bindgen/tests/headers/struct_with_anon_unnamed_union.h
third_party/rust/bindgen/tests/headers/struct_with_bitfields.h
third_party/rust/bindgen/tests/headers/struct_with_derive_debug.h
third_party/rust/bindgen/tests/headers/struct_with_nesting.h
third_party/rust/bindgen/tests/headers/struct_with_packing.h
third_party/rust/bindgen/tests/headers/struct_with_struct.h
third_party/rust/bindgen/tests/headers/struct_with_typedef_template_arg.hpp
third_party/rust/bindgen/tests/headers/template-fun-ty.hpp
third_party/rust/bindgen/tests/headers/template.hpp
third_party/rust/bindgen/tests/headers/template_alias.hpp
third_party/rust/bindgen/tests/headers/template_alias_basic.hpp
third_party/rust/bindgen/tests/headers/template_alias_namespace.hpp
third_party/rust/bindgen/tests/headers/template_partial_specification.hpp
third_party/rust/bindgen/tests/headers/template_typedef_transitive_param.hpp
third_party/rust/bindgen/tests/headers/template_typedefs.hpp
third_party/rust/bindgen/tests/headers/templateref_opaque.hpp
third_party/rust/bindgen/tests/headers/type-referenced-by-whitelisted-function.h
third_party/rust/bindgen/tests/headers/type_alias_empty.hpp
third_party/rust/bindgen/tests/headers/type_alias_partial_template_especialization.hpp
third_party/rust/bindgen/tests/headers/type_alias_template_specialized.hpp
third_party/rust/bindgen/tests/headers/typedefd-array-as-function-arg.h
third_party/rust/bindgen/tests/headers/typeref.hpp
third_party/rust/bindgen/tests/headers/union-in-ns.hpp
third_party/rust/bindgen/tests/headers/union_dtor.hpp
third_party/rust/bindgen/tests/headers/union_fields.hpp
third_party/rust/bindgen/tests/headers/union_template.hpp
third_party/rust/bindgen/tests/headers/union_with_anon_struct.h
third_party/rust/bindgen/tests/headers/union_with_anon_struct_bitfield.h
third_party/rust/bindgen/tests/headers/union_with_anon_union.h
third_party/rust/bindgen/tests/headers/union_with_anon_unnamed_struct.h
third_party/rust/bindgen/tests/headers/union_with_anon_unnamed_union.h
third_party/rust/bindgen/tests/headers/union_with_big_member.h
third_party/rust/bindgen/tests/headers/union_with_nesting.h
third_party/rust/bindgen/tests/headers/unknown_attr.h
third_party/rust/bindgen/tests/headers/use-core.h
third_party/rust/bindgen/tests/headers/using.hpp
third_party/rust/bindgen/tests/headers/var-tracing.hpp
third_party/rust/bindgen/tests/headers/variadic-method.hpp
third_party/rust/bindgen/tests/headers/variadic_template_function.hpp
third_party/rust/bindgen/tests/headers/vector.hpp
third_party/rust/bindgen/tests/headers/virtual_dtor.hpp
third_party/rust/bindgen/tests/headers/virtual_inheritance.hpp
third_party/rust/bindgen/tests/headers/virtual_overloaded.hpp
third_party/rust/bindgen/tests/headers/vtable_recursive_sig.hpp
third_party/rust/bindgen/tests/headers/weird_bitfields.hpp
third_party/rust/bindgen/tests/headers/what_is_going_on.hpp
third_party/rust/bindgen/tests/headers/whitelist-namespaces-basic.hpp
third_party/rust/bindgen/tests/headers/whitelist-namespaces.hpp
third_party/rust/bindgen/tests/headers/whitelist_basic.hpp
third_party/rust/bindgen/tests/headers/whitelist_fix.hpp
third_party/rust/bindgen/tests/headers/whitelist_vars.h
third_party/rust/bindgen/tests/tests.rs
third_party/rust/bindgen/tests/uses/.gitignore
third_party/rust/clang-sys/ci/before_install.sh
third_party/rust/clang-sys/ci/script.sh
third_party/rust/dtoa/.cargo-checksum.json
third_party/rust/dtoa/.cargo-ok
third_party/rust/dtoa/.gitignore
third_party/rust/dtoa/.travis.yml
third_party/rust/dtoa/Cargo.toml
third_party/rust/dtoa/LICENSE-APACHE
third_party/rust/dtoa/LICENSE-MIT
third_party/rust/dtoa/README.md
third_party/rust/dtoa/benches/bench.rs
third_party/rust/dtoa/performance.png
third_party/rust/dtoa/src/diyfp.rs
third_party/rust/dtoa/src/dtoa.rs
third_party/rust/dtoa/src/lib.rs
third_party/rust/dtoa/tests/test.rs
third_party/rust/itoa/.cargo-checksum.json
third_party/rust/itoa/.cargo-ok
third_party/rust/itoa/.gitignore
third_party/rust/itoa/.travis.yml
third_party/rust/itoa/Cargo.toml
third_party/rust/itoa/LICENSE-APACHE
third_party/rust/itoa/LICENSE-MIT
third_party/rust/itoa/README.md
third_party/rust/itoa/benches/bench.rs
third_party/rust/itoa/performance.png
third_party/rust/itoa/src/lib.rs
third_party/rust/itoa/tests/test.rs
third_party/rust/rayon/src/api.rs
third_party/rust/rayon/src/job.rs
third_party/rust/rayon/src/latch.rs
third_party/rust/rayon/src/log.rs
third_party/rust/rayon/src/par_iter/README.md
third_party/rust/rayon/src/par_iter/chain.rs
third_party/rust/rayon/src/par_iter/collect/consumer.rs
third_party/rust/rayon/src/par_iter/collect/mod.rs
third_party/rust/rayon/src/par_iter/collections.rs
third_party/rust/rayon/src/par_iter/enumerate.rs
third_party/rust/rayon/src/par_iter/filter.rs
third_party/rust/rayon/src/par_iter/filter_map.rs
third_party/rust/rayon/src/par_iter/find.rs
third_party/rust/rayon/src/par_iter/flat_map.rs
third_party/rust/rayon/src/par_iter/fold.rs
third_party/rust/rayon/src/par_iter/for_each.rs
third_party/rust/rayon/src/par_iter/from_par_iter.rs
third_party/rust/rayon/src/par_iter/internal.rs
third_party/rust/rayon/src/par_iter/len.rs
third_party/rust/rayon/src/par_iter/map.rs
third_party/rust/rayon/src/par_iter/misc.rs
third_party/rust/rayon/src/par_iter/mod.rs
third_party/rust/rayon/src/par_iter/noop.rs
third_party/rust/rayon/src/par_iter/option.rs
third_party/rust/rayon/src/par_iter/range.rs
third_party/rust/rayon/src/par_iter/reduce.rs
third_party/rust/rayon/src/par_iter/skip.rs
third_party/rust/rayon/src/par_iter/slice.rs
third_party/rust/rayon/src/par_iter/slice_mut.rs
third_party/rust/rayon/src/par_iter/string.rs
third_party/rust/rayon/src/par_iter/take.rs
third_party/rust/rayon/src/par_iter/test.rs
third_party/rust/rayon/src/par_iter/vec.rs
third_party/rust/rayon/src/par_iter/weight.rs
third_party/rust/rayon/src/par_iter/zip.rs
third_party/rust/rayon/src/scope/mod.rs
third_party/rust/rayon/src/scope/test.rs
third_party/rust/rayon/src/thread_pool.rs
third_party/rust/rayon/src/unwind.rs
third_party/rust/rayon/src/util.rs
third_party/rust/rayon/tests/compile-fail-unstable/scope_join_bad.rs
third_party/rust/rayon/tests/run-pass-unstable/scope_join.rs
third_party/rust/rayon/tests/run-pass/init_fail_thread_number_not_equal.rs
third_party/rust/rayon/tests/run-pass/init_fail_zero_threads.rs
third_party/rust/serde_json/.cargo-checksum.json
third_party/rust/serde_json/.cargo-ok
third_party/rust/serde_json/Cargo.toml
third_party/rust/serde_json/LICENSE-APACHE
third_party/rust/serde_json/LICENSE-MIT
third_party/rust/serde_json/README.md
third_party/rust/serde_json/src/de.rs
third_party/rust/serde_json/src/error.rs
third_party/rust/serde_json/src/lib.rs
third_party/rust/serde_json/src/macros.rs
third_party/rust/serde_json/src/map.rs
third_party/rust/serde_json/src/number.rs
third_party/rust/serde_json/src/read.rs
third_party/rust/serde_json/src/ser.rs
third_party/rust/serde_json/src/value.rs
third_party/rust/syntex_syntax/src/ext/proc_macro_shim.rs
third_party/rust/target_build_utils/.cargo-checksum.json
third_party/rust/target_build_utils/.cargo-ok
third_party/rust/target_build_utils/.gitignore
third_party/rust/target_build_utils/.travis.yml
third_party/rust/target_build_utils/Cargo.toml
third_party/rust/target_build_utils/README.md
third_party/rust/target_build_utils/appveyor.yml
third_party/rust/target_build_utils/build.rs
third_party/rust/target_build_utils/src/changelog.rs
third_party/rust/target_build_utils/src/lib.rs
third_party/rust/target_build_utils/src/my-great-target.json
--- a/.eslintignore
+++ b/.eslintignore
@@ -4,17 +4,16 @@
 # 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
 # below.
 addon-sdk/**
 build/**
-caps/**
 chrome/**
 config/**
 db/**
 docshell/**
 editor/**
 embedding/**
 extensions/**
 gfx/**
@@ -73,16 +72,19 @@ browser/extensions/pocket/content/panels
 browser/extensions/pocket/content/panels/js/vendor/**
 browser/locales/**
 # generated or library files in activity-stream
 browser/extensions/activity-stream/data/content/activity-stream.bundle.js
 browser/extensions/activity-stream/vendor/**
 # imported from chromium
 browser/extensions/mortar/**
 
+# caps/ exclusions
+caps/tests/mochitest/browser_checkloaduri.js
+
 # devtools/ exclusions
 devtools/client/canvasdebugger/**
 devtools/client/commandline/**
 devtools/client/debugger/**
 devtools/client/framework/**
 !devtools/client/framework/devtools.js
 !devtools/client/framework/devtools-browser.js
 !devtools/client/framework/selection.js
@@ -122,21 +124,16 @@ devtools/client/webaudioeditor/**
 devtools/client/webconsole/net/**
 devtools/client/webconsole/test/**
 devtools/client/webconsole/console-output.js
 devtools/client/webconsole/hudservice.js
 devtools/client/webconsole/webconsole-connection-proxy.js
 devtools/client/webconsole/webconsole.js
 devtools/client/webide/**
 !devtools/client/webide/components/webideCli.js
-devtools/server/actors/webconsole.js
-devtools/server/actors/object.js
-devtools/server/actors/script.js
-devtools/server/actors/styleeditor.js
-devtools/server/actors/stylesheets.js
 devtools/server/tests/browser/storage-*.html
 !devtools/server/tests/browser/storage-unsecured-iframe.html
 devtools/server/tests/browser/stylesheets-nested-iframes.html
 devtools/server/tests/unit/xpcshell_debugging_script.js
 devtools/shared/platform/content/test/test_clipboard.html
 devtools/shared/qrcode/tests/mochitest/test_decode.html
 devtools/shared/tests/mochitest/*.html
 devtools/shared/webconsole/test/test_*.html
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -10,21 +10,21 @@ module.exports = {
     "mozilla/avoid-nsISupportsString-preferences": "error",
     "mozilla/import-browser-window-globals": "error",
     "mozilla/import-globals": "warn",
     "mozilla/no-import-into-var-and-global": "error",
     "mozilla/no-useless-parameters": "error",
     "mozilla/no-useless-removeEventListener": "error",
     "mozilla/use-default-preference-values": "error",
     "mozilla/use-ownerGlobal": "error",
-
     // No (!foo in bar) or (!object instanceof Class)
     "no-unsafe-negation": "error",
     // No eval() and no strings in the first param of setTimeout or setInterval
     "no-implied-eval": "error",
+    "no-eval": "error",
   },
   "env": {
     "es6": true
   },
   "parserOptions": {
     "ecmaVersion": 8,
   },
 };
--- a/accessible/tests/mochitest/treeupdate/test_ariaowns.html
+++ b/accessible/tests/mochitest/treeupdate/test_ariaowns.html
@@ -671,16 +671,68 @@
         testAccessibleTree("t9_container", tree);
       }
 
       this.getID = () => {
         return `Set ARIA owns on a document (part3)`;
       }
     }
 
+    /**
+     * Put ARIA owned child back when ARIA owner removed.
+     */
+    function test10_removeARIAOwner()
+    {
+      this.eventSeq = [
+        new invokerChecker(EVENT_HIDE, getAccessible('t10_owner'))
+      ];
+
+      this.invoke = () => {
+        let tree =
+          { SECTION: [ // t10_container
+            { SECTION: [ // t10_owner
+              { ENTRY: [] } // t10_child
+            ] }
+          ] };
+        testAccessibleTree('t10_container', tree);
+
+        getNode('t10_owner').remove();
+      }
+
+      this.getID = () => {
+        return 'Put aria owned child back when aria owner removed';
+      }
+    }
+
+    function test10_finishTest()
+    {
+      this.eventSeq = [
+        new invokerChecker(EVENT_REORDER, 't10_container')
+      ];
+
+      this.invoke = () => {
+        // trigger a tree update.
+        getNode('t10_container').append(document.createElement('p'));
+      }
+
+      this.finalCheck = () => {
+        let tree =
+          { SECTION: [ // t10_container
+            // { ENTRY: [] }, // t10_child
+            { PARAGRAPH: [] }
+          ] };
+        testAccessibleTree('t10_container', tree);
+        todo(false, 'Input accessible has be moved back in the tree');
+      }
+
+      this.getID = () => {
+        return `Put aria owned child back when aria owner removed (finish test)`;
+      }
+    }
+
     ////////////////////////////////////////////////////////////////////////////
     // Test
     ////////////////////////////////////////////////////////////////////////////
 
     //gA11yEventDumpToConsole = true;
     //enableLogging("tree,eventTree,verbose"); // debug stuff
 
     var gQueue = null;
@@ -723,16 +775,19 @@
 
       gQueue.push(new setARIAOwnsOnElToRemove("t7_parent", "t7_child"));
 
       gQueue.push(new test8());
       gQueue.push(new test9_prepare());
       gQueue.push(new test9_setARIAOwns());
       gQueue.push(new test9_finish());
 
+      gQueue.push(new test10_removeARIAOwner());
+      gQueue.push(new test10_finishTest());
+
       gQueue.invoke(); // SimpleTest.finish() will be called in the end
     }
 
     SimpleTest.waitForExplicitFinish();
     addA11yLoadEvent(doTest);
 
   </script>
 </head>
@@ -787,11 +842,16 @@
     </div>
   </div>
 
   <div id="t8_container">
     <input id="t8_button" type="button"><span id="t8_span"><input><input><input></span>
   </div>
 
   <iframe id="t9_container"></iframe>
+
+  <div id="t10_container">
+    <div id="t10_owner" aria-owns="t10_child"></div>
+    <input id="t10_child">
+  </div>
 </body>
 
 </html>
--- a/browser/app/blocklist.xml
+++ b/browser/app/blocklist.xml
@@ -112,16 +112,20 @@
     <emItem blockID="i523" id="/^({7e8a1050-cf67-4575-92df-dcc60e7d952d}|{b3420a9c-a397-4409-b90d-bcf22da1a08a}|{eca6641f-2176-42ba-bdbe-f3e327f8e0af}|{707dca12-3f99-4d94-afea-06dcc0ae0108}|{aea20431-87fc-40be-bc5b-18066fe2819c}|{30ee6676-1ba6-455a-a7e8-298fa863a546})$/">
       <prefs/>
       <versionRange minVersion="0" maxVersion="*" severity="1"/>
     </emItem>
     <emItem blockID="i732" id="{e935dd68-f90d-46a6-b89e-c4657534b353}">
       <prefs/>
       <versionRange minVersion="0" maxVersion="*" severity="3"/>
     </emItem>
+    <emItem blockID="edad04eb-ea16-42f3-a4a7-20dded33cc37" id="@safesearchscoutee">
+      <prefs/>
+      <versionRange minVersion="0" maxVersion="*" severity="3"/>
+    </emItem>
     <emItem blockID="i436" id="/(\{7aeae561-714b-45f6-ace3-4a8aed6e227b\})|(\{01e86e69-a2f8-48a0-b068-83869bdba3d0\})|(\{77f5fe49-12e3-4cf5-abb4-d993a0164d9e\})/">
       <prefs/>
       <versionRange minVersion="0" maxVersion="*" severity="1"/>
     </emItem>
     <emItem blockID="i700" id="2bbadf1f-a5af-499f-9642-9942fcdb7c76@f05a14cc-8842-4eee-be17-744677a917ed.com">
       <prefs/>
       <versionRange minVersion="0" maxVersion="*" severity="1"/>
     </emItem>
@@ -176,16 +180,20 @@
     <emItem blockID="i626" id="{20AD702C-661E-4534-8CE9-BA4EC9AD6ECC}">
       <prefs/>
       <versionRange minVersion="0" maxVersion="*" severity="3"/>
     </emItem>
     <emItem blockID="i334" id="{0F827075-B026-42F3-885D-98981EE7B1AE}">
       <prefs/>
       <versionRange minVersion="0" maxVersion="*" severity="3"/>
     </emItem>
+    <emItem blockID="3fd71895-7fc6-4f3f-aa22-1cbb0c5fd922" id="/^({95E84BD3-3604-4AAC-B2CA-D9AC3E55B64B}|{E3605470-291B-44EB-8648-745EE356599A}|{95E5E0AD-65F9-4FFC-A2A2-0008DCF6ED25}|{FF20459C-DA6E-41A7-80BC-8F4FEFD9C575}|{6E727987-C8EA-44DA-8749-310C0FBE3C3E}|{12E8A6C2-B125-479F-AB3C-13B8757C7F04}|{EB6628CF-0675-4DAE-95CE-EFFA23169743})$/">
+      <prefs/>
+      <versionRange minVersion="0" maxVersion="*" severity="3"/>
+    </emItem>
     <emItem blockID="i716" id="{cc6cc772-f121-49e0-b1f0-c26583cb0c5e}">
       <prefs/>
       <versionRange minVersion="0" maxVersion="*" severity="3"/>
     </emItem>
     <emItem blockID="i501" id="xivars@aol.com">
       <prefs/>
       <versionRange minVersion="0" maxVersion="*" severity="3"/>
     </emItem>
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -355,22 +355,16 @@ pref("browser.download.loglevel", "Error
 // feedback from their action.
 pref("browser.download.saveLinkAsFilenameTimeout", 4000);
 
 pref("browser.download.useDownloadDir", true);
 pref("browser.download.folderList", 1);
 pref("browser.download.manager.addToRecentDocs", true);
 pref("browser.download.manager.resumeOnWakeDelay", 10000);
 
-#ifdef RELEASE_OR_BETA
-pref("browser.download.showPanelDropmarker", false);
-#else
-pref("browser.download.showPanelDropmarker", true);
-#endif
-
 // This allows disabling the animated notifications shown by
 // the Downloads Indicator when a download starts or completes.
 pref("browser.download.animateNotifications", true);
 
 // This records whether or not the panel has been shown at least once.
 pref("browser.download.panel.shown", false);
 
 #ifndef XP_MACOSX
@@ -1153,17 +1147,16 @@ pref("services.sync.prefs.sync.browser.u
 pref("services.sync.prefs.sync.browser.urlbar.suggest.searches", true);
 pref("services.sync.prefs.sync.dom.disable_open_during_load", true);
 pref("services.sync.prefs.sync.dom.disable_window_flip", true);
 pref("services.sync.prefs.sync.dom.disable_window_move_resize", true);
 pref("services.sync.prefs.sync.dom.event.contextmenu.enabled", true);
 pref("services.sync.prefs.sync.extensions.personas.current", true);
 pref("services.sync.prefs.sync.extensions.update.enabled", true);
 pref("services.sync.prefs.sync.intl.accept_languages", true);
-pref("services.sync.prefs.sync.javascript.enabled", true);
 pref("services.sync.prefs.sync.layout.spellcheckDefault", true);
 pref("services.sync.prefs.sync.lightweightThemes.selectedThemeID", true);
 pref("services.sync.prefs.sync.lightweightThemes.usedThemes", true);
 pref("services.sync.prefs.sync.network.cookie.cookieBehavior", true);
 pref("services.sync.prefs.sync.network.cookie.lifetimePolicy", true);
 pref("services.sync.prefs.sync.network.cookie.lifetime.days", true);
 pref("services.sync.prefs.sync.network.cookie.thirdparty.sessionOnly", true);
 pref("services.sync.prefs.sync.permissions.default.image", true);
--- a/browser/base/content/browser-sync.js
+++ b/browser/base/content/browser-sync.js
@@ -3,20 +3,16 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 Cu.import("resource://services-sync/UIState.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "EnsureFxAccountsWebChannel",
   "resource://gre/modules/FxAccountsWebChannel.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "Weave",
   "resource://services-sync/main.js");
-if (AppConstants.MOZ_SERVICES_CLOUDSYNC) {
-  XPCOMUtils.defineLazyModuleGetter(this, "CloudSync",
-                                    "resource://gre/modules/CloudSync.jsm");
-}
 
 const MIN_STATUS_ANIMATION_DURATION = 1600;
 
 var gSync = {
   _initialized: false,
   // The last sync start time. Used to calculate the leftover animation time
   // once syncing completes (bug 1239042).
   _syncStartTime: 0,
@@ -218,19 +214,17 @@ var gSync = {
   updateStateBroadcasters(state) {
     const status = state.status;
 
     // Start off with a clean slate
     document.getElementById("sync-reauth-state").hidden = true;
     document.getElementById("sync-setup-state").hidden = true;
     document.getElementById("sync-syncnow-state").hidden = true;
 
-    if (CloudSync && CloudSync.ready && CloudSync().adapters.count) {
-      document.getElementById("sync-syncnow-state").hidden = false;
-    } else if (status == UIState.STATUS_LOGIN_FAILED) {
+    if (status == UIState.STATUS_LOGIN_FAILED) {
       // unhiding this element makes the menubar show the login failure state.
       document.getElementById("sync-reauth-state").hidden = false;
     } else if (status == UIState.STATUS_NOT_CONFIGURED ||
                status == UIState.STATUS_NOT_VERIFIED) {
       document.getElementById("sync-setup-state").hidden = false;
     } else {
       document.getElementById("sync-syncnow-state").hidden = false;
     }
--- a/browser/base/content/browser.css
+++ b/browser/base/content/browser.css
@@ -7,16 +7,17 @@
 @namespace svg url("http://www.w3.org/2000/svg");
 
 :root {
   --identity-popup-expander-width: 38px;
   --panelui-subview-transition-duration: 150ms;
   --lwt-additional-images: none;
   --lwt-background-alignment: right top;
   --lwt-background-tiling: no-repeat;
+  --animation-easing-function: cubic-bezier(.07, .95, 0, 1);
 }
 
 :root:-moz-lwtheme {
   color: var(--lwt-text-color) !important;
 }
 
 :root:-moz-lwtheme:not([customization-lwtheme]) {
   background-color: var(--lwt-accent-color) !important;
@@ -188,18 +189,19 @@ tabbrowser {
 }
 
 .tabbrowser-tabs[movingtab] > .tabbrowser-tab[selected] {
   position: relative;
   z-index: 2;
   pointer-events: none; /* avoid blocking dragover events on scroll buttons */
 }
 
+.tabbrowser-tab[tabdrop-samewindow],
 .tabbrowser-tabs[movingtab] > .tabbrowser-tab[fadein]:not([selected]) {
-  transition: transform 200ms ease-out;
+  transition: transform 200ms var(--animation-easing-function);
 }
 
 .new-tab-popup,
 #alltabs-popup {
   -moz-binding: url("chrome://browser/content/tabbrowser.xml#tabbrowser-alltabs-popup");
 }
 
 toolbar[printpreview="true"] {
@@ -992,27 +994,16 @@ notification[value="translation"] {
 toolbarpaletteitem[place="palette"] > #downloads-button[indicator] > .toolbarbutton-badge-stack > image.toolbarbutton-icon {
   display: -moz-box;
 }
 
 toolbarpaletteitem[place="palette"] > #downloads-button[indicator] > #downloads-indicator-anchor {
   display: none;
 }
 
-#downloads-button.withProgressBar:-moz-any([progress], [counter], [paused]) #downloads-indicator-icon,
-#downloads-button:not(:-moz-any([progress], [counter], [paused])) #downloads-indicator-progress-area {
-  visibility: hidden;
-}
-
-/* Hide elements for another type of progressmeter if it's not in use. */
-#downloads-button.withProgressBar #downloads-indicator-progress-icon,
-#downloads-button:not(.withProgressBar) #downloads-indicator-progress-area {
-  visibility: hidden;
-}
-
 /* Combobox dropdown renderer */
 #ContentSelectDropdown > menupopup {
   /* The menupopup itself should always be rendered LTR to ensure the scrollbar aligns with
    * the dropdown arrow on the dropdown widget. If a menuitem is RTL, its style will be set accordingly */
   direction: ltr;
 }
 
 /* Indent options in optgroups */
--- a/browser/base/content/nsContextMenu.js
+++ b/browser/base/content/nsContextMenu.js
@@ -51,16 +51,24 @@ function openContextMenu(aMessage) {
                               selectionInfo: data.selectionInfo,
                               disableSetDesktopBackground: data.disableSetDesktopBg,
                               loginFillInfo: data.loginFillInfo,
                               parentAllowsMixedContent: data.parentAllowsMixedContent,
                               userContextId: data.userContextId,
                             };
   let popup = browser.ownerDocument.getElementById("contentAreaContextMenu");
   let event = gContextMenuContentData.event;
+
+  // Set touch mode to get larger menu items.
+  if (event.mozInputSource == MouseEvent.MOZ_SOURCE_TOUCH) {
+    popup.setAttribute("touchmode", "true");
+  } else {
+    popup.removeAttribute("touchmode");
+  }
+
   popup.openPopupAtScreen(event.screenX, event.screenY, true);
 }
 
 function nsContextMenu(aXulMenu, aIsShift) {
   this.shouldDisplay = true;
   this.initMenu(aXulMenu, aIsShift);
 }
 
--- a/browser/base/content/sync/aboutSyncTabs.js
+++ b/browser/base/content/sync/aboutSyncTabs.js
@@ -12,40 +12,31 @@ Cu.import("resource:///modules/PlacesUIU
 Cu.import("resource://gre/modules/AppConstants.jsm");
 Cu.import("resource://gre/modules/PlacesUtils.jsm", this);
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "Promise",
                                   "resource://gre/modules/Promise.jsm");
 
-if (AppConstants.MOZ_SERVICES_CLOUDSYNC) {
-  XPCOMUtils.defineLazyModuleGetter(this, "CloudSync",
-                                    "resource://gre/modules/CloudSync.jsm");
-}
-
 var RemoteTabViewer = {
   _tabsList: null,
 
   init() {
     Services.obs.addObserver(this, "weave:service:login:finish");
     Services.obs.addObserver(this, "weave:engine:sync:finish");
 
-    Services.obs.addObserver(this, "cloudsync:tabs:update");
-
     this._tabsList = document.getElementById("tabsList");
 
     this.buildList(true);
   },
 
   uninit() {
     Services.obs.removeObserver(this, "weave:service:login:finish");
     Services.obs.removeObserver(this, "weave:engine:sync:finish");
-
-    Services.obs.removeObserver(this, "cloudsync:tabs:update");
   },
 
   createItem(attrs) {
     let item = document.createElement("richlistitem");
 
     // Copy the attributes from the argument into the item.
     for (let attr in attrs) {
       item.setAttribute(attr, attrs[attr]);
@@ -173,28 +164,19 @@ var RemoteTabViewer = {
     if (Weave.Service.isLoggedIn) {
       this._refetchTabs(forceSync);
       this._generateWeaveTabList();
     } else {
       // XXXzpao We should say something about not being logged in & not having data
       //        or tell the appropriate condition. (bug 583344)
     }
 
-    let complete = () => {
-      this._waitingForBuildList = false;
-      if (this._buildListRequested) {
-        CommonUtils.nextTick(this.buildList, this);
-      }
-    }
-
-    if (CloudSync && CloudSync.ready && CloudSync().tabsReady && CloudSync().tabs.hasRemoteTabs()) {
-      this._generateCloudSyncTabList()
-          .then(complete, complete);
-    } else {
-      complete();
+    this._waitingForBuildList = false;
+    if (this._buildListRequested) {
+      CommonUtils.nextTick(this.buildList, this);
     }
   },
 
   _clearTabList() {
     let list = this._tabsList;
 
     // Clear out existing richlistitems.
     let count = list.getRowCount();
@@ -241,46 +223,16 @@ var RemoteTabViewer = {
           icon:  this.getIcon(icon),
         }
         let tab = this.createItem(attrs);
         list.appendChild(tab);
       }, this);
     }
   },
 
-  _generateCloudSyncTabList() {
-    let updateTabList = remoteTabs => {
-      let list = this._tabsList;
-
-      for (let client of remoteTabs) {
-        let clientAttrs = {
-          type: "client",
-          clientName: client.name,
-        };
-
-        let clientEnt = this.createItem(clientAttrs);
-        list.appendChild(clientEnt);
-
-        for (let tab of client.tabs) {
-          let tabAttrs = {
-            type: "tab",
-            title: tab.title,
-            url: tab.url,
-            icon: this.getIcon(tab.icon),
-          };
-          let tabEnt = this.createItem(tabAttrs);
-          list.appendChild(tabEnt);
-        }
-      }
-    };
-
-    return CloudSync().tabs.getRemoteTabs()
-                           .then(updateTabList, Promise.reject.bind(Promise));
-  },
-
   adjustContextMenu(event) {
     let mode = "all";
     switch (this._tabsList.selectedItems.length) {
       case 0:
         break;
       case 1:
         mode = "single"
         break;
@@ -330,19 +282,16 @@ var RemoteTabViewer = {
         break;
       case "weave:engine:sync:finish":
         if (data == "tabs") {
           // The tabs engine just finished, so re-build the list without
           // forcing a new sync of the tabs engine.
           this.buildList(false);
         }
         break;
-      case "cloudsync:tabs:update":
-        this.buildList(false);
-        break;
     }
   },
 
   handleClick(event) {
     if (event.target.getAttribute("type") != "tab") {
       return;
     }
 
--- a/browser/base/content/tabbrowser.css
+++ b/browser/base/content/tabbrowser.css
@@ -47,21 +47,21 @@
   overflow: hidden;
 }
 
 .tab-label-container[pinned] {
   width: 0;
 }
 
 .tab-label-container[textoverflow]:not([pinned]) {
-  mask-image: linear-gradient(to left, transparent, black 1em);
+  mask-image: linear-gradient(to left, transparent, black 2em);
 }
 
 .tab-label-container[textoverflow]:not([pinned]):-moz-locale-dir(rtl) {
-  mask-image: linear-gradient(to right, transparent, black 1em);
+  mask-image: linear-gradient(to right, transparent, black 2em);
 }
 
 .tab-stack {
   vertical-align: top; /* for pinned tabs */
 }
 
 tabpanels {
   background-color: transparent;
--- a/browser/base/content/tabbrowser.xml
+++ b/browser/base/content/tabbrowser.xml
@@ -6191,31 +6191,33 @@
           let pinned = draggedTab.pinned;
           let numPinned = this.tabbrowser._numPinnedTabs;
           let tabs = this.tabbrowser.visibleTabs
                                     .slice(pinned ? 0 : numPinned,
                                            pinned ? numPinned : undefined);
           if (rtl)
             tabs.reverse();
           let tabWidth = draggedTab.getBoundingClientRect().width;
+          draggedTab._dragData.tabWidth = tabWidth;
 
           // Move the dragged tab based on the mouse position.
 
           let leftTab = tabs[0];
           let rightTab = tabs[tabs.length - 1];
           let tabScreenX = draggedTab.boxObject.screenX;
           let translateX = screenX - draggedTab._dragData.screenX;
           if (!pinned)
             translateX += this.mTabstrip.scrollPosition - draggedTab._dragData.scrollX;
           let leftBound = leftTab.boxObject.screenX - tabScreenX;
           let rightBound = (rightTab.boxObject.screenX + rightTab.boxObject.width) -
                            (tabScreenX + tabWidth);
           translateX = Math.max(translateX, leftBound);
           translateX = Math.min(translateX, rightBound);
           draggedTab.style.transform = "translateX(" + translateX + "px)";
+          draggedTab._dragData.translateX = translateX;
 
           // Determine what tab we're dragging over.
           // * Point of reference is the center of the dragged tab. If that
           //   point touches a background tab, the dragged tab would take that
           //   tab's position when dropped.
           // * We're doing a binary search in order to reduce the amount of
           //   tabs we need to check.
 
@@ -6877,24 +6879,52 @@
         if (draggedTab && dropEffect == "copy") {
           // copy the dropped tab (wherever it's from)
           let newIndex = this._getDropIndex(event, false);
           let newTab = this.tabbrowser.duplicateTab(draggedTab);
           this.tabbrowser.moveTabTo(newTab, newIndex);
           if (draggedTab.parentNode != this || event.shiftKey)
             this.selectedItem = newTab;
         } else if (draggedTab && draggedTab.parentNode == this) {
-          this._finishAnimateTabMove();
-
-          // actually move the dragged tab
-          if ("animDropIndex" in draggedTab._dragData) {
-            let newIndex = draggedTab._dragData.animDropIndex;
-            if (newIndex > draggedTab._tPos)
-              newIndex--;
-            this.tabbrowser.moveTabTo(draggedTab, newIndex);
+          let oldTranslateX = draggedTab._dragData.translateX;
+          let tabWidth = draggedTab._dragData.tabWidth;
+          let translateOffset = oldTranslateX % tabWidth;
+          let newTranslateX = oldTranslateX - translateOffset;
+          if (oldTranslateX > 0 && translateOffset > tabWidth / 2) {
+            newTranslateX += tabWidth;
+          } else if (oldTranslateX < 0 && -translateOffset > tabWidth / 2) {
+            newTranslateX -= tabWidth;
+          }
+
+          let dropIndex = "animDropIndex" in draggedTab._dragData &&
+                          draggedTab._dragData.animDropIndex;
+          if (dropIndex && dropIndex > draggedTab._tPos)
+            dropIndex--;
+
+          if (oldTranslateX && oldTranslateX != newTranslateX) {
+            draggedTab.setAttribute("tabdrop-samewindow", "true");
+            draggedTab.style.transform = "translateX(" + newTranslateX + "px)";
+            let onTransitionEnd = transitionendEvent => {
+              if (transitionendEvent.propertyName != "transform" ||
+                  transitionendEvent.originalTarget != draggedTab) {
+                return;
+              }
+              draggedTab.removeEventListener("transitionend", onTransitionEnd);
+
+              draggedTab.removeAttribute("tabdrop-samewindow");
+
+              this._finishAnimateTabMove();
+              if (dropIndex !== false)
+                this.tabbrowser.moveTabTo(draggedTab, dropIndex);
+            }
+            draggedTab.addEventListener("transitionend", onTransitionEnd);
+          } else {
+            this._finishAnimateTabMove();
+            if (dropIndex !== false)
+              this.tabbrowser.moveTabTo(draggedTab, dropIndex);
           }
         } else if (draggedTab) {
           let newIndex = this._getDropIndex(event, false);
           this.tabbrowser.adoptTab(draggedTab, newIndex, true);
         } else {
           // Pass true to disallow dropping javascript: or data: urls
           let links;
           try {
@@ -6925,24 +6955,27 @@
         }
 
         if (draggedTab) {
           delete draggedTab._dragData;
         }
       ]]></handler>
 
       <handler event="dragend"><![CDATA[
-        // Note: while this case is correctly handled here, this event
-        // isn't dispatched when the tab is moved within the tabstrip,
-        // see bug 460801.
+        var dt = event.dataTransfer;
+        var draggedTab = dt.mozGetDataAt(TAB_DROP_TYPE, 0);
+
+        // Prevent this code from running if a tabdrop animation is
+        // running since calling _finishAnimateTabMove would clear
+        // any CSS transition that is running.
+        if (draggedTab.hasAttribute("tabdrop-samewindow"))
+          return;
 
         this._finishAnimateTabMove();
 
-        var dt = event.dataTransfer;
-        var draggedTab = dt.mozGetDataAt(TAB_DROP_TYPE, 0);
         if (dt.mozUserCancelled || dt.dropEffect != "none" || this._isCustomizing) {
           delete draggedTab._dragData;
           return;
         }
 
         // Disable detach within the browser toolbox
         var eX = event.screenX;
         var eY = event.screenY;
--- a/browser/base/content/test/general/browser_aboutHome.js
+++ b/browser/base/content/test/general/browser_aboutHome.js
@@ -563,16 +563,17 @@ function* withSnippetsMap(setupFn, testF
       }, function* (args) {
         return new Promise(resolve => {
           let document = content.document;
           // We're not using Promise-based listeners, because they resolve asynchronously.
           // The snippets test setup code relies on synchronous behaviour here.
           document.addEventListener("AboutHomeLoadSnippets", function() {
             let updateSnippets;
             if (args.setupFnSource) {
+              // eslint-disable-next-line no-eval
               updateSnippets = eval(`(() => (${args.setupFnSource}))()`);
             }
 
             content.wrappedJSObject.ensureSnippetsMapThen(snippetsMap => {
               snippetsMap = Cu.waiveXrays(snippetsMap);
               info("Got snippets map: " +
                    "{ last-update: " + snippetsMap.get("snippets-last-update") +
                    ", cached-version: " + snippetsMap.get("snippets-cached-version") +
--- a/browser/base/content/test/general/browser_bug633691.js
+++ b/browser/base/content/test/general/browser_bug633691.js
@@ -11,18 +11,20 @@ add_task(function* test() {
     function* ({ is_element_hidden_, is_hidden_ }) {
       let loadError =
         ContentTaskUtils.waitForEvent(this, "AboutNetErrorLoad", false, null, true);
       let iframe = content.document.querySelector("iframe");
       iframe.src = "https://expired.example.com/";
 
       yield loadError;
 
+      /* eslint-disable no-eval */
       let is_hidden = eval(`(() => ${is_hidden_})()`);
       let is_element_hidden = eval(`(() => ${is_element_hidden_})()`);
+      /* eslint-enable no-eval */
       let doc = content.document.getElementsByTagName("iframe")[0].contentDocument;
       let aP = doc.getElementById("badCertAdvancedPanel");
       ok(aP, "Advanced content should exist");
       void is_hidden; // Quiet eslint warnings (actual use under is_element_hidden)
       is_element_hidden(aP, "Advanced content should not be visible by default")
     });
   });
 });
--- a/browser/base/content/test/general/browser_tabReorder.js
+++ b/browser/base/content/test/general/browser_tabReorder.js
@@ -1,12 +1,12 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
-function test() {
+add_task(function*() {
   let initialTabsLength = gBrowser.tabs.length;
 
   let newTab1 = gBrowser.selectedTab = gBrowser.addTab("about:robots", {skipAnimation: true});
   let newTab2 = gBrowser.selectedTab = gBrowser.addTab("about:about", {skipAnimation: true});
   let newTab3 = gBrowser.selectedTab = gBrowser.addTab("about:config", {skipAnimation: true});
   registerCleanupFunction(function() {
     while (gBrowser.tabs.length > initialTabsLength) {
       gBrowser.removeTab(gBrowser.tabs[initialTabsLength]);
@@ -18,32 +18,37 @@ function test() {
   is(gBrowser.tabs[initialTabsLength + 1], newTab2, "newTab2 position is correct");
   is(gBrowser.tabs[initialTabsLength + 2], newTab3, "newTab3 position is correct");
 
   let scriptLoader = Cc["@mozilla.org/moz/jssubscript-loader;1"].
                      getService(Ci.mozIJSSubScriptLoader);
   let EventUtils = {};
   scriptLoader.loadSubScript("chrome://mochikit/content/tests/SimpleTest/EventUtils.js", EventUtils);
 
-  function dragAndDrop(tab1, tab2, copy) {
+  function* dragAndDrop(tab1, tab2, copy) {
     let rect = tab2.getBoundingClientRect();
     let event = {
       ctrlKey: copy,
       altKey: copy,
       clientX: rect.left + rect.width / 2 + 10,
       clientY: rect.top + rect.height / 2,
     };
 
+    let originalTPos = tab1._tPos;
     EventUtils.synthesizeDrop(tab1, tab2, null, copy ? "copy" : "move", window, window, event);
+    if (!copy) {
+      yield BrowserTestUtils.waitForCondition(() => tab1._tPos != originalTPos,
+        "Waiting for tab position to be updated");
+    }
   }
 
-  dragAndDrop(newTab1, newTab2, false);
+  yield dragAndDrop(newTab1, newTab2, false);
   is(gBrowser.tabs.length, initialTabsLength + 3, "tabs are still there");
   is(gBrowser.tabs[initialTabsLength], newTab2, "newTab2 and newTab1 are swapped");
   is(gBrowser.tabs[initialTabsLength + 1], newTab1, "newTab1 and newTab2 are swapped");
   is(gBrowser.tabs[initialTabsLength + 2], newTab3, "newTab3 stays same place");
 
-  dragAndDrop(newTab2, newTab1, true);
+  yield dragAndDrop(newTab2, newTab1, true);
   is(gBrowser.tabs.length, initialTabsLength + 4, "a tab is duplicated");
   is(gBrowser.tabs[initialTabsLength], newTab2, "newTab2 stays same place");
   is(gBrowser.tabs[initialTabsLength + 1], newTab1, "newTab1 stays same place");
   is(gBrowser.tabs[initialTabsLength + 3], newTab3, "a new tab is inserted before newTab3");
-}
+});
--- a/browser/base/content/test/newtab/head.js
+++ b/browser/base/content/test/newtab/head.js
@@ -138,16 +138,17 @@ add_task(function* setup() {
   * @param aIndex index of cell
   * @param aFn function to call in child process or tab.
   * @returns result of calling the function.
   */
 function performOnCell(aIndex, aFn) {
   return ContentTask.spawn(gWindow.gBrowser.selectedBrowser,
                            { index: aIndex, fn: aFn.toString() }, function* (args) {
     let cell = content.gGrid.cells[args.index];
+    // eslint-disable-next-line no-eval
     return eval(args.fn)(cell);
   });
 }
 
 /**
  * Allows to provide a list of links that is used to construct the grid.
  * @param aLinksPattern the pattern (see below)
  *
--- a/browser/base/content/test/static/browser_all_files_referenced.js
+++ b/browser/base/content/test/static/browser_all_files_referenced.js
@@ -52,17 +52,16 @@ var whitelist = new Set([
   {file: "chrome://global/locale/printPreviewProgress.dtd",
    platforms: ["macosx"]},
   {file: "chrome://global/locale/printProgress.dtd", platforms: ["macosx"]},
   {file: "chrome://global/locale/printdialog.dtd",
    platforms: ["macosx", "win"]},
   {file: "chrome://global/locale/printjoboptions.dtd",
    platforms: ["macosx", "win"]},
 
-  // services/cloudsync/CloudSyncLocal.jsm
   {file: "chrome://weave/locale/errors.properties"},
 
   // devtools/client/inspector/bin/dev-server.js
   {file: "chrome://devtools/content/inspector/markup/markup.xhtml",
    isFromDevTools: true},
 
   // extensions/pref/autoconfig/src/nsReadConfig.cpp
   {file: "resource://gre/defaults/autoconfig/prefcalls.js"},
copy from browser/components/customizableui/content/panelUI.xml
copy to browser/components/customizableui/PanelMultiView.jsm
--- a/browser/components/customizableui/content/panelUI.xml
+++ b/browser/components/customizableui/PanelMultiView.jsm
@@ -1,517 +1,477 @@
-<?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/. -->
+/* 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/. */
 
-<bindings id="browserPanelUIBindings"
-          xmlns="http://www.mozilla.org/xbl"
-          xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
-          xmlns:xbl="http://www.mozilla.org/xbl">
+"use strict";
+
+this.EXPORTED_SYMBOLS = ["PanelMultiView"];
 
-  <binding id="panelmultiview">
-    <resources>
-      <stylesheet src="chrome://browser/content/customizableui/panelUI.css"/>
-    </resources>
-    <content>
-      <xul:box anonid="viewContainer" class="panel-viewcontainer" xbl:inherits="panelopen,viewtype,transitioning">
-        <xul:stack anonid="viewStack" xbl:inherits="viewtype,transitioning" viewtype="main" class="panel-viewstack">
-          <xul:vbox anonid="mainViewContainer" class="panel-mainview" xbl:inherits="viewtype"/>
+/**
+ * This is the implementation of the panelUI.xml XBL binding, moved to this
+ * module, to make it easier to fork the logic for the newer photon structure.
+ * Goals are:
+ * 1. to make it easier to programmatically extend the list of panels,
+ * 2. allow for navigation between panels multiple levels deep and
+ * 3. maintain the pre-photon structure with as little effort possible.
+ *
+ * @type {PanelMultiView}
+ */
+this.PanelMultiView = class {
+  get document() {
+    return this.node.ownerDocument;
+  }
 
-          <!-- Used to capture click events over the PanelUI-mainView if we're in
-               subview mode. That way, any click on the PanelUI-mainView causes us
-               to revert to the mainView mode, whereupon PanelUI-click-capture then
-               allows click events to go through it. -->
-          <xul:vbox anonid="clickCapturer" class="panel-clickcapturer"/>
+  get window() {
+    return this.node.ownerGlobal;
+  }
+
+  get _panel() {
+    return this.node.parentNode;
+  }
 
-          <!-- We manually set display: none (via a CSS attribute selector) on the
-               subviews that are not being displayed. We're using this over a deck
-               because a deck assumes the size of its largest child, regardless of
-               whether or not it is shown. That's not good for our case, since we
-               want to allow each subview to be uniquely sized. -->
-          <xul:vbox anonid="subViews" class="panel-subviews" xbl:inherits="panelopen">
-            <children includes="panelview"/>
-          </xul:vbox>
-        </xul:stack>
-      </xul:box>
-    </content>
-    <implementation implements="nsIDOMEventListener">
-      <field name="_clickCapturer" readonly="true">
-        document.getAnonymousElementByAttribute(this, "anonid", "clickCapturer");
-      </field>
-      <field name="_viewContainer" readonly="true">
-        document.getAnonymousElementByAttribute(this, "anonid", "viewContainer");
-      </field>
-      <field name="_mainViewContainer" readonly="true">
-        document.getAnonymousElementByAttribute(this, "anonid", "mainViewContainer");
-      </field>
-      <field name="_subViews" readonly="true">
-        document.getAnonymousElementByAttribute(this, "anonid", "subViews");
-      </field>
-      <field name="_viewStack" readonly="true">
-        document.getAnonymousElementByAttribute(this, "anonid", "viewStack");
-      </field>
-      <field name="_panel" readonly="true">
-        this.parentNode;
-      </field>
+  get showingSubView() {
+    return this._viewStack.getAttribute("viewtype") == "subview";
+  }
+  get _mainViewId() {
+    return this.node.getAttribute("mainViewId");
+  }
+  set _mainViewId(val) {
+    this.node.setAttribute("mainViewId", val);
+    return val;
+  }
+  get _mainView() {
+    return this._mainViewId ? this.document.getElementById(this._mainViewId) : null;
+  }
+  get showingSubViewAsMainView() {
+    return this.node.getAttribute("mainViewIsSubView") == "true";
+  }
+
+  get ignoreMutations() {
+    return this._ignoreMutations;
+  }
+  set ignoreMutations(val) {
+    this._ignoreMutations = val;
+    if (!val && this._panel.state == "open") {
+      if (this.showingSubView) {
+        this._syncContainerWithSubView();
+      } else {
+        this._syncContainerWithMainView();
+      }
+    }
+  }
 
-      <field name="_currentSubView">null</field>
-      <field name="_anchorElement">null</field>
-      <field name="_mainViewHeight">0</field>
-      <field name="_subViewObserver">null</field>
-      <field name="__transitioning">false</field>
-      <field name="_ignoreMutations">false</field>
+  get _transitioning() {
+    return this.__transitioning;
+  }
+  set _transitioning(val) {
+    this.__transitioning = val;
+    if (val) {
+      this.node.setAttribute("transitioning", "true");
+    } else {
+      this.node.removeAttribute("transitioning");
+    }
+  }
+
+  constructor(xulNode) {
+    this.node = xulNode;
+
+    this._currentSubView = this._anchorElement = this._subViewObserver = null;
+    this._mainViewHeight = 0;
+    this.__transitioning = this._ignoreMutations = false;
+
+    const {document, window} = this;
+
+    this._clickCapturer =
+      document.getAnonymousElementByAttribute(this.node, "anonid", "clickCapturer");
+    this._viewContainer =
+      document.getAnonymousElementByAttribute(this.node, "anonid", "viewContainer");
+    this._mainViewContainer =
+      document.getAnonymousElementByAttribute(this.node, "anonid", "mainViewContainer");
+    this._subViews =
+      document.getAnonymousElementByAttribute(this.node, "anonid", "subViews");
+    this._viewStack =
+      document.getAnonymousElementByAttribute(this.node, "anonid", "viewStack");
 
-      <property name="showingSubView" readonly="true"
-                onget="return this._viewStack.getAttribute('viewtype') == 'subview'"/>
-      <property name="_mainViewId" onget="return this.getAttribute('mainViewId');" onset="this.setAttribute('mainViewId', val); return val;"/>
-      <property name="_mainView" readonly="true"
-                onget="return this._mainViewId ? document.getElementById(this._mainViewId) : null;"/>
-      <property name="showingSubViewAsMainView" readonly="true"
-                onget="return this.getAttribute('mainViewIsSubView') == 'true'"/>
+    this._clickCapturer.addEventListener("click", this);
+    this._panel.addEventListener("popupshowing", this);
+    this._panel.addEventListener("popupshown", this);
+    this._panel.addEventListener("popuphidden", this);
+    this._subViews.addEventListener("overflow", this);
+    this._mainViewContainer.addEventListener("overflow", this);
+
+    // Get a MutationObserver ready to react to subview size changes. We
+    // only attach this MutationObserver when a subview is being displayed.
+    this._subViewObserver = new window.MutationObserver(this._syncContainerWithSubView.bind(this));
+    this._mainViewObserver = new window.MutationObserver(this._syncContainerWithMainView.bind(this));
+
+    this._mainViewContainer.setAttribute("panelid", this._panel.id);
 
-      <property name="ignoreMutations">
-        <getter>
-          return this._ignoreMutations;
-        </getter>
-        <setter><![CDATA[
-          this._ignoreMutations = val;
-          if (!val && this._panel.state == "open") {
-            if (this.showingSubView) {
-              this._syncContainerWithSubView();
-            } else {
-              this._syncContainerWithMainView();
-            }
-          }
-        ]]></setter>
-      </property>
+    if (this._mainView) {
+      this.setMainView(this._mainView);
+    }
+    this.node.setAttribute("viewtype", "main");
+
+    // Proxy these public properties and methods, as used elsewhere by various
+    // parts of the browser, to this instance.
+    ["_mainView", "ignoreMutations", "showingSubView"].forEach(property => {
+      Object.defineProperty(this.node, property, {
+        enumerable: true,
+        get: () => this[property],
+        set: (val) => this[property] = val
+      });
+    });
+    ["setHeightToFit", "setMainView", "showMainView", "showSubView"].forEach(method => {
+      Object.defineProperty(this.node, method, {
+        enumerable: true,
+        value: (...args) => this[method](...args)
+      });
+    });
+  }
 
-      <property name="_transitioning">
-        <getter>
-          return this.__transitioning;
-        </getter>
-        <setter><![CDATA[
-          this.__transitioning = val;
-          if (val) {
-            this.setAttribute("transitioning", "true");
-          } else {
-            this.removeAttribute("transitioning");
-          }
-        ]]></setter>
-      </property>
-      <constructor><![CDATA[
-        this._clickCapturer.addEventListener("click", this);
-        this._panel.addEventListener("popupshowing", this);
-        this._panel.addEventListener("popupshown", this);
-        this._panel.addEventListener("popuphidden", this);
-        this._subViews.addEventListener("overflow", this);
-        this._mainViewContainer.addEventListener("overflow", this);
+  destructor() {
+    if (this._mainView) {
+      this._mainView.removeAttribute("mainview");
+    }
+    this._mainViewObserver.disconnect();
+    this._subViewObserver.disconnect();
+    this._panel.removeEventListener("popupshowing", this);
+    this._panel.removeEventListener("popupshown", this);
+    this._panel.removeEventListener("popuphidden", this);
+    this._subViews.removeEventListener("overflow", this);
+    this._mainViewContainer.removeEventListener("overflow", this);
+    this._clickCapturer.removeEventListener("click", this);
+
+    this.node = this.__clickCapturer = this.__viewContainer = this.__mainViewContainer =
+      this.__subViews = this.__viewStack = null;
+  }
+
+  setMainView(aNewMainView) {
+    if (this._mainView) {
+      this._mainViewObserver.disconnect();
+      this._subViews.appendChild(this._mainView);
+      this._mainView.removeAttribute("mainview");
+    }
+    this._mainViewId = aNewMainView.id;
+    aNewMainView.setAttribute("mainview", "true");
+    this._mainViewContainer.appendChild(aNewMainView);
+  }
 
-        // Get a MutationObserver ready to react to subview size changes. We
-        // only attach this MutationObserver when a subview is being displayed.
-        this._subViewObserver =
-          new MutationObserver(this._syncContainerWithSubView.bind(this));
-        this._mainViewObserver =
-          new MutationObserver(this._syncContainerWithMainView.bind(this));
+  showMainView() {
+    if (this.showingSubView) {
+      let viewNode = this._currentSubView;
+      let evt = new this.window.CustomEvent("ViewHiding", { bubbles: true, cancelable: true });
+      viewNode.dispatchEvent(evt);
 
-        this._mainViewContainer.setAttribute("panelid",
-                                             this._panel.id);
+      viewNode.removeAttribute("current");
+      this._currentSubView = null;
+
+      this._subViewObserver.disconnect();
+
+      this._setViewContainerHeight(this._mainViewHeight);
 
-        if (this._mainView) {
-          this.setMainView(this._mainView);
-        }
-        this.setAttribute("viewtype", "main");
-      ]]></constructor>
+      this.node.setAttribute("viewtype", "main");
+    }
+
+    this._shiftMainView();
+  }
 
-      <destructor><![CDATA[
-        if (this._mainView) {
-          this._mainView.removeAttribute("mainview");
+  showSubView(aViewId, aAnchor) {
+    const {document, window} = this;
+    window.Task.spawn(function*() {
+      let viewNode = this.node.querySelector("#" + aViewId);
+      if (!viewNode) {
+        viewNode = document.getElementById(aViewId);
+        if (viewNode) {
+          this._subViews.appendChild(viewNode);
+        } else {
+          throw new Error(`Subview ${aViewId} doesn't exist!`);
         }
-        this._mainViewObserver.disconnect();
-        this._subViewObserver.disconnect();
-        this._panel.removeEventListener("popupshowing", this);
-        this._panel.removeEventListener("popupshown", this);
-        this._panel.removeEventListener("popuphidden", this);
-        this._subViews.removeEventListener("overflow", this);
-        this._mainViewContainer.removeEventListener("overflow", this);
-        this._clickCapturer.removeEventListener("click", this);
-      ]]></destructor>
+      }
+      viewNode.setAttribute("current", true);
+      // Emit the ViewShowing event so that the widget definition has a chance
+      // to lazily populate the subview with things.
+      let detail = {
+        blockers: new Set(),
+        addBlocker(aPromise) {
+          this.blockers.add(aPromise);
+        },
+      };
 
-      <method name="setMainView">
-        <parameter name="aNewMainView"/>
-        <body><![CDATA[
-        if (this._mainView) {
-          this._mainViewObserver.disconnect();
-          this._subViews.appendChild(this._mainView);
-          this._mainView.removeAttribute("mainview");
+      let evt = new window.CustomEvent("ViewShowing", { bubbles: true, cancelable: true, detail });
+      viewNode.dispatchEvent(evt);
+
+      let cancel = evt.defaultPrevented;
+      if (detail.blockers.size) {
+        try {
+          let results = yield window.Promise.all(detail.blockers);
+          cancel = cancel || results.some(val => val === false);
+        } catch (e) {
+          Components.utils.reportError(e);
+          cancel = true;
         }
-        this._mainViewId = aNewMainView.id;
-        aNewMainView.setAttribute("mainview", "true");
-        this._mainViewContainer.appendChild(aNewMainView);
-        ]]></body>
-      </method>
-
-      <method name="showMainView">
-        <body><![CDATA[
-          if (this.showingSubView) {
-            let viewNode = this._currentSubView;
-            let evt = document.createEvent("CustomEvent");
-            evt.initCustomEvent("ViewHiding", true, true, viewNode);
-            viewNode.dispatchEvent(evt);
-
-            viewNode.removeAttribute("current");
-            this._currentSubView = null;
-
-            this._subViewObserver.disconnect();
-
-            this._setViewContainerHeight(this._mainViewHeight);
-
-            this.setAttribute("viewtype", "main");
-          }
-
-          this._shiftMainView();
-        ]]></body>
-      </method>
+      }
 
-      <method name="showSubView">
-        <parameter name="aViewId"/>
-        <parameter name="aAnchor"/>
-        <body><![CDATA[
-          Task.spawn(function*() {
-            let viewNode = this.querySelector("#" + aViewId);
-            if (!viewNode) {
-              viewNode = document.getElementById(aViewId);
-              if (viewNode) {
-                this._subViews.appendChild(viewNode);
-              } else {
-                throw new Error(`Subview ${aViewId} doesn't exist!`);
-              }
-            }
-            viewNode.setAttribute("current", true);
-            // Emit the ViewShowing event so that the widget definition has a chance
-            // to lazily populate the subview with things.
-            let detail = {
-              blockers: new Set(),
-              addBlocker(aPromise) {
-                this.blockers.add(aPromise);
-              },
-            };
+      if (cancel) {
+        return;
+      }
 
-            let evt = new CustomEvent("ViewShowing", { bubbles: true, cancelable: true, detail });
-            viewNode.dispatchEvent(evt);
-
-            let cancel = evt.defaultPrevented;
-            if (detail.blockers.size) {
-              try {
-                let results = yield Promise.all(detail.blockers);
-                cancel = cancel || results.some(val => val === false);
-              } catch (e) {
-                Components.utils.reportError(e);
-                cancel = true;
-              }
-            }
-
-            if (cancel) {
-              return;
-            }
+      this._currentSubView = viewNode;
 
-            this._currentSubView = viewNode;
-
-            // Now we have to transition the panel. There are a few parts to this:
-            //
-            // 1) The main view content gets shifted so that the center of the anchor
-            //    node is at the left-most edge of the panel.
-            // 2) The subview deck slides in so that it takes up almost all of the
-            //    panel.
-            // 3) If the subview is taller then the main panel contents, then the panel
-            //    must grow to meet that new height. Otherwise, it must shrink.
-            //
-            // All three of these actions make use of CSS transformations, so they
-            // should all occur simultaneously.
-            this.setAttribute("viewtype", "subview");
-            this._shiftMainView(aAnchor);
-
-            this._mainViewHeight = this._viewStack.clientHeight;
-
-            let newHeight = this._heightOfSubview(viewNode, this._subViews);
-            this._setViewContainerHeight(newHeight);
+      // Now we have to transition the panel. There are a few parts to this:
+      //
+      // 1) The main view content gets shifted so that the center of the anchor
+      //    node is at the left-most edge of the panel.
+      // 2) The subview deck slides in so that it takes up almost all of the
+      //    panel.
+      // 3) If the subview is taller then the main panel contents, then the panel
+      //    must grow to meet that new height. Otherwise, it must shrink.
+      //
+      // All three of these actions make use of CSS transformations, so they
+      // should all occur simultaneously.
+      this.node.setAttribute("viewtype", "subview");
+      this._shiftMainView(aAnchor);
 
-            this._subViewObserver.observe(viewNode, {
-              attributes: true,
-              characterData: true,
-              childList: true,
-              subtree: true
-            });
-          }.bind(this));
-        ]]></body>
-      </method>
+      this._mainViewHeight = this._viewStack.clientHeight;
 
-      <method name="_setViewContainerHeight">
-        <parameter name="aHeight"/>
-        <body><![CDATA[
-          let container = this._viewContainer;
-          this._transitioning = true;
-
-          let onTransitionEnd = () => {
-            container.removeEventListener("transitionend", onTransitionEnd);
-            this._transitioning = false;
-          };
+      let newHeight = this._heightOfSubview(viewNode, this._subViews);
+      this._setViewContainerHeight(newHeight);
 
-          container.addEventListener("transitionend", onTransitionEnd);
-          container.style.height = `${aHeight}px`;
-        ]]></body>
-      </method>
+      this._subViewObserver.observe(viewNode, {
+        attributes: true,
+        characterData: true,
+        childList: true,
+        subtree: true
+      });
+    }.bind(this));
+  }
+
+  _setViewContainerHeight(aHeight) {
+    let container = this._viewContainer;
+    this._transitioning = true;
+
+    let onTransitionEnd = () => {
+      container.removeEventListener("transitionend", onTransitionEnd);
+      this._transitioning = false;
+    };
 
-      <method name="_shiftMainView">
-        <parameter name="aAnchor"/>
-        <body><![CDATA[
-          if (aAnchor) {
-            // We need to find the edge of the anchor, relative to the main panel.
-            // Then we need to add half the width of the anchor. This is the target
-            // that we need to transition to.
-            let anchorRect = aAnchor.getBoundingClientRect();
-            let mainViewRect = this._mainViewContainer.getBoundingClientRect();
-            let center = aAnchor.clientWidth / 2;
-            let direction = aAnchor.ownerGlobal.getComputedStyle(aAnchor).direction;
-            let edge;
-            if (direction == "ltr") {
-              edge = anchorRect.left - mainViewRect.left;
-            } else {
-              edge = mainViewRect.right - anchorRect.right;
-            }
+    container.addEventListener("transitionend", onTransitionEnd);
+    container.style.height = `${aHeight}px`;
+  }
+
+  _shiftMainView(aAnchor) {
+    if (aAnchor) {
+      // We need to find the edge of the anchor, relative to the main panel.
+      // Then we need to add half the width of the anchor. This is the target
+      // that we need to transition to.
+      let anchorRect = aAnchor.getBoundingClientRect();
+      let mainViewRect = this._mainViewContainer.getBoundingClientRect();
+      let center = aAnchor.clientWidth / 2;
+      let direction = aAnchor.ownerGlobal.getComputedStyle(aAnchor).direction;
+      let edge;
+      if (direction == "ltr") {
+        edge = anchorRect.left - mainViewRect.left;
+      } else {
+        edge = mainViewRect.right - anchorRect.right;
+      }
 
-            // If the anchor is an element on the far end of the mainView we
-            // don't want to shift the mainView too far, we would reveal empty
-            // space otherwise.
-            let cstyle = window.getComputedStyle(document.documentElement);
-            let exitSubViewGutterWidth =
-              cstyle.getPropertyValue("--panel-ui-exit-subview-gutter-width");
-            let maxShift = mainViewRect.width - parseInt(exitSubViewGutterWidth);
-            let target = Math.min(maxShift, edge + center);
+      // If the anchor is an element on the far end of the mainView we
+      // don't want to shift the mainView too far, we would reveal empty
+      // space otherwise.
+      let cstyle = this.window.getComputedStyle(this.document.documentElement);
+      let exitSubViewGutterWidth =
+        cstyle.getPropertyValue("--panel-ui-exit-subview-gutter-width");
+      let maxShift = mainViewRect.width - parseInt(exitSubViewGutterWidth);
+      let target = Math.min(maxShift, edge + center);
 
-            let neg = direction == "ltr" ? "-" : "";
-            this._mainViewContainer.style.transform = `translateX(${neg}${target}px)`;
-            aAnchor.setAttribute("panel-multiview-anchor", true);
-          } else {
-            this._mainViewContainer.style.transform = "";
-            if (this.anchorElement)
-              this.anchorElement.removeAttribute("panel-multiview-anchor");
-          }
-          this.anchorElement = aAnchor;
-        ]]></body>
-      </method>
+      let neg = direction == "ltr" ? "-" : "";
+      this._mainViewContainer.style.transform = `translateX(${neg}${target}px)`;
+      aAnchor.setAttribute("panel-multiview-anchor", true);
+    } else {
+      this._mainViewContainer.style.transform = "";
+      if (this.anchorElement)
+        this.anchorElement.removeAttribute("panel-multiview-anchor");
+    }
+    this.anchorElement = aAnchor;
+  }
 
-      <method name="handleEvent">
-        <parameter name="aEvent"/>
-        <body><![CDATA[
-          if (aEvent.type.startsWith("popup") && aEvent.target != this._panel) {
-            // Shouldn't act on e.g. context menus being shown from within the panel.
-            return;
+  handleEvent(aEvent) {
+    if (aEvent.type.startsWith("popup") && aEvent.target != this._panel) {
+      // Shouldn't act on e.g. context menus being shown from within the panel.
+      return;
+    }
+    switch (aEvent.type) {
+      case "click":
+        if (aEvent.originalTarget == this._clickCapturer) {
+          this.showMainView();
+        }
+        break;
+      case "overflow":
+        if (aEvent.target.localName == "vbox") {
+          // Resize the right view on the next tick.
+          if (this.showingSubView) {
+            this.window.setTimeout(this._syncContainerWithSubView.bind(this), 0);
+          } else if (!this.transitioning) {
+            this.window.setTimeout(this._syncContainerWithMainView.bind(this), 0);
           }
-          switch (aEvent.type) {
-            case "click":
-              if (aEvent.originalTarget == this._clickCapturer) {
-                this.showMainView();
-              }
-              break;
-            case "overflow":
-              if (aEvent.target.localName == "vbox") {
-                // Resize the right view on the next tick.
-                if (this.showingSubView) {
-                  setTimeout(this._syncContainerWithSubView.bind(this), 0);
-                } else if (!this.transitioning) {
-                  setTimeout(this._syncContainerWithMainView.bind(this), 0);
-                }
-              }
-              break;
-            case "popupshowing":
-              this.setAttribute("panelopen", "true");
-              // Bug 941196 - The panel can get taller when opening a subview. Disabling
-              // autoPositioning means that the panel won't jump around if an opened
-              // subview causes the panel to exceed the dimensions of the screen in the
-              // direction that the panel originally opened in. This property resets
-              // every time the popup closes, which is why we have to set it each time.
-              this._panel.autoPosition = false;
-              this._syncContainerWithMainView();
+        }
+        break;
+      case "popupshowing":
+        this.node.setAttribute("panelopen", "true");
+        // Bug 941196 - The panel can get taller when opening a subview. Disabling
+        // autoPositioning means that the panel won't jump around if an opened
+        // subview causes the panel to exceed the dimensions of the screen in the
+        // direction that the panel originally opened in. This property resets
+        // every time the popup closes, which is why we have to set it each time.
+        this._panel.autoPosition = false;
+        this._syncContainerWithMainView();
+
+        this._mainViewObserver.observe(this._mainView, {
+          attributes: true,
+          characterData: true,
+          childList: true,
+          subtree: true
+        });
 
-              this._mainViewObserver.observe(this._mainView, {
-                attributes: true,
-                characterData: true,
-                childList: true,
-                subtree: true
-              });
+        break;
+      case "popupshown":
+        this._setMaxHeight();
+        break;
+      case "popuphidden":
+        this.node.removeAttribute("panelopen");
+        this._mainView.style.removeProperty("height");
+        this.showMainView();
+        this._mainViewObserver.disconnect();
+        break;
+    }
+  }
 
-              break;
-            case "popupshown":
-              this._setMaxHeight();
-              break;
-            case "popuphidden":
-              this.removeAttribute("panelopen");
-              this._mainView.style.removeProperty("height");
-              this.showMainView();
-              this._mainViewObserver.disconnect();
-              break;
-          }
-        ]]></body>
-      </method>
+  _shouldSetPosition() {
+    return this.node.getAttribute("nosubviews") == "true";
+  }
 
-      <method name="_shouldSetPosition">
-        <body><![CDATA[
-          return this.getAttribute("nosubviews") == "true";
-        ]]></body>
-      </method>
-
-      <method name="_shouldSetHeight">
-        <body><![CDATA[
-          return this.getAttribute("nosubviews") != "true";
-        ]]></body>
-      </method>
-
-      <method name="_setMaxHeight">
-        <body><![CDATA[
-          if (!this._shouldSetHeight())
-            return;
+  _shouldSetHeight() {
+    return this.node.getAttribute("nosubviews") != "true";
+  }
 
-          // Ignore the mutation that'll fire when we set the height of
-          // the main view.
-          this.ignoreMutations = true;
-          this._mainView.style.height =
-            this.getBoundingClientRect().height + "px";
-          this.ignoreMutations = false;
-        ]]></body>
-      </method>
-      <method name="_adjustContainerHeight">
-        <body><![CDATA[
-          if (!this.ignoreMutations && !this.showingSubView && !this._transitioning) {
-            let height;
-            if (this.showingSubViewAsMainView) {
-              height = this._heightOfSubview(this._mainView);
-            } else {
-              height = this._mainView.scrollHeight;
-            }
-            this._viewContainer.style.height = height + "px";
-          }
-        ]]></body>
-      </method>
-      <method name="_syncContainerWithSubView">
-        <body><![CDATA[
-          // Check that this panel is still alive:
-          if (!this._panel || !this._panel.parentNode) {
-            return;
-          }
+  _setMaxHeight() {
+    if (!this._shouldSetHeight())
+      return;
+
+    // Ignore the mutation that'll fire when we set the height of
+    // the main view.
+    this.ignoreMutations = true;
+    this._mainView.style.height = this.node.getBoundingClientRect().height + "px";
+    this.ignoreMutations = false;
+  }
+
+  _adjustContainerHeight() {
+    if (!this.ignoreMutations && !this.showingSubView && !this._transitioning) {
+      let height;
+      if (this.showingSubViewAsMainView) {
+        height = this._heightOfSubview(this._mainView);
+      } else {
+        height = this._mainView.scrollHeight;
+      }
+      this._viewContainer.style.height = height + "px";
+    }
+  }
 
-          if (!this.ignoreMutations && this.showingSubView) {
-            let newHeight = this._heightOfSubview(this._currentSubView, this._subViews);
-            this._viewContainer.style.height = newHeight + "px";
-          }
-        ]]></body>
-      </method>
-      <method name="_syncContainerWithMainView">
-        <body><![CDATA[
-          // Check that this panel is still alive:
-          if (!this._panel || !this._panel.parentNode) {
-            return;
-          }
+  _syncContainerWithSubView() {
+    // Check that this panel is still alive:
+    if (!this._panel || !this._panel.parentNode) {
+      return;
+    }
+
+    if (!this.ignoreMutations && this.showingSubView) {
+      let newHeight = this._heightOfSubview(this._currentSubView, this._subViews);
+      this._viewContainer.style.height = newHeight + "px";
+    }
+  }
 
-          if (this._shouldSetPosition()) {
-            this._panel.adjustArrowPosition();
-          }
+  _syncContainerWithMainView() {
+    // Check that this panel is still alive:
+    if (!this._panel || !this._panel.parentNode) {
+      return;
+    }
 
-          if (this._shouldSetHeight()) {
-            this._adjustContainerHeight();
-          }
-        ]]></body>
-      </method>
+    if (this._shouldSetPosition()) {
+      this._panel.adjustArrowPosition();
+    }
+
+    if (this._shouldSetHeight()) {
+      this._adjustContainerHeight();
+    }
+  }
 
-      <!-- Call this when the height of one of your views (the main view or a
-           subview) changes and you want the heights of the multiview and panel
-           to be the same as the view's height.
-           If the caller can give a hint of the expected height change with the
-           optional aExpectedChange parameter, it prevents flicker. -->
-      <method name="setHeightToFit">
-        <parameter name="aExpectedChange"/>
-        <body><![CDATA[
-          // Set the max-height to zero, wait until the height is actually
-          // updated, and then remove it.  If it's not removed, weird things can
-          // happen, like widgets in the panel won't respond to clicks even
-          // though they're visible.
-          let count = 5;
-          let height = getComputedStyle(this).height;
-          if (aExpectedChange)
-            this.style.maxHeight = (parseInt(height) + aExpectedChange) + "px";
-          else
-            this.style.maxHeight = "0";
-          let interval = setInterval(() => {
-            if (height != getComputedStyle(this).height || --count == 0) {
-              clearInterval(interval);
-              this.style.removeProperty("max-height");
-            }
-          }, 0);
-        ]]></body>
-      </method>
+  /**
+   * Call this when the height of one of your views (the main view or a
+   * subview) changes and you want the heights of the multiview and panel
+   * to be the same as the view's height.
+   * If the caller can give a hint of the expected height change with the
+   * optional aExpectedChange parameter, it prevents flicker.
+   */
+  setHeightToFit(aExpectedChange) {
+    // Set the max-height to zero, wait until the height is actually
+    // updated, and then remove it.  If it's not removed, weird things can
+    // happen, like widgets in the panel won't respond to clicks even
+    // though they're visible.
+    const {window} = this;
+    let count = 5;
+    let height = window.getComputedStyle(this.node).height;
+    if (aExpectedChange)
+      this.node.style.maxHeight = (parseInt(height, 10) + aExpectedChange) + "px";
+    else
+      this.node.style.maxHeight = "0";
+    let interval = window.setInterval(() => {
+      if (height != window.getComputedStyle(this.node).height || --count == 0) {
+        window.clearInterval(interval);
+        this.node.style.removeProperty("max-height");
+      }
+    }, 0);
+  }
 
-      <method name="_heightOfSubview">
-        <parameter name="aSubview"/>
-        <parameter name="aContainerToCheck"/>
-        <body><![CDATA[
-          function getFullHeight(element) {
-            // XXXgijs: unfortunately, scrollHeight rounds values, and there's no alternative
-            // that works with overflow: auto elements. Fortunately for us,
-            // we have exactly 1 (potentially) scrolling element in here (the subview body),
-            // and rounding 1 value is OK - rounding more than 1 and adding them means we get
-            // off-by-1 errors. Now we might be off by a subpixel, but we care less about that.
-            // So, use scrollHeight *only* if the element is vertically scrollable.
-            let height;
-            let elementCS;
-            if (element.scrollTopMax) {
-              height = element.scrollHeight;
-              // Bounding client rects include borders, scrollHeight doesn't:
-              elementCS = win.getComputedStyle(element);
-              height += parseFloat(elementCS.borderTopWidth) +
-                        parseFloat(elementCS.borderBottomWidth);
-            } else {
-              height = element.getBoundingClientRect().height;
-              if (height > 0) {
-                elementCS = win.getComputedStyle(element);
-              }
-            }
-            if (elementCS) {
-              // Include margins - but not borders or paddings because they
-              // were dealt with above.
-              height += parseFloat(elementCS.marginTop) + parseFloat(elementCS.marginBottom);
-            }
-            return height;
-          }
-          let win = aSubview.ownerGlobal;
-          let body = aSubview.querySelector(".panel-subview-body");
-          let height = getFullHeight(body || aSubview);
-          if (body) {
-            let header = aSubview.querySelector(".panel-subview-header");
-            let footer = aSubview.querySelector(".panel-subview-footer");
-            height += (header ? getFullHeight(header) : 0) +
-                      (footer ? getFullHeight(footer) : 0);
-          }
-          if (aContainerToCheck) {
-            let containerCS = win.getComputedStyle(aContainerToCheck);
-            height += parseFloat(containerCS.paddingTop) + parseFloat(containerCS.paddingBottom);
-          }
-          return Math.ceil(height);
-        ]]></body>
-      </method>
-
-    </implementation>
-  </binding>
-
-  <binding id="panelview">
-    <implementation>
-      <property name="panelMultiView" readonly="true">
-        <getter><![CDATA[
-          if (this.parentNode.localName != "panelmultiview") {
-            return document.getBindingParent(this.parentNode);
-          }
-
-          return this.parentNode;
-        ]]></getter>
-      </property>
-    </implementation>
-  </binding>
-</bindings>
+  _heightOfSubview(aSubview, aContainerToCheck) {
+    function getFullHeight(element) {
+      // XXXgijs: unfortunately, scrollHeight rounds values, and there's no alternative
+      // that works with overflow: auto elements. Fortunately for us,
+      // we have exactly 1 (potentially) scrolling element in here (the subview body),
+      // and rounding 1 value is OK - rounding more than 1 and adding them means we get
+      // off-by-1 errors. Now we might be off by a subpixel, but we care less about that.
+      // So, use scrollHeight *only* if the element is vertically scrollable.
+      let height;
+      let elementCS;
+      if (element.scrollTopMax) {
+        height = element.scrollHeight;
+        // Bounding client rects include borders, scrollHeight doesn't:
+        elementCS = win.getComputedStyle(element);
+        height += parseFloat(elementCS.borderTopWidth) +
+                  parseFloat(elementCS.borderBottomWidth);
+      } else {
+        height = element.getBoundingClientRect().height;
+        if (height > 0) {
+          elementCS = win.getComputedStyle(element);
+        }
+      }
+      if (elementCS) {
+        // Include margins - but not borders or paddings because they
+        // were dealt with above.
+        height += parseFloat(elementCS.marginTop) + parseFloat(elementCS.marginBottom);
+      }
+      return height;
+    }
+    let win = aSubview.ownerGlobal;
+    let body = aSubview.querySelector(".panel-subview-body");
+    let height = getFullHeight(body || aSubview);
+    if (body) {
+      let header = aSubview.querySelector(".panel-subview-header");
+      let footer = aSubview.querySelector(".panel-subview-footer");
+      height += (header ? getFullHeight(header) : 0) +
+                (footer ? getFullHeight(footer) : 0);
+    }
+    if (aContainerToCheck) {
+      let containerCS = win.getComputedStyle(aContainerToCheck);
+      height += parseFloat(containerCS.paddingTop) + parseFloat(containerCS.paddingBottom);
+    }
+    return Math.ceil(height);
+  }
+}
--- a/browser/components/customizableui/content/panelUI.xml
+++ b/browser/components/customizableui/content/panelUI.xml
@@ -29,481 +29,26 @@
                whether or not it is shown. That's not good for our case, since we
                want to allow each subview to be uniquely sized. -->
           <xul:vbox anonid="subViews" class="panel-subviews" xbl:inherits="panelopen">
             <children includes="panelview"/>
           </xul:vbox>
         </xul:stack>
       </xul:box>
     </content>
-    <implementation implements="nsIDOMEventListener">
-      <field name="_clickCapturer" readonly="true">
-        document.getAnonymousElementByAttribute(this, "anonid", "clickCapturer");
-      </field>
-      <field name="_viewContainer" readonly="true">
-        document.getAnonymousElementByAttribute(this, "anonid", "viewContainer");
-      </field>
-      <field name="_mainViewContainer" readonly="true">
-        document.getAnonymousElementByAttribute(this, "anonid", "mainViewContainer");
-      </field>
-      <field name="_subViews" readonly="true">
-        document.getAnonymousElementByAttribute(this, "anonid", "subViews");
-      </field>
-      <field name="_viewStack" readonly="true">
-        document.getAnonymousElementByAttribute(this, "anonid", "viewStack");
-      </field>
-      <field name="_panel" readonly="true">
-        this.parentNode;
-      </field>
-
-      <field name="_currentSubView">null</field>
-      <field name="_anchorElement">null</field>
-      <field name="_mainViewHeight">0</field>
-      <field name="_subViewObserver">null</field>
-      <field name="__transitioning">false</field>
-      <field name="_ignoreMutations">false</field>
-
-      <property name="showingSubView" readonly="true"
-                onget="return this._viewStack.getAttribute('viewtype') == 'subview'"/>
-      <property name="_mainViewId" onget="return this.getAttribute('mainViewId');" onset="this.setAttribute('mainViewId', val); return val;"/>
-      <property name="_mainView" readonly="true"
-                onget="return this._mainViewId ? document.getElementById(this._mainViewId) : null;"/>
-      <property name="showingSubViewAsMainView" readonly="true"
-                onget="return this.getAttribute('mainViewIsSubView') == 'true'"/>
-
-      <property name="ignoreMutations">
-        <getter>
-          return this._ignoreMutations;
-        </getter>
-        <setter><![CDATA[
-          this._ignoreMutations = val;
-          if (!val && this._panel.state == "open") {
-            if (this.showingSubView) {
-              this._syncContainerWithSubView();
-            } else {
-              this._syncContainerWithMainView();
-            }
-          }
-        ]]></setter>
-      </property>
-
-      <property name="_transitioning">
-        <getter>
-          return this.__transitioning;
-        </getter>
-        <setter><![CDATA[
-          this.__transitioning = val;
-          if (val) {
-            this.setAttribute("transitioning", "true");
-          } else {
-            this.removeAttribute("transitioning");
-          }
-        ]]></setter>
-      </property>
+    <implementation>
       <constructor><![CDATA[
-        this._clickCapturer.addEventListener("click", this);
-        this._panel.addEventListener("popupshowing", this);
-        this._panel.addEventListener("popupshown", this);
-        this._panel.addEventListener("popuphidden", this);
-        this._subViews.addEventListener("overflow", this);
-        this._mainViewContainer.addEventListener("overflow", this);
-
-        // Get a MutationObserver ready to react to subview size changes. We
-        // only attach this MutationObserver when a subview is being displayed.
-        this._subViewObserver =
-          new MutationObserver(this._syncContainerWithSubView.bind(this));
-        this._mainViewObserver =
-          new MutationObserver(this._syncContainerWithMainView.bind(this));
-
-        this._mainViewContainer.setAttribute("panelid",
-                                             this._panel.id);
-
-        if (this._mainView) {
-          this.setMainView(this._mainView);
-        }
-        this.setAttribute("viewtype", "main");
-      ]]></constructor>
-
-      <destructor><![CDATA[
-        if (this._mainView) {
-          this._mainView.removeAttribute("mainview");
-        }
-        this._mainViewObserver.disconnect();
-        this._subViewObserver.disconnect();
-        this._panel.removeEventListener("popupshowing", this);
-        this._panel.removeEventListener("popupshown", this);
-        this._panel.removeEventListener("popuphidden", this);
-        this._subViews.removeEventListener("overflow", this);
-        this._mainViewContainer.removeEventListener("overflow", this);
-        this._clickCapturer.removeEventListener("click", this);
-      ]]></destructor>
-
-      <method name="setMainView">
-        <parameter name="aNewMainView"/>
-        <body><![CDATA[
-        if (this._mainView) {
-          this._mainViewObserver.disconnect();
-          this._subViews.appendChild(this._mainView);
-          this._mainView.removeAttribute("mainview");
-        }
-        this._mainViewId = aNewMainView.id;
-        aNewMainView.setAttribute("mainview", "true");
-        this._mainViewContainer.appendChild(aNewMainView);
-        ]]></body>
-      </method>
-
-      <method name="showMainView">
-        <body><![CDATA[
-          if (this.showingSubView) {
-            let viewNode = this._currentSubView;
-            let evt = document.createEvent("CustomEvent");
-            evt.initCustomEvent("ViewHiding", true, true, viewNode);
-            viewNode.dispatchEvent(evt);
-
-            viewNode.removeAttribute("current");
-            this._currentSubView = null;
-
-            this._subViewObserver.disconnect();
-
-            this._setViewContainerHeight(this._mainViewHeight);
-
-            this.setAttribute("viewtype", "main");
-          }
-
-          this._shiftMainView();
-        ]]></body>
-      </method>
-
-      <method name="showSubView">
-        <parameter name="aViewId"/>
-        <parameter name="aAnchor"/>
-        <body><![CDATA[
-          Task.spawn(function*() {
-            let viewNode = this.querySelector("#" + aViewId);
-            if (!viewNode) {
-              viewNode = document.getElementById(aViewId);
-              if (viewNode) {
-                this._subViews.appendChild(viewNode);
-              } else {
-                throw new Error(`Subview ${aViewId} doesn't exist!`);
-              }
-            }
-            viewNode.setAttribute("current", true);
-            // Emit the ViewShowing event so that the widget definition has a chance
-            // to lazily populate the subview with things.
-            let detail = {
-              blockers: new Set(),
-              addBlocker(aPromise) {
-                this.blockers.add(aPromise);
-              },
-            };
-
-            let evt = new CustomEvent("ViewShowing", { bubbles: true, cancelable: true, detail });
-            viewNode.dispatchEvent(evt);
-
-            let cancel = evt.defaultPrevented;
-            if (detail.blockers.size) {
-              try {
-                let results = yield Promise.all(detail.blockers);
-                cancel = cancel || results.some(val => val === false);
-              } catch (e) {
-                Components.utils.reportError(e);
-                cancel = true;
-              }
-            }
-
-            if (cancel) {
-              return;
-            }
-
-            this._currentSubView = viewNode;
-
-            // Now we have to transition the panel. There are a few parts to this:
-            //
-            // 1) The main view content gets shifted so that the center of the anchor
-            //    node is at the left-most edge of the panel.
-            // 2) The subview deck slides in so that it takes up almost all of the
-            //    panel.
-            // 3) If the subview is taller then the main panel contents, then the panel
-            //    must grow to meet that new height. Otherwise, it must shrink.
-            //
-            // All three of these actions make use of CSS transformations, so they
-            // should all occur simultaneously.
-            this.setAttribute("viewtype", "subview");
-            this._shiftMainView(aAnchor);
-
-            this._mainViewHeight = this._viewStack.clientHeight;
-
-            let newHeight = this._heightOfSubview(viewNode, this._subViews);
-            this._setViewContainerHeight(newHeight);
-
-            this._subViewObserver.observe(viewNode, {
-              attributes: true,
-              characterData: true,
-              childList: true,
-              subtree: true
-            });
-          }.bind(this));
-        ]]></body>
-      </method>
-
-      <method name="_setViewContainerHeight">
-        <parameter name="aHeight"/>
-        <body><![CDATA[
-          let container = this._viewContainer;
-          this._transitioning = true;
-
-          let onTransitionEnd = () => {
-            container.removeEventListener("transitionend", onTransitionEnd);
-            this._transitioning = false;
-          };
-
-          container.addEventListener("transitionend", onTransitionEnd);
-          container.style.height = `${aHeight}px`;
-        ]]></body>
-      </method>
-
-      <method name="_shiftMainView">
-        <parameter name="aAnchor"/>
-        <body><![CDATA[
-          if (aAnchor) {
-            // We need to find the edge of the anchor, relative to the main panel.
-            // Then we need to add half the width of the anchor. This is the target
-            // that we need to transition to.
-            let anchorRect = aAnchor.getBoundingClientRect();
-            let mainViewRect = this._mainViewContainer.getBoundingClientRect();
-            let center = aAnchor.clientWidth / 2;
-            let direction = aAnchor.ownerGlobal.getComputedStyle(aAnchor).direction;
-            let edge;
-            if (direction == "ltr") {
-              edge = anchorRect.left - mainViewRect.left;
-            } else {
-              edge = mainViewRect.right - anchorRect.right;
-            }
-
-            // If the anchor is an element on the far end of the mainView we
-            // don't want to shift the mainView too far, we would reveal empty
-            // space otherwise.
-            let cstyle = window.getComputedStyle(document.documentElement);
-            let exitSubViewGutterWidth =
-              cstyle.getPropertyValue("--panel-ui-exit-subview-gutter-width");
-            let maxShift = mainViewRect.width - parseInt(exitSubViewGutterWidth);
-            let target = Math.min(maxShift, edge + center);
-
-            let neg = direction == "ltr" ? "-" : "";
-            this._mainViewContainer.style.transform = `translateX(${neg}${target}px)`;
-            aAnchor.setAttribute("panel-multiview-anchor", true);
-          } else {
-            this._mainViewContainer.style.transform = "";
-            if (this.anchorElement)
-              this.anchorElement.removeAttribute("panel-multiview-anchor");
-          }
-          this.anchorElement = aAnchor;
-        ]]></body>
-      </method>
-
-      <method name="handleEvent">
-        <parameter name="aEvent"/>
-        <body><![CDATA[
-          if (aEvent.type.startsWith("popup") && aEvent.target != this._panel) {
-            // Shouldn't act on e.g. context menus being shown from within the panel.
-            return;
-          }
-          switch (aEvent.type) {
-            case "click":
-              if (aEvent.originalTarget == this._clickCapturer) {
-                this.showMainView();
-              }
-              break;
-            case "overflow":
-              if (aEvent.target.localName == "vbox") {
-                // Resize the right view on the next tick.
-                if (this.showingSubView) {
-                  setTimeout(this._syncContainerWithSubView.bind(this), 0);
-                } else if (!this.transitioning) {
-                  setTimeout(this._syncContainerWithMainView.bind(this), 0);
-                }
-              }
-              break;
-            case "popupshowing":
-              this.setAttribute("panelopen", "true");
-              // Bug 941196 - The panel can get taller when opening a subview. Disabling
-              // autoPositioning means that the panel won't jump around if an opened
-              // subview causes the panel to exceed the dimensions of the screen in the
-              // direction that the panel originally opened in. This property resets
-              // every time the popup closes, which is why we have to set it each time.
-              this._panel.autoPosition = false;
-              this._syncContainerWithMainView();
-
-              this._mainViewObserver.observe(this._mainView, {
-                attributes: true,
-                characterData: true,
-                childList: true,
-                subtree: true
-              });
-
-              break;
-            case "popupshown":
-              this._setMaxHeight();
-              break;
-            case "popuphidden":
-              this.removeAttribute("panelopen");
-              this._mainView.style.removeProperty("height");
-              this.showMainView();
-              this._mainViewObserver.disconnect();
-              break;
-          }
-        ]]></body>
-      </method>
-
-      <method name="_shouldSetPosition">
-        <body><![CDATA[
-          return this.getAttribute("nosubviews") == "true";
-        ]]></body>
-      </method>
-
-      <method name="_shouldSetHeight">
-        <body><![CDATA[
-          return this.getAttribute("nosubviews") != "true";
-        ]]></body>
-      </method>
-
-      <method name="_setMaxHeight">
-        <body><![CDATA[
-          if (!this._shouldSetHeight())
-            return;
-
-          // Ignore the mutation that'll fire when we set the height of
-          // the main view.
-          this.ignoreMutations = true;
-          this._mainView.style.height =
-            this.getBoundingClientRect().height + "px";
-          this.ignoreMutations = false;
-        ]]></body>
-      </method>
-      <method name="_adjustContainerHeight">
-        <body><![CDATA[
-          if (!this.ignoreMutations && !this.showingSubView && !this._transitioning) {
-            let height;
-            if (this.showingSubViewAsMainView) {
-              height = this._heightOfSubview(this._mainView);
-            } else {
-              height = this._mainView.scrollHeight;
-            }
-            this._viewContainer.style.height = height + "px";
-          }
-        ]]></body>
-      </method>
-      <method name="_syncContainerWithSubView">
-        <body><![CDATA[
-          // Check that this panel is still alive:
-          if (!this._panel || !this._panel.parentNode) {
-            return;
-          }
-
-          if (!this.ignoreMutations && this.showingSubView) {
-            let newHeight = this._heightOfSubview(this._currentSubView, this._subViews);
-            this._viewContainer.style.height = newHeight + "px";
-          }
-        ]]></body>
-      </method>
-      <method name="_syncContainerWithMainView">
-        <body><![CDATA[
-          // Check that this panel is still alive:
-          if (!this._panel || !this._panel.parentNode) {
-            return;
-          }
-
-          if (this._shouldSetPosition()) {
-            this._panel.adjustArrowPosition();
-          }
-
-          if (this._shouldSetHeight()) {
-            this._adjustContainerHeight();
-          }
-        ]]></body>
-      </method>
-
-      <!-- Call this when the height of one of your views (the main view or a
-           subview) changes and you want the heights of the multiview and panel
-           to be the same as the view's height.
-           If the caller can give a hint of the expected height change with the
-           optional aExpectedChange parameter, it prevents flicker. -->
-      <method name="setHeightToFit">
-        <parameter name="aExpectedChange"/>
-        <body><![CDATA[
-          // Set the max-height to zero, wait until the height is actually
-          // updated, and then remove it.  If it's not removed, weird things can
-          // happen, like widgets in the panel won't respond to clicks even
-          // though they're visible.
-          let count = 5;
-          let height = getComputedStyle(this).height;
-          if (aExpectedChange)
-            this.style.maxHeight = (parseInt(height) + aExpectedChange) + "px";
-          else
-            this.style.maxHeight = "0";
-          let interval = setInterval(() => {
-            if (height != getComputedStyle(this).height || --count == 0) {
-              clearInterval(interval);
-              this.style.removeProperty("max-height");
-            }
-          }, 0);
-        ]]></body>
-      </method>
-
-      <method name="_heightOfSubview">
-        <parameter name="aSubview"/>
-        <parameter name="aContainerToCheck"/>
-        <body><![CDATA[
-          function getFullHeight(element) {
-            // XXXgijs: unfortunately, scrollHeight rounds values, and there's no alternative
-            // that works with overflow: auto elements. Fortunately for us,
-            // we have exactly 1 (potentially) scrolling element in here (the subview body),
-            // and rounding 1 value is OK - rounding more than 1 and adding them means we get
-            // off-by-1 errors. Now we might be off by a subpixel, but we care less about that.
-            // So, use scrollHeight *only* if the element is vertically scrollable.
-            let height;
-            let elementCS;
-            if (element.scrollTopMax) {
-              height = element.scrollHeight;
-              // Bounding client rects include borders, scrollHeight doesn't:
-              elementCS = win.getComputedStyle(element);
-              height += parseFloat(elementCS.borderTopWidth) +
-                        parseFloat(elementCS.borderBottomWidth);
-            } else {
-              height = element.getBoundingClientRect().height;
-              if (height > 0) {
-                elementCS = win.getComputedStyle(element);
-              }
-            }
-            if (elementCS) {
-              // Include margins - but not borders or paddings because they
-              // were dealt with above.
-              height += parseFloat(elementCS.marginTop) + parseFloat(elementCS.marginBottom);
-            }
-            return height;
-          }
-          let win = aSubview.ownerGlobal;
-          let body = aSubview.querySelector(".panel-subview-body");
-          let height = getFullHeight(body || aSubview);
-          if (body) {
-            let header = aSubview.querySelector(".panel-subview-header");
-            let footer = aSubview.querySelector(".panel-subview-footer");
-            height += (header ? getFullHeight(header) : 0) +
-                      (footer ? getFullHeight(footer) : 0);
-          }
-          if (aContainerToCheck) {
-            let containerCS = win.getComputedStyle(aContainerToCheck);
-            height += parseFloat(containerCS.paddingTop) + parseFloat(containerCS.paddingBottom);
-          }
-          return Math.ceil(height);
-        ]]></body>
-      </method>
-
-    </implementation>
+        const {PanelMultiView} = Components.utils.import("resource:///modules/PanelMultiView.jsm", {});
+        this.instance = new PanelMultiView(this);
+       ]]></constructor>
+ 
+       <destructor><![CDATA[
+        this.instance.destructor();
+       ]]></destructor>
+     </implementation>
   </binding>
 
   <binding id="panelview">
     <implementation>
       <property name="panelMultiView" readonly="true">
         <getter><![CDATA[
           if (this.parentNode.localName != "panelmultiview") {
             return document.getBindingParent(this.parentNode);
--- a/browser/components/customizableui/moz.build
+++ b/browser/components/customizableui/moz.build
@@ -10,16 +10,17 @@ DIRS += [
 
 BROWSER_CHROME_MANIFESTS += ['test/browser.ini']
 
 EXTRA_JS_MODULES += [
     'CustomizableUI.jsm',
     'CustomizableWidgets.jsm',
     'CustomizeMode.jsm',
     'DragPositionManager.jsm',
+    'PanelMultiView.jsm',
     'PanelWideWidgetTracker.jsm',
     'ScrollbarSampler.jsm',
 ]
 
 if CONFIG['MOZ_WIDGET_TOOLKIT'] in ('windows', 'cocoa'):
     DEFINES['CAN_DRAW_IN_TITLEBAR'] = 1
 
 with Files('**'):
--- a/browser/components/downloads/DownloadsCommon.jsm
+++ b/browser/components/downloads/DownloadsCommon.jsm
@@ -66,27 +66,21 @@ XPCOMUtils.defineLazyGetter(this, "Downl
   let { ConsoleAPI } = Cu.import("resource://gre/modules/Console.jsm", {});
   let consoleOptions = {
     maxLogLevelPref: "browser.download.loglevel",
     prefix: "Downloads"
   };
   return new ConsoleAPI(consoleOptions);
 });
 
-const nsIDM = Ci.nsIDownloadManager;
-
 const kDownloadsStringBundleUrl =
   "chrome://browser/locale/downloads/downloads.properties";
 
 const kDownloadsStringsRequiringFormatting = {
   sizeWithUnits: true,
-  shortTimeLeftSeconds: true,
-  shortTimeLeftMinutes: true,
-  shortTimeLeftHours: true,
-  shortTimeLeftDays: true,
   statusSeparator: true,
   statusSeparatorBeforeNumber: true,
   fileExecutableSecurityWarning: true
 };
 
 const kDownloadsStringsRequiringPluralForm = {
   otherDownloads3: true
 };
@@ -123,40 +117,45 @@ var PrefObserver = {
       });
     }
   },
 };
 
 PrefObserver.register({
   // prefName: defaultValue
   animateNotifications: true,
-  showPanelDropmarker: true,
 });
 
 
 // DownloadsCommon
 
 /**
  * This object is exposed directly to the consumers of this JavaScript module,
  * and provides shared methods for all the instances of the user interface.
  */
 this.DownloadsCommon = {
+  // The following legacy constants are still returned by stateOfDownload, but
+  // individual properties of the Download object should normally be used.
+  DOWNLOAD_NOTSTARTED: -1,
+  DOWNLOAD_DOWNLOADING: 0,
+  DOWNLOAD_FINISHED: 1,
+  DOWNLOAD_FAILED: 2,
+  DOWNLOAD_CANCELED: 3,
+  DOWNLOAD_PAUSED: 4,
+  DOWNLOAD_BLOCKED_PARENTAL: 6,
+  DOWNLOAD_DIRTY: 8,
+  DOWNLOAD_BLOCKED_POLICY: 9,
+
+  // The following are the possible values of the "attention" property.
   ATTENTION_NONE: "",
   ATTENTION_SUCCESS: "success",
   ATTENTION_WARNING: "warning",
   ATTENTION_SEVERE: "severe",
 
   /**
-   * This can be used by add-on experiments as a killswitch for the new style
-   * progress indication. This will be removed in bug 1329109 after the new
-   * indicator is released.
-   **/
-  arrowStyledIndicator: true,
-
-  /**
    * Returns an object whose keys are the string names from the downloads string
    * bundle, and whose values are either the translated strings or functions
    * returning formatted strings.
    */
   get strings() {
     let strings = {};
     let sb = Services.strings.createBundle(kDownloadsStringBundleUrl);
     let enumerator = sb.getSimpleEnumeration();
@@ -182,61 +181,24 @@ this.DownloadsCommon = {
         strings[stringName] = string.value;
       }
     }
     delete this.strings;
     return this.strings = strings;
   },
 
   /**
-   * Generates a very short string representing the given time left.
-   *
-   * @param aSeconds
-   *        Value to be formatted.  It represents the number of seconds, it must
-   *        be positive but does not need to be an integer.
-   *
-   * @return Formatted string, for example "30s" or "2h".  The returned value is
-   *         maximum three characters long, at least in English.
-   */
-  formatTimeLeft(aSeconds) {
-    // Decide what text to show for the time
-    let seconds = Math.round(aSeconds);
-    if (!seconds) {
-      return "";
-    } else if (seconds <= 30) {
-      return DownloadsCommon.strings["shortTimeLeftSeconds"](seconds);
-    }
-    let minutes = Math.round(aSeconds / 60);
-    if (minutes < 60) {
-      return DownloadsCommon.strings["shortTimeLeftMinutes"](minutes);
-    }
-    let hours = Math.round(minutes / 60);
-    if (hours < 48) { // two days
-      return DownloadsCommon.strings["shortTimeLeftHours"](hours);
-    }
-    let days = Math.round(hours / 24);
-    return DownloadsCommon.strings["shortTimeLeftDays"](Math.min(days, 99));
-  },
-
-  /**
    * Indicates whether we should show visual notification on the indicator
    * when a download event is triggered.
    */
   get animateNotifications() {
     return PrefObserver.animateNotifications;
   },
 
   /**
-   * Indicates whether we should show the dropmarker in the Downloads Panel.
-   */
-  get showPanelDropmarker() {
-    return PrefObserver.showPanelDropmarker;
-  },
-
-  /**
    * Get access to one of the DownloadsData or PrivateDownloadsData objects,
    * depending on the privacy status of the window in question.
    *
    * @param aWindow
    *        The browser window which owns the download button.
    */
   getData(aWindow) {
     if (PrivateBrowsingUtils.isContentWindowPrivate(aWindow)) {
@@ -292,37 +254,37 @@ this.DownloadsCommon = {
   _privateSummary: null,
 
   /**
    * Returns the legacy state integer value for the provided Download object.
    */
   stateOfDownload(download) {
     // Collapse state using the correct priority.
     if (!download.stopped) {
-      return nsIDM.DOWNLOAD_DOWNLOADING;
+      return DownloadsCommon.DOWNLOAD_DOWNLOADING;
     }
     if (download.succeeded) {
-      return nsIDM.DOWNLOAD_FINISHED;
+      return DownloadsCommon.DOWNLOAD_FINISHED;
     }
     if (download.error) {
       if (download.error.becauseBlockedByParentalControls) {
-        return nsIDM.DOWNLOAD_BLOCKED_PARENTAL;
+        return DownloadsCommon.DOWNLOAD_BLOCKED_PARENTAL;
       }
       if (download.error.becauseBlockedByReputationCheck) {
-        return nsIDM.DOWNLOAD_DIRTY;
+        return DownloadsCommon.DOWNLOAD_DIRTY;
       }
-      return nsIDM.DOWNLOAD_FAILED;
+      return DownloadsCommon.DOWNLOAD_FAILED;
     }
     if (download.canceled) {
       if (download.hasPartialData) {
-        return nsIDM.DOWNLOAD_PAUSED;
+        return DownloadsCommon.DOWNLOAD_PAUSED;
       }
-      return nsIDM.DOWNLOAD_CANCELED;
+      return DownloadsCommon.DOWNLOAD_CANCELED;
     }
-    return nsIDM.DOWNLOAD_NOTSTARTED;
+    return DownloadsCommon.DOWNLOAD_NOTSTARTED;
   },
 
   /**
    * Helper function required because the Downloads Panel and the Downloads View
    * don't share the controller yet.
    */
   removeAndFinalizeDownload(download) {
     Downloads.getList(Downloads.ALL)
@@ -1194,39 +1156,33 @@ DownloadsIndicatorDataCtor.prototype = {
         this.attention = DownloadsCommon.ATTENTION_SUCCESS;
       }
     } else if (download.error) {
       // Existing higher level attention indication trumps ATTENTION_WARNING.
       if (this._attention != DownloadsCommon.ATTENTION_SEVERE) {
         this.attention = DownloadsCommon.ATTENTION_WARNING;
       }
     }
-
-    // Since the state of a download changed, reset the estimated time left.
-    this._lastRawTimeLeft = -1;
-    this._lastTimeLeft = -1;
   },
 
   onDownloadChanged(download) {
     this._updateViews();
   },
 
   onDownloadRemoved(download) {
     this._itemCount--;
     this._updateViews();
   },
 
   // Propagation of properties to our views
 
   // The following properties are updated by _refreshProperties and are then
   // propagated to the views.  See _refreshProperties for details.
   _hasDownloads: false,
-  _counter: "",
   _percentComplete: -1,
-  _paused: false,
 
   /**
    * Indicates whether the download indicators should be highlighted.
    */
   set attention(aValue) {
     this._attention = aValue;
     this._updateViews();
     return aValue;
@@ -1261,47 +1217,29 @@ DownloadsIndicatorDataCtor.prototype = {
   /**
    * Updates the specified view with the current aggregate values.
    *
    * @param aView
    *        DownloadsIndicatorView object to be updated.
    */
   _updateView(aView) {
     aView.hasDownloads = this._hasDownloads;
-    aView.counter = this._counter;
     aView.percentComplete = this._percentComplete;
-    aView.paused = this._paused;
     aView.attention = this._attentionSuppressed ? DownloadsCommon.ATTENTION_NONE
                                                 : this._attention;
   },
 
   // Property updating based on current download status
 
   /**
    * Number of download items that are available to be displayed.
    */
   _itemCount: 0,
 
   /**
-   * Floating point value indicating the last number of seconds estimated until
-   * the longest download will finish.  We need to store this value so that we
-   * don't continuously apply smoothing if the actual download state has not
-   * changed.  This is set to -1 if the previous value is unknown.
-   */
-  _lastRawTimeLeft: -1,
-
-  /**
-   * Last number of seconds estimated until all in-progress downloads with a
-   * known size and speed will finish.  This value is stored to allow smoothing
-   * in case of small variations.  This is set to -1 if the previous value is
-   * unknown.
-   */
-  _lastTimeLeft: -1,
-
-  /**
    * A generator function for the Download objects this summary is currently
    * interested in. This generator is passed off to summarizeDownloads in order
    * to generate statistics about the downloads we care about - in this case,
    * it's all active downloads.
    */
   * _activeDownloads() {
     let downloads = this._isPrivate ? PrivateDownloadsData.downloads
                                     : DownloadsData.downloads;
@@ -1317,37 +1255,17 @@ DownloadsIndicatorDataCtor.prototype = {
    */
   _refreshProperties() {
     let summary =
       DownloadsCommon.summarizeDownloads(this._activeDownloads());
 
     // Determine if the indicator should be shown or get attention.
     this._hasDownloads = (this._itemCount > 0);
 
-    // If all downloads are paused, show the progress indicator as paused.
-    this._paused = summary.numActive > 0 &&
-                   summary.numActive == summary.numPaused;
-
     this._percentComplete = summary.percentComplete;
-
-    // Display the estimated time left, if present.
-    if (summary.rawTimeLeft == -1) {
-      // There are no downloads with a known time left.
-      this._lastRawTimeLeft = -1;
-      this._lastTimeLeft = -1;
-      this._counter = "";
-    } else {
-      // Compute the new time left only if state actually changed.
-      if (this._lastRawTimeLeft != summary.rawTimeLeft) {
-        this._lastRawTimeLeft = summary.rawTimeLeft;
-        this._lastTimeLeft = DownloadsCommon.smoothSeconds(summary.rawTimeLeft,
-                                                           this._lastTimeLeft);
-      }
-      this._counter = DownloadsCommon.formatTimeLeft(this._lastTimeLeft);
-    }
   }
 };
 
 XPCOMUtils.defineLazyGetter(this, "PrivateDownloadsIndicatorData", function() {
   return new DownloadsIndicatorDataCtor(true);
 });
 
 XPCOMUtils.defineLazyGetter(this, "DownloadsIndicatorData", function() {
--- a/browser/components/downloads/DownloadsViewUI.jsm
+++ b/browser/components/downloads/DownloadsViewUI.jsm
@@ -320,28 +320,28 @@ this.DownloadsViewUI.DownloadElementShel
   /**
    * Returns the name of the default command to use for the current state of the
    * download, when there is a double click or another default interaction. If
    * there is no default command for the current state, returns an empty string.
    * The commands are implemented as functions on this object or derived ones.
    */
   get currentDefaultCommandName() {
     switch (DownloadsCommon.stateOfDownload(this.download)) {
-      case Ci.nsIDownloadManager.DOWNLOAD_NOTSTARTED:
+      case DownloadsCommon.DOWNLOAD_NOTSTARTED:
         return "downloadsCmd_cancel";
-      case Ci.nsIDownloadManager.DOWNLOAD_FAILED:
-      case Ci.nsIDownloadManager.DOWNLOAD_CANCELED:
+      case DownloadsCommon.DOWNLOAD_FAILED:
+      case DownloadsCommon.DOWNLOAD_CANCELED:
         return "downloadsCmd_retry";
-      case Ci.nsIDownloadManager.DOWNLOAD_PAUSED:
+      case DownloadsCommon.DOWNLOAD_PAUSED:
         return "downloadsCmd_pauseResume";
-      case Ci.nsIDownloadManager.DOWNLOAD_FINISHED:
+      case DownloadsCommon.DOWNLOAD_FINISHED:
         return "downloadsCmd_open";
-      case Ci.nsIDownloadManager.DOWNLOAD_BLOCKED_PARENTAL:
+      case DownloadsCommon.DOWNLOAD_BLOCKED_PARENTAL:
         return "downloadsCmd_openReferrer";
-      case Ci.nsIDownloadManager.DOWNLOAD_DIRTY:
+      case DownloadsCommon.DOWNLOAD_DIRTY:
         return "downloadsCmd_showBlockedInfo";
     }
     return "";
   },
 
   /**
    * Returns true if the specified command can be invoked on the current item.
    * The commands are implemented as functions on this object or derived ones.
--- a/browser/components/downloads/content/allDownloadsViewOverlay.js
+++ b/browser/components/downloads/content/allDownloadsViewOverlay.js
@@ -25,18 +25,16 @@ XPCOMUtils.defineLazyModuleGetter(this, 
                                   "resource://gre/modules/Promise.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "RecentWindow",
                                   "resource:///modules/RecentWindow.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "Services",
                                   "resource://gre/modules/Services.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "Task",
                                   "resource://gre/modules/Task.jsm");
 
-const nsIDM = Ci.nsIDownloadManager;
-
 const DESTINATION_FILE_URI_ANNO  = "downloads/destinationFileURI";
 const DOWNLOAD_META_DATA_ANNO    = "downloads/metaData";
 
 /**
  * Represents a download from the browser history. It implements part of the
  * interface of the Download object.
  *
  * @param aPlacesNode
@@ -67,27 +65,27 @@ HistoryDownload.prototype = {
       this.target.path = Cc["@mozilla.org/network/protocol;1?name=file"]
                            .getService(Ci.nsIFileProtocolHandler)
                            .getFileFromURLSpec(metaData.targetFileSpec).path;
     } catch (ex) {
       this.target.path = undefined;
     }
 
     if ("state" in metaData) {
-      this.succeeded = metaData.state == nsIDM.DOWNLOAD_FINISHED;
-      this.canceled = metaData.state == nsIDM.DOWNLOAD_CANCELED ||
-                      metaData.state == nsIDM.DOWNLOAD_PAUSED;
+      this.succeeded = metaData.state == DownloadsCommon.DOWNLOAD_FINISHED;
+      this.canceled = metaData.state == DownloadsCommon.DOWNLOAD_CANCELED ||
+                      metaData.state == DownloadsCommon.DOWNLOAD_PAUSED;
       this.endTime = metaData.endTime;
 
       // Recreate partial error information from the state saved in history.
-      if (metaData.state == nsIDM.DOWNLOAD_FAILED) {
+      if (metaData.state == DownloadsCommon.DOWNLOAD_FAILED) {
         this.error = { message: "History download failed." };
-      } else if (metaData.state == nsIDM.DOWNLOAD_BLOCKED_PARENTAL) {
+      } else if (metaData.state == DownloadsCommon.DOWNLOAD_BLOCKED_PARENTAL) {
         this.error = { becauseBlockedByParentalControls: true };
-      } else if (metaData.state == nsIDM.DOWNLOAD_DIRTY) {
+      } else if (metaData.state == DownloadsCommon.DOWNLOAD_DIRTY) {
         this.error = {
           becauseBlockedByReputationCheck: true,
           reputationCheckVerdict: metaData.reputationCheckVerdict || "",
         };
       } else {
         this.error = null;
       }
 
--- a/browser/components/downloads/content/downloads.css
+++ b/browser/components/downloads/content/downloads.css
@@ -8,35 +8,29 @@ richlistitem[type="download"] {
   -moz-binding: url('chrome://browser/content/downloads/download.xml#download');
 }
 
 richlistitem[type="download"]:not([selected]) button {
   /* Only focus buttons in the selected item. */
   -moz-user-focus: none;
 }
 
-.downloadsHideDropmarker > #downloadsFooterButtonsSplitter,
-.downloadsHideDropmarker > #downloadsFooterDropmarker {
-  display: none;
-}
-
 richlistitem[type="download"].download-state[state="1"]:not([exists]) > .downloadButtonArea,
 richlistitem[type="download"].download-state[state="1"]:not([exists]) > toolbarseparator {
   display: none;
 }
 
 #downloadsSummary:not([inprogress]) > vbox > #downloadsSummaryProgress,
 #downloadsSummary:not([inprogress]) > vbox > #downloadsSummaryDetails,
 #downloadsFooter:not([showingsummary]) #downloadsSummary {
   display: none;
 }
 
-#downloadsFooter[showingdropdown] > stack > #downloadsSummary,
 #downloadsFooter[showingsummary] > stack:hover > #downloadsSummary,
-#downloadsFooter[showingsummary]:not([showingdropdown]) > stack:not(:hover) > #downloadsFooterButtons {
+#downloadsFooter[showingsummary] > stack:not(:hover) > #downloadsFooterButtons {
   /* If we used "visibility: hidden;" then the mouseenter event of
      #downloadsHistory wouldn't be triggered immediately, and the hover styling
      of the button would not apply until the mouse is moved again.
 
      "-moz-user-focus: ignore;" prevents the elements with "opacity: 0;" from
      being focused with the keyboard. */
   opacity: 0;
   -moz-user-focus: ignore;
--- a/browser/components/downloads/content/downloads.js
+++ b/browser/components/downloads/content/downloads.js
@@ -219,24 +219,16 @@ const DownloadsPanel = {
 
     if (this.isPanelShowing) {
       DownloadsCommon.log("Panel is already showing - focusing instead.");
       this._focusPanel();
       return;
     }
 
     this.initialize(() => {
-      let downloadsFooterButtons =
-        document.getElementById("downloadsFooterButtons");
-      if (DownloadsCommon.showPanelDropmarker) {
-        downloadsFooterButtons.classList.remove("downloadsHideDropmarker");
-      } else {
-        downloadsFooterButtons.classList.add("downloadsHideDropmarker");
-      }
-
       // Delay displaying the panel because this function will sometimes be
       // called while another window is closing (like the window for selecting
       // whether to save or open the file), and that would cause the panel to
       // close immediately.
       setTimeout(() => this._openPopupIfDataReady(), 0);
     });
 
     DownloadsCommon.log("Waiting for the downloads panel to appear.");
@@ -382,55 +374,30 @@ const DownloadsPanel = {
 
     // Allow the anchor to be hidden.
     DownloadsButton.releaseAnchor();
 
     // Allow the panel to be reopened.
     this._state = this.kStateHidden;
   },
 
-  onFooterPopupShowing(aEvent) {
-    let itemClearList = document.getElementById("downloadsDropdownItemClearList");
-    if (DownloadsCommon.getData(window).canRemoveFinished) {
-      itemClearList.removeAttribute("hidden");
-    } else {
-      itemClearList.setAttribute("hidden", "true");
-    }
-    DownloadsViewController.updateCommands();
-
-    document.getElementById("downloadsFooter")
-      .setAttribute("showingdropdown", true);
-  },
-
-  onFooterPopupHidden(aEvent) {
-    document.getElementById("downloadsFooter")
-      .removeAttribute("showingdropdown");
-  },
-
   // Related operations
 
   /**
    * Shows or focuses the user interface dedicated to downloads history.
    */
   showDownloadsHistory() {
     DownloadsCommon.log("Showing download history.");
     // Hide the panel before showing another window, otherwise focus will return
     // to the browser window when the panel closes automatically.
     this.hidePanel();
 
     BrowserDownloadsUI();
   },
 
-  openDownloadsFolder() {
-    Downloads.getPreferredDownloadsDirectory().then(downloadsPath => {
-      DownloadsCommon.showDirectory(new FileUtils.File(downloadsPath));
-    }).catch(Cu.reportError);
-    this.hidePanel();
-  },
-
   // Internal functions
 
   /**
    * Attach event listeners to a panel element. These listeners should be
    * removed in _unattachEventListeners. This is called automatically after the
    * panel has successfully loaded.
    */
   _attachEventListeners() {
--- a/browser/components/downloads/content/downloadsOverlay.xul
+++ b/browser/components/downloads/content/downloadsOverlay.xul
@@ -149,33 +149,16 @@
               </hbox>
               <hbox id="downloadsFooterButtons">
                 <button id="downloadsHistory"
                         class="downloadsPanelFooterButton"
                         label="&downloadsHistory.label;"
                         accesskey="&downloadsHistory.accesskey;"
                         flex="1"
                         oncommand="DownloadsPanel.showDownloadsHistory();"/>
-                <toolbarseparator id="downloadsFooterButtonsSplitter"
-                        class="downloadsDropmarkerSplitter"/>
-                <button id="downloadsFooterDropmarker"
-                        class="downloadsPanelFooterButton downloadsDropmarker"
-                        type="menu">
-                  <menupopup id="downloadSubPanel"
-                             onpopupshowing="DownloadsPanel.onFooterPopupShowing(event);"
-                             onpopuphidden="DownloadsPanel.onFooterPopupHidden(event);"
-                             position="after_end">
-                    <menuitem id="downloadsDropdownItemClearList"
-                              command="downloadsCmd_clearList"
-                              label="&cmd.clearList2.label;"/>
-                    <menuitem id="downloadsDropdownItemOpenDownloadsFolder"
-                              oncommand="DownloadsPanel.openDownloadsFolder();"
-                              label="&openDownloadsFolder.label;"/>
-                  </menupopup>
-                </button>
               </hbox>
             </stack>
           </vbox>
         </panelview>
 
         <panelview id="downloadsPanel-blockedSubview"
                    orient="vertical"
                    flex="1">
--- a/browser/components/downloads/content/indicator.js
+++ b/browser/components/downloads/content/indicator.js
@@ -211,48 +211,35 @@ const DownloadsIndicatorView = {
    * Prepares the downloads indicator to be displayed.
    */
   ensureInitialized() {
     if (this._initialized) {
       return;
     }
     this._initialized = true;
 
-    this._setIndicatorType();
     window.addEventListener("unload", this.onWindowUnload);
     DownloadsCommon.getIndicatorData(window).addView(this);
   },
 
-  _setIndicatorType() {
-    // We keep a killerswitch for old-styled progressbar for now. Corresponding
-    // css class is added here to reflect the type chosen for showing progress.
-    let node = CustomizableUI.getWidget("downloads-button")
-                             .forWindow(window).node;
-
-    node.classList.toggle("withProgressBar",
-                          !DownloadsCommon.arrowStyledIndicator);
-  },
-
   /**
    * Frees the internal resources related to the indicator.
    */
   ensureTerminated() {
     if (!this._initialized) {
       return;
     }
     this._initialized = false;
 
     window.removeEventListener("unload", this.onWindowUnload);
     DownloadsCommon.getIndicatorData(window).removeView(this);
 
     // Reset the view properties, so that a neutral indicator is displayed if we
     // are visible only temporarily as an anchor.
-    this.counter = "";
     this.percentComplete = 0;
-    this.paused = false;
     this.attention = DownloadsCommon.ATTENTION_NONE;
   },
 
   /**
    * Ensures that the user interface elements required to display the indicator
    * are loaded, then invokes the given callback.
    */
   _ensureOperational(aCallback) {
@@ -394,45 +381,18 @@ const DownloadsIndicatorView = {
     return aValue;
   },
   get hasDownloads() {
     return this._hasDownloads;
   },
   _hasDownloads: false,
 
   /**
-   * Status text displayed in the indicator.  If this is set to an empty value,
-   * then the small downloads icon is displayed instead of the text.
-   */
-  set counter(aValue) {
-    if (!this._operational) {
-      return this._counter;
-    }
-
-    if (this._counter !== aValue) {
-      this._counter = aValue;
-      if (this._counter)
-        this.indicator.setAttribute("counter", "true");
-      else
-        this.indicator.removeAttribute("counter");
-      // We have to set the attribute instead of using the property because the
-      // XBL binding isn't applied if the element is invisible for any reason.
-      this._indicatorCounter.setAttribute("value", aValue);
-    }
-    return aValue;
-  },
-  _counter: null,
-
-  /**
    * Progress indication to display, from 0 to 100, or -1 if unknown.
-   * Bar-type:
-   *   The progress bar is hidden if the current progress is unknown and no
-   *   status text is set in the "counter" property.
-   * Arrow-type:
-   *   progress is not visible if the current progress is unknown.
+   * Progress is not visible if the current progress is unknown.
    */
   set percentComplete(aValue) {
     if (!this._operational) {
       return this._percentComplete;
     }
 
     if (this._percentComplete !== aValue) {
       this._percentComplete = aValue;
@@ -443,47 +403,22 @@ const DownloadsIndicatorView = {
         // For arrow type only:
         // We set animationDelay to a minus value (0s ~ -100s) to show the
         // corresponding frame needed for progress.
         this._progressIcon.style.animationDelay = (-this._percentComplete) + "s";
       } else {
         this.indicator.removeAttribute("progress");
         this._progressIcon.style.animationDelay = "1s";
       }
-      // We have to set the attribute instead of using the property because the
-      // XBL binding isn't applied if the element is invisible for any reason.
-      this._indicatorProgress.setAttribute("value", Math.max(aValue, 0));
     }
     return aValue;
   },
   _percentComplete: null,
 
   /**
-   * Indicates whether the progress won't advance because of a paused state.
-   * Setting this property forces a paused progress bar to be displayed, even if
-   * the current progress information is unavailable.
-   */
-  set paused(aValue) {
-    if (!this._operational) {
-      return this._paused;
-    }
-
-    if (this._paused != aValue) {
-      this._paused = aValue;
-      if (this._paused) {
-        this.indicator.setAttribute("paused", "true")
-      } else {
-        this.indicator.removeAttribute("paused");
-      }
-    }
-    return aValue;
-  },
-  _paused: false,
-
-  /**
    * Set when the indicator should draw user attention to itself.
    */
   set attention(aValue) {
     if (!this._operational) {
       return this._attention;
     }
     if (this._attention != aValue) {
       this._attention = aValue;
@@ -495,17 +430,17 @@ const DownloadsIndicatorView = {
   _refreshAttention() {
     // Check if the downloads button is in the menu panel, to determine which
     // button needs to get a badge.
     let widgetGroup = CustomizableUI.getWidget("downloads-button");
     let inMenu = widgetGroup.areaType == CustomizableUI.TYPE_MENU_PANEL;
 
     // For arrow-Styled indicator, suppress success attention if we have
     // progress in toolbar
-    let suppressAttention = DownloadsCommon.arrowStyledIndicator && !inMenu &&
+    let suppressAttention = !inMenu &&
       this._attention == DownloadsCommon.ATTENTION_SUCCESS &&
       this._percentComplete >= 0;
 
     if (suppressAttention || this._attention == DownloadsCommon.ATTENTION_NONE) {
       this.indicator.removeAttribute("attention");
       if (inMenu) {
         PanelUI.removeNotification(/^download-/);
       }
@@ -561,18 +496,16 @@ const DownloadsIndicatorView = {
       handled = true;
     }
     if (handled) {
       aEvent.preventDefault();
     }
   },
 
   _indicator: null,
-  __indicatorCounter: null,
-  __indicatorProgress: null,
   __progressIcon: null,
 
   /**
    * Returns a reference to the main indicator element, or null if the element
    * is not present in the browser window yet.
    */
   get indicator() {
     if (this._indicator) {
@@ -591,41 +524,29 @@ const DownloadsIndicatorView = {
     let widget = CustomizableUI.getWidget("downloads-button")
                                .forWindow(window);
     if (widget.overflowed) {
       return widget.anchor;
     }
     return document.getElementById("downloads-indicator-anchor");
   },
 
-  get _indicatorCounter() {
-    return this.__indicatorCounter ||
-      (this.__indicatorCounter = document.getElementById("downloads-indicator-counter"));
-  },
-
-  get _indicatorProgress() {
-    return this.__indicatorProgress ||
-      (this.__indicatorProgress = document.getElementById("downloads-indicator-progress"));
-  },
-
   get _progressIcon() {
     return this.__progressIcon ||
       (this.__progressIcon = document.getElementById("downloads-indicator-progress-icon"));
   },
 
   get notifier() {
     return this._notifier ||
       (this._notifier = document.getElementById("downloads-notification-anchor"));
   },
 
   _onCustomizedAway() {
     this._indicator = null;
     this.__progressIcon = null;
-    this.__indicatorCounter = null;
-    this.__indicatorProgress = null;
   },
 
   afterCustomize() {
     // If the cached indicator is not the one currently in the document,
     // invalidate our references
     if (this._indicator != document.getElementById("downloads-button")) {
       this._onCustomizedAway();
       this._operational = false;
--- a/browser/components/downloads/content/indicatorOverlay.xul
+++ b/browser/components/downloads/content/indicatorOverlay.xul
@@ -23,16 +23,11 @@
          always be visible and must not move or resize when the indicator
          state changes, otherwise the panel could change its position or lose
          its arrow unexpectedly. -->
     <stack id="downloads-indicator-anchor"
            consumeanchor="downloads-button">
       <stack id="downloads-indicator-icon">
         <vbox id="downloads-indicator-progress-icon"/>
       </stack>
-      <vbox id="downloads-indicator-progress-area" pack="center">
-        <description id="downloads-indicator-counter"/>
-        <progressmeter id="downloads-indicator-progress" class="plain"
-                       min="0" max="100"/>
-      </vbox>
     </stack>
   </toolbarbutton>
 </overlay>
--- a/browser/components/downloads/moz.build
+++ b/browser/components/downloads/moz.build
@@ -2,17 +2,16 @@
 # vim: set filetype=python:
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 with Files('*'):
     BUG_COMPONENT = ('Firefox', 'Downloads Panel')
 
-XPCSHELL_TESTS_MANIFESTS += ['test/unit/xpcshell.ini']
 BROWSER_CHROME_MANIFESTS += ['test/browser/browser.ini']
 
 JAR_MANIFESTS += ['jar.mn']
 
 EXTRA_JS_MODULES += [
     'DownloadsCommon.jsm',
     'DownloadsTaskbar.jsm',
     'DownloadsViewUI.jsm',
--- a/browser/components/downloads/test/browser/browser.ini
+++ b/browser/components/downloads/test/browser/browser.ini
@@ -7,10 +7,9 @@ skip-if = os == "linux" # Bug 949434
 [browser_overflow_anchor.js]
 skip-if = os == "linux" # Bug 952422
 [browser_confirm_unblock_download.js]
 [browser_iframe_gone_mid_download.js]
 [browser_indicatorDrop.js]
 [browser_libraryDrop.js]
 [browser_downloads_panel_block.js]
 skip-if = true # Bug 1352792
-[browser_downloads_panel_footer.js]
 [browser_downloads_panel_height.js]
--- a/browser/components/downloads/test/browser/browser_basic_functionality.js
+++ b/browser/components/downloads/test/browser/browser_basic_functionality.js
@@ -9,21 +9,21 @@ registerCleanupFunction(function*() {
 
 /**
  * Make sure the downloads panel can display items in the right order and
  * contains the expected data.
  */
 add_task(function* test_basic_functionality() {
   // Display one of each download state.
   const DownloadData = [
-    { state: nsIDM.DOWNLOAD_NOTSTARTED },
-    { state: nsIDM.DOWNLOAD_PAUSED },
-    { state: nsIDM.DOWNLOAD_FINISHED },
-    { state: nsIDM.DOWNLOAD_FAILED },
-    { state: nsIDM.DOWNLOAD_CANCELED },
+    { state: DownloadsCommon.DOWNLOAD_NOTSTARTED },
+    { state: DownloadsCommon.DOWNLOAD_PAUSED },
+    { state: DownloadsCommon.DOWNLOAD_FINISHED },
+    { state: DownloadsCommon.DOWNLOAD_FAILED },
+    { state: DownloadsCommon.DOWNLOAD_CANCELED },
   ];
 
   // Wait for focus first
   yield promiseFocus();
 
   // Ensure that state is reset in case previous tests didn't finish.
   yield task_resetState();
 
--- a/browser/components/downloads/test/browser/browser_downloads_panel_block.js
+++ b/browser/components/downloads/test/browser/browser_downloads_panel_block.js
@@ -134,17 +134,17 @@ function promisePanelHidden() {
     DownloadsPanel.panel.addEventListener("popuphidden", function() {
       setTimeout(resolve, 0);
     }, {once: true});
   });
 }
 
 function makeDownload(verdict) {
   return {
-    state: nsIDM.DOWNLOAD_DIRTY,
+    state: DownloadsCommon.DOWNLOAD_DIRTY,
     hasBlockedData: true,
     errorObj: {
       result: Components.results.NS_ERROR_FAILURE,
       message: "Download blocked.",
       becauseBlocked: true,
       becauseBlockedByReputationCheck: true,
       reputationCheckVerdict:  verdict,
     },
deleted file mode 100644
--- a/browser/components/downloads/test/browser/browser_downloads_panel_footer.js
+++ /dev/null
@@ -1,95 +0,0 @@
-"use strict";
-
-function *task_openDownloadsSubPanel() {
-  let downloadSubPanel = document.getElementById("downloadSubPanel");
-  let popupShownPromise = BrowserTestUtils.waitForEvent(downloadSubPanel, "popupshown");
-
-  let downloadsDropmarker = document.getElementById("downloadsFooterDropmarker");
-  EventUtils.synthesizeMouseAtCenter(downloadsDropmarker, {}, window);
-
-  yield popupShownPromise;
-}
-
-add_task(function* test_openDownloadsFolder() {
-  yield SpecialPowers.pushPrefEnv({"set": [["browser.download.showPanelDropmarker", true]]});
-  yield task_openPanel();
-
-  yield task_openDownloadsSubPanel();
-
-  yield new Promise(resolve => {
-    sinon.stub(DownloadsCommon, "showDirectory", file => {
-      resolve(Downloads.getPreferredDownloadsDirectory().then(downloadsPath => {
-        is(file.path, downloadsPath, "Check the download folder path.");
-      }));
-    });
-
-    let itemOpenDownloadsFolder =
-      document.getElementById("downloadsDropdownItemOpenDownloadsFolder");
-    EventUtils.synthesizeMouseAtCenter(itemOpenDownloadsFolder, {}, window);
-  });
-
-  yield task_resetState();
-});
-
-add_task(function* test_clearList() {
-  const kTestCases = [{
-    downloads: [
-      { state: nsIDM.DOWNLOAD_NOTSTARTED },
-      { state: nsIDM.DOWNLOAD_FINISHED },
-      { state: nsIDM.DOWNLOAD_FAILED },
-      { state: nsIDM.DOWNLOAD_CANCELED },
-    ],
-    expectClearListShown: true,
-    expectedItemNumber: 0,
-  }, {
-    downloads: [
-      { state: nsIDM.DOWNLOAD_NOTSTARTED },
-      { state: nsIDM.DOWNLOAD_FINISHED },
-      { state: nsIDM.DOWNLOAD_FAILED },
-      { state: nsIDM.DOWNLOAD_PAUSED },
-      { state: nsIDM.DOWNLOAD_CANCELED },
-    ],
-    expectClearListShown: true,
-    expectedItemNumber: 1,
-  }, {
-    downloads: [
-      { state: nsIDM.DOWNLOAD_PAUSED },
-    ],
-    expectClearListShown: false,
-    expectedItemNumber: 1,
-  }];
-
-  for (let testCase of kTestCases) {
-    yield verify_clearList(testCase);
-  }
-});
-
-function *verify_clearList(testCase) {
-  let downloads = testCase.downloads;
-  yield task_addDownloads(downloads);
-
-  yield task_openPanel();
-  is(DownloadsView._downloads.length, downloads.length,
-    "Expect the number of download items");
-
-  yield task_openDownloadsSubPanel();
-
-  let itemClearList = document.getElementById("downloadsDropdownItemClearList");
-  let itemNumberPromise = BrowserTestUtils.waitForCondition(() => {
-    return DownloadsView._downloads.length === testCase.expectedItemNumber;
-  });
-  if (testCase.expectClearListShown) {
-    isnot("true", itemClearList.getAttribute("hidden"),
-      "Should show Clear Preview Panel button");
-    EventUtils.synthesizeMouseAtCenter(itemClearList, {}, window);
-  } else {
-    is("true", itemClearList.getAttribute("hidden"),
-      "Should not show Clear Preview Panel button");
-  }
-
-  yield itemNumberPromise;
-  is(DownloadsView._downloads.length, testCase.expectedItemNumber,
-    "Download items remained.");
-
-  yield task_resetState();
-}
--- a/browser/components/downloads/test/browser/browser_downloads_panel_height.js
+++ b/browser/components/downloads/test/browser/browser_downloads_panel_height.js
@@ -5,17 +5,17 @@
 
 /**
  * This test exists because we use a <panelmultiview> element and it handles
  * some of the height changes for us. We need to verify that the height is
  * updated correctly if downloads are removed while the panel is hidden.
  */
 add_task(function* test_height_reduced_after_removal() {
   yield task_addDownloads([
-    { state: nsIDM.DOWNLOAD_FINISHED },
+    { state: DownloadsCommon.DOWNLOAD_FINISHED },
   ]);
 
   yield task_openPanel();
   let panel = document.getElementById("downloadsPanel");
   let heightBeforeRemoval = panel.getBoundingClientRect().height;
 
   // We want to close the panel before we remove the download from the list.
   DownloadsPanel.hidePanel();
--- a/browser/components/downloads/test/browser/head.js
+++ b/browser/components/downloads/test/browser/head.js
@@ -19,18 +19,16 @@ XPCOMUtils.defineLazyModuleGetter(this, 
                                   "resource://gre/modules/Promise.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "Task",
                                   "resource://gre/modules/Task.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
                                   "resource://gre/modules/PlacesUtils.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "HttpServer",
     "resource://testing-common/httpd.js");
 
-const nsIDM = Ci.nsIDownloadManager;
-
 var gTestTargetFile = FileUtils.getFile("TmpD", ["dm-ui-test.file"]);
 gTestTargetFile.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, FileUtils.PERMS_FILE);
 
 // Load mocking/stubbing library, sinon
 // docs: http://sinonjs.org/docs/
 /* global sinon:false */
 Services.scriptloader.loadSubScript("resource://testing-common/sinon-1.16.1.js");
 
@@ -158,21 +156,22 @@ function* task_addDownloads(aItems) {
   for (let item of aItems) {
     let download = {
       source: {
         url: "http://www.example.com/test-download.txt",
       },
       target: {
         path: gTestTargetFile.path,
       },
-      succeeded: item.state == nsIDM.DOWNLOAD_FINISHED,
-      canceled: item.state == nsIDM.DOWNLOAD_CANCELED ||
-                item.state == nsIDM.DOWNLOAD_PAUSED,
-      error: item.state == nsIDM.DOWNLOAD_FAILED ? new Error("Failed.") : null,
-      hasPartialData: item.state == nsIDM.DOWNLOAD_PAUSED,
+      succeeded: item.state == DownloadsCommon.DOWNLOAD_FINISHED,
+      canceled: item.state == DownloadsCommon.DOWNLOAD_CANCELED ||
+                item.state == DownloadsCommon.DOWNLOAD_PAUSED,
+      error: item.state == DownloadsCommon.DOWNLOAD_FAILED ?
+             new Error("Failed.") : null,
+      hasPartialData: item.state == DownloadsCommon.DOWNLOAD_PAUSED,
       hasBlockedData: item.hasBlockedData || false,
       startTime: new Date(startTimeMs++),
     };
     // `"errorObj" in download` must be false when there's no error.
     if (item.errorObj) {
       download.errorObj = item.errorObj;
     }
     yield publicList.add(yield Downloads.createDownload(download));
deleted file mode 100644
--- a/browser/components/downloads/test/unit/.eslintrc.js
+++ /dev/null
@@ -1,7 +0,0 @@
-"use strict";
-
-module.exports = {
-  "extends": [
-    "plugin:mozilla/xpcshell-test"
-  ]
-};
deleted file mode 100644
--- a/browser/components/downloads/test/unit/head.js
+++ /dev/null
@@ -1,17 +0,0 @@
-/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
-/* vim: set ts=2 et sw=2 tw=80: */
-/* Any copyright is dedicated to the Public Domain.
-   http://creativecommons.org/publicdomain/zero/1.0/ */
-
-/**
- * Provides infrastructure for automated download components tests.
- */
-
-// Globals
-
-var Cc = Components.classes;
-var Ci = Components.interfaces;
-var Cu = Components.utils;
-var Cr = Components.results;
-
-Cu.import("resource:///modules/DownloadsCommon.jsm");
deleted file mode 100644
--- a/browser/components/downloads/test/unit/test_DownloadsCommon.js
+++ /dev/null
@@ -1,35 +0,0 @@
-/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
-/* vim: set ts=2 et sw=2 tw=80: */
-/* Any copyright is dedicated to the Public Domain.
-   http://creativecommons.org/publicdomain/zero/1.0/ */
-
-/**
- * Tests for the functions located directly in the "DownloadsCommon" object.
- */
-
-function testFormatTimeLeft(aSeconds, aExpectedValue, aExpectedUnitString) {
-  let expected = "";
-  if (aExpectedValue) {
-    // Format the expected result based on the current language.
-    expected = DownloadsCommon.strings[aExpectedUnitString](aExpectedValue);
-  }
-  do_check_eq(DownloadsCommon.formatTimeLeft(aSeconds), expected);
-}
-
-function run_test() {
-  testFormatTimeLeft(0, "", "");
-  testFormatTimeLeft(1, "1", "shortTimeLeftSeconds");
-  testFormatTimeLeft(29, "29", "shortTimeLeftSeconds");
-  testFormatTimeLeft(30, "30", "shortTimeLeftSeconds");
-  testFormatTimeLeft(31, "1", "shortTimeLeftMinutes");
-  testFormatTimeLeft(60, "1", "shortTimeLeftMinutes");
-  testFormatTimeLeft(89, "1", "shortTimeLeftMinutes");
-  testFormatTimeLeft(90, "2", "shortTimeLeftMinutes");
-  testFormatTimeLeft(91, "2", "shortTimeLeftMinutes");
-  testFormatTimeLeft(3600, "1", "shortTimeLeftHours");
-  testFormatTimeLeft(86400, "24", "shortTimeLeftHours");
-  testFormatTimeLeft(169200, "47", "shortTimeLeftHours");
-  testFormatTimeLeft(172800, "2", "shortTimeLeftDays");
-  testFormatTimeLeft(8553600, "99", "shortTimeLeftDays");
-  testFormatTimeLeft(8640000, "99", "shortTimeLeftDays");
-}
deleted file mode 100644
--- a/browser/components/downloads/test/unit/xpcshell.ini
+++ /dev/null
@@ -1,6 +0,0 @@
-[DEFAULT]
-head = head.js
-firefox-appdir = browser
-skip-if = toolkit == 'android'
-
-[test_DownloadsCommon.js]
--- a/browser/components/extensions/ext-browserAction.js
+++ b/browser/components/extensions/ext-browserAction.js
@@ -280,16 +280,20 @@ this.browserAction = class extends Exten
       case "mouseout":
         if (this.pendingPopup) {
           this.clearPopup();
         }
         break;
 
 
       case "popupshowing":
+        if (!global.actionContextMenu) {
+          break;
+        }
+
         const menu = event.target;
         const trigger = menu.triggerNode;
         const node = window.document.getElementById(this.id);
         const contexts = ["toolbar-context-menu", "customizationPanelItemContextMenu"];
 
         if (contexts.includes(menu.id) && node && isAncestorOrSelf(node, trigger)) {
           global.actionContextMenu({
             extension: this.extension,
@@ -596,9 +600,8 @@ this.browserAction = class extends Exten
           return Promise.resolve(color || [0xd9, 0, 0, 255]);
         },
       },
     };
   }
 };
 
 global.browserActionFor = this.browserAction.for;
-
--- a/browser/components/extensions/ext-pageAction.js
+++ b/browser/components/extensions/ext-pageAction.js
@@ -192,16 +192,20 @@ this.pageAction = class extends Extensio
     switch (event.type) {
       case "click":
         if (event.button === 0) {
           this.handleClick(window);
         }
         break;
 
       case "popupshowing":
+        if (!global.actionContextMenu) {
+          break;
+        }
+
         const menu = event.target;
         const trigger = menu.triggerNode;
 
         if (menu.id === "toolbar-context-menu" && trigger && trigger.id === this.id) {
           global.actionContextMenu({
             extension: this.extension,
             onPageAction: true,
             menu: menu,
--- a/browser/components/places/PlacesUIUtils.jsm
+++ b/browser/components/places/PlacesUIUtils.jsm
@@ -27,19 +27,16 @@ XPCOMUtils.defineLazyModuleGetter(this, 
                                   "resource:///modules/RecentWindow.jsm");
 
 // PlacesUtils exposes multiple symbols, so we can't use defineLazyModuleGetter.
 Cu.import("resource://gre/modules/PlacesUtils.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "PlacesTransactions",
                                   "resource://gre/modules/PlacesTransactions.jsm");
 
-XPCOMUtils.defineLazyModuleGetter(this, "CloudSync",
-                                  "resource://gre/modules/CloudSync.jsm");
-
 XPCOMUtils.defineLazyModuleGetter(this, "Weave",
                                   "resource://services-sync/main.js");
 
 const gInContentProcess = Services.appinfo.processType == Ci.nsIXULRuntime.PROCESS_TYPE_CONTENT;
 const FAVICON_REQUEST_TIMEOUT = 60 * 1000;
 // Map from windows to arrays of data about pending favicon loads.
 let gFaviconLoadDataMap = new Map();
 
--- a/browser/components/preferences/in-content-old/tests/browser_subdialogs.js
+++ b/browser/components/preferences/in-content-old/tests/browser_subdialogs.js
@@ -19,16 +19,17 @@ function* open_subdialog_and_test_generi
     let win = content.window;
     let subdialog = win.gSubDialog;
     subdialog.open(args.url, null, rv);
 
     info("waiting for subdialog DOMFrameContentLoaded");
     yield ContentTaskUtils.waitForEvent(win, "DOMFrameContentLoaded", true);
     let result;
     if (args.domcontentloadedFnStr) {
+      // eslint-disable-next-line no-eval
       result = eval(args.domcontentloadedFnStr);
     }
 
     info("waiting for subdialog load");
     yield ContentTaskUtils.waitForEvent(subdialog._frame, "load");
     info("subdialog window is loaded");
 
     let expectedStyleSheetURLs = subdialog._injectedStyleSheets.slice(0);
--- a/browser/components/preferences/in-content/tests/browser_subdialogs.js
+++ b/browser/components/preferences/in-content/tests/browser_subdialogs.js
@@ -19,16 +19,17 @@ function* open_subdialog_and_test_generi
     let win = content.window;
     let subdialog = win.gSubDialog;
     subdialog.open(args.url, null, rv);
 
     info("waiting for subdialog DOMFrameContentLoaded");
     yield ContentTaskUtils.waitForEvent(win, "DOMFrameContentLoaded", true);
     let result;
     if (args.domcontentloadedFnStr) {
+      // eslint-disable-next-line no-eval
       result = eval(args.domcontentloadedFnStr);
     }
 
     info("waiting for subdialog load");
     yield ContentTaskUtils.waitForEvent(subdialog._frame, "load");
     info("subdialog window is loaded");
 
     let expectedStyleSheetURLs = subdialog._injectedStyleSheets.slice(0);
--- a/browser/components/sessionstore/test/browser_async_remove_tab.js
+++ b/browser/components/sessionstore/test/browser_async_remove_tab.js
@@ -33,16 +33,17 @@ function restoreClosedTabWithValue(rval)
 }
 
 function promiseNewLocationAndHistoryEntryReplaced(browser, snippet) {
   return ContentTask.spawn(browser, snippet, function* (codeSnippet) {
     let webNavigation = docShell.QueryInterface(Ci.nsIWebNavigation);
     let shistory = webNavigation.sessionHistory;
 
     // Evaluate the snippet that the changes the location.
+    // eslint-disable-next-line no-eval
     eval(codeSnippet);
 
     return new Promise(resolve => {
       let listener = {
         OnHistoryReplaceEntry() {
           shistory.removeSHistoryListener(this);
           resolve();
         },
--- a/browser/extensions/formautofill/FormAutofillContent.jsm
+++ b/browser/extensions/formautofill/FormAutofillContent.jsm
@@ -101,63 +101,63 @@ AutofillProfileAutoCompleteSearch.protot
         onSearchResult: (search, result) => {
           listener.onSearchResult(this, result);
           ProfileAutocomplete.setProfileAutoCompleteResult(result);
         },
       });
       return;
     }
 
-    this._getProfiles({info, searchString}).then((profiles) => {
+    this._getAddresses({info, searchString}).then((addresses) => {
       if (this.forceStop) {
         return;
       }
 
       let allFieldNames = FormAutofillContent.getAllFieldNames(focusedInput);
       let result = new ProfileAutoCompleteResult(searchString,
                                                  info.fieldName,
                                                  allFieldNames,
-                                                 profiles,
+                                                 addresses,
                                                  {});
 
       listener.onSearchResult(this, result);
       ProfileAutocomplete.setProfileAutoCompleteResult(result);
     });
   },
 
   /**
    * Stops an asynchronous search that is in progress
    */
   stopSearch() {
     ProfileAutocomplete.setProfileAutoCompleteResult(null);
     this.forceStop = true;
   },
 
   /**
-   * Get the profile data from parent process for AutoComplete result.
+   * Get the address data from parent process for AutoComplete result.
    *
    * @private
    * @param  {Object} data
    *         Parameters for querying the corresponding result.
    * @param  {string} data.searchString
-   *         The typed string for filtering out the matched profile.
+   *         The typed string for filtering out the matched address.
    * @param  {string} data.info
    *         The input autocomplete property's information.
    * @returns {Promise}
-   *          Promise that resolves when profiles returned from parent process.
+   *          Promise that resolves when addresses returned from parent process.
    */
-  _getProfiles(data) {
-    this.log.debug("_getProfiles with data:", data);
+  _getAddresses(data) {
+    this.log.debug("_getAddresses with data:", data);
     return new Promise((resolve) => {
-      Services.cpmm.addMessageListener("FormAutofill:Profiles", function getResult(result) {
-        Services.cpmm.removeMessageListener("FormAutofill:Profiles", getResult);
+      Services.cpmm.addMessageListener("FormAutofill:Addresses", function getResult(result) {
+        Services.cpmm.removeMessageListener("FormAutofill:Addresses", getResult);
         resolve(result.data);
       });
 
-      Services.cpmm.sendAsyncMessage("FormAutofill:GetProfiles", data);
+      Services.cpmm.sendAsyncMessage("FormAutofill:GetAddresses", data);
     });
   },
 };
 
 this.NSGetFactory = XPCOMUtils.generateNSGetFactory([AutofillProfileAutoCompleteSearch]);
 
 let ProfileAutocomplete = {
   QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver]),
@@ -405,16 +405,23 @@ var FormAutofillContent = {
     // Collects root forms from inputs.
     for (let field of doc.getElementsByTagName("input")) {
       // We only consider text-like fields for now until we support radio and
       // checkbox buttons in the future.
       if (!field.mozIsTextField(true)) {
         continue;
       }
 
+      // For now skip consider fields in forms we've already seen before even
+      // if the specific field wasn't seen before. Ideally whether the field is
+      // already in the handler's form details would be considered.
+      if (this.getFormHandler(field)) {
+        continue;
+      }
+
       let formLike = FormLikeFactory.createFromField(field);
       if (!forms.some(form => form.rootElement === formLike.rootElement)) {
         forms.push(formLike);
       }
     }
 
     this.log.debug("Found", forms.length, "forms");
 
--- a/browser/extensions/formautofill/FormAutofillParent.jsm
+++ b/browser/extensions/formautofill/FormAutofillParent.jsm
@@ -71,19 +71,19 @@ FormAutofillParent.prototype = {
    */
   init() {
     log.debug("init");
     let storePath = OS.Path.join(OS.Constants.Path.profileDir, PROFILE_JSON_FILE_NAME);
     this._profileStore = new ProfileStorage(storePath);
     this._profileStore.initialize();
 
     Services.obs.addObserver(this, "advanced-pane-loaded");
-    Services.ppmm.addMessageListener("FormAutofill:GetProfiles", this);
-    Services.ppmm.addMessageListener("FormAutofill:SaveProfile", this);
-    Services.ppmm.addMessageListener("FormAutofill:RemoveProfiles", this);
+    Services.ppmm.addMessageListener("FormAutofill:GetAddresses", this);
+    Services.ppmm.addMessageListener("FormAutofill:SaveAddress", this);
+    Services.ppmm.addMessageListener("FormAutofill:RemoveAddresses", this);
 
     // Observing the pref and storage changes
     Services.prefs.addObserver(ENABLED_PREF, this);
     Services.obs.addObserver(this, "formautofill-storage-changed");
 
     // Force to trigger the onStatusChanged function for setting listeners properly
     // while initizlization
     this._setStatus(this._getStatus());
@@ -177,29 +177,29 @@ FormAutofillParent.prototype = {
    * Handles the message coming from FormAutofillContent.
    *
    * @param   {string} message.name The name of the message.
    * @param   {object} message.data The data of the message.
    * @param   {nsIFrameMessageManager} message.target Caller's message manager.
    */
   receiveMessage({name, data, target}) {
     switch (name) {
-      case "FormAutofill:GetProfiles": {
-        this._getProfiles(data, target);
+      case "FormAutofill:GetAddresses": {
+        this._getAddresses(data, target);
         break;
       }
-      case "FormAutofill:SaveProfile": {
+      case "FormAutofill:SaveAddress": {
         if (data.guid) {
-          this.getProfileStore().update(data.guid, data.profile);
+          this.getProfileStore().update(data.guid, data.address);
         } else {
-          this.getProfileStore().add(data.profile);
+          this.getProfileStore().add(data.address);
         }
         break;
       }
-      case "FormAutofill:RemoveProfiles": {
+      case "FormAutofill:RemoveAddresses": {
         data.guids.forEach(guid => this.getProfileStore().remove(guid));
         break;
       }
     }
   },
 
   /**
    * Returns the instance of ProfileStorage. To avoid syncing issues, anyone
@@ -218,56 +218,57 @@ FormAutofillParent.prototype = {
    * @private
    */
   _uninit() {
     if (this._profileStore) {
       this._profileStore._saveImmediately();
       this._profileStore = null;
     }
 
-    Services.ppmm.removeMessageListener("FormAutofill:GetProfiles", this);
-    Services.ppmm.removeMessageListener("FormAutofill:SaveProfile", this);
-    Services.ppmm.removeMessageListener("FormAutofill:RemoveProfiles", this);
+    Services.ppmm.removeMessageListener("FormAutofill:GetAddresses", this);
+    Services.ppmm.removeMessageListener("FormAutofill:SaveAddress", this);
+    Services.ppmm.removeMessageListener("FormAutofill:RemoveAddresses", this);
     Services.obs.removeObserver(this, "advanced-pane-loaded");
     Services.prefs.removeObserver(ENABLED_PREF, this);
   },
 
   /**
-   * Get the profile data from profile store and return profiles back to content process.
+   * Get the address data from profile store and return addresses back to content
+   * process.
    *
    * @private
    * @param  {string} data.searchString
-   *         The typed string for filtering out the matched profile.
+   *         The typed string for filtering out the matched address.
    * @param  {string} data.info
    *         The input autocomplete property's information.
    * @param  {nsIFrameMessageManager} target
    *         Content's message manager.
    */
-  _getProfiles({searchString, info}, target) {
-    let profiles = [];
+  _getAddresses({searchString, info}, target) {
+    let addresses = [];
 
     if (info && info.fieldName) {
-      profiles = this._profileStore.getByFilter({searchString, info});
+      addresses = this._profileStore.getByFilter({searchString, info});
     } else {
-      profiles = this._profileStore.getAll();
+      addresses = this._profileStore.getAll();
     }
 
-    target.sendAsyncMessage("FormAutofill:Profiles", profiles);
+    target.sendAsyncMessage("FormAutofill:Addresses", addresses);
   },
 
   _updateSavedFieldNames() {
     if (!Services.ppmm.initialProcessData.autofillSavedFieldNames) {
       Services.ppmm.initialProcessData.autofillSavedFieldNames = new Set();
     } else {
       Services.ppmm.initialProcessData.autofillSavedFieldNames.clear();
     }
 
-    this._profileStore.getAll().forEach((profile) => {
-      Object.keys(profile).forEach((fieldName) => {
-        if (!profile[fieldName]) {
+    this._profileStore.getAll().forEach((address) => {
+      Object.keys(address).forEach((fieldName) => {
+        if (!address[fieldName]) {
           return;
         }
         Services.ppmm.initialProcessData.autofillSavedFieldNames.add(fieldName);
       });
     });
 
     // Remove the internal guid and metadata fields.
     this._profileStore.INTERNAL_FIELDS.forEach((fieldName) => {
--- a/browser/extensions/formautofill/ProfileStorage.jsm
+++ b/browser/extensions/formautofill/ProfileStorage.jsm
@@ -6,35 +6,36 @@
  * Implements an interface of the storage of Form Autofill.
  *
  * The data is stored in JSON format, without indentation and the computed
  * fields, using UTF-8 encoding. With indentation and computed fields applied,
  * the schema would look like this:
  *
  * {
  *   version: 1,
- *   profiles: [
+ *   addresses: [
  *     {
- *       guid,             // 12 character...
+ *       guid,             // 12 characters
  *
- *       // profile
+ *       // address fields
  *       given-name,
  *       additional-name,
  *       family-name,
  *       organization,     // Company
  *       street-address,   // (Multiline)
  *       address-level2,   // City/Town
  *       address-level1,   // Province (Standardized code if possible)
  *       postal-code,
  *       country,          // ISO 3166
  *       tel,
  *       email,
  *
  *       // computed fields (These fields are not stored in the file as they are
  *       // generated at runtime.)
+ *       name,
  *       address-line1,
  *       address-line2,
  *       address-line3,
  *
  *       // metadata
  *       timeCreated,      // in ms
  *       timeLastUsed,     // in ms
  *       timeLastModified, // in ms
@@ -82,36 +83,22 @@ const VALID_FIELDS = [
   "address-level2",
   "address-level1",
   "postal-code",
   "country",
   "tel",
   "email",
 ];
 
-// TODO: Remove this once we can add profile from preference.
-const MOCK_MODE = false;
-const MOCK_STORAGE = [{
-  guid: "test-guid-1",
-  organization: "Sesame Street",
-  "street-address": "123 Sesame Street.",
-  tel: "1-345-345-3456",
-}, {
-  guid: "test-guid-2",
-  organization: "Mozilla",
-  "street-address": "331 E. Evelyn Avenue",
-  tel: "1-650-903-0800",
-}];
-
 function ProfileStorage(path) {
   this._path = path;
 }
 
 ProfileStorage.prototype = {
-  // These fields are defined internally for each profile.
+  // These fields are defined internally for each record.
   INTERNAL_FIELDS:
     ["guid", "timeCreated", "timeLastUsed", "timeLastModified", "timesUsed"],
   /**
    * Loads the profile data from file to memory.
    *
    * @returns {Promise}
    * @resolves When the operation finished successfully.
    * @rejects  JavaScript exception.
@@ -120,273 +107,273 @@ ProfileStorage.prototype = {
     this._store = new JSONFile({
       path: this._path,
       dataPostProcessor: this._dataPostProcessor.bind(this),
     });
     return this._store.load();
   },
 
   /**
-   * Adds a new profile.
+   * Adds a new address.
    *
-   * @param {Profile} profile
-   *        The new profile for saving.
+   * @param {Address} address
+   *        The new address for saving.
    */
-  add(profile) {
-    log.debug("add:", profile);
+  add(address) {
+    log.debug("add:", address);
     this._store.ensureDataReady();
 
-    let profileToSave = this._clone(profile);
-    this._normalizeProfile(profileToSave);
+    let addressToSave = this._clone(address);
+    this._normalizeAddress(addressToSave);
 
-    profileToSave.guid = gUUIDGenerator.generateUUID().toString()
+    addressToSave.guid = gUUIDGenerator.generateUUID().toString()
                                        .replace(/[{}-]/g, "").substring(0, 12);
 
     // Metadata
     let now = Date.now();
-    profileToSave.timeCreated = now;
-    profileToSave.timeLastModified = now;
-    profileToSave.timeLastUsed = 0;
-    profileToSave.timesUsed = 0;
+    addressToSave.timeCreated = now;
+    addressToSave.timeLastModified = now;
+    addressToSave.timeLastUsed = 0;
+    addressToSave.timesUsed = 0;
 
-    this._store.data.profiles.push(profileToSave);
+    this._store.data.addresses.push(addressToSave);
 
     this._store.saveSoon();
     Services.obs.notifyObservers(null, "formautofill-storage-changed", "add");
   },
 
   /**
-   * Update the specified profile.
+   * Update the specified address.
    *
    * @param  {string} guid
-   *         Indicates which profile to update.
-   * @param  {Profile} profile
-   *         The new profile used to overwrite the old one.
+   *         Indicates which address to update.
+   * @param  {Address} address
+   *         The new address used to overwrite the old one.
    */
-  update(guid, profile) {
-    log.debug("update:", guid, profile);
+  update(guid, address) {
+    log.debug("update:", guid, address);
     this._store.ensureDataReady();
 
-    let profileFound = this._findByGUID(guid);
-    if (!profileFound) {
-      throw new Error("No matching profile.");
+    let addressFound = this._findByGUID(guid);
+    if (!addressFound) {
+      throw new Error("No matching record.");
     }
 
-    let profileToUpdate = this._clone(profile);
-    this._normalizeProfile(profileToUpdate);
+    let addressToUpdate = this._clone(address);
+    this._normalizeAddress(addressToUpdate);
 
     for (let field of VALID_FIELDS) {
-      if (profileToUpdate[field] !== undefined) {
-        profileFound[field] = profileToUpdate[field];
+      if (addressToUpdate[field] !== undefined) {
+        addressFound[field] = addressToUpdate[field];
       } else {
-        delete profileFound[field];
+        delete addressFound[field];
       }
     }
 
-    profileFound.timeLastModified = Date.now();
+    addressFound.timeLastModified = Date.now();
 
     this._store.saveSoon();
     Services.obs.notifyObservers(null, "formautofill-storage-changed", "update");
   },
 
   /**
-   * Notifies the stroage of the use of the specified profile, so we can update
+   * Notifies the stroage of the use of the specified address, so we can update
    * the metadata accordingly.
    *
    * @param  {string} guid
-   *         Indicates which profile to be notified.
+   *         Indicates which address to be notified.
    */
   notifyUsed(guid) {
     this._store.ensureDataReady();
 
-    let profileFound = this._findByGUID(guid);
-    if (!profileFound) {
-      throw new Error("No matching profile.");
+    let addressFound = this._findByGUID(guid);
+    if (!addressFound) {
+      throw new Error("No matching record.");
     }
 
-    profileFound.timesUsed++;
-    profileFound.timeLastUsed = Date.now();
+    addressFound.timesUsed++;
+    addressFound.timeLastUsed = Date.now();
 
     this._store.saveSoon();
     Services.obs.notifyObservers(null, "formautofill-storage-changed", "notifyUsed");
   },
 
   /**
-   * Removes the specified profile. No error occurs if the profile isn't found.
+   * Removes the specified address. No error occurs if the address isn't found.
    *
    * @param  {string} guid
-   *         Indicates which profile to remove.
+   *         Indicates which address to remove.
    */
   remove(guid) {
     log.debug("remove:", guid);
     this._store.ensureDataReady();
 
-    this._store.data.profiles =
-      this._store.data.profiles.filter(profile => profile.guid != guid);
+    this._store.data.addresses =
+      this._store.data.addresses.filter(address => address.guid != guid);
     this._store.saveSoon();
     Services.obs.notifyObservers(null, "formautofill-storage-changed", "remove");
   },
 
   /**
-   * Returns the profile with the specified GUID.
+   * Returns the address with the specified GUID.
    *
    * @param   {string} guid
-   *          Indicates which profile to retrieve.
-   * @returns {Profile}
-   *          A clone of the profile.
+   *          Indicates which address to retrieve.
+   * @returns {Address}
+   *          A clone of the address.
    */
   get(guid) {
     log.debug("get:", guid);
     this._store.ensureDataReady();
 
-    let profileFound = this._findByGUID(guid);
-    if (!profileFound) {
-      throw new Error("No matching profile.");
+    let addressFound = this._findByGUID(guid);
+    if (!addressFound) {
+      throw new Error("No matching record.");
     }
 
-    // Profile is cloned to avoid accidental modifications from outside.
-    let clonedProfile = this._clone(profileFound);
-    this._computeFields(clonedProfile);
-    return clonedProfile;
+    // The record is cloned to avoid accidental modifications from outside.
+    let clonedAddress = this._clone(addressFound);
+    this._computeFields(clonedAddress);
+    return clonedAddress;
   },
 
   /**
-   * Returns all profiles.
+   * Returns all addresses.
    *
-   * @returns {Array.<Profile>}
-   *          An array containing clones of all profiles.
+   * @returns {Array.<Address>}
+   *          An array containing clones of all addresses.
    */
   getAll() {
     log.debug("getAll");
     this._store.ensureDataReady();
 
-    // Profiles are cloned to avoid accidental modifications from outside.
-    let clonedProfiles = this._store.data.profiles.map(this._clone);
-    clonedProfiles.forEach(this._computeFields);
-    return clonedProfiles;
+    // Records are cloned to avoid accidental modifications from outside.
+    let clonedAddresses = this._store.data.addresses.map(this._clone);
+    clonedAddresses.forEach(this._computeFields);
+    return clonedAddresses;
   },
 
   /**
-   * Returns the filtered profiles based on input's information and searchString.
+   * Returns the filtered addresses based on input's information and searchString.
    *
-   * @returns {Array.<Profile>}
-   *          An array containing clones of matched profiles.
+   * @returns {Array.<Address>}
+   *          An array containing clones of matched addresses.
    */
   getByFilter({info, searchString}) {
     log.debug("getByFilter:", info, searchString);
 
     let lcSearchString = searchString.toLowerCase();
-    let result = this.getAll().filter(profile => {
+    let result = this.getAll().filter(address => {
       // Return true if string is not provided and field exists.
       // TODO: We'll need to check if the address is for billing or shipping.
       //       (Bug 1358941)
-      let name = profile[info.fieldName];
+      let name = address[info.fieldName];
 
       if (!searchString) {
         return !!name;
       }
 
       return name.toLowerCase().startsWith(lcSearchString);
     });
 
     log.debug("getByFilter: Returning", result.length, "result(s)");
     return result;
   },
 
-  _clone(profile) {
-    return Object.assign({}, profile);
+  _clone(record) {
+    return Object.assign({}, record);
   },
 
   _findByGUID(guid) {
-    return this._store.data.profiles.find(profile => profile.guid == guid);
+    return this._store.data.addresses.find(address => address.guid == guid);
   },
 
-  _computeFields(profile) {
+  _computeFields(address) {
     // Compute name
-    profile.name = FormAutofillNameUtils.joinNameParts({
-      given: profile["given-name"],
-      middle: profile["additional-name"],
-      family: profile["family-name"],
+    address.name = FormAutofillNameUtils.joinNameParts({
+      given: address["given-name"],
+      middle: address["additional-name"],
+      family: address["family-name"],
     });
 
     // Compute address
-    if (profile["street-address"]) {
-      let streetAddress = profile["street-address"].split("\n");
+    if (address["street-address"]) {
+      let streetAddress = address["street-address"].split("\n");
       // TODO: we should prevent the dataloss by concatenating the rest of lines
       //       with a locale-specific character in the future (bug 1360114).
       for (let i = 0; i < 3; i++) {
         if (streetAddress[i]) {
-          profile["address-line" + (i + 1)] = streetAddress[i];
+          address["address-line" + (i + 1)] = streetAddress[i];
         }
       }
     }
   },
 
-  _normalizeAddress(profile) {
-    if (profile["address-line1"] || profile["address-line2"] ||
-        profile["address-line3"]) {
+  _normalizeAddressLines(address) {
+    if (address["address-line1"] || address["address-line2"] ||
+        address["address-line3"]) {
       // Treat "street-address" as "address-line1" if it contains only one line
       // and "address-line1" is omitted.
-      if (!profile["address-line1"] && profile["street-address"] &&
-          !profile["street-address"].includes("\n")) {
-        profile["address-line1"] = profile["street-address"];
-        delete profile["street-address"];
+      if (!address["address-line1"] && address["street-address"] &&
+          !address["street-address"].includes("\n")) {
+        address["address-line1"] = address["street-address"];
+        delete address["street-address"];
       }
 
       // Remove "address-line*" but keep the values.
       let addressLines = [1, 2, 3].map(i => {
-        let value = profile["address-line" + i];
-        delete profile["address-line" + i];
+        let value = address["address-line" + i];
+        delete address["address-line" + i];
         return value;
       });
 
       // Concatenate "address-line*" if "street-address" is omitted.
-      if (!profile["street-address"]) {
-        profile["street-address"] = addressLines.join("\n");
+      if (!address["street-address"]) {
+        address["street-address"] = addressLines.join("\n");
       }
     }
   },
 
-  _normalizeName(profile) {
-    if (!profile.name) {
+  _normalizeName(address) {
+    if (!address.name) {
       return;
     }
 
-    let nameParts = FormAutofillNameUtils.splitName(profile.name);
-    if (!profile["given-name"] && nameParts.given) {
-      profile["given-name"] = nameParts.given;
+    let nameParts = FormAutofillNameUtils.splitName(address.name);
+    if (!address["given-name"] && nameParts.given) {
+      address["given-name"] = nameParts.given;
     }
-    if (!profile["additional-name"] && nameParts.middle) {
-      profile["additional-name"] = nameParts.middle;
+    if (!address["additional-name"] && nameParts.middle) {
+      address["additional-name"] = nameParts.middle;
     }
-    if (!profile["family-name"] && nameParts.family) {
-      profile["family-name"] = nameParts.family;
+    if (!address["family-name"] && nameParts.family) {
+      address["family-name"] = nameParts.family;
     }
-    delete profile.name;
+    delete address.name;
   },
 
-  _normalizeProfile(profile) {
-    this._normalizeName(profile);
-    this._normalizeAddress(profile);
+  _normalizeAddress(address) {
+    this._normalizeName(address);
+    this._normalizeAddressLines(address);
 
-    for (let key in profile) {
+    for (let key in address) {
       if (!VALID_FIELDS.includes(key)) {
         throw new Error(`"${key}" is not a valid field.`);
       }
-      if (typeof profile[key] !== "string" &&
-          typeof profile[key] !== "number") {
+      if (typeof address[key] !== "string" &&
+          typeof address[key] !== "number") {
         throw new Error(`"${key}" contains invalid data type.`);
       }
     }
   },
 
   _dataPostProcessor(data) {
     data.version = SCHEMA_VERSION;
-    if (!data.profiles) {
-      data.profiles = MOCK_MODE ? MOCK_STORAGE : [];
+    if (!data.addresses) {
+      data.addresses = [];
     }
     return data;
   },
 
   // For test only.
   _saveImmediately() {
     return this._store._save();
   },
--- a/browser/extensions/formautofill/content/FormAutofillFrameScript.js
+++ b/browser/extensions/formautofill/content/FormAutofillFrameScript.js
@@ -18,37 +18,37 @@ Cu.import("resource://formautofill/FormA
 
 /**
  * Handles content's interactions for the frame.
  *
  * NOTE: Declares it by "var" to make it accessible in unit tests.
  */
 var FormAutofillFrameScript = {
   init() {
-    addEventListener("DOMContentLoaded", this);
+    addEventListener("focusin", this);
     addMessageListener("FormAutofill:PreviewProfile", this);
     addMessageListener("FormAutoComplete:PopupClosed", this);
   },
 
   handleEvent(evt) {
     if (!evt.isTrusted) {
       return;
     }
 
     if (!Services.prefs.getBoolPref("browser.formautofill.enabled")) {
       return;
     }
 
     switch (evt.type) {
-      case "DOMContentLoaded": {
-        let doc = evt.target;
-        if (!(doc instanceof Ci.nsIDOMHTMLDocument)) {
+      case "focusin": {
+        let element = evt.target;
+        if (!(element instanceof Ci.nsIDOMHTMLInputElement)) {
           return;
         }
-        FormAutofillContent.identifyAutofillFields(doc);
+        FormAutofillContent.identifyAutofillFields(element.ownerDocument);
         break;
       }
     }
   },
 
   receiveMessage(message) {
     if (!Services.prefs.getBoolPref("browser.formautofill.enabled")) {
       return;
--- a/browser/extensions/formautofill/content/editProfile.js
+++ b/browser/extensions/formautofill/content/editProfile.js
@@ -4,61 +4,61 @@
 
 "use strict";
 
 const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
 
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://formautofill/FormAutofillUtils.jsm");
 
-function EditDialog(profile) {
-  this._profile = profile;
+function EditDialog(address) {
+  this._address = address;
   window.addEventListener("DOMContentLoaded", this, {once: true});
 }
 
 EditDialog.prototype = {
   init() {
     this.refs = {
       controlsContainer: document.getElementById("controls-container"),
       cancel: document.getElementById("cancel"),
       save: document.getElementById("save"),
     };
     this.attachEventListeners();
   },
 
   /**
-   * Asks FormAutofillParent to save or update a profile.
+   * Asks FormAutofillParent to save or update an address.
    * @param  {object} data
    *         {
    *           {string} guid [optional]
-   *           {object} profile
+   *           {object} address
    *         }
    */
-  saveProfile(data) {
-    Services.cpmm.sendAsyncMessage("FormAutofill:SaveProfile", data);
+  saveAddress(data) {
+    Services.cpmm.sendAsyncMessage("FormAutofill:SaveAddress", data);
   },
 
   /**
-   * Fill the form with a profile object.
-   * @param  {object} profile
+   * Fill the form with an address object.
+   * @param  {object} address
    */
-  loadInitialValues(profile) {
-    for (let field in profile) {
+  loadInitialValues(address) {
+    for (let field in address) {
       let input = document.getElementById(field);
       if (input) {
-        input.value = profile[field];
+        input.value = address[field];
       }
     }
   },
 
   /**
    * Get inputs from the form.
    * @returns {object}
    */
-  buildProfileObject() {
+  buildAddressObject() {
     return Array.from(document.forms[0].elements).reduce((obj, input) => {
       if (input.value) {
         obj[input.id] = input.value;
       }
       return obj;
     }, {});
   },
 
@@ -66,29 +66,29 @@ EditDialog.prototype = {
    * Handle events
    *
    * @param  {DOMEvent} event
    */
   handleEvent(event) {
     switch (event.type) {
       case "DOMContentLoaded": {
         this.init();
-        if (this._profile) {
-          this.loadInitialValues(this._profile);
+        if (this._address) {
+          this.loadInitialValues(this._address);
         }
         break;
       }
       case "click": {
         this.handleClick(event);
         break;
       }
       case "input": {
         // Toggle disabled attribute on the save button based on
         // whether the form is filled or empty.
-        if (Object.keys(this.buildProfileObject()).length == 0) {
+        if (Object.keys(this.buildAddressObject()).length == 0) {
           this.refs.save.setAttribute("disabled", true);
         } else {
           this.refs.save.removeAttribute("disabled");
         }
         break;
       }
     }
   },
@@ -99,24 +99,24 @@ EditDialog.prototype = {
    * @param  {DOMEvent} event
    */
   handleClick(event) {
     if (event.target == this.refs.cancel) {
       this.detachEventListeners();
       window.close();
     }
     if (event.target == this.refs.save) {
-      if (this._profile) {
-        this.saveProfile({
-          guid: this._profile.guid,
-          profile: this.buildProfileObject(),
+      if (this._address) {
+        this.saveAddress({
+          guid: this._address.guid,
+          address: this.buildAddressObject(),
         });
       } else {
-        this.saveProfile({
-          profile: this.buildProfileObject(),
+        this.saveAddress({
+          address: this.buildAddressObject(),
         });
       }
       this.detachEventListeners();
       window.close();
     }
   },
 
   /**
--- a/browser/extensions/formautofill/content/manageProfiles.js
+++ b/browser/extensions/formautofill/content/manageProfiles.js
@@ -20,126 +20,126 @@ function ManageProfileDialog() {
 
 ManageProfileDialog.prototype = {
   QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports, Ci.nsIObserver]),
 
   _elements: {},
 
   /**
    * Count the number of "formautofill-storage-changed" events epected to
-   * receive to prevent repeatedly loading profiles.
+   * receive to prevent repeatedly loading addresses.
    * @type {number}
    */
   _pendingChangeCount: 0,
 
   /**
-   * Get the selected options on the profiles element.
+   * Get the selected options on the addresses element.
    *
    * @returns {array<DOMElement>}
    */
   get _selectedOptions() {
-    return Array.from(this._elements.profiles.selectedOptions);
+    return Array.from(this._elements.addresses.selectedOptions);
   },
 
   init() {
     this._elements = {
-      profiles: document.getElementById("profiles"),
+      addresses: document.getElementById("profiles"),
       controlsContainer: document.getElementById("controls-container"),
       remove: document.getElementById("remove"),
       add: document.getElementById("add"),
       edit: document.getElementById("edit"),
     };
     this.attachEventListeners();
   },
 
   uninit() {
     log.debug("uninit");
     this.detachEventListeners();
     this._elements = null;
   },
 
   /**
-   * Load profiles and render them.
+   * Load addresses and render them.
    *
    * @returns {promise}
    */
-  loadProfiles() {
-    return this.getProfiles().then(profiles => {
-      log.debug("profiles:", profiles);
+  loadAddresses() {
+    return this.getAddresses().then(addresses => {
+      log.debug("addresses:", addresses);
       // Sort by last modified time starting with most recent
-      profiles.sort((a, b) => b.timeLastModified - a.timeLastModified);
-      this.renderProfileElements(profiles);
+      addresses.sort((a, b) => b.timeLastModified - a.timeLastModified);
+      this.renderAddressElements(addresses);
       this.updateButtonsStates(this._selectedOptions.length);
     });
   },
 
   /**
-   * Get profiles from storage.
+   * Get addresses from storage.
    *
    * @returns {promise}
    */
-  getProfiles() {
+  getAddresses() {
     return new Promise(resolve => {
-      Services.cpmm.addMessageListener("FormAutofill:Profiles", function getResult(result) {
-        Services.cpmm.removeMessageListener("FormAutofill:Profiles", getResult);
+      Services.cpmm.addMessageListener("FormAutofill:Addresses", function getResult(result) {
+        Services.cpmm.removeMessageListener("FormAutofill:Addresses", getResult);
         resolve(result.data);
       });
-      Services.cpmm.sendAsyncMessage("FormAutofill:GetProfiles", {});
+      Services.cpmm.sendAsyncMessage("FormAutofill:GetAddresses", {});
     });
   },
 
   /**
-   * Render the profiles onto the page while maintaining selected options if
+   * Render the addresses onto the page while maintaining selected options if
    * they still exist.
    *
-   * @param  {array<object>} profiles
+   * @param  {array<object>} addresses
    */
-  renderProfileElements(profiles) {
+  renderAddressElements(addresses) {
     let selectedGuids = this._selectedOptions.map(option => option.value);
-    this.clearProfileElements();
-    for (let profile of profiles) {
-      let option = new Option(this.getProfileLabel(profile),
-                              profile.guid,
+    this.clearAddressElements();
+    for (let address of addresses) {
+      let option = new Option(this.getAddressLabel(address),
+                              address.guid,
                               false,
-                              selectedGuids.includes(profile.guid));
-      option.profile = profile;
-      this._elements.profiles.appendChild(option);
+                              selectedGuids.includes(address.guid));
+      option.address = address;
+      this._elements.addresses.appendChild(option);
     }
   },
 
   /**
-   * Remove all existing profile elements.
+   * Remove all existing address elements.
    */
-  clearProfileElements() {
-    let parent = this._elements.profiles;
+  clearAddressElements() {
+    let parent = this._elements.addresses;
     while (parent.lastChild) {
       parent.removeChild(parent.lastChild);
     }
   },
 
   /**
-   * Remove profiles by guids.
+   * Remove addresses by guids.
    * Keep track of the number of "formautofill-storage-changed" events to
-   * ignore before loading profiles.
+   * ignore before loading addresses.
    *
    * @param  {array<string>} guids
    */
-  removeProfiles(guids) {
+  removeAddresses(guids) {
     this._pendingChangeCount += guids.length - 1;
-    Services.cpmm.sendAsyncMessage("FormAutofill:RemoveProfiles", {guids});
+    Services.cpmm.sendAsyncMessage("FormAutofill:RemoveAddresses", {guids});
   },
 
   /**
-   * Get profile display label. It should display up to two pieces of
+   * Get address display label. It should display up to two pieces of
    * information, separated by a comma.
    *
-   * @param  {object} profile
+   * @param  {object} address
    * @returns {string}
    */
-  getProfileLabel(profile) {
+  getAddressLabel(address) {
     // TODO: Implement a smarter way for deciding what to display
     //       as option text. Possibly improve the algorithm in
     //       ProfileAutoCompleteResult.jsm and reuse it here.
     const fieldOrder = [
       "name",
       "street-address",  // Street address
       "address-level2",  // City/Town
       "organization",    // Company or organization name
@@ -147,36 +147,36 @@ ManageProfileDialog.prototype = {
       "country",         // Country
       "postal-code",     // Postal code
       "tel",             // Phone number
       "email",           // Email address
     ];
 
     let parts = [];
     for (const fieldName of fieldOrder) {
-      let string = profile[fieldName];
+      let string = address[fieldName];
       if (string) {
         parts.push(string);
       }
       if (parts.length == 2) {
         break;
       }
     }
     return parts.join(", ");
   },
 
   /**
-   * Open the edit profile dialog to create/edit a profile.
+   * Open the edit address dialog to create/edit an address.
    *
-   * @param  {object} profile [optional]
+   * @param  {object} address [optional]
    */
-  openEditDialog(profile) {
+  openEditDialog(address) {
     window.openDialog(EDIT_PROFILE_URL, null,
                       "chrome,centerscreen,modal,width=600,height=450",
-                      profile);
+                      address);
   },
 
   /**
    * Enable/disable the Edit and Remove buttons based on number of selected
    * options.
    *
    * @param  {number} selectedCount
    */
@@ -198,17 +198,17 @@ ManageProfileDialog.prototype = {
    * Handle events
    *
    * @param  {DOMEvent} event
    */
   handleEvent(event) {
     switch (event.type) {
       case "DOMContentLoaded": {
         this.init();
-        this.loadProfiles();
+        this.loadAddresses();
         break;
       }
       case "click": {
         this.handleClick(event);
         break;
       }
       case "change": {
         this.updateButtonsStates(this._selectedOptions.length);
@@ -223,49 +223,49 @@ ManageProfileDialog.prototype = {
 
   /**
    * Handle click events
    *
    * @param  {DOMEvent} event
    */
   handleClick(event) {
     if (event.target == this._elements.remove) {
-      this.removeProfiles(this._selectedOptions.map(option => option.value));
+      this.removeAddresses(this._selectedOptions.map(option => option.value));
     } else if (event.target == this._elements.add) {
       this.openEditDialog();
     } else if (event.target == this._elements.edit) {
-      this.openEditDialog(this._selectedOptions[0].profile);
+      this.openEditDialog(this._selectedOptions[0].address);
     }
   },
 
   observe(subject, topic, data) {
     switch (topic) {
       case "formautofill-storage-changed": {
         if (this._pendingChangeCount) {
           this._pendingChangeCount -= 1;
           return;
         }
-        this.loadProfiles();
+        this.loadAddresses();
       }
     }
   },
 
   /**
    * Attach event listener
    */
   attachEventListeners() {
     window.addEventListener("unload", this, {once: true});
-    this._elements.profiles.addEventListener("change", this);
+    this._elements.addresses.addEventListener("change", this);
     this._elements.controlsContainer.addEventListener("click", this);
     Services.obs.addObserver(this, "formautofill-storage-changed");
   },
 
   /**
    * Remove event listener
    */
   detachEventListeners() {
-    this._elements.profiles.removeEventListener("change", this);
+    this._elements.addresses.removeEventListener("change", this);
     this._elements.controlsContainer.removeEventListener("click", this);
     Services.obs.removeObserver(this, "formautofill-storage-changed");
   },
 };
 
 new ManageProfileDialog();
--- a/browser/extensions/formautofill/test/browser/browser_editProfileDialog.js
+++ b/browser/extensions/formautofill/test/browser/browser_editProfileDialog.js
@@ -1,90 +1,90 @@
 "use strict";
 
 registerCleanupFunction(function* () {
-  let profiles = yield getProfiles();
-  if (profiles.length) {
-    yield removeProfiles(profiles.map(profile => profile.guid));
+  let addresses = yield getAddresses();
+  if (addresses.length) {
+    yield removeAddresses(addresses.map(address => address.guid));
   }
 });
 
 add_task(function* test_cancelEditProfileDialog() {
   yield new Promise(resolve => {
     let win = window.openDialog(EDIT_PROFILE_DIALOG_URL, null, null, null);
     win.addEventListener("load", () => {
       win.addEventListener("unload", () => {
         ok(true, "Edit profile dialog is closed");
         resolve();
       }, {once: true});
       win.document.querySelector("#cancel").click();
     }, {once: true});
   });
 });
 
-add_task(function* test_saveProfile() {
+add_task(function* test_saveAddress() {
   yield new Promise(resolve => {
     let win = window.openDialog(EDIT_PROFILE_DIALOG_URL, null, null, null);
     win.addEventListener("load", () => {
       win.addEventListener("unload", () => {
         ok(true, "Edit profile dialog is closed");
         resolve();
       }, {once: true});
       EventUtils.synthesizeKey("VK_TAB", {}, win);
-      EventUtils.synthesizeKey(TEST_PROFILE_1["given-name"], {}, win);
+      EventUtils.synthesizeKey(TEST_ADDRESS_1["given-name"], {}, win);
       EventUtils.synthesizeKey("VK_TAB", {}, win);
-      EventUtils.synthesizeKey(TEST_PROFILE_1["additional-name"], {}, win);
+      EventUtils.synthesizeKey(TEST_ADDRESS_1["additional-name"], {}, win);
       EventUtils.synthesizeKey("VK_TAB", {}, win);
-      EventUtils.synthesizeKey(TEST_PROFILE_1["family-name"], {}, win);
+      EventUtils.synthesizeKey(TEST_ADDRESS_1["family-name"], {}, win);
       EventUtils.synthesizeKey("VK_TAB", {}, win);
-      EventUtils.synthesizeKey(TEST_PROFILE_1.organization, {}, win);
+      EventUtils.synthesizeKey(TEST_ADDRESS_1.organization, {}, win);
       EventUtils.synthesizeKey("VK_TAB", {}, win);
-      EventUtils.synthesizeKey(TEST_PROFILE_1["street-address"], {}, win);
+      EventUtils.synthesizeKey(TEST_ADDRESS_1["street-address"], {}, win);
       EventUtils.synthesizeKey("VK_TAB", {}, win);
-      EventUtils.synthesizeKey(TEST_PROFILE_1["address-level2"], {}, win);
+      EventUtils.synthesizeKey(TEST_ADDRESS_1["address-level2"], {}, win);
       EventUtils.synthesizeKey("VK_TAB", {}, win);
-      EventUtils.synthesizeKey(TEST_PROFILE_1["address-level1"], {}, win);
+      EventUtils.synthesizeKey(TEST_ADDRESS_1["address-level1"], {}, win);
       EventUtils.synthesizeKey("VK_TAB", {}, win);
-      EventUtils.synthesizeKey(TEST_PROFILE_1["postal-code"], {}, win);
+      EventUtils.synthesizeKey(TEST_ADDRESS_1["postal-code"], {}, win);
       EventUtils.synthesizeKey("VK_TAB", {}, win);
-      EventUtils.synthesizeKey(TEST_PROFILE_1.country, {}, win);
+      EventUtils.synthesizeKey(TEST_ADDRESS_1.country, {}, win);
       EventUtils.synthesizeKey("VK_TAB", {}, win);
-      EventUtils.synthesizeKey(TEST_PROFILE_1.email, {}, win);
+      EventUtils.synthesizeKey(TEST_ADDRESS_1.email, {}, win);
       EventUtils.synthesizeKey("VK_TAB", {}, win);
-      EventUtils.synthesizeKey(TEST_PROFILE_1.tel, {}, win);
+      EventUtils.synthesizeKey(TEST_ADDRESS_1.tel, {}, win);
       EventUtils.synthesizeKey("VK_TAB", {}, win);
       EventUtils.synthesizeKey("VK_TAB", {}, win);
       info("saving profile");
       EventUtils.synthesizeKey("VK_RETURN", {}, win);
     }, {once: true});
   });
-  let profiles = yield getProfiles();
+  let addresses = yield getAddresses();
 
-  is(profiles.length, 1, "only one profile is in storage");
-  is(Object.keys(TEST_PROFILE_1).length, 11, "Sanity check number of properties");
-  for (let [fieldName, fieldValue] of Object.entries(TEST_PROFILE_1)) {
-    is(profiles[0][fieldName], fieldValue, "check " + fieldName);
+  is(addresses.length, 1, "only one address is in storage");
+  is(Object.keys(TEST_ADDRESS_1).length, 11, "Sanity check number of properties");
+  for (let [fieldName, fieldValue] of Object.entries(TEST_ADDRESS_1)) {
+    is(addresses[0][fieldName], fieldValue, "check " + fieldName);
   }
 });
 
 add_task(function* test_editProfile() {
-  let profiles = yield getProfiles();
+  let addresses = yield getAddresses();
   yield new Promise(resolve => {
-    let win = window.openDialog(EDIT_PROFILE_DIALOG_URL, null, null, profiles[0]);
+    let win = window.openDialog(EDIT_PROFILE_DIALOG_URL, null, null, addresses[0]);
     win.addEventListener("load", () => {
       win.addEventListener("unload", () => {
         ok(true, "Edit profile dialog is closed");
         resolve();
       }, {once: true});
       EventUtils.synthesizeKey("VK_TAB", {}, win);
       EventUtils.synthesizeKey("test", {}, win);
       win.document.querySelector("#save").click();
     }, {once: true});
   });
-  profiles = yield getProfiles();
+  addresses = yield getAddresses();
 
-  is(profiles.length, 1, "only one profile is in storage");
-  is(profiles[0]["given-name"], TEST_PROFILE_1["given-name"] + "test", "given-name changed");
-  yield removeProfiles([profiles[0].guid]);
+  is(addresses.length, 1, "only one address is in storage");
+  is(addresses[0]["given-name"], TEST_ADDRESS_1["given-name"] + "test", "given-name changed");
+  yield removeAddresses([addresses[0].guid]);
 
-  profiles = yield getProfiles();
-  is(profiles.length, 0, "Profile storage is empty");
+  addresses = yield getAddresses();
+  is(addresses.length, 0, "Address storage is empty");
 });
--- a/browser/extensions/formautofill/test/browser/browser_manageProfilesDialog.js
+++ b/browser/extensions/formautofill/test/browser/browser_manageProfilesDialog.js
@@ -1,90 +1,90 @@
 "use strict";
 
 const TEST_SELECTORS = {
-  selProfiles: "#profiles",
+  selAddresses: "#profiles",
   btnRemove: "#remove",
   btnAdd: "#add",
   btnEdit: "#edit",
 };
 
-function waitForProfiles() {
+function waitForAddresses() {
   return new Promise(resolve => {
-    Services.cpmm.addMessageListener("FormAutofill:Profiles", function getResult(result) {
-      Services.cpmm.removeMessageListener("FormAutofill:Profiles", getResult);
+    Services.cpmm.addMessageListener("FormAutofill:Addresses", function getResult(result) {
+      Services.cpmm.removeMessageListener("FormAutofill:Addresses", getResult);
       // Wait for the next tick for elements to get rendered.
       SimpleTest.executeSoon(resolve.bind(null, result.data));
     });
   });
 }
 
 registerCleanupFunction(function* () {
-  let profiles = yield getProfiles();
-  if (profiles.length) {
-    yield removeProfiles(profiles.map(profile => profile.guid));
+  let addresses = yield getAddresses();
+  if (addresses.length) {
+    yield removeAddresses(addresses.map(address => address.guid));
   }
 });
 
 add_task(function* test_manageProfilesInitialState() {
   yield BrowserTestUtils.withNewTab({gBrowser, url: MANAGE_PROFILES_DIALOG_URL}, function* (browser) {
     yield ContentTask.spawn(browser, TEST_SELECTORS, (args) => {
-      let selProfiles = content.document.querySelector(args.selProfiles);
+      let selAddresses = content.document.querySelector(args.selAddresses);
       let btnRemove = content.document.querySelector(args.btnRemove);
       let btnEdit = content.document.querySelector(args.btnEdit);
       let btnAdd = content.document.querySelector(args.btnAdd);
 
-      is(selProfiles.length, 0, "No profile");
+      is(selAddresses.length, 0, "No address");
       is(btnAdd.disabled, false, "Add button enabled");
       is(btnRemove.disabled, true, "Remove button disabled");
       is(btnEdit.disabled, true, "Edit button disabled");
     });
   });
 });
 
 add_task(function* test_removingSingleAndMultipleProfiles() {
-  yield saveProfile(TEST_PROFILE_1);
-  yield saveProfile(TEST_PROFILE_2);
-  yield saveProfile(TEST_PROFILE_3);
+  yield saveAddress(TEST_ADDRESS_1);
+  yield saveAddress(TEST_ADDRESS_2);
+  yield saveAddress(TEST_ADDRESS_3);
 
   let win = window.openDialog(MANAGE_PROFILES_DIALOG_URL);
-  yield waitForProfiles();
+  yield waitForAddresses();
 
-  let selProfiles = win.document.querySelector(TEST_SELECTORS.selProfiles);
+  let selAddresses = win.document.querySelector(TEST_SELECTORS.selAddresses);
   let btnRemove = win.document.querySelector(TEST_SELECTORS.btnRemove);
   let btnEdit = win.document.querySelector(TEST_SELECTORS.btnEdit);
 
-  is(selProfiles.length, 3, "Three profiles");
+  is(selAddresses.length, 3, "Three addresses");
 
-  EventUtils.synthesizeMouseAtCenter(selProfiles.children[0], {}, win);
+  EventUtils.synthesizeMouseAtCenter(selAddresses.children[0], {}, win);
   is(btnRemove.disabled, false, "Remove button enabled");
   is(btnEdit.disabled, false, "Edit button enabled");
   EventUtils.synthesizeMouseAtCenter(btnRemove, {}, win);
-  yield waitForProfiles();
-  is(selProfiles.length, 2, "Two profiles left");
+  yield waitForAddresses();
+  is(selAddresses.length, 2, "Two addresses left");
 
-  EventUtils.synthesizeMouseAtCenter(selProfiles.children[0], {}, win);
-  EventUtils.synthesizeMouseAtCenter(selProfiles.children[1],
+  EventUtils.synthesizeMouseAtCenter(selAddresses.children[0], {}, win);
+  EventUtils.synthesizeMouseAtCenter(selAddresses.children[1],
                                      {shiftKey: true}, win);
   is(btnEdit.disabled, true, "Edit button disabled when multi-select");
 
   EventUtils.synthesizeMouseAtCenter(btnRemove, {}, win);
-  yield waitForProfiles();
-  is(selProfiles.length, 0, "All profiles are removed");
+  yield waitForAddresses();
+  is(selAddresses.length, 0, "All addresses are removed");
 
   win.close();
 });
 
 add_task(function* test_profilesDialogWatchesStorageChanges() {
   let win = window.openDialog(MANAGE_PROFILES_DIALOG_URL);
-  yield waitForProfiles();
+  yield waitForAddresses();
 
-  let selProfiles = win.document.querySelector(TEST_SELECTORS.selProfiles);
+  let selAddresses = win.document.querySelector(TEST_SELECTORS.selAddresses);
 
-  yield saveProfile(TEST_PROFILE_1);
-  let profiles = yield waitForProfiles();
-  is(selProfiles.length, 1, "One profile is shown");
+  yield saveAddress(TEST_ADDRESS_1);
+  let addresses = yield waitForAddresses();
+  is(selAddresses.length, 1, "One address is shown");
 
-  yield removeProfiles([profiles[0].guid]);
-  yield waitForProfiles();
-  is(selProfiles.length, 0, "Profile is removed");
+  yield removeAddresses([addresses[0].guid]);
+  yield waitForAddresses();
+  is(selAddresses.length, 0, "Address is removed");
   win.close();
 });
--- a/browser/extensions/formautofill/test/browser/head.js
+++ b/browser/extensions/formautofill/test/browser/head.js
@@ -1,52 +1,52 @@
 /* exported MANAGE_PROFILES_DIALOG_URL, EDIT_PROFILE_DIALOG_URL,
-            TEST_PROFILE_1, TEST_PROFILE_2, TEST_PROFILE_3,
-            getProfiles, saveProfile, removeProfiles */
+            TEST_ADDRESS_1, TEST_ADDRESS_2, TEST_ADDRESS_3,
+            getAddresses, saveAddress, removeAddresses */
 
 "use strict";
 
 const MANAGE_PROFILES_DIALOG_URL = "chrome://formautofill/content/manageProfiles.xhtml";
 const EDIT_PROFILE_DIALOG_URL = "chrome://formautofill/content/editProfile.xhtml";
 
-const TEST_PROFILE_1 = {
+const TEST_ADDRESS_1 = {
   "given-name": "John",
   "additional-name": "R.",
   "family-name": "Smith",
   organization: "World Wide Web Consortium",
   "street-address": "32 Vassar Street\nMIT Room 32-G524",
   "address-level2": "Cambridge",
   "address-level1": "MA",
   "postal-code": "02139",
   country: "US",
   tel: "+1 617 253 5702",
   email: "timbl@w3.org",
 };
 
-const TEST_PROFILE_2 = {
+const TEST_ADDRESS_2 = {
   "street-address": "Some Address",
   country: "US",
 };
 
-const TEST_PROFILE_3 = {
+const TEST_ADDRESS_3 = {
   "street-address": "Other Address",
   "postal-code": "12345",
 };
 
-function getProfiles() {
+function getAddresses() {
   return new Promise(resolve => {
-    Services.cpmm.addMessageListener("FormAutofill:Profiles", function getResult(result) {
-      Services.cpmm.removeMessageListener("FormAutofill:Profiles", getResult);
+    Services.cpmm.addMessageListener("FormAutofill:Addresses", function getResult(result) {
+      Services.cpmm.removeMessageListener("FormAutofill:Addresses", getResult);
       resolve(result.data);
     });
-    Services.cpmm.sendAsyncMessage("FormAutofill:GetProfiles", {});
+    Services.cpmm.sendAsyncMessage("FormAutofill:GetAddresses", {});
   });
 }
 
-function saveProfile(profile) {
-  Services.cpmm.sendAsyncMessage("FormAutofill:SaveProfile", {profile});
+function saveAddress(address) {
+  Services.cpmm.sendAsyncMessage("FormAutofill:SaveAddress", {address});
   return TestUtils.topicObserved("formautofill-storage-changed");
 }
 
-function removeProfiles(guids) {
-  Services.cpmm.sendAsyncMessage("FormAutofill:RemoveProfiles", {guids});
+function removeAddresses(guids) {
+  Services.cpmm.sendAsyncMessage("FormAutofill:RemoveAddresses", {guids});
   return TestUtils.topicObserved("formautofill-storage-changed");
 }
--- a/browser/extensions/formautofill/test/mochitest/formautofill_common.js
+++ b/browser/extensions/formautofill/test/mochitest/formautofill_common.js
@@ -16,43 +16,43 @@ function checkMenuEntries(expectedValues
   let actualValues = getMenuEntries();
 
   is(actualValues.length, expectedValues.length, " Checking length of expected menu");
   for (let i = 0; i < expectedValues.length; i++) {
     is(actualValues[i], expectedValues[i], " Checking menu entry #" + i);
   }
 }
 
-function addProfile(profile) {
+function addAddress(address) {
   return new Promise(resolve => {
-    formFillChromeScript.sendAsyncMessage("FormAutofillTest:AddProfile", {profile});
-    formFillChromeScript.addMessageListener("FormAutofillTest:ProfileAdded", function onAdded(data) {
-      formFillChromeScript.removeMessageListener("FormAutofillTest:ProfileAdded", onAdded);
+    formFillChromeScript.sendAsyncMessage("FormAutofillTest:AddAddress", {address});
+    formFillChromeScript.addMessageListener("FormAutofillTest:AddressAdded", function onAdded(data) {
+      formFillChromeScript.removeMessageListener("FormAutofillTest:AddressAdded", onAdded);
 
       resolve();
     });
   });
 }
 
-function removeProfile(guid) {
+function removeAddress(guid) {
   return new Promise(resolve => {
-    formFillChromeScript.sendAsyncMessage("FormAutofillTest:RemoveProfile", {guid});
-    formFillChromeScript.addMessageListener("FormAutofillTest:ProfileRemoved", function onDeleted(data) {
-      formFillChromeScript.removeMessageListener("FormAutofillTest:ProfileRemoved", onDeleted);
+    formFillChromeScript.sendAsyncMessage("FormAutofillTest:RemoveAddress", {guid});
+    formFillChromeScript.addMessageListener("FormAutofillTest:AddressRemoved", function onDeleted(data) {
+      formFillChromeScript.removeMessageListener("FormAutofillTest:AddressRemoved", onDeleted);
 
       resolve();
     });
   });
 }
 
-function updateProfile(guid, profile) {
+function updateAddress(guid, address) {
   return new Promise(resolve => {
-    formFillChromeScript.sendAsyncMessage("FormAutofillTest:UpdateProfile", {profile, guid});
-    formFillChromeScript.addMessageListener("FormAutofillTest:ProfileUpdated", function onUpdated(data) {
-      formFillChromeScript.removeMessageListener("FormAutofillTest:ProfileUpdated", onUpdated);
+    formFillChromeScript.sendAsyncMessage("FormAutofillTest:UpdateAddress", {address, guid});
+    formFillChromeScript.addMessageListener("FormAutofillTest:AddressUpdated", function onUpdated(data) {
+      formFillChromeScript.removeMessageListener("FormAutofillTest:AddressUpdated", onUpdated);
 
       resolve();
     });
   });
 }
 
 function formAutoFillCommonSetup() {
   let chromeURL = SimpleTest.getTestFileURL("formautofill_parent_utils.js");
--- a/browser/extensions/formautofill/test/mochitest/formautofill_parent_utils.js
+++ b/browser/extensions/formautofill/test/mochitest/formautofill_parent_utils.js
@@ -3,29 +3,29 @@
 
 "use strict";
 
 const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
 
 Cu.import("resource://gre/modules/Services.jsm");
 
 var ParentUtils = {
-  cleanUpProfile() {
-    Services.cpmm.addMessageListener("FormAutofill:Profiles", function getResult(result) {
-      Services.cpmm.removeMessageListener("FormAutofill:Profiles", getResult);
+  cleanUpAddress() {
+    Services.cpmm.addMessageListener("FormAutofill:Addresses", function getResult(result) {
+      Services.cpmm.removeMessageListener("FormAutofill:Addresses", getResult);
 
-      let profiles = result.data;
-      Services.cpmm.sendAsyncMessage("FormAutofill:RemoveProfiles",
-                                     {guids: profiles.map(profile => profile.guid)});
+      let addresses = result.data;
+      Services.cpmm.sendAsyncMessage("FormAutofill:RemoveAddresses",
+                                     {guids: addresses.map(address => address.guid)});
     });
 
-    Services.cpmm.sendAsyncMessage("FormAutofill:GetProfiles", {searchString: ""});
+    Services.cpmm.sendAsyncMessage("FormAutofill:GetAddresses", {searchString: ""});
   },
 
-  updateProfile(type, chromeMsg, msgData, contentMsg) {
+  updateAddress(type, chromeMsg, msgData, contentMsg) {
     Services.cpmm.sendAsyncMessage(chromeMsg, msgData);
     Services.obs.addObserver(function observer(subject, topic, data) {
       if (data != type) {
         return;
       }
 
       Services.obs.removeObserver(observer, topic);
       sendAsyncMessage(contentMsg);
@@ -34,30 +34,30 @@ var ParentUtils = {
 
   observe(subject, topic, data) {
     assert.ok(topic === "formautofill-storage-changed");
     sendAsyncMessage("formautofill-storage-changed", {subject: null, topic, data});
   },
 
   cleanup() {
     Services.obs.removeObserver(this, "formautofill-storage-changed");
-    this.cleanUpProfile();
+    this.cleanUpAddress();
   },
 };
 
-ParentUtils.cleanUpProfile();
+ParentUtils.cleanUpAddress();
 Services.obs.addObserver(ParentUtils, "formautofill-storage-changed");
 
-addMessageListener("FormAutofillTest:AddProfile", (msg) => {
-  ParentUtils.updateProfile("add", "FormAutofill:SaveProfile", msg, "FormAutofillTest:ProfileAdded");
+addMessageListener("FormAutofillTest:AddAddress", (msg) => {
+  ParentUtils.updateAddress("add", "FormAutofill:SaveAddress", msg, "FormAutofillTest:AddressAdded");
 });
 
-addMessageListener("FormAutofillTest:RemoveProfile", (msg) => {
-  ParentUtils.updateProfile("remove", "FormAutofill:Removefile", msg, "FormAutofillTest:ProfileRemoved");
+addMessageListener("FormAutofillTest:RemoveAddress", (msg) => {
+  ParentUtils.updateAddress("remove", "FormAutofill:RemoveAddress", msg, "FormAutofillTest:AddressRemoved");
 });
 
-addMessageListener("FormAutofillTest:UpdateProfile", (msg) => {
-  ParentUtils.updateProfile("update", "FormAutofill:SaveProfile", msg, "FormAutofillTest:ProfileUpdated");
+addMessageListener("FormAutofillTest:UpdateAddress", (msg) => {
+  ParentUtils.updateAddress("update", "FormAutofill:SaveAddress", msg, "FormAutofillTest:AddressUpdated");
 });
 
 addMessageListener("cleanup", () => {
   ParentUtils.cleanup();
 });
--- a/browser/extensions/formautofill/test/mochitest/test_basic_autocomplete_form.html
+++ b/browser/extensions/formautofill/test/mochitest/test_basic_autocomplete_form.html
@@ -5,17 +5,17 @@
   <title>Test basic autofill</title>
   <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <script type="text/javascript" src="/tests/SimpleTest/SpawnTask.js"></script>
   <script type="text/javascript" src="formautofill_common.js"></script>
   <script type="text/javascript" src="satchel_common.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
 </head>
 <body>
-Form autofill test: simple form profile autofill
+Form autofill test: simple form address autofill
 
 <script>
 /* import-globals-from ../../../../../testing/mochitest/tests/SimpleTest/SpawnTask.js */
 /* import-globals-from ../../../../../toolkit/components/satchel/test/satchel_common.js */
 /* import-globals-from formautofill_common.js */
 
 "use strict";
 
@@ -58,33 +58,33 @@ function checkAutoCompleteInputFilled(el
   return new Promise(resolve => {
     element.addEventListener("DOMAutoComplete", function onChange() {
       is(element.value, expectedvalue, "Checking " + element.name + " field");
       resolve();
     }, {once: true});
   });
 }
 
-function checkFormFilled(profile) {
+function checkFormFilled(address) {
   let promises = [];
-  for (let prop in profile) {
+  for (let prop in address) {
     let element = document.getElementById(prop);
     if (document.activeElement == element) {
-      promises.push(checkAutoCompleteInputFilled(element, profile[prop]));
+      promises.push(checkAutoCompleteInputFilled(element, address[prop]));
     } else {
-      promises.push(checkInputFilled(element, profile[prop]));
+      promises.push(checkInputFilled(element, address[prop]));
     }
   }
   doKey("return");
   return Promise.all(promises);
 }
 
-function* setupProfileStorage() {
-  yield addProfile(MOCK_STORAGE[0]);
-  yield addProfile(MOCK_STORAGE[1]);
+function* setupAddressStorage() {
+  yield addAddress(MOCK_STORAGE[0]);
+  yield addAddress(MOCK_STORAGE[1]);
 }
 
 function* setupFormHistory() {
   yield updateFormHistory([
     {op: "add", fieldname: "tel", value: "1-234-567-890"},
     {op: "add", fieldname: "country", value: "US"},
   ]);
 }
@@ -94,63 +94,63 @@ add_task(function* history_only_menu_che
   yield setupFormHistory();
 
   setInput("#tel", "");
   doKey("down");
   yield expectPopup();
   checkMenuEntries(["1-234-567-890"]);
 });
 
-// Form with both history and profile storage.
+// Form with both history and address storage.
 add_task(function* check_menu_when_both_existed() {
-  yield setupProfileStorage();
+  yield setupAddressStorage();
 
   setInput("#organization", "");
   doKey("down");
   yield expectPopup();
-  checkMenuEntries(MOCK_STORAGE.map(profile =>
-    JSON.stringify({primary: profile.organization, secondary: profile["street-address"]})
+  checkMenuEntries(MOCK_STORAGE.map(address =>
+    JSON.stringify({primary: address.organization, secondary: address["street-address"]})
   ));
 
   setInput("#street-address", "");
   doKey("down");
   yield expectPopup();
-  checkMenuEntries(MOCK_STORAGE.map(profile =>
-    JSON.stringify({primary: profile["street-address"], secondary: profile.organization})
+  checkMenuEntries(MOCK_STORAGE.map(address =>
+    JSON.stringify({primary: address["street-address"], secondary: address.organization})
   ));
 
   setInput("#tel", "");
   doKey("down");
   yield expectPopup();
-  checkMenuEntries(MOCK_STORAGE.map(profile =>
-    JSON.stringify({primary: profile.tel, secondary: profile["street-address"]})
+  checkMenuEntries(MOCK_STORAGE.map(address =>
+    JSON.stringify({primary: address.tel, secondary: address["street-address"]})
   ));
 });
 
-// Display history search result if no matched data in profiles.
+// Display history search result if no matched data in addresses.
 add_task(function* check_fallback_for_mismatched_field() {
   setInput("#country", "");
   doKey("down");
   yield expectPopup();
   checkMenuEntries(["US"]);
 });
 
-// Autofill the profile from dropdown menu.
+// Autofill the address from dropdown menu.
 add_task(function* check_fields_after_form_autofill() {
   setInput("#organization", "Moz");
   doKey("down");
   yield expectPopup();
-  checkMenuEntries(MOCK_STORAGE.map(profile =>
-    JSON.stringify({primary: profile.organization, secondary: profile["street-address"]})
+  checkMenuEntries(MOCK_STORAGE.map(address =>
+    JSON.stringify({primary: address.organization, secondary: address["street-address"]})
   ).slice(1));
   doKey("down");
   yield checkFormFilled(MOCK_STORAGE[1]);
 });
 
-// Fallback to history search after autofill profile.
+// Fallback to history search after autofill address.
 add_task(function* check_fallback_after_form_autofill() {
   setInput("#tel", "");
   doKey("down");
   yield expectPopup();
   checkMenuEntries(["1-234-567-890"]);
 });
 
 registerPopupShownListener(popupShownListener);
--- a/browser/extensions/formautofill/test/unit/test_profileStorage.js
+++ b/browser/extensions/formautofill/test/unit/test_profileStorage.js
@@ -4,267 +4,267 @@
 
 "use strict";
 
 Cu.import("resource://gre/modules/Task.jsm");
 Cu.import("resource://formautofill/ProfileStorage.jsm");
 
 const TEST_STORE_FILE_NAME = "test-profile.json";
 
-const TEST_PROFILE_1 = {
+const TEST_ADDRESS_1 = {
   "given-name": "Timothy",
   "additional-name": "John",
   "family-name": "Berners-Lee",
   organization: "World Wide Web Consortium",
   "street-address": "32 Vassar Street\nMIT Room 32-G524",
   "address-level2": "Cambridge",
   "address-level1": "MA",
   "postal-code": "02139",
   country: "US",
   tel: "+1 617 253 5702",
   email: "timbl@w3.org",
 };
 
-const TEST_PROFILE_2 = {
+const TEST_ADDRESS_2 = {
   "street-address": "Some Address",
   country: "US",
 };
 
-const TEST_PROFILE_3 = {
+const TEST_ADDRESS_3 = {
   "street-address": "Other Address",
   "postal-code": "12345",
 };
 
-const TEST_PROFILE_WITH_INVALID_FIELD = {
+const TEST_ADDRESS_WITH_INVALID_FIELD = {
   "street-address": "Another Address",
   invalidField: "INVALID",
 };
 
-let prepareTestProfiles = Task.async(function* (path) {
+let prepareTestRecords = Task.async(function* (path) {
   let profileStorage = new ProfileStorage(path);
   yield profileStorage.initialize();
 
   let onChanged = TestUtils.topicObserved("formautofill-storage-changed",
                                           (subject, data) => data == "add");
-  profileStorage.add(TEST_PROFILE_1);
+  profileStorage.add(TEST_ADDRESS_1);
   yield onChanged;
-  profileStorage.add(TEST_PROFILE_2);
+  profileStorage.add(TEST_ADDRESS_2);
   yield profileStorage._saveImmediately();
 });
 
-let do_check_profile_matches = (profileWithMeta, profile) => {
-  for (let key in profile) {
-    do_check_eq(profileWithMeta[key], profile[key]);
+let do_check_record_matches = (recordWithMeta, record) => {
+  for (let key in record) {
+    do_check_eq(recordWithMeta[key], record[key]);
   }
 };
 
 add_task(function* test_initialize() {
   let path = getTempFile(TEST_STORE_FILE_NAME).path;
   let profileStorage = new ProfileStorage(path);
   yield profileStorage.initialize();
 
   do_check_eq(profileStorage._store.data.version, 1);
-  do_check_eq(profileStorage._store.data.profiles.length, 0);
+  do_check_eq(profileStorage._store.data.addresses.length, 0);
 
   let data = profileStorage._store.data;
 
   yield profileStorage._saveImmediately();
 
   profileStorage = new ProfileStorage(path);
   yield profileStorage.initialize();
 
   Assert.deepEqual(profileStorage._store.data, data);
 });
 
 add_task(function* test_getAll() {
   let path = getTempFile(TEST_STORE_FILE_NAME).path;
-  yield prepareTestProfiles(path);
+  yield prepareTestRecords(path);
 
   let profileStorage = new ProfileStorage(path);
   yield profileStorage.initialize();
 
-  let profiles = profileStorage.getAll();
+  let addresses = profileStorage.getAll();
 
-  do_check_eq(profiles.length, 2);
-  do_check_profile_matches(profiles[0], TEST_PROFILE_1);
-  do_check_profile_matches(profiles[1], TEST_PROFILE_2);
+  do_check_eq(addresses.length, 2);
+  do_check_record_matches(addresses[0], TEST_ADDRESS_1);
+  do_check_record_matches(addresses[1], TEST_ADDRESS_2);
 
   // Modifying output shouldn't affect the storage.
-  profiles[0].organization = "test";
-  do_check_profile_matches(profileStorage.getAll()[0], TEST_PROFILE_1);
+  addresses[0].organization = "test";
+  do_check_record_matches(profileStorage.getAll()[0], TEST_ADDRESS_1);
 });
 
 add_task(function* test_get() {
   let path = getTempFile(TEST_STORE_FILE_NAME).path;
-  yield prepareTestProfiles(path);
+  yield prepareTestRecords(path);
 
   let profileStorage = new ProfileStorage(path);
   yield profileStorage.initialize();
 
-  let profiles = profileStorage.getAll();
-  let guid = profiles[0].guid;
+  let addresses = profileStorage.getAll();
+  let guid = addresses[0].guid;
 
-  let profile = profileStorage.get(guid);
-  do_check_profile_matches(profile, TEST_PROFILE_1);
+  let address = profileStorage.get(guid);
+  do_check_record_matches(address, TEST_ADDRESS_1);
 
   // Modifying output shouldn't affect the storage.
-  profile.organization = "test";
-  do_check_profile_matches(profileStorage.get(guid), TEST_PROFILE_1);
+  address.organization = "test";
+  do_check_record_matches(profileStorage.get(guid), TEST_ADDRESS_1);
 
   Assert.throws(() => profileStorage.get("INVALID_GUID"),
-    /No matching profile\./);
+    /No matching record\./);
 });
 
 add_task(function* test_getByFilter() {
   let path = getTempFile(TEST_STORE_FILE_NAME).path;
-  yield prepareTestProfiles(path);
+  yield prepareTestRecords(path);
 
   let profileStorage = new ProfileStorage(path);
   yield profileStorage.initialize();
 
   let filter = {info: {fieldName: "street-address"}, searchString: "Some"};
-  let profiles = profileStorage.getByFilter(filter);
-  do_check_eq(profiles.length, 1);
-  do_check_profile_matches(profiles[0], TEST_PROFILE_2);
+  let addresses = profileStorage.getByFilter(filter);
+  do_check_eq(addresses.length, 1);
+  do_check_record_matches(addresses[0], TEST_ADDRESS_2);
 
   filter = {info: {fieldName: "country"}, searchString: "u"};
-  profiles = profileStorage.getByFilter(filter);
-  do_check_eq(profiles.length, 2);
-  do_check_profile_matches(profiles[0], TEST_PROFILE_1);
-  do_check_profile_matches(profiles[1], TEST_PROFILE_2);
+  addresses = profileStorage.getByFilter(filter);
+  do_check_eq(addresses.length, 2);
+  do_check_record_matches(addresses[0], TEST_ADDRESS_1);
+  do_check_record_matches(addresses[1], TEST_ADDRESS_2);
 
   filter = {info: {fieldName: "street-address"}, searchString: "test"};
-  profiles = profileStorage.getByFilter(filter);
-  do_check_eq(profiles.length, 0);
+  addresses = profileStorage.getByFilter(filter);
+  do_check_eq(addresses.length, 0);
 
   filter = {info: {fieldName: "street-address"}, searchString: ""};
-  profiles = profileStorage.getByFilter(filter);
-  do_check_eq(profiles.length, 2);
+  addresses = profileStorage.getByFilter(filter);
+  do_check_eq(addresses.length, 2);
 
   // Check if the filtering logic is free from searching special chars.
   filter = {info: {fieldName: "street-address"}, searchString: ".*"};
-  profiles = profileStorage.getByFilter(filter);
-  do_check_eq(profiles.length, 0);
+  addresses = profileStorage.getByFilter(filter);
+  do_check_eq(addresses.length, 0);
 });
 
 add_task(function* test_add() {
   let path = getTempFile(TEST_STORE_FILE_NAME).path;
-  yield prepareTestProfiles(path);
+  yield prepareTestRecords(path);
 
   let profileStorage = new ProfileStorage(path);
   yield profileStorage.initialize();
 
-  let profiles = profileStorage.getAll();
+  let addresses = profileStorage.getAll();
 
-  do_check_eq(profiles.length, 2);
+  do_check_eq(addresses.length, 2);
 
-  do_check_profile_matches(profiles[0], TEST_PROFILE_1);
-  do_check_profile_matches(profiles[1], TEST_PROFILE_2);
+  do_check_record_matches(addresses[0], TEST_ADDRESS_1);
+  do_check_record_matches(addresses[1], TEST_ADDRESS_2);
 
-  do_check_neq(profiles[0].guid, undefined);
-  do_check_neq(profiles[0].timeCreated, undefined);
-  do_check_eq(profiles[0].timeLastModified, profiles[0].timeCreated);
-  do_check_eq(profiles[0].timeLastUsed, 0);
-  do_check_eq(profiles[0].timesUsed, 0);
+  do_check_neq(addresses[0].guid, undefined);
+  do_check_neq(addresses[0].timeCreated, undefined);
+  do_check_eq(addresses[0].timeLastModified, addresses[0].timeCreated);
+  do_check_eq(addresses[0].timeLastUsed, 0);
+  do_check_eq(addresses[0].timesUsed, 0);
 
-  Assert.throws(() => profileStorage.add(TEST_PROFILE_WITH_INVALID_FIELD),
+  Assert.throws(() => profileStorage.add(TEST_ADDRESS_WITH_INVALID_FIELD),
     /"invalidField" is not a valid field\./);
 });
 
 add_task(function* test_update() {
   let path = getTempFile(TEST_STORE_FILE_NAME).path;
-  yield prepareTestProfiles(path);
+  yield prepareTestRecords(path);
 
   let profileStorage = new ProfileStorage(path);
   yield profileStorage.initialize();
 
-  let profiles = profileStorage.getAll();
-  let guid = profiles[1].guid;
-  let timeLastModified = profiles[1].timeLastModified;
+  let addresses = profileStorage.getAll();
+  let guid = addresses[1].guid;
+  let timeLastModified = addresses[1].timeLastModified;
 
   let onChanged = TestUtils.topicObserved("formautofill-storage-changed",
                                           (subject, data) => data == "update");
 
-  do_check_neq(profiles[1].country, undefined);
+  do_check_neq(addresses[1].country, undefined);
 
-  profileStorage.update(guid, TEST_PROFILE_3);
+  profileStorage.update(guid, TEST_ADDRESS_3);
   yield onChanged;
   yield profileStorage._saveImmediately();
 
   profileStorage = new ProfileStorage(path);
   yield profileStorage.initialize();
 
-  let profile = profileStorage.get(guid);
+  let address = profileStorage.get(guid);
 
-  do_check_eq(profile.country, undefined);
-  do_check_neq(profile.timeLastModified, timeLastModified);
-  do_check_profile_matches(profile, TEST_PROFILE_3);
+  do_check_eq(address.country, undefined);
+  do_check_neq(address.timeLastModified, timeLastModified);
+  do_check_record_matches(address, TEST_ADDRESS_3);
 
   Assert.throws(
-    () => profileStorage.update("INVALID_GUID", TEST_PROFILE_3),
-    /No matching profile\./
+    () => profileStorage.update("INVALID_GUID", TEST_ADDRESS_3),
+    /No matching record\./
   );
 
   Assert.throws(
-    () => profileStorage.update(guid, TEST_PROFILE_WITH_INVALID_FIELD),
+    () => profileStorage.update(guid, TEST_ADDRESS_WITH_INVALID_FIELD),
     /"invalidField" is not a valid field\./
   );
 });
 
 add_task(function* test_notifyUsed() {
   let path = getTempFile(TEST_STORE_FILE_NAME).path;
-  yield prepareTestProfiles(path);
+  yield prepareTestRecords(path);
 
   let profileStorage = new ProfileStorage(path);
   yield profileStorage.initialize();
 
-  let profiles = profileStorage.getAll();
-  let guid = profiles[1].guid;
-  let timeLastUsed = profiles[1].timeLastUsed;
-  let timesUsed = profiles[1].timesUsed;
+  let addresses = profileStorage.getAll();
+  let guid = addresses[1].guid;
+  let timeLastUsed = addresses[1].timeLastUsed;
+  let timesUsed = addresses[1].timesUsed;
 
   let onChanged = TestUtils.topicObserved("formautofill-storage-changed",
                                           (subject, data) => data == "notifyUsed");
 
   profileStorage.notifyUsed(guid);
   yield onChanged;
   yield profileStorage._saveImmediately();
 
   profileStorage = new ProfileStorage(path);
   yield profileStorage.initialize();
 
-  let profile = profileStorage.get(guid);
+  let address = profileStorage.get(guid);
 
-  do_check_eq(profile.timesUsed, timesUsed + 1);
-  do_check_neq(profile.timeLastUsed, timeLastUsed);
+  do_check_eq(address.timesUsed, timesUsed + 1);
+  do_check_neq(address.timeLastUsed, timeLastUsed);
 
   Assert.throws(() => profileStorage.notifyUsed("INVALID_GUID"),
-    /No matching profile\./);
+    /No matching record\./);
 });
 
 add_task(function* test_remove() {
   let path = getTempFile(TEST_STORE_FILE_NAME).path;
-  yield prepareTestProfiles(path);
+  yield prepareTestRecords(path);
 
   let profileStorage = new ProfileStorage(path);
   yield profileStorage.initialize();
 
-  let profiles = profileStorage.getAll();
-  let guid = profiles[1].guid;
+  let addresses = profileStorage.getAll();
+  let guid = addresses[1].guid;
 
   let onChanged = TestUtils.topicObserved("formautofill-storage-changed",
                                           (subject, data) => data == "remove");
 
-  do_check_eq(profiles.length, 2);
+  do_check_eq(addresses.length, 2);
 
   profileStorage.remove(guid);
   yield onChanged;
   yield profileStorage._saveImmediately();
 
   profileStorage = new ProfileStorage(path);
   yield profileStorage.initialize();
 
-  profiles = profileStorage.getAll();
+  addresses = profileStorage.getAll();
 
-  do_check_eq(profiles.length, 1);
+  do_check_eq(addresses.length, 1);
 
-  Assert.throws(() => profileStorage.get(guid), /No matching profile\./);
+  Assert.throws(() => profileStorage.get(guid), /No matching record\./);
 });
--- a/browser/extensions/formautofill/test/unit/test_transformFields.js
+++ b/browser/extensions/formautofill/test/unit/test_transformFields.js
@@ -7,222 +7,222 @@
 Cu.import("resource://gre/modules/Task.jsm");
 Cu.import("resource://formautofill/ProfileStorage.jsm");
 
 const TEST_STORE_FILE_NAME = "test-profile.json";
 
 const COMPUTE_TESTCASES = [
   // Empty
   {
-    description: "Empty profile",
-    profile: {
+    description: "Empty address",
+    address: {
     },
     expectedResult: {
     },
   },
 
   // Name
   {
     description: "Has split names",
-    profile: {
+    address: {
       "given-name": "Timothy",
       "additional-name": "John",
       "family-name": "Berners-Lee",
     },
     expectedResult: {
       "given-name": "Timothy",
       "additional-name": "John",
       "family-name": "Berners-Lee",
       "name": "Timothy John Berners-Lee",
     },
   },
 
   // Address
   {
     description: "\"street-address\" with single line",
-    profile: {
+    address: {
       "street-address": "single line",
     },
     expectedResult: {
       "street-address": "single line",
       "address-line1": "single line",
     },
   },
   {
     description: "\"street-address\" with multiple lines",
-    profile: {
+    address: {
       "street-address": "line1\nline2\nline3",
     },
     expectedResult: {
       "street-address": "line1\nline2\nline3",
       "address-line1": "line1",
       "address-line2": "line2",
       "address-line3": "line3",
     },
   },
   {
     description: "\"street-address\" with multiple lines but line2 is omitted",
-    profile: {
+    address: {
       "street-address": "line1\n\nline3",
     },
     expectedResult: {
       "street-address": "line1\n\nline3",
       "address-line1": "line1",
       "address-line2": "",
       "address-line3": "line3",
     },
   },
   {
     description: "\"street-address\" with 4 lines",
-    profile: {
+    address: {
       "street-address": "line1\nline2\nline3\nline4",
     },
     expectedResult: {
       "street-address": "line1\nline2\nline3\nline4",
       "address-line1": "line1",
       "address-line2": "line2",
       "address-line3": "line3",
     },
   },
 ];
 
 const NORMALIZE_TESTCASES = [
   // Empty
   {
-    description: "Empty profile",
-    profile: {
+    description: "Empty address",
+    address: {
     },
     expectedResult: {
     },
   },
 
   // Name
   {
     description: "Has \"name\", and the split names are omitted",
-    profile: {
+    address: {
       "name": "Timothy John Berners-Lee",
     },
     expectedResult: {
       "given-name": "Timothy",
       "additional-name": "John",
       "family-name": "Berners-Lee",
     },
   },
   {
     description: "Has both \"name\" and split names",
-    profile: {
+    address: {
       "name": "John Doe",
       "given-name": "Timothy",
       "additional-name": "John",
       "family-name": "Berners-Lee",
     },
     expectedResult: {
       "given-name": "Timothy",
       "additional-name": "John",
       "family-name": "Berners-Lee",
     },
   },
   {
     description: "Has \"name\", and some of split names are omitted",
-    profile: {
+    address: {
       "name": "John Doe",
       "given-name": "Timothy",
     },
     expectedResult: {
       "given-name": "Timothy",
       "family-name": "Doe",
     },
   },
 
 
   // Address
   {
     description: "Has \"address-line1~3\" and \"street-address\" is omitted",
-    profile: {
+    address: {
       "address-line1": "line1",
       "address-line2": "line2",
       "address-line3": "line3",
     },
     expectedResult: {
       "street-address": "line1\nline2\nline3",
     },
   },
   {
     description: "Has both \"address-line1~3\" and \"street-address\"",
-    profile: {
+    address: {
       "street-address": "street address",
       "address-line1": "line1",
       "address-line2": "line2",
       "address-line3": "line3",
     },
     expectedResult: {
       "street-address": "street address",
     },
   },
   {
     description: "Has \"address-line2~3\" and single-line \"street-address\"",
-    profile: {
+    address: {
       "street-address": "street address",
       "address-line2": "line2",
       "address-line3": "line3",
     },
     expectedResult: {
       "street-address": "street address\nline2\nline3",
     },
   },
   {
     description: "Has \"address-line2~3\" and multiple-line \"street-address\"",
-    profile: {
+    address: {
       "street-address": "street address\nstreet address line 2",
       "address-line2": "line2",
       "address-line3": "line3",
     },
     expectedResult: {
       "street-address": "street address\nstreet address line 2",
     },
   },
 ];
 
-let do_check_profile_matches = (expectedProfile, profile) => {
-  for (let key in expectedProfile) {
-    do_check_eq(expectedProfile[key], profile[key] || "");
+let do_check_record_matches = (expectedRecord, record) => {
+  for (let key in expectedRecord) {
+    do_check_eq(expectedRecord[key], record[key] || "");
   }
 };
 
 add_task(function* test_computeFields() {
   let path = getTempFile(TEST_STORE_FILE_NAME).path;
 
   let profileStorage = new ProfileStorage(path);
   yield profileStorage.initialize();
 
-  COMPUTE_TESTCASES.forEach(testcase => profileStorage.add(testcase.profile));
+  COMPUTE_TESTCASES.forEach(testcase => profileStorage.add(testcase.address));
   yield profileStorage._saveImmediately();
 
   profileStorage = new ProfileStorage(path);
   yield profileStorage.initialize();
 
-  let profiles = profileStorage.getAll();
+  let addresses = profileStorage.getAll();
 
-  for (let i in profiles) {
+  for (let i in addresses) {
     do_print("Verify testcase: " + COMPUTE_TESTCASES[i].description);
-    do_check_profile_matches(COMPUTE_TESTCASES[i].expectedResult, profiles[i]);
+    do_check_record_matches(COMPUTE_TESTCASES[i].expectedResult, addresses[i]);
   }
 });
 
-add_task(function* test_normalizeProfile() {
+add_task(function* test_normalizeFields() {
   let path = getTempFile(TEST_STORE_FILE_NAME).path;
 
   let profileStorage = new ProfileStorage(path);
   yield profileStorage.initialize();
 
-  NORMALIZE_TESTCASES.forEach(testcase => profileStorage.add(testcase.profile));
+  NORMALIZE_TESTCASES.forEach(testcase => profileStorage.add(testcase.address));
   yield profileStorage._saveImmediately();
 
   profileStorage = new ProfileStorage(path);
   yield profileStorage.initialize();
 
-  let profiles = profileStorage.getAll();
+  let addresses = profileStorage.getAll();
 
-  for (let i in profiles) {
+  for (let i in addresses) {
     do_print("Verify testcase: " + NORMALIZE_TESTCASES[i].description);
-    do_check_profile_matches(NORMALIZE_TESTCASES[i].expectedResult, profiles[i]);
+    do_check_record_matches(NORMALIZE_TESTCASES[i].expectedResult, addresses[i]);
   }
 });
--- a/browser/extensions/pdfjs/README.mozilla
+++ b/browser/extensions/pdfjs/README.mozilla
@@ -1,5 +1,5 @@
 This is the PDF.js project output, https://github.com/mozilla/pdf.js
 
-Current extension version is: 1.8.314
+Current extension version is: 1.8.331
 
-Taken from upstream commit: 3adda80f
+Taken from upstream commit: 0dbc68a6
--- a/browser/extensions/pdfjs/content/build/pdf.js
+++ b/browser/extensions/pdfjs/content/build/pdf.js
@@ -93,17 +93,23 @@ return /******/ (function(modules) { // 
 /************************************************************************/
 /******/ ([
 /* 0 */
 /***/ (function(module, exports, __w_pdfjs_require__) {
 
 "use strict";
 /* WEBPACK VAR INJECTION */(function(global) {
 
-var compatibility = __w_pdfjs_require__(14);
+Object.defineProperty(exports, "__esModule", {
+  value: true
+});
+exports.warn = exports.utf8StringToString = exports.stringToUTF8String = exports.stringToPDFString = exports.stringToBytes = exports.string32 = exports.shadow = exports.setVerbosityLevel = exports.removeNullCharacters = exports.readUint32 = exports.readUint16 = exports.readInt8 = exports.log2 = exports.loadJpegStream = exports.isEvalSupported = exports.isLittleEndian = exports.createValidAbsoluteUrl = exports.isSameOrigin = exports.isNodeJS = exports.isSpace = exports.isString = exports.isNum = exports.isInt = exports.isEmptyObj = exports.isBool = exports.isArrayBuffer = exports.isArray = exports.info = exports.globalScope = exports.getVerbosityLevel = exports.getLookupTableFactory = exports.error = exports.deprecated = exports.createObjectURL = exports.createPromiseCapability = exports.createBlob = exports.bytesToString = exports.assert = exports.arraysToBytes = exports.arrayByteLength = exports.XRefParseException = exports.Util = exports.UnknownErrorException = exports.UnexpectedResponseException = exports.TextRenderingMode = exports.StreamType = exports.StatTimer = exports.PasswordResponses = exports.PasswordException = exports.PageViewport = exports.NotImplementedException = exports.MissingPDFException = exports.MissingDataException = exports.MessageHandler = exports.InvalidPDFException = exports.CMapCompressionType = exports.ImageKind = exports.FontType = exports.AnnotationType = exports.AnnotationFlag = exports.AnnotationFieldFlag = exports.AnnotationBorderStyleType = exports.UNSUPPORTED_FEATURES = exports.VERBOSITY_LEVELS = exports.OPS = exports.IDENTITY_MATRIX = exports.FONT_IDENTITY_MATRIX = undefined;
+
+__w_pdfjs_require__(14);
+
 var globalScope = typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : undefined;
 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,
@@ -3381,18 +3387,18 @@ var _UnsupportedManager = function Unsup
       for (var i = 0, ii = listeners.length; i < ii; i++) {
         listeners[i](featureId);
       }
     }
   };
 }();
 var version, build;
 {
-  exports.version = version = '1.8.314';
-  exports.build = build = '3adda80f';
+  exports.version = version = '1.8.331';
+  exports.build = build = '0dbc68a6';
 }
 exports.getDocument = getDocument;
 exports.LoopbackPort = LoopbackPort;
 exports.PDFDataRangeTransport = PDFDataRangeTransport;
 exports.PDFWorker = PDFWorker;
 exports.PDFDocumentProxy = PDFDocumentProxy;
 exports.PDFPageProxy = PDFPageProxy;
 exports._UnsupportedManager = _UnsupportedManager;
@@ -4384,18 +4390,18 @@ var _text_layer = __w_pdfjs_require__(5)
 var _svg = __w_pdfjs_require__(4);
 
 var isWorker = typeof window === 'undefined';
 if (!_util.globalScope.PDFJS) {
   _util.globalScope.PDFJS = {};
 }
 var PDFJS = _util.globalScope.PDFJS;
 {
-  PDFJS.version = '1.8.314';
-  PDFJS.build = '3adda80f';
+  PDFJS.version = '1.8.331';
+  PDFJS.build = '0dbc68a6';
 }
 PDFJS.pdfBug = false;
 if (PDFJS.verbosity !== undefined) {
   (0, _util.setVerbosityLevel)(PDFJS.verbosity);
 }
 delete PDFJS.verbosity;
 Object.defineProperty(PDFJS, 'verbosity', {
   get() {
@@ -6699,30 +6705,30 @@ exports.TilingPattern = TilingPattern;
 
 /***/ }),
 /* 13 */
 /***/ (function(module, exports, __w_pdfjs_require__) {
 
 "use strict";
 
 
-var pdfjsVersion = '1.8.314';
-var pdfjsBuild = '3adda80f';
+var pdfjsVersion = '1.8.331';
+var pdfjsBuild = '0dbc68a6';
 var pdfjsSharedUtil = __w_pdfjs_require__(0);
 var pdfjsDisplayGlobal = __w_pdfjs_require__(8);
 var pdfjsDisplayAPI = __w_pdfjs_require__(3);
 var pdfjsDisplayTextLayer = __w_pdfjs_require__(5);
 var pdfjsDisplayAnnotationLayer = __w_pdfjs_require__(2);
 var pdfjsDisplayDOMUtils = __w_pdfjs_require__(1);
 var pdfjsDisplaySVG = __w_pdfjs_require__(4);
 exports.PDFJS = pdfjsDisplayGlobal.PDFJS;
 exports.build = pdfjsDisplayAPI.build;
 exports.version = pdfjsDisplayAPI.version;
 exports.getDocument = pdfjsDisplayAPI.getDocument;
-exports.LoobpackPort = pdfjsDisplayAPI.LoopbackPort;
+exports.LoopbackPort = pdfjsDisplayAPI.LoopbackPort;
 exports.PDFDataRangeTransport = pdfjsDisplayAPI.PDFDataRangeTransport;
 exports.PDFWorker = pdfjsDisplayAPI.PDFWorker;
 exports.renderTextLayer = pdfjsDisplayTextLayer.renderTextLayer;
 exports.AnnotationLayer = pdfjsDisplayAnnotationLayer.AnnotationLayer;
 exports.CustomStyle = pdfjsDisplayDOMUtils.CustomStyle;
 exports.createPromiseCapability = pdfjsSharedUtil.createPromiseCapability;
 exports.PasswordResponses = pdfjsSharedUtil.PasswordResponses;
 exports.InvalidPDFException = pdfjsSharedUtil.InvalidPDFException;
@@ -6743,11 +6749,13 @@ exports.addLinkAttributes = pdfjsDisplay
 
 /***/ }),
 /* 14 */
 /***/ (function(module, exports, __w_pdfjs_require__) {
 
 "use strict";
 
 
+;
+
 /***/ })
 /******/ ]);
 });
\ No newline at end of file
--- a/browser/extensions/pdfjs/content/build/pdf.worker.js
+++ b/browser/extensions/pdfjs/content/build/pdf.worker.js
@@ -93,17 +93,23 @@ return /******/ (function(modules) { // 
 /************************************************************************/
 /******/ ([
 /* 0 */
 /***/ (function(module, exports, __w_pdfjs_require__) {
 
 "use strict";
 /* WEBPACK VAR INJECTION */(function(global) {
 
-var compatibility = __w_pdfjs_require__(36);
+Object.defineProperty(exports, "__esModule", {
+  value: true
+});
+exports.warn = exports.utf8StringToString = exports.stringToUTF8String = exports.stringToPDFString = exports.stringToBytes = exports.string32 = exports.shadow = exports.setVerbosityLevel = exports.removeNullCharacters = exports.readUint32 = exports.readUint16 = exports.readInt8 = exports.log2 = exports.loadJpegStream = exports.isEvalSupported = exports.isLittleEndian = exports.createValidAbsoluteUrl = exports.isSameOrigin = exports.isNodeJS = exports.isSpace = exports.isString = exports.isNum = exports.isInt = exports.isEmptyObj = exports.isBool = exports.isArrayBuffer = exports.isArray = exports.info = exports.globalScope = exports.getVerbosityLevel = exports.getLookupTableFactory = exports.error = exports.deprecated = exports.createObjectURL = exports.createPromiseCapability = exports.createBlob = exports.bytesToString = exports.assert = exports.arraysToBytes = exports.arrayByteLength = exports.XRefParseException = exports.Util = exports.UnknownErrorException = exports.UnexpectedResponseException = exports.TextRenderingMode = exports.StreamType = exports.StatTimer = exports.PasswordResponses = exports.PasswordException = exports.PageViewport = exports.NotImplementedException = exports.MissingPDFException = exports.MissingDataException = exports.MessageHandler = exports.InvalidPDFException = exports.CMapCompressionType = exports.ImageKind = exports.FontType = exports.AnnotationType = exports.AnnotationFlag = exports.AnnotationFieldFlag = exports.AnnotationBorderStyleType = exports.UNSUPPORTED_FEATURES = exports.VERBOSITY_LEVELS = exports.OPS = exports.IDENTITY_MATRIX = exports.FONT_IDENTITY_MATRIX = undefined;
+
+__w_pdfjs_require__(36);
+
 var globalScope = typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : undefined;
 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,
@@ -36621,24 +36627,26 @@ exports.Type1Parser = Type1Parser;
 
 /***/ }),
 /* 35 */
 /***/ (function(module, exports, __w_pdfjs_require__) {
 
 "use strict";
 
 
-var pdfjsVersion = '1.8.314';
-var pdfjsBuild = '3adda80f';
+var pdfjsVersion = '1.8.331';
+var pdfjsBuild = '0dbc68a6';
 var pdfjsCoreWorker = __w_pdfjs_require__(17);
 ;
 exports.WorkerMessageHandler = pdfjsCoreWorker.WorkerMessageHandler;
 
 /***/ }),
 /* 36 */
 /***/ (function(module, exports, __w_pdfjs_require__) {
 
 "use strict";
 
 
+;
+
 /***/ })
 /******/ ]);
 });
\ No newline at end of file
--- a/browser/extensions/pdfjs/content/web/debugger.js
+++ b/browser/extensions/pdfjs/content/web/debugger.js
@@ -144,21 +144,21 @@ var FontInspector = (function FontInspec
       font.appendChild(document.createTextNode(' '));
       font.appendChild(download);
       font.appendChild(document.createTextNode(' '));
       font.appendChild(logIt);
       font.appendChild(moreInfo);
       fonts.appendChild(font);
       // Somewhat of a hack, should probably add a hook for when the text layer
       // is done rendering.
-      setTimeout(function() {
+      setTimeout(() => {
         if (this.active) {
           resetSelection();
         }
-      }.bind(this), 2000);
+      }, 2000);
     }
   };
 })();
 
 var opMap;
 
 // Manages all the page steppers.
 var StepperManager = (function StepperManagerClosure() {
--- a/browser/extensions/pdfjs/content/web/viewer.js
+++ b/browser/extensions/pdfjs/content/web/viewer.js
@@ -86,17 +86,17 @@
 /***/ (function(module, exports, __webpack_require__) {
 
 "use strict";
 
 
 Object.defineProperty(exports, "__esModule", {
   value: true
 });
-exports.localized = exports.animationStarted = exports.normalizeWheelEventDelta = exports.binarySearchFirstItem = exports.watchScroll = exports.scrollIntoView = exports.getOutputScale = exports.approximateFraction = exports.roundToDivide = exports.getVisibleElements = exports.parseQueryString = exports.noContextMenuHandler = exports.getPDFFileNameFromURL = exports.ProgressBar = exports.EventBus = exports.mozL10n = exports.RendererType = exports.VERTICAL_PADDING = exports.SCROLLBAR_PADDING = exports.MAX_AUTO_SCALE = exports.UNKNOWN_SCALE = exports.MAX_SCALE = exports.MIN_SCALE = exports.DEFAULT_SCALE = exports.DEFAULT_SCALE_VALUE = exports.CSS_UNITS = undefined;
+exports.localized = exports.animationStarted = exports.normalizeWheelEventDelta = exports.binarySearchFirstItem = exports.watchScroll = exports.scrollIntoView = exports.getOutputScale = exports.approximateFraction = exports.roundToDivide = exports.getVisibleElements = exports.parseQueryString = exports.noContextMenuHandler = exports.getPDFFileNameFromURL = exports.ProgressBar = exports.EventBus = exports.mozL10n = exports.RendererType = exports.cloneObj = exports.VERTICAL_PADDING = exports.SCROLLBAR_PADDING = exports.MAX_AUTO_SCALE = exports.UNKNOWN_SCALE = exports.MAX_SCALE = exports.MIN_SCALE = exports.DEFAULT_SCALE = exports.DEFAULT_SCALE_VALUE = exports.CSS_UNITS = undefined;
 
 var _pdfjs = __webpack_require__(1);
 
 var CSS_UNITS = 96.0 / 72.0;
 var DEFAULT_SCALE_VALUE = 'auto';
 var DEFAULT_SCALE = 1.0;
 var MIN_SCALE = 0.25;
 var MAX_SCALE = 10.0;
@@ -354,16 +354,25 @@ function normalizeWheelEventDelta(evt) {
   var MOUSE_LINES_PER_PAGE = 30;
   if (evt.deltaMode === MOUSE_DOM_DELTA_PIXEL_MODE) {
     delta /= MOUSE_PIXELS_PER_LINE * MOUSE_LINES_PER_PAGE;
   } else if (evt.deltaMode === MOUSE_DOM_DELTA_LINE_MODE) {
     delta /= MOUSE_LINES_PER_PAGE;
   }
   return delta;
 }
+function cloneObj(obj) {
+  var result = {};
+  for (var i in obj) {
+    if (Object.prototype.hasOwnProperty.call(obj, i)) {
+      result[i] = obj[i];
+    }
+  }
+  return result;
+}
 var animationStarted = new Promise(function (resolve) {
   window.requestAnimationFrame(resolve);
 });
 var localized = new Promise(function (resolve, reject) {
   if (!mozL10n) {
     resolve();
     return;
   }
@@ -474,16 +483,17 @@ exports.CSS_UNITS = CSS_UNITS;
 exports.DEFAULT_SCALE_VALUE = DEFAULT_SCALE_VALUE;
 exports.DEFAULT_SCALE = DEFAULT_SCALE;
 exports.MIN_SCALE = MIN_SCALE;
 exports.MAX_SCALE = MAX_SCALE;
 exports.UNKNOWN_SCALE = UNKNOWN_SCALE;
 exports.MAX_AUTO_SCALE = MAX_AUTO_SCALE;
 exports.SCROLLBAR_PADDING = SCROLLBAR_PADDING;
 exports.VERTICAL_PADDING = VERTICAL_PADDING;
+exports.cloneObj = cloneObj;
 exports.RendererType = RendererType;
 exports.mozL10n = mozL10n;
 exports.EventBus = EventBus;
 exports.ProgressBar = ProgressBar;
 exports.getPDFFileNameFromURL = getPDFFileNameFromURL;
 exports.noContextMenuHandler = noContextMenuHandler;
 exports.parseQueryString = parseQueryString;
 exports.getVisibleElements = getVisibleElements;
@@ -1162,16 +1172,17 @@ var PDFViewerApplication = {
     }
     var promise = this.pdfLoadingTask.destroy();
     this.pdfLoadingTask = null;
     if (this.pdfDocument) {
       this.pdfDocument = null;
       this.pdfThumbnailViewer.setDocument(null);
       this.pdfViewer.setDocument(null);
       this.pdfLinkService.setDocument(null, null);
+      this.pdfDocumentProperties.setDocument(null, null);
     }
     this.store = null;
     this.isInitialViewSet = false;
     this.pdfSidebar.reset();
     this.pdfOutlineViewer.reset();
     this.pdfAttachmentViewer.reset();
     this.findController.reset();
     this.findBar.reset();
@@ -1305,39 +1316,39 @@ var PDFViewerApplication = {
     if (percent > this.loadingBar.percent || isNaN(percent)) {
       this.loadingBar.percent = percent;
       if (_pdfjs.PDFJS.disableAutoFetch && percent) {
         if (this.disableAutoFetchLoadingBarTimeout) {
           clearTimeout(this.disableAutoFetchLoadingBarTimeout);
           this.disableAutoFetchLoadingBarTimeout = null;
         }
         this.loadingBar.show();
-        this.disableAutoFetchLoadingBarTimeout = setTimeout(function () {
+        this.disableAutoFetchLoadingBarTimeout = setTimeout(() => {
           this.loadingBar.hide();
           this.disableAutoFetchLoadingBarTimeout = null;
-        }.bind(this), DISABLE_AUTO_FETCH_LOADING_BAR_TIMEOUT);
+        }, DISABLE_AUTO_FETCH_LOADING_BAR_TIMEOUT);
       }
     }
   },
   load: function pdfViewLoad(pdfDocument, scale) {
     var self = this;
     scale = scale || _ui_utils.UNKNOWN_SCALE;
     this.pdfDocument = pdfDocument;
-    this.pdfDocumentProperties.setDocumentAndUrl(pdfDocument, this.url);
     var downloadedPromise = pdfDocument.getDownloadInfo().then(function () {
       self.downloadComplete = true;
       self.loadingBar.hide();
     });
     this.toolbar.setPagesCount(pdfDocument.numPages, false);
     this.secondaryToolbar.setPagesCount(pdfDocument.numPages);
     var id = this.documentFingerprint = pdfDocument.fingerprint;
     var store = this.store = new _view_history.ViewHistory(id);
     var baseDocumentUrl;
     baseDocumentUrl = this.baseUrl;
     this.pdfLinkService.setDocument(pdfDocument, baseDocumentUrl);
+    this.pdfDocumentProperties.setDocument(pdfDocument, this.url);
     var pdfViewer = this.pdfViewer;
     pdfViewer.currentScale = scale;
     pdfViewer.setDocument(pdfDocument);
     var firstPagePromise = pdfViewer.firstPagePromise;
     var pagesPromise = pdfViewer.pagesPromise;
     var onePageRendered = pdfViewer.onePageRendered;
     this.pageRotation = 0;
     var pdfThumbnailViewer = this.pdfThumbnailViewer;
@@ -2268,88 +2279,88 @@ exports.PDFPrintServiceFactory = PDFPrin
 
 
 Object.defineProperty(exports, "__esModule", {
   value: true
 });
 var OverlayManager = {
   overlays: {},
   active: null,
-  register: function overlayManagerRegister(name, element, callerCloseMethod, canForceClose) {
-    return new Promise(function (resolve) {
+  register(name, element, callerCloseMethod, canForceClose) {
+    return new Promise(resolve => {
       var container;
       if (!name || !element || !(container = element.parentNode)) {
         throw new Error('Not enough parameters.');
       } else if (this.overlays[name]) {
         throw new Error('The overlay is already registered.');
       }
       this.overlays[name] = {
         element,
         container,
         callerCloseMethod: callerCloseMethod || null,
         canForceClose: canForceClose || false
       };
       resolve();
-    }.bind(this));
+    });
   },
-  unregister: function overlayManagerUnregister(name) {
-    return new Promise(function (resolve) {
+  unregister(name) {
+    return new Promise(resolve => {
       if (!this.overlays[name]) {
         throw new Error('The overlay does not exist.');
       } else if (this.active === name) {
         throw new Error('The overlay cannot be removed while it is active.');
       }
       delete this.overlays[name];
       resolve();
-    }.bind(this));
+    });
   },
-  open: function overlayManagerOpen(name) {
-    return new Promise(function (resolve) {
+  open(name) {
+    return new Promise(resolve => {
       if (!this.overlays[name]) {
         throw new Error('The overlay does not exist.');
       } else if (this.active) {
         if (this.overlays[name].canForceClose) {
           this._closeThroughCaller();
         } else if (this.active === name) {
           throw new Error('The overlay is already active.');
         } else {
           throw new Error('Another overlay is currently active.');
         }
       }
       this.active = name;
       this.overlays[this.active].element.classList.remove('hidden');
       this.overlays[this.active].container.classList.remove('hidden');
       window.addEventListener('keydown', this._keyDown);
       resolve();
-    }.bind(this));
+    });
   },
-  close: function overlayManagerClose(name) {
-    return new Promise(function (resolve) {
+  close(name) {
+    return new Promise(resolve => {
       if (!this.overlays[name]) {
         throw new Error('The overlay does not exist.');
       } else if (!this.active) {
         throw new Error('The overlay is currently not active.');
       } else if (this.active !== name) {
         throw new Error('Another overlay is currently active.');
       }
       this.overlays[this.active].container.classList.add('hidden');
       this.overlays[this.active].element.classList.add('hidden');
       this.active = null;
       window.removeEventListener('keydown', this._keyDown);
       resolve();
-    }.bind(this));
+    });
   },
-  _keyDown: function overlayManager_keyDown(evt) {
+  _keyDown(evt) {
     var self = OverlayManager;
     if (self.active && evt.keyCode === 27) {
       self._closeThroughCaller();
       evt.preventDefault();
     }
   },
-  _closeThroughCaller: function overlayManager_closeThroughCaller() {
+  _closeThroughCaller() {
     if (this.overlays[this.active].callerCloseMethod) {
       this.overlays[this.active].callerCloseMethod();
     }
     if (this.active) {
       this.close(this.active);
     }
   }
 };
@@ -2890,25 +2901,25 @@ var PDFFindController = function PDFFind
       extractPageText(0);
     },
     executeCommand: function PDFFindController_executeCommand(cmd, state) {
       if (this.state === null || cmd !== 'findagain') {
         this.dirtyMatch = true;
       }
       this.state = state;
       this.updateUIState(FindStates.FIND_PENDING);
-      this._firstPagePromise.then(function () {
+      this._firstPagePromise.then(() => {
         this.extractText();
         clearTimeout(this.findTimeout);
         if (cmd === 'find') {
           this.findTimeout = setTimeout(this.nextMatch.bind(this), 250);
         } else {
           this.nextMatch();
         }
-      }.bind(this));
+      });
     },
     updatePage: function PDFFindController_updatePage(index) {
       if (this.selected.pageIdx === index) {
         this.pdfViewer.currentPageNumber = index + 1;
       }
       var page = this.pdfViewer.getPageView(index);
       if (page.textLayer) {
         page.textLayer.updateMatches();
@@ -3207,27 +3218,28 @@ var DownloadManager = function DownloadM
       FirefoxCom.request('download', {
         blobUrl,
         originalUrl: blobUrl,
         filename,
         isAttachment: true
       });
     },
     download: function DownloadManager_download(blob, url, filename) {
-      var blobUrl = window.URL.createObjectURL(blob);
+      let blobUrl = window.URL.createObjectURL(blob);
+      let onResponse = err => {
+        if (err && this.onerror) {
+          this.onerror(err);
+        }
+        window.URL.revokeObjectURL(blobUrl);
+      };
       FirefoxCom.request('download', {
         blobUrl,
         originalUrl: url,
         filename
-      }, function response(err) {
-        if (err && this.onerror) {
-          this.onerror(err);
-        }
-        window.URL.revokeObjectURL(blobUrl);
-      }.bind(this));
+      }, onResponse);
     }
   };
   return DownloadManager;
 }();
 class FirefoxPreferences extends _preferences.BasePreferences {
   _writeToStorage(prefObj) {
     return new Promise(function (resolve) {
       FirefoxCom.request('setPreferences', prefObj, resolve);
@@ -3603,26 +3615,26 @@ var HandTool = function HandToolClosure(
       }
     });
     this.eventBus.on('togglehandtool', this.toggle.bind(this));
     Promise.all([_ui_utils.localized, preferences.get('enableHandToolOnLoad')]).then(values => {
       if (values[1] === true) {
         this.handTool.activate();
       }
     }).catch(function rejected(reason) {});
-    this.eventBus.on('presentationmodechanged', function (e) {
-      if (e.switchInProgress) {
+    this.eventBus.on('presentationmodechanged', evt => {
+      if (evt.switchInProgress) {
         return;
       }
-      if (e.active) {
+      if (evt.active) {
         this.enterPresentationMode();
       } else {
         this.exitPresentationMode();
       }
-    }.bind(this));
+    });
   }
   HandTool.prototype = {
     get isActive() {
       return !!this.handTool.active;
     },
     toggle: function HandTool_toggle() {
       this.handTool.toggle();
     },
@@ -3795,18 +3807,18 @@ class PDFAttachmentViewer {
       } else {
         this._bindLink(button, item.content, filename);
       }
       div.appendChild(button);
       this.container.appendChild(div);
     }
     this._dispatchEvent(attachmentsCount);
   }
-  _appendAttachment(item) {
-    this._renderedCapability.promise.then(function (id, filename, content) {
+  _appendAttachment({ id, filename, content }) {
+    this._renderedCapability.promise.then(() => {
       var attachments = this.attachments;
       if (!attachments) {
         attachments = Object.create(null);
       } else {
         for (var name in attachments) {
           if (id === name) {
             return;
           }
@@ -3815,17 +3827,17 @@ class PDFAttachmentViewer {
       attachments[id] = {
         filename,
         content
       };
       this.render({
         attachments,
         keepRenderedCapability: true
       });
-    }.bind(this, item.id, item.filename, item.content));
+    });
   }
 }
 exports.PDFAttachmentViewer = PDFAttachmentViewer;
 
 /***/ }),
 /* 15 */
 /***/ (function(module, exports, __webpack_require__) {
 
@@ -3838,105 +3850,129 @@ Object.defineProperty(exports, "__esModu
 exports.PDFDocumentProperties = undefined;
 
 var _ui_utils = __webpack_require__(0);
 
 var _pdfjs = __webpack_require__(1);
 
 var _overlay_manager = __webpack_require__(5);
 
+const DEFAULT_FIELD_CONTENT = '-';
 class PDFDocumentProperties {
-  constructor(options) {
-    this.overlayName = options.overlayName;
-    this.fields = options.fields;
-    this.container = options.container;
-    this.rawFileSize = 0;
-    this.url = null;
-    this.pdfDocument = null;
-    if (options.closeButton) {
-      options.closeButton.addEventListener('click', this.close.bind(this));
-    }
-    this._dataAvailableCapability = (0, _pdfjs.createPromiseCapability)();
+  constructor({ overlayName, fields, container, closeButton }) {
+    this.overlayName = overlayName;
+    this.fields = fields;
+    this.container = container;
+    this._reset();
+    if (closeButton) {
+      closeButton.addEventListener('click', this.close.bind(this));
+    }
     _overlay_manager.OverlayManager.register(this.overlayName, this.container, this.close.bind(this));
   }
   open() {
+    let freezeFieldData = data => {
+      Object.defineProperty(this, 'fieldData', {
+        value: Object.freeze(data),
+        writable: false,
+        enumerable: true,
+        configurable: true
+      });
+    };
     Promise.all([_overlay_manager.OverlayManager.open(this.overlayName), this._dataAvailableCapability.promise]).then(() => {
-      this._getProperties();
+      if (this.fieldData) {
+        this._updateUI();
+        return;
+      }
+      this.pdfDocument.getMetadata().then(({ info, metadata }) => {
+        freezeFieldData({
+          'fileName': (0, _ui_utils.getPDFFileNameFromURL)(this.url),
+          'fileSize': this._parseFileSize(this.maybeFileSize),
+          'title': info.Title,
+          'author': info.Author,
+          'subject': info.Subject,
+          'keywords': info.Keywords,
+          'creationDate': this._parseDate(info.CreationDate),
+          'modificationDate': this._parseDate(info.ModDate),
+          'creator': info.Creator,
+          'producer': info.Producer,
+          'version': info.PDFFormatVersion,
+          'pageCount': this.pdfDocument.numPages
+        });
+        this._updateUI();
+        return this.pdfDocument.getDownloadInfo();
+      }).then(({ length }) => {
+        let data = (0, _ui_utils.cloneObj)(this.fieldData);
+        data['fileSize'] = this._parseFileSize(length);
+        freezeFieldData(data);
+        this._updateUI();
+      });
     });
   }
   close() {
     _overlay_manager.OverlayManager.close(this.overlayName);
   }
-  setFileSize(fileSize) {
-    if (fileSize > 0) {
-      this.rawFileSize = fileSize;
-    }
-  }
-  setDocumentAndUrl(pdfDocument, url) {
+  setDocument(pdfDocument, url) {
+    if (this.pdfDocument) {
+      this._reset();
+      this._updateUI(true);
+    }
+    if (!pdfDocument) {
+      return;
+    }
     this.pdfDocument = pdfDocument;
     this.url = url;
     this._dataAvailableCapability.resolve();
   }
-  _getProperties() {
-    if (!_overlay_manager.OverlayManager.active) {
+  setFileSize(fileSize) {
+    if (typeof fileSize === 'number' && fileSize > 0) {
+      this.maybeFileSize = fileSize;
+    }
+  }
+  _reset() {
+    this.pdfDocument = null;
+    this.url = null;
+    this.maybeFileSize = 0;
+    delete this.fieldData;
+    this._dataAvailableCapability = (0, _pdfjs.createPromiseCapability)();
+  }
+  _updateUI(reset = false) {
+    if (reset || !this.fieldData) {
+      for (let id in this.fields) {
+        this.fields[id].textContent = DEFAULT_FIELD_CONTENT;
+      }
       return;
     }
-    this.pdfDocument.getDownloadInfo().then(data => {
-      if (data.length === this.rawFileSize) {
-        return;
-      }
-      this.setFileSize(data.length);
-      this._updateUI(this.fields['fileSize'], this._parseFileSize());
-    });
-    this.pdfDocument.getMetadata().then(data => {
-      var content = {
-        'fileName': (0, _ui_utils.getPDFFileNameFromURL)(this.url),
-        'fileSize': this._parseFileSize(),
-        'title': data.info.Title,
-        'author': data.info.Author,
-        'subject': data.info.Subject,
-        'keywords': data.info.Keywords,
-        'creationDate': this._parseDate(data.info.CreationDate),
-        'modificationDate': this._parseDate(data.info.ModDate),
-        'creator': data.info.Creator,
-        'producer': data.info.Producer,
-        'version': data.info.PDFFormatVersion,
-        'pageCount': this.pdfDocument.numPages
-      };
-      for (var identifier in content) {
-        this._updateUI(this.fields[identifier], content[identifier]);
-      }
-    });
-  }
-  _updateUI(field, content) {
-    if (field && content !== undefined && content !== '') {
-      field.textContent = content;
-    }
-  }
-  _parseFileSize() {
-    var fileSize = this.rawFileSize,
-        kb = fileSize / 1024;
+    if (_overlay_manager.OverlayManager.active !== this.overlayName) {
+      return;
+    }
+    for (let id in this.fields) {
+      let content = this.fieldData[id];
+      this.fields[id].textContent = content || content === 0 ? content : DEFAULT_FIELD_CONTENT;
+    }
+  }
+  _parseFileSize(fileSize = 0) {
+    let kb = fileSize / 1024;
     if (!kb) {
       return;
     } else if (kb < 1024) {
       return _ui_utils.mozL10n.get('document_properties_kb', {
         size_kb: (+kb.toPrecision(3)).toLocaleString(),
         size_b: fileSize.toLocaleString()
       }, '{{size_kb}} KB ({{size_b}} bytes)');
     }
     return _ui_utils.mozL10n.get('document_properties_mb', {
       size_mb: (+(kb / 1024).toPrecision(3)).toLocaleString(),
       size_b: fileSize.toLocaleString()
     }, '{{size_mb}} MB ({{size_b}} bytes)');
   }
   _parseDate(inputDate) {
-    var dateToParse = inputDate;
-    if (dateToParse === undefined) {
-      return '';
-    }
+    if (!inputDate) {
+      return;
+    }
+    let dateToParse = inputDate;
     if (dateToParse.substring(0, 2) === 'D:') {
       dateToParse = dateToParse.substring(2);
     }
     var year = parseInt(dateToParse.substring(0, 4), 10);
     var month = parseInt(dateToParse.substring(4, 6), 10) - 1;
     var day = parseInt(dateToParse.substring(6, 8), 10);
     var hours = parseInt(dateToParse.substring(8, 10), 10);
     var minutes = parseInt(dateToParse.substring(10, 12), 10);
@@ -6015,31 +6051,31 @@ var PDFThumbnailViewer = function PDFThu
       if (this.pdfDocument) {
         this._cancelRendering();
         this._resetView();
       }
       this.pdfDocument = pdfDocument;
       if (!pdfDocument) {
         return Promise.resolve();
       }
-      return pdfDocument.getPage(1).then(function (firstPage) {
+      return pdfDocument.getPage(1).then(firstPage => {
         var pagesCount = pdfDocument.numPages;
         var viewport = firstPage.getViewport(1.0);
-        for (var pageNum = 1; pageNum <= pagesCount; ++pageNum) {
+        for (let pageNum = 1; pageNum <= pagesCount; ++pageNum) {
           var thumbnail = new _pdf_thumbnail_view.PDFThumbnailView({
             container: this.container,
             id: pageNum,
             defaultViewport: viewport.clone(),
             linkService: this.linkService,
             renderingQueue: this.renderingQueue,
             disableCanvasToImageConversion: false
           });
           this.thumbnails.push(thumbnail);
         }
-      }.bind(this));
+      });
     },
     _cancelRendering: function PDFThumbnailViewer_cancelRendering() {
       for (var i = 0, ii = this.thumbnails.length; i < ii; i++) {
         if (this.thumbnails[i]) {
           this.thumbnails[i].cancelRendering();
         }
       }
     },
@@ -6349,27 +6385,27 @@ var PDFViewer = function pdfViewer() {
           this._pages.push(pageView);
         }
         onePageRenderedCapability.promise.then(() => {
           if (_pdfjs.PDFJS.disableAutoFetch) {
             pagesCapability.resolve();
             return;
           }
           var getPagesLeft = pagesCount;
-          for (var pageNum = 1; pageNum <= pagesCount; ++pageNum) {
-            pdfDocument.getPage(pageNum).then(function (pageNum, pdfPage) {
+          for (let pageNum = 1; pageNum <= pagesCount; ++pageNum) {
+            pdfDocument.getPage(pageNum).then(pdfPage => {
               var pageView = this._pages[pageNum - 1];
               if (!pageView.pdfPage) {
                 pageView.setPdfPage(pdfPage);
               }
               this.linkService.cachePageRef(pageNum, pdfPage.ref);
               if (--getPagesLeft === 0) {
                 pagesCapability.resolve();
               }
-            }.bind(this, pageNum));
+            });
           }
         });
         this.eventBus.dispatch('pagesinit', { source: this });
         if (this.defaultRenderingQueue) {
           this.update();
         }
         if (this.findController) {
           this.findController.resolveFirstPage();
@@ -6780,16 +6816,20 @@ exports.PDFViewer = PDFViewer;
 /***/ (function(module, exports, __webpack_require__) {
 
 "use strict";
 
 
 Object.defineProperty(exports, "__esModule", {
   value: true
 });
+exports.BasePreferences = undefined;
+
+var _ui_utils = __webpack_require__(0);
+
 var defaultPreferences = null;
 function getDefaultPreferences() {
   if (!defaultPreferences) {
     defaultPreferences = Promise.resolve({
       "showPreviousViewOnLoad": true,
       "defaultZoomValue": "",
       "sidebarViewOnLoad": 0,
       "enableHandToolOnLoad": false,
@@ -6806,55 +6846,46 @@ function getDefaultPreferences() {
       "renderer": "canvas",
       "renderInteractiveForms": false,
       "enablePrintAutoRotate": false,
       "disablePageLabels": false
     });
   }
   return defaultPreferences;
 }
-function cloneObj(obj) {
-  var result = {};
-  for (var i in obj) {
-    if (Object.prototype.hasOwnProperty.call(obj, i)) {
-      result[i] = obj[i];
-    }
-  }
-  return result;
-}
 class BasePreferences {
   constructor() {
     if (this.constructor === BasePreferences) {
       throw new Error('Cannot initialize BasePreferences.');
     }
     this.prefs = null;
     this._initializedPromise = getDefaultPreferences().then(defaults => {
       Object.defineProperty(this, 'defaults', {
         value: Object.freeze(defaults),
         writable: false,
         enumerable: true,
         configurable: false
       });
-      this.prefs = cloneObj(defaults);
+      this.prefs = (0, _ui_utils.cloneObj)(defaults);
       return this._readFromStorage(defaults);
     }).then(prefObj => {
       if (prefObj) {
         this.prefs = prefObj;
       }
     });
   }
   _writeToStorage(prefObj) {
     return Promise.reject(new Error('Not implemented: _writeToStorage'));
   }
   _readFromStorage(prefObj) {
     return Promise.reject(new Error('Not implemented: _readFromStorage'));
   }
   reset() {
     return this._initializedPromise.then(() => {
-      this.prefs = cloneObj(this.defaults);
+      this.prefs = (0, _ui_utils.cloneObj)(this.defaults);
       return this._writeToStorage(this.defaults);
     });
   }
   reload() {
     return this._initializedPromise.then(() => {
       return this._readFromStorage(this.defaults);
     }).then(prefObj => {
       if (prefObj) {
@@ -7004,28 +7035,26 @@ var SecondaryToolbar = function Secondar
       var items = this.items;
       items.firstPage.disabled = this.pageNumber <= 1;
       items.lastPage.disabled = this.pageNumber >= this.pagesCount;
       items.pageRotateCw.disabled = this.pagesCount === 0;
       items.pageRotateCcw.disabled = this.pagesCount === 0;
     },
     _bindClickListeners: function SecondaryToolbar_bindClickListeners() {
       this.toggleButton.addEventListener('click', this.toggle.bind(this));
-      for (var button in this.buttons) {
-        var element = this.buttons[button].element;
-        var eventName = this.buttons[button].eventName;
-        var close = this.buttons[button].close;
-        element.addEventListener('click', function (eventName, close) {
+      for (let button in this.buttons) {
+        let { element, eventName, close } = this.buttons[button];
+        element.addEventListener('click', evt => {
           if (eventName !== null) {
             this.eventBus.dispatch(eventName, { source: this });
           }
           if (close) {
             this.close();
           }
-        }.bind(this, eventName, close));
+        });
       }
     },
     _bindHandToolListener: function SecondaryToolbar_bindHandToolListener(toggleHandToolButton) {
       var isHandToolActive = false;
       this.eventBus.on('handtoolchanged', function (e) {
         if (isHandToolActive === e.isActive) {
           return;
         }
@@ -7136,21 +7165,21 @@ var TextLayerBuilder = function TextLaye
       this.textLayerRenderTask = (0, _pdfjs.renderTextLayer)({
         textContent: this.textContent,
         container: textLayerFrag,
         viewport: this.viewport,
         textDivs: this.textDivs,
         timeout,
         enhanceTextSelection: this.enhanceTextSelection
       });
-      this.textLayerRenderTask.promise.then(function () {
+      this.textLayerRenderTask.promise.then(() => {
         this.textLayerDiv.appendChild(textLayerFrag);
         this._finishRendering();
         this.updateMatches();
-      }.bind(this), function (reason) {});
+      }, function (reason) {});
     },
     cancel: function TextLayerBuilder_cancel() {
       if (this.textLayerRenderTask) {
         this.textLayerRenderTask.cancel();
         this.textLayerRenderTask = null;
       }
     },
     setTextContent: function TextLayerBuilder_setTextContent(textContent) {
--- a/browser/locales/en-US/chrome/browser/downloads/downloads.dtd
+++ b/browser/locales/en-US/chrome/browser/downloads/downloads.dtd
@@ -129,17 +129,21 @@
 <!-- LOCALIZATION NOTE (downloadsHistory.label, downloadsHistory.accesskey):
      This string is shown at the bottom of the Downloads Panel when all the
      downloads fit in the available space, or when there are no downloads in
      the panel at all.
      -->
 <!ENTITY downloadsHistory.label           "Show All Downloads">
 <!ENTITY downloadsHistory.accesskey       "S">
 
-<!ENTITY openDownloadsFolder.label       "Open Downloads Folder">
+<!-- LOCALIZATION NOTE (openDownloadsFolder.label):
+     This command is not currently available in the user interface, but the
+     string was preserved by bug 1362207 to be used in a future version.
+     -->
+<!ENTITY openDownloadsFolder.label        "Open Downloads Folder">
 
 <!ENTITY clearDownloadsButton.label       "Clear Downloads">
 <!ENTITY clearDownloadsButton.tooltip     "Clears completed, canceled and failed downloads">
 
 <!-- LOCALIZATION NOTE (downloadsListEmpty.label):
      This string is shown when there are no items in the Downloads view, when it
      is displayed inside a browser tab.
      -->
--- a/browser/locales/en-US/chrome/browser/downloads/downloads.properties
+++ b/browser/locales/en-US/chrome/browser/downloads/downloads.properties
@@ -74,27 +74,16 @@ unblockButtonOpen=Open
 unblockButtonUnblock=Allow download
 unblockButtonConfirmBlock=Remove file
 
 # LOCALIZATION NOTE (sizeWithUnits):
 # %1$S is replaced with the size number, and %2$S with the measurement unit.
 sizeWithUnits=%1$S %2$S
 sizeUnknown=Unknown size
 
-# LOCALIZATION NOTE (shortTimeLeftSeconds, shortTimeLeftMinutes,
-#                    shortTimeLeftHours, shortTimeLeftDays):
-# These values are displayed in the downloads indicator in the main browser
-# window, where space is available for three characters maximum.  %1$S is
-# replaced with the time left for the given measurement unit.  Even for days,
-# the value is never longer than two digits.
-shortTimeLeftSeconds=%1$Ss
-shortTimeLeftMinutes=%1$Sm
-shortTimeLeftHours=%1$Sh
-shortTimeLeftDays=%1$Sd
-
 # LOCALIZATION NOTE (statusSeparator, statusSeparatorBeforeNumber):
 # These strings define templates for the separation of different elements in the
 # status line of a download item.  As a separator, by default we use the Unicode
 # character U+2014 'EM DASH' (long dash).  Examples of status lines include
 # "Canceled - 222.net", "1.1 MB - website2.com", or "Paused -  1.1 MB".  Note
 # that we use a wider space after the separator when it is followed by a number,
 # just to avoid visually confusing it with with a minus sign with some fonts.
 # If you use a different separator, this might not be necessary.  However, there
--- a/browser/moz.configure
+++ b/browser/moz.configure
@@ -2,11 +2,10 @@
 # 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/.
 
 imply_option('MOZ_PLACES', True)
 imply_option('MOZ_SERVICES_HEALTHREPORT', True)
 imply_option('MOZ_SERVICES_SYNC', True)
-imply_option('MOZ_SERVICES_CLOUDSYNC', True)
 
 include('../toolkit/moz.configure')
--- a/browser/themes/linux/downloads/indicator.css
+++ b/browser/themes/linux/downloads/indicator.css
@@ -72,36 +72,16 @@ toolbar[brighttext] #downloads-button[cu
   fill: var(--toolbarbutton-icon-fill-attention);
 }
 
 #downloads-button[cui-areatype="menu-panel"][attention="success"] {
   list-style-image: url("chrome://browser/skin/downloads/download-glow-menuPanel.png");
   -moz-image-region: auto;
 }
 
-/* In the next few rules, we use :not([counter]) as a shortcut that is
-   equivalent to -moz-any([progress], [paused]). */
-
-#downloads-button:not([counter]) > #downloads-indicator-anchor > #downloads-indicator-progress-area > #downloads-indicator-counter {
-  background: var(--downloads-indicator-image) center no-repeat;
-  -moz-context-properties: fill;
-  fill: var(--toolbarbutton-icon-fill);
-  background-size: 12px;
-}
-
-toolbar[brighttext] #downloads-button:not([counter]):not([attention="success"]) > #downloads-indicator-anchor > #downloads-button-progress-area > #downloads-indicator-counter {
-  fill: var(--toolbarbutton-icon-fill-inverted);
-}
-
-#downloads-button:not([counter])[attention="success"] > #downloads-indicator-anchor > #downloads-indicator-progress-area > #downloads-indicator-counter {
-  fill: var(--toolbarbutton-icon-fill-attention);
-}
-
-/*** Download notifications ***/
-
 #downloads-indicator-notification {
   opacity: 0;
   background-size: 16px;
   background-position: center;
   background-repeat: no-repeat;
   width: 16px;
   height: 16px;
 }
@@ -134,69 +114,8 @@ toolbar[brighttext] #downloads-button:no
   to   { opacity: 0; transform: scale(8); }
 }
 
 #downloads-notification-anchor[notification="finish"] > #downloads-indicator-notification {
   background-image: url("chrome://browser/skin/downloads/download-notification-finish.png");
   animation-name: downloadsIndicatorNotificationFinish;
   animation-duration: 1s;
 }
-
-/*** Progress bar and text ***/
-
-#downloads-indicator-counter {
-  height: 10px;
-  margin: 0;
-  color: hsl(0,0%,30%);
-  text-shadow: 0 1px 0 hsla(0,0%,100%,.5);
-  font-size: 10px;
-  line-height: 10px;
-  text-align: center;
-}
-
-toolbar[brighttext] #downloads-indicator-counter {
-  color: white;
-  text-shadow: 0 0 1px rgba(0,0,0,.7),
-               0 1px 1.5px rgba(0,0,0,.5);
-}
-
-#downloads-indicator-progress {
-  width: 18px;
-  height: 6px;
-  min-width: 0;
-  min-height: 0;
-  margin-top: 1px;
-  margin-bottom: 2px;
-  border-radius: 2px;
-  box-shadow: 0 1px 0 hsla(0,0%,100%,.4);
-}
-
-#downloads-indicator-progress > .progress-bar {
-  -moz-appearance: none;
-  min-width: 0;
-  min-height: 0;
-  /* The background-clip: border-box; and background-image: none; are there to expand the background-color behind the border */
-  background-clip: padding-box, border-box;
-  background-color: rgb(255, 135, 94);
-  background-image: linear-gradient(transparent 1px, rgba(255, 255, 255, 0.4) 1px, rgba(255, 255, 255, 0.4) 2px, transparent 2px), none;
-  border: 1px solid;
-  border-color: rgba(0,43,86,.6) rgba(0,43,86,.4) rgba(0,43,86,.4);
-  border-radius: 2px 0 0 2px;
-}
-
-#downloads-indicator-progress > .progress-remainder {
-  -moz-appearance: none;
-  min-width: 0;
-  min-height: 0;
-  background-image: linear-gradient(#505050, #575757);
-  border: 1px solid;
-  border-color: hsla(0,0%,0%,.6) hsla(0,0%,0%,.4) hsla(0,0%,0%,.4);
-  border-inline-start: none;
-  border-radius: 0 2px 2px 0;
-}
-
-#downloads-button[paused] > #downloads-indicator-anchor > #downloads-indicator-progress-area > #downloads-indicator-progress > .progress-bar {
-  background-color: rgb(220, 230, 81);
-}
-
-#downloads-button[paused] > #downloads-indicator-anchor > #downloads-indicator-progress-area > #downloads-indicator-progress > .progress-remainder {
-  background-image: linear-gradient(#4b5000, #515700);
-}
--- a/browser/themes/osx/downloads/indicator.css
+++ b/browser/themes/osx/downloads/indicator.css
@@ -75,34 +75,16 @@ toolbar[brighttext] #downloads-indicator
   fill: var(--toolbarbutton-icon-fill-attention);
 }
 
 #downloads-button[cui-areatype="menu-panel"][attention="success"] {
   list-style-image: url("chrome://browser/skin/downloads/download-glow-menuPanel.png");
   -moz-image-region: auto;
 }
 
-/* In the next few rules, we use :not([counter]) as a shortcut that is
-   equivalent to -moz-any([progress], [paused]). */
-
-#downloads-button:not([counter]) > #downloads-indicator-anchor > #downloads-indicator-progress-area > #downloads-indicator-counter {
-  background: var(--downloads-indicator-image) center no-repeat;
-  -moz-context-properties: fill;
-  fill: var(--toolbarbutton-icon-fill);
-  background-size: 12px;
-}
-
-toolbar[brighttext] #downloads-button:not([counter]):not([attention="success"]) > #downloads-indicator-anchor > #downloads-indicator-progress-area > #downloads-indicator-counter {
-  fill: var(--toolbarbutton-icon-fill-inverted);
-}
-
-#downloads-button:not([counter])[attention="success"] > #downloads-indicator-anchor > #downloads-indicator-progress-area > #downloads-indicator-counter {
-  fill: var(--toolbarbutton-icon-fill-attention);
-}
-
 @media (min-resolution: 2dppx) {
   #downloads-button[cui-areatype="menu-panel"][attention="success"] {
     list-style-image: url("chrome://browser/skin/downloads/download-glow-menuPanel@2x.png");
   }
 }
 
 /*** Download notifications ***/
 
@@ -157,63 +139,8 @@ toolbar[brighttext] #downloads-button:no
   animation-duration: 1s;
 }
 
 @media (min-resolution: 2dppx) {
   #downloads-notification-anchor[notification="finish"] > #downloads-indicator-notification {
     background-image: url("chrome://browser/skin/downloads/download-notification-finish@2x.png");
   }
 }
-
-/*** Progress bar and text ***/
-
-#downloads-indicator-counter {
-  height: 9px;
-  margin: -3px 0 0;
-  color: hsl(0,0%,30%);
-  text-shadow: 0 1px 0 hsla(0,0%,100%,.5);
-  font-size: 9px;
-  line-height: 9px;
-  text-align: center;
-}
-
-#downloads-indicator-progress {
-  width: 16px;
-  height: 5px;
-  min-width: 0;
-  min-height: 0;
-  margin-top: 1px;
-  margin-bottom: 2px;
-  border-radius: 2px;
-  box-shadow: 0 1px 0 hsla(0,0%,100%,.4);
-}
-
-#downloads-indicator-progress > .progress-bar {
-  -moz-appearance: none;
-  min-width: 0;
-  min-height: 0;
-  /* The background-clip: border-box; and background-image: none; are there to expand the background-color behind the border */
-  background-clip: padding-box, border-box;
-  background-color: rgb(90, 185, 255);
-  background-image: linear-gradient(transparent 1px, rgba(255, 255, 255, 0.4) 1px, rgba(255, 255, 255, 0.4) 2px, transparent 2px), none;
-  border: 1px solid;
-  border-color: rgba(0,43,86,.6) rgba(0,43,86,.4) rgba(0,43,86,.4);
-  border-radius: 2px 0 0 2px;
-}
-
-#downloads-indicator-progress > .progress-remainder {
-  -moz-appearance: none;
-  min-width: 0;
-  min-height: 0;
-  background-image: linear-gradient(#505050, #575757);
-  border: 1px solid;
-  border-color: hsla(0,0%,0%,.6) hsla(0,0%,0%,.4) hsla(0,0%,0%,.4);
-  border-inline-start: none;
-  border-radius: 0 2px 2px 0;
-}
-
-#downloads-button[paused] > #downloads-indicator-anchor > #downloads-indicator-progress-area > #downloads-indicator-progress > .progress-bar {
-  background-color: rgb(220, 230, 81);
-}
-
-#downloads-button[paused] > #downloads-indicator-anchor > #downloads-indicator-progress-area > #downloads-indicator-progress > .progress-remainder {
-  background-image: linear-gradient(#4b5000, #515700);
-}
--- a/browser/themes/shared/compacttheme.inc.css
+++ b/browser/themes/shared/compacttheme.inc.css
@@ -173,22 +173,16 @@ toolbar:-moz-lwtheme-darktext {
 
 #navigator-toolbox .toolbarbutton-1,
 .browserContainer > findbar .findbar-button,
 #PlacesToolbar toolbarbutton.bookmark-item {
   color: var(--chrome-color);
   text-shadow: var(--toolbarbutton-text-shadow);
 }
 
-/* Using toolbar[brighttext] instead of important to override linux */
-toolbar[brighttext] #downloads-indicator-counter {
-  text-shadow: var(--toolbarbutton-text-shadow);
-  color: var(--chrome-color);
-}
-
 #TabsToolbar {
   text-shadow: none !important;
 }
 
 /* Back and forward button */
 
 #back-button > .toolbarbutton-icon,
 #forward-button > .toolbarbutton-icon {
--- a/browser/themes/shared/downloads/downloads.inc.css
+++ b/browser/themes/shared/downloads/downloads.inc.css
@@ -92,64 +92,24 @@
   padding: 0;
 }
 
 #downloadsHistory {
   padding-inline-start: 10px;
   padding-inline-end: 10px;
 }
 
-#downloadsPanel[hasdownloads] #downloadsFooterButtons:not(.downloadsHideDropmarker) > #downloadsHistory {
-  padding-inline-start: 68px;
-}
-
-toolbarseparator.downloadsDropmarkerSplitter {
-  margin: 7px 0;
-}
-
 @item@ > toolbarseparator {
   margin: 10px 0;
 }
 
-@item@:hover > toolbarseparator,
-#downloadsFooter:hover toolbarseparator.downloadsDropmarkerSplitter,
-#downloadsFooter[showingdropdown] toolbarseparator {
+@item@:hover > toolbarseparator {
   margin: 0;
 }
 
-.downloadsDropmarker {
-  padding: 0 21px;
-}
-
-.downloadsDropmarker > .button-box > hbox {
-  display: none;
-}
-
-.downloadsDropmarker > .button-box > .button-menu-dropmarker {
-  /* This is to override the linux !important */
-  -moz-appearance: none !important;
-  display: -moz-box;
-  padding: 0;
-  margin: 0;
-}
-
-.downloadsDropmarker > .button-box > .button-menu-dropmarker > .dropmarker-icon {
-  width: 16px;
-  height: 16px;
-  list-style-image: url("chrome://global/skin/icons/menubutton-dropmarker.svg");
-  filter: url("chrome://global/skin/filters.svg#fill");
-  fill: currentColor;
-}
-
-/* Override default icon size which is too small for this dropdown */
-.downloadsDropmarker > .button-box > .button-menu-dropmarker {
-  width: 16px;
-  height: 16px;
-}
-
 #downloadsSummary {
   -moz-user-focus: normal;
 }
 
 #downloadsSummary > .downloadTypeIcon {
   list-style-image: url("chrome://browser/skin/downloads/download-summary.svg");
 }
 
--- a/browser/themes/shared/tabs.inc.css
+++ b/browser/themes/shared/tabs.inc.css
@@ -187,23 +187,23 @@
 }
 
 .tab-label {
   margin-inline-end: 0;
   margin-inline-start: 0;
 }
 
 .tab-close-button {
-  margin-inline-start: 2px;
+  margin-inline-start: 1px;
   margin-inline-end: -2px;
   padding: 0;
 }
 
 .tab-icon-sound {
-  margin-inline-start: 4px;
+  margin-inline-start: 1px;
   width: 16px;
   height: 16px;
   padding: 0;
 }
 
 .tab-icon-sound[soundplaying],
 .tab-icon-sound[muted],
 .tab-icon-sound[blocked] {
--- a/browser/themes/windows/browser.css
+++ b/browser/themes/windows/browser.css
@@ -2009,16 +2009,28 @@ notification.pluginVulnerable > .notific
    */
   margin-left: -10px;
   margin-right: -10px;
   margin-bottom: -10px;
 }
 
 %include ../shared/contextmenu.inc.css
 
+/* Make context menu items larger when opened through touch. */
+#contentAreaContextMenu[touchmode] menu,
+#contentAreaContextMenu[touchmode] menuitem {
+  padding-top: 12px;
+  padding-bottom: 12px;
+}
+
+#contentAreaContextMenu[touchmode] > #context-navigation > menuitem {
+  padding-top: 7px;
+  padding-bottom: 7px;
+}
+
 #context-navigation {
   background-color: menu;
   padding-bottom: 4px;
 }
 
 #context-sep-navigation {
   margin-inline-start: -28px;
   margin-top: -4px;
--- a/browser/themes/windows/downloads/indicator.css
+++ b/browser/themes/windows/downloads/indicator.css
@@ -72,34 +72,16 @@ toolbar[brighttext] #downloads-button:no
   fill: var(--toolbarbutton-icon-fill-attention);
 }
 
 #downloads-button[cui-areatype="menu-panel"][attention="success"] {
   list-style-image: url("chrome://browser/skin/downloads/download-glow-menuPanel.png");
   -moz-image-region: auto;
 }
 
-/* In the next few rules, we use :not([counter]) as a shortcut that is
-   equivalent to -moz-any([progress], [paused]). */
-
-#downloads-button:not([counter]) > #downloads-indicator-anchor > #downloads-indicator-progress-area > #downloads-indicator-counter {
-  background: var(--downloads-indicator-image) center no-repeat;
-  -moz-context-properties: fill;
-  fill: var(--toolbarbutton-icon-fill);
-  background-size: 12px;
-}
-
-toolbar[brighttext] #downloads-button:not([counter]):not([attention="success"]) > #downloads-indicator-anchor > #downloads-button-progress-area > #downloads-indicator-counter {
-  fill: var(--toolbarbutton-icon-fill-inverted);
-}
-
-#downloads-button:not([counter])[attention="success"] > #downloads-indicator-anchor > #downloads-indicator-progress-area > #downloads-indicator-counter {
-  fill: var(--toolbarbutton-icon-fill-attention);
-}
-
 /*** Download notifications ***/
 
 #downloads-indicator-notification {
   opacity: 0;
   background-size: 16px;
   background-position: center;
   background-repeat: no-repeat;
   width: 16px;
@@ -134,72 +116,8 @@ toolbar[brighttext] #downloads-button:no
   to   { opacity: 0; transform: scale(8); }
 }
 
 #downloads-notification-anchor[notification="finish"] > #downloads-indicator-notification {
   background-image: url("chrome://browser/skin/downloads/download-notification-finish.png");
   animation-name: downloadsIndicatorNotificationFinish;
   animation-duration: 1s;
 }
-
-/*** Progress bar and text ***/
-
-#downloads-indicator-counter {
-  height: 9px;
-  margin: -3px 0px 0px 0px;
-  color: hsl(0,0%,30%);
-  text-shadow: hsla(0,0%,100%,.5) 0 1px;
-  font-size: 9px;
-  line-height: 9px;
-  text-align: center;
-
-  /* Bug 812345 added this... */
-  margin-bottom: -1px;
-}
-
-toolbar[brighttext] #downloads-indicator-counter {
-  color: white;
-  text-shadow: 0 0 1px rgba(0,0,0,.7),
-               0 1px 1.5px rgba(0,0,0,.5);
-}
-
-#downloads-indicator-progress {
-  width: 16px;
-  height: 5px;
-  min-width: 0;
-  min-height: 0;
-  margin-top: 1px;
-  margin-bottom: 2px;
-  border-radius: 2px;
-  box-shadow: 0 1px 0 hsla(0,0%,100%,.4);
-}
-
-#downloads-indicator-progress > .progress-bar {
-  -moz-appearance: none;
-  min-width: 0;
-  min-height: 0;
-  /* The background-clip: border-box; and background-image: none; are there to expand the background-color behind the border */
-  background-clip: padding-box, border-box;
-  background-color: rgb(90, 201, 66);
-  background-image: linear-gradient(transparent 1px, rgba(255, 255, 255, 0.4) 1px, rgba(255, 255, 255, 0.4) 2px, transparent 2px), none;
-  border: 1px solid;
-  border-color: rgba(0,43,86,.6) rgba(0,43,86,.4) rgba(0,43,86,.4);
-  border-radius: 2px 0 0 2px;
-}
-
-#downloads-indicator-progress > .progress-remainder {
-  -moz-appearance: none;
-  min-width: 0;
-  min-height: 0;
-  background-image: linear-gradient(#505050, #575757);
-  border: 1px solid;
-  border-color: hsla(0,0%,0%,.6) hsla(0,0%,0%,.4) hsla(0,0%,0%,.4);
-  border-inline-start: none;
-  border-radius: 0 2px 2px 0;
-}
-
-#downloads-button[paused] > #downloads-indicator-anchor > #downloads-indicator-progress-area > #downloads-indicator-progress > .progress-bar {
-  background-color: rgb(220, 230, 81);
-}
-
-#downloads-button[paused] > #downloads-indicator-anchor > #downloads-indicator-progress-area > #downloads-indicator-progress > .progress-remainder {
-  background-image: linear-gradient(#4b5000, #515700);
-}
--- a/build/valgrind/valgrind.sh
+++ b/build/valgrind/valgrind.sh
@@ -22,17 +22,17 @@ cd $objdir
 if [ "`uname -m`" = "x86_64" ]; then
     _arch=64
 else
     _arch=32
 fi
 
 TOOLTOOL_MANIFEST=browser/config/tooltool-manifests/linux${_arch}/releng.manifest
 TOOLTOOL_SERVER=https://api.pub.build.mozilla.org/tooltool/
-(cd $srcdir; python /builds/tooltool.py --url $TOOLTOOL_SERVER --overwrite -m $TOOLTOOL_MANIFEST fetch ${TOOLTOOL_CACHE:+ -c ${TOOLTOOL_CACHE}}) || exit 2
+(cd $srcdir; ./mach artifact toolchain -v --tooltool-url $TOOLTOOL_SERVER --tooltool-manifest $TOOLTOOL_MANIFEST ${TOOLTOOL_CACHE:+ --cache-dir ${TOOLTOOL_CACHE}}) || exit 2
 
 # Note: an exit code of 2 turns the job red on TBPL.
 MOZCONFIG=$srcdir/browser/config/mozconfigs/linux${_arch}/valgrind make -f $srcdir/client.mk configure || exit 2
 make -j4 || exit 2
 make package || exit 2
 
 # We need to set MOZBUILD_STATE_PATH so that |mach| skips its first-run
 # initialization step and actually runs the |valgrind-test| command.
new file mode 100644
--- /dev/null
+++ b/caps/.eslintrc.js
@@ -0,0 +1,7 @@
+"use strict";
+
+module.exports = {
+  "extends": [
+    "plugin:mozilla/recommended"
+  ]
+};
new file mode 100644
--- /dev/null
+++ b/caps/tests/mochitest/.eslintrc.js
@@ -0,0 +1,8 @@
+"use strict";
+
+module.exports = {
+  "extends": [
+    "plugin:mozilla/mochitest-test",
+    "plugin:mozilla/browser-test"
+  ]
+};
--- a/caps/tests/mochitest/test_addonMayLoad.html
+++ b/caps/tests/mochitest/test_addonMayLoad.html
@@ -17,18 +17,18 @@ https://bugzilla.mozilla.org/show_bug.cg
   const Cu = Components.utils;
   Cu.import("resource://gre/modules/Services.jsm");
   let ssm = Services.scriptSecurityManager;
   let aps = Cc["@mozilla.org/addons/policy-service;1"].getService(Ci.nsIAddonPolicyService).wrappedJSObject;
 
   SimpleTest.waitForExplicitFinish();
   let oldAddonIdCallback = aps.setExtensionURIToAddonIdCallback(uri => uri.host);
   SimpleTest.registerCleanupFunction(function() {
-    aps.setAddonLoadURICallback('addona', null);
-    aps.setAddonLoadURICallback('addonb', null);
+    aps.setAddonLoadURICallback("addona", null);
+    aps.setAddonLoadURICallback("addonb", null);
     aps.setExtensionURIToAddonIdCallback(oldAddonIdCallback);
   });
 
   function tryLoad(sb, uri) {
     let p = new Promise(function(resolve, reject) {
       Cu.exportFunction(resolve, sb, { defineAs: "finish" });
       Cu.exportFunction(reject, sb, { defineAs: "error" });
       sb.eval("try { (function () { " +
@@ -36,38 +36,38 @@ https://bugzilla.mozilla.org/show_bug.cg
               "  xhr.onreadystatechange = function() { if (xhr.readyState == XMLHttpRequest.DONE) { finish(xhr.status == 200); } };" +
               "  xhr.open('GET', '" + uri + "', true);" +
               "  xhr.send();" +
               "})() } catch (e) { error(e); }");
     });
     return p;
   }
 
-  let addonA = new Cu.Sandbox(ssm.createCodebasePrincipal(Services.io.newURI('moz-extension://addonA/'), {}),
-                              {wantGlobalProperties: ['XMLHttpRequest']});
-  let addonB = new Cu.Sandbox(ssm.createCodebasePrincipal(Services.io.newURI('moz-extension://addonB/'), {}),
-                              {wantGlobalProperties: ['XMLHttpRequest']});
+  let addonA = new Cu.Sandbox(ssm.createCodebasePrincipal(Services.io.newURI("moz-extension://addonA/"), {}),
+                              {wantGlobalProperties: ["XMLHttpRequest"]});
+  let addonB = new Cu.Sandbox(ssm.createCodebasePrincipal(Services.io.newURI("moz-extension://addonB/"), {}),
+                              {wantGlobalProperties: ["XMLHttpRequest"]});
 
-  function uriForDomain(d) { return d + '/tests/caps/tests/mochitest/file_data.txt' }
+  function uriForDomain(d) { return d + "/tests/caps/tests/mochitest/file_data.txt" }
 
-  tryLoad(addonA, uriForDomain('http://test1.example.org'))
+  tryLoad(addonA, uriForDomain("http://test1.example.org"))
   .then(function(success) {
     ok(!success, "cross-origin load should fail for addon A");
-    aps.setAddonLoadURICallback('addona', function(uri) { return /test1/.test(uri.host); });
-    aps.setAddonLoadURICallback('addonb', function(uri) { return /test2/.test(uri.host); });
-    return tryLoad(addonA, uriForDomain('http://test1.example.org'));
+    aps.setAddonLoadURICallback("addona", function(uri) { return /test1/.test(uri.host); });
+    aps.setAddonLoadURICallback("addonb", function(uri) { return /test2/.test(uri.host); });
+    return tryLoad(addonA, uriForDomain("http://test1.example.org"));
   }).then(function(success) {
     ok(success, "whitelisted cross-origin load of test1 should succeed for addon A");
-    return tryLoad(addonB, uriForDomain('http://test1.example.org'));
+    return tryLoad(addonB, uriForDomain("http://test1.example.org"));
   }).then(function(success) {
     ok(!success, "non-whitelisted cross-origin load of test1 should fail for addon B");
-    return tryLoad(addonB, uriForDomain('http://test2.example.org'));
+    return tryLoad(addonB, uriForDomain("http://test2.example.org"));
   }).then(function(success) {
     ok(success, "whitelisted cross-origin load of test2 should succeed for addon B");
-    return tryLoad(addonA, uriForDomain('http://test2.example.org'));
+    return tryLoad(addonA, uriForDomain("http://test2.example.org"));
   }).then(function(success) {
     ok(!success, "non-whitelisted cross-origin load of test2 should fail for addon A");
     SimpleTest.finish();
   }, function(e) {
     ok(false, "Rejected promise chain: " + e);
     SimpleTest.finish();
   });
 
--- a/caps/tests/mochitest/test_bug246699.html
+++ b/caps/tests/mochitest/test_bug246699.html
@@ -16,45 +16,41 @@ https://bugzilla.mozilla.org/show_bug.cg
 </div>
 <pre id="test">
 <script class="testbody" type="text/javascript">
 
 /**
  ** Test for Bug 246699
  ** (should produce stack information for caps errors)
  **/
-function isError(e)
-{
+function isError(e) {
   return e.constructor.name === "Error" || e.constructor.name === "TypeError";
 }
 
-function hasStack(e)
-{
+function hasStack(e) {
   return isError(e) && /inciteCaps/.test(e.stack);
 }
 
-function inciteCaps(f)
-{
+function inciteCaps(f) {
     try {
         f();
         return "operation succeeded";
-    } catch (e if hasStack(e)) {
-        return "denied-stack";
     } catch (e) {
+        if (hasStack(e)) {
+          return "denied-stack";
+        }
         return "unexpected: " + e;
     }
 }
 
-function tryChromeLoad()
-{
+function tryChromeLoad() {
     window.frames[0].location = "chrome://global/content/mozilla.xhtml";
 }
 
-function tryComponentsClasses()
-{
+function tryComponentsClasses() {
     return SpecialPowers.Components.classes["@mozilla.org/dummy;1"];
 }
 
 
 is(inciteCaps(tryChromeLoad), "denied-stack",
    "should get stack for content-loading-chrome rejection");
 is(inciteCaps(tryComponentsClasses), "denied-stack",
    "should get stack for SpecialPowers.Components.classes rejection");
--- a/caps/tests/mochitest/test_bug292789.html
+++ b/caps/tests/mochitest/test_bug292789.html
@@ -20,16 +20,18 @@ https://bugzilla.mozilla.org/show_bug.cg
 <script class="testbody" type="text/javascript">
 
 /** Test for Bug 292789
  **
  ** Selectively allow access to whitelisted chrome packages
  ** even for ALLOW_CHROME mechanisms (<script>, <img> etc)
  **/
 
+/* import-globals-from ../../../toolkit/content/treeUtils.js */
+
 SimpleTest.waitForExplicitFinish();
 
 /** <script src=""> test **/
 function testScriptSrc(aCallback) {
     is(typeof gTreeUtils.sort, "function",
        "content can still load <script> from chrome://global");
     is(typeof XPInstallConfirm, "undefined",
        "content should not be able to load <script> from chrome://mozapps");
@@ -70,36 +72,36 @@ function runImgTest() {
 }
 
 function finishTest() {
   SimpleTest.finish();
 }
 
 function fail(event) {
     is("fail", event.target.expected,
-       "content should not be allowed to load "+event.target.src);
+       "content should not be allowed to load " + event.target.src);
     if (event.target.callback)
       event.target.callback();
 }
 
 function success(event) {
     is("success", event.target.expected,
-       "content should be able to load "+event.target.src);
+       "content should be able to load " + event.target.src);
     if (event.target.callback)
       event.target.callback();
 }
 
 function loadImage(uri, expect, callback) {
     var img = document.createElement("img");
     img.onerror = fail;
     img.onload = success;
     img.expected = expect;
     img.callback = callback;
     img.src = uri;
-    //document.getElementById("content").appendChild(img);
+    // document.getElementById("content").appendChild(img);
 }
 
 // Start off the script src test, and have it start the img tests when complete.
 testScriptSrc(runImgTest);
 </script>
 </pre>
 </body>
 </html>
--- a/caps/tests/mochitest/test_bug423375.html
+++ b/caps/tests/mochitest/test_bug423375.html
@@ -16,24 +16,24 @@ https://bugzilla.mozilla.org/show_bug.cg
 </div>
 <pre id="test">
 <script class="testbody" type="text/javascript">
 
 /**
  ** Test for Bug 423375
  ** (content shouldn't be able to load chrome: or resource:)
  **/
-function tryLoad(url)
-{
+function tryLoad(url) {
     try {
         window.frames[0].location = url;
         return "loaded";
-    } catch (e if /Access.*denied/.test(String(e))) {
-        return "denied";
     } catch (e) {
+        if (/Access.*denied/.test(String(e))) {
+          return "denied";
+        }
         return "unexpected: " + e;
     }
 }
 
 is(tryLoad("chrome://global/content/mozilla.xhtml"), "denied",
    "content should have been prevented from loading chrome: URL");
 is(tryLoad("resource://gre-resources/html.css"), "denied",
    "content should have been prevented from loading resource: URL");
--- a/caps/tests/mochitest/test_extensionURL.html
+++ b/caps/tests/mochitest/test_extensionURL.html
@@ -18,93 +18,93 @@ https://bugzilla.mozilla.org/show_bug.cg
   var oldLoadCallback = aps.setExtensionURILoadCallback(null);
   var oldMapCallback = aps.setExtensionURIToAddonIdCallback(null);
   var resourceHandler = SpecialPowers.Services.io.getProtocolHandler("resource")
                                      .QueryInterface(SpecialPowers.Ci.nsISubstitutingProtocolHandler);
   var extensionHandler = SpecialPowers.Services.io.getProtocolHandler("moz-extension")
                                      .QueryInterface(SpecialPowers.Ci.nsISubstitutingProtocolHandler);
 
   SimpleTest.registerCleanupFunction(function() {
-      extensionHandler.setSubstitution('cherise', null);
-      extensionHandler.setSubstitution('liebchen', null);
+      extensionHandler.setSubstitution("cherise", null);
+      extensionHandler.setSubstitution("liebchen", null);
       aps.setExtensionURILoadCallback(oldLoadCallback);
       aps.setExtensionURIToAddonIdCallback(oldMapCallback);
   });
 
   addLoadEvent(function() {
 
     // First, get a file:// URI to something - open to suggestions on how to do
     //  this more easily.
-    var resURI = SpecialPowers.Services.io.newURI('resource://testing-common/resource_test_file.html');
+    var resURI = SpecialPowers.Services.io.newURI("resource://testing-common/resource_test_file.html");
     var filePath = resourceHandler.resolveURI(resURI);
-    ok(filePath.startsWith('file://'), 'resource:// URI resolves where we expect: ' + filePath);
+    ok(filePath.startsWith("file://"), "resource:// URI resolves where we expect: " + filePath);
     var fileURI = SpecialPowers.Services.io.newURI(filePath);
 
     // Register a moz-extension:// URI.
-    extensionHandler.setSubstitution('cherise', fileURI);
+    extensionHandler.setSubstitution("cherise", fileURI);
 
     // Alias the above.
-    extensionHandler.setSubstitution('liebchen', SpecialPowers.Services.io.newURI('moz-extension://cherise'));
+    extensionHandler.setSubstitution("liebchen", SpecialPowers.Services.io.newURI("moz-extension://cherise"));
 
     //
     // Make sure that non-file:// URIs don't work.
     //
 
     // resource://
     try {
-      extensionHandler.setSubstitution('interdit', resURI);
+      extensionHandler.setSubstitution("interdit", resURI);
       ok(false, "Should have thrown for mapping moz-extension to resource");
     } catch (e) {
       ok(true, "Threw correctly: " + e);
     }
 
     // chrome://
     try {
-      var chromeURI = SpecialPowers.Services.io.newURI('chrome://global/content/mozilla.xhtml');
-      extensionHandler.setSubstitution('verboten', chromeURI);
+      var chromeURI = SpecialPowers.Services.io.newURI("chrome://global/content/mozilla.xhtml");
+      extensionHandler.setSubstitution("verboten", chromeURI);
       ok(false, "Should have thrown for mapping moz-extension to chrome");
     } catch (e) {
       ok(true, "Threw correctly: " + e);
     }
 
     function navigateWithLocation(ifr, url) { ifr.contentWindow.location = url; }
-    function navigateWithSrc(ifr, url) { ifr.setAttribute('src', url); }
+    function navigateWithSrc(ifr, url) { ifr.setAttribute("src", url); }
     function navigateFromChromeWithLocation(ifr, url) { SpecialPowers.wrap(ifr).contentWindow.location = url; }
     function navigateFromChromeWithWebNav(ifr, url) {
       SpecialPowers.wrap(ifr).contentWindow
                    .QueryInterface(SpecialPowers.Ci.nsIInterfaceRequestor)
                    .getInterface(SpecialPowers.Ci.nsIWebNavigation)
                    .loadURI(url, 0, null, null, null);
     }
 
 
     function setWhitelistCallback(rgxp) {
       var cb = SpecialPowers.wrapCallback(function(uri) { return rgxp.test(uri.spec); });
       aps.setExtensionURILoadCallback(cb);
     }
 
-    aps.setExtensionURIToAddonIdCallback(SpecialPowers.wrapCallback(function (uri) { return 'imaginaryaddon-' + uri.host[0]; }));
+    aps.setExtensionURIToAddonIdCallback(SpecialPowers.wrapCallback(function(uri) { return "imaginaryaddon-" + uri.host[0]; }));
 
     function testLoad(url, navigate, shouldThrow) {
-      var ifr = document.createElement('iframe');
+      var ifr = document.createElement("iframe");
       var p = new Promise(function(resolve, reject) {
         ifr.onload = function() {
-          ok(true, 'Loaded ' + url);
+          ok(true, "Loaded " + url);
           var prin = SpecialPowers.wrap(ifr.contentWindow).document.nodePrincipal;
-          function stripTrailingSlash(s) { return s.replace(/\/$/, ''); };
-          is(stripTrailingSlash(prin.URI.spec), url, 'Principal uri is correct: ' + url);
-          function stripPath(s) { return s.replace(/(.*\/\/.+)\/.*/, '$1'); };
-          is(prin.originNoSuffix, stripPath(url), 'Principal origin is correct: ' + prin.originNoSuffix);
-          is(prin.addonId, 'imaginaryaddon-' + url[url.indexOf('/') + 2], 'addonId is correct');
+          function stripTrailingSlash(s) { return s.replace(/\/$/, ""); }
+          is(stripTrailingSlash(prin.URI.spec), url, "Principal uri is correct: " + url);
+          function stripPath(s) { return s.replace(/(.*\/\/.+)\/.*/, "$1"); }
+          is(prin.originNoSuffix, stripPath(url), "Principal origin is correct: " + prin.originNoSuffix);
+          is(prin.addonId, "imaginaryaddon-" + url[url.indexOf("/") + 2], "addonId is correct");
           if (/_blank/.test(url)) {
             is(SpecialPowers.wrap(ifr.contentWindow).document.documentElement.innerHTML,
-               '<head></head><body></body>', 'blank document looks right');
+               "<head></head><body></body>", "blank document looks right");
           } else {
-            is(SpecialPowers.wrap(ifr.contentWindow).document.title, 'resource test file',
-               'document looks right');
+            is(SpecialPowers.wrap(ifr.contentWindow).document.title, "resource test file",
+               "document looks right");
           }
           ifr.remove();
           resolve();
         };
         document.body.appendChild(ifr);
 
         var threw = false;
         try {
@@ -129,30 +129,30 @@ https://bugzilla.mozilla.org/show_bug.cg
         xhr.open("GET", url, true);
         xhr.send();
       });
     }
 
     //
     // Perform some loads and make sure they work correctly.
     //
-    testLoad.bind(null, 'moz-extension://cherise', navigateFromChromeWithLocation)()
-    .then(testLoad.bind(null, 'moz-extension://cherise', navigateFromChromeWithWebNav))
-    .then(testLoad.bind(null, 'moz-extension://cherise', navigateWithLocation, /* shouldThrow = */ true))
-    .then(testXHR.bind(null, 'moz-extension://cherise', /* shouldError = */ true))
+    testLoad.bind(null, "moz-extension://cherise", navigateFromChromeWithLocation)()
+    .then(testLoad.bind(null, "moz-extension://cherise", navigateFromChromeWithWebNav))
+    .then(testLoad.bind(null, "moz-extension://cherise", navigateWithLocation, /* shouldThrow = */ true))
+    .then(testXHR.bind(null, "moz-extension://cherise", /* shouldError = */ true))
     .then(setWhitelistCallback.bind(null, /cherise/))
-    .then(testLoad.bind(null, 'moz-extension://cherise', navigateWithLocation))
-    .then(testXHR.bind(null, 'moz-extension://cherise'))
-    .then(testLoad.bind(null, 'moz-extension://liebchen', navigateWithLocation, /* shouldThrow = */ true))
-    .then(testXHR.bind(null, 'moz-extension://liebchen', /* shouldError = */ true))
+    .then(testLoad.bind(null, "moz-extension://cherise", navigateWithLocation))
+    .then(testXHR.bind(null, "moz-extension://cherise"))
+    .then(testLoad.bind(null, "moz-extension://liebchen", navigateWithLocation, /* shouldThrow = */ true))
+    .then(testXHR.bind(null, "moz-extension://liebchen", /* shouldError = */ true))
     .then(setWhitelistCallback.bind(null, /cherise|liebchen/))
-    .then(testLoad.bind(null, 'moz-extension://liebchen', navigateWithLocation))
-    .then(testLoad.bind(null, 'moz-extension://liebchen', navigateWithSrc))
-    .then(testLoad.bind(null, 'moz-extension://cherise', navigateWithSrc))
-    .then(testLoad.bind(null, 'moz-extension://cherise/_blank.html', navigateWithSrc))
+    .then(testLoad.bind(null, "moz-extension://liebchen", navigateWithLocation))
+    .then(testLoad.bind(null, "moz-extension://liebchen", navigateWithSrc))
+    .then(testLoad.bind(null, "moz-extension://cherise", navigateWithSrc))
+    .then(testLoad.bind(null, "moz-extension://cherise/_blank.html", navigateWithSrc))
     .then(SimpleTest.finish.bind(SimpleTest),
           function(e) { ok(false, "rejected promise: " + e); SimpleTest.finish() }
     );
   });
 
   </script>
 </head>
 <body>
new file mode 100644
--- /dev/null
+++ b/caps/tests/unit/.eslintrc.js
@@ -0,0 +1,7 @@
+"use strict";
+
+module.exports = {
+  "extends": [
+    "plugin:mozilla/xpcshell-test"
+  ]
+};
--- a/caps/tests/unit/test_origin.js
+++ b/caps/tests/unit/test_origin.js
@@ -20,20 +20,20 @@ function checkCrossOrigin(a, b) {
   do_check_false(b.subsumes(a));
   do_check_false(b.subsumesConsideringDomain(a));
 }
 
 function checkOriginAttributes(prin, attrs, suffix) {
   attrs = attrs || {};
   do_check_eq(prin.originAttributes.appId, attrs.appId || 0);
   do_check_eq(prin.originAttributes.inIsolatedMozBrowser, attrs.inIsolatedMozBrowser || false);
-  do_check_eq(prin.originSuffix, suffix || '');
-  do_check_eq(ChromeUtils.originAttributesToSuffix(attrs), suffix || '');
+  do_check_eq(prin.originSuffix, suffix || "");
+  do_check_eq(ChromeUtils.originAttributesToSuffix(attrs), suffix || "");
   do_check_true(ChromeUtils.originAttributesMatchPattern(prin.originAttributes, attrs));
-  if (!prin.isNullPrincipal && !prin.origin.startsWith('[')) {
+  if (!prin.isNullPrincipal && !prin.origin.startsWith("[")) {
     do_check_true(ssm.createCodebasePrincipalFromOrigin(prin.origin).equals(prin));
   } else {
     checkThrows(() => ssm.createCodebasePrincipalFromOrigin(prin.origin));
   }
 }
 
 function checkSandboxOriginAttributes(arr, attrs, options) {
   options = options || {};
@@ -50,147 +50,147 @@ function printAttrs(name, attrs) {
            "\tinIsolatedMozBrowser: " + attrs.inIsolatedMozBrowser + ",\n" +
            "\tprivateBrowsingId: '" + attrs.privateBrowsingId + "',\n" +
            "\tfirstPartyDomain: '" + attrs.firstPartyDomain + "'\n}");
 }
 
 
 function checkValues(attrs, values) {
   values = values || {};
-  //printAttrs("attrs", attrs);
-  //printAttrs("values", values);
+  // printAttrs("attrs", attrs);
+  // printAttrs("values", values);
   do_check_eq(attrs.appId, values.appId || 0);
   do_check_eq(attrs.userContextId, values.userContextId || 0);
   do_check_eq(attrs.inIsolatedMozBrowser, values.inIsolatedMozBrowser || false);
-  do_check_eq(attrs.privateBrowsingId, values.privateBrowsingId || '');
-  do_check_eq(attrs.firstPartyDomain, values.firstPartyDomain || '');
+  do_check_eq(attrs.privateBrowsingId, values.privateBrowsingId || "");
+  do_check_eq(attrs.firstPartyDomain, values.firstPartyDomain || "");
 }