Merge autoland to mozilla-central. a=merge
authorCiure Andrei <aciure@mozilla.com>
Wed, 03 Oct 2018 14:19:01 +0300
changeset 495087 d7c13cd0825713f86d33f87e3698da285c30c4cf
parent 495075 775ad70be30122138b07b3928e1421187046afb0 (current diff)
parent 495086 9bdbc378f0371982a76400c02c19d68f90d828ee (diff)
child 495092 758cfb5e4ddc3e26584d67e81679ad6a9c3e914e
child 495118 8e41fdb9ddd83f18b58919fb01cead698598daeb
push id9984
push userffxbld-merge
push dateMon, 15 Oct 2018 21:07:35 +0000
treeherdermozilla-beta@183d27ea8570 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone64.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge autoland to mozilla-central. a=merge
--- a/caps/ContentPrincipal.cpp
+++ b/caps/ContentPrincipal.cpp
@@ -386,86 +386,119 @@ ContentPrincipal::SetDomain(nsIURI* aDom
     xpc::SetCompartmentChangedDocumentDomain(comp);
   };
   JS::IterateRealmsWithPrincipals(cx, principals, nullptr, cb);
 
   return NS_OK;
 }
 
 static nsresult
-GetBaseDomainHelper(const nsCOMPtr<nsIURI>& aCodebase,
-                    bool* aHasBaseDomain,
-                    nsACString& aBaseDomain)
+GetSpecialBaseDomain(const nsCOMPtr<nsIURI>& aCodebase,
+                     bool* aHandled,
+                     nsACString& aBaseDomain)
 {
-  *aHasBaseDomain = false;
+  *aHandled = false;
 
   // For a file URI, we return the file path.
   if (NS_URIIsLocalFile(aCodebase)) {
     nsCOMPtr<nsIURL> url = do_QueryInterface(aCodebase);
 
     if (url) {
+      *aHandled = true;
       return url->GetFilePath(aBaseDomain);
     }
   }
 
   bool hasNoRelativeFlag;
   nsresult rv = NS_URIChainHasFlags(aCodebase,
                                     nsIProtocolHandler::URI_NORELATIVE,
                                     &hasNoRelativeFlag);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   if (hasNoRelativeFlag) {
+    *aHandled = true;
     return aCodebase->GetSpec(aBaseDomain);
   }
 
-  *aHasBaseDomain = true;
-
-  // For everything else, we ask the TLD service via
-  // the ThirdPartyUtil.
-  nsCOMPtr<mozIThirdPartyUtil> thirdPartyUtil =
-    do_GetService(THIRDPARTYUTIL_CONTRACTID);
-  if (thirdPartyUtil) {
-    return thirdPartyUtil->GetBaseDomain(aCodebase, aBaseDomain);
-  }
-
   return NS_OK;
 }
 
 NS_IMETHODIMP
 ContentPrincipal::GetBaseDomain(nsACString& aBaseDomain)
 {
-  bool hasBaseDomain;
-  return GetBaseDomainHelper(mCodebase, &hasBaseDomain, aBaseDomain);
+  // Handle some special URIs first.
+  bool handled;
+  nsresult rv = GetSpecialBaseDomain(mCodebase, &handled, aBaseDomain);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  if (handled) {
+    return NS_OK;
+  }
+
+  // For everything else, we ask the TLD service via the ThirdPartyUtil.
+  nsCOMPtr<mozIThirdPartyUtil> thirdPartyUtil =
+    do_GetService(THIRDPARTYUTIL_CONTRACTID);
+  if (!thirdPartyUtil) {
+    return NS_ERROR_FAILURE;
+  }
+
+  return thirdPartyUtil->GetBaseDomain(mCodebase, aBaseDomain);
 }
 
 NS_IMETHODIMP
 ContentPrincipal::GetSiteOrigin(nsACString& aSiteOrigin)
 {
-  // Determine our base domain.
-  bool hasBaseDomain;
+  // Handle some special URIs first.
   nsAutoCString baseDomain;
-  nsresult rv = GetBaseDomainHelper(mCodebase, &hasBaseDomain, baseDomain);
+  bool handled;
+  nsresult rv = GetSpecialBaseDomain(mCodebase, &handled, baseDomain);
   NS_ENSURE_SUCCESS(rv, rv);
 
-  if (!hasBaseDomain) {
+  if (handled) {
     // This is a special URI ("file:", "about:", "view-source:", etc). Just
     // return the origin.
     return GetOrigin(aSiteOrigin);
   }
 
+  // For everything else, we ask the TLD service. Note that, unlike in
+  // GetBaseDomain, we don't use ThirdPartyUtil.getBaseDomain because if the
+  // host is an IP address that returns the raw address and we can't use it with
+  // SetHost below because SetHost expects '[' and ']' around IPv6 addresses.
+  // See bug 1491728.
+  nsCOMPtr<nsIEffectiveTLDService> tldService =
+    do_GetService(NS_EFFECTIVETLDSERVICE_CONTRACTID);
+  if (!tldService) {
+    return NS_ERROR_FAILURE;
+  }
+
+  bool gotBaseDomain = false;
+  rv = tldService->GetBaseDomain(mCodebase, 0, baseDomain);
+  if (NS_SUCCEEDED(rv)) {
+    gotBaseDomain = true;
+  } else {
+    // If this is an IP address or something like "localhost", we just continue
+    // with gotBaseDomain = false.
+    if (rv != NS_ERROR_HOST_IS_IP_ADDRESS &&
+        rv != NS_ERROR_INSUFFICIENT_DOMAIN_LEVELS) {
+      return rv;
+    }
+  }
+
   // NOTE: Calling `SetHostPort` with a portless domain is insufficient to clear
   // the port, so an extra `SetPort` call has to be made.
   nsCOMPtr<nsIURI> siteUri;
-  rv = NS_MutateURI(mCodebase)
-    .SetUserPass(EmptyCString())
-    .SetPort(-1)
-    .SetHostPort(baseDomain)
-    .Finalize(siteUri);
+  NS_MutateURI mutator(mCodebase);
+  mutator.SetUserPass(EmptyCString())
+         .SetPort(-1);
+  if (gotBaseDomain) {
+    mutator.SetHost(baseDomain);
+  }
+  rv = mutator.Finalize(siteUri);
   MOZ_ASSERT(NS_SUCCEEDED(rv), "failed to create siteUri");
   NS_ENSURE_SUCCESS(rv, rv);
 
   rv = GenerateOriginNoSuffixFromURI(siteUri, aSiteOrigin);
   MOZ_ASSERT(NS_SUCCEEDED(rv), "failed to create siteOriginNoSuffix");
   NS_ENSURE_SUCCESS(rv, rv);
 
   nsAutoCString suffix;
--- a/dom/animation/Animation.cpp
+++ b/dom/animation/Animation.cpp
@@ -369,27 +369,31 @@ Animation::SetPlaybackRate(double aPlayb
 // https://drafts.csswg.org/web-animations/#seamlessly-update-the-playback-rate
 void
 Animation::UpdatePlaybackRate(double aPlaybackRate)
 {
   if (mPendingPlaybackRate && mPendingPlaybackRate.value() == aPlaybackRate) {
     return;
   }
 
+  // Calculate the play state using the existing playback rate since below we
+  // want to know if the animation is _currently_ finished or not, not whether
+  // it _will_ be finished.
+  AnimationPlayState playState = PlayState();
+
   mPendingPlaybackRate = Some(aPlaybackRate);
 
   // If we already have a pending task, there is nothing more to do since the
   // playback rate will be applied then.
   if (Pending()) {
     return;
   }
 
   AutoMutationBatchForAnimation mb(*this);
 
-  AnimationPlayState playState = PlayState();
   if (playState == AnimationPlayState::Idle ||
       playState == AnimationPlayState::Paused) {
     // We are either idle or paused. In either case we can apply the pending
     // playback rate immediately.
     ApplyPendingPlaybackRate();
 
     // We don't need to update timing or post an update here because:
     //
@@ -452,19 +456,20 @@ Animation::PlayState() const
     return AnimationPlayState::Idle;
   }
 
   if (mPendingState == PendingState::PausePending ||
       (mStartTime.IsNull() && !Pending())) {
     return AnimationPlayState::Paused;
   }
 
+  double playbackRate = CurrentOrPendingPlaybackRate();
   if (!currentTime.IsNull() &&
-      ((mPlaybackRate > 0.0 && currentTime.Value() >= EffectEnd()) ||
-       (mPlaybackRate < 0.0 && currentTime.Value() <= TimeDuration())))  {
+      ((playbackRate > 0.0 && currentTime.Value() >= EffectEnd()) ||
+       (playbackRate < 0.0 && currentTime.Value() <= TimeDuration())))  {
     return AnimationPlayState::Finished;
   }
 
   return AnimationPlayState::Running;
 }
 
 Promise*
 Animation::GetReady(ErrorResult& aRv)
--- a/gfx/layers/wr/WebRenderBridgeParent.cpp
+++ b/gfx/layers/wr/WebRenderBridgeParent.cpp
@@ -1559,17 +1559,17 @@ WebRenderBridgeParent::CompositeToTarget
   // This function should only get called in the root WRBP
   MOZ_ASSERT(IsRootWebRenderBridgeParent());
 
   // The two arguments are part of the CompositorVsyncSchedulerOwner API but in
   // this implementation they should never be non-null.
   MOZ_ASSERT(aTarget == nullptr);
   MOZ_ASSERT(aRect == nullptr);
 
-  AUTO_PROFILER_TRACING("Paint", "CompositeToTraget");
+  AUTO_PROFILER_TRACING("Paint", "CompositeToTarget");
   if (mPaused || !mReceivedDisplayList) {
     mPreviousFrameTimeStamp = TimeStamp();
     return;
   }
 
   if (wr::RenderThread::Get()->TooManyPendingFrames(mApi->GetId())) {
     // Render thread is busy, try next time.
     mCompositorScheduler->ScheduleComposition();
--- a/testing/web-platform/tests/web-animations/timing-model/animations/play-states.html
+++ b/testing/web-platform/tests/web-animations/timing-model/animations/play-states.html
@@ -17,18 +17,17 @@ test(t => {
   assert_equals(animation.currentTime, null,
                 'Current time should be initially unresolved');
 
   assert_equals(animation.playState, 'idle');
 }, 'reports \'idle\' for an animation with an unresolved current time'
    + ' and no pending tasks')
 
 test(t => {
-  const div = createDiv(t);
-  const animation = div.animate({}, 100 * MS_PER_SEC);
+  const animation = createDiv(t).animate({}, 100 * MS_PER_SEC);
 
   animation.pause();
 
   assert_equals(animation.playState, 'paused');
 }, 'reports \'paused\' for an animation with a pending pause task');
 
 test(t => {
   const animation = new Animation(
@@ -129,29 +128,58 @@ test(t => {
   animation.playbackRate = -1;
   animation.currentTime = 0;
 
   assert_equals(animation.playState, 'finished');
 }, 'reports \'finished\' when playback rate < 0 and'
    + ' current time = 0');
 
 test(t => {
-  const div = createDiv(t);
-  const animation = div.animate({}, 0);
+  const animation = createDiv(t).animate({}, 0);
   assert_equals(animation.startTime, null,
                 'Sanity check: start time should be unresolved');
 
   assert_equals(animation.playState, 'finished');
 }, 'reports \'finished\' when playback rate > 0 and'
    + ' current time = target effect end and there is a pending play task');
 
 test(t => {
-  const div = createDiv(t);
-  const animation = div.animate({}, 100 * MS_PER_SEC);
+  const animation = createDiv(t).animate({}, 100 * MS_PER_SEC);
   assert_equals(animation.startTime, null,
                 'Sanity check: start time should be unresolved');
 
   assert_equals(animation.playState, 'running');
 }, 'reports \'running\' when playback rate > 0 and'
    + ' current time < target effect end and there is a pending play task');
 
+test(t => {
+  const animation = createDiv(t).animate({}, 100 * MS_PER_SEC);
+  assert_equals(animation.playState, 'running');
+  assert_true(animation.pending);
+}, 'reports \'running\' for a play-pending animation');
+
+test(t => {
+  const animation = createDiv(t).animate({}, 100 * MS_PER_SEC);
+  animation.pause();
+  assert_equals(animation.playState, 'paused');
+  assert_true(animation.pending);
+}, 'reports \'paused\' for a pause-pending animation');
+
+test(t => {
+  const animation = createDiv(t).animate({}, 0);
+  assert_equals(animation.playState, 'finished');
+  assert_true(animation.pending);
+}, 'reports \'finished\' for a finished-pending animation');
+
+test(t => {
+  const animation = createDiv(t).animate({}, 100 * MS_PER_SEC);
+  // Set up the pending playback rate
+  animation.updatePlaybackRate(-1);
+  // Call play again so that we seek to the end while remaining play-pending
+  animation.play();
+  // For a pending animation, the play state should always report what the
+  // play state _will_ be once we finish pending.
+  assert_equals(animation.playState, 'running');
+  assert_true(animation.pending);
+}, 'reports the play state based on the pending playback rate');
+
 </script>
 </body>
--- a/toolkit/components/places/tests/unit/test_origins.js
+++ b/toolkit/components/places/tests/unit/test_origins.js
@@ -1238,16 +1238,20 @@ function expectedOriginFrecency(urls) {
  *        An array of expected origins.  Each origin in the array is itself an
  *        array that looks like this:
  *          [prefix, host, [url1, url2, ..., urln]]
  *        The element at index 2 is an array of all the URLs with the origin.
  *        If you don't care about checking frecencies and origin frecency stats,
  *        this element can be `undefined`.
  */
 async function checkDB(expectedOrigins) {
+  // Frencencies for bookmarks are generated asynchronously but not within the
+  // await cycle for bookmarks.insert() etc, so wait for them to happen.
+  await PlacesTestUtils.promiseAsyncUpdates();
+
   let db = await PlacesUtils.promiseDBConnection();
   let rows = await db.execute(`
     SELECT prefix, host, frecency
     FROM moz_origins
     ORDER BY id ASC
   `);
   let checkFrecencies = !expectedOrigins.length || expectedOrigins[0][2] !== undefined;
   let actualOrigins = rows.map(row => {
--- a/toolkit/content/widgets/videocontrols.js
+++ b/toolkit/content/widgets/videocontrols.js
@@ -1768,29 +1768,61 @@ this.VideoControlsImplPageWidget = class
           return Reflect.set(obj, prop, value);
         },
       },
 
       installReflowCallValidator(element) {
         return new Proxy(element, this.reflowTriggeringCallValidator);
       },
 
-      // Set the values to intrinsic dimensions before the first update.
       reflowedDimensions: {
+        // Set the dimensions to intrinsic <video> dimensions before the first
+        // update.
+        // These values are not picked up by <audio> in adjustControlSize()
+        // (except for the fact that they are non-zero),
+        // it takes controlBarMinHeight and the value below instead.
         videoHeight: 150,
         videoWidth: 300,
-        videocontrolsWidth: 300,
+
+        // <audio> takes this width to grow/shrink controls.
+        // The initial value has to be smaller than the calculated minRequiredWidth
+        // so that we don't run into bug 1495821 (see comment on adjustControlSize()
+        // below)
+        videocontrolsWidth: 0,
       },
 
       updateReflowedDimensions() {
         this.reflowedDimensions.videoHeight = this.video.clientHeight;
         this.reflowedDimensions.videoWidth = this.video.clientWidth;
         this.reflowedDimensions.videocontrolsWidth = this.videocontrols.clientWidth;
       },
 
+      /**
+       * adjustControlSize() considers outer dimensions of the <video>/<audio> element
+       * from layout, and accordingly, sets/hides the controls, and adjusts
+       * the width/height of the control bar.
+       *
+       * It's important to remember that for <audio>, layout (specifically,
+       * nsVideoFrame) rely on us to expose the intrinsic dimensions of the
+       * control bar to properly size the <audio> element. We interact with layout
+       * by:
+       *
+       * 1) When the element has a non-zero height, explicitly set the height
+       *    of the control bar to a size between controlBarMinHeight and
+       *    controlBarMinVisibleHeight in response.
+       *    Note: the logic here is flawed and had caused the end height to be
+       *    depend on its previous state, see bug 1495817.
+       * 2) When the element has a outer width smaller or equal to minControlBarPaddingWidth,
+       *    explicitly set the control bar to minRequiredWidth, so that when the
+       *    outer width is unset, the audio element could go back to minRequiredWidth.
+       *    Otherwise, set the width of the control bar to be the current outer width.
+       *    Note: the logic here is also flawed; when the control bar is set to
+       *    the current outer width, it never go back when the width is unset,
+       *    see bug 1495821.
+       */
       adjustControlSize() {
         const minControlBarPaddingWidth = 18;
 
         this.fullscreenButton.isWanted = !this.controlBar.hasAttribute("fullscreen-unavailable");
         this.castingButton.isWanted = this.isCastingAvailable;
         this.closedCaptionButton.isWanted = this.isClosedCaptionAvailable;
         this.volumeStack.isWanted = !this.muteButton.hasAttribute("noAudio");
 
--- a/tools/profiler/core/EHABIStackWalk.cpp
+++ b/tools/profiler/core/EHABIStackWalk.cpp
@@ -102,17 +102,17 @@ public:
 bool operator<(const EHEntryHandle &lhs, const EHEntryHandle &rhs) {
   return lhs.value()->startPC.compute() < rhs.value()->startPC.compute();
 }
 #endif
 
 class EHTable {
   uint32_t mStartPC;
   uint32_t mEndPC;
-  uint32_t mLoadOffset;
+  uint32_t mBaseAddress;
 #ifdef HAVE_UNSORTED_EXIDX
   // In principle we should be able to binary-search the index section in
   // place, but the ICS toolchain's linker is noncompliant and produces
   // indices that aren't entirely sorted (e.g., libc).  So we have this:
   std::vector<EHEntryHandle> mEntries;
   typedef std::vector<EHEntryHandle>::const_iterator EntryIterator;
   EntryIterator entriesBegin() const { return mEntries.begin(); }
   EntryIterator entriesEnd() const { return mEntries.end(); }
@@ -129,17 +129,17 @@ class EHTable {
   std::string mName;
 public:
   EHTable(const void *aELF, size_t aSize, const std::string &aName);
   const EHEntry *lookup(uint32_t aPC) const;
   bool isValid() const { return entriesEnd() != entriesBegin(); }
   const std::string &name() const { return mName; }
   uint32_t startPC() const { return mStartPC; }
   uint32_t endPC() const { return mEndPC; }
-  uint32_t loadOffset() const { return mLoadOffset; }
+  uint32_t baseAddress() const { return mBaseAddress; }
 };
 
 class EHAddrSpace {
   std::vector<uint32_t> mStarts;
   std::vector<EHTable> mTables;
   static mozilla::Atomic<const EHAddrSpace*> sCurrent;
 public:
   explicit EHAddrSpace(const std::vector<EHTable>& aTables);
@@ -543,22 +543,22 @@ EHTable::EHTable(const void *aELF, size_
   : mStartPC(~0), // largest uint32_t
     mEndPC(0),
 #ifndef HAVE_UNSORTED_EXIDX
     mEntriesBegin(nullptr),
     mEntriesEnd(nullptr),
 #endif
     mName(aName)
 {
-  const uint32_t base = reinterpret_cast<uint32_t>(aELF);
+  const uint32_t fileHeaderAddr = reinterpret_cast<uint32_t>(aELF);
 
   if (aSize < sizeof(Elf32_Ehdr))
     return;
 
-  const Elf32_Ehdr &file = *(reinterpret_cast<Elf32_Ehdr *>(base));
+  const Elf32_Ehdr &file = *(reinterpret_cast<Elf32_Ehdr *>(fileHeaderAddr));
   if (memcmp(&file.e_ident[EI_MAG0], ELFMAG, SELFMAG) != 0 ||
       file.e_ident[EI_CLASS] != ELFCLASS32 ||
       file.e_ident[EI_DATA] != hostEndian ||
       file.e_ident[EI_VERSION] != EV_CURRENT ||
       file.e_ident[EI_OSABI] != ELFOSABI_SYSV ||
 #ifdef EI_ABIVERSION
       file.e_ident[EI_ABIVERSION] != 0 ||
 #endif
@@ -566,17 +566,17 @@ EHTable::EHTable(const void *aELF, size_
       file.e_version != EV_CURRENT)
     // e_flags?
     return;
 
   MOZ_ASSERT(file.e_phoff + file.e_phnum * file.e_phentsize <= aSize);
   const Elf32_Phdr *exidxHdr = 0, *zeroHdr = 0;
   for (unsigned i = 0; i < file.e_phnum; ++i) {
     const Elf32_Phdr &phdr =
-      *(reinterpret_cast<Elf32_Phdr *>(base + file.e_phoff
+      *(reinterpret_cast<Elf32_Phdr *>(fileHeaderAddr + file.e_phoff
                                        + i * file.e_phentsize));
     if (phdr.p_type == PT_ARM_EXIDX) {
       exidxHdr = &phdr;
     } else if (phdr.p_type == PT_LOAD) {
       if (phdr.p_offset == 0) {
         zeroHdr = &phdr;
       }
       if (phdr.p_flags & PF_X) {
@@ -584,25 +584,25 @@ EHTable::EHTable(const void *aELF, size_
         mEndPC = std::max(mEndPC, phdr.p_vaddr + phdr.p_memsz);
       }
     }
   }
   if (!exidxHdr)
     return;
   if (!zeroHdr)
     return;
-  mLoadOffset = base - zeroHdr->p_vaddr;
-  mStartPC += mLoadOffset;
-  mEndPC += mLoadOffset;
+  mBaseAddress = fileHeaderAddr - zeroHdr->p_vaddr;
+  mStartPC += mBaseAddress;
+  mEndPC += mBaseAddress;
 
   // Create a sorted index of the index to work around linker bugs.
   const EHEntry *startTable =
-    reinterpret_cast<const EHEntry *>(mLoadOffset + exidxHdr->p_vaddr);
+    reinterpret_cast<const EHEntry *>(mBaseAddress + exidxHdr->p_vaddr);
   const EHEntry *endTable =
-    reinterpret_cast<const EHEntry *>(mLoadOffset + exidxHdr->p_vaddr
+    reinterpret_cast<const EHEntry *>(mBaseAddress + exidxHdr->p_vaddr
                                     + exidxHdr->p_memsz);
 #ifdef HAVE_UNSORTED_EXIDX
   mEntries.reserve(endTable - startTable);
   for (const EHEntry *i = startTable; i < endTable; ++i)
     mEntries.push_back(i);
   std::sort(mEntries.begin(), mEntries.end());
 #else
   mEntriesBegin = startTable;
@@ -625,23 +625,19 @@ void EHAddrSpace::Update() {
   if (space)
     return;
 
   SharedLibraryInfo info = SharedLibraryInfo::GetInfoForSelf();
   std::vector<EHTable> tables;
 
   for (size_t i = 0; i < info.GetSize(); ++i) {
     const SharedLibrary &lib = info.GetEntry(i);
-    if (lib.GetOffset() != 0)
-      // TODO: if it has a name, and we haven't seen a mapping of
-      // offset 0 for that file, try opening it and reading the
-      // headers instead.  The only thing I've seen so far that's
-      // linked so as to need that treatment is the dynamic linker
-      // itself.
-      continue;
+    // FIXME: This isn't correct if the start address isn't p_offset 0, because
+    // the start address will not point at the file header. But this is worked
+    // around by magic number checks in the EHTable constructor.
     EHTable tab(reinterpret_cast<const void *>(lib.GetStart()),
               lib.GetEnd() - lib.GetStart(), lib.GetNativeDebugPath());
     if (tab.isValid())
       tables.push_back(tab);
   }
   space = new EHAddrSpace(tables);
 
   if (!sCurrent.compareExchange(nullptr, space)) {
--- a/tools/profiler/core/shared-libraries-linux.cc
+++ b/tools/profiler/core/shared-libraries-linux.cc
@@ -38,26 +38,29 @@ int dl_iterate_phdr(
           int (*callback)(struct dl_phdr_info *info, size_t size, void *data),
           void *data);
 #else
 # error "Unexpected configuration"
 #endif
 
 struct LoadedLibraryInfo
 {
-  LoadedLibraryInfo(const char* aName, unsigned long aStart, unsigned long aEnd)
+  LoadedLibraryInfo(const char* aName, unsigned long aBaseAddress,
+                    unsigned long aFirstMappingStart, unsigned long aLastMappingEnd)
     : mName(aName)
-    , mStart(aStart)
-    , mEnd(aEnd)
+    , mBaseAddress(aBaseAddress)
+    , mFirstMappingStart(aFirstMappingStart)
+    , mLastMappingEnd(aLastMappingEnd)
   {
   }
 
   nsCString mName;
-  unsigned long mStart;
-  unsigned long mEnd;
+  unsigned long mBaseAddress;
+  unsigned long mFirstMappingStart;
+  unsigned long mLastMappingEnd;
 };
 
 #if defined(GP_OS_android)
 static void
 outputMapperLog(const char* aBuf)
 {
   LOG("%s", aBuf);
 }
@@ -129,33 +132,36 @@ SharedLibraryAtPath(const char* path, un
 static int
 dl_iterate_callback(struct dl_phdr_info *dl_info, size_t size, void *data)
 {
   auto libInfoList = reinterpret_cast<nsTArray<LoadedLibraryInfo>*>(data);
 
   if (dl_info->dlpi_phnum <= 0)
     return 0;
 
-  unsigned long libStart = -1;
-  unsigned long libEnd = 0;
+  unsigned long baseAddress = dl_info->dlpi_addr;
+  unsigned long firstMappingStart = -1;
+  unsigned long lastMappingEnd = 0;
 
   for (size_t i = 0; i < dl_info->dlpi_phnum; i++) {
     if (dl_info->dlpi_phdr[i].p_type != PT_LOAD) {
       continue;
     }
     unsigned long start = dl_info->dlpi_addr + dl_info->dlpi_phdr[i].p_vaddr;
     unsigned long end = start + dl_info->dlpi_phdr[i].p_memsz;
-    if (start < libStart)
-      libStart = start;
-    if (end > libEnd)
-      libEnd = end;
+    if (start < firstMappingStart) {
+      firstMappingStart = start;
+    }
+    if (end > lastMappingEnd) {
+      lastMappingEnd = end;
+    }
   }
 
-  libInfoList->AppendElement(LoadedLibraryInfo(dl_info->dlpi_name,
-                                               libStart, libEnd));
+  libInfoList->AppendElement(LoadedLibraryInfo(dl_info->dlpi_name, baseAddress,
+                                               firstMappingStart, lastMappingEnd));
 
   return 0;
 }
 
 SharedLibraryInfo SharedLibraryInfo::GetInfoForSelf()
 {
   SharedLibraryInfo info;
 
@@ -239,17 +245,20 @@ SharedLibraryInfo SharedLibraryInfo::Get
 
   nsTArray<LoadedLibraryInfo> libInfoList;
 
   // We collect the bulk of the library info using dl_iterate_phdr.
   dl_iterate_phdr(dl_iterate_callback, &libInfoList);
 
   for (const auto& libInfo : libInfoList) {
     info.AddSharedLibrary(
-      SharedLibraryAtPath(libInfo.mName.get(), libInfo.mStart, libInfo.mEnd));
+      SharedLibraryAtPath(libInfo.mName.get(),
+                          libInfo.mFirstMappingStart,
+                          libInfo.mLastMappingEnd,
+                          libInfo.mFirstMappingStart - libInfo.mBaseAddress));
   }
 
 #if defined(GP_OS_linux)
   // Make another pass over the information we just harvested from
   // dl_iterate_phdr.  If we see a nameless object mapped at what we earlier
   // established to be the main executable's load address, attach the
   // executable's name to that entry.
   for (size_t i = 0; i < info.GetSize(); i++) {
--- a/tools/profiler/gecko/nsIProfiler.idl
+++ b/tools/profiler/gecko/nsIProfiler.idl
@@ -98,19 +98,20 @@ interface nsIProfiler : nsISupports
    */
   double getElapsedTime();
 
   /**
    * Contains an array of shared library objects.
    * Every object has the properties:
    *  - start:      The start address of the memory region occupied by this library.
    *  - end:        The end address of the memory region occupied by this library.
-   *  - offset:     Usually zero, except on Android if the region was mapped from
-   *                a file (using mmap), then this is the offset in the file where
-   *                the mapping begins.
+   *  - offset:     Usually zero, except on Linux / Android if the first mapped
+   *                section of the library has been mapped to an address that's
+   *                different from the library's base address.
+   *                Then offset = start - baseAddress.
    *  - name:       The name (file basename) of the binary.
    *  - path:       The full absolute path to the binary.
    *  - debugName:  On Windows, the name of the pdb file for the binary. On other
    *                platforms, the same as |name|.
    *  - debugPath:  On Windows, the full absolute path of the pdb file for the
    *                binary. On other platforms, the same as |path|.
    *  - arch:       On Mac, the name of the architecture that identifies the right
    *                binary image of a fat binary. Example values are "i386", "x86_64",