Merge m-c to oak
authorRobert Strong <robert.bugzilla@gmail.com>
Tue, 03 May 2016 12:18:59 -0700
changeset 491518 9291c3b0effd78f29db8c03f8a5a687ebb423ce9
parent 491517 d154bc80a284d6cbb34a359b2b56079f7f04271f (current diff)
parent 362943 0a25833062a880f369e6f9f622413a94cc671bf4 (diff)
child 491519 29c972eff732f14deeab1f98baf0290bf90f75d8
push id47343
push userbmo:dothayer@mozilla.com
push dateWed, 01 Mar 2017 22:58:58 +0000
milestone49.0a1
Merge m-c to oak
CLOBBER
b2g/app/b2g.js
b2g/installer/package-manifest.in
browser/app/profile/firefox.js
browser/base/content/aboutDialog-appUpdater.js
browser/base/content/browser.js
browser/components/preferences/in-content/advanced.js
browser/installer/package-manifest.in
dom/animation/Animation.cpp
dom/animation/NonOwningAnimationTarget.h
dom/apps/tests/test_widget_browser.html
dom/datastore/DataStore.cpp
dom/datastore/DataStore.h
dom/datastore/DataStore.manifest
dom/datastore/DataStoreCallbacks.h
dom/datastore/DataStoreChangeNotifier.jsm
dom/datastore/DataStoreCursor.cpp
dom/datastore/DataStoreCursor.h
dom/datastore/DataStoreCursorImpl.jsm
dom/datastore/DataStoreDB.cpp
dom/datastore/DataStoreDB.h
dom/datastore/DataStoreDB.jsm
dom/datastore/DataStoreImpl.js
dom/datastore/DataStoreRevision.cpp
dom/datastore/DataStoreRevision.h
dom/datastore/DataStoreService.cpp
dom/datastore/DataStoreService.h
dom/datastore/moz.build
dom/datastore/nsIDataStore.idl
dom/datastore/nsIDataStoreService.idl
dom/datastore/tests/file_app.sjs
dom/datastore/tests/file_app.template.webapp
dom/datastore/tests/file_app2.template.webapp
dom/datastore/tests/file_app_install.html
dom/datastore/tests/file_arrays.html
dom/datastore/tests/file_basic.html
dom/datastore/tests/file_basic_common.js
dom/datastore/tests/file_basic_worker.html
dom/datastore/tests/file_basic_worker.js
dom/datastore/tests/file_bug1008044.html
dom/datastore/tests/file_bug1058108.html
dom/datastore/tests/file_bug924104.html
dom/datastore/tests/file_bug957086.html
dom/datastore/tests/file_bug976311.html
dom/datastore/tests/file_bug976311.template.webapp
dom/datastore/tests/file_bug986056.html
dom/datastore/tests/file_bug986056.template.webapp
dom/datastore/tests/file_certifiedApp.html
dom/datastore/tests/file_changes.html
dom/datastore/tests/file_changes2.html
dom/datastore/tests/file_duplicate.html
dom/datastore/tests/file_event_maker.html
dom/datastore/tests/file_event_receiver.html
dom/datastore/tests/file_keys.html
dom/datastore/tests/file_notify_system_message.html
dom/datastore/tests/file_readonly.html
dom/datastore/tests/file_sync.html
dom/datastore/tests/file_sync_common.js
dom/datastore/tests/file_sync_worker.html
dom/datastore/tests/file_sync_worker.js
dom/datastore/tests/file_transactions.html
dom/datastore/tests/file_worker_close.html
dom/datastore/tests/file_worker_close.js
dom/datastore/tests/mochitest.ini
dom/datastore/tests/test_app_install.html
dom/datastore/tests/test_arrays.html
dom/datastore/tests/test_basic.html
dom/datastore/tests/test_basic_worker.html
dom/datastore/tests/test_bug1008044.html
dom/datastore/tests/test_bug1058108.html
dom/datastore/tests/test_bug924104.html
dom/datastore/tests/test_bug957086.html
dom/datastore/tests/test_bug976311.html
dom/datastore/tests/test_bug986056.html
dom/datastore/tests/test_certifiedApp.html
dom/datastore/tests/test_changes.html
dom/datastore/tests/test_duplicate.html
dom/datastore/tests/test_keys.html
dom/datastore/tests/test_notify_system_message.html
dom/datastore/tests/test_oop.html
dom/datastore/tests/test_oop_events.html
dom/datastore/tests/test_readonly.html
dom/datastore/tests/test_sync.html
dom/datastore/tests/test_sync_worker.html
dom/datastore/tests/test_transactions.html
dom/datastore/tests/test_worker_close.html
dom/requestsync/RequestSync.manifest
dom/requestsync/RequestSyncApp.jsm
dom/requestsync/RequestSyncManager.js
dom/requestsync/RequestSyncScheduler.js
dom/requestsync/RequestSyncService.jsm
dom/requestsync/RequestSyncTask.jsm
dom/requestsync/RequestSyncWifiService.cpp
dom/requestsync/RequestSyncWifiService.h
dom/requestsync/moz.build
dom/requestsync/tests/common_app.js
dom/requestsync/tests/common_basic.js
dom/requestsync/tests/file_app.sjs
dom/requestsync/tests/file_app.template.webapp
dom/requestsync/tests/file_basic_app.html
dom/requestsync/tests/file_interface.html
dom/requestsync/tests/mochitest.ini
dom/requestsync/tests/system_message_chrome_script.js
dom/requestsync/tests/test_basic.html
dom/requestsync/tests/test_basic_app.html
dom/requestsync/tests/test_bug1151082.html
dom/requestsync/tests/test_minInterval.html
dom/requestsync/tests/test_promise.html
dom/requestsync/tests/test_runNow.html
dom/requestsync/tests/test_wakeUp.html
dom/requestsync/tests/test_webidl.html
dom/webidl/DataStore.webidl
dom/webidl/DataStoreChangeEvent.webidl
dom/webidl/DataStoreImpl.webidl
dom/webidl/RequestSyncManager.webidl
dom/webidl/RequestSyncScheduler.webidl
dom/workers/DataStore.cpp
dom/workers/DataStore.h
dom/workers/DataStoreCursor.cpp
dom/workers/DataStoreCursor.h
extensions/spellcheck/hunspell/src/dictmgr.cxx
extensions/spellcheck/hunspell/src/dictmgr.hxx
extensions/spellcheck/hunspell/src/patches/01-675553.diff
extensions/spellcheck/hunspell/src/patches/02-690892.diff
extensions/spellcheck/hunspell/src/patches/03-clang.diff
extensions/spellcheck/hunspell/src/patches/04-777292.diff
extensions/spellcheck/hunspell/src/patches/05-579517.diff
extensions/spellcheck/hunspell/src/patches/06-784776.diff
extensions/spellcheck/hunspell/src/patches/07-927728.diff
extensions/spellcheck/hunspell/src/patches/08-943268.diff
extensions/spellcheck/hunspell/src/patches/09-license.diff
extensions/spellcheck/hunspell/src/patches/10-983817.diff
extensions/spellcheck/hunspell/src/patches/11-318040.diff
gfx/skia/skia/include/core/SkComposeShader.h
gfx/skia/skia/include/core/SkFixed.h
gfx/skia/skia/include/core/SkImageDecoder.h
gfx/skia/skia/include/core/SkPackBits.h
gfx/skia/skia/include/core/SkTArray.h
gfx/skia/skia/include/core/SkTDArray.h
gfx/skia/skia/include/core/SkTDStack.h
gfx/skia/skia/include/core/SkTInternalLList.h
gfx/skia/skia/include/core/SkUtils.h
gfx/skia/skia/include/device/xps/SkConstexprMath.h
gfx/skia/skia/include/device/xps/SkXPSDevice.h
gfx/skia/skia/include/effects/SkDrawExtraPathEffect.h
gfx/skia/skia/include/effects/SkLerpXfermode.h
gfx/skia/skia/include/effects/SkPixelXorXfermode.h
gfx/skia/skia/include/gpu/gl/SkGLContext.h
gfx/skia/skia/include/gpu/gl/SkNullGLContext.h
gfx/skia/skia/include/gpu/gl/angle/SkANGLEGLContext.h
gfx/skia/skia/include/gpu/gl/command_buffer/SkCommandBufferGLContext.h
gfx/skia/skia/include/images/SkDecodingImageGenerator.h
gfx/skia/skia/include/images/SkPageFlipper.h
gfx/skia/skia/include/private/SkUniquePtr.h
gfx/skia/skia/include/utils/SkDebugUtils.h
gfx/skia/skia/include/views/SkBGViewArtist.h
gfx/skia/skia/include/views/SkParsePaint.h
gfx/skia/skia/include/views/SkStackViewLayout.h
gfx/skia/skia/include/views/SkViewInflate.h
gfx/skia/skia/include/views/SkWidget.h
gfx/skia/skia/include/views/animated/SkBorderView.h
gfx/skia/skia/include/views/animated/SkImageView.h
gfx/skia/skia/include/views/animated/SkProgressBarView.h
gfx/skia/skia/include/views/animated/SkScrollBarView.h
gfx/skia/skia/include/views/animated/SkWidgetViews.h
gfx/skia/skia/src/animator/SkTime.cpp
gfx/skia/skia/src/codec/SkCodec_libpng.cpp
gfx/skia/skia/src/codec/SkCodec_libpng.h
gfx/skia/skia/src/codec/SkJpegUtility_codec.cpp
gfx/skia/skia/src/codec/SkJpegUtility_codec.h
gfx/skia/skia/src/core/SkPackBits.cpp
gfx/skia/skia/src/core/SkRWBuffer.h
gfx/skia/skia/src/core/SkRemote.cpp
gfx/skia/skia/src/core/SkRemote.h
gfx/skia/skia/src/core/SkRemote_protocol.h
gfx/skia/skia/src/core/SkValue.h
gfx/skia/skia/src/device/xps/SkXPSDevice.cpp
gfx/skia/skia/src/doc/SkDocument.cpp
gfx/skia/skia/src/doc/SkDocument_PDF.cpp
gfx/skia/skia/src/doc/SkDocument_PDF_None.cpp
gfx/skia/skia/src/doc/SkDocument_XPS.cpp
gfx/skia/skia/src/doc/SkDocument_XPS_None.cpp
gfx/skia/skia/src/effects/SkLerpXfermode.cpp
gfx/skia/skia/src/effects/SkPixelXorXfermode.cpp
gfx/skia/skia/src/gpu/GrContextFactory.cpp
gfx/skia/skia/src/gpu/GrContextFactory.h
gfx/skia/skia/src/gpu/GrGeometryBuffer.h
gfx/skia/skia/src/gpu/GrIndexBuffer.h
gfx/skia/skia/src/gpu/GrTest.cpp
gfx/skia/skia/src/gpu/GrTest.h
gfx/skia/skia/src/gpu/GrTransferBuffer.h
gfx/skia/skia/src/gpu/GrVertexBuffer.h
gfx/skia/skia/src/gpu/GrVertices.h
gfx/skia/skia/src/gpu/effects/GrYUVtoRGBEffect.cpp
gfx/skia/skia/src/gpu/effects/GrYUVtoRGBEffect.h
gfx/skia/skia/src/gpu/gl/GrGLAssembleInterface.h
gfx/skia/skia/src/gpu/gl/GrGLBufferImpl.cpp
gfx/skia/skia/src/gpu/gl/GrGLBufferImpl.h
gfx/skia/skia/src/gpu/gl/GrGLIndexBuffer.cpp
gfx/skia/skia/src/gpu/gl/GrGLIndexBuffer.h
gfx/skia/skia/src/gpu/gl/GrGLNoOpInterface.cpp
gfx/skia/skia/src/gpu/gl/GrGLNoOpInterface.h
gfx/skia/skia/src/gpu/gl/GrGLTransferBuffer.cpp
gfx/skia/skia/src/gpu/gl/GrGLTransferBuffer.h
gfx/skia/skia/src/gpu/gl/GrGLVertexBuffer.cpp
gfx/skia/skia/src/gpu/gl/GrGLVertexBuffer.h
gfx/skia/skia/src/gpu/gl/SkGLContext.cpp
gfx/skia/skia/src/gpu/gl/SkNullGLContext.cpp
gfx/skia/skia/src/gpu/gl/angle/GrGLCreateANGLEInterface.cpp
gfx/skia/skia/src/gpu/gl/angle/SkANGLEGLContext.cpp
gfx/skia/skia/src/gpu/gl/command_buffer/SkCommandBufferGLContext.cpp
gfx/skia/skia/src/gpu/gl/debug/GrBufferObj.cpp
gfx/skia/skia/src/gpu/gl/debug/GrBufferObj.h
gfx/skia/skia/src/gpu/gl/debug/GrDebugGL.cpp
gfx/skia/skia/src/gpu/gl/debug/GrDebugGL.h
gfx/skia/skia/src/gpu/gl/debug/GrFBBindableObj.h
gfx/skia/skia/src/gpu/gl/debug/GrFakeRefObj.h
gfx/skia/skia/src/gpu/gl/debug/GrFrameBufferObj.cpp
gfx/skia/skia/src/gpu/gl/debug/GrFrameBufferObj.h
gfx/skia/skia/src/gpu/gl/debug/GrGLCreateDebugInterface.cpp
gfx/skia/skia/src/gpu/gl/debug/GrProgramObj.cpp
gfx/skia/skia/src/gpu/gl/debug/GrProgramObj.h
gfx/skia/skia/src/gpu/gl/debug/GrRenderBufferObj.h
gfx/skia/skia/src/gpu/gl/debug/GrShaderObj.cpp
gfx/skia/skia/src/gpu/gl/debug/GrShaderObj.h
gfx/skia/skia/src/gpu/gl/debug/GrTextureObj.cpp
gfx/skia/skia/src/gpu/gl/debug/GrTextureObj.h
gfx/skia/skia/src/gpu/gl/debug/GrTextureUnitObj.cpp
gfx/skia/skia/src/gpu/gl/debug/GrTextureUnitObj.h
gfx/skia/skia/src/gpu/gl/debug/GrVertexArrayObj.h
gfx/skia/skia/src/gpu/gl/debug/SkDebugGLContext.cpp
gfx/skia/skia/src/gpu/gl/debug/SkDebugGLContext.h
gfx/skia/skia/src/gpu/gl/egl/SkCreatePlatformGLContext_egl.cpp
gfx/skia/skia/src/gpu/gl/glx/SkCreatePlatformGLContext_glx.cpp
gfx/skia/skia/src/gpu/gl/iOS/SkCreatePlatformGLContext_iOS.mm
gfx/skia/skia/src/gpu/gl/mac/SkCreatePlatformGLContext_mac.cpp
gfx/skia/skia/src/gpu/gl/mesa/GrGLCreateMesaInterface.cpp
gfx/skia/skia/src/gpu/gl/mesa/SkMesaGLContext.cpp
gfx/skia/skia/src/gpu/gl/mesa/SkMesaGLContext.h
gfx/skia/skia/src/gpu/gl/nacl/SkCreatePlatformGLContext_nacl.cpp
gfx/skia/skia/src/gpu/gl/win/SkCreatePlatformGLContext_win.cpp
gfx/skia/skia/src/gpu/text/GrTextContext.cpp
gfx/skia/skia/src/gpu/text/GrTextContext.h
gfx/skia/skia/src/images/SkDecodingImageGenerator.cpp
gfx/skia/skia/src/images/SkImageDecoder.cpp
gfx/skia/skia/src/images/SkImageDecoder_FactoryDefault.cpp
gfx/skia/skia/src/images/SkImageDecoder_FactoryRegistrar.cpp
gfx/skia/skia/src/images/SkImageDecoder_astc.cpp
gfx/skia/skia/src/images/SkImageDecoder_ktx.cpp
gfx/skia/skia/src/images/SkImageDecoder_libbmp.cpp
gfx/skia/skia/src/images/SkImageDecoder_libgif.cpp
gfx/skia/skia/src/images/SkImageDecoder_libico.cpp
gfx/skia/skia/src/images/SkImageDecoder_libjpeg.cpp
gfx/skia/skia/src/images/SkImageDecoder_libpng.cpp
gfx/skia/skia/src/images/SkImageDecoder_libwebp.cpp
gfx/skia/skia/src/images/SkImageDecoder_pkm.cpp
gfx/skia/skia/src/images/SkImageDecoder_wbmp.cpp
gfx/skia/skia/src/images/SkImageEncoder_argb.cpp
gfx/skia/skia/src/images/SkJpegUtility.cpp
gfx/skia/skia/src/images/SkJpegUtility.h
gfx/skia/skia/src/images/SkMovie_gif.cpp
gfx/skia/skia/src/images/SkPageFlipper.cpp
gfx/skia/skia/src/images/SkScaledBitmapSampler.cpp
gfx/skia/skia/src/images/SkScaledBitmapSampler.h
gfx/skia/skia/src/images/bmpdecoderhelper.cpp
gfx/skia/skia/src/images/bmpdecoderhelper.h
gfx/skia/skia/src/opts/SkBlitRow_opts_SSE4.cpp
gfx/skia/skia/src/opts/SkBlitRow_opts_SSE4.h
gfx/skia/skia/src/opts/SkFloatingPoint_opts.h
gfx/skia/skia/src/opts/SkNx_avx.h
gfx/skia/skia/src/opts/SkOpts_avx.cpp
gfx/skia/skia/src/opts/SkUtils_opts.h
gfx/skia/skia/src/ports/SkImageDecoder_CG.cpp
gfx/skia/skia/src/ports/SkImageDecoder_WIC.cpp
gfx/skia/skia/src/ports/SkImageDecoder_empty.cpp
gfx/skia/skia/src/ports/SkTime_Unix.cpp
gfx/skia/skia/src/ports/SkTime_win.cpp
gfx/skia/skia/src/utils/SkBitmapHasher.cpp
gfx/skia/skia/src/utils/SkBitmapHasher.h
gfx/skia/skia/src/utils/SkImageGeneratorUtils.cpp
gfx/skia/skia/src/utils/SkImageGeneratorUtils.h
gfx/skia/skia/src/utils/SkRunnable.h
gfx/skia/skia/src/utils/SkSHA1.cpp
gfx/skia/skia/src/utils/SkSHA1.h
gfx/skia/skia/src/utils/SkTFitsIn.h
gfx/skia/skia/src/utils/debugger/SkDebugCanvas.cpp
gfx/skia/skia/src/utils/debugger/SkDebugCanvas.h
gfx/skia/skia/src/utils/debugger/SkDrawCommand.cpp
gfx/skia/skia/src/utils/debugger/SkDrawCommand.h
gfx/skia/skia/src/utils/debugger/SkObjectParser.cpp
gfx/skia/skia/src/utils/debugger/SkObjectParser.h
gfx/skia/skia/src/views/SkBGViewArtist.cpp
gfx/skia/skia/src/views/SkParsePaint.cpp
gfx/skia/skia/src/views/SkProgressView.cpp
gfx/skia/skia/src/views/SkStackViewLayout.cpp
gfx/skia/skia/src/views/SkViewInflate.cpp
gfx/skia/skia/src/views/SkWidgets.cpp
gfx/skia/skia/src/views/animated/SkBorderView.cpp
gfx/skia/skia/src/views/animated/SkImageView.cpp
gfx/skia/skia/src/views/animated/SkProgressBarView.cpp
gfx/skia/skia/src/views/animated/SkScrollBarView.cpp
gfx/skia/skia/src/views/animated/SkStaticTextView.cpp
gfx/skia/skia/src/views/animated/SkWidgetViews.cpp
ipc/glue/GeckoChildProcessHost.cpp
layout/reftests/backgrounds/background-position-6.html
mobile/android/base/java/org/mozilla/gecko/RemoteTabsExpandableListAdapter.java
mobile/android/base/java/org/mozilla/gecko/menu/QuickShareBarActionView.java
mobile/android/base/resources/drawable-hdpi/helper_first_readerview_bookmark.png
mobile/android/base/resources/drawable-xhdpi/helper_first_readerview_bookmark.png
mobile/android/base/resources/drawable-xxhdpi-v11/ic_menu_bookmark_add.png
mobile/android/base/resources/drawable-xxhdpi-v11/star_blue.png
mobile/android/base/resources/drawable-xxhdpi/helper_first_readerview_bookmark.png
mobile/android/base/resources/drawable/textbox_bg.xml
mobile/android/base/resources/layout/home_history_clear_button.xml
mobile/android/base/resources/layout/remote_tabs_setup.xml
mobile/android/base/resources/menu-large-v11/browser_app_menu.xml
mobile/android/base/resources/menu-v11/browser_app_menu.xml
mobile/android/base/resources/menu-xlarge-v11/browser_app_menu.xml
modules/fdlibm/src/fdlibm.h.orig
testing/mochitest/runtests.py
testing/web-platform/meta/dom/collections/domstringmap-supported-property-names.html.ini
testing/web-platform/meta/mixed-content/allowed/http-csp/same-host-https/audio-tag/top-level/keep-scheme-redirect/allowed.https.html.ini
testing/web-platform/meta/mixed-content/allowed/http-csp/same-host-https/audio-tag/top-level/no-redirect/allowed.https.html.ini
testing/web-platform/meta/mixed-content/allowed/http-csp/same-host-https/img-tag/top-level/keep-scheme-redirect/allowed.https.html.ini
testing/web-platform/meta/mixed-content/allowed/http-csp/same-host-https/img-tag/top-level/no-redirect/allowed.https.html.ini
testing/web-platform/meta/mixed-content/allowed/http-csp/same-host-https/picture-tag/top-level/keep-scheme-redirect/allowed.https.html.ini
testing/web-platform/meta/mixed-content/allowed/http-csp/same-host-https/picture-tag/top-level/no-redirect/allowed.https.html.ini
testing/web-platform/meta/mixed-content/allowed/meta-csp/same-host-https/audio-tag/top-level/no-redirect/allowed.https.html.ini
testing/web-platform/meta/mixed-content/allowed/meta-csp/same-host-https/img-tag/top-level/no-redirect/allowed.https.html.ini
testing/web-platform/meta/mixed-content/allowed/meta-csp/same-host-https/picture-tag/top-level/no-redirect/allowed.https.html.ini
testing/web-platform/meta/mixed-content/allowed/no-opt-in/same-host-https/audio-tag/top-level/keep-scheme-redirect/allowed.https.html.ini
testing/web-platform/meta/mixed-content/allowed/no-opt-in/same-host-https/audio-tag/top-level/no-redirect/allowed.https.html.ini
testing/web-platform/meta/mixed-content/allowed/no-opt-in/same-host-https/img-tag/top-level/keep-scheme-redirect/allowed.https.html.ini
testing/web-platform/meta/mixed-content/allowed/no-opt-in/same-host-https/img-tag/top-level/no-redirect/allowed.https.html.ini
testing/web-platform/meta/mixed-content/allowed/no-opt-in/same-host-https/picture-tag/top-level/keep-scheme-redirect/allowed.https.html.ini
testing/web-platform/meta/mixed-content/allowed/no-opt-in/same-host-https/picture-tag/top-level/no-redirect/allowed.https.html.ini
testing/web-platform/meta/mixed-content/blockable/http-csp/same-host-http/picture-tag/top-level/no-redirect/opt-in-blocks.https.html.ini
testing/web-platform/meta/mixed-content/blockable/no-opt-in/cross-origin-http/picture-tag/top-level/no-redirect/no-opt-in-blocks.https.html.ini
testing/web-platform/meta/mixed-content/blockable/no-opt-in/same-host-http/picture-tag/top-level/no-redirect/no-opt-in-blocks.https.html.ini
testing/web-platform/meta/mixed-content/optionally-blockable/no-opt-in/cross-origin-http/audio-tag/top-level/keep-scheme-redirect/no-opt-in-allows.https.html.ini
testing/web-platform/meta/mixed-content/optionally-blockable/no-opt-in/cross-origin-http/audio-tag/top-level/no-redirect/no-opt-in-allows.https.html.ini
testing/web-platform/meta/mixed-content/optionally-blockable/no-opt-in/cross-origin-http/audio-tag/top-level/swap-scheme-redirect/no-opt-in-allows.https.html.ini
testing/web-platform/meta/mixed-content/optionally-blockable/no-opt-in/cross-origin-http/img-tag/top-level/keep-scheme-redirect/no-opt-in-allows.https.html.ini
testing/web-platform/meta/mixed-content/optionally-blockable/no-opt-in/cross-origin-http/img-tag/top-level/no-redirect/no-opt-in-allows.https.html.ini
testing/web-platform/meta/mixed-content/optionally-blockable/no-opt-in/cross-origin-http/img-tag/top-level/swap-scheme-redirect/no-opt-in-allows.https.html.ini
testing/web-platform/meta/mixed-content/optionally-blockable/no-opt-in/same-host-http/audio-tag/top-level/keep-scheme-redirect/no-opt-in-allows.https.html.ini
testing/web-platform/meta/mixed-content/optionally-blockable/no-opt-in/same-host-http/audio-tag/top-level/no-redirect/no-opt-in-allows.https.html.ini
testing/web-platform/meta/mixed-content/optionally-blockable/no-opt-in/same-host-http/audio-tag/top-level/swap-scheme-redirect/no-opt-in-allows.https.html.ini
testing/web-platform/meta/mixed-content/optionally-blockable/no-opt-in/same-host-http/img-tag/top-level/keep-scheme-redirect/no-opt-in-allows.https.html.ini
testing/web-platform/meta/mixed-content/optionally-blockable/no-opt-in/same-host-http/img-tag/top-level/no-redirect/no-opt-in-allows.https.html.ini
testing/web-platform/meta/mixed-content/optionally-blockable/no-opt-in/same-host-http/img-tag/top-level/swap-scheme-redirect/no-opt-in-allows.https.html.ini
testing/web-platform/meta/referrer-policy/no-referrer/attr-referrer/cross-origin/http-http/iframe-tag/generic.keep-origin-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/no-referrer/attr-referrer/cross-origin/http-http/iframe-tag/generic.no-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/no-referrer/attr-referrer/cross-origin/http-http/iframe-tag/generic.swap-origin-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/no-referrer/attr-referrer/cross-origin/http-https/iframe-tag/generic.keep-origin-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/no-referrer/attr-referrer/cross-origin/http-https/iframe-tag/generic.no-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/no-referrer/attr-referrer/cross-origin/http-https/iframe-tag/generic.swap-origin-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/no-referrer/attr-referrer/same-origin/http-http/iframe-tag/generic.keep-origin-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/no-referrer/attr-referrer/same-origin/http-http/iframe-tag/generic.no-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/no-referrer/attr-referrer/same-origin/http-http/iframe-tag/generic.swap-origin-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/no-referrer/attr-referrer/same-origin/http-https/iframe-tag/generic.keep-origin-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/no-referrer/attr-referrer/same-origin/http-https/iframe-tag/generic.no-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/no-referrer/attr-referrer/same-origin/http-https/iframe-tag/generic.swap-origin-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/origin-only/attr-referrer/cross-origin/http-http/iframe-tag/generic.keep-origin-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/origin-only/attr-referrer/cross-origin/http-http/iframe-tag/generic.no-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/origin-only/attr-referrer/cross-origin/http-http/iframe-tag/generic.swap-origin-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/origin-only/attr-referrer/cross-origin/http-https/iframe-tag/generic.keep-origin-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/origin-only/attr-referrer/cross-origin/http-https/iframe-tag/generic.no-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/origin-only/attr-referrer/cross-origin/http-https/iframe-tag/generic.swap-origin-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/origin-only/attr-referrer/same-origin/http-http/iframe-tag/generic.keep-origin-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/origin-only/attr-referrer/same-origin/http-http/iframe-tag/generic.no-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/origin-only/attr-referrer/same-origin/http-http/iframe-tag/generic.swap-origin-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/origin-only/attr-referrer/same-origin/http-https/iframe-tag/generic.keep-origin-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/origin-only/attr-referrer/same-origin/http-https/iframe-tag/generic.no-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/origin-only/attr-referrer/same-origin/http-https/iframe-tag/generic.swap-origin-redirect.http.html.ini
toolkit/components/passwordmgr/test/chrome/test_formless_submit.html
toolkit/components/passwordmgr/test/test_master_password_cleanup.html
toolkit/components/passwordmgr/test/test_prompt.html
toolkit/components/telemetry/Histograms.json
toolkit/locales/en-US/chrome/mozapps/update/updates.dtd
toolkit/locales/en-US/chrome/mozapps/update/updates.properties
toolkit/mozapps/update/UpdateTelemetry.jsm
toolkit/mozapps/update/content/updates.js
toolkit/mozapps/update/content/updates.xul
toolkit/mozapps/update/nsIUpdateService.idl
toolkit/mozapps/update/nsUpdateService.js
toolkit/mozapps/update/tests/chrome/utils.js
toolkit/mozapps/update/tests/data/shared.js
toolkit/mozapps/update/tests/unit_base_updater/marAppApplyDirLockedStageFailure_win.js
toolkit/mozapps/update/tests/unit_base_updater/marAppApplyUpdateAppBinInUseStageSuccess_win.js
toolkit/mozapps/update/tests/unit_base_updater/marAppApplyUpdateStageSuccess.js
toolkit/mozapps/update/tests/unit_base_updater/marAppApplyUpdateSuccess.js
toolkit/mozapps/update/tests/unit_service_updater/marAppApplyDirLockedStageFailureSvc_win.js
toolkit/mozapps/update/tests/unit_service_updater/marAppApplyUpdateAppBinInUseStageSuccessSvc_win.js
toolkit/mozapps/update/tests/unit_service_updater/marAppApplyUpdateStageSuccessSvc.js
toolkit/mozapps/update/tests/unit_service_updater/marAppApplyUpdateSuccessSvc.js
toolkit/mozapps/update/updater/Makefile.in
toolkit/mozapps/update/updater/moz.build
toolkit/themes/osx/global/icons/find-arrows.png
toolkit/themes/osx/global/icons/search-textbox.png
toolkit/xre/nsUpdateDriver.cpp
--- a/.eslintignore
+++ b/.eslintignore
@@ -103,16 +103,17 @@ devtools/client/shadereditor/**
 devtools/client/shared/**
 devtools/client/sourceeditor/**
 devtools/client/webaudioeditor/**
 devtools/client/webconsole/**
 !devtools/client/webconsole/panel.js
 !devtools/client/webconsole/jsterm.js
 devtools/client/webide/**
 devtools/server/**
+!devtools/server/actors/webbrowser.js
 devtools/shared/*.js
 !devtools/shared/css-color.js
 devtools/shared/*.jsm
 devtools/shared/apps/**
 devtools/shared/client/**
 devtools/shared/discovery/**
 devtools/shared/gcli/**
 devtools/shared/heapsnapshot/**
--- a/CLOBBER
+++ b/CLOBBER
@@ -17,9 +17,9 @@
 #
 # Modifying this file will now automatically clobber the buildbot machines \o/
 #
 
 # Are you updating CLOBBER because you think it's needed for your WebIDL
 # changes to stick? As of bug 928195, this shouldn't be necessary! Please
 # don't change CLOBBER for WebIDL changes any more.
 
-Merge day clobber
\ No newline at end of file
+Bug 1265131 - Update Skia to m51 branch
--- a/accessible/base/EventTree.cpp
+++ b/accessible/base/EventTree.cpp
@@ -148,16 +148,17 @@ TreeMutation::Done()
   for (uint32_t idx = 0; idx < mStartIdx && idx < length; idx++) {
     MOZ_ASSERT(mParent->mChildren[idx]->mIndexInParent == static_cast<int32_t>(idx),
                "Wrong index detected");
   }
 #endif
 
   for (uint32_t idx = mStartIdx; idx < length; idx++) {
     mParent->mChildren[idx]->mIndexInParent = idx;
+    mParent->mChildren[idx]->mInt.mIndexOfEmbeddedChild = -1;
     mParent->mChildren[idx]->mStateFlags |= Accessible::eGroupInfoDirty;
   }
 
   if (mStartIdx < mParent->mChildren.Length() - 1) {
     mParent->mEmbeddedObjCollector = nullptr;
   }
 
   mParent->mStateFlags |= mStateFlagsCopy & Accessible::eKidsMutating;
@@ -178,23 +179,22 @@ TreeMutation::Done()
 
 
 ////////////////////////////////////////////////////////////////////////////////
 // EventTree
 
 void
 EventTree::Process()
 {
-  EventTree* node = mFirst;
-  while (node) {
+  while (mFirst) {
     // Skip a node and its subtree if its container is not in the document.
-    if (node->mContainer->IsInDocument()) {
-      node->Process();
+    if (mFirst->mContainer->IsInDocument()) {
+      mFirst->Process();
     }
-    node = node->mNext;
+    mFirst = mFirst->mNext.forget();
   }
 
   MOZ_ASSERT(mContainer || mDependentEvents.IsEmpty(),
              "No container, no events");
   MOZ_ASSERT(!mContainer || !mContainer->IsDefunct(),
              "Processing events for defunct container");
 
   // Fire mutation events.
--- a/accessible/base/NotificationController.cpp
+++ b/accessible/base/NotificationController.cpp
@@ -397,17 +397,16 @@ NotificationController::WillRefresh(mozi
   mRelocations.Clear();
 
   // If a generic notification occurs after this point then we may be allowed to
   // process it synchronously.  However we do not want to reenter if fireing
   // events causes script to run.
   mObservingState = eRefreshProcessing;
 
   mEventTree.Process();
-  mEventTree.Clear();
 
   ProcessEventQueue();
 
   if (IPCAccessibilityActive()) {
     size_t newDocCount = newChildDocs.Length();
     for (size_t i = 0; i < newDocCount; i++) {
       DocAccessible* childDoc = newChildDocs[i];
       Accessible* parent = childDoc->Parent();
--- a/accessible/ipc/DocAccessibleParent.h
+++ b/accessible/ipc/DocAccessibleParent.h
@@ -161,17 +161,17 @@ private:
     enum { ALLOW_MEMMOVE = true };
 
     ProxyAccessible* mProxy;
   };
 
   uint32_t AddSubtree(ProxyAccessible* aParent,
                       const nsTArray<AccessibleData>& aNewTree, uint32_t aIdx,
                       uint32_t aIdxInParent);
-  MOZ_WARN_UNUSED_RESULT bool CheckDocTree() const;
+  MOZ_MUST_USE bool CheckDocTree() const;
   xpcAccessibleGeneric* GetXPCAccessible(ProxyAccessible* aProxy);
 
   nsTArray<DocAccessibleParent*> mChildDocs;
   DocAccessibleParent* mParentDoc;
 
   /*
    * Conceptually this is a map from IDs to proxies, but we store the ID in the
    * proxy object so we can't use a real map.
--- a/accessible/tests/mochitest/hypertext/test_update.html
+++ b/accessible/tests/mochitest/hypertext/test_update.html
@@ -119,28 +119,54 @@
       }
 
       this.getID = function removeChild_getID()
       {
         return "check text after removing child from '" + aContainerID + "'";
       }
     }
 
+    function removeFirstChild(aContainer)
+    {
+      this.ht = getAccessible(aContainer, [ nsIAccessibleHyperText ]);
+      this.eventSeq = [
+        new invokerChecker(EVENT_REORDER, aContainer)
+      ];
 
+      this.invoke = function removeFirstChild_invoke()
+      {
+        is(this.ht.linkCount, 2, "Wrong embedded objects count before removal");
+
+        getNode(aContainer).removeChild(getNode(aContainer).firstElementChild);
+      }
+
+      this.finalCheck = function removeFirstChild_finalCheck()
+      {
+        // check list index before link count
+        is(this.ht.getLinkIndex(this.ht.firstChild), 0, "Wrong child index");
+        is(this.ht.linkCount, 1, "Wrong embedded objects count after removal");
+      }
+
+      this.getID = function removeFirstChild_getID()
+      {
+        return "Remove first child and check embedded object indeces";
+      }
+    }
 
     //gA11yEventDumpToConsole = true; // debug stuff
 
     var gQueue = null;
     function doTest()
     {
       gQueue = new eventQueue();
       gQueue.push(new addLinks("p1"));
       gQueue.push(new updateText("p2"));
       gQueue.push(new removeChild("div1","div2",
                                   "hello my good friend", "hello friend"));
+      gQueue.push(new removeFirstChild("c4"));
 
       gQueue.invoke(); // Will call SimpleTest.finish();
     }
 
     SimpleTest.waitForExplicitFinish();
     addA11yLoadEvent(doTest);
   </script>
 </head>
@@ -164,10 +190,14 @@
   <p id="display"></p>
   <div id="content" style="display: none"></div>
   <pre id="test">
   </pre>
 
   <p id="p1"></p>
   <p id="p2"><b>hello</b><a>friend</a></p>
   <div id="div1">hello<span id="div2"> my<span id="div3"> good</span></span> friend</span></div>
+  <form id="c4">
+    <label for="c4_input">label</label>
+    <input id="c4_input">
+  </form>
 </body>
 </html>
--- a/b2g/app/b2g.js
+++ b/b2g/app/b2g.js
@@ -880,23 +880,16 @@ pref("disk_space_watcher.enabled", true)
 // SNTP preferences.
 pref("network.sntp.maxRetryCount", 10);
 pref("network.sntp.refreshPeriod", 86400); // In seconds.
 pref("network.sntp.pools", // Servers separated by ';'.
      "0.pool.ntp.org;1.pool.ntp.org;2.pool.ntp.org;3.pool.ntp.org");
 pref("network.sntp.port", 123);
 pref("network.sntp.timeout", 30); // In seconds.
 
-// Enable dataStore
-pref("dom.datastore.enabled", true);
-// When an entry is changed, use two timers to fire system messages in a more
-// moderate pattern.
-pref("dom.datastore.sysMsgOnChangeShortTimeoutSec", 10);
-pref("dom.datastore.sysMsgOnChangeLongTimeoutSec", 60);
-
 // DOM Inter-App Communication API.
 pref("dom.inter-app-communication-api.enabled", true);
 
 // Allow ADB to run for this many hours before disabling
 // (only applies when marionette is disabled)
 // 0 disables the timer.
 pref("b2g.adb.timeout-hours", 12);
 
@@ -1001,16 +994,19 @@ pref("dom.apps.reviewer_paths", "/review
 
 // New implementation to unify touch-caret and selection-carets.
 pref("layout.accessiblecaret.enabled", true);
 
 // Show the selection bars at the two ends of the selection highlight. Required
 // by the spec in bug 921965.
 pref("layout.accessiblecaret.bar.enabled", true);
 
+// Hide the caret in cursor mode after 3 seconds.
+pref("layout.accessiblecaret.timeout_ms", 3000);
+
 // APZ on real devices supports long tap events.
 #ifdef MOZ_WIDGET_GONK
 pref("layout.accessiblecaret.use_long_tap_injector", false);
 #endif
 
 // Hide carets and text selection dialog during scrolling.
 pref("layout.accessiblecaret.always_show_when_scrolling", false);
 
@@ -1054,19 +1050,16 @@ pref("dom.mozSettings.SettingsManager.ve
 pref("dom.mozSettings.SettingsRequestManager.verbose.enabled", false);
 pref("dom.mozSettings.SettingsService.verbose.enabled", false);
 
 // Controlling whether we want to allow forcing some Settings
 // IndexedDB transactions to be opened as readonly or keep everything as
 // readwrite.
 pref("dom.mozSettings.allowForceReadOnly", false);
 
-// RequestSync API is enabled by default on B2G.
-pref("dom.requestSync.enabled", true);
-
 // Comma separated list of activity names that can only be provided by
 // the system app in dev mode.
 pref("dom.activities.developer_mode_only", "import-app");
 
 // mulet apparently loads firefox.js as well as b2g.js, so we have to explicitly
 // disable serviceworkers and push here to get them disabled in mulet.
 pref("dom.serviceWorkers.enabled", false);
 pref("dom.push.enabled", false);
--- a/b2g/chrome/content/shell.js
+++ b/b2g/chrome/content/shell.js
@@ -2,27 +2,25 @@
 /* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
 /* 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/. */
 
 window.performance.mark('gecko-shell-loadstart');
 
 Cu.import('resource://gre/modules/ContactService.jsm');
-Cu.import('resource://gre/modules/DataStoreChangeNotifier.jsm');
 Cu.import('resource://gre/modules/AlarmService.jsm');
 Cu.import('resource://gre/modules/ActivitiesService.jsm');
 Cu.import('resource://gre/modules/NotificationDB.jsm');
 Cu.import('resource://gre/modules/Payment.jsm');
 Cu.import("resource://gre/modules/AppsUtils.jsm");
 Cu.import('resource://gre/modules/UserAgentOverrides.jsm');
 Cu.import('resource://gre/modules/Keyboard.jsm');
 Cu.import('resource://gre/modules/ErrorPage.jsm');
 Cu.import('resource://gre/modules/AlertsHelper.jsm');
-Cu.import('resource://gre/modules/RequestSyncService.jsm');
 Cu.import('resource://gre/modules/SystemUpdateService.jsm');
 
 if (isGonk) {
   Cu.import('resource://gre/modules/NetworkStatsService.jsm');
   Cu.import('resource://gre/modules/ResourceStatsService.jsm');
 }
 
 Cu.import('resource://gre/modules/KillSwitchMain.jsm');
--- a/b2g/installer/package-manifest.in
+++ b/b2g/installer/package-manifest.in
@@ -346,19 +346,16 @@
 @RESPATH@/components/xpcom_xpti.xpt
 @RESPATH@/components/xpconnect.xpt
 @RESPATH@/components/xulapp.xpt
 @RESPATH@/components/xul.xpt
 @RESPATH@/components/xultmpl.xpt
 @RESPATH@/components/zipwriter.xpt
 
 ; JavaScript components
-@RESPATH@/components/RequestSync.manifest
-@RESPATH@/components/RequestSyncManager.js
-@RESPATH@/components/RequestSyncScheduler.js
 @RESPATH@/components/ChromeNotifications.js
 @RESPATH@/components/ChromeNotifications.manifest
 @RESPATH@/components/ConsoleAPI.manifest
 @RESPATH@/components/ConsoleAPIStorage.js
 @RESPATH@/components/BrowserElementParent.manifest
 @RESPATH@/components/BrowserElementParent.js
 @RESPATH@/components/BrowserElementProxy.manifest
 @RESPATH@/components/BrowserElementProxy.js
@@ -927,20 +924,16 @@ bin/libfreebl_32int64_3.so
 
 #ifndef MOZ_WIDGET_GONK
 @RESPATH@/components/SimulatorScreen.js
 #endif
 
 @RESPATH@/components/FxAccountsUIGlue.js
 @RESPATH@/components/services_fxaccounts.xpt
 
-@RESPATH@/components/DataStore.manifest
-@RESPATH@/components/DataStoreImpl.js
-@RESPATH@/components/dom_datastore.xpt
-
 @RESPATH@/components/MobileIdentity.manifest
 @RESPATH@/components/MobileIdentity.js
 @RESPATH@/components/dom_mobileidentity.xpt
 @RESPATH@/components/MobileIdentityUIGlue.js
 @RESPATH@/components/services_mobileidentity.xpt
 
 #ifdef MOZ_WEBSPEECH
 @RESPATH@/components/dom_webspeechsynth.xpt
--- a/browser/app/blocklist.xml
+++ b/browser/app/blocklist.xml
@@ -1,10 +1,10 @@
 <?xml version="1.0"?>
-<blocklist xmlns="http://www.mozilla.org/2006/addons-blocklist" lastupdate="1461272551000">
+<blocklist xmlns="http://www.mozilla.org/2006/addons-blocklist" lastupdate="1461962224000">
   <emItems>
       <emItem  blockID="i58" id="webmaster@buzzzzvideos.info">
                         <versionRange  minVersion="0" maxVersion="*">
                     </versionRange>
                     <prefs>
               </prefs>
     </emItem>
       <emItem  blockID="i71" id="youtube@2youtube.com">
@@ -3589,16 +3589,17 @@
     <gfxBlacklistEntry  blockID="g1124">      <os>All</os>      <vendor>0x8086</vendor>              <devices>
                       <device>0x2a42</device>
                       <device>0x2e22</device>
                       <device>0x2e12</device>
                       <device>0x2e32</device>
                       <device>0x0046</device>
                   </devices>
             <featureStatus>BLOCKED_DRIVER_VERSION</featureStatus>      <driverVersion>8.15.10.2086</driverVersion>      <driverVersionComparator>EQUAL</driverVersionComparator>    </gfxBlacklistEntry>
+    <gfxBlacklistEntry  blockID="g1208">      <os>All</os>      <vendor>0x8086</vendor>            <feature>FEATURE_HARDWARE_VIDEO_DECODING</feature>      <featureStatus>BLOCKED_DRIVER_VERSION</featureStatus>      <driverVersion>10.18.10.3947</driverVersion>      <driverVersionComparator>EQUAL</driverVersionComparator>    </gfxBlacklistEntry>
     </gfxItems>
 
   <certItems>
         <certItem issuerName="MIGQMQswCQYDVQQGEwJHQjEbMBkGA1UECBMSR3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHEwdTYWxmb3JkMRowGAYDVQQKExFDT01PRE8gQ0EgTGltaXRlZDE2MDQGA1UEAxMtQ09NT0RPIFJTQSBEb21haW4gVmFsaWRhdGlvbiBTZWN1cmUgU2VydmVyIENB">
       <serialNumber>D9UltDPl4XVfSSqQOvdiwQ==</serialNumber>
     </certItem>
         <certItem issuerName="MDIxCzAJBgNVBAYTAkNOMQ4wDAYDVQQKEwVDTk5JQzETMBEGA1UEAxMKQ05OSUMgUk9PVA==">
       <serialNumber>STMAjg==</serialNumber>
@@ -3728,19 +3729,16 @@
     </certItem>
         <certItem issuerName="MFwxCzAJBgNVBAYTAkJFMRUwEwYDVQQLEwxUcnVzdGVkIFJvb3QxGTAXBgNVBAoTEEdsb2JhbFNpZ24gbnYtc2ExGzAZBgNVBAMTElRydXN0ZWQgUm9vdCBDQSBHMg==">
       <serialNumber>DAk9hy8DhHSo+aQetvPB/fY=</serialNumber>
     </certItem>
         <certItem issuerName="MFwxCzAJBgNVBAYTAkJFMRUwEwYDVQQLEwxUcnVzdGVkIFJvb3QxGTAXBgNVBAoTEEdsb2JhbFNpZ24gbnYtc2ExGzAZBgNVBAMTElRydXN0ZWQgUm9vdCBDQSBHMg==">
       <serialNumber>O2S99lVUxErLSk56GvWRv+E=</serialNumber>
     </certItem>
         <certItem issuerName="MFwxCzAJBgNVBAYTAkJFMRUwEwYDVQQLEwxUcnVzdGVkIFJvb3QxGTAXBgNVBAoTEEdsb2JhbFNpZ24gbnYtc2ExGzAZBgNVBAMTElRydXN0ZWQgUm9vdCBDQSBHMg==">
-      <serialNumber>F7PAjw2k0dTX5escPnyVOBo=</serialNumber>
-    </certItem>
-        <certItem issuerName="MFwxCzAJBgNVBAYTAkJFMRUwEwYDVQQLEwxUcnVzdGVkIFJvb3QxGTAXBgNVBAoTEEdsb2JhbFNpZ24gbnYtc2ExGzAZBgNVBAMTElRydXN0ZWQgUm9vdCBDQSBHMg==">
       <serialNumber>Mq0P6o03FDk0B2bnJ+mYPGo=</serialNumber>
     </certItem>
         <certItem issuerName="MIGBMQswCQYDVQQGEwJCRTEZMBcGA1UEChMQR2xvYmFsU2lnbiBudi1zYTElMCMGA1UECxMcUHJpbWFyeSBPYmplY3QgUHVibGlzaGluZyBDQTEwMC4GA1UEAxMnR2xvYmFsU2lnbiBQcmltYXJ5IE9iamVjdCBQdWJsaXNoaW5nIENB">
       <serialNumber>BAAAAAABHkSl7L4=</serialNumber>
     </certItem>
         <certItem issuerName="MIGBMQswCQYDVQQGEwJCRTEZMBcGA1UEChMQR2xvYmFsU2lnbiBudi1zYTElMCMGA1UECxMcUHJpbWFyeSBPYmplY3QgUHVibGlzaGluZyBDQTEwMC4GA1UEAxMnR2xvYmFsU2lnbiBQcmltYXJ5IE9iamVjdCBQdWJsaXNoaW5nIENB">
       <serialNumber>BAAAAAABI54PryQ=</serialNumber>
     </certItem>
@@ -3766,12 +3764,180 @@
       <serialNumber>COwoDFvz7GD8R2K7Lo0rYQ==</serialNumber>
     </certItem>
         <certItem issuerName="MIG8MQswCQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0IE5ldHdvcmsxOzA5BgNVBAsTMlRlcm1zIG9mIHVzZSBhdCBodHRwczovL3d3dy52ZXJpc2lnbi5jb20vcnBhIChjKTEwMTYwNAYDVQQDEy1WZXJpU2lnbiBDbGFzcyAzIEludGVybmF0aW9uYWwgU2VydmVyIENBIC0gRzM=">
       <serialNumber>VOcIuNbTqkpOMUyI108FOg==</serialNumber>
     </certItem>
         <certItem issuerName="MIG0MRQwEgYDVQQKEwtFbnRydXN0Lm5ldDFAMD4GA1UECxQ3d3d3LmVudHJ1c3QubmV0L0NQU18yMDQ4IGluY29ycC4gYnkgcmVmLiAobGltaXRzIGxpYWIuKTElMCMGA1UECxMcKGMpIDE5OTkgRW50cnVzdC5uZXQgTGltaXRlZDEzMDEGA1UEAxMqRW50cnVzdC5uZXQgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgKDIwNDgp">
       <serialNumber>TA6EVg==</serialNumber>
     </certItem>
+        <certItem issuerName="MGExCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMR0wGwYDVQQLExREb21haW4gVmFsaWRhdGVkIFNTTDEbMBkGA1UEAxMSR2VvVHJ1c3QgRFYgU1NMIENB">
+      <serialNumber>CWhp</serialNumber>
+    </certItem>
+        <certItem issuerName="MGYxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMR0wGwYDVQQLExREb21haW4gVmFsaWRhdGVkIFNTTDEgMB4GA1UEAxMXR2VvVHJ1c3QgRFYgU1NMIENBIC0gRzI=">
+      <serialNumber>XhcFm2g619rt8Sro+a4rHA==</serialNumber>
+    </certItem>
+        <certItem issuerName="MGYxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMR0wGwYDVQQLExREb21haW4gVmFsaWRhdGVkIFNTTDEgMB4GA1UEAxMXR2VvVHJ1c3QgRFYgU1NMIENBIC0gRzI=">
+      <serialNumber>EDQMI0tR4kSntv1O37N10g==</serialNumber>
+    </certItem>
+        <certItem issuerName="MGYxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMR0wGwYDVQQLExREb21haW4gVmFsaWRhdGVkIFNTTDEgMB4GA1UEAxMXR2VvVHJ1c3QgRFYgU1NMIENBIC0gRzI=">
+      <serialNumber>P6G7IYSL2RZxtzTh8I6qPA==</serialNumber>
+    </certItem>
+        <certItem issuerName="MGYxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMR0wGwYDVQQLExREb21haW4gVmFsaWRhdGVkIFNTTDEgMB4GA1UEAxMXR2VvVHJ1c3QgRFYgU1NMIENBIC0gRzM=">
+      <serialNumber>HNo1DR4XCe4mS1iUMsY6Wg==</serialNumber>
+    </certItem>
+        <certItem issuerName="MGYxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMR0wGwYDVQQLExREb21haW4gVmFsaWRhdGVkIFNTTDEgMB4GA1UEAxMXR2VvVHJ1c3QgRFYgU1NMIENBIC0gRzM=">
+      <serialNumber>KjoVfZ3by6+pL8fssyfM6A==</serialNumber>
+    </certItem>
+        <certItem issuerName="MGYxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMR0wGwYDVQQLExREb21haW4gVmFsaWRhdGVkIFNTTDEgMB4GA1UEAxMXR2VvVHJ1c3QgRFYgU1NMIENBIC0gRzM=">
+      <serialNumber>UW3oKZKTDsrPy/rfwmGNaQ==</serialNumber>
+    </certItem>
+        <certItem issuerName="MGYxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMR0wGwYDVQQLExREb21haW4gVmFsaWRhdGVkIFNTTDEgMB4GA1UEAxMXR2VvVHJ1c3QgRFYgU1NMIENBIC0gRzM=">
+      <serialNumber>XLhHIg7vP+tWfRqvuKeAxw==</serialNumber>
+    </certItem>
+        <certItem issuerName="MGYxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMR0wGwYDVQQLExREb21haW4gVmFsaWRhdGVkIFNTTDEgMB4GA1UEAxMXR2VvVHJ1c3QgRFYgU1NMIENBIC0gRzM=">
+      <serialNumber>YNOos6YJoPC77qwSGCpb7w==</serialNumber>
+    </certItem>
+        <certItem issuerName="MGYxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMR0wGwYDVQQLExREb21haW4gVmFsaWRhdGVkIFNTTDEgMB4GA1UEAxMXR2VvVHJ1c3QgRFYgU1NMIENBIC0gRzM=">
+      <serialNumber>dItWlz2V62Philqj9m6Pbg==</serialNumber>
+    </certItem>
+        <certItem issuerName="MGYxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMR0wGwYDVQQLExREb21haW4gVmFsaWRhdGVkIFNTTDEgMB4GA1UEAxMXR2VvVHJ1c3QgRFYgU1NMIENBIC0gRzM=">
+      <serialNumber>ORFgmCj072NjcJnrxOMfQA==</serialNumber>
+    </certItem>
+        <certItem issuerName="MGYxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMR0wGwYDVQQLExREb21haW4gVmFsaWRhdGVkIFNTTDEgMB4GA1UEAxMXR2VvVHJ1c3QgRFYgU1NMIENBIC0gRzM=">
+      <serialNumber>L79XLVO2ZmtAu7FAG8Wmzw==</serialNumber>
+    </certItem>
+        <certItem issuerName="MGYxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMR0wGwYDVQQLExREb21haW4gVmFsaWRhdGVkIFNTTDEgMB4GA1UEAxMXR2VvVHJ1c3QgRFYgU1NMIENBIC0gRzQ=">
+      <serialNumber>H08=</serialNumber>
+    </certItem>
+        <certItem issuerName="MGgxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMR0wGwYDVQQLExREb21haW4gVmFsaWRhdGVkIFNTTDEiMCAGA1UEAxMZR2VvVHJ1c3QgRFYgU1NMIFNIQTI1NiBDQQ==">
+      <serialNumber>OE4/d+p3YRzzcSl+kmZ8Mw==</serialNumber>
+    </certItem>
+        <certItem issuerName="MGgxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMR0wGwYDVQQLExREb21haW4gVmFsaWRhdGVkIFNTTDEiMCAGA1UEAxMZR2VvVHJ1c3QgRFYgU1NMIFNIQTI1NiBDQQ==">
+      <serialNumber>ZgwfEqZnBsUNvNuZ77FbQA==</serialNumber>
+    </certItem>
+        <certItem issuerName="MEYxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMR8wHQYDVQQDExZHZW9UcnVzdCBTSEEyNTYgU1NMIENB">
+      <serialNumber>OUvvVscW0/NltofkmV9qmg==</serialNumber>
+    </certItem>
+        <certItem issuerName="MEQxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMR0wGwYDVQQDExRHZW9UcnVzdCBTU0wgQ0EgLSBHMg==">
+      <serialNumber>SdegFrLaFTCsoMAW5ED+zA==</serialNumber>
+    </certItem>
+        <certItem issuerName="MEQxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMR0wGwYDVQQDExRHZW9UcnVzdCBTU0wgQ0EgLSBHMg==">
+      <serialNumber>VfTSum25nb65YPlpuhJAvg==</serialNumber>
+    </certItem>
+        <certItem issuerName="MEQxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMR0wGwYDVQQDExRHZW9UcnVzdCBTU0wgQ0EgLSBHMg==">
+      <serialNumber>WX89jn8yGZVvoKTD9jDfRQ==</serialNumber>
+    </certItem>
+        <certItem issuerName="MEQxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMR0wGwYDVQQDExRHZW9UcnVzdCBTU0wgQ0EgLSBHMw==">
+      <serialNumber>cpqpXVWPk5AXzGw+zNIcBw==</serialNumber>
+    </certItem>
+        <certItem issuerName="MEQxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMR0wGwYDVQQDExRHZW9UcnVzdCBTU0wgQ0EgLSBHMw==">
+      <serialNumber>RUT1Gehd1KKYPfqOlgspoQ==</serialNumber>
+    </certItem>
+        <certItem issuerName="MEQxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMR0wGwYDVQQDExRHZW9UcnVzdCBTU0wgQ0EgLSBHMw==">
+      <serialNumber>bx/XHJqcwxDOptxJ2lh5vw==</serialNumber>
+    </certItem>
+        <certItem issuerName="MHMxCzAJBgNVBAYTAlVTMR0wGwYDVQQKExRTeW1hbnRlYyBDb3Jwb3JhdGlvbjEfMB0GA1UECxMWU3ltYW50ZWMgVHJ1c3QgTmV0d29yazEkMCIGA1UEAxMbU3ltYW50ZWMgQ2xhc3MgMyBEU0EgU1NMIENB">
+      <serialNumber>AuhvPsYZfVP6UDsuyjeZ4Q==</serialNumber>
+    </certItem>
+        <certItem issuerName="MH8xCzAJBgNVBAYTAlVTMR0wGwYDVQQKExRTeW1hbnRlYyBDb3Jwb3JhdGlvbjEfMB0GA1UECxMWU3ltYW50ZWMgVHJ1c3QgTmV0d29yazEwMC4GA1UEAxMnU3ltYW50ZWMgQ2xhc3MgMyBFQ0MgMjU2IGJpdCBFViBDQSAtIEcy">
+      <serialNumber>OhrtngFwotLcm4i+z00SjA==</serialNumber>
+    </certItem>
+        <certItem issuerName="MHsxCzAJBgNVBAYTAlVTMR0wGwYDVQQKExRTeW1hbnRlYyBDb3Jwb3JhdGlvbjEfMB0GA1UECxMWU3ltYW50ZWMgVHJ1c3QgTmV0d29yazEsMCoGA1UEAxMjU3ltYW50ZWMgQ2xhc3MgMyBFQ0MgMjU2IGJpdCBTU0wgQ0E=">
+      <serialNumber>U3SgRR3J+D6575WuCxuXeQ==</serialNumber>
+    </certItem>
+        <certItem issuerName="MHcxCzAJBgNVBAYTAlVTMR0wGwYDVQQKExRTeW1hbnRlYyBDb3Jwb3JhdGlvbjEfMB0GA1UECxMWU3ltYW50ZWMgVHJ1c3QgTmV0d29yazEoMCYGA1UEAxMfU3ltYW50ZWMgQ2xhc3MgMyBFViBTU0wgQ0EgLSBHMg==">
+      <serialNumber>UVKsEezpGWOVQ4W9esstng==</serialNumber>
+    </certItem>
+        <certItem issuerName="MHcxCzAJBgNVBAYTAlVTMR0wGwYDVQQKExRTeW1hbnRlYyBDb3Jwb3JhdGlvbjEfMB0GA1UECxMWU3ltYW50ZWMgVHJ1c3QgTmV0d29yazEoMCYGA1UEAxMfU3ltYW50ZWMgQ2xhc3MgMyBFViBTU0wgQ0EgLSBHMw==">
+      <serialNumber>acI1CFIgmwSFBoU5+ahDgg==</serialNumber>
+    </certItem>
+        <certItem issuerName="MH4xCzAJBgNVBAYTAlVTMR0wGwYDVQQKExRTeW1hbnRlYyBDb3Jwb3JhdGlvbjEfMB0GA1UECxMWU3ltYW50ZWMgVHJ1c3QgTmV0d29yazEvMC0GA1UEAxMmU3ltYW50ZWMgQ2xhc3MgMyBTZWN1cmUgU2VydmVyIENBIC0gRzQ=">
+      <serialNumber>Sx51x7V8pYe8rp7PMP/3qg==</serialNumber>
+    </certItem>
+        <certItem issuerName="MH4xCzAJBgNVBAYTAlVTMR0wGwYDVQQKExRTeW1hbnRlYyBDb3Jwb3JhdGlvbjEfMB0GA1UECxMWU3ltYW50ZWMgVHJ1c3QgTmV0d29yazEvMC0GA1UEAxMmU3ltYW50ZWMgQ2xhc3MgMyBTZWN1cmUgU2VydmVyIENBIC0gRzQ=">
+      <serialNumber>PAdKZPiaac2CvPxbOrsHOw==</serialNumber>
+    </certItem>
+        <certItem issuerName="MH4xCzAJBgNVBAYTAlVTMR0wGwYDVQQKExRTeW1hbnRlYyBDb3Jwb3JhdGlvbjEfMB0GA1UECxMWU3ltYW50ZWMgVHJ1c3QgTmV0d29yazEvMC0GA1UEAxMmU3ltYW50ZWMgQ2xhc3MgMyBTZWN1cmUgU2VydmVyIENBIC0gRzQ=">
+      <serialNumber>E77H6yvyFQjO0PcN3x0H+Q==</serialNumber>
+    </certItem>
+        <certItem issuerName="MH4xCzAJBgNVBAYTAlVTMR0wGwYDVQQKExRTeW1hbnRlYyBDb3Jwb3JhdGlvbjEfMB0GA1UECxMWU3ltYW50ZWMgVHJ1c3QgTmV0d29yazEvMC0GA1UEAxMmU3ltYW50ZWMgQ2xhc3MgMyBTZWN1cmUgU2VydmVyIENBIC0gRzQ=">
+      <serialNumber>d8AtKymQwkOPDBj+hjPzFg==</serialNumber>
+    </certItem>
+        <certItem issuerName="MH4xCzAJBgNVBAYTAlVTMR0wGwYDVQQKExRTeW1hbnRlYyBDb3Jwb3JhdGlvbjEfMB0GA1UECxMWU3ltYW50ZWMgVHJ1c3QgTmV0d29yazEvMC0GA1UEAxMmU3ltYW50ZWMgQ2xhc3MgMyBTZWN1cmUgU2VydmVyIENBIC0gRzQ=">
+      <serialNumber>TurPPI6eivtNeGYdM0ZWXQ==</serialNumber>
+    </certItem>
+        <certItem issuerName="MH4xCzAJBgNVBAYTAlVTMR0wGwYDVQQKExRTeW1hbnRlYyBDb3Jwb3JhdGlvbjEfMB0GA1UECxMWU3ltYW50ZWMgVHJ1c3QgTmV0d29yazEvMC0GA1UEAxMmU3ltYW50ZWMgQ2xhc3MgMyBTZWN1cmUgU2VydmVyIENBIC0gRzQ=">
+      <serialNumber>a9/VeyVWrzFD7rM2PEHwQA==</serialNumber>
+    </certItem>
+        <certItem issuerName="MH4xCzAJBgNVBAYTAlVTMR0wGwYDVQQKExRTeW1hbnRlYyBDb3Jwb3JhdGlvbjEfMB0GA1UECxMWU3ltYW50ZWMgVHJ1c3QgTmV0d29yazEvMC0GA1UEAxMmU3ltYW50ZWMgQ2xhc3MgMyBTZWN1cmUgU2VydmVyIENBIC0gRzQ=">
+      <serialNumber>LnfcUaXG/pxV2CpXM5+YSg==</serialNumber>
+    </certItem>
+        <certItem issuerName="MH4xCzAJBgNVBAYTAlVTMR0wGwYDVQQKExRTeW1hbnRlYyBDb3Jwb3JhdGlvbjEfMB0GA1UECxMWU3ltYW50ZWMgVHJ1c3QgTmV0d29yazEvMC0GA1UEAxMmU3ltYW50ZWMgQ2xhc3MgMyBTZWN1cmUgU2VydmVyIENBIC0gRzQ=">
+      <serialNumber>AygWP2Fgd2T+iLbmAlKT6g==</serialNumber>
+    </certItem>
+        <certItem issuerName="MH4xCzAJBgNVBAYTAlVTMR0wGwYDVQQKExRTeW1hbnRlYyBDb3Jwb3JhdGlvbjEfMB0GA1UECxMWU3ltYW50ZWMgVHJ1c3QgTmV0d29yazEvMC0GA1UEAxMmU3ltYW50ZWMgQ2xhc3MgMyBTZWN1cmUgU2VydmVyIENBIC0gRzQ=">
+      <serialNumber>ezdAeCxKH7BFs7vn3byYaw==</serialNumber>
+    </certItem>
+        <certItem issuerName="MH4xCzAJBgNVBAYTAlVTMR0wGwYDVQQKExRTeW1hbnRlYyBDb3Jwb3JhdGlvbjEfMB0GA1UECxMWU3ltYW50ZWMgVHJ1c3QgTmV0d29yazEvMC0GA1UEAxMmU3ltYW50ZWMgQ2xhc3MgMyBTZWN1cmUgU2VydmVyIENBIC0gRzQ=">
+      <serialNumber>45KI4WIxyXfNrdtdj7C6</serialNumber>
+    </certItem>
+        <certItem issuerName="MH4xCzAJBgNVBAYTAlVTMR0wGwYDVQQKExRTeW1hbnRlYyBDb3Jwb3JhdGlvbjEfMB0GA1UECxMWU3ltYW50ZWMgVHJ1c3QgTmV0d29yazEvMC0GA1UEAxMmU3ltYW50ZWMgQ2xhc3MgMyBTZWN1cmUgU2VydmVyIENBIC0gRzQ=">
+      <serialNumber>UMUwXwT1Z4juyQ/CNTf4mw==</serialNumber>
+    </certItem>
+        <certItem issuerName="MH4xCzAJBgNVBAYTAlVTMR0wGwYDVQQKExRTeW1hbnRlYyBDb3Jwb3JhdGlvbjEfMB0GA1UECxMWU3ltYW50ZWMgVHJ1c3QgTmV0d29yazEvMC0GA1UEAxMmU3ltYW50ZWMgQ2xhc3MgMyBTZWN1cmUgU2VydmVyIENBIC0gRzQ=">
+      <serialNumber>HZyLf+K70FKc+jomm8DiDw==</serialNumber>
+    </certItem>
+        <certItem issuerName="MGMxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwx0aGF3dGUsIEluYy4xHTAbBgNVBAsTFERvbWFpbiBWYWxpZGF0ZWQgU1NMMR4wHAYDVQQDExV0aGF3dGUgRFYgU1NMIENBIC0gRzI=">
+      <serialNumber>IIxFSyNM6mWtCgTG0IL3Og==</serialNumber>
+    </certItem>
+        <certItem issuerName="MGMxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwx0aGF3dGUsIEluYy4xHTAbBgNVBAsTFERvbWFpbiBWYWxpZGF0ZWQgU1NMMR4wHAYDVQQDExV0aGF3dGUgRFYgU1NMIENBIC0gRzI=">
+      <serialNumber>Rvm2CEw2IC2Mu/ax0A46QQ==</serialNumber>
+    </certItem>
+        <certItem issuerName="MGMxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwx0aGF3dGUsIEluYy4xHTAbBgNVBAsTFERvbWFpbiBWYWxpZGF0ZWQgU1NMMR4wHAYDVQQDExV0aGF3dGUgRFYgU1NMIENBIC0gRzI=">
+      <serialNumber>TqfXw+FkhxfVgE9GVMgjWQ==</serialNumber>
+    </certItem>
+        <certItem issuerName="MGMxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwx0aGF3dGUsIEluYy4xHTAbBgNVBAsTFERvbWFpbiBWYWxpZGF0ZWQgU1NMMR4wHAYDVQQDExV0aGF3dGUgRFYgU1NMIENBIC0gRzI=">
+      <serialNumber>E5I2y6sIonl4a+TmlXc7fw==</serialNumber>
+    </certItem>
+        <certItem issuerName="MGMxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwx0aGF3dGUsIEluYy4xHTAbBgNVBAsTFERvbWFpbiBWYWxpZGF0ZWQgU1NMMR4wHAYDVQQDExV0aGF3dGUgRFYgU1NMIENBIC0gRzI=">
+      <serialNumber>GdXz4L1b6FKNCMG9Jz2tjA==</serialNumber>
+    </certItem>
+        <certItem issuerName="MGMxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwx0aGF3dGUsIEluYy4xHTAbBgNVBAsTFERvbWFpbiBWYWxpZGF0ZWQgU1NMMR4wHAYDVQQDExV0aGF3dGUgRFYgU1NMIENBIC0gRzI=">
+      <serialNumber>BUrYjru5px1ym4QUN33TOQ==</serialNumber>
+    </certItem>
+        <certItem issuerName="MGMxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwx0aGF3dGUsIEluYy4xHTAbBgNVBAsTFERvbWFpbiBWYWxpZGF0ZWQgU1NMMR4wHAYDVQQDExV0aGF3dGUgRFYgU1NMIENBIC0gRzI=">
+      <serialNumber>CqZgEvHAsnzkT//QV9KjXw==</serialNumber>
+    </certItem>
+        <certItem issuerName="MGMxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwx0aGF3dGUsIEluYy4xHTAbBgNVBAsTFERvbWFpbiBWYWxpZGF0ZWQgU1NMMR4wHAYDVQQDExV0aGF3dGUgRFYgU1NMIENBIC0gRzI=">
+      <serialNumber>DYifRdP6aQQ8MLbXZY2f5g==</serialNumber>
+    </certItem>
+        <certItem issuerName="MEExCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxUaGF3dGUsIEluYy4xGzAZBgNVBAMTElRoYXd0ZSBTR0MgQ0EgLSBHMg==">
+      <serialNumber>cDggUYfwJ3A1YcdoeT6s4A==</serialNumber>
+    </certItem>
+        <certItem issuerName="MEExCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxUaGF3dGUsIEluYy4xGzAZBgNVBAMTElRoYXd0ZSBTR0MgQ0EgLSBHMg==">
+      <serialNumber>e0bEFhI16xx9U1yvlI56rA==</serialNumber>
+    </certItem>
+        <certItem issuerName="MEMxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwx0aGF3dGUsIEluYy4xHTAbBgNVBAMTFHRoYXd0ZSBTSEEyNTYgU1NMIENB">
+      <serialNumber>UKKK5ol/rKBZchAAOnZjaA==</serialNumber>
+    </certItem>
+        <certItem issuerName="MEExCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwx0aGF3dGUsIEluYy4xGzAZBgNVBAMTEnRoYXd0ZSBTU0wgQ0EgLSBHMg==">
+      <serialNumber>FNISyWWTGi5Yco6fGh58/A==</serialNumber>
+    </certItem>
+        <certItem issuerName="MEExCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwx0aGF3dGUsIEluYy4xGzAZBgNVBAMTEnRoYXd0ZSBTU0wgQ0EgLSBHMg==">
+      <serialNumber>JpUvYJyWjdGmeoH7YcYunw==</serialNumber>
+    </certItem>
+        <certItem issuerName="MIG8MQswCQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0IE5ldHdvcmsxOzA5BgNVBAsTMlRlcm1zIG9mIHVzZSBhdCBodHRwczovL3d3dy52ZXJpc2lnbi5jb20vcnBhIChjKTEwMTYwNAYDVQQDEy1WZXJpU2lnbiBDbGFzcyAzIEludGVybmF0aW9uYWwgU2VydmVyIENBIC0gRzM=">
+      <serialNumber>OnvXX72mvUI2Id/NMzegmg==</serialNumber>
+    </certItem>
+        <certItem issuerName="MIG1MQswCQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0IE5ldHdvcmsxOzA5BgNVBAsTMlRlcm1zIG9mIHVzZSBhdCBodHRwczovL3d3dy52ZXJpc2lnbi5jb20vcnBhIChjKTEwMS8wLQYDVQQDEyZWZXJpU2lnbiBDbGFzcyAzIFNlY3VyZSBTZXJ2ZXIgQ0EgLSBHMw==">
+      <serialNumber>QZBvapTZFvmYktEPsBYLQQ==</serialNumber>
+    </certItem>
+        <certItem issuerName="MIG1MQswCQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0IE5ldHdvcmsxOzA5BgNVBAsTMlRlcm1zIG9mIHVzZSBhdCBodHRwczovL3d3dy52ZXJpc2lnbi5jb20vcnBhIChjKTEwMS8wLQYDVQQDEyZWZXJpU2lnbiBDbGFzcyAzIFNlY3VyZSBTZXJ2ZXIgQ0EgLSBHMw==">
+      <serialNumber>OqQ2rV0ISTc308Z/oQgzFw==</serialNumber>
+    </certItem>
+        <certItem issuerName="MIG1MQswCQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0IE5ldHdvcmsxOzA5BgNVBAsTMlRlcm1zIG9mIHVzZSBhdCBodHRwczovL3d3dy52ZXJpc2lnbi5jb20vcnBhIChjKTEwMS8wLQYDVQQDEyZWZXJpU2lnbiBDbGFzcyAzIFNlY3VyZSBTZXJ2ZXIgQ0EgLSBHMw==">
+      <serialNumber>NvEJoRYL2yvAZrAjbDIipQ==</serialNumber>
+    </certItem>
       </certItems>
 
 
 </blocklist>
\ No newline at end of file
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -481,16 +481,18 @@ pref("browser.ctrlTab.previews", false);
 pref("browser.bookmarks.autoExportHTML",          false);
 
 // The maximum number of daily bookmark backups to
 // keep in {PROFILEDIR}/bookmarkbackups. Special values:
 // -1: unlimited
 //  0: no backups created (and deletes all existing backups)
 pref("browser.bookmarks.max_backups",             15);
 
+pref("browser.bookmarks.showRecentlyBookmarked",  true);
+
 // Scripts & Windows prefs
 pref("dom.disable_open_during_load",              true);
 pref("javascript.options.showInConsole",          true);
 #ifdef DEBUG
 pref("general.warnOnAboutConfig",                 false);
 #endif
 
 // This is the pref to control the location bar, change this to true to
--- a/browser/base/content/aboutDialog-appUpdater.js
+++ b/browser/base/content/aboutDialog-appUpdater.js
@@ -74,18 +74,17 @@ function appUpdater()
     // selectPanel("downloading") is called from setupDownloadingUI().
     return;
   }
 
   // Honor the "Never check for updates" option by not only disabling background
   // update checks, but also in the About dialog, by presenting a
   // "Check for updates" button.
   // If updates are found, the user is then asked if he wants to "Update to <version>".
-  if (!this.updateEnabled ||
-      Services.prefs.prefHasUserValue(PREF_APP_UPDATE_ELEVATE_NEVER)) {
+  if (!this.updateEnabled) {
     this.selectPanel("checkForUpdates");
     return;
   }
 
   // That leaves the options
   // "Check for updates, but let me choose whether to install them", and
   // "Automatically install updates".
   // In both cases, we check for updates without asking.
@@ -97,23 +96,21 @@ appUpdater.prototype =
 {
   // true when there is an update check in progress.
   isChecking: false,
 
   // true when there is an update already staged / ready to be applied.
   get isPending() {
     if (this.update) {
       return this.update.state == "pending" ||
-             this.update.state == "pending-service" ||
-             this.update.state == "pending-elevate";
+             this.update.state == "pending-service";
     }
     return this.um.activeUpdate &&
            (this.um.activeUpdate.state == "pending" ||
-            this.um.activeUpdate.state == "pending-service" ||
-            this.um.activeUpdate.state == "pending-elevate");
+            this.um.activeUpdate.state == "pending-service");
   },
 
   // true when there is an update already installed in the background.
   get isApplied() {
     if (this.update)
       return this.update.state == "applied" ||
              this.update.state == "applied-service";
     return this.um.activeUpdate &&
@@ -184,61 +181,51 @@ appUpdater.prototype =
       this.updateDeck.selectedPanel = panel;
     }
   },
 
   /**
    * Check for updates
    */
   checkForUpdates: function() {
-    // Clear prefs that could prevent a user from discovering available updates.
-    if (Services.prefs.prefHasUserValue(PREF_APP_UPDATE_CANCELATIONS_OSX)) {
-      Services.prefs.clearUserPref(PREF_APP_UPDATE_CANCELATIONS_OSX);
-    }
-    if (Services.prefs.prefHasUserValue(PREF_APP_UPDATE_ELEVATE_NEVER)) {
-      Services.prefs.clearUserPref(PREF_APP_UPDATE_ELEVATE_NEVER);
-    }
     this.selectPanel("checkingForUpdates");
     this.isChecking = true;
     this.checker.checkForUpdates(this.updateCheckListener, true);
     // after checking, onCheckComplete() is called
   },
 
   /**
    * Handles oncommand for the "Restart to Update" button
    * which is presented after the download has been downloaded.
    */
   buttonRestartAfterDownload: function() {
-    if (!this.isPending && !this.isApplied) {
-      return;
-    }
-
-    // Notify all windows that an application quit has been requested.
-    let cancelQuit = Components.classes["@mozilla.org/supports-PRBool;1"].
-                     createInstance(Components.interfaces.nsISupportsPRBool);
-    Services.obs.notifyObservers(cancelQuit, "quit-application-requested",
-                                 "restart");
-
-    // Something aborted the quit process.
-    if (cancelQuit.data) {
+    if (!this.isPending && !this.isApplied)
       return;
-    }
 
-    let appStartup = Components.classes["@mozilla.org/toolkit/app-startup;1"].
-                     getService(Components.interfaces.nsIAppStartup);
+      // Notify all windows that an application quit has been requested.
+      let cancelQuit = Components.classes["@mozilla.org/supports-PRBool;1"].
+                       createInstance(Components.interfaces.nsISupportsPRBool);
+      Services.obs.notifyObservers(cancelQuit, "quit-application-requested", "restart");
+
+      // Something aborted the quit process.
+      if (cancelQuit.data)
+        return;
 
-    // If already in safe mode restart in safe mode (bug 327119)
-    if (Services.appinfo.inSafeMode) {
-      appStartup.restartInSafeMode(Components.interfaces.nsIAppStartup.eAttemptQuit);
-      return;
-    }
+      let appStartup = Components.classes["@mozilla.org/toolkit/app-startup;1"].
+                       getService(Components.interfaces.nsIAppStartup);
 
-    appStartup.quit(Components.interfaces.nsIAppStartup.eAttemptQuit |
-                    Components.interfaces.nsIAppStartup.eRestart);
-  },
+      // If already in safe mode restart in safe mode (bug 327119)
+      if (Services.appinfo.inSafeMode) {
+        appStartup.restartInSafeMode(Components.interfaces.nsIAppStartup.eAttemptQuit);
+        return;
+      }
+
+      appStartup.quit(Components.interfaces.nsIAppStartup.eAttemptQuit |
+                      Components.interfaces.nsIAppStartup.eRestart);
+    },
 
   /**
    * Handles oncommand for the "Apply Update…" button
    * which is presented if we need to show the billboard.
    */
   buttonApplyBillboard: function() {
     const URI_UPDATE_PROMPT_DIALOG = "chrome://mozapps/content/update/updates.xul";
     var ary = null;
@@ -382,18 +369,17 @@ appUpdater.prototype =
       if (this.backgroundUpdateEnabled) {
         this.selectPanel("applying");
         let update = this.um.activeUpdate;
         let self = this;
         Services.obs.addObserver(function (aSubject, aTopic, aData) {
           // Update the UI when the background updater is finished
           let status = aData;
           if (status == "applied" || status == "applied-service" ||
-              status == "pending" || status == "pending-service" ||
-              status == "pending-elevate") {
+              status == "pending" || status == "pending-service") {
             // If the update is successfully applied, or if the updater has
             // fallen back to non-staged updates, show the "Restart to Update"
             // button.
             self.selectPanel("apply");
           } else if (status == "failed") {
             // Background update has failed, let's show the UI responsible for
             // prompting the user to update manually.
             self.selectPanel("downloadFailed");
--- a/browser/base/content/aboutDialog.xul
+++ b/browser/base/content/aboutDialog.xul
@@ -62,17 +62,17 @@
                 <button id="checkForUpdatesButton" align="start"
                         label="&update.checkForUpdatesButton.label;"
                         accesskey="&update.checkForUpdatesButton.accesskey;"
                         oncommand="gAppUpdater.checkForUpdates();"/>
                 <spacer flex="1"/>
               </hbox>
               <hbox id="downloadAndInstall" align="center">
                 <button id="downloadAndInstallButton" align="start"
-                        oncommand="gAppUpdater.doUpdate();"/>
+                        oncommand="gAppUpdater.startDownload();"/>
                         <!-- label and accesskey will be filled by JS -->
                 <spacer flex="1"/>
               </hbox>
               <hbox id="apply" align="center">
                 <button id="updateButton" align="start"
                         label="&update.updateButton.label2;"
                         accesskey="&update.updateButton.accesskey;"
                         oncommand="gAppUpdater.buttonRestartAfterDownload();"/>
--- a/browser/base/content/browser-fullScreen.js
+++ b/browser/base/content/browser-fullScreen.js
@@ -126,32 +126,17 @@ var FullScreen = {
         // to get its corresponding browser here.
         let browser;
         if (event.target == gBrowser) {
           browser = event.originalTarget;
         } else {
           let topWin = event.target.ownerDocument.defaultView.top;
           browser = gBrowser.getBrowserForContentWindow(topWin);
         }
-        if (!browser || !this.enterDomFullscreen(browser)) {
-          if (document.fullscreenElement) {
-            // MozDOMFullscreen:Entered is dispatched synchronously in
-            // fullscreen change, hence we have to avoid calling this
-            // method synchronously here.
-            setTimeout(() => document.exitFullscreen(), 0);
-          }
-          break;
-        }
-        // If it is a remote browser, send a message to ask the content
-        // to enter fullscreen state. We don't need to do so if it is an
-        // in-process browser, since all related document should have
-        // entered fullscreen state at this point.
-        if (this._isRemoteBrowser(browser)) {
-          browser.messageManager.sendAsyncMessage("DOMFullscreen:Entered");
-        }
+        this.enterDomFullscreen(browser);
         break;
       }
       case "MozDOMFullscreen:Exited":
         this.cleanupDomFullscreen();
         break;
     }
   },
 
@@ -173,32 +158,40 @@ var FullScreen = {
       case "DOMFullscreen:Painted": {
         Services.obs.notifyObservers(window, "fullscreen-painted", "");
         break;
       }
     }
   },
 
   enterDomFullscreen : function(aBrowser) {
-    if (!document.fullscreenElement)
-      return false;
+    if (!document.fullscreenElement) {
+      return;
+    }
 
     // If we've received a fullscreen notification, we have to ensure that the
     // element that's requesting fullscreen belongs to the browser that's currently
     // active. If not, we exit fullscreen since the "full-screen document" isn't
     // actually visible now.
-    if (gBrowser.selectedBrowser != aBrowser) {
-      return false;
+    if (!aBrowser || gBrowser.selectedBrowser != aBrowser ||
+        // The top-level window has lost focus since the request to enter
+        // full-screen was made. Cancel full-screen.
+        Services.focus.activeWindow != window) {
+      // This function is called synchronously in fullscreen change, so
+      // we have to avoid calling exitFullscreen synchronously here.
+      setTimeout(() => document.exitFullscreen(), 0);
+      return;
     }
 
-    let focusManager = Services.focus;
-    if (focusManager.activeWindow != window) {
-      // The top-level window has lost focus since the request to enter
-      // full-screen was made. Cancel full-screen.
-      return false;
+    // If it is a remote browser, send a message to ask the content
+    // to enter fullscreen state. We don't need to do so if it is an
+    // in-process browser, since all related document should have
+    // entered fullscreen state at this point.
+    if (this._isRemoteBrowser(aBrowser)) {
+      aBrowser.messageManager.sendAsyncMessage("DOMFullscreen:Entered");
     }
 
     document.documentElement.setAttribute("inDOMFullscreen", true);
 
     if (gFindBarInitialized) {
       gFindBar.close(true);
     }
 
@@ -206,40 +199,38 @@ var FullScreen = {
     gBrowser.tabContainer.addEventListener("TabOpen", this.exitDomFullScreen);
     gBrowser.tabContainer.addEventListener("TabClose", this.exitDomFullScreen);
     gBrowser.tabContainer.addEventListener("TabSelect", this.exitDomFullScreen);
 
     // Add listener to detect when the fullscreen window is re-focused.
     // If a fullscreen window loses focus, we show a warning when the
     // fullscreen window is refocused.
     window.addEventListener("activate", this);
-
-    return true;
   },
 
   cleanup: function () {
     if (!window.fullScreen) {
       MousePosTracker.removeListener(this);
       document.removeEventListener("keypress", this._keyToggleCallback, false);
       document.removeEventListener("popupshown", this._setPopupOpen, false);
       document.removeEventListener("popuphidden", this._setPopupOpen, false);
     }
   },
 
   cleanupDomFullscreen: function () {
+    window.messageManager
+          .broadcastAsyncMessage("DOMFullscreen:CleanUp");
+
     this._WarningBox.close();
     gBrowser.tabContainer.removeEventListener("TabOpen", this.exitDomFullScreen);
     gBrowser.tabContainer.removeEventListener("TabClose", this.exitDomFullScreen);
     gBrowser.tabContainer.removeEventListener("TabSelect", this.exitDomFullScreen);
     window.removeEventListener("activate", this);
 
     document.documentElement.removeAttribute("inDOMFullscreen");
-
-    window.messageManager
-          .broadcastAsyncMessage("DOMFullscreen:CleanUp");
   },
 
   _isRemoteBrowser: function (aBrowser) {
     return gMultiProcessBrowser && aBrowser.getAttribute("remote") == "true";
   },
 
   get _windowUtils() {
     return window.QueryInterface(Ci.nsIInterfaceRequestor)
--- a/browser/base/content/browser-places.js
+++ b/browser/base/content/browser-places.js
@@ -545,21 +545,24 @@ var PlacesCommandHook = {
   /**
    * List of nsIURI objects characterizing the tabs currently open in the
    * browser, modulo pinned tabs.  The URIs will be in the order in which their
    * corresponding tabs appeared and duplicates are discarded.
    */
   get uniqueCurrentPages() {
     let uniquePages = {};
     let URIs = [];
-    gBrowser.visibleTabs.forEach(function (tab) {
-      let spec = tab.linkedBrowser.currentURI.spec;
+
+    gBrowser.visibleTabs.forEach(tab => {
+      let browser = tab.linkedBrowser;
+      let uri = browser.currentURI;
+      let spec = uri.spec;
       if (!tab.pinned && !(spec in uniquePages)) {
         uniquePages[spec] = null;
-        URIs.push(tab.linkedBrowser.currentURI);
+        URIs.push({ uri, title: browser.contentTitle });
       }
     });
     return URIs;
   },
 
   /**
    * Adds a folder with bookmarks to all of the currently open tabs in this
    * window.
@@ -1321,18 +1324,18 @@ var BookmarkingUI = {
     if (widget.overflowed) {
       // Don't open a popup in the overflow popup, rather just open the Library.
       event.preventDefault();
       widget.node.removeAttribute("closemenu");
       PlacesCommandHook.showPlacesOrganizer("BookmarksMenu");
       return;
     }
 
-    this._updateRecentBookmarks(document.getElementById("BMB_recentBookmarks"),
-                                "subviewbutton");
+    this._initRecentBookmarks(document.getElementById("BMB_recentBookmarks"),
+                              "subviewbutton");
 
     if (!this._popupNeedsUpdate)
       return;
     this._popupNeedsUpdate = false;
 
     let popup = event.target;
     let getPlacesAnonymousElement =
       aAnonId => document.getAnonymousElementByAttribute(popup.parentNode,
@@ -1357,31 +1360,87 @@ var BookmarkingUI = {
       extraClasses: {
         entry: "subviewbutton",
         footer: "panel-subview-footer"
       },
       insertionPoint: ".panel-subview-footer"
     });
   },
 
-  _updateRecentBookmarks: function(aHeaderItem, extraCSSClass = "") {
+  RECENTLY_BOOKMARKED_PREF: "browser.bookmarks.showRecentlyBookmarked",
+
+  _initRecentBookmarks(aHeaderItem, aExtraCSSClass) {
+    this._populateRecentBookmarks(aHeaderItem, aExtraCSSClass);
+
+    // Add observers and listeners and remove them again when the menupopup closes.
+
+    let bookmarksMenu = aHeaderItem.parentNode;
+    let placesContextMenu = document.getElementById("placesContext");
+
+    let prefObserver = () => {
+      this._populateRecentBookmarks(aHeaderItem, aExtraCSSClass);
+    };
+
+    let updatePlacesContextMenu = (shouldHidePrefUI = false) => {
+      let prefEnabled = !shouldHidePrefUI && Services.prefs.getBoolPref(this.RECENTLY_BOOKMARKED_PREF);
+      document.getElementById("placesContext_showRecentlyBookmarked").hidden = shouldHidePrefUI || prefEnabled;
+      document.getElementById("placesContext_hideRecentlyBookmarked").hidden = shouldHidePrefUI || !prefEnabled;
+      document.getElementById("placesContext_recentlyBookmarkedSeparator").hidden = shouldHidePrefUI;
+    };
+
+    let onPlacesContextMenuShowing = event => {
+      if (event.target == event.currentTarget) {
+        let triggerPopup = event.target.triggerNode;
+        while (triggerPopup && triggerPopup.localName != "menupopup") {
+          triggerPopup = triggerPopup.parentNode;
+        }
+        let shouldHidePrefUI = triggerPopup != bookmarksMenu;
+        updatePlacesContextMenu(shouldHidePrefUI);
+      }
+    };
+
+    let onBookmarksMenuHidden = event => {
+      if (event.target == event.currentTarget) {
+        updatePlacesContextMenu(true);
+
+        Services.prefs.removeObserver(this.RECENTLY_BOOKMARKED_PREF, prefObserver, false);
+        placesContextMenu.removeEventListener("popupshowing", onPlacesContextMenuShowing);
+        bookmarksMenu.removeEventListener("popuphidden", onBookmarksMenuHidden);
+      }
+    };
+
+    Services.prefs.addObserver(this.RECENTLY_BOOKMARKED_PREF, prefObserver, false);
+    placesContextMenu.addEventListener("popupshowing", onPlacesContextMenuShowing);
+    bookmarksMenu.addEventListener("popuphidden", onBookmarksMenuHidden);
+  },
+
+  _populateRecentBookmarks(aHeaderItem, aExtraCSSClass = "") {
+    while (aHeaderItem.nextSibling &&
+           aHeaderItem.nextSibling.localName == "menuitem") {
+      aHeaderItem.nextSibling.remove();
+    }
+
+    let shouldShow = Services.prefs.getBoolPref(this.RECENTLY_BOOKMARKED_PREF);
+    let separator = aHeaderItem.previousSibling;
+    aHeaderItem.hidden = !shouldShow;
+    separator.hidden = !shouldShow;
+
+    if (!shouldShow) {
+      return;
+    }
+
     const kMaxResults = 5;
 
     let options = PlacesUtils.history.getNewQueryOptions();
     options.excludeQueries = true;
     options.queryType = options.QUERY_TYPE_BOOKMARKS;
     options.sortingMode = options.SORT_BY_DATEADDED_DESCENDING;
     options.maxResults = kMaxResults;
     let query = PlacesUtils.history.getNewQuery();
 
-    while (aHeaderItem.nextSibling &&
-           aHeaderItem.nextSibling.localName == "menuitem") {
-      aHeaderItem.nextSibling.remove();
-    }
-
     let onItemCommand = function (aEvent) {
       let item = aEvent.target;
       openUILink(item.getAttribute("targetURI"), aEvent);
       CustomizableUI.hidePanelForNode(item);
     };
 
     let fragment = document.createDocumentFragment();
     let root = PlacesUtils.history.executeQuery(query, options).root;
@@ -1392,47 +1451,36 @@ var BookmarkingUI = {
       let title = node.title;
       let icon = node.icon;
 
       let item =
         document.createElementNS("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul",
                                  "menuitem");
       item.setAttribute("label", title || uri);
       item.setAttribute("targetURI", uri);
+      item.setAttribute("context", "hideRecentlyBookmarked");
       item.setAttribute("class", "menuitem-iconic menuitem-with-favicon bookmark-item " +
-                                 extraCSSClass);
+                                 aExtraCSSClass);
       item.addEventListener("command", onItemCommand);
       if (icon) {
         item.setAttribute("image", icon);
       }
       fragment.appendChild(item);
     }
     root.containerOpen = false;
     aHeaderItem.parentNode.insertBefore(fragment, aHeaderItem.nextSibling);
+    aHeaderItem.setAttribute("context", "hideRecentlyBookmarked");
   },
 
-  /**
-   * Handles star styling based on page proxy state changes.
-   */
-  onPageProxyStateChanged: function BUI_onPageProxyStateChanged(aState) {
-    if (!this._shouldUpdateStarState() || !this.star) {
-      return;
-    }
+  showRecentlyBookmarked() {
+    Services.prefs.setBoolPref(this.RECENTLY_BOOKMARKED_PREF, true);
+  },
 
-    if (aState == "invalid") {
-      this.star.setAttribute("disabled", "true");
-      this.broadcaster.setAttribute("stardisabled", "true");
-      this.broadcaster.removeAttribute("starred");
-      this.broadcaster.setAttribute("buttontooltiptext", "");
-    }
-    else {
-      this.star.removeAttribute("disabled");
-      this.broadcaster.removeAttribute("stardisabled");
-      this._updateStar();
-    }
+  hideRecentlyBookmarked() {
+    Services.prefs.setBoolPref(this.RECENTLY_BOOKMARKED_PREF, false);
   },
 
   _updateCustomizationState: function BUI__updateCustomizationState() {
     let placement = CustomizableUI.getPlacementOfWidget(this.BOOKMARK_BUTTON_ID);
     this._currentAreaType = placement && CustomizableUI.getAreaType(placement.area);
   },
 
   _uninitView: function BUI__uninitView() {
@@ -1541,21 +1589,16 @@ var BookmarkingUI = {
     this._uri = gBrowser.currentURI;
     this._itemIds = [];
 
     if (this._pendingStmt) {
       this._pendingStmt.cancel();
       delete this._pendingStmt;
     }
 
-    // We can load about:blank before the actual page, but there is no point in handling that page.
-    if (isBlankPageURL(this._uri.spec)) {
-      return;
-    }
-
     this._pendingStmt = PlacesUtils.asyncGetBookmarkIds(this._uri, (aItemIds, aURI) => {
       // Safety check that the bookmarked URI equals the tracked one.
       if (!aURI.equals(this._uri)) {
         Components.utils.reportError("BookmarkingUI did not receive current URI");
         return;
       }
 
       // It's possible that onItemAdded gets called before the async statement
@@ -1620,17 +1663,17 @@ var BookmarkingUI = {
 
   onMainMenuPopupShowing: function BUI_onMainMenuPopupShowing(event) {
     // Don't handle events for submenus.
     if (event.target != event.currentTarget)
       return;
 
     this._updateBookmarkPageMenuItem();
     PlacesCommandHook.updateBookmarkAllTabsCommand();
-    this._updateRecentBookmarks(document.getElementById("menu_recentBookmarks"));
+    this._initRecentBookmarks(document.getElementById("menu_recentBookmarks"));
   },
 
   _showBookmarkedNotification: function BUI_showBookmarkedNotification() {
     function getCenteringTransformForRects(rectToPosition, referenceRect) {
       let topDiff = referenceRect.top - rectToPosition.top;
       let leftDiff = referenceRect.left - rectToPosition.left;
       let heightDiff = referenceRect.height - rectToPosition.height;
       let widthDiff = referenceRect.width - rectToPosition.width;
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -2468,17 +2468,16 @@ function UpdateUrlbarSearchSplitterState
 function UpdatePageProxyState()
 {
   if (gURLBar && gURLBar.value != gLastValidURLStr)
     SetPageProxyState("invalid");
 }
 
 function SetPageProxyState(aState)
 {
-  BookmarkingUI.onPageProxyStateChanged(aState);
   if (!gURLBar)
     return;
 
   gURLBar.setAttribute("pageproxystate", aState);
 
   // the page proxy state is set to valid via OnLocationChange, which
   // gets called when we switch tabs.
   if (aState == "valid") {
--- a/browser/base/content/browser.xul
+++ b/browser/base/content/browser.xul
@@ -378,17 +378,45 @@
                onpopuphiding="if (event.target != this)
                                 return;
                               gContextMenu.hiding();
                               gContextMenu = null;
                               updateEditUIVisibility();">
 #include browser-context.inc
     </menupopup>
 
-    <menupopup id="placesContext"/>
+    <menupopup id="placesContext">
+      <menuseparator id="placesContext_recentlyBookmarkedSeparator"
+                     ignoreitem="true"
+                     ordinal="2"
+                     hidden="true"/>
+      <menuitem id="placesContext_hideRecentlyBookmarked"
+                label="&hideRecentlyBookmarked.label;"
+                accesskey="&hideRecentlyBookmarked.accesskey;"
+                oncommand="BookmarkingUI.hideRecentlyBookmarked();"
+                closemenu="single"
+                ignoreitem="true"
+                ordinal="2"
+                hidden="true"/>
+      <menuitem id="placesContext_showRecentlyBookmarked"
+                label="&showRecentlyBookmarked.label;"
+                accesskey="&showRecentlyBookmarked.accesskey;"
+                oncommand="BookmarkingUI.showRecentlyBookmarked();"
+                closemenu="single"
+                ignoreitem="true"
+                ordinal="2"
+                hidden="true"/>
+    </menupopup>
+
+    <menupopup id="hideRecentlyBookmarked">
+      <menuitem label="&hideRecentlyBookmarked.label;"
+                accesskey="&hideRecentlyBookmarked.accesskey;"
+                oncommand="BookmarkingUI.hideRecentlyBookmarked();"
+                closemenu="single"/>
+    </menupopup>
 
     <panel id="ctrlTab-panel" class="KUI-panel" hidden="true" norestorefocus="true" level="top">
       <hbox>
         <button class="ctrlTab-preview" flex="1"/>
         <button class="ctrlTab-preview" flex="1"/>
         <button class="ctrlTab-preview" flex="1"/>
         <button class="ctrlTab-preview" flex="1"/>
         <button class="ctrlTab-preview" flex="1"/>
--- a/browser/base/content/content.js
+++ b/browser/base/content/content.js
@@ -1014,16 +1014,17 @@ var PageInfoListener = {
     let docInfo = {};
     docInfo.title = document.title;
     docInfo.location = document.location.toString();
     docInfo.referrer = document.referrer;
     docInfo.compatMode = document.compatMode;
     docInfo.contentType = document.contentType;
     docInfo.characterSet = document.characterSet;
     docInfo.lastModified = document.lastModified;
+    docInfo.principal = document.nodePrincipal;
 
     let documentURIObject = {};
     documentURIObject.spec = document.documentURIObject.spec;
     documentURIObject.originCharset = document.documentURIObject.originCharset;
     docInfo.documentURIObject = documentURIObject;
 
     docInfo.isContentWindowPrivate = PrivateBrowsingUtils.isContentWindowPrivate(content);
 
--- a/browser/base/content/nsContextMenu.js
+++ b/browser/base/content/nsContextMenu.js
@@ -286,17 +286,16 @@ nsContextMenu.prototype = {
   initMiscItems: function CM_initMiscItems() {
     // Use "Bookmark This Link" if on a link.
     let bookmarkPage = document.getElementById("context-bookmarkpage");
     this.showItem(bookmarkPage,
                   !(this.isContentSelected || this.onTextInput || this.onLink ||
                     this.onImage || this.onVideo || this.onAudio || this.onSocial ||
                     this.onCanvas));
     bookmarkPage.setAttribute("tooltiptext", bookmarkPage.getAttribute("buttontooltiptext"));
-    bookmarkPage.disabled = bookmarkPage.hasAttribute("stardisabled");
 
     this.showItem("context-bookmarklink", (this.onLink && !this.onMailtoLink &&
                                            !this.onSocial) || this.onPlainTextLink);
     this.showItem("context-keywordfield",
                   this.onTextInput && this.onKeywordField);
     this.showItem("frame", this.inFrame);
 
     let showSearchSelect = (this.isTextSelected || this.onLink) && !this.onImage;
--- a/browser/base/content/pageinfo/pageInfo.js
+++ b/browser/base/content/pageinfo/pageInfo.js
@@ -370,29 +370,30 @@ function loadPageInfo(frameOuterWindowID
   // Get initial pageInfoData needed to display the general, feeds, permission and security tabs.
   mm.addMessageListener("PageInfo:data", function onmessage(message) {
     mm.removeMessageListener("PageInfo:data", onmessage);
     pageInfoData = message.data;
     let docInfo = pageInfoData.docInfo;
     let windowInfo = pageInfoData.windowInfo;
     let uri = makeURI(docInfo.documentURIObject.spec,
                       docInfo.documentURIObject.originCharset);
+    let principal = docInfo.principal;
     gDocInfo = docInfo;
 
     gImageElement = pageInfoData.imageInfo;
 
     var titleFormat = windowInfo.isTopWindow ? "pageInfo.page.title"
                                              : "pageInfo.frame.title";
     document.title = gBundle.getFormattedString(titleFormat, [docInfo.location]);
 
     document.getElementById("main-window").setAttribute("relatedUrl", docInfo.location);
 
     makeGeneralTab(pageInfoData.metaViewRows, docInfo);
     initFeedTab(pageInfoData.feeds);
-    onLoadPermission(uri);
+    onLoadPermission(uri, principal);
     securityOnLoad(uri, windowInfo);
   });
 
   // Get the media elements from content script to setup the media tab.
   mm.addMessageListener("PageInfo:mediaData", function onmessage(message) {
     // Page info window was closed.
     if (window.closed) {
       mm.removeMessageListener("PageInfo:mediaData", onmessage);
--- a/browser/base/content/pageinfo/permissions.js
+++ b/browser/base/content/pageinfo/permissions.js
@@ -3,16 +3,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 Components.utils.import("resource:///modules/SitePermissions.jsm");
 Components.utils.import("resource://gre/modules/BrowserUtils.jsm");
 
 const nsIQuotaManagerService = Components.interfaces.nsIQuotaManagerService;
 
 var gPermURI;
+var gPermPrincipal;
 var gUsageRequest;
 
 var gPermissions = SitePermissions.listPermissions();
 gPermissions.push("plugins");
 
 var permissionObserver = {
   observe: function (aSubject, aTopic, aData)
   {
@@ -23,21 +24,22 @@ var permissionObserver = {
           initRow(permission.type);
         else if (permission.type.startsWith("plugin"))
           setPluginsRadioState();
       }
     }
   }
 };
 
-function onLoadPermission(uri)
+function onLoadPermission(uri, principal)
 {
   var permTab = document.getElementById("permTab");
   if (SitePermissions.isSupportedURI(uri)) {
     gPermURI = uri;
+    gPermPrincipal = principal;
     var hostText = document.getElementById("hostText");
     hostText.value = gPermURI.prePath;
 
     for (var i of gPermissions)
       initRow(i);
     var os = Components.classes["@mozilla.org/observer-service;1"]
                        .getService(Components.interfaces.nsIObserverService);
     os.addObserver(permissionObserver, "perm-changed", false);
@@ -184,40 +186,33 @@ function initIndexedDBRow()
   let row = document.getElementById("perm-indexedDB-row");
   let extras = document.getElementById("perm-indexedDB-extras");
 
   row.appendChild(extras);
 
   var quotaManagerService =
     Components.classes["@mozilla.org/dom/quota-manager-service;1"]
               .getService(nsIQuotaManagerService);
-  let principal = Components.classes["@mozilla.org/scriptsecuritymanager;1"]
-                            .getService(Components.interfaces.nsIScriptSecurityManager)
-                            .createCodebasePrincipal(gPermURI, {});
   gUsageRequest =
-    quotaManagerService.getUsageForPrincipal(principal,
+    quotaManagerService.getUsageForPrincipal(gPermPrincipal,
                                              onIndexedDBUsageCallback);
 
   var status = document.getElementById("indexedDBStatus");
   var button = document.getElementById("indexedDBClear");
 
   status.value = "";
   status.setAttribute("hidden", "true");
   button.setAttribute("hidden", "true");
 }
 
 function onIndexedDBClear()
 {
-  let principal = Components.classes["@mozilla.org/scriptsecuritymanager;1"]
-                            .getService(Components.interfaces.nsIScriptSecurityManager)
-                            .createCodebasePrincipal(gPermURI, {});
-
   Components.classes["@mozilla.org/dom/quota-manager-service;1"]
             .getService(nsIQuotaManagerService)
-            .clearStoragesForPrincipal(principal);
+            .clearStoragesForPrincipal(gPermPrincipal);
 
   Components.classes["@mozilla.org/serviceworkers/manager;1"]
             .getService(Components.interfaces.nsIServiceWorkerManager)
             .removeAndPropagate(gPermURI.host);
 
   SitePermissions.remove(gPermURI, "indexedDB");
   initIndexedDBRow();
 }
--- a/browser/base/content/test/general/browser_aboutCertError.js
+++ b/browser/base/content/test/general/browser_aboutCertError.js
@@ -319,17 +319,17 @@ add_task(function* checkAdvancedDetailsF
     return {
       divDisplay: content.getComputedStyle(div).display,
       text: text.textContent,
       securityInfoAsString: serializedSecurityInfo
     };
   });
   isnot(message.divDisplay, "none", "Debug information is visible");
   ok(message.text.includes(badStsUri.spec), "Correct URL found");
-  ok(message.text.includes("requested domain name does not match the server's certificate"),
+  ok(message.text.includes("requested domain name does not match the server\u2019s certificate"),
      "Correct error message found");
   ok(message.text.includes("HTTP Strict Transport Security: false"),
      "Correct HSTS value found");
   ok(message.text.includes("HTTP Public Key Pinning: true"),
      "Correct HPKP value found");
   let certChain = getCertChain(message.securityInfoAsString);
   ok(message.text.includes(certChain), "Found certificate chain");
 
--- a/browser/base/content/test/general/browser_misused_characters_in_strings.js
+++ b/browser/base/content/test/general/browser_misused_characters_in_strings.js
@@ -1,168 +1,244 @@
-/* Any copyright is dedicated to the Public Domain.
- * http://creativecommons.org/publicdomain/zero/1.0/ */
-
-/* This list allows pre-existing or 'unfixable' issues to remain, while we
- * detect newly occurring issues in shipping files. It is a list of objects
- * specifying conditions under which an error should be ignored.
- *
- * As each issue is found in the whitelist, it is removed from the list. At
- * the end of the test, there is an assertion that all items have been
- * removed from the whitelist, thus ensuring there are no stale entries. */
-let gWhitelist = [{
-    file: "search.properties",
-    key: "searchForSomethingWith",
-    type: "single-quote"
-  }, {
-    file: "browser.dtd",
-    key: "social.activated.description",
-    type: "single-quote"
-  }, {
-    file: "netError.dtd",
-    key: "certerror.introPara",
-    type: "single-quote"
-  }, {
-    file: "netError.dtd",
-    key: "weakCryptoAdvanced.longDesc",
-    type: "single-quote"
-  }, {
-    file: "netError.dtd",
-    key: "weakCryptoAdvanced.override",
-    type: "single-quote"
-  }, {
-    file: "netError.dtd",
-    key: "inadequateSecurityError.longDesc",
-    type: "single-quote"
-  }, {
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/* This list allows pre-existing or 'unfixable' issues to remain, while we
+ * detect newly occurring issues in shipping files. It is a list of objects
+ * specifying conditions under which an error should be ignored.
+ *
+ * As each issue is found in the whitelist, it is removed from the list. At
+ * the end of the test, there is an assertion that all items have been
+ * removed from the whitelist, thus ensuring there are no stale entries. */
+let gWhitelist = [{
+    file: "search.properties",
+    key: "searchForSomethingWith",
+    type: "single-quote"
+  }, {
+    file: "browser.dtd",
+    key: "social.activated.description",
+    type: "single-quote"
+  }, {
+    file: "netError.dtd",
+    key: "certerror.introPara",
+    type: "single-quote"
+  }, {
+    file: "netError.dtd",
+    key: "weakCryptoAdvanced.longDesc",
+    type: "single-quote"
+  }, {
+    file: "netError.dtd",
+    key: "weakCryptoAdvanced.override",
+    type: "single-quote"
+  }, {
+    file: "netError.dtd",
+    key: "inadequateSecurityError.longDesc",
+    type: "single-quote"
+  }, {
     file: "netError.dtd",
     key: "certerror.wrongSystemTime",
     type: "single-quote"
   }, {
-    file: "phishing-afterload-warning-message.dtd",
-    key: "safeb.blocked.malwarePage.shortDesc",
-    type: "single-quote"
-  }, {
-    file: "phishing-afterload-warning-message.dtd",
-    key: "safeb.blocked.unwantedPage.shortDesc",
-    type: "single-quote"
-  }, {
-    file: "phishing-afterload-warning-message.dtd",
-    key: "safeb.blocked.phishingPage.shortDesc2",
-    type: "single-quote"
-  }, {
-    file: "phishing-afterload-warning-message.dtd",
-    key: "safeb.blocked.forbiddenPage.shortDesc2",
-    type: "single-quote"
-  }
-];
-
-var moduleLocation = gTestPath.replace(/\/[^\/]*$/i, "/parsingTestHelpers.jsm");
-var {generateURIsFromDirTree} = Cu.import(moduleLocation, {});
-
-/**
- * Check if an error should be ignored due to matching one of the whitelist
- * objects defined in gWhitelist.
- *
- * @param filepath The URI spec of the locale file
- * @param key The key of the entity that is being checked
- * @param type The type of error that has been found
- * @return true if the error should be ignored, false otherwise.
- */
-function ignoredError(filepath, key, type) {
-  for (let index in gWhitelist) {
-    let whitelistItem = gWhitelist[index];
-    if (filepath.endsWith(whitelistItem.file) &&
-        key == whitelistItem.key &&
-        type == whitelistItem.type) {
-      gWhitelist.splice(index, 1);
-      return true;
-    }
-  }
-  return false;
-}
-
-function fetchFile(uri) {
-  return new Promise((resolve, reject) => {
-    let xhr = new XMLHttpRequest();
-    xhr.open("GET", uri, true);
-    xhr.onreadystatechange = function() {
-      if (this.readyState != this.DONE) {
-        return;
-      }
-      try {
-        resolve(this.responseText);
-      } catch (ex) {
-        ok(false, `Script error reading ${uri}: ${ex}`);
-        resolve("");
-      }
-    };
-    xhr.onerror = error => {
-      ok(false, `XHR error reading ${uri}: ${error}`);
-      resolve("");
-    };
-    xhr.send(null);
-  });
-}
-
-function testForError(filepath, key, str, pattern, type, helpText) {
-  if (str.match(pattern) &&
-      !ignoredError(filepath, key, type)) {
-    ok(false, `${filepath} with key=${key} has a misused ${type}. ${helpText}`);
-  }
-}
-
-function testForErrors(filepath, key, str) {
-  testForError(filepath, key, str, /\w'\w/, "apostrophe", "Strings with apostrophes should use foo\u2019s instead of foo's.");
-  testForError(filepath, key, str, /\w\u2018\w/, "incorrect-apostrophe", "Strings with apostrophes should use foo\u2019s instead of foo\u2018s.");
-  testForError(filepath, key, str, /'.+'/, "single-quote", "Single-quoted strings should use Unicode \u2018foo\u2019 instead of 'foo'.");
-  testForError(filepath, key, str, /"/, "double-quote", "Double-quoted strings should use Unicode \u201cfoo\u201d instead of \"foo\".");
-  testForError(filepath, key, str, /\.\.\./, "ellipsis", "Strings with an ellipsis should use the Unicode \u2026 character instead of three periods.");
-}
-
-add_task(function* checkAllTheProperties() {
-  let appDir = Services.dirsvc.get("XCurProcD", Ci.nsIFile);
-  // This asynchronously produces a list of URLs (sadly, mostly sync on our
-  // test infrastructure because it runs against jarfiles there, and
-  // our zipreader APIs are all sync)
-  let uris = yield generateURIsFromDirTree(appDir, [".properties"]);
-  ok(uris.length, `Found ${uris.length} .properties files to scan for misused characters`);
-
-  for (let uri of uris) {
-    let bundle = new StringBundle(uri.spec);
-    let entities = bundle.getAll();
-    for (let entity of entities) {
-      testForErrors(uri.spec, entity.key, entity.value);
-    }
-  }
-});
-
-var checkDTD = Task.async(function* (aURISpec) {
-  let rawContents = yield fetchFile(aURISpec);
-  // The regular expression below is adapted from:
-  // https://hg.mozilla.org/mozilla-central/file/68c0b7d6f16ce5bb023e08050102b5f2fe4aacd8/python/compare-locales/compare_locales/parser.py#l233
-  let entities = rawContents.match(/<!ENTITY\s+([\w\.]*)\s+("[^"]*"|'[^']*')\s*>/g);
-  for (let entity of entities) {
-    let [, key, str] = entity.match(/<!ENTITY\s+([\w\.]*)\s+("[^"]*"|'[^']*')\s*>/);
-    // The matched string includes the enclosing quotation marks,
-    // we need to slice them off.
-    str = str.slice(1, -1);
-    testForErrors(aURISpec, key, str);
-  }
-});
-
-add_task(function* checkAllTheDTDs() {
-  let appDir = Services.dirsvc.get("XCurProcD", Ci.nsIFile);
-  let uris = yield generateURIsFromDirTree(appDir, [".dtd"]);
-  ok(uris.length, `Found ${uris.length} .dtd files to scan for misused characters`);
-  for (let uri of uris) {
-    yield checkDTD(uri.spec);
-  }
-
-  // This support DTD file supplies a string with a newline to make sure
-  // the regex in checkDTD works correctly for that case.
-  let dtdLocation = gTestPath.replace(/\/[^\/]*$/i, "/bug1262648_string_with_newlines.dtd");
-  yield checkDTD(dtdLocation);
-});
-
-add_task(function* ensureWhiteListIsEmpty() {
-  is(gWhitelist.length, 0, "No remaining whitelist entries exist");
-});
+    file: "phishing-afterload-warning-message.dtd",
+    key: "safeb.blocked.malwarePage.shortDesc",
+    type: "single-quote"
+  }, {
+    file: "phishing-afterload-warning-message.dtd",
+    key: "safeb.blocked.unwantedPage.shortDesc",
+    type: "single-quote"
+  }, {
+    file: "phishing-afterload-warning-message.dtd",
+    key: "safeb.blocked.phishingPage.shortDesc2",
+    type: "single-quote"
+  }, {
+    file: "phishing-afterload-warning-message.dtd",
+    key: "safeb.blocked.forbiddenPage.shortDesc2",
+    type: "single-quote"
+  }, {
+    file: "mathfont.properties",
+    key: "operator.\\u002E\\u002E\\u002E.postfix",
+    type: "ellipsis"
+  }, {
+    file: "layout_errors.properties",
+    key: "ImageMapRectBoundsError",
+    type: "double-quote"
+  }, {
+    file: "layout_errors.properties",
+    key: "ImageMapCircleWrongNumberOfCoords",
+    type: "double-quote"
+  }, {
+    file: "layout_errors.properties",
+    key: "ImageMapCircleNegativeRadius",
+    type: "double-quote"
+  }, {
+    file: "layout_errors.properties",
+    key: "ImageMapPolyWrongNumberOfCoords",
+    type: "double-quote"
+  }, {
+    file: "layout_errors.properties",
+    key: "ImageMapPolyOddNumberOfCoords",
+    type: "double-quote"
+  }, {
+    file: "xbl.properties",
+    key: "CommandNotInChrome",
+    type: "double-quote"
+  }, {
+    file: "dom.properties",
+    key: "PatternAttributeCompileFailure",
+    type: "single-quote"
+  }, {
+    file: "pipnss.properties",
+    key: "certErrorMismatchSingle2",
+    type: "double-quote"
+  }, {
+    file: "pipnss.properties",
+    key: "certErrorCodePrefix2",
+    type: "double-quote"
+  }, {
+    file: "aboutSupport.dtd",
+    key: "aboutSupport.pageSubtitle",
+    type: "single-quote"
+  }, {
+    file: "aboutSupport.dtd",
+    key: "aboutSupport.userJSDescription",
+    type: "single-quote"
+  }, {
+    file: "netError.dtd",
+    key: "inadequateSecurityError.longDesc",
+    type: "single-quote"
+  }, {
+    file: "netErrorApp.dtd",
+    key: "securityOverride.warningContent",
+    type: "single-quote"
+  }, {
+    file: "sync.properties",
+    key: "client.name2",
+    type: "apostrophe"
+  }
+];
+
+var moduleLocation = gTestPath.replace(/\/[^\/]*$/i, "/parsingTestHelpers.jsm");
+var {generateURIsFromDirTree} = Cu.import(moduleLocation, {});
+
+/**
+ * Check if an error should be ignored due to matching one of the whitelist
+ * objects defined in gWhitelist.
+ *
+ * @param filepath The URI spec of the locale file
+ * @param key The key of the entity that is being checked
+ * @param type The type of error that has been found
+ * @return true if the error should be ignored, false otherwise.
+ */
+function ignoredError(filepath, key, type) {
+  for (let index in gWhitelist) {
+    let whitelistItem = gWhitelist[index];
+    if (filepath.endsWith(whitelistItem.file) &&
+        key == whitelistItem.key &&
+        type == whitelistItem.type) {
+      gWhitelist.splice(index, 1);
+      return true;
+    }
+  }
+  return false;
+}
+
+function fetchFile(uri) {
+  return new Promise((resolve, reject) => {
+    let xhr = new XMLHttpRequest();
+    xhr.open("GET", uri, true);
+    xhr.onreadystatechange = function() {
+      if (this.readyState != this.DONE) {
+        return;
+      }
+      try {
+        resolve(this.responseText);
+      } catch (ex) {
+        ok(false, `Script error reading ${uri}: ${ex}`);
+        resolve("");
+      }
+    };
+    xhr.onerror = error => {
+      ok(false, `XHR error reading ${uri}: ${error}`);
+      resolve("");
+    };
+    xhr.send(null);
+  });
+}
+
+function testForError(filepath, key, str, pattern, type, helpText) {
+  if (str.match(pattern) &&
+      !ignoredError(filepath, key, type)) {
+    ok(false, `${filepath} with key=${key} has a misused ${type}. ${helpText}`);
+  }
+}
+
+function testForErrors(filepath, key, str) {
+  testForError(filepath, key, str, /\w'\w/, "apostrophe", "Strings with apostrophes should use foo\u2019s instead of foo's.");
+  testForError(filepath, key, str, /\w\u2018\w/, "incorrect-apostrophe", "Strings with apostrophes should use foo\u2019s instead of foo\u2018s.");
+  testForError(filepath, key, str, /'.+'/, "single-quote", "Single-quoted strings should use Unicode \u2018foo\u2019 instead of 'foo'.");
+  testForError(filepath, key, str, /"/, "double-quote", "Double-quoted strings should use Unicode \u201cfoo\u201d instead of \"foo\".");
+  testForError(filepath, key, str, /\.\.\./, "ellipsis", "Strings with an ellipsis should use the Unicode \u2026 character instead of three periods.");
+}
+
+function* getAllTheFiles(extension) {
+  let appDirGreD = Services.dirsvc.get("GreD", Ci.nsIFile);
+  let appDirXCurProcD = Services.dirsvc.get("XCurProcD", Ci.nsIFile);
+  if (appDirGreD.contains(appDirXCurProcD)) {
+    return yield generateURIsFromDirTree(appDirGreD, [extension]);
+  }
+  if (appDirXCurProcD.contains(appDirGreD)) {
+    return yield generateURIsFromDirTree(appDirXCurProcD, [extension]);
+  }
+  let urisGreD = yield generateURIsFromDirTree(appDirGreD, [extension]);
+  let urisXCurProcD = yield generateURIsFromDirTree(appDirXCurProcD, [extension]);
+  return Array.from(new Set(urisGreD.concat(appDirXCurProcD)));
+}
+
+add_task(function* checkAllTheProperties() {
+  // This asynchronously produces a list of URLs (sadly, mostly sync on our
+  // test infrastructure because it runs against jarfiles there, and
+  // our zipreader APIs are all sync)
+  let uris = yield getAllTheFiles(".properties");
+  ok(uris.length, `Found ${uris.length} .properties files to scan for misused characters`);
+
+  for (let uri of uris) {
+    let bundle = new StringBundle(uri.spec);
+    let entities = bundle.getAll();
+    for (let entity of entities) {
+      testForErrors(uri.spec, entity.key, entity.value);
+    }
+  }
+});
+
+var checkDTD = Task.async(function* (aURISpec) {
+  let rawContents = yield fetchFile(aURISpec);
+  // The regular expression below is adapted from:
+  // https://hg.mozilla.org/mozilla-central/file/68c0b7d6f16ce5bb023e08050102b5f2fe4aacd8/python/compare-locales/compare_locales/parser.py#l233
+  let entities = rawContents.match(/<!ENTITY\s+([\w\.]*)\s+("[^"]*"|'[^']*')\s*>/g);
+  if (!entities) {
+    // Some files, such as requestAutocomplete.dtd, have no entities defined.
+    return;
+  }
+  for (let entity of entities) {
+    let [, key, str] = entity.match(/<!ENTITY\s+([\w\.]*)\s+("[^"]*"|'[^']*')\s*>/);
+    // The matched string includes the enclosing quotation marks,
+    // we need to slice them off.
+    str = str.slice(1, -1);
+    testForErrors(aURISpec, key, str);
+  }
+});
+
+add_task(function* checkAllTheDTDs() {
+  let uris = yield getAllTheFiles(".dtd");
+  ok(uris.length, `Found ${uris.length} .dtd files to scan for misused characters`);
+  for (let uri of uris) {
+    yield checkDTD(uri.spec);
+  }
+
+  // This support DTD file supplies a string with a newline to make sure
+  // the regex in checkDTD works correctly for that case.
+  let dtdLocation = gTestPath.replace(/\/[^\/]*$/i, "/bug1262648_string_with_newlines.dtd");
+  yield checkDTD(dtdLocation);
+});
+
+add_task(function* ensureWhiteListIsEmpty() {
+  is(gWhitelist.length, 0, "No remaining whitelist entries exist");
+});
--- a/browser/base/content/test/general/browser_visibleTabs_bookmarkAllPages.js
+++ b/browser/base/content/test/general/browser_visibleTabs_bookmarkAllPages.js
@@ -15,17 +15,17 @@ function test() {
 
     gBrowser.showOnlyTheseTabs([tabTwo]);
 
     is(gBrowser.visibleTabs.length, 1, "Only one tab is visible");
 
     let uris = PlacesCommandHook.uniqueCurrentPages;
     is(uris.length, 1, "Only one uri is returned");
 
-    is(uris[0].spec, tabTwo.linkedBrowser.currentURI.spec, "It's the correct URI");
+    is(uris[0].uri.spec, tabTwo.linkedBrowser.currentURI.spec, "It's the correct URI");
 
     gBrowser.removeTab(tabOne);
     gBrowser.removeTab(tabTwo);
     Array.forEach(gBrowser.tabs, function(tab) {
       gBrowser.showTab(tab);
     });
 
     finish();
--- a/browser/base/content/urlbarBindings.xml
+++ b/browser/base/content/urlbarBindings.xml
@@ -129,16 +129,22 @@ file, You can obtain one at http://mozil
         this.inputField.removeEventListener("underflow", this, false);
       ]]></destructor>
 
       <field name="_value">""</field>
       <field name="gotResultForCurrentQuery">false</field>
       <field name="handleEnterWhenGotResult">false</field>
 
       <!--
+        For performance reasons we want to limit the size of the text runs we
+        build and show to the user.
+      -->
+      <field name="textRunsMaxLen">255</field>
+
+      <!--
         onBeforeValueGet is called by the base-binding's .value getter.
         It can return an object with a "value" property, to override the
         return value of the getter.
       -->
       <method name="onBeforeValueGet">
         <body><![CDATA[
           return {value: this._value};
         ]]></body>
--- a/browser/branding/aurora/pref/firefox-branding.js
+++ b/browser/branding/aurora/pref/firefox-branding.js
@@ -8,18 +8,18 @@ pref("startup.homepage_override_url", ""
 pref("startup.homepage_welcome_url", "https://www.mozilla.org/%LOCALE%/firefox/%VERSION%/firstrun/");
 pref("startup.homepage_welcome_url.additional", "");
 // The time interval between checks for a new version (in seconds)
 pref("app.update.interval", 28800); // 8 hours
 // The time interval between the downloading of mar file chunks in the
 // background (in seconds)
 // 0 means "download everything at once"
 pref("app.update.download.backgroundInterval", 0);
-// Give the user x seconds to react before showing the big UI. default=168 hours
-pref("app.update.promptWaitTime", 604800);
+// Give the user x seconds to react before showing the big UI. default=184 hours
+pref("app.update.promptWaitTime", 691200);
 // URL user can browse to manually if for some reason all update installation
 // attempts fail.
 pref("app.update.url.manual", "https://www.mozilla.org/firefox/aurora/");
 // A default value for the "More information about this update" link
 // supplied in the "An update is available" page of the update wizard.
 pref("app.update.url.details", "https://www.mozilla.org/firefox/aurora/");
 
 // The number of days a binary is permitted to be old
--- a/browser/branding/official/pref/firefox-branding.js
+++ b/browser/branding/official/pref/firefox-branding.js
@@ -5,18 +5,18 @@
 pref("startup.homepage_override_url", "");
 pref("startup.homepage_welcome_url", "https://www.mozilla.org/%LOCALE%/firefox/%VERSION%/firstrun/");
 pref("startup.homepage_welcome_url.additional", "https://www.mozilla.org/%LOCALE%/firefox/%VERSION%/firstrun/learnmore/");
 // Interval: Time between checks for a new version (in seconds)
 pref("app.update.interval", 43200); // 12 hours
 // The time interval between the downloading of mar file chunks in the
 // background (in seconds)
 pref("app.update.download.backgroundInterval", 60);
-// Give the user x seconds to react before showing the big UI. default=48 hours
-pref("app.update.promptWaitTime", 172800);
+// Give the user x seconds to react before showing the big UI. default=184 hours
+pref("app.update.promptWaitTime", 691200);
 // URL user can browse to manually if for some reason all update installation
 // attempts fail.
 pref("app.update.url.manual", "https://www.mozilla.org/firefox/");
 // A default value for the "More information about this update" link
 // supplied in the "An update is available" page of the update wizard.
 pref("app.update.url.details", "https://www.mozilla.org/%LOCALE%/firefox/notes");
 
 // The number of days a binary is permitted to be old
--- a/browser/components/places/content/bookmarkProperties.js
+++ b/browser/components/places/content/bookmarkProperties.js
@@ -552,20 +552,25 @@ var BookmarkPropertiesPanel = {
 
   /**
    * Returns a childItems-transactions array representing the URIList with
    * which the dialog has been opened.
    */
   _getTransactionsForURIList: function BPP__getTransactionsForURIList() {
     var transactions = [];
     for (let uri of this._URIs) {
-      let title = this._getURITitleFromHistory(uri);
-      let createTxn = new PlacesCreateBookmarkTransaction(uri, -1,
-                                                          PlacesUtils.bookmarks.DEFAULT_INDEX,
-                                                          title);
+      // uri should be an object in the form { url, title }. Though add-ons
+      // could still use the legacy form, where it's an nsIURI.
+      let [_uri, _title] = uri instanceof Ci.nsIURI ?
+        [uri, this._getURITitleFromHistory(uri)] : [uri.uri, uri.title];
+
+      let createTxn =
+        new PlacesCreateBookmarkTransaction(_uri, -1,
+                                            PlacesUtils.bookmarks.DEFAULT_INDEX,
+                                            _title);
       transactions.push(createTxn);
     }
     return transactions;
   },
 
   /**
    * Returns a transaction for creating a new folder item representing the
    * various fields and opening arguments of the dialog.
--- a/browser/components/places/content/controller.js
+++ b/browser/components/places/content/controller.js
@@ -555,16 +555,18 @@ PlacesController.prototype = {
     }
     return anyMatched;
   },
 
   /**
    * Detects information (meta-data rules) about the current selection in the
    * view (see _buildSelectionMetadata) and sets the visibility state for each
    * of the menu-items in the given popup with the following rules applied:
+   *  0) The "ignoreitem" attribute may be set to "true" for this code not to
+   *     handle that menuitem.
    *  1) The "selectiontype" attribute may be set on a menu-item to "single"
    *     if the menu-item should be visible only if there is a single node
    *     selected, or to "multiple" if the menu-item should be visible only if
    *     multiple nodes are selected, or to "none" if the menuitems should be
    *     visible for if there are no selected nodes, or to a |-separated
    *     combination of these.
    *     If the attribute is not set or set to an invalid value, the menu-item
    *     may be visible irrespective of the selection.
@@ -596,16 +598,19 @@ PlacesController.prototype = {
     var ip = this._view.insertionPoint;
     var noIp = !ip || ip.isTag;
 
     var separator = null;
     var visibleItemsBeforeSep = false;
     var usableItemCount = 0;
     for (var i = 0; i < aPopup.childNodes.length; ++i) {
       var item = aPopup.childNodes[i];
+      if (item.getAttribute("ignoreitem") == "true") {
+        continue;
+      }
       if (item.localName != "menuseparator") {
         // We allow pasting into tag containers, so special case that.
         var hideIfNoIP = item.getAttribute("hideifnoinsertionpoint") == "true" &&
                          noIp && !(ip && ip.isTag && item.id == "placesContext_paste");
         var hideIfPrivate = item.getAttribute("hideifprivatebrowsing") == "true" &&
                             PrivateBrowsingUtils.isWindowPrivate(window);
         var shouldHideItem = hideIfNoIP || hideIfPrivate ||
                              !this._shouldShowMenuItem(item, metadata);
new file mode 100644
--- /dev/null
+++ b/browser/components/places/tests/browser/bookmark_dummy_1.html
@@ -0,0 +1,9 @@
+<html>
+<head>
+<title>Bookmark Dummy 1</title>
+<meta http-equiv="Content-Type" content="text/html;charset=utf-8"></meta>
+</head>
+<body>
+<p>Bookmark Dummy 1</p>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/browser/components/places/tests/browser/bookmark_dummy_2.html
@@ -0,0 +1,9 @@
+<html>
+<head>
+<title>Bookmark Dummy 2</title>
+<meta http-equiv="Content-Type" content="text/html;charset=utf-8"></meta>
+</head>
+<body>
+<p>Bookmark Dummy 2</p>
+</body>
+</html>
--- a/browser/components/places/tests/browser/browser.ini
+++ b/browser/components/places/tests/browser/browser.ini
@@ -46,8 +46,12 @@ support-files =
 [browser_library_views_liveupdate.js]
 [browser_markPageAsFollowedLink.js]
 [browser_sidebarpanels_click.js]
 skip-if = true # temporarily disabled for breaking the treeview - bug 658744
 [browser_sort_in_library.js]
 [browser_toolbar_migration.js]
 [browser_toolbarbutton_menu_context.js]
 [browser_views_liveupdate.js]
+[browser_bookmark_all_tabs.js]
+support-files =
+  bookmark_dummy_1.html
+  bookmark_dummy_2.html
new file mode 100644
--- /dev/null
+++ b/browser/components/places/tests/browser/browser_bookmark_all_tabs.js
@@ -0,0 +1,37 @@
+/**
+ * Test for Bug 446171 - Name field of bookmarks saved via 'Bookmark All Tabs'
+ * has '(null)' value if history is disabled or just in private browsing mode
+ */
+"use strict"
+
+add_task(function* () {
+  const BASE_URL = "http://example.org/browser/browser/components/places/tests/browser/";
+  const TEST_PAGES = [
+    BASE_URL + "bookmark_dummy_1.html",
+    BASE_URL + "bookmark_dummy_2.html",
+    BASE_URL + "bookmark_dummy_1.html"
+  ];
+
+  function promiseAddTab(url) {
+    return BrowserTestUtils.openNewForegroundTab(gBrowser, url);
+  }
+
+  let tabs = yield Promise.all(TEST_PAGES.map(promiseAddTab));
+
+  let URIs = PlacesCommandHook.uniqueCurrentPages;
+  is(URIs.length, 3, "Only unique pages are returned");
+
+  Assert.deepEqual(URIs.map(URI => URI.uri.spec), [
+    "about:blank",
+    BASE_URL + "bookmark_dummy_1.html",
+    BASE_URL + "bookmark_dummy_2.html"
+  ], "Correct URIs are returned");
+
+  Assert.deepEqual(URIs.map(URI => URI.title), [
+    "", "Bookmark Dummy 1", "Bookmark Dummy 2"
+  ], "Correct titles are returned");
+
+  registerCleanupFunction(function* () {
+    yield Promise.all(tabs.map(BrowserTestUtils.removeTab));
+  });
+});
--- a/browser/components/preferences/in-content/advanced.js
+++ b/browser/components/preferences/in-content/advanced.js
@@ -84,16 +84,18 @@ var gAdvancedPane = {
                      gAdvancedPane.updateWritePrefs);
     setEventListener("showUpdateHistory", "command",
                      gAdvancedPane.showUpdates);
 #endif
     setEventListener("viewCertificatesButton", "command",
                      gAdvancedPane.showCertificates);
     setEventListener("viewSecurityDevicesButton", "command",
                      gAdvancedPane.showSecurityDevices);
+    setEventListener("cacheSize", "change",
+                     gAdvancedPane.updateCacheSizePref);
 
 #ifdef MOZ_WIDGET_GTK
     // GTK tabbox' allow the scroll wheel to change the selected tab,
     // but we don't want this behavior for the in-content preferences.
     let tabsElement = document.getElementById("tabsElement");
     tabsElement.addEventListener("DOMMouseScroll", event => {
       event.stopPropagation();
     }, true);
--- a/browser/components/preferences/in-content/advanced.xul
+++ b/browser/components/preferences/in-content/advanced.xul
@@ -275,17 +275,16 @@
                     accesskey="&overrideSmartCacheSize.accesskey;"/>
         </hbox>
         <hbox align="center" class="indent">
           <label id="useCacheBefore" control="cacheSize"
                  accesskey="&limitCacheSizeBefore.accesskey;">
             &limitCacheSizeBefore.label;
           </label>
           <textbox id="cacheSize" type="number" size="4" max="1024"
-                   onchange="gAdvancedPane.updateCacheSizePref();"
                    aria-labelledby="useCacheBefore cacheSize useCacheAfter"/>
           <label id="useCacheAfter" flex="1">&limitCacheSizeAfter.label;</label>
         </hbox>
       </groupbox>
 
       <!-- Offline apps -->
       <groupbox id="offlineGroup">
         <caption><label>&offlineStorage2.label;</label></caption>
--- a/browser/components/search/test/browser.ini
+++ b/browser/components/search/test/browser.ini
@@ -21,16 +21,17 @@ support-files =
 [browser_bing.js]
 [browser_bing_behavior.js]
 [browser_contextmenu.js]
 [browser_contextSearchTabPosition.js]
 skip-if = os == "mac" # bug 967013
 [browser_eBay.js]
 [browser_eBay_behavior.js]
 [browser_google.js]
+[browser_google_codes.js]
 [browser_google_behavior.js]
 [browser_healthreport.js]
 [browser_hiddenOneOffs_cleanup.js]
 [browser_hiddenOneOffs_diacritics.js]
 [browser_oneOffHeader.js]
 [browser_private_search_perwindowpb.js]
 [browser_yahoo.js]
 [browser_yahoo_behavior.js]
--- a/browser/components/search/test/browser_bing.js
+++ b/browser/components/search/test/browser_bing.js
@@ -13,17 +13,17 @@ function test() {
   let engine = Services.search.getEngineByName("Bing");
   ok(engine, "Bing");
 
   let base = "https://www.bing.com/search?q=foo&pc=MOZI";
   let url;
 
   // Test search URLs (including purposes).
   url = engine.getSubmission("foo").uri.spec;
-  is(url, base, "Check search URL for 'foo'");
+  is(url, base + "&form=MOZSBR", "Check search URL for 'foo'");
   url = engine.getSubmission("foo", null, "contextmenu").uri.spec;
   is(url, base + "&form=MOZCON", "Check context menu search URL for 'foo'");
   url = engine.getSubmission("foo", null, "keyword").uri.spec;
   is(url, base + "&form=MOZLBR", "Check keyword search URL for 'foo'");
   url = engine.getSubmission("foo", null, "searchbar").uri.spec;
   is(url, base + "&form=MOZSBR", "Check search bar search URL for 'foo'");
   url = engine.getSubmission("foo", null, "homepage").uri.spec;
   is(url, base + "&form=MOZSPG", "Check homepage search URL for 'foo'");
@@ -34,17 +34,17 @@ function test() {
   url = engine.getSubmission("foo", "application/x-suggestions+json").uri.spec;
   is(url, "https://www.bing.com/osjson.aspx?query=foo&form=OSDJAS&language=" + getLocale(), "Check search suggestion URL for 'foo'");
 
   // Check all other engine properties.
   const EXPECTED_ENGINE = {
     name: "Bing",
     alias: null,
     description: "Bing. Search by Microsoft.",
-    searchForm: "https://www.bing.com/search?q=&pc=MOZI",
+    searchForm: "https://www.bing.com/search?q=&pc=MOZI&form=MOZSBR",
     hidden: false,
     wrappedJSObject: {
       queryCharset: "UTF-8",
       "_iconURL": "data:image/x-icon;base64,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",
       _urls : [
         {
           type: "application/x-suggestions+json",
           method: "GET",
--- a/browser/components/search/test/browser_bing_behavior.js
+++ b/browser/components/search/test/browser_bing_behavior.js
@@ -18,17 +18,17 @@ function test() {
   Services.search.currentEngine = engine;
   engine.alias = "b";
 
   let base = "https://www.bing.com/search?q=foo&pc=MOZI";
   let url;
 
   // Test search URLs (including purposes).
   url = engine.getSubmission("foo").uri.spec;
-  is(url, base, "Check search URL for 'foo'");
+  is(url, base + "&form=MOZSBR", "Check search URL for 'foo'");
 
   waitForExplicitFinish();
 
   var gCurrTest;
   var gTests = [
     {
       name: "context menu search",
       searchURL: base + "&form=MOZCON",
new file mode 100644
--- /dev/null
+++ b/browser/components/search/test/browser_google_codes.js
@@ -0,0 +1,161 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const kUrlPref = "geoSpecificDefaults.url";
+const BROWSER_SEARCH_PREF = "browser.search.";
+
+var originalGeoURL;
+
+/**
+ * Clean the profile of any cache file left from a previous run.
+ * Returns a boolean indicating if the cache file existed.
+ */
+function removeCacheFile()
+{
+  const CACHE_FILENAME = "search.json.mozlz4";
+
+  let file =  Services.dirsvc.get("ProfD", Ci.nsIFile);
+  file.append(CACHE_FILENAME);
+  if (file.exists()) {
+    file.remove(false);
+    return true;
+  }
+  return false;
+}
+
+/**
+ * Returns a promise that is resolved when an observer notification from the
+ * search service fires with the specified data.
+ *
+ * @param aExpectedData
+ *        The value the observer notification sends that causes us to resolve
+ *        the promise.
+ */
+function waitForSearchNotification(aExpectedData, aCallback) {
+  const SEARCH_SERVICE_TOPIC = "browser-search-service";
+  Services.obs.addObserver(function observer(aSubject, aTopic, aData) {
+    if (aData != aExpectedData)
+      return;
+
+    Services.obs.removeObserver(observer, SEARCH_SERVICE_TOPIC);
+    aCallback();
+  }, SEARCH_SERVICE_TOPIC, false);
+}
+
+function asyncInit() {
+  return new Promise(resolve => {
+    Services.search.init(function() {
+      ok(Services.search.isInitialized, "search service should be initialized");
+      resolve();
+    });
+  });
+}
+
+function asyncReInit() {
+  const kLocalePref = "general.useragent.locale";
+
+  let promise = new Promise(resolve => {
+    waitForSearchNotification("reinit-complete", resolve);
+  });
+
+  Services.search.QueryInterface(Ci.nsIObserver)
+          .observe(null, "nsPref:changed", kLocalePref);
+
+  return promise;
+}
+
+let gEngineCount;
+
+add_task(function* preparation() {
+  // ContentSearch is interferring with our async re-initializations of the
+  // search service: once _initServicePromise has resolved, it will access
+  // the search service, thus causing unpredictable behavior due to
+  // synchronous initializations of the service.
+  let originalContentSearchPromise = ContentSearch._initServicePromise;
+  ContentSearch._initServicePromise = new Promise(resolve => {
+    registerCleanupFunction(() => {
+      ContentSearch._initServicePromise = originalContentSearchPromise;
+      resolve();
+    });
+  });
+
+  yield asyncInit();
+  gEngineCount = Services.search.getVisibleEngines().length;
+
+  waitForSearchNotification("uninit-complete", () => {
+    // Verify search service is not initialized
+    is(Services.search.isInitialized, false, "Search service should NOT be initialized");
+
+    removeCacheFile();
+
+    // Geo specific defaults won't be fetched if there's no country code.
+    Services.prefs.setCharPref("browser.search.geoip.url",
+                               'data:application/json,{"country_code": "US"}');
+
+    Services.prefs.setBoolPref("browser.search.geoSpecificDefaults", true);
+
+    // Make the new Google the only engine
+    originalGeoURL = Services.prefs.getCharPref(BROWSER_SEARCH_PREF + kUrlPref);
+    let geoUrl = 'data:application/json,{"interval": 31536000, "settings": {"searchDefault": "Google", "visibleDefaultEngines": ["google"]}}';
+    Services.prefs.getDefaultBranch(BROWSER_SEARCH_PREF).setCharPref(kUrlPref, geoUrl);
+  });
+
+  yield asyncReInit();
+
+  yield new Promise(resolve => {
+    waitForSearchNotification("write-cache-to-disk-complete", resolve);
+  });
+});
+
+add_task(function* tests() {
+  let engines = Services.search.getEngines();
+  is(Services.search.currentEngine.name, "Google", "Search engine should be Google");
+  is(engines.length, 1, "There should only be one engine");
+
+  let engine = Services.search.getEngineByName("Google");
+  ok(engine, "Google");
+
+  let base = "https://www.google.com/search?q=foo&ie=utf-8&oe=utf-8&client=firefox-b";
+
+  // Keyword uses a slightly different code
+  let keywordBase = base + "-ab";
+
+  let url;
+
+  // Test search URLs (including purposes).
+  url = engine.getSubmission("foo", null, "contextmenu").uri.spec;
+  is(url, base, "Check context menu search URL for 'foo'");
+  url = engine.getSubmission("foo", null, "keyword").uri.spec;
+  is(url, keywordBase, "Check keyword search URL for 'foo'");
+  url = engine.getSubmission("foo", null, "searchbar").uri.spec;
+  is(url, base, "Check search bar search URL for 'foo'");
+  url = engine.getSubmission("foo", null, "homepage").uri.spec;
+  is(url, base, "Check homepage search URL for 'foo'");
+  url = engine.getSubmission("foo", null, "newtab").uri.spec;
+  is(url, base, "Check newtab search URL for 'foo'");
+  url = engine.getSubmission("foo", null, "system").uri.spec;
+  is(url, base, "Check system search URL for 'foo'");
+});
+
+
+add_task(function* cleanup() {
+  waitForSearchNotification("uninit-complete", () => {
+    // Verify search service is not initialized
+    is(Services.search.isInitialized, false,
+       "Search service should NOT be initialized");
+    removeCacheFile();
+
+    Services.prefs.clearUserPref("browser.search.geoip.url");
+
+    // We can't clear the pref because it's set to false by testing/profiles/prefs_general.js
+    Services.prefs.setBoolPref("browser.search.geoSpecificDefaults", false);
+
+    Services.prefs.getDefaultBranch(BROWSER_SEARCH_PREF).setCharPref(kUrlPref, originalGeoURL);
+  });
+
+  yield asyncReInit();
+  is(gEngineCount, Services.search.getVisibleEngines().length,
+     "correct engine count after cleanup");
+});
--- a/browser/components/search/test/browser_yahoo.js
+++ b/browser/components/search/test/browser_yahoo.js
@@ -13,28 +13,42 @@ function test() {
   let engine = Services.search.getEngineByName("Yahoo");
   ok(engine, "Yahoo");
 
   let base = "https://search.yahoo.com/yhs/search?p=foo&ei=UTF-8&hspart=mozilla";
   let url;
 
   // Test search URLs (including purposes).
   url = engine.getSubmission("foo").uri.spec;
-  is(url, base, "Check search URL for 'foo'");
+  is(url, base + "&hsimp=yhs-001", "Check search URL for 'foo'");
+  url = engine.getSubmission("foo", null, "searchbar").uri.spec;
+  is(url, base + "&hsimp=yhs-001", "Check search bar search URL for 'foo'");
+  url = engine.getSubmission("foo", null, "keyword").uri.spec;
+  is(url, base + "&hsimp=yhs-002", "Check keyword search URL for 'foo'");
+  url = engine.getSubmission("foo", null, "homepage").uri.spec;
+  is(url, base + "&hsimp=yhs-003", "Check homepage search URL for 'foo'");
+  url = engine.getSubmission("foo", null, "newtab").uri.spec;
+  is(url, base + "&hsimp=yhs-004", "Check newtab search URL for 'foo'");
+  url = engine.getSubmission("foo", null, "contextmenu").uri.spec;
+  is(url, base + "&hsimp=yhs-005", "Check context menu search URL for 'foo'");
+  url = engine.getSubmission("foo", null, "system").uri.spec;
+  is(url, base + "&hsimp=yhs-007", "Check system search URL for 'foo'");
+  url = engine.getSubmission("foo", null, "invalid").uri.spec;
+  is(url, base + "&hsimp=yhs-001", "Check invalid URL for 'foo'");
 
   // Check search suggestion URL.
   url = engine.getSubmission("foo", "application/x-suggestions+json").uri.spec;
   is(url, "https://search.yahoo.com/sugg/ff?output=fxjson&appid=ffd&command=foo", "Check search suggestion URL for 'foo'");
 
   // Check all other engine properties.
   const EXPECTED_ENGINE = {
     name: "Yahoo",
     alias: null,
     description: "Yahoo Search",
-    searchForm: "https://search.yahoo.com/yhs/search?p=&ei=UTF-8&hspart=mozilla",
+    searchForm: "https://search.yahoo.com/yhs/search?p=&ei=UTF-8&hspart=mozilla&hsimp=yhs-001",
     hidden: false,
     wrappedJSObject: {
       queryCharset: "UTF-8",
       "_iconURL": "data:image/x-icon;base64,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",
       _urls : [
         {
           type: "application/x-suggestions+json",
           method: "GET",
--- a/browser/components/search/test/browser_yahoo_behavior.js
+++ b/browser/components/search/test/browser_yahoo_behavior.js
@@ -18,17 +18,17 @@ function test() {
   Services.search.currentEngine = engine;
   engine.alias = "y";
 
   let base = "https://search.yahoo.com/yhs/search?p=foo&ei=UTF-8&hspart=mozilla";
   let url;
 
   // Test search URLs (including purposes).
   url = engine.getSubmission("foo").uri.spec;
-  is(url, base, "Check search URL for 'foo'");
+  is(url, base + "&hsimp=yhs-001", "Check search URL for 'foo'");
 
   waitForExplicitFinish();
 
   var gCurrTest;
   var gTests = [
     {
       name: "context menu search",
       searchURL: base + "&hsimp=yhs-005",
--- a/browser/components/sessionstore/test/browser.ini
+++ b/browser/components/sessionstore/test/browser.ini
@@ -152,17 +152,17 @@ skip-if = true
 [browser_500328.js]
 [browser_514751.js]
 [browser_522375.js]
 [browser_522545.js]
 [browser_524745.js]
 [browser_528776.js]
 [browser_579868.js]
 [browser_579879.js]
-skip-if = os == "linux" && e10s && debug # Bug 1234404 - Intermittent | Found an unexpected tab at the end of test run about:blank
+skip-if = (os == 'linux' && e10s && (debug||asan)) # Bug 1234404
 [browser_581937.js]
 [browser_586147.js]
 [browser_586068-apptabs.js]
 [browser_586068-apptabs_ondemand.js]
 [browser_586068-browser_state_interrupted.js]
 [browser_586068-cascade.js]
 [browser_586068-multi_window.js]
 [browser_586068-reload.js]
--- a/browser/experiments/test/xpcshell/test_conditions.js
+++ b/browser/experiments/test/xpcshell/test_conditions.js
@@ -1,16 +1,17 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
 
 Cu.import("resource:///modules/experiments/Experiments.jsm");
 Cu.import("resource://gre/modules/TelemetryController.jsm", this);
+Cu.import("resource://gre/modules/TelemetrySession.jsm", this);
 
 const FILE_MANIFEST            = "experiments.manifest";
 const SEC_IN_ONE_DAY = 24 * 60 * 60;
 const MS_IN_ONE_DAY  = SEC_IN_ONE_DAY * 1000;
 
 var gProfileDir = null;
 var gHttpServer = null;
 var gHttpRoot   = null;
@@ -47,17 +48,18 @@ function applicableFromManifestData(data
 function run_test() {
   run_next_test();
 }
 
 add_task(function* test_setup() {
   createAppInfo();
   gProfileDir = do_get_profile();
   startAddonManagerOnly();
-  yield TelemetryController.testSetup();
+  yield TelemetryController.setup();
+  yield TelemetrySession.setup();
   gPolicy = new Experiments.Policy();
 
   patchPolicy(gPolicy, {
     updatechannel: () => "nightly",
     locale: () => "en-US",
     random: () => 0.5,
   });
 
@@ -322,10 +324,10 @@ add_task(function* test_times() {
       + i + ": " + JSON.stringify([entry[2], entry[3]]));
     if (!applicable && entry[1]) {
       Assert.equal(reason, entry[1], "Experiment rejection reason should match for test " + i);
     }
   }
 });
 
 add_task(function* test_shutdown() {
-  yield TelemetryController.testShutdown();
+  yield TelemetrySession.shutdown(false);
 });
--- a/browser/extensions/e10srollout/bootstrap.js
+++ b/browser/extensions/e10srollout/bootstrap.js
@@ -4,20 +4,19 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
 
 Cu.import("resource://gre/modules/Preferences.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/UpdateUtils.jsm");
 
- // The amount of people to be part of e10s, in %
+ // The amount of people to be part of e10s
 const TEST_THRESHOLD = {
-  "beta"    : 50,
-  "release" : 0,
+  "beta"    : 0.5,  // 50%
 };
 
 const PREF_COHORT_SAMPLE       = "e10s.rollout.cohortSample";
 const PREF_COHORT_NAME         = "e10s.rollout.cohort";
 const PREF_E10S_OPTED_IN       = "browser.tabs.remote.autostart";
 const PREF_E10S_FORCE_ENABLED  = "browser.tabs.remote.force-enable";
 const PREF_E10S_FORCE_DISABLED = "browser.tabs.remote.force-disable";
 const PREF_TOGGLE_E10S         = "browser.tabs.remote.autostart.2";
@@ -74,24 +73,33 @@ function defineCohort() {
 
 function shutdown(data, reason) {
 }
 
 function uninstall() {
 }
 
 function getUserSample() {
-  let existingVal = Preferences.get(PREF_COHORT_SAMPLE, undefined);
-  if (typeof(existingVal) == "number") {
-    return existingVal;
+  let prefValue = Preferences.get(PREF_COHORT_SAMPLE, undefined);
+  let value = 0.0;
+
+  if (typeof(prefValue) == "string") {
+    value = parseFloat(prefValue, 10);
+    return value;
   }
 
-  let val = Math.floor(Math.random() * 100);
-  Preferences.set(PREF_COHORT_SAMPLE, val);
-  return val;
+  if (typeof(prefValue) == "number") {
+    // convert old integer value
+    value = prefValue / 100;
+  } else {
+    value = Math.random();
+  }
+
+  Preferences.set(PREF_COHORT_SAMPLE, value.toString().substr(0, 8));
+  return value;
 }
 
 function setCohort(cohortName) {
   Preferences.set(PREF_COHORT_NAME, cohortName);
   try {
     if (Ci.nsICrashReporter) {
       Services.appinfo.QueryInterface(Ci.nsICrashReporter).annotateCrashReport("E10SCohort", cohortName);
     }
--- a/browser/extensions/moz.build
+++ b/browser/extensions/moz.build
@@ -4,9 +4,10 @@
 # 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/.
 
 DIRS += [
     'e10srollout',
     'loop',
     'pdfjs',
     'pocket',
+    'webcompat',
 ]
--- a/browser/extensions/pdfjs/README.mozilla
+++ b/browser/extensions/pdfjs/README.mozilla
@@ -1,3 +1,3 @@
 This is the pdf.js project output, https://github.com/mozilla/pdf.js
 
-Current extension version is: 1.4.258
+Current extension version is: 1.5.222
--- a/browser/extensions/pdfjs/content/build/pdf.js
+++ b/browser/extensions/pdfjs/content/build/pdf.js
@@ -23,18 +23,18 @@ define('pdfjs-dist/build/pdf', ['exports
     factory(exports);
   } else {
 factory((root.pdfjsDistBuildPdf = {}));
   }
 }(this, function (exports) {
   // Use strict in our context only - users might not want it
   'use strict';
 
-var pdfjsVersion = '1.4.258';
-var pdfjsBuild = '990150c';
+var pdfjsVersion = '1.5.222';
+var pdfjsBuild = 'd20002b';
 
   var pdfjsFilePath =
     typeof document !== 'undefined' && document.currentScript ?
       document.currentScript.src : null;
 
   var pdfjsLibs = {};
 
   (function pdfjsWrapper() {
@@ -2930,33 +2930,31 @@ var renderTextLayer = (function renderTe
       // Only build font string and set to context if different from last.
       if (fontSize !== lastFontSize || fontFamily !== lastFontFamily) {
         ctx.font = fontSize + ' ' + fontFamily;
         lastFontSize = fontSize;
         lastFontFamily = fontFamily;
       }
 
       var width = ctx.measureText(textDiv.textContent).width;
-      if (width > 0) {
-        textLayerFrag.appendChild(textDiv);
-        var transform;
-        if (textDiv.dataset.canvasWidth !== undefined) {
-          // Dataset values come of type string.
-          var textScale = textDiv.dataset.canvasWidth / width;
-          transform = 'scaleX(' + textScale + ')';
-        } else {
-          transform = '';
-        }
-        var rotation = textDiv.dataset.angle;
-        if (rotation) {
-          transform = 'rotate(' + rotation + 'deg) ' + transform;
-        }
-        if (transform) {
-          CustomStyle.setProp('transform' , textDiv, transform);
-        }
+      textLayerFrag.appendChild(textDiv);
+      var transform;
+      if (textDiv.dataset.canvasWidth !== undefined && width > 0) {
+        // Dataset values come of type string.
+        var textScale = textDiv.dataset.canvasWidth / width;
+        transform = 'scaleX(' + textScale + ')';
+      } else {
+        transform = '';
+      }
+      var rotation = textDiv.dataset.angle;
+      if (rotation) {
+        transform = 'rotate(' + rotation + 'deg) ' + transform;
+      }
+      if (transform) {
+        CustomStyle.setProp('transform' , textDiv, transform);
       }
     }
     capability.resolve();
   }
 
   /**
    * Text layer rendering task.
    *
@@ -7616,38 +7614,36 @@ var WorkerTransport = (function WorkerTr
         if (this.commonObjs.hasData(id)) {
           return;
         }
 
         switch (type) {
           case 'Font':
             var exportedData = data[2];
 
-            var font;
             if ('error' in exportedData) {
-              var error = exportedData.error;
-              warn('Error during font loading: ' + error);
-              this.commonObjs.resolve(id, error);
+              var exportedError = exportedData.error;
+              warn('Error during font loading: ' + exportedError);
+              this.commonObjs.resolve(id, exportedError);
               break;
-            } else {
-              var fontRegistry = null;
-              if (getDefaultSetting('pdfBug') && globalScope.FontInspector &&
-                  globalScope['FontInspector'].enabled) {
-                fontRegistry = {
-                  registerFont: function (font, url) {
-                    globalScope['FontInspector'].fontAdded(font, url);
-                  }
-                };
-              }
-              font = new FontFaceObject(exportedData, {
-                isEvalSuported: getDefaultSetting('isEvalSupported'),
-                disableFontFace: getDefaultSetting('disableFontFace'),
-                fontRegistry: fontRegistry
-              });
             }
+            var fontRegistry = null;
+            if (getDefaultSetting('pdfBug') && globalScope.FontInspector &&
+                globalScope['FontInspector'].enabled) {
+              fontRegistry = {
+                registerFont: function (font, url) {
+                  globalScope['FontInspector'].fontAdded(font, url);
+                }
+              };
+            }
+            var font = new FontFaceObject(exportedData, {
+              isEvalSuported: getDefaultSetting('isEvalSupported'),
+              disableFontFace: getDefaultSetting('disableFontFace'),
+              fontRegistry: fontRegistry
+            });
 
             this.fontLoader.bind(
               [font],
               function fontReady(fontObjs) {
                 this.commonObjs.resolve(id, font);
               }.bind(this)
             );
             break;
@@ -8445,18 +8441,16 @@ exports._UnsupportedManager = _Unsupport
   PDFJS.SVGGraphics = displaySVG.SVGGraphics;
 
   PDFJS.UnsupportedManager = displayAPI._UnsupportedManager;
 
   exports.globalScope = globalScope;
   exports.isWorker = isWorker;
   exports.PDFJS = globalScope.PDFJS;
 }));
-
-
   }).call(pdfjsLibs);
 
   exports.PDFJS = pdfjsLibs.pdfjsDisplayGlobal.PDFJS;
   exports.build = pdfjsLibs.pdfjsDisplayAPI.build;
   exports.version = pdfjsLibs.pdfjsDisplayAPI.version;
   exports.getDocument = pdfjsLibs.pdfjsDisplayAPI.getDocument;
   exports.PDFDataRangeTransport =
     pdfjsLibs.pdfjsDisplayAPI.PDFDataRangeTransport;
@@ -8478,9 +8472,8 @@ exports._UnsupportedManager = _Unsupport
   exports.removeNullCharacters = pdfjsLibs.pdfjsSharedUtil.removeNullCharacters;
   exports.shadow = pdfjsLibs.pdfjsSharedUtil.shadow;
   exports.createBlob = pdfjsLibs.pdfjsSharedUtil.createBlob;
   exports.getFilenameFromUrl =
     pdfjsLibs.pdfjsDisplayDOMUtils.getFilenameFromUrl;
   exports.addLinkAttributes = pdfjsLibs.pdfjsDisplayDOMUtils.addLinkAttributes;
 }));
 
-
--- a/browser/extensions/pdfjs/content/build/pdf.worker.js
+++ b/browser/extensions/pdfjs/content/build/pdf.worker.js
@@ -23,18 +23,18 @@ define('pdfjs-dist/build/pdf.worker', ['
     factory(exports);
   } else {
 factory((root.pdfjsDistBuildPdfWorker = {}));
   }
 }(this, function (exports) {
   // Use strict in our context only - users might not want it
   'use strict';
 
-var pdfjsVersion = '1.4.258';
-var pdfjsBuild = '990150c';
+var pdfjsVersion = '1.5.222';
+var pdfjsBuild = 'd20002b';
 
   var pdfjsFilePath =
     typeof document !== 'undefined' && document.currentScript ?
       document.currentScript.src : null;
 
   var pdfjsLibs = {};
 
   (function pdfjsWrapper() {
@@ -446,17 +446,17 @@ exports.ArithmeticDecoder = ArithmeticDe
         var j;
         for (j = i - 1; j >= 0; --j) {
           if (types[j] !== 'ET') {
             break;
           }
           types[j] = 'EN';
         }
         // do after
-        for (j = i + 1; j < strLength; --j) {
+        for (j = i + 1; j < strLength; ++j) {
           if (types[j] !== 'ET') {
             break;
           }
           types[j] = 'EN';
         }
       }
     }
 
@@ -34151,35 +34151,35 @@ var XRef = (function XRefClosure() {
           }
 
           // Validate entry obj
           if (!isInt(entry.offset) || !isInt(entry.gen) ||
               !(entry.free || entry.uncompressed)) {
             error('Invalid entry in XRef subsection: ' + first + ', ' + count);
           }
 
+          // The first xref table entry, i.e. obj 0, should be free. Attempting
+          // to adjust an incorrect first obj # (fixes issue 3248 and 7229).
+          if (i === 0 && entry.free && first === 1) {
+            first = 0;
+          }
+
           if (!this.entries[i + first]) {
             this.entries[i + first] = entry;
           }
         }
 
         tableState.entryNum = 0;
         tableState.streamPos = stream.pos;
         tableState.parserBuf1 = parser.buf1;
         tableState.parserBuf2 = parser.buf2;
         delete tableState.firstEntryNum;
         delete tableState.entryCount;
       }
 
-      // Per issue 3248: hp scanners generate bad XRef
-      if (first === 1 && this.entries[1] && this.entries[1].free) {
-        // shifting the entries
-        this.entries.shift();
-      }
-
       // Sanity check: as per spec, first object must be free
       if (this.entries[0] && !this.entries[0].free) {
         error('Invalid XRef table: unexpected first object');
       }
       return obj;
     },
 
     processXRefStream: function XRef_processXRefStream(stream) {
@@ -41537,16 +41537,13 @@ if (typeof window === 'undefined' &&
     !(typeof module !== 'undefined' && module.require)) {
   initializeWorker();
 }
 
 exports.setPDFNetworkStreamClass = setPDFNetworkStreamClass;
 exports.WorkerTask = WorkerTask;
 exports.WorkerMessageHandler = WorkerMessageHandler;
 }));
-
-
   }).call(pdfjsLibs);
 
   exports.WorkerMessageHandler = pdfjsLibs.pdfjsCoreWorker.WorkerMessageHandler;
 }));
 
-
--- a/browser/extensions/pdfjs/content/web/debugger.js
+++ b/browser/extensions/pdfjs/content/web/debugger.js
@@ -526,17 +526,17 @@ var PDFBug = (function PDFBugClosure() {
           var indexA = ids.indexOf(a.id);
           indexA = indexA < 0 ? tools.length : indexA;
           var indexB = ids.indexOf(b.id);
           indexB = indexB < 0 ? tools.length : indexB;
           return indexA - indexB;
         });
       }
     },
-    init: function init(pdfjsLib) {
+    init: function init(pdfjsLib, container) {
       /*
        * Basic Layout:
        * PDFBug
        *  Controls
        *  Panels
        *    Panel
        *    Panel
        *    ...
@@ -547,17 +547,16 @@ var PDFBug = (function PDFBugClosure() {
       var controls = document.createElement('div');
       controls.setAttribute('class', 'controls');
       ui.appendChild(controls);
 
       var panels = document.createElement('div');
       panels.setAttribute('class', 'panels');
       ui.appendChild(panels);
 
-      var container = document.getElementById('viewerContainer');
       container.appendChild(ui);
       container.style.right = panelWidth + 'px';
 
       // Initialize all the debugging tools.
       var tools = this.tools;
       var self = this;
       for (var i = 0; i < tools.length; ++i) {
         var tool = tools[i];
--- a/browser/extensions/pdfjs/content/web/l10n.js
+++ b/browser/extensions/pdfjs/content/web/l10n.js
@@ -1,19 +1,19 @@
-/* globals FirefoxCom */
 
 'use strict';
 
 // Small subset of the webL10n API by Fabien Cazenave for pdf.js extension.
 (function(window) {
   var gLanguage = '';
+  var gExternalLocalizerServices = null;
 
   // fetch an l10n objects
   function getL10nData(key) {
-    var response = FirefoxCom.requestSync('getStrings', key);
+    var response = gExternalLocalizerServices.getStrings(key);
     var data = JSON.parse(response);
     if (!data) {
       console.warn('[l10n] #' + key + ' missing for [' + gLanguage + ']');
     }
     return data;
   }
 
   // replace {{arguments}} with their values
@@ -89,26 +89,33 @@
     }
 
     // translate element itself if necessary
     if (element.dataset.l10nId) {
       translateElement(element);
     }
   }
 
-  window.addEventListener('DOMContentLoaded', function() {
-    gLanguage = FirefoxCom.requestSync('getLocale', null);
+  function translateDocument() {
+    gLanguage = gExternalLocalizerServices.getLocale();
 
     translateFragment();
 
     // fire a 'localized' DOM event
     var evtObject = document.createEvent('Event');
     evtObject.initEvent('localized', false, false);
     evtObject.language = gLanguage;
     window.dispatchEvent(evtObject);
+  }
+
+  window.addEventListener('DOMContentLoaded', function() {
+    if (gExternalLocalizerServices) {
+      translateDocument();
+    }
+    // ... else see setExternalLocalizerServices below
   });
 
   // Public API
   document.mozL10n = {
     // get a localized string
     get: translateString,
 
     // get the document language
@@ -123,12 +130,22 @@
       var rtlList = ['ar', 'he', 'fa', 'ps', 'ur'];
 
       // use the short language code for "full" codes like 'ar-sa' (issue 5440)
       var shortCode = gLanguage.split('-')[0];
 
       return (rtlList.indexOf(shortCode) >= 0) ? 'rtl' : 'ltr';
     },
 
+    setExternalLocalizerServices: function (externalLocalizerServices) {
+      gExternalLocalizerServices = externalLocalizerServices;
+
+      // ... in case if we missed DOMContentLoaded above.
+      if (window.document.readyState === 'interactive' ||
+          window.document.readyState === 'complete') {
+        translateDocument();
+      }
+    },
+
     // translate an element or document fragment
     translate: translateFragment
   };
 })(this);
--- a/browser/extensions/pdfjs/content/web/viewer.css
+++ b/browser/extensions/pdfjs/content/web/viewer.css
@@ -154,16 +154,25 @@
   background-color: white;
 }
 
 .pdfViewer.removePageBorders .page {
   margin: 0px auto 10px auto;
   border: none;
 }
 
+.pdfViewer.singlePageView {
+  display: inline-block;
+}
+
+.pdfViewer.singlePageView .page {
+  margin: 0;
+  border: none;
+}
+
 .pdfViewer .page canvas {
   margin: 0;
   display: block;
 }
 
 .pdfViewer .page .loadingIcon {
   position: absolute;
   display: block;
--- a/browser/extensions/pdfjs/content/web/viewer.js
+++ b/browser/extensions/pdfjs/content/web/viewer.js
@@ -231,51 +231,42 @@ var DEFAULT_URL = 'compressed.tracemonke
   }
 
   exports.GrabToPan = GrabToPan;
 }));
 
 
 (function (root, factory) {
   {
-    factory((root.pdfjsWebMozPrintCallbackPolyfill = {}));
-  }
-}(this, function (exports) {
-}));
-
-
-(function (root, factory) {
-  {
     factory((root.pdfjsWebOverlayManager = {}));
   }
 }(this, function (exports) {
 
 var OverlayManager = {
   overlays: {},
   active: null,
 
   /**
-   * @param {string} name The name of the overlay that is registered. This must
-   *                 be equal to the ID of the overlay's DOM element.
+   * @param {string} name The name of the overlay that is registered.
+   * @param {HTMLDivElement} element The overlay's DOM element.
    * @param {function} callerCloseMethod (optional) The method that, if present,
    *                   will call OverlayManager.close from the Object
    *                   registering the overlay. Access to this method is
    *                   necessary in order to run cleanup code when e.g.
    *                   the overlay is force closed. The default is null.
    * @param {boolean} canForceClose (optional) Indicates if opening the overlay
    *                  will close an active overlay. The default is false.
    * @returns {Promise} A promise that is resolved when the overlay has been
    *                    registered.
    */
-  register: function overlayManagerRegister(name,
+  register: function overlayManagerRegister(name, element,
                                             callerCloseMethod, canForceClose) {
     return new Promise(function (resolve) {
-      var element, container;
-      if (!name || !(element = document.getElementById(name)) ||
-          !(container = element.parentNode)) {
+      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: element,
                               container: container,
                               callerCloseMethod: (callerCloseMethod || null),
                               canForceClose: (canForceClose || false) };
@@ -376,459 +367,77 @@ var OverlayManager = {
 };
 
 exports.OverlayManager = OverlayManager;
 }));
 
 
 (function (root, factory) {
   {
-    factory((root.pdfjsWebPDFHistory = {}));
-  }
-}(this, function (exports) {
-
-  function PDFHistory(options) {
-    this.linkService = options.linkService;
-
-    this.initialized = false;
-    this.initialDestination = null;
-    this.initialBookmark = null;
-  }
-
-  PDFHistory.prototype = {
-    /**
-     * @param {string} fingerprint
-     * @param {IPDFLinkService} linkService
-     */
-    initialize: function pdfHistoryInitialize(fingerprint) {
-      this.initialized = true;
-      this.reInitialized = false;
-      this.allowHashChange = true;
-      this.historyUnlocked = true;
-      this.isViewerInPresentationMode = false;
-
-      this.previousHash = window.location.hash.substring(1);
-      this.currentBookmark = '';
-      this.currentPage = 0;
-      this.updatePreviousBookmark = false;
-      this.previousBookmark = '';
-      this.previousPage = 0;
-      this.nextHashParam = '';
-
-      this.fingerprint = fingerprint;
-      this.currentUid = this.uid = 0;
-      this.current = {};
-
-      var state = window.history.state;
-      if (this._isStateObjectDefined(state)) {
-        // This corresponds to navigating back to the document
-        // from another page in the browser history.
-        if (state.target.dest) {
-          this.initialDestination = state.target.dest;
-        } else {
-          this.initialBookmark = state.target.hash;
-        }
-        this.currentUid = state.uid;
-        this.uid = state.uid + 1;
-        this.current = state.target;
-      } else {
-        // This corresponds to the loading of a new document.
-        if (state && state.fingerprint &&
-          this.fingerprint !== state.fingerprint) {
-          // Reinitialize the browsing history when a new document
-          // is opened in the web viewer.
-          this.reInitialized = true;
-        }
-        this._pushOrReplaceState({fingerprint: this.fingerprint}, true);
-      }
-
-      var self = this;
-      window.addEventListener('popstate', function pdfHistoryPopstate(evt) {
-        if (!self.historyUnlocked) {
-          return;
-        }
-        if (evt.state) {
-          // Move back/forward in the history.
-          self._goTo(evt.state);
-          return;
-        }
-
-        // If the state is not set, then the user tried to navigate to a
-        // different hash by manually editing the URL and pressing Enter, or by
-        // clicking on an in-page link (e.g. the "current view" link).
-        // Save the current view state to the browser history.
-
-        // Note: In Firefox, history.null could also be null after an in-page
-        // navigation to the same URL, and without dispatching the popstate
-        // event: https://bugzilla.mozilla.org/show_bug.cgi?id=1183881
-
-        if (self.uid === 0) {
-          // Replace the previous state if it was not explicitly set.
-          var previousParams = (self.previousHash && self.currentBookmark &&
-            self.previousHash !== self.currentBookmark) ?
-            {hash: self.currentBookmark, page: self.currentPage} :
-            {page: 1};
-          replacePreviousHistoryState(previousParams, function() {
-            updateHistoryWithCurrentHash();
-          });
-        } else {
-          updateHistoryWithCurrentHash();
-        }
-      }, false);
-
-
-      function updateHistoryWithCurrentHash() {
-        self.previousHash = window.location.hash.slice(1);
-        self._pushToHistory({hash: self.previousHash}, false, true);
-        self._updatePreviousBookmark();
-      }
-
-      function replacePreviousHistoryState(params, callback) {
-        // To modify the previous history entry, the following happens:
-        // 1. history.back()
-        // 2. _pushToHistory, which calls history.replaceState( ... )
-        // 3. history.forward()
-        // Because a navigation via the history API does not immediately update
-        // the history state, the popstate event is used for synchronization.
-        self.historyUnlocked = false;
-
-        // Suppress the hashchange event to avoid side effects caused by
-        // navigating back and forward.
-        self.allowHashChange = false;
-        window.addEventListener('popstate', rewriteHistoryAfterBack);
-        history.back();
-
-        function rewriteHistoryAfterBack() {
-          window.removeEventListener('popstate', rewriteHistoryAfterBack);
-          window.addEventListener('popstate', rewriteHistoryAfterForward);
-          self._pushToHistory(params, false, true);
-          history.forward();
-        }
-        function rewriteHistoryAfterForward() {
-          window.removeEventListener('popstate', rewriteHistoryAfterForward);
-          self.allowHashChange = true;
-          self.historyUnlocked = true;
-          callback();
-        }
-      }
-
-      function pdfHistoryBeforeUnload() {
-        var previousParams = self._getPreviousParams(null, true);
-        if (previousParams) {
-          var replacePrevious = (!self.current.dest &&
-          self.current.hash !== self.previousHash);
-          self._pushToHistory(previousParams, false, replacePrevious);
-          self._updatePreviousBookmark();
-        }
-        // Remove the event listener when navigating away from the document,
-        // since 'beforeunload' prevents Firefox from caching the document.
-        window.removeEventListener('beforeunload', pdfHistoryBeforeUnload,
-                                   false);
-      }
-
-      window.addEventListener('beforeunload', pdfHistoryBeforeUnload, false);
-
-      window.addEventListener('pageshow', function pdfHistoryPageShow(evt) {
-        // If the entire viewer (including the PDF file) is cached in
-        // the browser, we need to reattach the 'beforeunload' event listener
-        // since the 'DOMContentLoaded' event is not fired on 'pageshow'.
-        window.addEventListener('beforeunload', pdfHistoryBeforeUnload, false);
-      }, false);
-
-      window.addEventListener('presentationmodechanged', function(e) {
-        self.isViewerInPresentationMode = !!e.detail.active;
-      });
-    },
-
-    clearHistoryState: function pdfHistory_clearHistoryState() {
-      this._pushOrReplaceState(null, true);
-    },
-
-    _isStateObjectDefined: function pdfHistory_isStateObjectDefined(state) {
-      return (state && state.uid >= 0 &&
-      state.fingerprint && this.fingerprint === state.fingerprint &&
-      state.target && state.target.hash) ? true : false;
-    },
-
-    _pushOrReplaceState: function pdfHistory_pushOrReplaceState(stateObj,
-                                                                replace) {
-      if (replace) {
-      window.history.replaceState(stateObj, '');
-      } else {
-      window.history.pushState(stateObj, '');
-      }
-    },
-
-    get isHashChangeUnlocked() {
-      if (!this.initialized) {
-        return true;
-      }
-      return this.allowHashChange;
-    },
-
-    _updatePreviousBookmark: function pdfHistory_updatePreviousBookmark() {
-      if (this.updatePreviousBookmark &&
-        this.currentBookmark && this.currentPage) {
-        this.previousBookmark = this.currentBookmark;
-        this.previousPage = this.currentPage;
-        this.updatePreviousBookmark = false;
-      }
-    },
-
-    updateCurrentBookmark: function pdfHistoryUpdateCurrentBookmark(bookmark,
-                                                                    pageNum) {
-      if (this.initialized) {
-        this.currentBookmark = bookmark.substring(1);
-        this.currentPage = pageNum | 0;
-        this._updatePreviousBookmark();
-      }
-    },
-
-    updateNextHashParam: function pdfHistoryUpdateNextHashParam(param) {
-      if (this.initialized) {
-        this.nextHashParam = param;
-      }
-    },
-
-    push: function pdfHistoryPush(params, isInitialBookmark) {
-      if (!(this.initialized && this.historyUnlocked)) {
-        return;
-      }
-      if (params.dest && !params.hash) {
-        params.hash = (this.current.hash && this.current.dest &&
-        this.current.dest === params.dest) ?
-          this.current.hash :
-          this.linkService.getDestinationHash(params.dest).split('#')[1];
-      }
-      if (params.page) {
-        params.page |= 0;
-      }
-      if (isInitialBookmark) {
-        var target = window.history.state.target;
-        if (!target) {
-          // Invoked when the user specifies an initial bookmark,
-          // thus setting initialBookmark, when the document is loaded.
-          this._pushToHistory(params, false);
-          this.previousHash = window.location.hash.substring(1);
-        }
-        this.updatePreviousBookmark = this.nextHashParam ? false : true;
-        if (target) {
-          // If the current document is reloaded,
-          // avoid creating duplicate entries in the history.
-          this._updatePreviousBookmark();
-        }
-        return;
-      }
-      if (this.nextHashParam) {
-        if (this.nextHashParam === params.hash) {
-          this.nextHashParam = null;
-          this.updatePreviousBookmark = true;
-          return;
-        } else {
-          this.nextHashParam = null;
-        }
-      }
-
-      if (params.hash) {
-        if (this.current.hash) {
-          if (this.current.hash !== params.hash) {
-            this._pushToHistory(params, true);
-          } else {
-            if (!this.current.page && params.page) {
-              this._pushToHistory(params, false, true);
-            }
-            this.updatePreviousBookmark = true;
-          }
-        } else {
-          this._pushToHistory(params, true);
-        }
-      } else if (this.current.page && params.page &&
-        this.current.page !== params.page) {
-        this._pushToHistory(params, true);
-      }
-    },
-
-    _getPreviousParams: function pdfHistory_getPreviousParams(onlyCheckPage,
-                                                              beforeUnload) {
-      if (!(this.currentBookmark && this.currentPage)) {
-        return null;
-      } else if (this.updatePreviousBookmark) {
-        this.updatePreviousBookmark = false;
-      }
-      if (this.uid > 0 && !(this.previousBookmark && this.previousPage)) {
-        // Prevent the history from getting stuck in the current state,
-        // effectively preventing the user from going back/forward in
-        // the history.
-        //
-        // This happens if the current position in the document didn't change
-        // when the history was previously updated. The reasons for this are
-        // either:
-        // 1. The current zoom value is such that the document does not need to,
-        //    or cannot, be scrolled to display the destination.
-        // 2. The previous destination is broken, and doesn't actally point to a
-        //    position within the document.
-        //    (This is either due to a bad PDF generator, or the user making a
-        //     mistake when entering a destination in the hash parameters.)
-        return null;
-      }
-      if ((!this.current.dest && !onlyCheckPage) || beforeUnload) {
-        if (this.previousBookmark === this.currentBookmark) {
-          return null;
-        }
-      } else if (this.current.page || onlyCheckPage) {
-        if (this.previousPage === this.currentPage) {
-          return null;
-        }
-      } else {
-        return null;
-      }
-      var params = {hash: this.currentBookmark, page: this.currentPage};
-      if (this.isViewerInPresentationMode) {
-        params.hash = null;
-      }
-      return params;
-    },
-
-    _stateObj: function pdfHistory_stateObj(params) {
-      return {fingerprint: this.fingerprint, uid: this.uid, target: params};
-    },
-
-    _pushToHistory: function pdfHistory_pushToHistory(params,
-                                                      addPrevious, overwrite) {
-      if (!this.initialized) {
-        return;
-      }
-      if (!params.hash && params.page) {
-        params.hash = ('page=' + params.page);
-      }
-      if (addPrevious && !overwrite) {
-        var previousParams = this._getPreviousParams();
-        if (previousParams) {
-          var replacePrevious = (!this.current.dest &&
-          this.current.hash !== this.previousHash);
-          this._pushToHistory(previousParams, false, replacePrevious);
-        }
-      }
-      this._pushOrReplaceState(this._stateObj(params),
-        (overwrite || this.uid === 0));
-      this.currentUid = this.uid++;
-      this.current = params;
-      this.updatePreviousBookmark = true;
-    },
-
-    _goTo: function pdfHistory_goTo(state) {
-      if (!(this.initialized && this.historyUnlocked &&
-        this._isStateObjectDefined(state))) {
-        return;
-      }
-      if (!this.reInitialized && state.uid < this.currentUid) {
-        var previousParams = this._getPreviousParams(true);
-        if (previousParams) {
-          this._pushToHistory(this.current, false);
-          this._pushToHistory(previousParams, false);
-          this.currentUid = state.uid;
-          window.history.back();
-          return;
-        }
-      }
-      this.historyUnlocked = false;
-
-      if (state.target.dest) {
-        this.linkService.navigateTo(state.target.dest);
-      } else {
-        this.linkService.setHash(state.target.hash);
-      }
-      this.currentUid = state.uid;
-      if (state.uid > this.uid) {
-        this.uid = state.uid;
-      }
-      this.current = state.target;
-      this.updatePreviousBookmark = true;
-
-      var currentHash = window.location.hash.substring(1);
-      if (this.previousHash !== currentHash) {
-        this.allowHashChange = false;
-      }
-      this.previousHash = currentHash;
-
-      this.historyUnlocked = true;
-    },
-
-    back: function pdfHistoryBack() {
-      this.go(-1);
-    },
-
-    forward: function pdfHistoryForward() {
-      this.go(1);
-    },
-
-    go: function pdfHistoryGo(direction) {
-      if (this.initialized && this.historyUnlocked) {
-        var state = window.history.state;
-        if (direction === -1 && state && state.uid > 0) {
-          window.history.back();
-        } else if (direction === 1 && state && state.uid < (this.uid - 1)) {
-          window.history.forward();
-        }
-      }
-    }
-  };
-
-  exports.PDFHistory = PDFHistory;
-}));
-
-
-(function (root, factory) {
-  {
     factory((root.pdfjsWebPDFPresentationMode = {}));
   }
 }(this, function (exports) {
 
 var DELAY_BEFORE_RESETTING_SWITCH_IN_PROGRESS = 1500; // in ms
 var DELAY_BEFORE_HIDING_CONTROLS = 3000; // in ms
 var ACTIVE_SELECTOR = 'pdfPresentationMode';
 var CONTROLS_SELECTOR = 'pdfPresentationModeControls';
 
 /**
  * @typedef {Object} PDFPresentationModeOptions
  * @property {HTMLDivElement} container - The container for the viewer element.
  * @property {HTMLDivElement} viewer - (optional) The viewer element.
  * @property {PDFViewer} pdfViewer - The document viewer.
+ * @property {EventBus} eventBus - The application event bus.
  * @property {Array} contextMenuItems - (optional) The menuitems that are added
  *   to the context menu in Presentation Mode.
  */
 
 /**
  * @class
  */
 var PDFPresentationMode = (function PDFPresentationModeClosure() {
   /**
    * @constructs PDFPresentationMode
    * @param {PDFPresentationModeOptions} options
    */
   function PDFPresentationMode(options) {
     this.container = options.container;
     this.viewer = options.viewer || options.container.firstElementChild;
     this.pdfViewer = options.pdfViewer;
+    this.eventBus = options.eventBus;
     var contextMenuItems = options.contextMenuItems || null;
 
     this.active = false;
     this.args = null;
     this.contextMenuOpen = false;
     this.mouseScrollTimeStamp = 0;
     this.mouseScrollDelta = 0;
 
     if (contextMenuItems) {
-      for (var i = 0, ii = contextMenuItems.length; i < ii; i++) {
-        var item = contextMenuItems[i];
-        item.element.addEventListener('click', function (handler) {
-          this.contextMenuOpen = false;
-          handler();
-        }.bind(this, item.handler));
-      }
+      contextMenuItems.contextFirstPage.addEventListener('click',
+          function PDFPresentationMode_contextFirstPageClick(e) {
+        this.contextMenuOpen = false;
+        this.eventBus.dispatch('firstpage');
+      }.bind(this));
+      contextMenuItems.contextLastPage.addEventListener('click',
+          function PDFPresentationMode_contextLastPageClick(e) {
+        this.contextMenuOpen = false;
+        this.eventBus.dispatch('lastpage');
+      }.bind(this));
+      contextMenuItems.contextPageRotateCw.addEventListener('click',
+          function PDFPresentationMode_contextPageRotateCwClick(e) {
+        this.contextMenuOpen = false;
+        this.eventBus.dispatch('rotatecw');
+      }.bind(this));
+      contextMenuItems.contextPageRotateCcw.addEventListener('click',
+          function PDFPresentationMode_contextPageRotateCcwClick(e) {
+        this.contextMenuOpen = false;
+        this.eventBus.dispatch('rotateccw');
+      }.bind(this));
     }
   }
 
   PDFPresentationMode.prototype = {
     /**
      * Request the browser to enter fullscreen mode.
      * @returns {boolean} Indicating if the request was successful.
      */
@@ -915,22 +524,21 @@ var PDFPresentationMode = (function PDFP
                 document.webkitIsFullScreen ||
                 document.msFullscreenElement);
     },
 
     /**
      * @private
      */
     _notifyStateChange: function PDFPresentationMode_notifyStateChange() {
-      var event = document.createEvent('CustomEvent');
-      event.initCustomEvent('presentationmodechanged', true, true, {
+      this.eventBus.dispatch('presentationmodechanged', {
+        source: this,
         active: this.active,
         switchInProgress: !!this.switchInProgress
       });
-      window.dispatchEvent(event);
     },
 
     /**
      * Used to initialize a timeout when requesting Presentation Mode,
      * i.e. when the browser is requested to enter fullscreen mode.
      * This timeout is used to prevent the current page from being scrolled
      * partially, or completely, out of view when entering Presentation Mode.
      * NOTE: This issue seems limited to certain zoom levels (e.g. page-width).
@@ -1475,17 +1083,16 @@ var Preferences = {
         }
       }
       return defaultValue;
     }.bind(this));
   }
 };
 
 
-
 exports.Preferences = Preferences;
 }));
 
 
 (function (root, factory) {
   {
     factory((root.pdfjsWebViewHistory = {}));
   }
@@ -1590,153 +1197,117 @@ exports.ViewHistory = ViewHistory;
     factory((root.pdfjsWebDownloadManager = {}), root.pdfjsWebPDFJS);
   }
 }(this, function (exports, pdfjsLib) {
 }));
 
 
 (function (root, factory) {
   {
-    factory((root.pdfjsWebFirefoxCom = {}), root.pdfjsWebPreferences,
-      root.pdfjsWebPDFJS);
-  }
-}(this, function (exports, preferences, pdfjsLib) {
+    factory((root.pdfjsWebHandTool = {}), root.pdfjsWebGrabToPan,
+      root.pdfjsWebPreferences);
+  }
+}(this, function (exports, grabToPan, preferences) {
+
+var GrabToPan = grabToPan.GrabToPan;
 var Preferences = preferences.Preferences;
 
-var FirefoxCom = (function FirefoxComClosure() {
-  return {
-    /**
-     * Creates an event that the extension is listening for and will
-     * synchronously respond to.
-     * NOTE: It is reccomended to use request() instead since one day we may not
-     * be able to synchronously reply.
-     * @param {String} action The action to trigger.
-     * @param {String} data Optional data to send.
-     * @return {*} The response.
-     */
-    requestSync: function(action, data) {
-      var request = document.createTextNode('');
-      document.documentElement.appendChild(request);
-
-      var sender = document.createEvent('CustomEvent');
-      sender.initCustomEvent('pdf.js.message', true, false,
-                             {action: action, data: data, sync: true});
-      request.dispatchEvent(sender);
-      var response = sender.detail.response;
-      document.documentElement.removeChild(request);
-      return response;
-    },
+/**
+ * @typedef {Object} HandToolOptions
+ * @property {HTMLDivElement} container - The document container.
+ * @property {EventBus} eventBus - The application event bus.
+ */
+
+/**
+ * @class
+ */
+var HandTool = (function HandToolClosure() {
+  /**
+   * @constructs HandTool
+   * @param {HandToolOptions} options
+   */
+  function HandTool(options) {
+    this.container = options.container;
+    this.eventBus = options.eventBus;
+
+    this.wasActive = false;
+
+    this.handTool = new GrabToPan({
+      element: this.container,
+      onActiveChanged: function(isActive) {
+        this.eventBus.dispatch('handtoolchanged', {isActive: isActive});
+      }.bind(this)
+    });
+
+    this.eventBus.on('togglehandtool', this.toggle.bind(this));
+
+    this.eventBus.on('localized', function (e) {
+      Preferences.get('enableHandToolOnLoad').then(function resolved(value) {
+        if (value) {
+          this.handTool.activate();
+        }
+      }.bind(this), function rejected(reason) {});
+    }.bind(this));
+
+    this.eventBus.on('presentationmodechanged', function (e) {
+      if (e.switchInProgress) {
+        return;
+      }
+      if (e.active) {
+        this.enterPresentationMode();
+      } else {
+        this.exitPresentationMode();
+      }
+    }.bind(this));
+  }
+
+  HandTool.prototype = {
     /**
-     * Creates an event that the extension is listening for and will
-     * asynchronously respond by calling the callback.
-     * @param {String} action The action to trigger.
-     * @param {String} data Optional data to send.
-     * @param {Function} callback Optional response callback that will be called
-     * with one data argument.
+     * @return {boolean}
      */
-    request: function(action, data, callback) {
-      var request = document.createTextNode('');
-      if (callback) {
-        document.addEventListener('pdf.js.response', function listener(event) {
-          var node = event.target;
-          var response = event.detail.response;
-
-          document.documentElement.removeChild(node);
-
-          document.removeEventListener('pdf.js.response', listener, false);
-          return callback(response);
-        }, false);
-      }
-      document.documentElement.appendChild(request);
-
-      var sender = document.createEvent('CustomEvent');
-      sender.initCustomEvent('pdf.js.message', true, false, {
-        action: action,
-        data: data,
-        sync: false,
-        responseExpected: !!callback
-      });
-      return request.dispatchEvent(sender);
+    get isActive() {
+      return !!this.handTool.active;
+    },
+
+    toggle: function HandTool_toggle() {
+      this.handTool.toggle();
+    },
+
+    enterPresentationMode: function HandTool_enterPresentationMode() {
+      if (this.isActive) {
+        this.wasActive = true;
+        this.handTool.deactivate();
+      }
+    },
+
+    exitPresentationMode: function HandTool_exitPresentationMode() {
+      if (this.wasActive) {
+        this.wasActive = false;
+        this.handTool.activate();
+      }
     }
   };
+
+  return HandTool;
 })();
 
-var DownloadManager = (function DownloadManagerClosure() {
-  function DownloadManager() {}
-
-  DownloadManager.prototype = {
-    downloadUrl: function DownloadManager_downloadUrl(url, filename) {
-      FirefoxCom.request('download', {
-        originalUrl: url,
-        filename: filename
-      });
-    },
-
-    downloadData: function DownloadManager_downloadData(data, filename,
-                                                        contentType) {
-      var blobUrl = pdfjsLib.createObjectURL(data, contentType, false);
-
-      FirefoxCom.request('download', {
-        blobUrl: blobUrl,
-        originalUrl: blobUrl,
-        filename: filename,
-        isAttachment: true
-      });
-    },
-
-    download: function DownloadManager_download(blob, url, filename) {
-      var blobUrl = window.URL.createObjectURL(blob);
-
-      FirefoxCom.request('download', {
-        blobUrl: blobUrl,
-        originalUrl: url,
-        filename: filename
-      },
-        function response(err) {
-          if (err && this.onerror) {
-            this.onerror(err);
-          }
-          window.URL.revokeObjectURL(blobUrl);
-        }.bind(this)
-      );
-    }
-  };
-
-  return DownloadManager;
-})();
-
-Preferences._writeToStorage = function (prefObj) {
-  return new Promise(function (resolve) {
-    FirefoxCom.request('setPreferences', prefObj, resolve);
-  });
-};
-
-Preferences._readFromStorage = function (prefObj) {
-  return new Promise(function (resolve) {
-    FirefoxCom.request('getPreferences', prefObj, function (prefStr) {
-      var readPrefs = JSON.parse(prefStr);
-      resolve(readPrefs);
-    });
-  });
-};
-
-exports.DownloadManager = DownloadManager;
-exports.FirefoxCom = FirefoxCom;
+exports.HandTool = HandTool;
 }));
 
 
 (function (root, factory) {
   {
     factory((root.pdfjsWebPDFAttachmentViewer = {}), root.pdfjsWebPDFJS);
   }
 }(this, function (exports, pdfjsLib) {
 
 /**
  * @typedef {Object} PDFAttachmentViewerOptions
  * @property {HTMLDivElement} container - The viewer element.
+ * @property {EventBus} eventBus - The application event bus.
  * @property {DownloadManager} downloadManager - The download manager.
  */
 
 /**
  * @typedef {Object} PDFAttachmentViewerRenderParameters
  * @property {Array|null} attachments - An array of attachment objects.
  */
 
@@ -1746,16 +1317,17 @@ exports.FirefoxCom = FirefoxCom;
 var PDFAttachmentViewer = (function PDFAttachmentViewerClosure() {
   /**
    * @constructs PDFAttachmentViewer
    * @param {PDFAttachmentViewerOptions} options
    */
   function PDFAttachmentViewer(options) {
     this.attachments = null;
     this.container = options.container;
+    this.eventBus = options.eventBus;
     this.downloadManager = options.downloadManager;
   }
 
   PDFAttachmentViewer.prototype = {
     reset: function PDFAttachmentViewer_reset() {
       this.attachments = null;
 
       var container = this.container;
@@ -1764,21 +1336,20 @@ var PDFAttachmentViewer = (function PDFA
       }
     },
 
     /**
      * @private
      */
     _dispatchEvent:
         function PDFAttachmentViewer_dispatchEvent(attachmentsCount) {
-      var event = document.createEvent('CustomEvent');
-      event.initCustomEvent('attachmentsloaded', true, true, {
+      this.eventBus.dispatch('attachmentsloaded', {
+        source: this,
         attachmentsCount: attachmentsCount
       });
-      this.container.dispatchEvent(event);
     },
 
     /**
      * @private
      */
     _bindLink:
         function PDFAttachmentViewer_bindLink(button, content, filename) {
       button.onclick = function downloadFile(e) {
@@ -1839,16 +1410,17 @@ exports.PDFAttachmentViewer = PDFAttachm
 }(this, function (exports, pdfjsLib) {
 
 var DEFAULT_TITLE = '\u2013';
 
 /**
  * @typedef {Object} PDFOutlineViewerOptions
  * @property {HTMLDivElement} container - The viewer element.
  * @property {IPDFLinkService} linkService - The navigation/linking service.
+ * @property {EventBus} eventBus - The application event bus.
  */
 
 /**
  * @typedef {Object} PDFOutlineViewerRenderParameters
  * @property {Array|null} outline - An array of outline objects.
  */
 
 /**
@@ -1859,16 +1431,17 @@ var PDFOutlineViewer = (function PDFOutl
    * @constructs PDFOutlineViewer
    * @param {PDFOutlineViewerOptions} options
    */
   function PDFOutlineViewer(options) {
     this.outline = null;
     this.lastToggleIsShow = true;
     this.container = options.container;
     this.linkService = options.linkService;
+    this.eventBus = options.eventBus;
   }
 
   PDFOutlineViewer.prototype = {
     reset: function PDFOutlineViewer_reset() {
       this.outline = null;
       this.lastToggleIsShow = true;
 
       var container = this.container;
@@ -1876,21 +1449,20 @@ var PDFOutlineViewer = (function PDFOutl
         container.removeChild(container.firstChild);
       }
     },
 
     /**
      * @private
      */
     _dispatchEvent: function PDFOutlineViewer_dispatchEvent(outlineCount) {
-      var event = document.createEvent('CustomEvent');
-      event.initCustomEvent('outlineloaded', true, true, {
+      this.eventBus.dispatch('outlineloaded', {
+        source: this,
         outlineCount: outlineCount
       });
-      this.container.dispatchEvent(event);
     },
 
     /**
      * @private
      */
     _bindLink: function PDFOutlineViewer_bindLink(element, item) {
       if (item.url) {
         pdfjsLib.addLinkAttributes(element, { url: item.url });
@@ -2056,16 +1628,17 @@ var SidebarView = {
  * @typedef {Object} PDFSidebarOptions
  * @property {PDFViewer} pdfViewer - The document viewer.
  * @property {PDFThumbnailViewer} pdfThumbnailViewer - The thumbnail viewer.
  * @property {PDFOutlineViewer} pdfOutlineViewer - The outline viewer.
  * @property {HTMLDivElement} mainContainer - The main container
  *   (in which the viewer element is placed).
  * @property {HTMLDivElement} outerContainer - The outer container
  *   (encasing both the viewer and sidebar elements).
+ * @property {EventBus} eventBus - The application event bus.
  * @property {HTMLButtonElement} toggleButton - The button used for
  *   opening/closing the sidebar.
  * @property {HTMLButtonElement} thumbnailButton - The button used to show
  *   the thumbnail view.
  * @property {HTMLButtonElement} outlineButton - The button used to show
  *   the outline view.
  * @property {HTMLButtonElement} attachmentsButton - The button used to show
  *   the attachments view.
@@ -2097,16 +1670,17 @@ var PDFSidebar = (function PDFSidebarClo
     this.onToggled = null;
 
     this.pdfViewer = options.pdfViewer;
     this.pdfThumbnailViewer = options.pdfThumbnailViewer;
     this.pdfOutlineViewer = options.pdfOutlineViewer;
 
     this.mainContainer = options.mainContainer;
     this.outerContainer = options.outerContainer;
+    this.eventBus = options.eventBus;
     this.toggleButton = options.toggleButton;
 
     this.thumbnailButton = options.thumbnailButton;
     this.outlineButton = options.outlineButton;
     this.attachmentsButton = options.attachmentsButton;
 
     this.thumbnailView = options.thumbnailView;
     this.outlineView = options.outlineView;
@@ -2284,21 +1858,20 @@ var PDFSidebar = (function PDFSidebarClo
         this.open();
       }
     },
 
     /**
      * @private
      */
     _dispatchEvent: function PDFSidebar_dispatchEvent() {
-      var event = document.createEvent('CustomEvent');
-      event.initCustomEvent('sidebarviewchanged', true, true, {
-        view: this.visibleView,
+      this.eventBus.dispatch('sidebarviewchanged', {
+        source: this,
+        view: this.visibleView
       });
-      this.outerContainer.dispatchEvent(event);
     },
 
     /**
      * @private
      */
     _forceRendering: function PDFSidebar_forceRendering() {
       if (this.onToggled) {
         this.onToggled();
@@ -2351,377 +1924,53 @@ var PDFSidebar = (function PDFSidebarClo
         self.pdfOutlineViewer.toggleOutlineTree();
       });
 
       self.attachmentsButton.addEventListener('click', function() {
         self.switchView(SidebarView.ATTACHMENTS);
       });
 
       // Disable/enable views.
-      self.outlineView.addEventListener('outlineloaded', function(evt) {
-        var outlineCount = evt.detail.outlineCount;
+      self.eventBus.on('outlineloaded', function(e) {
+        var outlineCount = e.outlineCount;
 
         self.outlineButton.disabled = !outlineCount;
         if (!outlineCount && self.active === SidebarView.OUTLINE) {
           self.switchView(SidebarView.THUMBS);
         }
       });
 
-      self.attachmentsView.addEventListener('attachmentsloaded', function(evt) {
-        var attachmentsCount = evt.detail.attachmentsCount;
+      self.eventBus.on('attachmentsloaded', function(e) {
+        var attachmentsCount = e.attachmentsCount;
 
         self.attachmentsButton.disabled = !attachmentsCount;
         if (!attachmentsCount && self.active === SidebarView.ATTACHMENTS) {
           self.switchView(SidebarView.THUMBS);
         }
       });
 
       // Update the thumbnailViewer, if visible, when exiting presentation mode.
-      window.addEventListener('presentationmodechanged', function(evt) {
-        if (!evt.detail.active && !evt.detail.switchInProgress &&
-            self.isThumbnailViewVisible) {
+      self.eventBus.on('presentationmodechanged', function(e) {
+        if (!e.active && !e.switchInProgress && self.isThumbnailViewVisible) {
           self._updateThumbnailViewer();
         }
       });
     },
   };
 
   return PDFSidebar;
 })();
 
 exports.SidebarView = SidebarView;
 exports.PDFSidebar = PDFSidebar;
 }));
 
 
 (function (root, factory) {
   {
-    factory((root.pdfjsWebTextLayerBuilder = {}), root.pdfjsWebPDFJS);
-  }
-}(this, function (exports, pdfjsLib) {
-
-/**
- * @typedef {Object} TextLayerBuilderOptions
- * @property {HTMLDivElement} textLayerDiv - The text layer container.
- * @property {number} pageIndex - The page index.
- * @property {PageViewport} viewport - The viewport of the text layer.
- * @property {PDFFindController} findController
- */
-
-/**
- * TextLayerBuilder provides text-selection functionality for the PDF.
- * It does this by creating overlay divs over the PDF text. These divs
- * contain text that matches the PDF text they are overlaying. This object
- * also provides a way to highlight text that is being searched for.
- * @class
- */
-var TextLayerBuilder = (function TextLayerBuilderClosure() {
-  function TextLayerBuilder(options) {
-    this.textLayerDiv = options.textLayerDiv;
-    this.renderingDone = false;
-    this.divContentDone = false;
-    this.pageIdx = options.pageIndex;
-    this.pageNumber = this.pageIdx + 1;
-    this.matches = [];
-    this.viewport = options.viewport;
-    this.textDivs = [];
-    this.findController = options.findController || null;
-    this.textLayerRenderTask = null;
-    this._bindMouse();
-  }
-
-  TextLayerBuilder.prototype = {
-    _finishRendering: function TextLayerBuilder_finishRendering() {
-      this.renderingDone = true;
-
-      var endOfContent = document.createElement('div');
-      endOfContent.className = 'endOfContent';
-      this.textLayerDiv.appendChild(endOfContent);
-
-      var event = document.createEvent('CustomEvent');
-      event.initCustomEvent('textlayerrendered', true, true, {
-        pageNumber: this.pageNumber
-      });
-      this.textLayerDiv.dispatchEvent(event);
-    },
-
-    /**
-     * Renders the text layer.
-     * @param {number} timeout (optional) if specified, the rendering waits
-     *   for specified amount of ms.
-     */
-    render: function TextLayerBuilder_render(timeout) {
-      if (!this.divContentDone || this.renderingDone) {
-        return;
-      }
-
-      if (this.textLayerRenderTask) {
-        this.textLayerRenderTask.cancel();
-        this.textLayerRenderTask = null;
-      }
-
-      this.textDivs = [];
-      var textLayerFrag = document.createDocumentFragment();
-      this.textLayerRenderTask = pdfjsLib.renderTextLayer({
-        textContent: this.textContent,
-        container: textLayerFrag,
-        viewport: this.viewport,
-        textDivs: this.textDivs,
-        timeout: timeout
-      });
-      this.textLayerRenderTask.promise.then(function () {
-        this.textLayerDiv.appendChild(textLayerFrag);
-        this._finishRendering();
-        this.updateMatches();
-      }.bind(this), function (reason) {
-        // canceled or failed to render text layer -- skipping errors
-      });
-    },
-
-    setTextContent: function TextLayerBuilder_setTextContent(textContent) {
-      if (this.textLayerRenderTask) {
-        this.textLayerRenderTask.cancel();
-        this.textLayerRenderTask = null;
-      }
-      this.textContent = textContent;
-      this.divContentDone = true;
-    },
-
-    convertMatches: function TextLayerBuilder_convertMatches(matches) {
-      var i = 0;
-      var iIndex = 0;
-      var bidiTexts = this.textContent.items;
-      var end = bidiTexts.length - 1;
-      var queryLen = (this.findController === null ?
-                      0 : this.findController.state.query.length);
-      var ret = [];
-
-      for (var m = 0, len = matches.length; m < len; m++) {
-        // Calculate the start position.
-        var matchIdx = matches[m];
-
-        // Loop over the divIdxs.
-        while (i !== end && matchIdx >= (iIndex + bidiTexts[i].str.length)) {
-          iIndex += bidiTexts[i].str.length;
-          i++;
-        }
-
-        if (i === bidiTexts.length) {
-          console.error('Could not find a matching mapping');
-        }
-
-        var match = {
-          begin: {
-            divIdx: i,
-            offset: matchIdx - iIndex
-          }
-        };
-
-        // Calculate the end position.
-        matchIdx += queryLen;
-
-        // Somewhat the same array as above, but use > instead of >= to get
-        // the end position right.
-        while (i !== end && matchIdx > (iIndex + bidiTexts[i].str.length)) {
-          iIndex += bidiTexts[i].str.length;
-          i++;
-        }
-
-        match.end = {
-          divIdx: i,
-          offset: matchIdx - iIndex
-        };
-        ret.push(match);
-      }
-
-      return ret;
-    },
-
-    renderMatches: function TextLayerBuilder_renderMatches(matches) {
-      // Early exit if there is nothing to render.
-      if (matches.length === 0) {
-        return;
-      }
-
-      var bidiTexts = this.textContent.items;
-      var textDivs = this.textDivs;
-      var prevEnd = null;
-      var pageIdx = this.pageIdx;
-      var isSelectedPage = (this.findController === null ?
-        false : (pageIdx === this.findController.selected.pageIdx));
-      var selectedMatchIdx = (this.findController === null ?
-                              -1 : this.findController.selected.matchIdx);
-      var highlightAll = (this.findController === null ?
-                          false : this.findController.state.highlightAll);
-      var infinity = {
-        divIdx: -1,
-        offset: undefined
-      };
-
-      function beginText(begin, className) {
-        var divIdx = begin.divIdx;
-        textDivs[divIdx].textContent = '';
-        appendTextToDiv(divIdx, 0, begin.offset, className);
-      }
-
-      function appendTextToDiv(divIdx, fromOffset, toOffset, className) {
-        var div = textDivs[divIdx];
-        var content = bidiTexts[divIdx].str.substring(fromOffset, toOffset);
-        var node = document.createTextNode(content);
-        if (className) {
-          var span = document.createElement('span');
-          span.className = className;
-          span.appendChild(node);
-          div.appendChild(span);
-          return;
-        }
-        div.appendChild(node);
-      }
-
-      var i0 = selectedMatchIdx, i1 = i0 + 1;
-      if (highlightAll) {
-        i0 = 0;
-        i1 = matches.length;
-      } else if (!isSelectedPage) {
-        // Not highlighting all and this isn't the selected page, so do nothing.
-        return;
-      }
-
-      for (var i = i0; i < i1; i++) {
-        var match = matches[i];
-        var begin = match.begin;
-        var end = match.end;
-        var isSelected = (isSelectedPage && i === selectedMatchIdx);
-        var highlightSuffix = (isSelected ? ' selected' : '');
-
-        if (this.findController) {
-          this.findController.updateMatchPosition(pageIdx, i, textDivs,
-                                                  begin.divIdx, end.divIdx);
-        }
-
-        // Match inside new div.
-        if (!prevEnd || begin.divIdx !== prevEnd.divIdx) {
-          // If there was a previous div, then add the text at the end.
-          if (prevEnd !== null) {
-            appendTextToDiv(prevEnd.divIdx, prevEnd.offset, infinity.offset);
-          }
-          // Clear the divs and set the content until the starting point.
-          beginText(begin);
-        } else {
-          appendTextToDiv(prevEnd.divIdx, prevEnd.offset, begin.offset);
-        }
-
-        if (begin.divIdx === end.divIdx) {
-          appendTextToDiv(begin.divIdx, begin.offset, end.offset,
-                          'highlight' + highlightSuffix);
-        } else {
-          appendTextToDiv(begin.divIdx, begin.offset, infinity.offset,
-                          'highlight begin' + highlightSuffix);
-          for (var n0 = begin.divIdx + 1, n1 = end.divIdx; n0 < n1; n0++) {
-            textDivs[n0].className = 'highlight middle' + highlightSuffix;
-          }
-          beginText(end, 'highlight end' + highlightSuffix);
-        }
-        prevEnd = end;
-      }
-
-      if (prevEnd) {
-        appendTextToDiv(prevEnd.divIdx, prevEnd.offset, infinity.offset);
-      }
-    },
-
-    updateMatches: function TextLayerBuilder_updateMatches() {
-      // Only show matches when all rendering is done.
-      if (!this.renderingDone) {
-        return;
-      }
-
-      // Clear all matches.
-      var matches = this.matches;
-      var textDivs = this.textDivs;
-      var bidiTexts = this.textContent.items;
-      var clearedUntilDivIdx = -1;
-
-      // Clear all current matches.
-      for (var i = 0, len = matches.length; i < len; i++) {
-        var match = matches[i];
-        var begin = Math.max(clearedUntilDivIdx, match.begin.divIdx);
-        for (var n = begin, end = match.end.divIdx; n <= end; n++) {
-          var div = textDivs[n];
-          div.textContent = bidiTexts[n].str;
-          div.className = '';
-        }
-        clearedUntilDivIdx = match.end.divIdx + 1;
-      }
-
-      if (this.findController === null || !this.findController.active) {
-        return;
-      }
-
-      // Convert the matches on the page controller into the match format
-      // used for the textLayer.
-      this.matches = this.convertMatches(this.findController === null ?
-        [] : (this.findController.pageMatches[this.pageIdx] || []));
-      this.renderMatches(this.matches);
-    },
-
-    /**
-     * Fixes text selection: adds additional div where mouse was clicked.
-     * This reduces flickering of the content if mouse slowly dragged down/up.
-     * @private
-     */
-    _bindMouse: function TextLayerBuilder_bindMouse() {
-      var div = this.textLayerDiv;
-      div.addEventListener('mousedown', function (e) {
-        var end = div.querySelector('.endOfContent');
-        if (!end) {
-          return;
-        }
-        end.classList.add('active');
-      });
-      div.addEventListener('mouseup', function (e) {
-        var end = div.querySelector('.endOfContent');
-        if (!end) {
-          return;
-        }
-        end.classList.remove('active');
-      });
-    },
-  };
-  return TextLayerBuilder;
-})();
-
-/**
- * @constructor
- * @implements IPDFTextLayerFactory
- */
-function DefaultTextLayerFactory() {}
-DefaultTextLayerFactory.prototype = {
-  /**
-   * @param {HTMLDivElement} textLayerDiv
-   * @param {number} pageIndex
-   * @param {PageViewport} viewport
-   * @returns {TextLayerBuilder}
-   */
-  createTextLayerBuilder: function (textLayerDiv, pageIndex, viewport) {
-    return new TextLayerBuilder({
-      textLayerDiv: textLayerDiv,
-      pageIndex: pageIndex,
-      viewport: viewport
-    });
-  }
-};
-
-exports.TextLayerBuilder = TextLayerBuilder;
-exports.DefaultTextLayerFactory = DefaultTextLayerFactory;
-}));
-
-
-(function (root, factory) {
-  {
     factory((root.pdfjsWebUIUtils = {}), root.pdfjsWebPDFJS);
   }
 }(this, function (exports, pdfjsLib) {
 
 var CSS_UNITS = 96.0 / 72.0;
 var DEFAULT_SCALE_VALUE = 'auto';
 var DEFAULT_SCALE = 1.0;
 var UNKNOWN_SCALE = 0;
@@ -3068,16 +2317,59 @@ function getPDFFileNameFromURL(url) {
         // URIError "Malformed URI", e.g. for "%AA.pdf"
         // TypeError "null has no properties", e.g. for "%2F.pdf"
       }
     }
   }
   return suggestedFilename || 'document.pdf';
 }
 
+/**
+ * Simple event bus for an application. Listeners are attached using the
+ * `on` and `off` methods. To raise an event, the `dispatch` method shall be
+ * used.
+ */
+var EventBus = (function EventBusClosure() {
+  function EventBus() {
+    this._listeners = Object.create(null);
+  }
+  EventBus.prototype = {
+    on: function EventBus_on(eventName, listener) {
+      var eventListeners = this._listeners[eventName];
+      if (!eventListeners) {
+        eventListeners = [];
+        this._listeners[eventName] = eventListeners;
+      }
+      eventListeners.push(listener);
+    },
+    off: function EventBus_on(eventName, listener) {
+      var eventListeners = this._listeners[eventName];
+      var i;
+      if (!eventListeners || ((i = eventListeners.indexOf(listener)) < 0)) {
+        return;
+      }
+      eventListeners.splice(i, 1);
+    },
+    dispatch: function EventBus_dispath(eventName) {
+      var eventListeners = this._listeners[eventName];
+      if (!eventListeners || eventListeners.length === 0) {
+        return;
+      }
+      // Passing all arguments after the eventName to the listeners.
+      var args = Array.prototype.slice.call(arguments, 1);
+      // Making copy of the listeners array in case if it will be modified
+      // during dispatch.
+      eventListeners.slice(0).forEach(function (listener) {
+        listener.apply(null, args);
+      });
+    }
+  };
+  return EventBus;
+})();
+
 var ProgressBar = (function ProgressBarClosure() {
 
   function clamp(v, min, max) {
     return Math.min(Math.max(v, min), max);
   }
 
   function ProgressBar(id, opts) {
     this.visible = true;
@@ -3158,103 +2450,264 @@ var ProgressBar = (function ProgressBarC
 exports.CSS_UNITS = CSS_UNITS;
 exports.DEFAULT_SCALE_VALUE = DEFAULT_SCALE_VALUE;
 exports.DEFAULT_SCALE = DEFAULT_SCALE;
 exports.UNKNOWN_SCALE = UNKNOWN_SCALE;
 exports.MAX_AUTO_SCALE = MAX_AUTO_SCALE;
 exports.SCROLLBAR_PADDING = SCROLLBAR_PADDING;
 exports.VERTICAL_PADDING = VERTICAL_PADDING;
 exports.mozL10n = mozL10n;
+exports.EventBus = EventBus;
 exports.ProgressBar = ProgressBar;
 exports.getPDFFileNameFromURL = getPDFFileNameFromURL;
 exports.noContextMenuHandler = noContextMenuHandler;
 exports.parseQueryString = parseQueryString;
 exports.getVisibleElements = getVisibleElements;
 exports.roundToDivide = roundToDivide;
 exports.approximateFraction = approximateFraction;
 exports.getOutputScale = getOutputScale;
 exports.scrollIntoView = scrollIntoView;
 exports.watchScroll = watchScroll;
 exports.binarySearchFirstItem = binarySearchFirstItem;
 }));
 
 
 (function (root, factory) {
   {
+    factory((root.pdfjsWebDOMEvents = {}), root.pdfjsWebUIUtils);
+  }
+}(this, function (exports, uiUtils) {
+  var EventBus = uiUtils.EventBus;
+
+  // Attaching to the application event bus to dispatch events to the DOM for
+  // backwards viewer API compatibility.
+  function attachDOMEventsToEventBus(eventBus) {
+    eventBus.on('documentload', function () {
+      var event = document.createEvent('CustomEvent');
+      event.initCustomEvent('documentload', true, true, {});
+      window.dispatchEvent(event);
+    });
+    eventBus.on('pagerendered', function (e) {
+      var event = document.createEvent('CustomEvent');
+      event.initCustomEvent('pagerendered', true, true, {
+        pageNumber: e.pageNumber,
+        cssTransform: e.cssTransform,
+      });
+      e.source.div.dispatchEvent(event);
+    });
+    eventBus.on('textlayerrendered', function (e) {
+      var event = document.createEvent('CustomEvent');
+      event.initCustomEvent('textlayerrendered', true, true, {
+        pageNumber: e.pageNumber
+      });
+      e.source.textLayerDiv.dispatchEvent(event);
+    });
+    eventBus.on('pagechange', function (e) {
+      var event = document.createEvent('UIEvents');
+      event.initUIEvent('pagechange', true, true, window, 0);
+      event.updateInProgress = e.updateInProgress;
+      event.pageNumber = e.pageNumber;
+      event.previousPageNumber = e.previousPageNumber;
+      e.source.container.dispatchEvent(event);
+    });
+    eventBus.on('pagesinit', function (e) {
+      var event = document.createEvent('CustomEvent');
+      event.initCustomEvent('pagesinit', true, true, null);
+      e.source.container.dispatchEvent(event);
+    });
+    eventBus.on('pagesloaded', function (e) {
+      var event = document.createEvent('CustomEvent');
+      event.initCustomEvent('pagesloaded', true, true, {
+        pagesCount: e.pagesCount
+      });
+      e.source.container.dispatchEvent(event);
+    });
+    eventBus.on('scalechange', function (e) {
+      var event = document.createEvent('UIEvents');
+      event.initUIEvent('scalechange', true, true, window, 0);
+      event.scale = e.scale;
+      event.presetValue = e.presetValue;
+      e.source.container.dispatchEvent(event);
+    });
+    eventBus.on('updateviewarea', function (e) {
+      var event = document.createEvent('UIEvents');
+      event.initUIEvent('updateviewarea', true, true, window, 0);
+      event.location = e.location;
+      e.source.container.dispatchEvent(event);
+    });
+    eventBus.on('find', function (e) {
+      if (e.source === window) {
+        return; // event comes from FirefoxCom, no need to replicate
+      }
+      var event = document.createEvent('CustomEvent');
+      event.initCustomEvent('find' + e.type, true, true, {
+        query: e.query,
+        caseSensitive: e.caseSensitive,
+        highlightAll: e.highlightAll,
+        findPrevious: e.findPrevious
+      });
+      window.dispatchEvent(event);
+    });
+    eventBus.on('attachmentsloaded', function (e) {
+      var event = document.createEvent('CustomEvent');
+      event.initCustomEvent('attachmentsloaded', true, true, {
+        attachmentsCount: e.attachmentsCount
+      });
+      e.source.container.dispatchEvent(event);
+    });
+    eventBus.on('sidebarviewchanged', function (e) {
+      var event = document.createEvent('CustomEvent');
+      event.initCustomEvent('sidebarviewchanged', true, true, {
+        view: e.view,
+      });
+      e.source.outerContainer.dispatchEvent(event);
+    });
+    eventBus.on('pagemode', function (e) {
+      var event = document.createEvent('CustomEvent');
+      event.initCustomEvent('pagemode', true, true, {
+        mode: e.mode,
+      });
+      e.source.pdfViewer.container.dispatchEvent(event);
+    });
+    eventBus.on('namedaction', function (e) {
+      var event = document.createEvent('CustomEvent');
+      event.initCustomEvent('namedaction', true, true, {
+        action: e.action
+      });
+      e.source.pdfViewer.container.dispatchEvent(event);
+    });
+    eventBus.on('presentationmodechanged', function (e) {
+      var event = document.createEvent('CustomEvent');
+      event.initCustomEvent('presentationmodechanged', true, true, {
+        active: e.active,
+        switchInProgress: e.switchInProgress
+      });
+      window.dispatchEvent(event);
+    });
+    eventBus.on('outlineloaded', function (e) {
+      var event = document.createEvent('CustomEvent');
+      event.initCustomEvent('outlineloaded', true, true, {
+        outlineCount: e.outlineCount
+      });
+      e.source.container.dispatchEvent(event);
+    });
+  }
+
+  var globalEventBus = null;
+  function getGlobalEventBus() {
+    if (globalEventBus) {
+      return globalEventBus;
+    }
+    globalEventBus = new EventBus();
+    attachDOMEventsToEventBus(globalEventBus);
+    return globalEventBus;
+  }
+
+  exports.attachDOMEventsToEventBus = attachDOMEventsToEventBus;
+  exports.getGlobalEventBus = getGlobalEventBus;
+}));
+
+
+(function (root, factory) {
+  {
     factory((root.pdfjsWebPasswordPrompt = {}), root.pdfjsWebUIUtils,
       root.pdfjsWebOverlayManager, root.pdfjsWebPDFJS);
   }
 }(this, function (exports, uiUtils, overlayManager, pdfjsLib) {
 
 var mozL10n = uiUtils.mozL10n;
 var OverlayManager = overlayManager.OverlayManager;
 
-var PasswordPrompt = {
-  overlayName: null,
-  updatePassword: null,
-  reason: null,
-  passwordField: null,
-  passwordText: null,
-  passwordSubmit: null,
-  passwordCancel: null,
-
-  initialize: function secondaryToolbarInitialize(options) {
+/**
+ * @typedef {Object} PasswordPromptOptions
+ * @property {string} overlayName - Name of the overlay for the overlay manager.
+ * @property {HTMLDivElement} container - Div container for the overlay.
+ * @property {HTMLParagraphElement} label - Label containing instructions for
+ *                                          entering the password.
+ * @property {HTMLInputElement} input - Input field for entering the password.
+ * @property {HTMLButtonElement} submitButton - Button for submitting the
+ *                                              password.
+ * @property {HTMLButtonElement} cancelButton - Button for cancelling password
+ *                                              entry.
+ */
+
+/**
+ * @class
+ */
+var PasswordPrompt = (function PasswordPromptClosure() {
+  /**
+   * @constructs PasswordPrompt
+   * @param {PasswordPromptOptions} options
+   */
+  function PasswordPrompt(options) {
     this.overlayName = options.overlayName;
-    this.passwordField = options.passwordField;
-    this.passwordText = options.passwordText;
-    this.passwordSubmit = options.passwordSubmit;
-    this.passwordCancel = options.passwordCancel;
+    this.container = options.container;
+    this.label = options.label;
+    this.input = options.input;
+    this.submitButton = options.submitButton;
+    this.cancelButton = options.cancelButton;
+
+    this.updateCallback = null;
+    this.reason = null;
 
     // Attach the event listeners.
-    this.passwordSubmit.addEventListener('click',
-      this.verifyPassword.bind(this));
-
-    this.passwordCancel.addEventListener('click', this.close.bind(this));
-
-    this.passwordField.addEventListener('keydown', function (e) {
+    this.submitButton.addEventListener('click', this.verify.bind(this));
+    this.cancelButton.addEventListener('click', this.close.bind(this));
+    this.input.addEventListener('keydown', function (e) {
       if (e.keyCode === 13) { // Enter key
-        this.verifyPassword();
+        this.verify();
       }
     }.bind(this));
 
-    OverlayManager.register(this.overlayName, this.close.bind(this), true);
-  },
-
-  open: function passwordPromptOpen() {
-    OverlayManager.open(this.overlayName).then(function () {
-      this.passwordField.type = 'password';
-      this.passwordField.focus();
-
-      var promptString = mozL10n.get('password_label', null,
-        'Enter the password to open this PDF file.');
-
-      if (this.reason === pdfjsLib.PasswordResponses.INCORRECT_PASSWORD) {
-        promptString = mozL10n.get('password_invalid', null,
-          'Invalid password. Please try again.');
-      }
-
-      this.passwordText.textContent = promptString;
-    }.bind(this));
-  },
-
-  close: function passwordPromptClose() {
-    OverlayManager.close(this.overlayName).then(function () {
-      this.passwordField.value = '';
-      this.passwordField.type = '';
-    }.bind(this));
-  },
-
-  verifyPassword: function passwordPromptVerifyPassword() {
-    var password = this.passwordField.value;
-    if (password && password.length > 0) {
-      this.close();
-      return this.updatePassword(password);
-    }
-  }
-};
+    OverlayManager.register(this.overlayName, this.container,
+                            this.close.bind(this), true);
+  }
+
+  PasswordPrompt.prototype = {
+    open: function PasswordPrompt_open() {
+      OverlayManager.open(this.overlayName).then(function () {
+        this.input.type = 'password';
+        this.input.focus();
+
+        var promptString = mozL10n.get('password_label', null,
+          'Enter the password to open this PDF file.');
+
+        if (this.reason === pdfjsLib.PasswordResponses.INCORRECT_PASSWORD) {
+          promptString = mozL10n.get('password_invalid', null,
+            'Invalid password. Please try again.');
+        }
+
+        this.label.textContent = promptString;
+      }.bind(this));
+    },
+
+    close: function PasswordPrompt_close() {
+      OverlayManager.close(this.overlayName).then(function () {
+        this.input.value = '';
+        this.input.type = '';
+      }.bind(this));
+    },
+
+    verify: function PasswordPrompt_verify() {
+      var password = this.input.value;
+      if (password && password.length > 0) {
+        this.close();
+        return this.updateCallback(password);
+      }
+    },
+
+    setUpdateCallback:
+        function PasswordPrompt_setUpdateCallback(updateCallback, reason) {
+      this.updateCallback = updateCallback;
+      this.reason = reason;
+    }
+  };
+
+  return PasswordPrompt;
+})();
 
 exports.PasswordPrompt = PasswordPrompt;
 }));
 
 
 (function (root, factory) {
   {
     factory((root.pdfjsWebPDFDocumentProperties = {}), root.pdfjsWebUIUtils,
@@ -3279,31 +2732,33 @@ var OverlayManager = overlayManager.Over
 var PDFDocumentProperties = (function PDFDocumentPropertiesClosure() {
   /**
    * @constructs PDFDocumentProperties
    * @param {PDFDocumentPropertiesOptions} options
    */
   function PDFDocumentProperties(options) {
     this.fields = options.fields;
     this.overlayName = options.overlayName;
+    this.container = options.container;
 
     this.rawFileSize = 0;
     this.url = null;
     this.pdfDocument = null;
 
     // Bind the event listener for the Close button.
     if (options.closeButton) {
       options.closeButton.addEventListener('click', this.close.bind(this));
     }
 
     this.dataAvailablePromise = new Promise(function (resolve) {
       this.resolveDataAvailable = resolve;
     }.bind(this));
 
-    OverlayManager.register(this.overlayName, this.close.bind(this));
+    OverlayManager.register(this.overlayName, this.container,
+                            this.close.bind(this));
   }
 
   PDFDocumentProperties.prototype = {
     /**
      * Open the document properties overlay.
      */
     open: function PDFDocumentProperties_open() {
       Promise.all([OverlayManager.open(this.overlayName),
@@ -3474,23 +2929,21 @@ var PDFDocumentProperties = (function PD
 })();
 
 exports.PDFDocumentProperties = PDFDocumentProperties;
 }));
 
 
 (function (root, factory) {
   {
-    factory((root.pdfjsWebPDFFindController = {}), root.pdfjsWebUIUtils,
-      root.pdfjsWebFirefoxCom);
+    factory((root.pdfjsWebPDFFindController = {}), root.pdfjsWebUIUtils);
   }
 }(this, function (exports, uiUtils, firefoxCom) {
 
 var scrollIntoView = uiUtils.scrollIntoView;
-var FirefoxCom = firefoxCom.FirefoxCom;
 
 var FindStates = {
   FIND_FOUND: 0,
   FIND_NOTFOUND: 1,
   FIND_WRAPPED: 2,
   FIND_PENDING: 3
 };
 
@@ -3513,43 +2966,28 @@ var CHARACTERS_TO_NORMALIZE = {
 
 /**
  * Provides "search" or "find" functionality for the PDF.
  * This object actually performs the search for a given string.
  */
 var PDFFindController = (function PDFFindControllerClosure() {
   function PDFFindController(options) {
     this.pdfViewer = options.pdfViewer || null;
-    this.integratedFind = options.integratedFind || false;
-    this.findBar = options.findBar || null;
+
+    this.onUpdateResultsCount = null;
+    this.onUpdateState = null;
 
     this.reset();
 
     // Compile the regular expression for text normalization once.
     var replace = Object.keys(CHARACTERS_TO_NORMALIZE).join('');
     this.normalizationRegex = new RegExp('[' + replace + ']', 'g');
-
-    var events = [
-      'find',
-      'findagain',
-      'findhighlightallchange',
-      'findcasesensitivitychange'
-    ];
-    this.handleEvent = this.handleEvent.bind(this);
-
-    for (var i = 0, len = events.length; i < len; i++) {
-      window.addEventListener(events[i], this.handleEvent);
-    }
   }
 
   PDFFindController.prototype = {
-    setFindBar: function PDFFindController_setFindBar(findBar) {
-      this.findBar = findBar;
-    },
-
     reset: function PDFFindController_reset() {
       this.startedTextExtraction = false;
       this.extractTextPromises = [];
       this.pendingFindMatches = Object.create(null);
       this.active = false; // If active, find results will be highlighted.
       this.pageContents = []; // Stores the text for each page.
       this.pageMatches = [];
       this.matchCount = 0;
@@ -3651,28 +3089,28 @@ var PDFFindController = (function PDFFin
               extractPageText(pageIndex + 1);
             }
           }
         );
       }
       extractPageText(0);
     },
 
-    handleEvent: function PDFFindController_handleEvent(e) {
-      if (this.state === null || e.type !== 'findagain') {
+    executeCommand: function PDFFindController_executeCommand(cmd, state) {
+      if (this.state === null || cmd !== 'findagain') {
         this.dirtyMatch = true;
       }
-      this.state = e.detail;
+      this.state = state;
       this.updateUIState(FindStates.FIND_PENDING);
 
       this.firstPagePromise.then(function() {
         this.extractText();
 
         clearTimeout(this.findTimeout);
-        if (e.type === 'find') {
+        if (cmd === 'find') {
           // Only trigger the find action after 250ms of silence.
           this.findTimeout = setTimeout(this.nextMatch.bind(this), 250);
         } else {
           this.nextMatch();
         }
       }.bind(this));
     },
 
@@ -3860,960 +3298,37 @@ var PDFFindController = (function PDFFin
       this.updateUIState(state, this.state.findPrevious);
       if (this.selected.pageIdx !== -1) {
         this.updatePage(this.selected.pageIdx);
       }
     },
 
     updateUIResultsCount:
         function PDFFindController_updateUIResultsCount() {
-      if (this.findBar === null) {
-        throw new Error('PDFFindController is not initialized with a ' +
-          'PDFFindBar instance.');
-      }
-      this.findBar.updateResultsCount(this.matchCount);
+      if (this.onUpdateResultsCount) {
+        this.onUpdateResultsCount(this.matchCount);
+      }
     },
 
     updateUIState: function PDFFindController_updateUIState(state, previous) {
-      if (this.integratedFind) {
-        FirefoxCom.request('updateFindControlState',
-                           { result: state, findPrevious: previous });
-        return;
-      }
-      if (this.findBar === null) {
-        throw new Error('PDFFindController is not initialized with a ' +
-                        'PDFFindBar instance.');
-      }
-      this.findBar.updateUIState(state, previous, this.matchCount);
+      if (this.onUpdateState) {
+        this.onUpdateState(state, previous, this.matchCount);
+      }
     }
   };
   return PDFFindController;
 })();
 
 exports.FindStates = FindStates;
 exports.PDFFindController = PDFFindController;
 }));
 
 
 (function (root, factory) {
   {
-    factory((root.pdfjsWebPDFLinkService = {}), root.pdfjsWebUIUtils);
-  }
-}(this, function (exports, uiUtils) {
-
-var parseQueryString = uiUtils.parseQueryString;
-
-/**
- * Performs navigation functions inside PDF, such as opening specified page,
- * or destination.
- * @class
- * @implements {IPDFLinkService}
- */
-var PDFLinkService = (function () {
-  /**
-   * @constructs PDFLinkService
-   */
-  function PDFLinkService() {
-    this.baseUrl = null;
-    this.pdfDocument = null;
-    this.pdfViewer = null;
-    this.pdfHistory = null;
-
-    this._pagesRefCache = null;
-  }
-
-  PDFLinkService.prototype = {
-    setDocument: function PDFLinkService_setDocument(pdfDocument, baseUrl) {
-      this.baseUrl = baseUrl;
-      this.pdfDocument = pdfDocument;
-      this._pagesRefCache = Object.create(null);
-    },
-
-    setViewer: function PDFLinkService_setViewer(pdfViewer) {
-      this.pdfViewer = pdfViewer;
-    },
-
-    setHistory: function PDFLinkService_setHistory(pdfHistory) {
-      this.pdfHistory = pdfHistory;
-    },
-
-    /**
-     * @returns {number}
-     */
-    get pagesCount() {
-      return this.pdfDocument.numPages;
-    },
-
-    /**
-     * @returns {number}
-     */
-    get page() {
-      return this.pdfViewer.currentPageNumber;
-    },
-
-    /**
-     * @param {number} value
-     */
-    set page(value) {
-      this.pdfViewer.currentPageNumber = value;
-    },
-
-    /**
-     * @param dest - The PDF destination object.
-     */
-    navigateTo: function PDFLinkService_navigateTo(dest) {
-      var destString = '';
-      var self = this;
-
-      var goToDestination = function(destRef) {
-        // dest array looks like that: <page-ref> </XYZ|FitXXX> <args..>
-        var pageNumber = destRef instanceof Object ?
-          self._pagesRefCache[destRef.num + ' ' + destRef.gen + ' R'] :
-          (destRef + 1);
-        if (pageNumber) {
-          if (pageNumber > self.pagesCount) {
-            pageNumber = self.pagesCount;
-          }
-          self.pdfViewer.scrollPageIntoView(pageNumber, dest);
-
-          if (self.pdfHistory) {
-            // Update the browsing history.
-            self.pdfHistory.push({
-              dest: dest,
-              hash: destString,
-              page: pageNumber
-            });
-          }
-        } else {
-          self.pdfDocument.getPageIndex(destRef).then(function (pageIndex) {
-            var pageNum = pageIndex + 1;
-            var cacheKey = destRef.num + ' ' + destRef.gen + ' R';
-            self._pagesRefCache[cacheKey] = pageNum;
-            goToDestination(destRef);
-          });
-        }
-      };
-
-      var destinationPromise;
-      if (typeof dest === 'string') {
-        destString = dest;
-        destinationPromise = this.pdfDocument.getDestination(dest);
-      } else {
-        destinationPromise = Promise.resolve(dest);
-      }
-      destinationPromise.then(function(destination) {
-        dest = destination;
-        if (!(destination instanceof Array)) {
-          return; // invalid destination
-        }
-        goToDestination(destination[0]);
-      });
-    },
-
-    /**
-     * @param dest - The PDF destination object.
-     * @returns {string} The hyperlink to the PDF object.
-     */
-    getDestinationHash: function PDFLinkService_getDestinationHash(dest) {
-      if (typeof dest === 'string') {
-        return this.getAnchorUrl('#' + escape(dest));
-      }
-      if (dest instanceof Array) {
-        var destRef = dest[0]; // see navigateTo method for dest format
-        var pageNumber = destRef instanceof Object ?
-          this._pagesRefCache[destRef.num + ' ' + destRef.gen + ' R'] :
-          (destRef + 1);
-        if (pageNumber) {
-          var pdfOpenParams = this.getAnchorUrl('#page=' + pageNumber);
-          var destKind = dest[1];
-          if (typeof destKind === 'object' && 'name' in destKind &&
-              destKind.name === 'XYZ') {
-            var scale = (dest[4] || this.pdfViewer.currentScaleValue);
-            var scaleNumber = parseFloat(scale);
-            if (scaleNumber) {
-              scale = scaleNumber * 100;
-            }
-            pdfOpenParams += '&zoom=' + scale;
-            if (dest[2] || dest[3]) {
-              pdfOpenParams += ',' + (dest[2] || 0) + ',' + (dest[3] || 0);
-            }
-          }
-          return pdfOpenParams;
-        }
-      }
-      return this.getAnchorUrl('');
-    },
-
-    /**
-     * Prefix the full url on anchor links to make sure that links are resolved
-     * relative to the current URL instead of the one defined in <base href>.
-     * @param {String} anchor The anchor hash, including the #.
-     * @returns {string} The hyperlink to the PDF object.
-     */
-    getAnchorUrl: function PDFLinkService_getAnchorUrl(anchor) {
-      return (this.baseUrl || '') + anchor;
-    },
-
-    /**
-     * @param {string} hash
-     */
-    setHash: function PDFLinkService_setHash(hash) {
-      if (hash.indexOf('=') >= 0) {
-        var params = parseQueryString(hash);
-        // borrowing syntax from "Parameters for Opening PDF Files"
-        if ('nameddest' in params) {
-          if (this.pdfHistory) {
-            this.pdfHistory.updateNextHashParam(params.nameddest);
-          }
-          this.navigateTo(params.nameddest);
-          return;
-        }
-        var pageNumber, dest;
-        if ('page' in params) {
-          pageNumber = (params.page | 0) || 1;
-        }
-        if ('zoom' in params) {
-          // Build the destination array.
-          var zoomArgs = params.zoom.split(','); // scale,left,top
-          var zoomArg = zoomArgs[0];
-          var zoomArgNumber = parseFloat(zoomArg);
-
-          if (zoomArg.indexOf('Fit') === -1) {
-            // If the zoomArg is a number, it has to get divided by 100. If it's
-            // a string, it should stay as it is.
-            dest = [null, { name: 'XYZ' },
-                    zoomArgs.length > 1 ? (zoomArgs[1] | 0) : null,
-                    zoomArgs.length > 2 ? (zoomArgs[2] | 0) : null,
-                    (zoomArgNumber ? zoomArgNumber / 100 : zoomArg)];
-          } else {
-            if (zoomArg === 'Fit' || zoomArg === 'FitB') {
-              dest = [null, { name: zoomArg }];
-            } else if ((zoomArg === 'FitH' || zoomArg === 'FitBH') ||
-                       (zoomArg === 'FitV' || zoomArg === 'FitBV')) {
-              dest = [null, { name: zoomArg },
-                      zoomArgs.length > 1 ? (zoomArgs[1] | 0) : null];
-            } else if (zoomArg === 'FitR') {
-              if (zoomArgs.length !== 5) {
-                console.error('PDFLinkService_setHash: ' +
-                              'Not enough parameters for \'FitR\'.');
-              } else {
-                dest = [null, { name: zoomArg },
-                        (zoomArgs[1] | 0), (zoomArgs[2] | 0),
-                        (zoomArgs[3] | 0), (zoomArgs[4] | 0)];
-              }
-            } else {
-              console.error('PDFLinkService_setHash: \'' + zoomArg +
-                            '\' is not a valid zoom value.');
-            }
-          }
-        }
-        if (dest) {
-          this.pdfViewer.scrollPageIntoView(pageNumber || this.page, dest);
-        } else if (pageNumber) {
-          this.page = pageNumber; // simple page
-        }
-        if ('pagemode' in params) {
-          var event = document.createEvent('CustomEvent');
-          event.initCustomEvent('pagemode', true, true, {
-            mode: params.pagemode,
-          });
-          this.pdfViewer.container.dispatchEvent(event);
-        }
-      } else if (/^\d+$/.test(hash)) { // page number
-        this.page = hash;
-      } else { // named destination
-        if (this.pdfHistory) {
-          this.pdfHistory.updateNextHashParam(unescape(hash));
-        }
-        this.navigateTo(unescape(hash));
-      }
-    },
-
-    /**
-     * @param {string} action
-     */
-    executeNamedAction: function PDFLinkService_executeNamedAction(action) {
-      // See PDF reference, table 8.45 - Named action
-      switch (action) {
-        case 'GoBack':
-          if (this.pdfHistory) {
-            this.pdfHistory.back();
-          }
-          break;
-
-        case 'GoForward':
-          if (this.pdfHistory) {
-            this.pdfHistory.forward();
-          }
-          break;
-
-        case 'NextPage':
-          this.page++;
-          break;
-
-        case 'PrevPage':
-          this.page--;
-          break;
-
-        case 'LastPage':
-          this.page = this.pagesCount;
-          break;
-
-        case 'FirstPage':
-          this.page = 1;
-          break;
-
-        default:
-          break; // No action according to spec
-      }
-
-      var event = document.createEvent('CustomEvent');
-      event.initCustomEvent('namedaction', true, true, {
-        action: action
-      });
-      this.pdfViewer.container.dispatchEvent(event);
-    },
-
-    /**
-     * @param {number} pageNum - page number.
-     * @param {Object} pageRef - reference to the page.
-     */
-    cachePageRef: function PDFLinkService_cachePageRef(pageNum, pageRef) {
-      var refStr = pageRef.num + ' ' + pageRef.gen + ' R';
-      this._pagesRefCache[refStr] = pageNum;
-    }
-  };
-
-  return PDFLinkService;
-})();
-
-var SimpleLinkService = (function SimpleLinkServiceClosure() {
-  function SimpleLinkService() {}
-
-  SimpleLinkService.prototype = {
-    /**
-     * @returns {number}
-     */
-    get page() {
-      return 0;
-    },
-    /**
-     * @param {number} value
-     */
-    set page(value) {},
-    /**
-     * @param dest - The PDF destination object.
-     */
-    navigateTo: function (dest) {},
-    /**
-     * @param dest - The PDF destination object.
-     * @returns {string} The hyperlink to the PDF object.
-     */
-    getDestinationHash: function (dest) {
-      return '#';
-    },
-    /**
-     * @param hash - The PDF parameters/hash.
-     * @returns {string} The hyperlink to the PDF object.
-     */
-    getAnchorUrl: function (hash) {
-      return '#';
-    },
-    /**
-     * @param {string} hash
-     */
-    setHash: function (hash) {},
-    /**
-     * @param {string} action
-     */
-    executeNamedAction: function (action) {},
-    /**
-     * @param {number} pageNum - page number.
-     * @param {Object} pageRef - reference to the page.
-     */
-    cachePageRef: function (pageNum, pageRef) {}
-  };
-  return SimpleLinkService;
-})();
-
-exports.PDFLinkService = PDFLinkService;
-exports.SimpleLinkService = SimpleLinkService;
-}));
-
-
-(function (root, factory) {
-  {
-    factory((root.pdfjsWebPDFPageView = {}), root.pdfjsWebUIUtils,
-      root.pdfjsWebPDFRenderingQueue, root.pdfjsWebPDFJS);
-  }
-}(this, function (exports, uiUtils, pdfRenderingQueue, pdfjsLib) {
-
-var CSS_UNITS = uiUtils.CSS_UNITS;
-var DEFAULT_SCALE = uiUtils.DEFAULT_SCALE;
-var getOutputScale = uiUtils.getOutputScale;
-var approximateFraction = uiUtils.approximateFraction;
-var roundToDivide = uiUtils.roundToDivide;
-var RenderingStates = pdfRenderingQueue.RenderingStates;
-
-var TEXT_LAYER_RENDER_DELAY = 200; // ms
-
-/**
- * @typedef {Object} PDFPageViewOptions
- * @property {HTMLDivElement} container - The viewer element.
- * @property {number} id - The page unique ID (normally its number).
- * @property {number} scale - The page scale display.
- * @property {PageViewport} defaultViewport - The page viewport.
- * @property {PDFRenderingQueue} renderingQueue - The rendering queue object.
- * @property {IPDFTextLayerFactory} textLayerFactory
- * @property {IPDFAnnotationLayerFactory} annotationLayerFactory
- */
-
-/**
- * @class
- * @implements {IRenderableView}
- */
-var PDFPageView = (function PDFPageViewClosure() {
-  /**
-   * @constructs PDFPageView
-   * @param {PDFPageViewOptions} options
-   */
-  function PDFPageView(options) {
-    var container = options.container;
-    var id = options.id;
-    var scale = options.scale;
-    var defaultViewport = options.defaultViewport;
-    var renderingQueue = options.renderingQueue;
-    var textLayerFactory = options.textLayerFactory;
-    var annotationLayerFactory = options.annotationLayerFactory;
-
-    this.id = id;
-    this.renderingId = 'page' + id;
-
-    this.rotation = 0;
-    this.scale = scale || DEFAULT_SCALE;
-    this.viewport = defaultViewport;
-    this.pdfPageRotate = defaultViewport.rotation;
-    this.hasRestrictedScaling = false;
-
-    this.renderingQueue = renderingQueue;
-    this.textLayerFactory = textLayerFactory;
-    this.annotationLayerFactory = annotationLayerFactory;
-
-    this.renderingState = RenderingStates.INITIAL;
-    this.resume = null;
-
-    this.onBeforeDraw = null;
-    this.onAfterDraw = null;
-
-    this.textLayer = null;
-
-    this.zoomLayer = null;
-
-    this.annotationLayer = null;
-
-    var div = document.createElement('div');
-    div.id = 'pageContainer' + this.id;
-    div.className = 'page';
-    div.style.width = Math.floor(this.viewport.width) + 'px';
-    div.style.height = Math.floor(this.viewport.height) + 'px';
-    div.setAttribute('data-page-number', this.id);
-    this.div = div;
-
-    container.appendChild(div);
-  }
-
-  PDFPageView.prototype = {
-    setPdfPage: function PDFPageView_setPdfPage(pdfPage) {
-      this.pdfPage = pdfPage;
-      this.pdfPageRotate = pdfPage.rotate;
-      var totalRotation = (this.rotation + this.pdfPageRotate) % 360;
-      this.viewport = pdfPage.getViewport(this.scale * CSS_UNITS,
-                                          totalRotation);
-      this.stats = pdfPage.stats;
-      this.reset();
-    },
-
-    destroy: function PDFPageView_destroy() {
-      this.zoomLayer = null;
-      this.reset();
-      if (this.pdfPage) {
-        this.pdfPage.cleanup();
-      }
-    },
-
-    reset: function PDFPageView_reset(keepZoomLayer, keepAnnotations) {
-      if (this.renderTask) {
-        this.renderTask.cancel();
-      }
-      this.resume = null;
-      this.renderingState = RenderingStates.INITIAL;
-
-      var div = this.div;
-      div.style.width = Math.floor(this.viewport.width) + 'px';
-      div.style.height = Math.floor(this.viewport.height) + 'px';
-
-      var childNodes = div.childNodes;
-      var currentZoomLayerNode = (keepZoomLayer && this.zoomLayer) || null;
-      var currentAnnotationNode = (keepAnnotations && this.annotationLayer &&
-                                   this.annotationLayer.div) || null;
-      for (var i = childNodes.length - 1; i >= 0; i--) {
-        var node = childNodes[i];
-        if (currentZoomLayerNode === node || currentAnnotationNode === node) {
-          continue;
-        }
-        div.removeChild(node);
-      }
-      div.removeAttribute('data-loaded');
-
-      if (currentAnnotationNode) {
-        // Hide annotationLayer until all elements are resized
-        // so they are not displayed on the already-resized page
-        this.annotationLayer.hide();
-      } else {
-        this.annotationLayer = null;
-      }
-
-      if (this.canvas && !currentZoomLayerNode) {
-        // Zeroing the width and height causes Firefox to release graphics
-        // resources immediately, which can greatly reduce memory consumption.
-        this.canvas.width = 0;
-        this.canvas.height = 0;
-        delete this.canvas;
-      }
-
-      this.loadingIconDiv = document.createElement('div');
-      this.loadingIconDiv.className = 'loadingIcon';
-      div.appendChild(this.loadingIconDiv);
-    },
-
-    update: function PDFPageView_update(scale, rotation) {
-      this.scale = scale || this.scale;
-
-      if (typeof rotation !== 'undefined') {
-        this.rotation = rotation;
-      }
-
-      var totalRotation = (this.rotation + this.pdfPageRotate) % 360;
-      this.viewport = this.viewport.clone({
-        scale: this.scale * CSS_UNITS,
-        rotation: totalRotation
-      });
-
-      var isScalingRestricted = false;
-      if (this.canvas && pdfjsLib.PDFJS.maxCanvasPixels > 0) {
-        var outputScale = this.outputScale;
-        var pixelsInViewport = this.viewport.width * this.viewport.height;
-        if (((Math.floor(this.viewport.width) * outputScale.sx) | 0) *
-            ((Math.floor(this.viewport.height) * outputScale.sy) | 0) >
-            pdfjsLib.PDFJS.maxCanvasPixels) {
-          isScalingRestricted = true;
-        }
-      }
-
-      if (this.canvas) {
-        if (pdfjsLib.PDFJS.useOnlyCssZoom ||
-            (this.hasRestrictedScaling && isScalingRestricted)) {
-          this.cssTransform(this.canvas, true);
-
-          var event = document.createEvent('CustomEvent');
-          event.initCustomEvent('pagerendered', true, true, {
-            pageNumber: this.id,
-            cssTransform: true,
-          });
-          this.div.dispatchEvent(event);
-
-          return;
-        }
-        if (!this.zoomLayer) {
-          this.zoomLayer = this.canvas.parentNode;
-          this.zoomLayer.style.position = 'absolute';
-        }
-      }
-      if (this.zoomLayer) {
-        this.cssTransform(this.zoomLayer.firstChild);
-      }
-      this.reset(/* keepZoomLayer = */ true, /* keepAnnotations = */ true);
-    },
-
-    /**
-     * Called when moved in the parent's container.
-     */
-    updatePosition: function PDFPageView_updatePosition() {
-      if (this.textLayer) {
-        this.textLayer.render(TEXT_LAYER_RENDER_DELAY);
-      }
-    },
-
-    cssTransform: function PDFPageView_transform(canvas, redrawAnnotations) {
-      var CustomStyle = pdfjsLib.CustomStyle;
-
-      // Scale canvas, canvas wrapper, and page container.
-      var width = this.viewport.width;
-      var height = this.viewport.height;
-      var div = this.div;
-      canvas.style.width = canvas.parentNode.style.width = div.style.width =
-        Math.floor(width) + 'px';
-      canvas.style.height = canvas.parentNode.style.height = div.style.height =
-        Math.floor(height) + 'px';
-      // The canvas may have been originally rotated, rotate relative to that.
-      var relativeRotation = this.viewport.rotation - canvas._viewport.rotation;
-      var absRotation = Math.abs(relativeRotation);
-      var scaleX = 1, scaleY = 1;
-      if (absRotation === 90 || absRotation === 270) {
-        // Scale x and y because of the rotation.
-        scaleX = height / width;
-        scaleY = width / height;
-      }
-      var cssTransform = 'rotate(' + relativeRotation + 'deg) ' +
-        'scale(' + scaleX + ',' + scaleY + ')';
-      CustomStyle.setProp('transform', canvas, cssTransform);
-
-      if (this.textLayer) {
-        // Rotating the text layer is more complicated since the divs inside the
-        // the text layer are rotated.
-        // TODO: This could probably be simplified by drawing the text layer in
-        // one orientation then rotating overall.
-        var textLayerViewport = this.textLayer.viewport;
-        var textRelativeRotation = this.viewport.rotation -
-          textLayerViewport.rotation;
-        var textAbsRotation = Math.abs(textRelativeRotation);
-        var scale = width / textLayerViewport.width;
-        if (textAbsRotation === 90 || textAbsRotation === 270) {
-          scale = width / textLayerViewport.height;
-        }
-        var textLayerDiv = this.textLayer.textLayerDiv;
-        var transX, transY;
-        switch (textAbsRotation) {
-          case 0:
-            transX = transY = 0;
-            break;
-          case 90:
-            transX = 0;
-            transY = '-' + textLayerDiv.style.height;
-            break;
-          case 180:
-            transX = '-' + textLayerDiv.style.width;
-            transY = '-' + textLayerDiv.style.height;
-            break;
-          case 270:
-            transX = '-' + textLayerDiv.style.width;
-            transY = 0;
-            break;
-          default:
-            console.error('Bad rotation value.');
-            break;
-        }
-        CustomStyle.setProp('transform', textLayerDiv,
-            'rotate(' + textAbsRotation + 'deg) ' +
-            'scale(' + scale + ', ' + scale + ') ' +
-            'translate(' + transX + ', ' + transY + ')');
-        CustomStyle.setProp('transformOrigin', textLayerDiv, '0% 0%');
-      }
-
-      if (redrawAnnotations && this.annotationLayer) {
-        this.annotationLayer.render(this.viewport, 'display');
-      }
-    },
-
-    get width() {
-      return this.viewport.width;
-    },
-
-    get height() {
-      return this.viewport.height;
-    },
-
-    getPagePoint: function PDFPageView_getPagePoint(x, y) {
-      return this.viewport.convertToPdfPoint(x, y);
-    },
-
-    draw: function PDFPageView_draw() {
-      if (this.renderingState !== RenderingStates.INITIAL) {
-        console.error('Must be in new state before drawing');
-      }
-
-      this.renderingState = RenderingStates.RUNNING;
-
-      var pdfPage = this.pdfPage;
-      var viewport = this.viewport;
-      var div = this.div;
-      // Wrap the canvas so if it has a css transform for highdpi the overflow
-      // will be hidden in FF.
-      var canvasWrapper = document.createElement('div');
-      canvasWrapper.style.width = div.style.width;
-      canvasWrapper.style.height = div.style.height;
-      canvasWrapper.classList.add('canvasWrapper');
-
-      var canvas = document.createElement('canvas');
-      canvas.id = 'page' + this.id;
-      // Keep the canvas hidden until the first draw callback, or until drawing
-      // is complete when `!this.renderingQueue`, to prevent black flickering.
-      canvas.setAttribute('hidden', 'hidden');
-      var isCanvasHidden = true;
-
-      canvasWrapper.appendChild(canvas);
-      if (this.annotationLayer && this.annotationLayer.div) {
-        // annotationLayer needs to stay on top
-        div.insertBefore(canvasWrapper, this.annotationLayer.div);
-      } else {
-        div.appendChild(canvasWrapper);
-      }
-      this.canvas = canvas;
-
-      canvas.mozOpaque = true;
-      var ctx = canvas.getContext('2d', {alpha: false});
-      var outputScale = getOutputScale(ctx);
-      this.outputScale = outputScale;
-
-      if (pdfjsLib.PDFJS.useOnlyCssZoom) {
-        var actualSizeViewport = viewport.clone({scale: CSS_UNITS});
-        // Use a scale that will make the canvas be the original intended size
-        // of the page.
-        outputScale.sx *= actualSizeViewport.width / viewport.width;
-        outputScale.sy *= actualSizeViewport.height / viewport.height;
-        outputScale.scaled = true;
-      }
-
-      if (pdfjsLib.PDFJS.maxCanvasPixels > 0) {
-        var pixelsInViewport = viewport.width * viewport.height;
-        var maxScale =
-          Math.sqrt(pdfjsLib.PDFJS.maxCanvasPixels / pixelsInViewport);
-        if (outputScale.sx > maxScale || outputScale.sy > maxScale) {
-          outputScale.sx = maxScale;
-          outputScale.sy = maxScale;
-          outputScale.scaled = true;
-          this.hasRestrictedScaling = true;
-        } else {
-          this.hasRestrictedScaling = false;
-        }
-      }
-
-      var sfx = approximateFraction(outputScale.sx);
-      var sfy = approximateFraction(outputScale.sy);
-      canvas.width = roundToDivide(viewport.width * outputScale.sx, sfx[0]);
-      canvas.height = roundToDivide(viewport.height * outputScale.sy, sfy[0]);
-      canvas.style.width = roundToDivide(viewport.width, sfx[1]) + 'px';
-      canvas.style.height = roundToDivide(viewport.height, sfy[1]) + 'px';
-      // Add the viewport so it's known what it was originally drawn with.
-      canvas._viewport = viewport;
-
-      var textLayerDiv = null;
-      var textLayer = null;
-      if (this.textLayerFactory) {
-        textLayerDiv = document.createElement('div');
-        textLayerDiv.className = 'textLayer';
-        textLayerDiv.style.width = canvasWrapper.style.width;
-        textLayerDiv.style.height = canvasWrapper.style.height;
-        if (this.annotationLayer && this.annotationLayer.div) {
-          // annotationLayer needs to stay on top
-          div.insertBefore(textLayerDiv, this.annotationLayer.div);
-        } else {
-          div.appendChild(textLayerDiv);
-        }
-
-        textLayer = this.textLayerFactory.createTextLayerBuilder(textLayerDiv,
-                                                                 this.id - 1,
-                                                                 this.viewport);
-      }
-      this.textLayer = textLayer;
-
-      var resolveRenderPromise, rejectRenderPromise;
-      var promise = new Promise(function (resolve, reject) {
-        resolveRenderPromise = resolve;
-        rejectRenderPromise = reject;
-      });
-
-      // Rendering area
-
-      var self = this;
-      function pageViewDrawCallback(error) {
-        // The renderTask may have been replaced by a new one, so only remove
-        // the reference to the renderTask if it matches the one that is
-        // triggering this callback.
-        if (renderTask === self.renderTask) {
-          self.renderTask = null;
-        }
-
-        if (error === 'cancelled') {
-          rejectRenderPromise(error);
-          return;
-        }
-
-        self.renderingState = RenderingStates.FINISHED;
-
-        if (isCanvasHidden) {
-          self.canvas.removeAttribute('hidden');
-          isCanvasHidden = false;
-        }
-
-        if (self.loadingIconDiv) {
-          div.removeChild(self.loadingIconDiv);
-          delete self.loadingIconDiv;
-        }
-
-        if (self.zoomLayer) {
-          // Zeroing the width and height causes Firefox to release graphics
-          // resources immediately, which can greatly reduce memory consumption.
-          var zoomLayerCanvas = self.zoomLayer.firstChild;
-          zoomLayerCanvas.width = 0;
-          zoomLayerCanvas.height = 0;
-
-          div.removeChild(self.zoomLayer);
-          self.zoomLayer = null;
-        }
-
-        self.error = error;
-        self.stats = pdfPage.stats;
-        if (self.onAfterDraw) {
-          self.onAfterDraw();
-        }
-        var event = document.createEvent('CustomEvent');
-        event.initCustomEvent('pagerendered', true, true, {
-          pageNumber: self.id,
-          cssTransform: false,
-        });
-        div.dispatchEvent(event);
-
-        if (!error) {
-          resolveRenderPromise(undefined);
-        } else {
-          rejectRenderPromise(error);
-        }
-      }
-
-      var renderContinueCallback = null;
-      if (this.renderingQueue) {
-        renderContinueCallback = function renderContinueCallback(cont) {
-          if (!self.renderingQueue.isHighestPriority(self)) {
-            self.renderingState = RenderingStates.PAUSED;
-            self.resume = function resumeCallback() {
-              self.renderingState = RenderingStates.RUNNING;
-              cont();
-            };
-            return;
-          }
-          if (isCanvasHidden) {
-            self.canvas.removeAttribute('hidden');
-            isCanvasHidden = false;
-          }
-          cont();
-        };
-      }
-
-      var transform = !outputScale.scaled ? null :
-        [outputScale.sx, 0, 0, outputScale.sy, 0, 0];
-      var renderContext = {
-        canvasContext: ctx,
-        transform: transform,
-        viewport: this.viewport,
-        // intent: 'default', // === 'display'
-      };
-      var renderTask = this.renderTask = this.pdfPage.render(renderContext);
-      renderTask.onContinue = renderContinueCallback;
-
-      this.renderTask.promise.then(
-        function pdfPageRenderCallback() {
-          pageViewDrawCallback(null);
-          if (textLayer) {
-            self.pdfPage.getTextContent({ normalizeWhitespace: true }).then(
-              function textContentResolved(textContent) {
-                textLayer.setTextContent(textContent);
-                textLayer.render(TEXT_LAYER_RENDER_DELAY);
-              }
-            );
-          }
-        },
-        function pdfPageRenderError(error) {
-          pageViewDrawCallback(error);
-        }
-      );
-
-      if (this.annotationLayerFactory) {
-        if (!this.annotationLayer) {
-          this.annotationLayer = this.annotationLayerFactory.
-            createAnnotationLayerBuilder(div, this.pdfPage);
-        }
-        this.annotationLayer.render(this.viewport, 'display');
-      }
-      div.setAttribute('data-loaded', true);
-
-      if (self.onBeforeDraw) {
-        self.onBeforeDraw();
-      }
-      return promise;
-    },
-
-    beforePrint: function PDFPageView_beforePrint() {
-      var CustomStyle = pdfjsLib.CustomStyle;
-      var pdfPage = this.pdfPage;
-
-      var viewport = pdfPage.getViewport(1);
-      // Use the same hack we use for high dpi displays for printing to get
-      // better output until bug 811002 is fixed in FF.
-      var PRINT_OUTPUT_SCALE = 2;
-      var canvas = document.createElement('canvas');
-
-      // The logical size of the canvas.
-      canvas.width = Math.floor(viewport.width) * PRINT_OUTPUT_SCALE;
-      canvas.height = Math.floor(viewport.height) * PRINT_OUTPUT_SCALE;
-
-      // The rendered size of the canvas, relative to the size of canvasWrapper.
-      canvas.style.width = (PRINT_OUTPUT_SCALE * 100) + '%';
-
-      var cssScale = 'scale(' + (1 / PRINT_OUTPUT_SCALE) + ', ' +
-                                (1 / PRINT_OUTPUT_SCALE) + ')';
-      CustomStyle.setProp('transform' , canvas, cssScale);
-      CustomStyle.setProp('transformOrigin' , canvas, '0% 0%');
-
-      var printContainer = document.getElementById('printContainer');
-      var canvasWrapper = document.createElement('div');
-      canvasWrapper.appendChild(canvas);
-      printContainer.appendChild(canvasWrapper);
-
-      canvas.mozPrintCallback = function(obj) {
-        var ctx = obj.context;
-
-        ctx.save();
-        ctx.fillStyle = 'rgb(255, 255, 255)';
-        ctx.fillRect(0, 0, canvas.width, canvas.height);
-        ctx.restore();
-        ctx.scale(PRINT_OUTPUT_SCALE, PRINT_OUTPUT_SCALE);
-
-        var renderContext = {
-          canvasContext: ctx,
-          viewport: viewport,
-          intent: 'print'
-        };
-
-        pdfPage.render(renderContext).promise.then(function() {
-          // Tell the printEngine that rendering this canvas/page has finished.
-          obj.done();
-        }, function(error) {
-          console.error(error);
-          // Tell the printEngine that rendering this canvas/page has failed.
-          // This will make the print proces stop.
-          if ('abort' in obj) {
-            obj.abort();
-          } else {
-            obj.done();
-          }
-        });
-      };
-    },
-  };
-
-  return PDFPageView;
-})();
-
-exports.PDFPageView = PDFPageView;
-}));
-
-
-(function (root, factory) {
-  {
     factory((root.pdfjsWebPDFThumbnailView = {}), root.pdfjsWebUIUtils,
       root.pdfjsWebPDFRenderingQueue);
   }
 }(this, function (exports, uiUtils, pdfRenderingQueue) {
 
 var mozL10n = uiUtils.mozL10n;
 var getOutputScale = uiUtils.getOutputScale;
 var RenderingStates = pdfRenderingQueue.RenderingStates;
@@ -5182,117 +3697,140 @@ exports.PDFThumbnailView = PDFThumbnailV
 
 (function (root, factory) {
   {
     factory((root.pdfjsWebSecondaryToolbar = {}), root.pdfjsWebUIUtils);
   }
 }(this, function (exports, uiUtils) {
 
 var SCROLLBAR_PADDING = uiUtils.SCROLLBAR_PADDING;
-
-var app; // Avoiding circular reference, see _setApp function below.
-var PDFViewerApplication = null; // = app.PDFViewerApplication;
+var mozL10n = uiUtils.mozL10n;
 
 var SecondaryToolbar = {
   opened: false,
   previousContainerHeight: null,
   newContainerHeight: null,
 
-  initialize: function secondaryToolbarInitialize(options) {
+  initialize: function secondaryToolbarInitialize(options, eventBus) {
+    this.eventBus = eventBus;
     this.toolbar = options.toolbar;
     this.buttonContainer = this.toolbar.firstElementChild;
 
     // Define the toolbar buttons.
     this.toggleButton = options.toggleButton;
     this.presentationModeButton = options.presentationModeButton;
     this.openFile = options.openFile;
     this.print = options.print;
     this.download = options.download;
     this.viewBookmark = options.viewBookmark;
     this.firstPage = options.firstPage;
     this.lastPage = options.lastPage;
     this.pageRotateCw = options.pageRotateCw;
     this.pageRotateCcw = options.pageRotateCcw;
+    this.toggleHandTool = options.toggleHandTool;
     this.documentPropertiesButton = options.documentPropertiesButton;
 
     // Attach the event listeners.
     var elements = [
       // Button to toggle the visibility of the secondary toolbar:
       { element: this.toggleButton, handler: this.toggle },
       // All items within the secondary toolbar
-      // (except for toggleHandTool, hand_tool.js is responsible for it):
       { element: this.presentationModeButton,
         handler: this.presentationModeClick },
       { element: this.openFile, handler: this.openFileClick },
       { element: this.print, handler: this.printClick },
       { element: this.download, handler: this.downloadClick },
       { element: this.viewBookmark, handler: this.viewBookmarkClick },
       { element: this.firstPage, handler: this.firstPageClick },
       { element: this.lastPage, handler: this.lastPageClick },
       { element: this.pageRotateCw, handler: this.pageRotateCwClick },
       { element: this.pageRotateCcw, handler: this.pageRotateCcwClick },
+      { element: this.toggleHandTool, handler: this.toggleHandToolClick },
       { element: this.documentPropertiesButton,
         handler: this.documentPropertiesClick }
     ];
 
     for (var item in elements) {
       var element = elements[item].element;
       if (element) {
         element.addEventListener('click', elements[item].handler.bind(this));
       }
     }
+
+    // Tracking hand tool menu item changes.
+    var isHandToolActive = false;
+    this.eventBus.on('handtoolchanged', function (e) {
+      if (isHandToolActive === e.isActive) {
+        return;
+      }
+      isHandToolActive = e.isActive;
+      if (isHandToolActive) {
+        this.toggleHandTool.title =
+          mozL10n.get('hand_tool_disable.title', null, 'Disable hand tool');
+        this.toggleHandTool.firstElementChild.textContent =
+          mozL10n.get('hand_tool_disable_label', null, 'Disable hand tool');
+      } else {
+        this.toggleHandTool.title =
+          mozL10n.get('hand_tool_enable.title', null, 'Enable hand tool');
+        this.toggleHandTool.firstElementChild.textContent =
+          mozL10n.get('hand_tool_enable_label', null, 'Enable hand tool');
+      }
+    }.bind(this));
   },
 
   // Event handling functions.
   presentationModeClick: function secondaryToolbarPresentationModeClick(evt) {
-    PDFViewerApplication.requestPresentationMode();
+    this.eventBus.dispatch('presentationmode');
     this.close();
   },
 
   openFileClick: function secondaryToolbarOpenFileClick(evt) {
-    document.getElementById('fileInput').click();
+    this.eventBus.dispatch('openfile');
     this.close();
   },
 
   printClick: function secondaryToolbarPrintClick(evt) {
-    window.print();
+    this.eventBus.dispatch('print');
     this.close();
   },
 
   downloadClick: function secondaryToolbarDownloadClick(evt) {
-    PDFViewerApplication.download();
+    this.eventBus.dispatch('download');
     this.close();
   },
 
   viewBookmarkClick: function secondaryToolbarViewBookmarkClick(evt) {
     this.close();
   },
 
   firstPageClick: function secondaryToolbarFirstPageClick(evt) {
-    PDFViewerApplication.page = 1;
+    this.eventBus.dispatch('firstpage');
     this.close();
   },
 
   lastPageClick: function secondaryToolbarLastPageClick(evt) {
-    if (PDFViewerApplication.pdfDocument) {
-      PDFViewerApplication.page = PDFViewerApplication.pagesCount;
-    }
+    this.eventBus.dispatch('lastpage');
     this.close();
   },
 
   pageRotateCwClick: function secondaryToolbarPageRotateCwClick(evt) {
-    PDFViewerApplication.rotatePages(90);
+    this.eventBus.dispatch('rotatecw');
   },
 
   pageRotateCcwClick: function secondaryToolbarPageRotateCcwClick(evt) {
-    PDFViewerApplication.rotatePages(-90);
+    this.eventBus.dispatch('rotateccw');
+  },
+
+  toggleHandToolClick: function secondaryToolbarToggleHandToolClick(evt) {
+    this.eventBus.dispatch('togglehandtool');
+    this.close();
   },
 
   documentPropertiesClick: function secondaryToolbarDocumentPropsClick(evt) {
-    PDFViewerApplication.pdfDocumentProperties.open();
+    this.eventBus.dispatch('documentproperties');
     this.close();
   },
 
   // Misc. functions for interacting with the toolbar.
   setMaxHeight: function secondaryToolbarSetMaxHeight(container) {
     if (!container || !this.buttonContainer) {
       return;
     }
@@ -5329,255 +3867,17 @@ var SecondaryToolbar = {
     if (this.opened) {
       this.close();
     } else {
       this.open();
     }
   }
 };
 
-function _setApp(app_) {
-  app = app_;
-  PDFViewerApplication = app.PDFViewerApplication;
-}
-
 exports.SecondaryToolbar = SecondaryToolbar;
-exports._setApp = _setApp;
-}));
-
-
-(function (root, factory) {
-  {
-    factory((root.pdfjsWebAnnotationLayerBuilder = {}), root.pdfjsWebUIUtils,
-      root.pdfjsWebPDFLinkService, root.pdfjsWebPDFJS);
-  }
-}(this, function (exports, uiUtils, pdfLinkService, pdfjsLib) {
-
-var mozL10n = uiUtils.mozL10n;
-var SimpleLinkService = pdfLinkService.SimpleLinkService;
-
-/**
- * @typedef {Object} AnnotationLayerBuilderOptions
- * @property {HTMLDivElement} pageDiv
- * @property {PDFPage} pdfPage
- * @property {IPDFLinkService} linkService
- * @property {DownloadManager} downloadManager
- */
-
-/**
- * @class
- */
-var AnnotationLayerBuilder = (function AnnotationLayerBuilderClosure() {
-  /**
-   * @param {AnnotationLayerBuilderOptions} options
-   * @constructs AnnotationLayerBuilder
-   */
-  function AnnotationLayerBuilder(options) {
-    this.pageDiv = options.pageDiv;
-    this.pdfPage = options.pdfPage;
-    this.linkService = options.linkService;
-    this.downloadManager = options.downloadManager;
-
-    this.div = null;
-  }
-
-  AnnotationLayerBuilder.prototype =
-      /** @lends AnnotationLayerBuilder.prototype */ {
-
-    /**
-     * @param {PageViewport} viewport
-     * @param {string} intent (default value is 'display')
-     */
-    render: function AnnotationLayerBuilder_render(viewport, intent) {
-      var self = this;
-      var parameters = {
-        intent: (intent === undefined ? 'display' : intent),
-      };
-
-      this.pdfPage.getAnnotations(parameters).then(function (annotations) {
-        viewport = viewport.clone({ dontFlip: true });
-        parameters = {
-          viewport: viewport,
-          div: self.div,
-          annotations: annotations,
-          page: self.pdfPage,
-          linkService: self.linkService,
-          downloadManager: self.downloadManager
-        };
-
-        if (self.div) {
-          // If an annotationLayer already exists, refresh its children's
-          // transformation matrices.
-          pdfjsLib.AnnotationLayer.update(parameters);
-        } else {
-          // Create an annotation layer div and render the annotations
-          // if there is at least one annotation.
-          if (annotations.length === 0) {
-            return;
-          }
-
-          self.div = document.createElement('div');
-          self.div.className = 'annotationLayer';
-          self.pageDiv.appendChild(self.div);
-          parameters.div = self.div;
-
-          pdfjsLib.AnnotationLayer.render(parameters);
-          if (typeof mozL10n !== 'undefined') {
-            mozL10n.translate(self.div);
-          }
-        }
-      });
-    },
-
-    hide: function AnnotationLayerBuilder_hide() {
-      if (!this.div) {
-        return;
-      }
-      this.div.setAttribute('hidden', 'true');
-    }
-  };
-
-  return AnnotationLayerBuilder;
-})();
-
-/**
- * @constructor
- * @implements IPDFAnnotationLayerFactory
- */
-function DefaultAnnotationLayerFactory() {}
-DefaultAnnotationLayerFactory.prototype = {
-  /**
-   * @param {HTMLDivElement} pageDiv
-   * @param {PDFPage} pdfPage
-   * @returns {AnnotationLayerBuilder}
-   */
-  createAnnotationLayerBuilder: function (pageDiv, pdfPage) {
-    return new AnnotationLayerBuilder({
-      pageDiv: pageDiv,
-      pdfPage: pdfPage,
-      linkService: new SimpleLinkService(),
-    });
-  }
-};
-
-exports.AnnotationLayerBuilder = AnnotationLayerBuilder;
-exports.DefaultAnnotationLayerFactory = DefaultAnnotationLayerFactory;
-}));
-
-
-(function (root, factory) {
-  {
-    factory((root.pdfjsWebHandTool = {}), root.pdfjsWebUIUtils,
-      root.pdfjsWebGrabToPan, root.pdfjsWebPreferences,
-      root.pdfjsWebSecondaryToolbar);
-  }
-}(this, function (exports, uiUtils, grabToPan, preferences, secondaryToolbar) {
-
-var mozL10n = uiUtils.mozL10n;
-var GrabToPan = grabToPan.GrabToPan;
-var Preferences = preferences.Preferences;
-var SecondaryToolbar = secondaryToolbar.SecondaryToolbar;
-
-/**
- * @typedef {Object} HandToolOptions
- * @property {HTMLDivElement} container - The document container.
- * @property {HTMLButtonElement} toggleHandTool - The button element for
- *                                                toggling the hand tool.
- */
-
-/**
- * @class
- */
-var HandTool = (function HandToolClosure() {
-  /**
-   * @constructs HandTool
-   * @param {HandToolOptions} options
-   */
-  function HandTool(options) {
-    this.container = options.container;
-    this.toggleHandTool = options.toggleHandTool;
-
-    this.wasActive = false;
-
-    this.handTool = new GrabToPan({
-      element: this.container,
-      onActiveChanged: function(isActive) {
-        if (!this.toggleHandTool) {
-          return;
-        }
-        if (isActive) {
-          this.toggleHandTool.title =
-            mozL10n.get('hand_tool_disable.title', null, 'Disable hand tool');
-          this.toggleHandTool.firstElementChild.textContent =
-            mozL10n.get('hand_tool_disable_label', null, 'Disable hand tool');
-        } else {
-          this.toggleHandTool.title =
-            mozL10n.get('hand_tool_enable.title', null, 'Enable hand tool');
-          this.toggleHandTool.firstElementChild.textContent =
-            mozL10n.get('hand_tool_enable_label', null, 'Enable hand tool');
-        }
-      }.bind(this)
-    });
-
-    if (this.toggleHandTool) {
-      this.toggleHandTool.addEventListener('click', this.toggle.bind(this));
-
-      window.addEventListener('localized', function (evt) {
-        Preferences.get('enableHandToolOnLoad').then(function resolved(value) {
-          if (value) {
-            this.handTool.activate();
-          }
-        }.bind(this), function rejected(reason) {});
-      }.bind(this));
-
-      window.addEventListener('presentationmodechanged', function (evt) {
-        if (evt.detail.switchInProgress) {
-          return;
-        }
-        if (evt.detail.active) {
-          this.enterPresentationMode();
-        } else {
-          this.exitPresentationMode();
-        }
-      }.bind(this));
-    }
-  }
-
-  HandTool.prototype = {
-    /**
-     * @return {boolean}
-     */
-    get isActive() {
-      return !!this.handTool.active;
-    },
-
-    toggle: function HandTool_toggle() {
-      this.handTool.toggle();
-      SecondaryToolbar.close();
-    },
-
-    enterPresentationMode: function HandTool_enterPresentationMode() {
-      if (this.isActive) {
-        this.wasActive = true;
-        this.handTool.deactivate();
-      }
-    },
-
-    exitPresentationMode: function HandTool_exitPresentationMode() {
-      if (this.wasActive) {
-        this.wasActive = false;
-        this.handTool.activate();
-      }
-    }
-  };
-
-  return HandTool;
-})();
-
-exports.HandTool = HandTool;
 }));
 
 
 (function (root, factory) {
   {
     factory((root.pdfjsWebPDFFindBar = {}), root.pdfjsWebUIUtils,
       root.pdfjsWebPDFFindController);
   }
@@ -5601,16 +3901,17 @@ var PDFFindBar = (function PDFFindBarClo
     this.highlightAll = options.highlightAllCheckbox || null;
     this.caseSensitive = options.caseSensitiveCheckbox || null;
     this.findMsg = options.findMsg || null;
     this.findResultsCount = options.findResultsCount || null;
     this.findStatusIcon = options.findStatusIcon || null;
     this.findPreviousButton = options.findPreviousButton || null;
     this.findNextButton = options.findNextButton || null;
     this.findController = options.findController || null;
+    this.eventBus = options.eventBus;
 
     if (this.findController === null) {
       throw new Error('PDFFindBar cannot be used without a ' +
                       'PDFFindController instance.');
     }
 
     // Add event listeners to the DOM elements.
     var self = this;
@@ -5653,24 +3954,24 @@ var PDFFindBar = (function PDFFindBarClo
   }
 
   PDFFindBar.prototype = {
     reset: function PDFFindBar_reset() {
       this.updateUIState();
     },
 
     dispatchEvent: function PDFFindBar_dispatchEvent(type, findPrev) {
-      var event = document.createEvent('CustomEvent');
-      event.initCustomEvent('find' + type, true, true, {
+      this.eventBus.dispatch('find', {
+        source: this,
+        type: type,
         query: this.findField.value,
         caseSensitive: this.caseSensitive.checked,
         highlightAll: this.highlightAll.checked,
         findPrevious: findPrev
       });
-      return window.dispatchEvent(event);
     },
 
     updateUIState:
         function PDFFindBar_updateUIState(state, previous, matchCount) {
       var notFound = false;
       var findMsg = '';
       var status = '';
 
@@ -5760,16 +4061,1334 @@ var PDFFindBar = (function PDFFindBarClo
 })();
 
 exports.PDFFindBar = PDFFindBar;
 }));
 
 
 (function (root, factory) {
   {
+    factory((root.pdfjsWebPDFHistory = {}), root.pdfjsWebDOMEvents);
+  }
+}(this, function (exports, domEvents) {
+
+  function PDFHistory(options) {
+    this.linkService = options.linkService;
+    this.eventBus = options.eventBus || domEvents.getGlobalEventBus();
+
+    this.initialized = false;
+    this.initialDestination = null;
+    this.initialBookmark = null;
+  }
+
+  PDFHistory.prototype = {
+    /**
+     * @param {string} fingerprint
+     * @param {IPDFLinkService} linkService
+     */
+    initialize: function pdfHistoryInitialize(fingerprint) {
+      this.initialized = true;
+      this.reInitialized = false;
+      this.allowHashChange = true;
+      this.historyUnlocked = true;
+      this.isViewerInPresentationMode = false;
+
+      this.previousHash = window.location.hash.substring(1);
+      this.currentBookmark = '';
+      this.currentPage = 0;
+      this.updatePreviousBookmark = false;
+      this.previousBookmark = '';
+      this.previousPage = 0;
+      this.nextHashParam = '';
+
+      this.fingerprint = fingerprint;
+      this.currentUid = this.uid = 0;
+      this.current = {};
+
+      var state = window.history.state;
+      if (this._isStateObjectDefined(state)) {
+        // This corresponds to navigating back to the document
+        // from another page in the browser history.
+        if (state.target.dest) {
+          this.initialDestination = state.target.dest;
+        } else {
+          this.initialBookmark = state.target.hash;
+        }
+        this.currentUid = state.uid;
+        this.uid = state.uid + 1;
+        this.current = state.target;
+      } else {
+        // This corresponds to the loading of a new document.
+        if (state && state.fingerprint &&
+          this.fingerprint !== state.fingerprint) {
+          // Reinitialize the browsing history when a new document
+          // is opened in the web viewer.
+          this.reInitialized = true;
+        }
+        this._pushOrReplaceState({fingerprint: this.fingerprint}, true);
+      }
+
+      var self = this;
+      window.addEventListener('popstate', function pdfHistoryPopstate(evt) {
+        if (!self.historyUnlocked) {
+          return;
+        }
+        if (evt.state) {
+          // Move back/forward in the history.
+          self._goTo(evt.state);
+          return;
+        }
+
+        // If the state is not set, then the user tried to navigate to a
+        // different hash by manually editing the URL and pressing Enter, or by
+        // clicking on an in-page link (e.g. the "current view" link).
+        // Save the current view state to the browser history.
+
+        // Note: In Firefox, history.null could also be null after an in-page
+        // navigation to the same URL, and without dispatching the popstate
+        // event: https://bugzilla.mozilla.org/show_bug.cgi?id=1183881
+
+        if (self.uid === 0) {
+          // Replace the previous state if it was not explicitly set.
+          var previousParams = (self.previousHash && self.currentBookmark &&
+            self.previousHash !== self.currentBookmark) ?
+            {hash: self.currentBookmark, page: self.currentPage} :
+            {page: 1};
+          replacePreviousHistoryState(previousParams, function() {
+            updateHistoryWithCurrentHash();
+          });
+        } else {
+          updateHistoryWithCurrentHash();
+        }
+      }, false);
+
+
+      function updateHistoryWithCurrentHash() {
+        self.previousHash = window.location.hash.slice(1);
+        self._pushToHistory({hash: self.previousHash}, false, true);
+        self._updatePreviousBookmark();
+      }
+
+      function replacePreviousHistoryState(params, callback) {
+        // To modify the previous history entry, the following happens:
+        // 1. history.back()
+        // 2. _pushToHistory, which calls history.replaceState( ... )
+        // 3. history.forward()
+        // Because a navigation via the history API does not immediately update
+        // the history state, the popstate event is used for synchronization.
+        self.historyUnlocked = false;
+
+        // Suppress the hashchange event to avoid side effects caused by
+        // navigating back and forward.
+        self.allowHashChange = false;
+        window.addEventListener('popstate', rewriteHistoryAfterBack);
+        history.back();
+
+        function rewriteHistoryAfterBack() {
+          window.removeEventListener('popstate', rewriteHistoryAfterBack);
+          window.addEventListener('popstate', rewriteHistoryAfterForward);
+          self._pushToHistory(params, false, true);
+          history.forward();
+        }
+        function rewriteHistoryAfterForward() {
+          window.removeEventListener('popstate', rewriteHistoryAfterForward);
+          self.allowHashChange = true;
+          self.historyUnlocked = true;
+          callback();
+        }
+      }
+
+      function pdfHistoryBeforeUnload() {
+        var previousParams = self._getPreviousParams(null, true);
+        if (previousParams) {
+          var replacePrevious = (!self.current.dest &&
+          self.current.hash !== self.previousHash);
+          self._pushToHistory(previousParams, false, replacePrevious);
+          self._updatePreviousBookmark();
+        }
+        // Remove the event listener when navigating away from the document,
+        // since 'beforeunload' prevents Firefox from caching the document.
+        window.removeEventListener('beforeunload', pdfHistoryBeforeUnload,
+                                   false);
+      }
+
+      window.addEventListener('beforeunload', pdfHistoryBeforeUnload, false);
+
+      window.addEventListener('pageshow', function pdfHistoryPageShow(evt) {
+        // If the entire viewer (including the PDF file) is cached in
+        // the browser, we need to reattach the 'beforeunload' event listener
+        // since the 'DOMContentLoaded' event is not fired on 'pageshow'.
+        window.addEventListener('beforeunload', pdfHistoryBeforeUnload, false);
+      }, false);
+
+      self.eventBus.on('presentationmodechanged', function(e) {
+        self.isViewerInPresentationMode = e.active;
+      });
+    },
+
+    clearHistoryState: function pdfHistory_clearHistoryState() {
+      this._pushOrReplaceState(null, true);
+    },
+
+    _isStateObjectDefined: function pdfHistory_isStateObjectDefined(state) {
+      return (state && state.uid >= 0 &&
+      state.fingerprint && this.fingerprint === state.fingerprint &&
+      state.target && state.target.hash) ? true : false;
+    },
+
+    _pushOrReplaceState: function pdfHistory_pushOrReplaceState(stateObj,
+                                                                replace) {
+      if (replace) {
+      window.history.replaceState(stateObj, '');
+      } else {
+      window.history.pushState(stateObj, '');
+      }
+    },
+
+    get isHashChangeUnlocked() {
+      if (!this.initialized) {
+        return true;
+      }
+      return this.allowHashChange;
+    },
+
+    _updatePreviousBookmark: function pdfHistory_updatePreviousBookmark() {
+      if (this.updatePreviousBookmark &&
+        this.currentBookmark && this.currentPage) {
+        this.previousBookmark = this.currentBookmark;
+        this.previousPage = this.currentPage;
+        this.updatePreviousBookmark = false;
+      }
+    },
+
+    updateCurrentBookmark: function pdfHistoryUpdateCurrentBookmark(bookmark,
+                                                                    pageNum) {
+      if (this.initialized) {
+        this.currentBookmark = bookmark.substring(1);
+        this.currentPage = pageNum | 0;
+        this._updatePreviousBookmark();
+      }
+    },
+
+    updateNextHashParam: function pdfHistoryUpdateNextHashParam(param) {
+      if (this.initialized) {
+        this.nextHashParam = param;
+      }
+    },
+
+    push: function pdfHistoryPush(params, isInitialBookmark) {
+      if (!(this.initialized && this.historyUnlocked)) {
+        return;
+      }
+      if (params.dest && !params.hash) {
+        params.hash = (this.current.hash && this.current.dest &&
+        this.current.dest === params.dest) ?
+          this.current.hash :
+          this.linkService.getDestinationHash(params.dest).split('#')[1];
+      }
+      if (params.page) {
+        params.page |= 0;
+      }
+      if (isInitialBookmark) {
+        var target = window.history.state.target;
+        if (!target) {
+          // Invoked when the user specifies an initial bookmark,
+          // thus setting initialBookmark, when the document is loaded.
+          this._pushToHistory(params, false);
+          this.previousHash = window.location.hash.substring(1);
+        }
+        this.updatePreviousBookmark = this.nextHashParam ? false : true;
+        if (target) {
+          // If the current document is reloaded,
+          // avoid creating duplicate entries in the history.
+          this._updatePreviousBookmark();
+        }
+        return;
+      }
+      if (this.nextHashParam) {
+        if (this.nextHashParam === params.hash) {
+          this.nextHashParam = null;
+          this.updatePreviousBookmark = true;
+          return;
+        } else {
+          this.nextHashParam = null;
+        }
+      }
+
+      if (params.hash) {
+        if (this.current.hash) {
+          if (this.current.hash !== params.hash) {
+            this._pushToHistory(params, true);
+          } else {
+            if (!this.current.page && params.page) {
+              this._pushToHistory(params, false, true);
+            }
+            this.updatePreviousBookmark = true;
+          }
+        } else {
+          this._pushToHistory(params, true);
+        }
+      } else if (this.current.page && params.page &&
+        this.current.page !== params.page) {
+        this._pushToHistory(params, true);
+      }
+    },
+
+    _getPreviousParams: function pdfHistory_getPreviousParams(onlyCheckPage,
+                                                              beforeUnload) {
+      if (!(this.currentBookmark && this.currentPage)) {
+        return null;
+      } else if (this.updatePreviousBookmark) {
+        this.updatePreviousBookmark = false;
+      }
+      if (this.uid > 0 && !(this.previousBookmark && this.previousPage)) {
+        // Prevent the history from getting stuck in the current state,
+        // effectively preventing the user from going back/forward in
+        // the history.
+        //
+        // This happens if the current position in the document didn't change
+        // when the history was previously updated. The reasons for this are
+        // either:
+        // 1. The current zoom value is such that the document does not need to,
+        //    or cannot, be scrolled to display the destination.
+        // 2. The previous destination is broken, and doesn't actally point to a
+        //    position within the document.
+        //    (This is either due to a bad PDF generator, or the user making a
+        //     mistake when entering a destination in the hash parameters.)
+        return null;
+      }
+      if ((!this.current.dest && !onlyCheckPage) || beforeUnload) {
+        if (this.previousBookmark === this.currentBookmark) {
+          return null;
+        }
+      } else if (this.current.page || onlyCheckPage) {
+        if (this.previousPage === this.currentPage) {
+          return null;
+        }
+      } else {
+        return null;
+      }
+      var params = {hash: this.currentBookmark, page: this.currentPage};
+      if (this.isViewerInPresentationMode) {
+        params.hash = null;
+      }
+      return params;
+    },
+
+    _stateObj: function pdfHistory_stateObj(params) {
+      return {fingerprint: this.fingerprint, uid: this.uid, target: params};
+    },
+
+    _pushToHistory: function pdfHistory_pushToHistory(params,
+                                                      addPrevious, overwrite) {
+      if (!this.initialized) {
+        return;
+      }
+      if (!params.hash && params.page) {
+        params.hash = ('page=' + params.page);
+      }
+      if (addPrevious && !overwrite) {
+        var previousParams = this._getPreviousParams();
+        if (previousParams) {
+          var replacePrevious = (!this.current.dest &&
+          this.current.hash !== this.previousHash);
+          this._pushToHistory(previousParams, false, replacePrevious);
+        }
+      }
+      this._pushOrReplaceState(this._stateObj(params),
+        (overwrite || this.uid === 0));
+      this.currentUid = this.uid++;
+      this.current = params;
+      this.updatePreviousBookmark = true;
+    },
+
+    _goTo: function pdfHistory_goTo(state) {
+      if (!(this.initialized && this.historyUnlocked &&
+        this._isStateObjectDefined(state))) {
+        return;
+      }
+      if (!this.reInitialized && state.uid < this.currentUid) {
+        var previousParams = this._getPreviousParams(true);
+        if (previousParams) {
+          this._pushToHistory(this.current, false);
+          this._pushToHistory(previousParams, false);
+          this.currentUid = state.uid;
+          window.history.back();
+          return;
+        }
+      }
+      this.historyUnlocked = false;
+
+      if (state.target.dest) {
+        this.linkService.navigateTo(state.target.dest);
+      } else {
+        this.linkService.setHash(state.target.hash);
+      }
+      this.currentUid = state.uid;
+      if (state.uid > this.uid) {
+        this.uid = state.uid;
+      }
+      this.current = state.target;
+      this.updatePreviousBookmark = true;
+
+      var currentHash = window.location.hash.substring(1);
+      if (this.previousHash !== currentHash) {
+        this.allowHashChange = false;
+      }
+      this.previousHash = currentHash;
+
+      this.historyUnlocked = true;
+    },
+
+    back: function pdfHistoryBack() {
+      this.go(-1);
+    },
+
+    forward: function pdfHistoryForward() {
+      this.go(1);
+    },
+
+    go: function pdfHistoryGo(direction) {
+      if (this.initialized && this.historyUnlocked) {
+        var state = window.history.state;
+        if (direction === -1 && state && state.uid > 0) {
+          window.history.back();
+        } else if (direction === 1 && state && state.uid < (this.uid - 1)) {
+          window.history.forward();
+        }
+      }
+    }
+  };
+
+  exports.PDFHistory = PDFHistory;
+}));
+
+
+(function (root, factory) {
+  {
+    factory((root.pdfjsWebPDFLinkService = {}), root.pdfjsWebUIUtils,
+      root.pdfjsWebDOMEvents);
+  }
+}(this, function (exports, uiUtils, domEvents) {
+
+var parseQueryString = uiUtils.parseQueryString;
+
+/**
+ * @typedef {Object} PDFLinkServiceOptions
+ * @property {EventBus} eventBus - The application event bus.
+ */
+
+/**
+ * Performs navigation functions inside PDF, such as opening specified page,
+ * or destination.
+ * @class
+ * @implements {IPDFLinkService}
+ */
+var PDFLinkService = (function () {
+  /**
+   * @constructs PDFLinkService
+   * @param {PDFLinkServiceOptions} options
+   */
+  function PDFLinkService(options) {
+    options = options || {};
+    this.eventBus = options.eventBus || domEvents.getGlobalEventBus();
+    this.baseUrl = null;
+    this.pdfDocument = null;
+    this.pdfViewer = null;
+    this.pdfHistory = null;
+
+    this._pagesRefCache = null;
+  }
+
+  PDFLinkService.prototype = {
+    setDocument: function PDFLinkService_setDocument(pdfDocument, baseUrl) {
+      this.baseUrl = baseUrl;
+      this.pdfDocument = pdfDocument;
+      this._pagesRefCache = Object.create(null);
+    },
+
+    setViewer: function PDFLinkService_setViewer(pdfViewer) {
+      this.pdfViewer = pdfViewer;
+    },
+
+    setHistory: function PDFLinkService_setHistory(pdfHistory) {
+      this.pdfHistory = pdfHistory;
+    },
+
+    /**
+     * @returns {number}
+     */
+    get pagesCount() {
+      return this.pdfDocument.numPages;
+    },
+
+    /**
+     * @returns {number}
+     */
+    get page() {
+      return this.pdfViewer.currentPageNumber;
+    },
+
+    /**
+     * @param {number} value
+     */
+    set page(value) {
+      this.pdfViewer.currentPageNumber = value;
+    },
+
+    /**
+     * @param dest - The PDF destination object.
+     */
+    navigateTo: function PDFLinkService_navigateTo(dest) {
+      var destString = '';
+      var self = this;
+
+      var goToDestination = function(destRef) {
+        // dest array looks like that: <page-ref> </XYZ|FitXXX> <args..>
+        var pageNumber = destRef instanceof Object ?
+          self._pagesRefCache[destRef.num + ' ' + destRef.gen + ' R'] :
+          (destRef + 1);
+        if (pageNumber) {
+          if (pageNumber > self.pagesCount) {
+            pageNumber = self.pagesCount;
+          }
+          self.pdfViewer.scrollPageIntoView(pageNumber, dest);
+
+          if (self.pdfHistory) {
+            // Update the browsing history.
+            self.pdfHistory.push({
+              dest: dest,
+              hash: destString,
+              page: pageNumber
+            });
+          }
+        } else {
+          self.pdfDocument.getPageIndex(destRef).then(function (pageIndex) {
+            var pageNum = pageIndex + 1;
+            var cacheKey = destRef.num + ' ' + destRef.gen + ' R';
+            self._pagesRefCache[cacheKey] = pageNum;
+            goToDestination(destRef);
+          });
+        }
+      };
+
+      var destinationPromise;
+      if (typeof dest === 'string') {
+        destString = dest;
+        destinationPromise = this.pdfDocument.getDestination(dest);
+      } else {
+        destinationPromise = Promise.resolve(dest);
+      }
+      destinationPromise.then(function(destination) {
+        dest = destination;
+        if (!(destination instanceof Array)) {
+          return; // invalid destination
+        }
+        goToDestination(destination[0]);
+      });
+    },
+
+    /**
+     * @param dest - The PDF destination object.
+     * @returns {string} The hyperlink to the PDF object.
+     */
+    getDestinationHash: function PDFLinkService_getDestinationHash(dest) {
+      if (typeof dest === 'string') {
+        return this.getAnchorUrl('#' + escape(dest));
+      }
+      if (dest instanceof Array) {
+        var destRef = dest[0]; // see navigateTo method for dest format
+        var pageNumber = destRef instanceof Object ?
+          this._pagesRefCache[destRef.num + ' ' + destRef.gen + ' R'] :
+          (destRef + 1);
+        if (pageNumber) {
+          var pdfOpenParams = this.getAnchorUrl('#page=' + pageNumber);
+          var destKind = dest[1];
+          if (typeof destKind === 'object' && 'name' in destKind &&
+              destKind.name === 'XYZ') {
+            var scale = (dest[4] || this.pdfViewer.currentScaleValue);
+            var scaleNumber = parseFloat(scale);
+            if (scaleNumber) {
+              scale = scaleNumber * 100;
+            }
+            pdfOpenParams += '&zoom=' + scale;
+            if (dest[2] || dest[3]) {
+              pdfOpenParams += ',' + (dest[2] || 0) + ',' + (dest[3] || 0);
+            }
+          }
+          return pdfOpenParams;
+        }
+      }
+      return this.getAnchorUrl('');
+    },
+
+    /**
+     * Prefix the full url on anchor links to make sure that links are resolved
+     * relative to the current URL instead of the one defined in <base href>.
+     * @param {String} anchor The anchor hash, including the #.
+     * @returns {string} The hyperlink to the PDF object.
+     */
+    getAnchorUrl: function PDFLinkService_getAnchorUrl(anchor) {
+      return (this.baseUrl || '') + anchor;
+    },
+
+    /**
+     * @param {string} hash
+     */
+    setHash: function PDFLinkService_setHash(hash) {
+      if (hash.indexOf('=') >= 0) {
+        var params = parseQueryString(hash);
+        // borrowing syntax from "Parameters for Opening PDF Files"
+        if ('nameddest' in params) {
+          if (this.pdfHistory) {
+            this.pdfHistory.updateNextHashParam(params.nameddest);
+          }
+          this.navigateTo(params.nameddest);
+          return;
+        }
+        var pageNumber, dest;
+        if ('page' in params) {
+          pageNumber = (params.page | 0) || 1;
+        }
+        if ('zoom' in params) {
+          // Build the destination array.
+          var zoomArgs = params.zoom.split(','); // scale,left,top
+          var zoomArg = zoomArgs[0];
+          var zoomArgNumber = parseFloat(zoomArg);
+
+          if (zoomArg.indexOf('Fit') === -1) {
+            // If the zoomArg is a number, it has to get divided by 100. If it's
+            // a string, it should stay as it is.
+            dest = [null, { name: 'XYZ' },
+                    zoomArgs.length > 1 ? (zoomArgs[1] | 0) : null,
+                    zoomArgs.length > 2 ? (zoomArgs[2] | 0) : null,
+                    (zoomArgNumber ? zoomArgNumber / 100 : zoomArg)];
+          } else {
+            if (zoomArg === 'Fit' || zoomArg === 'FitB') {
+              dest = [null, { name: zoomArg }];
+            } else if ((zoomArg === 'FitH' || zoomArg === 'FitBH') ||
+                       (zoomArg === 'FitV' || zoomArg === 'FitBV')) {
+              dest = [null, { name: zoomArg },
+                      zoomArgs.length > 1 ? (zoomArgs[1] | 0) : null];
+            } else if (zoomArg === 'FitR') {
+              if (zoomArgs.length !== 5) {
+                console.error('PDFLinkService_setHash: ' +
+                              'Not enough parameters for \'FitR\'.');
+              } else {
+                dest = [null, { name: zoomArg },
+                        (zoomArgs[1] | 0), (zoomArgs[2] | 0),
+                        (zoomArgs[3] | 0), (zoomArgs[4] | 0)];
+              }
+            } else {
+              console.error('PDFLinkService_setHash: \'' + zoomArg +
+                            '\' is not a valid zoom value.');
+            }
+          }
+        }
+        if (dest) {
+          this.pdfViewer.scrollPageIntoView(pageNumber || this.page, dest);
+        } else if (pageNumber) {
+          this.page = pageNumber; // simple page
+        }
+        if ('pagemode' in params) {
+          this.eventBus.dispatch('pagemode', {
+            source: this,
+            mode: params.pagemode
+          });
+        }
+      } else if (/^\d+$/.test(hash)) { // page number
+        this.page = hash;
+      } else { // named destination
+        if (this.pdfHistory) {
+          this.pdfHistory.updateNextHashParam(unescape(hash));
+        }
+        this.navigateTo(unescape(hash));
+      }
+    },
+
+    /**
+     * @param {string} action
+     */
+    executeNamedAction: function PDFLinkService_executeNamedAction(action) {
+      // See PDF reference, table 8.45 - Named action
+      switch (action) {
+        case 'GoBack':
+          if (this.pdfHistory) {
+            this.pdfHistory.back();
+          }
+          break;
+
+        case 'GoForward':
+          if (this.pdfHistory) {
+            this.pdfHistory.forward();
+          }
+          break;
+
+        case 'NextPage':
+          this.page++;
+          break;
+
+        case 'PrevPage':
+          this.page--;
+          break;
+
+        case 'LastPage':
+          this.page = this.pagesCount;
+          break;
+
+        case 'FirstPage':
+          this.page = 1;
+          break;
+
+        default:
+          break; // No action according to spec
+      }
+
+      this.eventBus.dispatch('namedaction', {
+        source: this,
+        action: action
+      });
+    },
+
+    /**
+     * @param {number} pageNum - page number.
+     * @param {Object} pageRef - reference to the page.
+     */
+    cachePageRef: function PDFLinkService_cachePageRef(pageNum, pageRef) {
+      var refStr = pageRef.num + ' ' + pageRef.gen + ' R';
+      this._pagesRefCache[refStr] = pageNum;
+    }
+  };
+
+  return PDFLinkService;
+})();
+
+var SimpleLinkService = (function SimpleLinkServiceClosure() {
+  function SimpleLinkService() {}
+
+  SimpleLinkService.prototype = {
+    /**
+     * @returns {number}
+     */
+    get page() {
+      return 0;
+    },
+    /**
+     * @param {number} value
+     */
+    set page(value) {},
+    /**
+     * @param dest - The PDF destination object.
+     */
+    navigateTo: function (dest) {},
+    /**
+     * @param dest - The PDF destination object.
+     * @returns {string} The hyperlink to the PDF object.
+     */
+    getDestinationHash: function (dest) {
+      return '#';
+    },
+    /**
+     * @param hash - The PDF parameters/hash.
+     * @returns {string} The hyperlink to the PDF object.
+     */
+    getAnchorUrl: function (hash) {
+      return '#';
+    },
+    /**
+     * @param {string} hash
+     */
+    setHash: function (hash) {},
+    /**
+     * @param {string} action
+     */
+    executeNamedAction: function (action) {},
+    /**
+     * @param {number} pageNum - page number.
+     * @param {Object} pageRef - reference to the page.
+     */
+    cachePageRef: function (pageNum, pageRef) {}
+  };
+  return SimpleLinkService;
+})();
+
+exports.PDFLinkService = PDFLinkService;
+exports.SimpleLinkService = SimpleLinkService;
+}));
+
+
+(function (root, factory) {
+  {
+    factory((root.pdfjsWebPDFPageView = {}), root.pdfjsWebUIUtils,
+      root.pdfjsWebPDFRenderingQueue, root.pdfjsWebDOMEvents,
+      root.pdfjsWebPDFJS);
+  }
+}(this, function (exports, uiUtils, pdfRenderingQueue, domEvents, pdfjsLib) {
+
+var CSS_UNITS = uiUtils.CSS_UNITS;
+var DEFAULT_SCALE = uiUtils.DEFAULT_SCALE;
+var getOutputScale = uiUtils.getOutputScale;
+var approximateFraction = uiUtils.approximateFraction;