| 
Commits:
a9279174
 by Edgar Chen   at 2023-07-31T23:49:06+02:00 
 Bug 1821884 - Ensure consistent state for fullscreen/pointerlock warnings; r=Gijs
Fullscreen/PointerLock warnings are initialized with hidden="true", but
change to hidden="" after being shown and hidden again. I think this
started happening when we began using HTML elements instead of XUL as
they handle hidden attribute differently.
Differential Revision: https://phabricator.services.mozilla.com/D177790
9f2eaedb
 by Edgar Chen   at 2023-07-31T23:49:06+02:00 
 Bug 1821884 - Reshow initial fullscreen notification; r=Gijs
Depends on D177790
Differential Revision: https://phabricator.services.mozilla.com/D178339
30d19aa0
 by Eitan Isaacson   at 2023-07-31T23:49:07+02:00 
 Bug 1819160 - Map Android ids to doc/accessible id pairs. r=Jamie
Differential Revision: https://phabricator.services.mozilla.com/D179737
efee8978
 by Jon Coppeard   at 2023-07-31T23:49:07+02:00 
 Bug 1828024 - Require the helper thread lock in the GC helper thread count getter r=sfink
This makes us take a lock to read this state (we already lock when writing it).
Also it adds a release assert in case something goes wrong with the thread
count calculations, as a crash is preferable to the potential deadlock.
Differential Revision: https://phabricator.services.mozilla.com/D181257 
12 changed files:
Changes:
accessible/android/SessionAccessibility.cpp
 
| ... | ... | @@ -269,12 +269,9 @@ RefPtr<SessionAccessibility> SessionAccessibility::GetInstanceFor( |  
| 269 | 269 |        return GetInstanceFor(doc->GetPresShell());
 |  
| 270 | 270 |      }
 |  
| 271 | 271 |    } else {
 |  
| 272 |  | -    DocAccessibleParent* remoteDoc = aAccessible->AsRemote()->Document();
 |  
| 273 |  | -    if (remoteDoc->mSessionAccessibility) {
 |  
| 274 |  | -      return remoteDoc->mSessionAccessibility;
 |  
| 275 |  | -    }
 |  
| 276 | 272 |      dom::CanonicalBrowsingContext* cbc =
 |  
| 277 |  | -        static_cast<dom::BrowserParent*>(remoteDoc->Manager())
 |  
|  | 273 | +        static_cast<dom::BrowserParent*>(
 |  
|  | 274 | +            aAccessible->AsRemote()->Document()->Manager())
 |  
| 278 | 275 |              ->GetBrowsingContext()
 |  
| 279 | 276 |              ->Top();
 |  
| 280 | 277 |      dom::BrowserParent* bp = cbc->GetBrowserParent();
 |  
| ... | ... | @@ -285,10 +282,7 @@ RefPtr<SessionAccessibility> SessionAccessibility::GetInstanceFor( |  
| 285 | 282 |      if (auto element = bp->GetOwnerElement()) {
 |  
| 286 | 283 |        if (auto doc = element->OwnerDoc()) {
 |  
| 287 | 284 |          if (nsPresContext* presContext = doc->GetPresContext()) {
 |  
| 288 |  | -          RefPtr<SessionAccessibility> sessionAcc =
 |  
| 289 |  | -              GetInstanceFor(presContext->PresShell());
 |  
| 290 |  | -          remoteDoc->mSessionAccessibility = sessionAcc;
 |  
| 291 |  | -          return sessionAcc;
 |  
|  | 285 | +          return GetInstanceFor(presContext->PresShell());
 |  
| 292 | 286 |          }
 |  
| 293 | 287 |        } else {
 |  
| 294 | 288 |          MOZ_ASSERT_UNREACHABLE(
 |  
| ... | ... | @@ -684,14 +678,7 @@ void SessionAccessibility::PopulateNodeInfo( |  
| 684 | 678 |  }
 |  
| 685 | 679 |  
 |  
| 686 | 680 |  Accessible* SessionAccessibility::GetAccessibleByID(int32_t aID) const {
 |  
| 687 |  | -  Accessible* accessible = mIDToAccessibleMap.Get(aID);
 |  
| 688 |  | -  if (accessible && accessible->IsLocal() &&
 |  
| 689 |  | -      accessible->AsLocal()->IsDefunct()) {
 |  
| 690 |  | -    MOZ_ASSERT_UNREACHABLE("Registered accessible is defunct!");
 |  
| 691 |  | -    return nullptr;
 |  
| 692 |  | -  }
 |  
| 693 |  | -
 |  
| 694 |  | -  return accessible;
 |  
|  | 681 | +  return mIDToAccessibleMap.Get(aID);
 |  
| 695 | 682 |  }
 |  
| 696 | 683 |  
 |  
| 697 | 684 |  #ifdef DEBUG
 |  
| ... | ... | @@ -705,6 +692,58 @@ static bool IsDetachedDoc(Accessible* aAccessible) { |  
| 705 | 692 |  }
 |  
| 706 | 693 |  #endif
 |  
| 707 | 694 |  
 |  
|  | 695 | +SessionAccessibility::IDMappingEntry::IDMappingEntry(Accessible* aAccessible)
 |  
|  | 696 | +    : mInternalID(0) {
 |  
|  | 697 | +  *this = aAccessible;
 |  
|  | 698 | +}
 |  
|  | 699 | +
 |  
|  | 700 | +SessionAccessibility::IDMappingEntry&
 |  
|  | 701 | +SessionAccessibility::IDMappingEntry::operator=(Accessible* aAccessible) {
 |  
|  | 702 | +  mInternalID = aAccessible->ID();
 |  
|  | 703 | +  MOZ_ASSERT(!(mInternalID & IS_REMOTE), "First bit is used in accessible ID!");
 |  
|  | 704 | +  if (aAccessible->IsRemote()) {
 |  
|  | 705 | +    mInternalID |= IS_REMOTE;
 |  
|  | 706 | +  }
 |  
|  | 707 | +
 |  
|  | 708 | +  Accessible* docAcc = nsAccUtils::DocumentFor(aAccessible);
 |  
|  | 709 | +  MOZ_ASSERT(docAcc);
 |  
|  | 710 | +  if (docAcc) {
 |  
|  | 711 | +    MOZ_ASSERT(docAcc->IsRemote() == aAccessible->IsRemote());
 |  
|  | 712 | +    if (docAcc->IsRemote()) {
 |  
|  | 713 | +      mDoc = docAcc->AsRemote()->AsDoc();
 |  
|  | 714 | +    } else {
 |  
|  | 715 | +      mDoc = docAcc->AsLocal();
 |  
|  | 716 | +    }
 |  
|  | 717 | +  }
 |  
|  | 718 | +
 |  
|  | 719 | +  return *this;
 |  
|  | 720 | +}
 |  
|  | 721 | +
 |  
|  | 722 | +SessionAccessibility::IDMappingEntry::operator Accessible*() const {
 |  
|  | 723 | +  if (mInternalID == 0) {
 |  
|  | 724 | +    return static_cast<LocalAccessible*>(mDoc.get());
 |  
|  | 725 | +  }
 |  
|  | 726 | +
 |  
|  | 727 | +  if (mInternalID == IS_REMOTE) {
 |  
|  | 728 | +    return static_cast<DocAccessibleParent*>(mDoc.get());
 |  
|  | 729 | +  }
 |  
|  | 730 | +
 |  
|  | 731 | +  if (mInternalID & IS_REMOTE) {
 |  
|  | 732 | +    return static_cast<DocAccessibleParent*>(mDoc.get())
 |  
|  | 733 | +        ->GetAccessible(mInternalID & ~IS_REMOTE);
 |  
|  | 734 | +  }
 |  
|  | 735 | +
 |  
|  | 736 | +  Accessible* accessible =
 |  
|  | 737 | +      static_cast<LocalAccessible*>(mDoc.get())
 |  
|  | 738 | +          ->AsDoc()
 |  
|  | 739 | +          ->GetAccessibleByUniqueID(reinterpret_cast<void*>(mInternalID));
 |  
|  | 740 | +  // If the accessible is retrievable from the DocAccessible, it can't be
 |  
|  | 741 | +  // defunct.
 |  
|  | 742 | +  MOZ_ASSERT(!accessible->AsLocal()->IsDefunct());
 |  
|  | 743 | +
 |  
|  | 744 | +  return accessible;
 |  
|  | 745 | +}
 |  
|  | 746 | +
 |  
| 708 | 747 |  void SessionAccessibility::RegisterAccessible(Accessible* aAccessible) {
 |  
| 709 | 748 |    if (IPCAccessibilityActive()) {
 |  
| 710 | 749 |      // Don't register accessible in content process.
 |  
| ... | ... | @@ -766,7 +805,6 @@ void SessionAccessibility::UnregisterAccessible(Accessible* aAccessible) { |  
| 766 | 805 |    }
 |  
| 767 | 806 |  
 |  
| 768 | 807 |    RefPtr<SessionAccessibility> sessionAcc = GetInstanceFor(aAccessible);
 |  
| 769 |  | -  MOZ_ASSERT(sessionAcc, "Need SessionAccessibility to unregister Accessible!");
 |  
| 770 | 808 |    if (sessionAcc) {
 |  
| 771 | 809 |      Accessible* registeredAcc =
 |  
| 772 | 810 |          sessionAcc->mIDToAccessibleMap.Get(virtualViewID);
 |  accessible/android/SessionAccessibility.h
 
 
| ... | ... | @@ -110,10 +110,34 @@ class SessionAccessibility final |  
| 110 | 110 |    jni::NativeWeakPtr<widget::GeckoViewSupport> mWindow;  // Parent only
 |  
| 111 | 111 |    java::SessionAccessibility::NativeProvider::GlobalRef mSessionAccessibility;
 |  
| 112 | 112 |  
 |  
|  | 113 | +  class IDMappingEntry {
 |  
|  | 114 | +   public:
 |  
|  | 115 | +    explicit IDMappingEntry(Accessible* aAccessible);
 |  
|  | 116 | +
 |  
|  | 117 | +    IDMappingEntry& operator=(Accessible* aAccessible);
 |  
|  | 118 | +
 |  
|  | 119 | +    operator Accessible*() const;
 |  
|  | 120 | +
 |  
|  | 121 | +   private:
 |  
|  | 122 | +    // A strong reference to a DocAccessible or DocAccessibleParent. They don't
 |  
|  | 123 | +    // share any useful base class except nsISupports, so we use that.
 |  
|  | 124 | +    // When we retrieve the document from this reference we cast it to
 |  
|  | 125 | +    // LocalAccessible in the DocAccessible case because DocAccessible has
 |  
|  | 126 | +    // multiple inheritance paths for nsISupports.
 |  
|  | 127 | +    RefPtr<nsISupports> mDoc;
 |  
|  | 128 | +    // The ID of the accessible as used in the internal doc mapping.
 |  
|  | 129 | +    // We rely on this ID being pointer derived and therefore divisible by two
 |  
|  | 130 | +    // so we can use the first bit to mark if it is remote or not.
 |  
|  | 131 | +    uint64_t mInternalID;
 |  
|  | 132 | +
 |  
|  | 133 | +    static const uintptr_t IS_REMOTE = 0x1;
 |  
|  | 134 | +  };
 |  
|  | 135 | +
 |  
| 113 | 136 |    /*
 |  
| 114 | 137 |     * This provides a mapping from 32 bit id to accessible objects.
 |  
| 115 | 138 |     */
 |  
| 116 |  | -  nsTHashMap<nsUint32HashKey, Accessible*> mIDToAccessibleMap;
 |  
|  | 139 | +  nsBaseHashtable<nsUint32HashKey, IDMappingEntry, Accessible*>
 |  
|  | 140 | +      mIDToAccessibleMap;
 |  
| 117 | 141 |  };
 |  
| 118 | 142 |  
 |  
| 119 | 143 |  }  // namespace a11y
 |  accessible/ipc/DocAccessibleParent.cpp
 
 
| ... | ... | @@ -29,7 +29,6 @@ |  
| 29 | 29 |  #endif
 |  
| 30 | 30 |  
 |  
| 31 | 31 |  #if defined(ANDROID)
 |  
| 32 |  | -#  include "mozilla/a11y/SessionAccessibility.h"
 |  
| 33 | 32 |  #  define ACQUIRE_ANDROID_LOCK \
 |  
| 34 | 33 |      MonitorAutoLock mal(nsAccessibilityService::GetAndroidMonitor());
 |  
| 35 | 34 |  #else
 |  accessible/ipc/DocAccessibleParent.h
 
 
| ... | ... | @@ -29,10 +29,6 @@ class xpcAccessibleGeneric; |  
| 29 | 29 |  class DocAccessiblePlatformExtParent;
 |  
| 30 | 30 |  #endif
 |  
| 31 | 31 |  
 |  
| 32 |  | -#ifdef ANDROID
 |  
| 33 |  | -class SessionAccessibility;
 |  
| 34 |  | -#endif
 |  
| 35 |  | -
 |  
| 36 | 32 |  /*
 |  
| 37 | 33 |   * These objects live in the main process and comunicate with and represent
 |  
| 38 | 34 |   * an accessible document in a content process.
 |  
| ... | ... | @@ -348,10 +344,6 @@ class DocAccessibleParent : public RemoteAccessible, |  
| 348 | 344 |  
 |  
| 349 | 345 |    size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) override;
 |  
| 350 | 346 |  
 |  
| 351 |  | -#ifdef ANDROID
 |  
| 352 |  | -  RefPtr<SessionAccessibility> mSessionAccessibility;
 |  
| 353 |  | -#endif
 |  
| 354 |  | -
 |  
| 355 | 347 |   private:
 |  
| 356 | 348 |    ~DocAccessibleParent();
 |  
| 357 | 349 |  
 |  accessible/ipc/moz.build
 
 
| ... | ... | @@ -24,11 +24,6 @@ else: |  
| 24 | 24 |          LOCAL_INCLUDES += [
 |  
| 25 | 25 |              "/accessible/mac",
 |  
| 26 | 26 |          ]
 |  
| 27 |  | -    elif CONFIG["MOZ_WIDGET_TOOLKIT"] == "android":
 |  
| 28 |  | -        LOCAL_INCLUDES += [
 |  
| 29 |  | -            "/accessible/android",
 |  
| 30 |  | -            "/widget/android",
 |  
| 31 |  | -        ]
 |  
| 32 | 27 |      else:
 |  
| 33 | 28 |          LOCAL_INCLUDES += [
 |  
| 34 | 29 |              "/accessible/other",
 |  browser/base/content/browser-fullScreenAndPointerLock.js
 
 
| ... | ... | @@ -62,9 +62,14 @@ var PointerlockFsWarning = { |  
| 62 | 62 |        this._element = document.getElementById(elementId);
 |  
| 63 | 63 |        // Setup event listeners
 |  
| 64 | 64 |        this._element.addEventListener("transitionend", this);
 |  
|  | 65 | +      this._element.addEventListener("transitioncancel", this);
 |  
| 65 | 66 |        window.addEventListener("mousemove", this, true);
 |  
|  | 67 | +      window.addEventListener("activate", this);
 |  
|  | 68 | +      window.addEventListener("deactivate", this);
 |  
| 66 | 69 |        // The timeout to hide the warning box after a while.
 |  
| 67 | 70 |        this._timeoutHide = new this.Timeout(() => {
 |  
|  | 71 | +        window.removeEventListener("activate", this);
 |  
|  | 72 | +        window.removeEventListener("deactivate", this);
 |  
| 68 | 73 |          this._state = "hidden";
 |  
| 69 | 74 |        }, timeout);
 |  
| 70 | 75 |        // The timeout to show the warning box when the pointer is at the top
 |  
| ... | ... | @@ -116,11 +121,10 @@ var PointerlockFsWarning = { |  
| 116 | 121 |        return;
 |  
| 117 | 122 |      }
 |  
| 118 | 123 |  
 |  
| 119 |  | -    // Explicitly set the last state to hidden to avoid the warning
 |  
| 120 |  | -    // box being hidden immediately because of mousemove.
 |  
| 121 |  | -    this._state = "onscreen";
 |  
| 122 |  | -    this._lastState = "hidden";
 |  
| 123 |  | -    this._timeoutHide.start();
 |  
|  | 124 | +    if (Services.focus.activeWindow == window) {
 |  
|  | 125 | +      this._state = "onscreen";
 |  
|  | 126 | +      this._timeoutHide.start();
 |  
|  | 127 | +    }
 |  
| 124 | 128 |    },
 |  
| 125 | 129 |  
 |  
| 126 | 130 |    /**
 |  
| ... | ... | @@ -148,7 +152,10 @@ var PointerlockFsWarning = { |  
| 148 | 152 |      this._element.hidden = true;
 |  
| 149 | 153 |      // Remove all event listeners
 |  
| 150 | 154 |      this._element.removeEventListener("transitionend", this);
 |  
|  | 155 | +    this._element.removeEventListener("transitioncancel", this);
 |  
| 151 | 156 |      window.removeEventListener("mousemove", this, true);
 |  
|  | 157 | +    window.removeEventListener("activate", this);
 |  
|  | 158 | +    window.removeEventListener("deactivate", this);
 |  
| 152 | 159 |      // Clear fields
 |  
| 153 | 160 |      this._element = null;
 |  
| 154 | 161 |      this._timeoutHide = null;
 |  
| ... | ... | @@ -186,7 +193,7 @@ var PointerlockFsWarning = { |  
| 186 | 193 |      }
 |  
| 187 | 194 |      if (newState != "hidden") {
 |  
| 188 | 195 |        if (currentState != "hidden") {
 |  
| 189 |  | -        this._element.setAttribute(newState, true);
 |  
|  | 196 | +        this._element.setAttribute(newState, "");
 |  
| 190 | 197 |        } else {
 |  
| 191 | 198 |          // When the previous state is hidden, the display was none,
 |  
| 192 | 199 |          // thus no box was constructed. We need to wait for the new
 |  
| ... | ... | @@ -197,7 +204,7 @@ var PointerlockFsWarning = { |  
| 197 | 204 |          requestAnimationFrame(() => {
 |  
| 198 | 205 |            requestAnimationFrame(() => {
 |  
| 199 | 206 |              if (this._element) {
 |  
| 200 |  | -              this._element.setAttribute(newState, true);
 |  
|  | 207 | +              this._element.setAttribute(newState, "");
 |  
| 201 | 208 |              }
 |  
| 202 | 209 |            });
 |  
| 203 | 210 |          });
 |  
| ... | ... | @@ -217,7 +224,7 @@ var PointerlockFsWarning = { |  
| 217 | 224 |            } else if (this._timeoutShow.delay >= 0) {
 |  
| 218 | 225 |              this._timeoutShow.start();
 |  
| 219 | 226 |            }
 |  
| 220 |  | -        } else {
 |  
|  | 227 | +        } else if (state != "onscreen") {
 |  
| 221 | 228 |            let elemRect = this._element.getBoundingClientRect();
 |  
| 222 | 229 |            if (state == "hiding" && this._lastState != "hidden") {
 |  
| 223 | 230 |              // If we are on the hiding transition, and the pointer
 |  
| ... | ... | @@ -239,12 +246,23 @@ var PointerlockFsWarning = { |  
| 239 | 246 |          }
 |  
| 240 | 247 |          break;
 |  
| 241 | 248 |        }
 |  
| 242 |  | -      case "transitionend": {
 |  
|  | 249 | +      case "transitionend":
 |  
|  | 250 | +      case "transitioncancel": {
 |  
| 243 | 251 |          if (this._state == "hiding") {
 |  
| 244 | 252 |            this._element.hidden = true;
 |  
| 245 | 253 |          }
 |  
| 246 | 254 |          break;
 |  
| 247 | 255 |        }
 |  
|  | 256 | +      case "activate": {
 |  
|  | 257 | +        this._state = "onscreen";
 |  
|  | 258 | +        this._timeoutHide.start();
 |  
|  | 259 | +        break;
 |  
|  | 260 | +      }
 |  
|  | 261 | +      case "deactivate": {
 |  
|  | 262 | +        this._state = "hidden";
 |  
|  | 263 | +        this._timeoutHide.cancel();
 |  
|  | 264 | +        break;
 |  
|  | 265 | +      }
 |  
| 248 | 266 |      }
 |  
| 249 | 267 |    },
 |  
| 250 | 268 |  };
 |  browser/base/content/fullscreen-and-pointerlock.inc.xhtml
 
 
| ... | ... | @@ -3,7 +3,7 @@ |  
| 3 | 3 |  # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 |  
| 4 | 4 |  
 |  
| 5 | 5 |  <html:div id="fullscreen-and-pointerlock-wrapper">
 |  
| 6 |  | -  <html:div id="fullscreen-warning" class="pointerlockfswarning" hidden="true">
 |  
|  | 6 | +  <html:div id="fullscreen-warning" class="pointerlockfswarning" hidden="">
 |  
| 7 | 7 |      <html:div class="pointerlockfswarning-domain-text">
 |  
| 8 | 8 |        <html:span class="pointerlockfswarning-domain" data-l10n-name="domain"/>
 |  
| 9 | 9 |      </html:div>
 |  
| ... | ... | @@ -20,7 +20,7 @@ |  
| 20 | 20 |      </html:button>
 |  
| 21 | 21 |    </html:div>
 |  
| 22 | 22 |  
 |  
| 23 |  | -  <html:div id="pointerlock-warning" class="pointerlockfswarning" hidden="true">
 |  
|  | 23 | +  <html:div id="pointerlock-warning" class="pointerlockfswarning" hidden="">
 |  
| 24 | 24 |      <html:div class="pointerlockfswarning-domain-text">
 |  
| 25 | 25 |        <html:span class="pointerlockfswarning-domain" data-l10n-name="domain"/>
 |  
| 26 | 26 |      </html:div>
 |  browser/base/content/test/fullscreen/browser_fullscreen_warning.js
 
 
| ... | ... | @@ -3,14 +3,35 @@ |  
| 3 | 3 |  
 |  
| 4 | 4 |  "use strict";
 |  
| 5 | 5 |  
 |  
| 6 |  | -add_task(async function test_fullscreen_display_none() {
 |  
|  | 6 | +function checkWarningState(aWarningElement, aExpectedState, aMsg) {
 |  
|  | 7 | +  ["hidden", "ontop", "onscreen"].forEach(state => {
 |  
|  | 8 | +    is(
 |  
|  | 9 | +      aWarningElement.hasAttribute(state),
 |  
|  | 10 | +      state == aExpectedState,
 |  
|  | 11 | +      `${aMsg} - check ${state} attribute.`
 |  
|  | 12 | +    );
 |  
|  | 13 | +  });
 |  
|  | 14 | +}
 |  
|  | 15 | +
 |  
|  | 16 | +async function waitForWarningState(aWarningElement, aExpectedState) {
 |  
|  | 17 | +  await BrowserTestUtils.waitForAttribute(aExpectedState, aWarningElement, "");
 |  
|  | 18 | +  checkWarningState(
 |  
|  | 19 | +    aWarningElement,
 |  
|  | 20 | +    aExpectedState,
 |  
|  | 21 | +    `Wait for ${aExpectedState} state`
 |  
|  | 22 | +  );
 |  
|  | 23 | +}
 |  
|  | 24 | +
 |  
|  | 25 | +add_setup(async function init() {
 |  
| 7 | 26 |    await SpecialPowers.pushPrefEnv({
 |  
| 8 | 27 |      set: [
 |  
| 9 | 28 |        ["full-screen-api.enabled", true],
 |  
| 10 | 29 |        ["full-screen-api.allow-trusted-requests-only", false],
 |  
| 11 | 30 |      ],
 |  
| 12 | 31 |    });
 |  
|  | 32 | +});
 |  
| 13 | 33 |  
 |  
|  | 34 | +add_task(async function test_fullscreen_display_none() {
 |  
| 14 | 35 |    await BrowserTestUtils.withNewTab(
 |  
| 15 | 36 |      {
 |  
| 16 | 37 |        gBrowser,
 |  
| ... | ... | @@ -30,11 +51,13 @@ add_task(async function test_fullscreen_display_none() { |  
| 30 | 51 |      },
 |  
| 31 | 52 |      async function (browser) {
 |  
| 32 | 53 |        let warning = document.getElementById("fullscreen-warning");
 |  
| 33 |  | -      let warningShownPromise = BrowserTestUtils.waitForAttribute(
 |  
| 34 |  | -        "onscreen",
 |  
|  | 54 | +      checkWarningState(
 |  
| 35 | 55 |          warning,
 |  
| 36 |  | -        "true"
 |  
|  | 56 | +        "hidden",
 |  
|  | 57 | +        "Should not show full screen warning initially"
 |  
| 37 | 58 |        );
 |  
|  | 59 | +
 |  
|  | 60 | +      let warningShownPromise = waitForWarningState(warning, "onscreen");
 |  
| 38 | 61 |        // Enter fullscreen
 |  
| 39 | 62 |        await SpecialPowers.spawn(browser, [], async () => {
 |  
| 40 | 63 |          let frame = content.document.querySelector("iframe");
 |  
| ... | ... | @@ -54,39 +77,33 @@ add_task(async function test_fullscreen_display_none() { |  
| 54 | 77 |        );
 |  
| 55 | 78 |        document.getElementById("fullscreen-exit-button").click();
 |  
| 56 | 79 |        await exitFullscreenPromise;
 |  
|  | 80 | +
 |  
|  | 81 | +      checkWarningState(
 |  
|  | 82 | +        warning,
 |  
|  | 83 | +        "hidden",
 |  
|  | 84 | +        "Should hide fullscreen warning after exiting fullscreen"
 |  
|  | 85 | +      );
 |  
| 57 | 86 |      }
 |  
| 58 | 87 |    );
 |  
| 59 | 88 |  });
 |  
| 60 | 89 |  
 |  
| 61 | 90 |  add_task(async function test_fullscreen_pointerlock_conflict() {
 |  
| 62 |  | -  await SpecialPowers.pushPrefEnv({
 |  
| 63 |  | -    set: [
 |  
| 64 |  | -      ["full-screen-api.enabled", true],
 |  
| 65 |  | -      ["full-screen-api.allow-trusted-requests-only", false],
 |  
| 66 |  | -    ],
 |  
| 67 |  | -  });
 |  
| 68 |  | -
 |  
| 69 | 91 |    await BrowserTestUtils.withNewTab("https://example.com", async browser => {
 |  
| 70 | 92 |      let fsWarning = document.getElementById("fullscreen-warning");
 |  
| 71 | 93 |      let plWarning = document.getElementById("pointerlock-warning");
 |  
| 72 | 94 |  
 |  
| 73 |  | -    is(
 |  
| 74 |  | -      fsWarning.getAttribute("onscreen"),
 |  
| 75 |  | -      null,
 |  
| 76 |  | -      "Should not show full screen warning initially."
 |  
| 77 |  | -    );
 |  
| 78 |  | -    is(
 |  
| 79 |  | -      plWarning.getAttribute("onscreen"),
 |  
| 80 |  | -      null,
 |  
| 81 |  | -      "Should not show pointer lock warning initially."
 |  
| 82 |  | -    );
 |  
| 83 |  | -
 |  
| 84 |  | -    let fsWarningShownPromise = BrowserTestUtils.waitForAttribute(
 |  
| 85 |  | -      "onscreen",
 |  
|  | 95 | +    checkWarningState(
 |  
| 86 | 96 |        fsWarning,
 |  
| 87 |  | -      "true"
 |  
|  | 97 | +      "hidden",
 |  
|  | 98 | +      "Should not show full screen warning initially"
 |  
|  | 99 | +    );
 |  
|  | 100 | +    checkWarningState(
 |  
|  | 101 | +      plWarning,
 |  
|  | 102 | +      "hidden",
 |  
|  | 103 | +      "Should not show pointer lock warning initially"
 |  
| 88 | 104 |      );
 |  
| 89 | 105 |  
 |  
|  | 106 | +    let fsWarningShownPromise = waitForWarningState(fsWarning, "onscreen");
 |  
| 90 | 107 |      info("Entering full screen and pointer lock.");
 |  
| 91 | 108 |      await SpecialPowers.spawn(browser, [], async () => {
 |  
| 92 | 109 |        await content.document.body.requestFullscreen();
 |  
| ... | ... | @@ -94,15 +111,10 @@ add_task(async function test_fullscreen_pointerlock_conflict() { |  
| 94 | 111 |      });
 |  
| 95 | 112 |  
 |  
| 96 | 113 |      await fsWarningShownPromise;
 |  
| 97 |  | -    is(
 |  
| 98 |  | -      fsWarning.getAttribute("onscreen"),
 |  
| 99 |  | -      "true",
 |  
| 100 |  | -      "Should show full screen warning."
 |  
| 101 |  | -    );
 |  
| 102 |  | -    is(
 |  
| 103 |  | -      plWarning.getAttribute("onscreen"),
 |  
| 104 |  | -      null,
 |  
| 105 |  | -      "Should not show pointer lock warning."
 |  
|  | 114 | +    checkWarningState(
 |  
|  | 115 | +      plWarning,
 |  
|  | 116 | +      "hidden",
 |  
|  | 117 | +      "Should not show pointer lock warning"
 |  
| 106 | 118 |      );
 |  
| 107 | 119 |  
 |  
| 108 | 120 |      info("Exiting pointerlock");
 |  
| ... | ... | @@ -110,18 +122,19 @@ add_task(async function test_fullscreen_pointerlock_conflict() { |  
| 110 | 122 |        await content.document.exitPointerLock();
 |  
| 111 | 123 |      });
 |  
| 112 | 124 |  
 |  
| 113 |  | -    is(
 |  
| 114 |  | -      fsWarning.getAttribute("onscreen"),
 |  
| 115 |  | -      "true",
 |  
| 116 |  | -      "Should still show full screen warning."
 |  
|  | 125 | +    checkWarningState(
 |  
|  | 126 | +      fsWarning,
 |  
|  | 127 | +      "onscreen",
 |  
|  | 128 | +      "Should still show full screen warning"
 |  
| 117 | 129 |      );
 |  
| 118 |  | -    is(
 |  
| 119 |  | -      plWarning.getAttribute("onscreen"),
 |  
| 120 |  | -      null,
 |  
| 121 |  | -      "Should not show pointer lock warning."
 |  
|  | 130 | +    checkWarningState(
 |  
|  | 131 | +      plWarning,
 |  
|  | 132 | +      "hidden",
 |  
|  | 133 | +      "Should not show pointer lock warning"
 |  
| 122 | 134 |      );
 |  
| 123 | 135 |  
 |  
| 124 | 136 |      // Cleanup
 |  
|  | 137 | +    info("Exiting fullscreen");
 |  
| 125 | 138 |      await document.exitFullscreen();
 |  
| 126 | 139 |    });
 |  
| 127 | 140 |  }); |  dom/tests/browser/browser_pointerlock_warning.js
 
 
| ... | ... | @@ -15,6 +15,25 @@ const FRAME_TEST_URL = |  
| 15 | 15 |    encodeURI(BODY_URL) +
 |  
| 16 | 16 |    '"></iframe></body>';
 |  
| 17 | 17 |  
 |  
|  | 18 | +function checkWarningState(aWarningElement, aExpectedState, aMsg) {
 |  
|  | 19 | +  ["hidden", "ontop", "onscreen"].forEach(state => {
 |  
|  | 20 | +    is(
 |  
|  | 21 | +      aWarningElement.hasAttribute(state),
 |  
|  | 22 | +      state == aExpectedState,
 |  
|  | 23 | +      `${aMsg} - check ${state} attribute.`
 |  
|  | 24 | +    );
 |  
|  | 25 | +  });
 |  
|  | 26 | +}
 |  
|  | 27 | +
 |  
|  | 28 | +async function waitForWarningState(aWarningElement, aExpectedState) {
 |  
|  | 29 | +  await BrowserTestUtils.waitForAttribute(aExpectedState, aWarningElement, "");
 |  
|  | 30 | +  checkWarningState(
 |  
|  | 31 | +    aWarningElement,
 |  
|  | 32 | +    aExpectedState,
 |  
|  | 33 | +    `Wait for ${aExpectedState} state`
 |  
|  | 34 | +  );
 |  
|  | 35 | +}
 |  
|  | 36 | +
 |  
| 18 | 37 |  // Make sure the pointerlock warning is shown and exited with the escape key
 |  
| 19 | 38 |  add_task(async function show_pointerlock_warning_escape() {
 |  
| 20 | 39 |    let urls = [TEST_URL, FRAME_TEST_URL];
 |  
| ... | ... | @@ -24,11 +43,7 @@ add_task(async function show_pointerlock_warning_escape() { |  
| 24 | 43 |      let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, url);
 |  
| 25 | 44 |  
 |  
| 26 | 45 |      let warning = document.getElementById("pointerlock-warning");
 |  
| 27 |  | -    let warningShownPromise = BrowserTestUtils.waitForAttribute(
 |  
| 28 |  | -      "onscreen",
 |  
| 29 |  | -      warning,
 |  
| 30 |  | -      "true"
 |  
| 31 |  | -    );
 |  
|  | 46 | +    let warningShownPromise = waitForWarningState(warning, "onscreen");
 |  
| 32 | 47 |  
 |  
| 33 | 48 |      let expectedWarningText;
 |  
| 34 | 49 |  
 |  
| ... | ... | @@ -49,11 +64,7 @@ add_task(async function show_pointerlock_warning_escape() { |  
| 49 | 64 |  
 |  
| 50 | 65 |      ok(true, "Pointerlock warning shown");
 |  
| 51 | 66 |  
 |  
| 52 |  | -    let warningHiddenPromise = BrowserTestUtils.waitForAttribute(
 |  
| 53 |  | -      "hidden",
 |  
| 54 |  | -      warning,
 |  
| 55 |  | -      ""
 |  
| 56 |  | -    );
 |  
|  | 67 | +    let warningHiddenPromise = waitForWarningState(warning, "hidden");
 |  
| 57 | 68 |  
 |  
| 58 | 69 |      await BrowserTestUtils.waitForCondition(
 |  
| 59 | 70 |        () => warning.innerText == expectedWarningText,
 |  js/src/gc/GC.cpp
 
 
| ... | ... | @@ -1331,6 +1331,11 @@ void GCRuntime::assertNoMarkingWork() const { |  
| 1331 | 1331 |  }
 |  
| 1332 | 1332 |  #endif
 |  
| 1333 | 1333 |  
 |  
|  | 1334 | +static size_t GetGCParallelThreadCount() {
 |  
|  | 1335 | +  AutoLockHelperThreadState lock;
 |  
|  | 1336 | +  return HelperThreadState().getGCParallelThreadCount(lock);
 |  
|  | 1337 | +}
 |  
|  | 1338 | +
 |  
| 1334 | 1339 |  bool GCRuntime::updateMarkersVector() {
 |  
| 1335 | 1340 |    MOZ_ASSERT(helperThreadCount >= 1,
 |  
| 1336 | 1341 |               "There must always be at least one mark task");
 |  
| ... | ... | @@ -1339,8 +1344,8 @@ bool GCRuntime::updateMarkersVector() { |  
| 1339 | 1344 |  
 |  
| 1340 | 1345 |    // Limit worker count to number of GC parallel tasks that can run
 |  
| 1341 | 1346 |    // concurrently, otherwise one thread can deadlock waiting on another.
 |  
| 1342 |  | -  size_t targetCount = std::min(markingWorkerCount(),
 |  
| 1343 |  | -                                HelperThreadState().getGCParallelThreadCount());
 |  
|  | 1347 | +  size_t targetCount =
 |  
|  | 1348 | +      std::min(markingWorkerCount(), GetGCParallelThreadCount());
 |  
| 1344 | 1349 |  
 |  
| 1345 | 1350 |    if (markers.length() > targetCount) {
 |  
| 1346 | 1351 |      return markers.resize(targetCount);
 |  js/src/gc/ParallelMarking.cpp
 
 
| ... | ... | @@ -103,6 +103,10 @@ bool ParallelMarker::markOneColor(MarkColor color, SliceBudget& sliceBudget) { |  
| 103 | 103 |    {
 |  
| 104 | 104 |      AutoLockHelperThreadState lock;
 |  
| 105 | 105 |  
 |  
|  | 106 | +    // There should always be enough parallel tasks to run our marking work.
 |  
|  | 107 | +    MOZ_RELEASE_ASSERT(HelperThreadState().getGCParallelThreadCount(lock) >=
 |  
|  | 108 | +                       workerCount());
 |  
|  | 109 | +
 |  
| 106 | 110 |      for (size_t i = 0; i < workerCount(); i++) {
 |  
| 107 | 111 |        gc->startTask(*tasks[i], lock);
 |  
| 108 | 112 |      }
 |  js/src/vm/HelperThreadState.h
 
 
| ... | ... | @@ -333,9 +333,11 @@ class GlobalHelperThreadState { |  
| 333 | 333 |  
 |  
| 334 | 334 |    GCParallelTaskList& gcParallelWorklist() { return gcParallelWorklist_; }
 |  
| 335 | 335 |  
 |  
| 336 |  | -  size_t getGCParallelThreadCount() const { return gcParallelThreadCount; }
 |  
|  | 336 | +  size_t getGCParallelThreadCount(const AutoLockHelperThreadState& lock) const {
 |  
|  | 337 | +    return gcParallelThreadCount;
 |  
|  | 338 | +  }
 |  
| 337 | 339 |    void setGCParallelThreadCount(size_t count,
 |  
| 338 |  | -                                const AutoLockHelperThreadState&) {
 |  
|  | 340 | +                                const AutoLockHelperThreadState& lock) {
 |  
| 339 | 341 |      MOZ_ASSERT(count >= 1);
 |  
| 340 | 342 |      MOZ_ASSERT(count <= threadCount);
 |  
| 341 | 343 |      gcParallelThreadCount = count;
 |  
 |