зеркало из https://github.com/mozilla/gecko-dev.git
Merge mozilla-central to inbound a=merge on a CLOSED TREE
This commit is contained in:
Коммит
36018a1b0d
|
@ -338,6 +338,7 @@ xmlns="http://www.w3.org/1999/xhtml"
|
|||
oncommand="StarUI.onShowForNewBookmarksCheckboxCommand();"/>
|
||||
</vbox>
|
||||
<hbox id="editBookmarkPanelBottomButtons"
|
||||
class="panel-footer"
|
||||
style="min-width: &editBookmark.panel.width;;">
|
||||
#ifndef XP_UNIX
|
||||
<button id="editBookmarkPanelDoneButton"
|
||||
|
|
|
@ -218,7 +218,7 @@ var tests = [
|
|||
},
|
||||
onShown(popup) {
|
||||
let notification = popup.children[0];
|
||||
is(notification.getAttribute("buttonhighlight"), "true", "default action is highlighted");
|
||||
ok(notification.hasAttribute("buttonhighlight"), "default action is highlighted");
|
||||
triggerMainCommand(popup);
|
||||
},
|
||||
onHidden(popup) {
|
||||
|
@ -239,7 +239,7 @@ var tests = [
|
|||
onShown(popup) {
|
||||
let notification = popup.children[0];
|
||||
is(notification.getAttribute("secondarybuttonhidden"), "true", "secondary button is hidden");
|
||||
is(notification.getAttribute("buttonhighlight"), "true", "default action is highlighted");
|
||||
ok(notification.hasAttribute("buttonhighlight"), "default action is highlighted");
|
||||
triggerMainCommand(popup);
|
||||
},
|
||||
onHidden(popup) {
|
||||
|
|
|
@ -205,8 +205,8 @@ function checkPopup(popup, notifyObj) {
|
|||
"main action label matches");
|
||||
is(notification.getAttribute("buttonaccesskey"),
|
||||
notifyObj.mainAction.accessKey, "main action accesskey matches");
|
||||
is(notification.getAttribute("buttonhighlight"),
|
||||
(!notifyObj.mainAction.disableHighlight).toString(),
|
||||
is(notification.hasAttribute("buttonhighlight"),
|
||||
!notifyObj.mainAction.disableHighlight,
|
||||
"main action highlight matches");
|
||||
}
|
||||
if (notifyObj.secondaryActions && notifyObj.secondaryActions.length > 0) {
|
||||
|
|
|
@ -98,11 +98,14 @@ var AttributionCode = {
|
|||
let referrer = attributionSvc.getReferrerUrl(appPath);
|
||||
let params = new URL(referrer).searchParams;
|
||||
for (let key of ATTR_CODE_KEYS) {
|
||||
let utm_key = `utm_${key}`;
|
||||
if (params.has(utm_key)) {
|
||||
let value = params.get(utm_key);
|
||||
if (value && ATTR_CODE_VALUE_REGEX.test(value)) {
|
||||
gCachedAttrData[key] = value;
|
||||
// We support the key prefixed with utm_ or not, but intentionally
|
||||
// choose non-utm params over utm params.
|
||||
for (let paramKey of [`utm_${key}`, key]) {
|
||||
if (params.has(paramKey)) {
|
||||
let value = params.get(paramKey);
|
||||
if (value && ATTR_CODE_VALUE_REGEX.test(value)) {
|
||||
gCachedAttrData[key] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
"use strict";
|
||||
|
||||
ChromeUtils.import("resource://gre/modules/Services.jsm");
|
||||
ChromeUtils.import("resource:///modules/AttributionCode.jsm");
|
||||
|
||||
add_task(async function test_attribution() {
|
||||
let appPath = Services.dirsvc.get("GreD", Ci.nsIFile).parent.parent.path;
|
||||
|
@ -15,8 +16,8 @@ add_task(async function test_attribution() {
|
|||
let referrer = attributionSvc.getReferrerUrl(appPath);
|
||||
equal(referrer, "", "force an empty referrer url");
|
||||
|
||||
// Set a url referrer
|
||||
let url = "http://example.com";
|
||||
// Set a url referrer, testing both utm and non-utm codes
|
||||
let url = "http://example.com?content=foo&utm_source=bar&utm_content=baz";
|
||||
attributionSvc.setReferrerUrl(appPath, url, true);
|
||||
referrer = attributionSvc.getReferrerUrl(appPath);
|
||||
equal(referrer, url, "overwrite referrer url");
|
||||
|
@ -25,4 +26,7 @@ add_task(async function test_attribution() {
|
|||
attributionSvc.setReferrerUrl(appPath, "http://test.com", false);
|
||||
referrer = attributionSvc.getReferrerUrl(appPath);
|
||||
equal(referrer, url, "referrer url is not changed");
|
||||
|
||||
let result = await AttributionCode.getAttrDataAsync();
|
||||
Assert.deepEqual(result, {content: "foo", source: "bar"}, "parsed attributes match");
|
||||
});
|
||||
|
|
|
@ -136,7 +136,7 @@
|
|||
<!-- Clear Site Data Button -->
|
||||
<vbox hidden="true"
|
||||
id="identity-popup-clear-sitedata-footer"
|
||||
class="identity-popup-footer">
|
||||
class="panel-footer identity-popup-footer">
|
||||
<button id="identity-popup-clear-sitedata-button"
|
||||
label="&identity.clearSiteData;"
|
||||
oncommand="gIdentityHandler.clearSiteData(event); gIdentityHandler.recordClick('clear_sitedata');"/>
|
||||
|
@ -225,7 +225,7 @@
|
|||
oncommand="gIdentityHandler.enableMixedContentProtection()"/>
|
||||
</vbox>
|
||||
|
||||
<vbox id="identity-popup-more-info-footer" class="identity-popup-footer">
|
||||
<vbox id="identity-popup-more-info-footer" class="panel-footer identity-popup-footer">
|
||||
<!-- More Security Information -->
|
||||
<button id="identity-popup-more-info"
|
||||
label="&identity.moreInfoLinkText2;"
|
||||
|
@ -245,7 +245,7 @@
|
|||
<image/>
|
||||
<label>&contentBlocking.trackersView.strictInfo.label;</label>
|
||||
</hbox>
|
||||
<vbox class="identity-popup-footer">
|
||||
<vbox class="panel-footer identity-popup-footer">
|
||||
<button id="identity-popup-trackersView-settings-button"
|
||||
label="&contentBlocking.manageSettings.label;"
|
||||
accesskey="&contentBlocking.manageSettings.accesskey;"
|
||||
|
@ -260,7 +260,7 @@
|
|||
descriptionheightworkaround="true">
|
||||
<vbox id="identity-popup-cookiesView-list" class="identity-popup-content-blocking-list">
|
||||
</vbox>
|
||||
<vbox class="identity-popup-footer">
|
||||
<vbox class="panel-footer identity-popup-footer">
|
||||
<button id="identity-popup-cookiesView-settings-button"
|
||||
label="&contentBlocking.manageSettings.label;"
|
||||
accesskey="&contentBlocking.manageSettings.accesskey;"
|
||||
|
@ -287,7 +287,8 @@
|
|||
<textbox multiline="true" id="identity-popup-breakageReportView-collection-comments"/>
|
||||
</vbox>
|
||||
</vbox>
|
||||
<vbox id="identity-popup-breakageReportView-footer" class="identity-popup-footer">
|
||||
<vbox id="identity-popup-breakageReportView-footer"
|
||||
class="panel-footer identity-popup-footer">
|
||||
<button id="identity-popup-breakageReportView-cancel"
|
||||
label="&contentBlocking.breakageReportView.cancel.label;"
|
||||
oncommand="ContentBlocking.backToMainView();"/>
|
||||
|
|
|
@ -135,7 +135,8 @@
|
|||
crop="end"/>
|
||||
</vbox>
|
||||
</hbox>
|
||||
<hbox id="downloadsFooterButtons">
|
||||
<hbox id="downloadsFooterButtons"
|
||||
class="panel-footer">
|
||||
<button id="downloadsHistory"
|
||||
class="downloadsPanelFooterButton"
|
||||
label="&downloadsHistory.label;"
|
||||
|
@ -158,7 +159,7 @@
|
|||
<description id="downloadsPanel-blockedSubview-details2"/>
|
||||
</vbox>
|
||||
<hbox id="downloadsPanel-blockedSubview-buttons"
|
||||
class="downloadsPanelFooter"
|
||||
class="panel-footer downloadsPanelFooter"
|
||||
align="stretch">
|
||||
<button id="downloadsPanel-blockedSubview-openButton"
|
||||
class="downloadsPanelFooterButton"
|
||||
|
|
|
@ -234,23 +234,21 @@ class UrlbarInput {
|
|||
try {
|
||||
new URL(url);
|
||||
} catch (ex) {
|
||||
// TODO: Figure out why we need lastLocationChange here.
|
||||
// let lastLocationChange = browser.lastLocationChange;
|
||||
// UrlbarUtils.getShortcutOrURIAndPostData(text).then(data => {
|
||||
// if (where != "current" ||
|
||||
// browser.lastLocationChange == lastLocationChange) {
|
||||
// params.postData = data.postData;
|
||||
// params.allowInheritPrincipal = data.mayInheritPrincipal;
|
||||
// this._loadURL(data.url, browser, where,
|
||||
// openUILinkParams);
|
||||
// }
|
||||
// });
|
||||
let browser = this.window.gBrowser.selectedBrowser;
|
||||
let lastLocationChange = browser.lastLocationChange;
|
||||
|
||||
UrlbarUtils.getShortcutOrURIAndPostData(url).then(data => {
|
||||
if (where != "current" ||
|
||||
browser.lastLocationChange == lastLocationChange) {
|
||||
openParams.postData = data.postData;
|
||||
openParams.allowInheritPrincipal = data.mayInheritPrincipal;
|
||||
this._loadURL(data.url, where, openParams);
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
this._loadURL(url, where, openParams);
|
||||
|
||||
this.view.close();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -560,7 +558,7 @@ class UrlbarInput {
|
|||
browser.initialPageLoadedFromURLBar = url;
|
||||
}
|
||||
try {
|
||||
UrlbarUtils.addToUrlbarHistory(url);
|
||||
UrlbarUtils.addToUrlbarHistory(url, this.window);
|
||||
} catch (ex) {
|
||||
// Things may go wrong when adding url to session history,
|
||||
// but don't let that interfere with the loading of the url.
|
||||
|
@ -602,6 +600,8 @@ class UrlbarInput {
|
|||
// TODO This should probably be handed via input.
|
||||
// Ensure the start of the URL is visible for usability reasons.
|
||||
// this.selectionStart = this.selectionEnd = 0;
|
||||
|
||||
this.closePopup();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -13,3 +13,4 @@ support-files =
|
|||
subsuite = clipboard
|
||||
[browser_UrlbarInput_unit.js]
|
||||
support-files = empty.xul
|
||||
[browser_UrlbarLoadRace.js]
|
||||
|
|
|
@ -0,0 +1,72 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/
|
||||
*/
|
||||
|
||||
// This test is for testing races of loading the Urlbar when loading shortcuts.
|
||||
// For example, ensuring that if a search query is entered, but something causes
|
||||
// a page load whilst we're getting the search url, then we don't handle the
|
||||
// original search query.
|
||||
|
||||
add_task(async function setup() {
|
||||
sandbox = sinon.sandbox.create();
|
||||
|
||||
registerCleanupFunction(async () => {
|
||||
sandbox.restore();
|
||||
});
|
||||
});
|
||||
|
||||
async function checkShortcutLoading(modifierKeys) {
|
||||
let deferred = PromiseUtils.defer();
|
||||
let tab = await BrowserTestUtils.openNewForegroundTab({
|
||||
gBrowser,
|
||||
opening: "about:robots",
|
||||
});
|
||||
|
||||
// We stub getShortcutOrURIAndPostData so that we can guarentee it doesn't resolve
|
||||
// until after we've loaded a new page.
|
||||
sandbox.stub(UrlbarUtils, "getShortcutOrURIAndPostData").callsFake(() => deferred.promise);
|
||||
|
||||
gURLBar.focus();
|
||||
gURLBar.value = "search";
|
||||
gURLBar.userTypedValue = true;
|
||||
EventUtils.synthesizeKey("KEY_Enter", modifierKeys);
|
||||
|
||||
Assert.ok(UrlbarUtils.getShortcutOrURIAndPostData.calledOnce,
|
||||
"should have called getShortcutOrURIAndPostData");
|
||||
|
||||
BrowserTestUtils.loadURI(tab.linkedBrowser, "about:license");
|
||||
await BrowserTestUtils.browserLoaded(tab.linkedBrowser);
|
||||
|
||||
let openedTab;
|
||||
function listener(event) {
|
||||
openedTab = event.target;
|
||||
}
|
||||
window.addEventListener("TabOpen", listener);
|
||||
|
||||
deferred.resolve({
|
||||
url: "https://example.com/1/",
|
||||
postData: {},
|
||||
mayInheritPrincipal: true,
|
||||
});
|
||||
|
||||
await deferred.promise;
|
||||
|
||||
if (modifierKeys) {
|
||||
Assert.ok(openedTab, "Should have attempted to open the shortcut page");
|
||||
BrowserTestUtils.removeTab(openedTab);
|
||||
} else {
|
||||
Assert.ok(!openedTab, "Should have not attempted to open the shortcut page");
|
||||
}
|
||||
|
||||
window.removeEventListener("TabOpen", listener);
|
||||
BrowserTestUtils.removeTab(tab);
|
||||
sandbox.restore();
|
||||
}
|
||||
|
||||
add_task(async function test_location_change_stops_load() {
|
||||
await checkShortcutLoading();
|
||||
});
|
||||
|
||||
add_task(async function test_opening_different_tab_with_location_change() {
|
||||
await checkShortcutLoading({altKey: true});
|
||||
});
|
|
@ -11,6 +11,7 @@ let sandbox;
|
|||
|
||||
ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetters(this, {
|
||||
PromiseUtils: "resource://gre/modules/PromiseUtils.jsm",
|
||||
Services: "resource://gre/modules/Services.jsm",
|
||||
QueryContext: "resource:///modules/UrlbarUtils.jsm",
|
||||
UrlbarController: "resource:///modules/UrlbarController.jsm",
|
||||
|
|
|
@ -313,26 +313,10 @@
|
|||
margin-top: 1em;
|
||||
}
|
||||
|
||||
.identity-popup-footer {
|
||||
background-color: var(--arrowpanel-dimmed);
|
||||
}
|
||||
|
||||
.identity-popup-footer > button {
|
||||
-moz-appearance: none;
|
||||
margin: 0;
|
||||
border-top: 1px solid var(--panel-separator-color);
|
||||
padding: 8px 20px;
|
||||
/* !important overrides :hover and :active colors from button.css: */
|
||||
color: inherit !important;
|
||||
}
|
||||
|
||||
.identity-popup-footer > button:hover,
|
||||
.identity-popup-footer > button:focus {
|
||||
background-color: var(--arrowpanel-dimmed);
|
||||
}
|
||||
|
||||
.identity-popup-footer > button:hover:active {
|
||||
background-color: var(--arrowpanel-dimmed-further);
|
||||
}
|
||||
|
||||
#identity-popup-content-verifier ~ description {
|
||||
|
@ -362,19 +346,6 @@ description#identity-popup-content-verifier,
|
|||
flex: 1;
|
||||
}
|
||||
|
||||
#identity-popup-breakageReportView-footer > button[default] {
|
||||
color: white;
|
||||
background-color: #0996f8;
|
||||
}
|
||||
|
||||
#identity-popup-breakageReportView-footer > button[default]:hover {
|
||||
background-color: #0675d3;
|
||||
}
|
||||
|
||||
#identity-popup-breakageReportView-footer > button[default]:hover:active {
|
||||
background-color: #0568ba;
|
||||
}
|
||||
|
||||
#identity-popup-breakageReportView-heading,
|
||||
#identity-popup-breakageReportView-body {
|
||||
padding: 16px;
|
||||
|
|
|
@ -31,7 +31,6 @@
|
|||
}
|
||||
|
||||
.downloadsPanelFooter {
|
||||
background-color: var(--arrowpanel-dimmed);
|
||||
border-top: 1px solid var(--panel-separator-color);
|
||||
}
|
||||
|
||||
|
@ -45,41 +44,22 @@
|
|||
}
|
||||
|
||||
.downloadsPanelFooterButton {
|
||||
-moz-appearance: none;
|
||||
background-color: transparent;
|
||||
color: inherit;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
min-width: 0;
|
||||
min-height: 40px;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.downloadsPanelFooterButton:hover {
|
||||
outline: 1px solid var(--arrowpanel-dimmed);
|
||||
background-color: var(--arrowpanel-dimmed);
|
||||
}
|
||||
|
||||
.downloadsPanelFooterButton:hover:active,
|
||||
.downloadsPanelFooterButton[open="true"] {
|
||||
outline: 1px solid var(--arrowpanel-dimmed-further);
|
||||
background-color: var(--arrowpanel-dimmed-further);
|
||||
box-shadow: 0 1px 0 hsla(210,4%,10%,.05) inset;
|
||||
}
|
||||
|
||||
.downloadsPanelFooterButton[default] {
|
||||
background-color: #0996f8;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.downloadsPanelFooterButton[default]:hover {
|
||||
background-color: #0675d3;
|
||||
}
|
||||
|
||||
.downloadsPanelFooterButton[default]:hover:active {
|
||||
background-color: #0568ba;
|
||||
}
|
||||
|
||||
.downloadsPanelFooterButton > .button-box {
|
||||
padding: 0;
|
||||
}
|
||||
|
|
|
@ -57,35 +57,11 @@ html|img#editBookmarkPanelFavicon[src] {
|
|||
|
||||
.editBookmarkPanelBottomButton {
|
||||
flex: 1;
|
||||
-moz-appearance: none;
|
||||
margin: 0;
|
||||
padding: .8em 0;
|
||||
color: inherit;
|
||||
background-color: var(--arrowpanel-dimmed);
|
||||
border-top: 1px solid var(--panel-separator-color);
|
||||
}
|
||||
|
||||
.editBookmarkPanelBottomButton:not(:last-child) {
|
||||
border-inline-end: 1px solid var(--panel-separator-color);
|
||||
}
|
||||
|
||||
.editBookmarkPanelBottomButton:hover {
|
||||
background-color: var(--arrowpanel-dimmed-further);
|
||||
}
|
||||
|
||||
.editBookmarkPanelBottomButton:hover:active {
|
||||
background-color: var(--arrowpanel-dimmed-even-further);
|
||||
}
|
||||
|
||||
.editBookmarkPanelBottomButton[default] {
|
||||
color: white;
|
||||
background-color: #0996f8;
|
||||
}
|
||||
|
||||
.editBookmarkPanelBottomButton[default]:hover {
|
||||
background-color: #0675d3;
|
||||
}
|
||||
|
||||
.editBookmarkPanelBottomButton[default]:hover:active {
|
||||
background-color: #0568ba;
|
||||
}
|
||||
|
|
|
@ -5,8 +5,16 @@
|
|||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "VisualViewport.h"
|
||||
|
||||
#include "mozilla/EventDispatcher.h"
|
||||
#include "mozilla/ToString.h"
|
||||
#include "nsIScrollableFrame.h"
|
||||
#include "nsIDocShell.h"
|
||||
#include "nsPresContext.h"
|
||||
#include "nsRefreshDriver.h"
|
||||
|
||||
#define VVP_LOG(...)
|
||||
// #define VVP_LOG(...) printf_stderr("VVP: " __VA_ARGS__)
|
||||
|
||||
using namespace mozilla;
|
||||
using namespace mozilla::dom;
|
||||
|
@ -14,7 +22,15 @@ using namespace mozilla::dom;
|
|||
VisualViewport::VisualViewport(nsPIDOMWindowInner* aWindow)
|
||||
: DOMEventTargetHelper(aWindow) {}
|
||||
|
||||
VisualViewport::~VisualViewport() {}
|
||||
VisualViewport::~VisualViewport() {
|
||||
if (mResizeEvent) {
|
||||
mResizeEvent->Revoke();
|
||||
}
|
||||
|
||||
if (mScrollEvent) {
|
||||
mScrollEvent->Revoke();
|
||||
}
|
||||
}
|
||||
|
||||
/* virtual */
|
||||
JSObject* VisualViewport::WrapObject(JSContext* aCx,
|
||||
|
@ -22,6 +38,24 @@ JSObject* VisualViewport::WrapObject(JSContext* aCx,
|
|||
return VisualViewport_Binding::Wrap(aCx, this, aGivenProto);
|
||||
}
|
||||
|
||||
/* virtual */
|
||||
void VisualViewport::GetEventTargetParent(EventChainPreVisitor& aVisitor) {
|
||||
EventMessage msg = aVisitor.mEvent->mMessage;
|
||||
|
||||
aVisitor.mCanHandle = true;
|
||||
EventTarget* parentTarget = nullptr;
|
||||
// Only our special internal events are allowed to escape the
|
||||
// Visual Viewport and be dispatched further up the DOM tree.
|
||||
if (msg == eMozVisualScroll || msg == eMozVisualResize) {
|
||||
if (nsPIDOMWindowInner* win = GetOwner()) {
|
||||
if (nsIDocument* doc = win->GetExtantDoc()) {
|
||||
parentTarget = doc;
|
||||
}
|
||||
}
|
||||
}
|
||||
aVisitor.SetParentTarget(parentTarget, false);
|
||||
}
|
||||
|
||||
CSSSize VisualViewport::VisualViewportSize() const {
|
||||
CSSSize size = CSSSize(0, 0);
|
||||
|
||||
|
@ -71,12 +105,8 @@ CSSPoint VisualViewport::VisualViewportOffset() const {
|
|||
CSSPoint VisualViewport::LayoutViewportOffset() const {
|
||||
CSSPoint offset = CSSPoint(0, 0);
|
||||
|
||||
nsIPresShell* presShell = GetPresShell();
|
||||
if (presShell) {
|
||||
nsIScrollableFrame* sf = presShell->GetRootScrollFrameAsScrollable();
|
||||
if (sf) {
|
||||
offset = CSSPoint::FromAppUnits(sf->GetScrollPosition());
|
||||
}
|
||||
if (nsIPresShell* presShell = GetPresShell()) {
|
||||
offset = CSSPoint::FromAppUnits(presShell->GetLayoutViewportOffset());
|
||||
}
|
||||
return offset;
|
||||
}
|
||||
|
@ -106,3 +136,133 @@ nsIPresShell* VisualViewport::GetPresShell() const {
|
|||
|
||||
return docShell->GetPresShell();
|
||||
}
|
||||
|
||||
nsPresContext* VisualViewport::GetPresContext() const {
|
||||
nsIPresShell* presShell = GetPresShell();
|
||||
if (!presShell) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return presShell->GetPresContext();
|
||||
}
|
||||
|
||||
/* ================= Resize event handling ================= */
|
||||
|
||||
void VisualViewport::PostResizeEvent() {
|
||||
VVP_LOG("%p: PostResizeEvent\n", this);
|
||||
if (mResizeEvent) {
|
||||
return;
|
||||
}
|
||||
|
||||
// The event constructor will register itself with the refresh driver.
|
||||
if (nsPresContext* presContext = GetPresContext()) {
|
||||
mResizeEvent = new VisualViewportResizeEvent(this, presContext);
|
||||
VVP_LOG("%p: PostResizeEvent, created new event\n", this);
|
||||
}
|
||||
}
|
||||
|
||||
VisualViewport::VisualViewportResizeEvent::VisualViewportResizeEvent(
|
||||
VisualViewport* aViewport, nsPresContext* aPresContext)
|
||||
: Runnable("VisualViewport::VisualViewportResizeEvent"),
|
||||
mViewport(aViewport) {
|
||||
aPresContext->RefreshDriver()->PostVisualViewportResizeEvent(this);
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
VisualViewport::VisualViewportResizeEvent::Run() {
|
||||
if (mViewport) {
|
||||
mViewport->FireResizeEvent();
|
||||
}
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
void VisualViewport::FireResizeEvent() {
|
||||
MOZ_ASSERT(mResizeEvent);
|
||||
mResizeEvent->Revoke();
|
||||
mResizeEvent = nullptr;
|
||||
|
||||
VVP_LOG("%p, FireResizeEvent, fire mozvisualresize\n", this);
|
||||
WidgetEvent mozEvent(true, eMozVisualResize);
|
||||
mozEvent.mFlags.mOnlySystemGroupDispatch = true;
|
||||
EventDispatcher::Dispatch(this, GetPresContext(), &mozEvent);
|
||||
|
||||
VVP_LOG("%p, FireResizeEvent, fire VisualViewport resize\n", this);
|
||||
WidgetEvent event(true, eResize);
|
||||
event.mFlags.mBubbles = false;
|
||||
event.mFlags.mCancelable = false;
|
||||
EventDispatcher::Dispatch(this, GetPresContext(), &event);
|
||||
}
|
||||
|
||||
/* ================= Scroll event handling ================= */
|
||||
|
||||
void VisualViewport::PostScrollEvent(const nsPoint& aPrevVisualOffset,
|
||||
const nsPoint& aPrevLayoutOffset) {
|
||||
VVP_LOG("%p: PostScrollEvent, prevRelativeOffset=%s\n", this,
|
||||
ToString(aPrevVisualOffset - aPrevLayoutOffset).c_str());
|
||||
if (mScrollEvent) {
|
||||
return;
|
||||
}
|
||||
|
||||
// The event constructor will register itself with the refresh driver.
|
||||
if (nsPresContext* presContext = GetPresContext()) {
|
||||
mScrollEvent = new VisualViewportScrollEvent(
|
||||
this, presContext, aPrevVisualOffset, aPrevLayoutOffset);
|
||||
VVP_LOG("%p: PostScrollEvent, created new event\n", this);
|
||||
}
|
||||
}
|
||||
|
||||
VisualViewport::VisualViewportScrollEvent::VisualViewportScrollEvent(
|
||||
VisualViewport* aViewport, nsPresContext* aPresContext,
|
||||
const nsPoint& aPrevVisualOffset, const nsPoint& aPrevLayoutOffset)
|
||||
: Runnable("VisualViewport::VisualViewportScrollEvent"),
|
||||
mViewport(aViewport),
|
||||
mPrevVisualOffset(aPrevVisualOffset),
|
||||
mPrevLayoutOffset(aPrevLayoutOffset) {
|
||||
aPresContext->RefreshDriver()->PostVisualViewportScrollEvent(this);
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
VisualViewport::VisualViewportScrollEvent::Run() {
|
||||
if (mViewport) {
|
||||
mViewport->FireScrollEvent();
|
||||
}
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
void VisualViewport::FireScrollEvent() {
|
||||
MOZ_ASSERT(mScrollEvent);
|
||||
nsPoint prevVisualOffset = mScrollEvent->PrevVisualOffset();
|
||||
nsPoint prevLayoutOffset = mScrollEvent->PrevLayoutOffset();
|
||||
mScrollEvent->Revoke();
|
||||
mScrollEvent = nullptr;
|
||||
|
||||
if (nsIPresShell* presShell = GetPresShell()) {
|
||||
if (presShell->GetVisualViewportOffset() != prevVisualOffset) {
|
||||
// The internal event will be fired whenever the visual viewport's
|
||||
// *absolute* offset changed, i.e. relative to the page.
|
||||
VVP_LOG("%p: FireScrollEvent, fire mozvisualscroll\n", this);
|
||||
WidgetEvent mozEvent(true, eMozVisualScroll);
|
||||
mozEvent.mFlags.mOnlySystemGroupDispatch = true;
|
||||
EventDispatcher::Dispatch(this, GetPresContext(), &mozEvent);
|
||||
}
|
||||
|
||||
// Check whether the relative visual viewport offset actually changed -
|
||||
// maybe both visual and layout viewport scrolled together and there was no
|
||||
// change after all.
|
||||
nsPoint curRelativeOffset =
|
||||
presShell->GetVisualViewportOffsetRelativeToLayoutViewport();
|
||||
nsPoint prevRelativeOffset = prevVisualOffset - prevLayoutOffset;
|
||||
VVP_LOG(
|
||||
"%p: FireScrollEvent, curRelativeOffset %s, "
|
||||
"prevRelativeOffset %s\n",
|
||||
this, ToString(curRelativeOffset).c_str(),
|
||||
ToString(prevRelativeOffset).c_str());
|
||||
if (curRelativeOffset != prevRelativeOffset) {
|
||||
VVP_LOG("%p, FireScrollEvent, fire VisualViewport scroll\n", this);
|
||||
WidgetGUIEvent event(true, eScroll, nullptr);
|
||||
event.mFlags.mBubbles = false;
|
||||
event.mFlags.mCancelable = false;
|
||||
EventDispatcher::Dispatch(this, GetPresContext(), &event);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -28,9 +28,55 @@ class VisualViewport final : public mozilla::DOMEventTargetHelper {
|
|||
double Width() const;
|
||||
double Height() const;
|
||||
double Scale() const;
|
||||
IMPL_EVENT_HANDLER(resize)
|
||||
IMPL_EVENT_HANDLER(scroll)
|
||||
|
||||
virtual JSObject* WrapObject(JSContext* aCx,
|
||||
JS::Handle<JSObject*> aGivenProto) override;
|
||||
void GetEventTargetParent(EventChainPreVisitor& aVisitor) override;
|
||||
|
||||
void PostResizeEvent();
|
||||
void PostScrollEvent(const nsPoint& aPrevVisualOffset,
|
||||
const nsPoint& aPrevLayoutOffset);
|
||||
|
||||
// These two events are modelled after the ScrollEvent class in
|
||||
// nsGfxScrollFrame.h.
|
||||
class VisualViewportResizeEvent : public Runnable {
|
||||
public:
|
||||
NS_DECL_NSIRUNNABLE
|
||||
VisualViewportResizeEvent(VisualViewport* aViewport,
|
||||
nsPresContext* aPresContext);
|
||||
void Revoke() { mViewport = nullptr; }
|
||||
|
||||
private:
|
||||
VisualViewport* mViewport;
|
||||
};
|
||||
|
||||
class VisualViewportScrollEvent : public Runnable {
|
||||
public:
|
||||
NS_DECL_NSIRUNNABLE
|
||||
VisualViewportScrollEvent(VisualViewport* aViewport,
|
||||
nsPresContext* aPresContext,
|
||||
const nsPoint& aPrevVisualOffset,
|
||||
const nsPoint& aPrevLayoutOffset);
|
||||
void Revoke() { mViewport = nullptr; }
|
||||
nsPoint PrevVisualOffset() const { return mPrevVisualOffset; }
|
||||
nsPoint PrevLayoutOffset() const { return mPrevLayoutOffset; }
|
||||
|
||||
private:
|
||||
VisualViewport* mViewport;
|
||||
// The VisualViewport "scroll" event is supposed to be fired only when the
|
||||
// *relative* offset between visual and layout viewport changes. The two
|
||||
// viewports are updated independently from each other, though, so the only
|
||||
// thing we can do is note the fact that one of the inputs into the relative
|
||||
// visual viewport offset changed and then check the offset again at the
|
||||
// next refresh driver tick, just before the event is going to fire.
|
||||
// Hopefully, at this point both visual and layout viewport positions have
|
||||
// been updated, so that we're able to tell whether the relative offset did
|
||||
// in fact change or not.
|
||||
const nsPoint mPrevVisualOffset;
|
||||
const nsPoint mPrevLayoutOffset;
|
||||
};
|
||||
|
||||
private:
|
||||
virtual ~VisualViewport();
|
||||
|
@ -39,6 +85,13 @@ class VisualViewport final : public mozilla::DOMEventTargetHelper {
|
|||
CSSPoint VisualViewportOffset() const;
|
||||
CSSPoint LayoutViewportOffset() const;
|
||||
nsIPresShell* GetPresShell() const;
|
||||
nsPresContext* GetPresContext() const;
|
||||
|
||||
void FireResizeEvent();
|
||||
void FireScrollEvent();
|
||||
|
||||
RefPtr<VisualViewportResizeEvent> mResizeEvent;
|
||||
RefPtr<VisualViewportScrollEvent> mScrollEvent;
|
||||
};
|
||||
|
||||
} // namespace dom
|
||||
|
|
|
@ -330,6 +330,10 @@ class EventTargetChainItem {
|
|||
if (aVisitor.mEvent->PropagationStopped()) {
|
||||
return;
|
||||
}
|
||||
if (aVisitor.mEvent->mFlags.mOnlySystemGroupDispatch &&
|
||||
!aVisitor.mEvent->mFlags.mInSystemGroup) {
|
||||
return;
|
||||
}
|
||||
if (aVisitor.mEvent->mFlags.mOnlySystemGroupDispatchInContent &&
|
||||
!aVisitor.mEvent->mFlags.mInSystemGroup && !IsCurrentTargetChrome()) {
|
||||
return;
|
||||
|
|
|
@ -265,6 +265,10 @@ FORWARDED_EVENT(load, eLoad, EventNameType_All, eBasicEventClass)
|
|||
FORWARDED_EVENT(resize, eResize, EventNameType_All, eBasicEventClass)
|
||||
FORWARDED_EVENT(scroll, eScroll, (EventNameType_HTMLXUL | EventNameType_SVGSVG),
|
||||
eBasicEventClass)
|
||||
NON_IDL_EVENT(mozvisualresize, eMozVisualResize, EventNameType_None,
|
||||
eBasicEventClass)
|
||||
NON_IDL_EVENT(mozvisualscroll, eMozVisualScroll, EventNameType_None,
|
||||
eBasicEventClass)
|
||||
|
||||
WINDOW_EVENT(afterprint, eAfterPrint,
|
||||
EventNameType_XUL | EventNameType_HTMLBodyOrFramesetOnly,
|
||||
|
|
|
@ -1631,6 +1631,14 @@ void HTMLMediaElement::SetVisible(bool aVisible) {
|
|||
}
|
||||
}
|
||||
|
||||
bool HTMLMediaElement::IsVideoDecodingSuspended() const {
|
||||
return mDecoder && mDecoder->IsVideoDecodingSuspended();
|
||||
}
|
||||
|
||||
bool HTMLMediaElement::IsVisible() const {
|
||||
return mVisibilityState == Visibility::APPROXIMATELY_VISIBLE;
|
||||
}
|
||||
|
||||
already_AddRefed<layers::Image> HTMLMediaElement::GetCurrentImage() {
|
||||
MarkAsTainted();
|
||||
|
||||
|
@ -6281,6 +6289,9 @@ void HTMLMediaElement::OnVisibilityChange(Visibility aNewVisibility) {
|
|||
("OnVisibilityChange(): %s\n", VisibilityString(aNewVisibility)));
|
||||
|
||||
mVisibilityState = aNewVisibility;
|
||||
if (StaticPrefs::MediaTestVideoSuspend()) {
|
||||
DispatchAsyncEvent(NS_LITERAL_STRING("visibilitychanged"));
|
||||
}
|
||||
|
||||
if (!mDecoder) {
|
||||
return;
|
||||
|
|
|
@ -560,6 +560,12 @@ class HTMLMediaElement : public nsGenericHTMLElement,
|
|||
// For use by mochitests. Enabling pref "media.test.video-suspend"
|
||||
bool HasSuspendTaint() const;
|
||||
|
||||
// For use by mochitests.
|
||||
bool IsVideoDecodingSuspended() const;
|
||||
|
||||
// For use by mochitests only.
|
||||
bool IsVisible() const;
|
||||
|
||||
// Synchronously, return the next video frame and mark the element unable to
|
||||
// participate in decode suspending.
|
||||
//
|
||||
|
|
|
@ -434,9 +434,11 @@ void MediaDecoder::OnPlaybackEvent(MediaPlaybackEvent&& aEvent) {
|
|||
break;
|
||||
case MediaPlaybackEvent::EnterVideoSuspend:
|
||||
GetOwner()->DispatchAsyncEvent(NS_LITERAL_STRING("mozentervideosuspend"));
|
||||
mIsVideoDecodingSuspended = true;
|
||||
break;
|
||||
case MediaPlaybackEvent::ExitVideoSuspend:
|
||||
GetOwner()->DispatchAsyncEvent(NS_LITERAL_STRING("mozexitvideosuspend"));
|
||||
mIsVideoDecodingSuspended = false;
|
||||
break;
|
||||
case MediaPlaybackEvent::StartVideoSuspendTimer:
|
||||
GetOwner()->DispatchAsyncEvent(
|
||||
|
@ -459,6 +461,10 @@ void MediaDecoder::OnPlaybackEvent(MediaPlaybackEvent&& aEvent) {
|
|||
}
|
||||
}
|
||||
|
||||
bool MediaDecoder::IsVideoDecodingSuspended() const {
|
||||
return mIsVideoDecodingSuspended;
|
||||
}
|
||||
|
||||
void MediaDecoder::OnPlaybackErrorEvent(const MediaResult& aError) {
|
||||
DecodeError(aError);
|
||||
}
|
||||
|
@ -966,14 +972,6 @@ void MediaDecoder::UpdateVideoDecodeMode() {
|
|||
return;
|
||||
}
|
||||
|
||||
// If an element is in-tree with UNTRACKED visibility, the visibility is
|
||||
// incomplete and don't update the video decode mode.
|
||||
if (mIsElementInTree && mElementVisibility == Visibility::UNTRACKED) {
|
||||
LOG("UpdateVideoDecodeMode(), early return because we have incomplete "
|
||||
"visibility states.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Seeking is required when leaving suspend mode.
|
||||
if (!mMediaSeekable) {
|
||||
LOG("UpdateVideoDecodeMode(), set Normal because the media is not "
|
||||
|
@ -1014,6 +1012,16 @@ void MediaDecoder::UpdateVideoDecodeMode() {
|
|||
return;
|
||||
}
|
||||
|
||||
// If the element is in-tree with UNTRACKED visibility, that means the element
|
||||
// is not close enough to the viewport so we have not start to update its
|
||||
// visibility. In this case, it's equals to invisible.
|
||||
if (mIsElementInTree && mElementVisibility == Visibility::UNTRACKED) {
|
||||
LOG("UpdateVideoDecodeMode(), set Suspend because element hasn't be "
|
||||
"updated visibility state.");
|
||||
mDecoderStateMachine->SetVideoDecodeMode(VideoDecodeMode::Suspend);
|
||||
return;
|
||||
}
|
||||
|
||||
// Otherwise, depends on the owner's visibility state.
|
||||
// A element is visible only if its document is visible and the element
|
||||
// itself is visible.
|
||||
|
|
|
@ -312,6 +312,8 @@ class MediaDecoder : public DecoderDoctorLifeLogger<MediaDecoder> {
|
|||
|
||||
void SetIsBackgroundVideoDecodingAllowed(bool aAllowed);
|
||||
|
||||
bool IsVideoDecodingSuspended() const;
|
||||
|
||||
/******
|
||||
* The following methods must only be called on the main
|
||||
* thread.
|
||||
|
@ -578,6 +580,9 @@ class MediaDecoder : public DecoderDoctorLifeLogger<MediaDecoder> {
|
|||
MediaEventListener mOnDecodeWarning;
|
||||
MediaEventListener mOnNextFrameStatus;
|
||||
|
||||
// True if we have suspended video decoding.
|
||||
bool mIsVideoDecodingSuspended = false;
|
||||
|
||||
protected:
|
||||
// PlaybackRate and pitch preservation status we should start at.
|
||||
double mPlaybackRate;
|
||||
|
|
|
@ -61,42 +61,40 @@ class DecodedStreamGraphListener {
|
|||
TrackID aVideoTrackID,
|
||||
MozPromiseHolder<DecodedStream::EndedPromise>&& aVideoEndedHolder,
|
||||
AbstractThread* aMainThread)
|
||||
: mMutex("DecodedStreamGraphListener::mMutex"),
|
||||
mAudioTrackListener(IsTrackIDExplicit(aAudioTrackID)
|
||||
: mAudioTrackListener(IsTrackIDExplicit(aAudioTrackID)
|
||||
? MakeRefPtr<DecodedStreamTrackListener>(
|
||||
this, aStream, aAudioTrackID)
|
||||
: nullptr),
|
||||
mAudioTrackID(aAudioTrackID),
|
||||
mAudioEndedHolder(std::move(aAudioEndedHolder)),
|
||||
mVideoTrackListener(IsTrackIDExplicit(aVideoTrackID)
|
||||
? MakeRefPtr<DecodedStreamTrackListener>(
|
||||
this, aStream, aVideoTrackID)
|
||||
: nullptr),
|
||||
mAudioTrackID(aAudioTrackID),
|
||||
mAudioEndedHolder(std::move(aAudioEndedHolder)),
|
||||
mVideoTrackID(aVideoTrackID),
|
||||
mVideoEndedHolder(std::move(aVideoEndedHolder)),
|
||||
mStream(aStream),
|
||||
mAbstractMainThread(aMainThread) {
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
if (mAudioTrackListener) {
|
||||
aStream->AddTrackListener(mAudioTrackListener, mAudioTrackID);
|
||||
mStream->AddTrackListener(mAudioTrackListener, mAudioTrackID);
|
||||
} else {
|
||||
mAudioEndedHolder.ResolveIfExists(true, __func__);
|
||||
}
|
||||
|
||||
if (mVideoTrackListener) {
|
||||
aStream->AddTrackListener(mVideoTrackListener, mVideoTrackID);
|
||||
mStream->AddTrackListener(mVideoTrackListener, mVideoTrackID);
|
||||
} else {
|
||||
mVideoEndedHolder.ResolveIfExists(true, __func__);
|
||||
}
|
||||
}
|
||||
|
||||
void NotifyOutput(const RefPtr<SourceMediaStream>& aStream, TrackID aTrackID,
|
||||
StreamTime aCurrentTrackTime) {
|
||||
void NotifyOutput(TrackID aTrackID, StreamTime aCurrentTrackTime) {
|
||||
if (aTrackID != mAudioTrackID && mAudioTrackID != TRACK_NONE) {
|
||||
// Only audio playout drives the clock forward, if present.
|
||||
return;
|
||||
}
|
||||
if (aStream) {
|
||||
mOnOutput.Notify(aStream->StreamTimeToMicroseconds(aCurrentTrackTime));
|
||||
}
|
||||
mOnOutput.Notify(mStream->StreamTimeToMicroseconds(aCurrentTrackTime));
|
||||
}
|
||||
|
||||
TrackID AudioTrackID() const { return mAudioTrackID; }
|
||||
|
@ -115,16 +113,21 @@ class DecodedStreamGraphListener {
|
|||
}
|
||||
|
||||
void Forget() {
|
||||
RefPtr<DecodedStreamGraphListener> self = this;
|
||||
mAbstractMainThread->Dispatch(
|
||||
NS_NewRunnableFunction("DecodedStreamGraphListener::Forget", [self]() {
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
self->mAudioEndedHolder.ResolveIfExists(false, __func__);
|
||||
self->mVideoEndedHolder.ResolveIfExists(false, __func__);
|
||||
}));
|
||||
MutexAutoLock lock(mMutex);
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
|
||||
if (mAudioTrackListener && !mStream->IsDestroyed()) {
|
||||
mStream->EndTrack(mAudioTrackID);
|
||||
mStream->RemoveTrackListener(mAudioTrackListener, mAudioTrackID);
|
||||
}
|
||||
mAudioTrackListener = nullptr;
|
||||
mAudioEndedHolder.ResolveIfExists(false, __func__);
|
||||
|
||||
if (mVideoTrackListener && !mStream->IsDestroyed()) {
|
||||
mStream->EndTrack(mVideoTrackID);
|
||||
mStream->RemoveTrackListener(mVideoTrackListener, mVideoTrackID);
|
||||
}
|
||||
mVideoTrackListener = nullptr;
|
||||
mVideoEndedHolder.ResolveIfExists(false, __func__);
|
||||
}
|
||||
|
||||
MediaEventSource<int64_t>& OnOutput() { return mOnOutput; }
|
||||
|
@ -137,16 +140,15 @@ class DecodedStreamGraphListener {
|
|||
|
||||
MediaEventProducer<int64_t> mOnOutput;
|
||||
|
||||
Mutex mMutex;
|
||||
// Members below are protected by mMutex.
|
||||
RefPtr<DecodedStreamTrackListener> mAudioTrackListener;
|
||||
RefPtr<DecodedStreamTrackListener> mVideoTrackListener;
|
||||
// Main thread only.
|
||||
RefPtr<DecodedStreamTrackListener> mAudioTrackListener;
|
||||
const TrackID mAudioTrackID;
|
||||
MozPromiseHolder<DecodedStream::EndedPromise> mAudioEndedHolder;
|
||||
RefPtr<DecodedStreamTrackListener> mVideoTrackListener;
|
||||
const TrackID mVideoTrackID;
|
||||
MozPromiseHolder<DecodedStream::EndedPromise> mVideoEndedHolder;
|
||||
|
||||
const RefPtr<SourceMediaStream> mStream;
|
||||
const RefPtr<AbstractThread> mAbstractMainThread;
|
||||
};
|
||||
|
||||
|
@ -157,7 +159,7 @@ DecodedStreamTrackListener::DecodedStreamTrackListener(
|
|||
|
||||
void DecodedStreamTrackListener::NotifyOutput(MediaStreamGraph* aGraph,
|
||||
StreamTime aCurrentTrackTime) {
|
||||
mGraphListener->NotifyOutput(mStream, mTrackID, aCurrentTrackTime);
|
||||
mGraphListener->NotifyOutput(mTrackID, aCurrentTrackTime);
|
||||
}
|
||||
|
||||
void DecodedStreamTrackListener::NotifyEnded() {
|
||||
|
@ -450,7 +452,7 @@ void DecodedStream::Shutdown() {
|
|||
mWatchManager.Shutdown();
|
||||
}
|
||||
|
||||
void DecodedStream::DestroyData(UniquePtr<DecodedStreamData> aData) {
|
||||
void DecodedStream::DestroyData(UniquePtr<DecodedStreamData>&& aData) {
|
||||
AssertOwnerThread();
|
||||
|
||||
if (!aData) {
|
||||
|
@ -459,11 +461,9 @@ void DecodedStream::DestroyData(UniquePtr<DecodedStreamData> aData) {
|
|||
|
||||
mOutputListener.Disconnect();
|
||||
|
||||
DecodedStreamData* data = aData.release();
|
||||
data->Forget();
|
||||
nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction("DecodedStream::DestroyData",
|
||||
[=]() { delete data; });
|
||||
NS_DispatchToMainThread(r.forget());
|
||||
NS_DispatchToMainThread(
|
||||
NS_NewRunnableFunction("DecodedStream::DestroyData",
|
||||
[data = std::move(aData)]() { data->Forget(); }));
|
||||
}
|
||||
|
||||
void DecodedStream::SetPlaying(bool aPlaying) {
|
||||
|
|
|
@ -76,7 +76,7 @@ class DecodedStream : public MediaSink {
|
|||
media::TimeUnit FromMicroseconds(int64_t aTime) {
|
||||
return media::TimeUnit::FromMicroseconds(aTime);
|
||||
}
|
||||
void DestroyData(UniquePtr<DecodedStreamData> aData);
|
||||
void DestroyData(UniquePtr<DecodedStreamData>&& aData);
|
||||
void SendAudio(double aVolume, bool aIsSameOrigin,
|
||||
const PrincipalHandle& aPrincipalHandle);
|
||||
void SendVideo(bool aIsSameOrigin, const PrincipalHandle& aPrincipalHandle);
|
||||
|
|
|
@ -48,6 +48,28 @@ function appendVideoToDoc(url, token, width, height) {
|
|||
return v;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param {HTMLMediaElement} video Video element with under test.
|
||||
* @returns {Promise} Promise that is resolved when video 'visibilitychanged' event fires.
|
||||
*/
|
||||
function waitUntilVisible(video) {
|
||||
let videoChrome = SpecialPowers.wrap(video);
|
||||
if (videoChrome.isVisible) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
return new Promise(resolve => {
|
||||
videoChrome.addEventListener("visibilitychanged", () => {
|
||||
if (videoChrome.isVisible) {
|
||||
ok(true, `${video.token} is visible.`);
|
||||
videoChrome.removeEventListener("visibilitychanged", this);
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {HTMLMediaElement} video Video element under test.
|
||||
* @returns {Promise} Promise that is resolved when video 'playing' event fires.
|
||||
|
|
|
@ -767,6 +767,9 @@ skip-if = toolkit == 'android' # bug 1306916, bug 1329566, android(bug 1232305)
|
|||
[test_bug1248229.html]
|
||||
skip-if = android_version == '17' # bug 1306917, 1323778, android(bug 1232305)
|
||||
tags=capturestream
|
||||
[test_bug1512958.html]
|
||||
skip-if = toolkit == 'android' # android(bug 1232305)
|
||||
tags=msg capturestream
|
||||
[test_can_play_type.html]
|
||||
skip-if = (android_version == '23' && debug) || (android_version == '25' && debug) # android(bug 1232305)
|
||||
[test_can_play_type_mpeg.html]
|
||||
|
|
|
@ -60,10 +60,11 @@ startTest({
|
|||
let v = appendVideoToDoc(test.name, token);
|
||||
manager.started(token);
|
||||
|
||||
waitUntilPlaying(v)
|
||||
waitUntilVisible(v)
|
||||
.then(() => waitUntilPlaying(v))
|
||||
.then(() => testSuspendTimerStartedWhenHidden(v))
|
||||
.then(() => testSuspendTimerCanceledWhenTainted(v))
|
||||
.then(() => { manager.finished(token); });
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</script>
|
||||
|
|
|
@ -0,0 +1,74 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<title>Test that pausing and resuming a captured media element with audio doesn't stall</title>
|
||||
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
|
||||
<script type="text/javascript" src="manifest.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<audio id="a"></audio>
|
||||
<pre id="test">
|
||||
<script class="testbody" type="text/javascript">
|
||||
function dumpEvent({target, type}) {
|
||||
info(`${target.name} GOT EVENT ${type} currentTime=${target.currentTime} ` +
|
||||
`paused=${target.paused} ended=${target.ended} ` +
|
||||
`readyState=${target.readyState}`);
|
||||
}
|
||||
|
||||
function wait(ms) {
|
||||
return new Promise(resolve => setTimeout(resolve, ms));
|
||||
}
|
||||
|
||||
const a = document.getElementById('a');
|
||||
|
||||
const events = ["timeupdate", "seeking", "seeked", "ended", "playing", "pause"];
|
||||
for (let ev of events) {
|
||||
a.addEventListener(ev, dumpEvent);
|
||||
}
|
||||
|
||||
(async _ => {
|
||||
try {
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
SimpleTest.requestFlakyTimeout("Timeouts for shortcutting test-timeout");
|
||||
|
||||
const test = getPlayableAudio(gTrackTests.filter(t => t.duration > 2));
|
||||
if (!test) {
|
||||
todo(false, "No playable audio");
|
||||
return;
|
||||
}
|
||||
|
||||
// Start playing and capture
|
||||
a.src = test.name;
|
||||
a.name = test.name;
|
||||
const ac = new AudioContext();
|
||||
const src = ac.createMediaElementSource(a);
|
||||
a.play();
|
||||
do {
|
||||
await new Promise(r => a.ontimeupdate = r);
|
||||
} while(a.currentTime == 0)
|
||||
|
||||
// Pause to trigger recreating tracks in DecodedStream
|
||||
a.pause();
|
||||
await new Promise(r => a.onpause = r);
|
||||
|
||||
// Resuming should now work. Bug 1512958 would cause a stall because the
|
||||
// original track wasn't ended and we'd block on it.
|
||||
// See https://bugzilla.mozilla.org/show_bug.cgi?id=1512958#c5
|
||||
a.play();
|
||||
await new Promise(r => a.onplaying = r);
|
||||
a.currentTime = test.duration - 1;
|
||||
await Promise.race([
|
||||
new Promise(res => a.onended = res),
|
||||
wait(30000).then(_ => Promise.reject(new Error("Timeout"))),
|
||||
]);
|
||||
} catch(e) {
|
||||
ok(false, e);
|
||||
} finally {
|
||||
SimpleTest.finish();
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
</pre>
|
||||
</body>
|
||||
</html>
|
|
@ -204,13 +204,15 @@ partial interface HTMLMediaElement {
|
|||
};
|
||||
|
||||
/*
|
||||
* This is an API for simulating visibility changes to help debug and write
|
||||
* These APIs are testing only, they are used to simulate visibility changes to help debug and write
|
||||
* tests about suspend-video-decoding.
|
||||
*
|
||||
* - SetVisible() is for simulating visibility changes.
|
||||
* - HasSuspendTaint() is for querying that the element's decoder cannot suspend
|
||||
* video decoding because it has been tainted by an operation, such as
|
||||
* drawImage().
|
||||
* - isVisible is a boolean value which indicate whether media element is visible.
|
||||
* - isVideoDecodingSuspended() is used to know whether video decoding has suspended.
|
||||
*/
|
||||
partial interface HTMLMediaElement {
|
||||
[Pref="media.test.video-suspend"]
|
||||
|
@ -218,6 +220,12 @@ partial interface HTMLMediaElement {
|
|||
|
||||
[Pref="media.test.video-suspend"]
|
||||
boolean hasSuspendTaint();
|
||||
|
||||
[ChromeOnly]
|
||||
readonly attribute boolean isVisible;
|
||||
|
||||
[ChromeOnly]
|
||||
readonly attribute boolean isVideoDecodingSuspended;
|
||||
};
|
||||
|
||||
/* Audio Output Devices API */
|
||||
|
|
|
@ -18,4 +18,7 @@ interface VisualViewport : EventTarget {
|
|||
readonly attribute double height;
|
||||
|
||||
readonly attribute double scale;
|
||||
|
||||
attribute EventHandler onresize;
|
||||
attribute EventHandler onscroll;
|
||||
};
|
||||
|
|
|
@ -282,6 +282,8 @@ function runSubtestsSeriallyInFreshWindows(aSubtests) {
|
|||
// called with at most 2 arguments.
|
||||
return SimpleTest.ok.apply(SimpleTest, arguments);
|
||||
};
|
||||
w.todo_is = function(a, b, msg) { return todo_is(a, b, aFile + " | " + msg); };
|
||||
w.todo = function(cond, msg) { return todo(cond, aFile + " | " + msg); };
|
||||
if (test.onload) {
|
||||
w.addEventListener('load', function(e) { test.onload(w); }, { once: true });
|
||||
}
|
||||
|
|
|
@ -7,26 +7,65 @@
|
|||
<script type="application/javascript" src="apz_test_native_event_utils.js"></script>
|
||||
<script type="application/javascript" src="apz_test_utils.js"></script>
|
||||
<script type="application/javascript" src="/tests/SimpleTest/paint_listener.js"></script>
|
||||
<script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
|
||||
<script type="application/javascript">
|
||||
|
||||
function scrollPage() {
|
||||
var transformEnd = function() {
|
||||
SpecialPowers.Services.obs.removeObserver(transformEnd, "APZ:TransformEnd", false);
|
||||
dump("Transform complete; flushing repaints...\n");
|
||||
flushApzRepaints(checkScroll);
|
||||
};
|
||||
SpecialPowers.Services.obs.addObserver(transformEnd, "APZ:TransformEnd");
|
||||
function* test(testDriver) {
|
||||
let scrEvt = new EventCounter(window, "scroll");
|
||||
let visScrEvt = new EventCounter(window.visualViewport, "scroll");
|
||||
// Our internal visual viewport events aren't restricted to the visual view-
|
||||
// port itself, so we can listen on the window itself, however the event
|
||||
// listener needs to be in the system group.
|
||||
let visScrEvtInternal = new EventCounter(window, "mozvisualscroll",
|
||||
{ mozSystemGroup: true });
|
||||
|
||||
// This listener will trigger the test to continue once APZ is done with
|
||||
// processing the scroll.
|
||||
SpecialPowers.Services.obs.addObserver(testDriver, "APZ:TransformEnd");
|
||||
|
||||
synthesizeNativeTouchDrag(document.body, 10, 100, 0, -50);
|
||||
dump("Finished native drag, waiting for transform-end observer...\n");
|
||||
}
|
||||
|
||||
function checkScroll() {
|
||||
// Wait for the APZ:TransformEnd to be fired after touch events are processed.
|
||||
yield true;
|
||||
|
||||
// We get here once the APZ:TransformEnd has fired, so we don't need that
|
||||
// observer any more.
|
||||
SpecialPowers.Services.obs.removeObserver(testDriver, "APZ:TransformEnd", false);
|
||||
|
||||
// Flush state.
|
||||
yield waitForApzFlushedRepaints(testDriver);
|
||||
|
||||
is(window.scrollY, 50, "check that the window scrolled");
|
||||
subtestDone();
|
||||
|
||||
// Check we've got the expected events.
|
||||
// This page is using "width=device-width; initial-scale=1.0" and we haven't
|
||||
// pinch-zoomed any further, so layout and visual viewports have the same
|
||||
// size and will scroll together. Therefore we should be getting layout
|
||||
// viewport "scroll" events as well.
|
||||
scrEvt.unregister();
|
||||
ok(scrEvt.count > 0, "Got some layout viewport scroll events");
|
||||
// This one is a bit tricky: Visual viewport "scroll" events are supposed to
|
||||
// fire only when the relative offset between layout and visual viewport
|
||||
// changes. Even when they're both scrolling together, we may update their
|
||||
// positions independently, though, leading to some jitter in the offset and
|
||||
// triggering the event after all.
|
||||
// At least for the case here, where both viewports are the same size and we
|
||||
// have a freshly loaded page, we should however be able to keep the offset at
|
||||
// a constant zero and therefore not cause any visual viewport scroll events
|
||||
// to fire.
|
||||
visScrEvt.unregister();
|
||||
is(visScrEvt.count, 0, "Got no visual viewport scroll events");
|
||||
visScrEvtInternal.unregister();
|
||||
// Our internal visual viewport scroll event on the other hand only cares
|
||||
// about the absolute offset of the visual viewport and should therefore
|
||||
// definitively fire.
|
||||
ok(visScrEvtInternal.count > 0, "Got some mozvisualscroll events");
|
||||
}
|
||||
|
||||
waitUntilApzStable().then(scrollPage);
|
||||
waitUntilApzStable()
|
||||
.then(runContinuation(test))
|
||||
.then(subtestDone);
|
||||
|
||||
</script>
|
||||
</head>
|
||||
|
|
|
@ -7,9 +7,22 @@
|
|||
<script type="application/javascript" src="apz_test_native_event_utils.js"></script>
|
||||
<script type="application/javascript" src="apz_test_utils.js"></script>
|
||||
<script type="application/javascript" src="/tests/SimpleTest/paint_listener.js"></script>
|
||||
<script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
|
||||
<script type="application/javascript">
|
||||
|
||||
function* test(testDriver) {
|
||||
let visResEvt = new EventCounter(window.visualViewport, "resize");
|
||||
let visScrEvt = new EventCounter(window.visualViewport, "scroll");
|
||||
// Our internal visual viewport events aren't restricted to the visual view-
|
||||
// port itself, so we can listen on the window itself, however the event
|
||||
// listener needs to be in the system group.
|
||||
let visResEvtInternal = new EventCounter(window, "mozvisualresize",
|
||||
{ mozSystemGroup: true });
|
||||
let visScrEvtInternal = new EventCounter(window, "mozvisualscroll",
|
||||
{ mozSystemGroup: true });
|
||||
let visResEvtContent = new EventCounter(window, "mozvisualresize");
|
||||
let visScrEvtContent = new EventCounter(window, "mozvisualscroll");
|
||||
|
||||
var initial_resolution = getResolution();
|
||||
ok(initial_resolution > 0,
|
||||
'The initial_resolution is ' + initial_resolution + ', which is some sane value');
|
||||
|
@ -38,9 +51,31 @@ function* test(testDriver) {
|
|||
SpecialPowers.Services.obs.removeObserver(testDriver, "APZ:TransformEnd", false);
|
||||
|
||||
// Flush state and get the resolution we're at now
|
||||
yield flushApzRepaints(testDriver);
|
||||
yield waitForApzFlushedRepaints(testDriver);
|
||||
let final_resolution = getResolution();
|
||||
ok(final_resolution > initial_resolution, 'The final resolution (' + final_resolution + ') is greater after zooming in');
|
||||
|
||||
// Check we've got the expected events.
|
||||
// Pinch-zooming the page should fire visual viewport resize events:
|
||||
visResEvt.unregister();
|
||||
ok(visResEvt.count > 0, "Got some visual viewport resize events");
|
||||
visResEvtInternal.unregister();
|
||||
ok(visResEvtInternal.count > 0, "Got some mozvisualresize events");
|
||||
|
||||
// We're pinch-zooming somewhere in the middle of the page, so the visual
|
||||
// viewport's coordinates change, too.
|
||||
// This is true both relative to the page (mozvisualscroll), as well as
|
||||
// relative to the layout viewport (visual viewport "scroll" event).
|
||||
visScrEvt.unregister();
|
||||
ok(visScrEvt.count > 0, "Got some visual viewport scroll events");
|
||||
visScrEvtInternal.unregister();
|
||||
ok(visScrEvtInternal.count > 0, "Got some mozvisualscroll events");
|
||||
|
||||
// Our internal events shouldn't leak to normal content.
|
||||
visResEvtContent.unregister();
|
||||
is(visResEvtContent.count, 0, "Got no mozvisualresize events in content");
|
||||
visScrEvtContent.unregister();
|
||||
is(visScrEvtContent.count, 0, "Got no mozvisualscroll events in content");
|
||||
}
|
||||
|
||||
waitUntilApzStable()
|
||||
|
|
|
@ -13,7 +13,8 @@ var basic_pan_prefs = getPrefs("TOUCH_EVENTS:PAN");
|
|||
|
||||
var subtests = [
|
||||
// Simple tests to exercise basic panning behaviour
|
||||
{'file': 'helper_basic_pan.html', 'prefs': basic_pan_prefs},
|
||||
// The visual viewport isn't yet enabled by default and we want to test its events, too.
|
||||
{'file': 'helper_basic_pan.html', 'prefs': basic_pan_prefs.concat([["dom.visualviewport.enabled", true]])},
|
||||
{'file': 'helper_div_pan.html', 'prefs': basic_pan_prefs},
|
||||
{'file': 'helper_iframe_pan.html', 'prefs': basic_pan_prefs},
|
||||
|
||||
|
|
|
@ -39,6 +39,8 @@ var prefs = [
|
|||
// they too need to be disabled for now.
|
||||
["layout.display-list.retain", false],
|
||||
["layout.display-list.retain.chrome", false],
|
||||
// The VisualViewport API currently isn't enabled by default.
|
||||
["dom.visualviewport.enabled", true],
|
||||
];
|
||||
|
||||
// Increase the tap timeouts so the double-tap is still detected in case of
|
||||
|
|
|
@ -175,7 +175,8 @@ static ScreenMargin ScrollFrame(nsIContent* aContent,
|
|||
if (sf->IsRootScrollFrameOfDocument()) {
|
||||
if (nsCOMPtr<nsIPresShell> shell = GetPresShell(aContent)) {
|
||||
shell->SetVisualViewportOffset(
|
||||
CSSPoint::ToAppUnits(aRequest.GetScrollOffset()));
|
||||
CSSPoint::ToAppUnits(aRequest.GetScrollOffset()),
|
||||
shell->GetLayoutViewportOffset());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -187,6 +187,7 @@
|
|||
#include "nsBindingManager.h"
|
||||
#include "nsClassHashtable.h"
|
||||
#include "nsHashKeys.h"
|
||||
#include "VisualViewport.h"
|
||||
|
||||
#ifdef MOZ_TASK_TRACER
|
||||
#include "GeckoTaskTracer.h"
|
||||
|
@ -5173,6 +5174,9 @@ nsresult PresShell::SetResolutionAndScaleTo(float aResolution,
|
|||
if (aOrigin != nsGkAtoms::apz) {
|
||||
mResolutionUpdated = true;
|
||||
}
|
||||
if (auto* window = nsGlobalWindowInner::Cast(mDocument->GetInnerWindow())) {
|
||||
window->VisualViewport()->PostResizeEvent();
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
@ -10031,13 +10035,34 @@ void nsIPresShell::SetVisualViewportSize(nscoord aWidth, nscoord aHeight) {
|
|||
rootScrollFrame->MarkScrollbarsDirtyForReflow();
|
||||
}
|
||||
MarkFixedFramesForReflow(nsIPresShell::eResize);
|
||||
|
||||
if (auto* window = nsGlobalWindowInner::Cast(mDocument->GetInnerWindow())) {
|
||||
window->VisualViewport()->PostResizeEvent();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void nsIPresShell::SetVisualViewportOffset(
|
||||
const nsPoint& aScrollOffset, const nsPoint& aPrevLayoutScrollPos) {
|
||||
if (mVisualViewportOffset != aScrollOffset) {
|
||||
nsPoint prevOffset = mVisualViewportOffset;
|
||||
mVisualViewportOffset = aScrollOffset;
|
||||
|
||||
if (auto* window = nsGlobalWindowInner::Cast(mDocument->GetInnerWindow())) {
|
||||
window->VisualViewport()->PostScrollEvent(prevOffset,
|
||||
aPrevLayoutScrollPos);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
nsPoint nsIPresShell::GetVisualViewportOffsetRelativeToLayoutViewport() const {
|
||||
return GetVisualViewportOffset() - GetLayoutViewportOffset();
|
||||
}
|
||||
|
||||
nsPoint nsIPresShell::GetLayoutViewportOffset() const {
|
||||
nsPoint result;
|
||||
if (nsIScrollableFrame* sf = GetRootScrollFrameAsScrollable()) {
|
||||
result = GetVisualViewportOffset() - sf->GetScrollPosition();
|
||||
result = sf->GetScrollPosition();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
|
|
@ -1647,14 +1647,15 @@ class nsIPresShell : public nsStubDocumentObserver {
|
|||
return mVisualViewportSize;
|
||||
}
|
||||
|
||||
void SetVisualViewportOffset(const nsPoint& aScrollOffset) {
|
||||
mVisualViewportOffset = aScrollOffset;
|
||||
}
|
||||
void SetVisualViewportOffset(const nsPoint& aScrollOffset,
|
||||
const nsPoint& aPrevLayoutScrollPos);
|
||||
|
||||
nsPoint GetVisualViewportOffset() const { return mVisualViewportOffset; }
|
||||
|
||||
nsPoint GetVisualViewportOffsetRelativeToLayoutViewport() const;
|
||||
|
||||
nsPoint GetLayoutViewportOffset() const;
|
||||
|
||||
virtual void WindowSizeMoveDone() = 0;
|
||||
virtual void SysColorChanged() = 0;
|
||||
virtual void ThemeChanged() = 0;
|
||||
|
|
|
@ -1144,6 +1144,23 @@ void nsRefreshDriver::RemoveTimerAdjustmentObserver(
|
|||
mTimerAdjustmentObservers.RemoveElement(aObserver);
|
||||
}
|
||||
|
||||
void nsRefreshDriver::PostVisualViewportResizeEvent(
|
||||
VVPResizeEvent* aResizeEvent) {
|
||||
mVisualViewportResizeEvents.AppendElement(aResizeEvent);
|
||||
EnsureTimerStarted();
|
||||
}
|
||||
|
||||
void nsRefreshDriver::DispatchVisualViewportResizeEvents() {
|
||||
// We're taking a hint from scroll events and only dispatch the current set
|
||||
// of queued resize events. If additional events are posted in response to
|
||||
// the current events being dispatched, we'll dispatch them on the next tick.
|
||||
VisualViewportResizeEventArray events;
|
||||
events.SwapElements(mVisualViewportResizeEvents);
|
||||
for (auto& event : events) {
|
||||
event->Run();
|
||||
}
|
||||
}
|
||||
|
||||
void nsRefreshDriver::PostScrollEvent(mozilla::Runnable* aScrollEvent, bool aDelayed) {
|
||||
if (aDelayed) {
|
||||
mDelayedScrollEvents.AppendElement(aScrollEvent);
|
||||
|
@ -1165,6 +1182,24 @@ void nsRefreshDriver::DispatchScrollEvents() {
|
|||
}
|
||||
}
|
||||
|
||||
void nsRefreshDriver::PostVisualViewportScrollEvent(
|
||||
VVPScrollEvent* aScrollEvent) {
|
||||
mVisualViewportScrollEvents.AppendElement(aScrollEvent);
|
||||
EnsureTimerStarted();
|
||||
}
|
||||
|
||||
void nsRefreshDriver::DispatchVisualViewportScrollEvents() {
|
||||
// Scroll events are one-shot, so after running them we can drop them.
|
||||
// However, dispatching a scroll event can potentially cause more scroll
|
||||
// events to be posted, so we move the initial set into a temporary array
|
||||
// first. (Newly posted scroll events will be dispatched on the next tick.)
|
||||
VisualViewportScrollEventArray events;
|
||||
events.SwapElements(mVisualViewportScrollEvents);
|
||||
for (auto& event : events) {
|
||||
event->Run();
|
||||
}
|
||||
}
|
||||
|
||||
void nsRefreshDriver::AddPostRefreshObserver(
|
||||
nsAPostRefreshObserver* aObserver) {
|
||||
mPostRefreshObservers.AppendElement(aObserver);
|
||||
|
@ -1734,6 +1769,7 @@ void nsRefreshDriver::Tick(VsyncId aId, TimeStamp aNowTime) {
|
|||
}
|
||||
shell->FireResizeEvent();
|
||||
}
|
||||
DispatchVisualViewportResizeEvents();
|
||||
|
||||
/*
|
||||
* The timer holds a reference to |this| while calling |Notify|.
|
||||
|
@ -1757,6 +1793,7 @@ void nsRefreshDriver::Tick(VsyncId aId, TimeStamp aNowTime) {
|
|||
// This is the FlushType::Style case.
|
||||
|
||||
DispatchScrollEvents();
|
||||
DispatchVisualViewportScrollEvents();
|
||||
DispatchAnimationEvents();
|
||||
RunFullscreenSteps();
|
||||
RunFrameRequestCallbacks(aNowTime);
|
||||
|
|
|
@ -24,6 +24,7 @@
|
|||
#include "nsHashKeys.h"
|
||||
#include "mozilla/Attributes.h"
|
||||
#include "mozilla/Maybe.h"
|
||||
#include "mozilla/dom/VisualViewport.h"
|
||||
#include "mozilla/layers/TransactionIdAllocator.h"
|
||||
#include "mozilla/VsyncDispatcher.h"
|
||||
|
||||
|
@ -92,6 +93,10 @@ class nsAPostRefreshObserver {
|
|||
class nsRefreshDriver final : public mozilla::layers::TransactionIdAllocator,
|
||||
public nsARefreshObserver {
|
||||
using TransactionId = mozilla::layers::TransactionId;
|
||||
using VVPResizeEvent =
|
||||
mozilla::dom::VisualViewport::VisualViewportResizeEvent;
|
||||
using VVPScrollEvent =
|
||||
mozilla::dom::VisualViewport::VisualViewportScrollEvent;
|
||||
|
||||
public:
|
||||
explicit nsRefreshDriver(nsPresContext* aPresContext);
|
||||
|
@ -146,9 +151,15 @@ class nsRefreshDriver final : public mozilla::layers::TransactionIdAllocator,
|
|||
void AddTimerAdjustmentObserver(nsATimerAdjustmentObserver* aObserver);
|
||||
void RemoveTimerAdjustmentObserver(nsATimerAdjustmentObserver* aObserver);
|
||||
|
||||
void PostVisualViewportResizeEvent(VVPResizeEvent* aResizeEvent);
|
||||
void DispatchVisualViewportResizeEvents();
|
||||
|
||||
void PostScrollEvent(mozilla::Runnable* aScrollEvent, bool aDelayed = false);
|
||||
void DispatchScrollEvents();
|
||||
|
||||
void PostVisualViewportScrollEvent(VVPScrollEvent* aScrollEvent);
|
||||
void DispatchVisualViewportScrollEvents();
|
||||
|
||||
/**
|
||||
* Add an observer that will be called after each refresh. The caller
|
||||
* must remove the observer before it is deleted. This does not trigger
|
||||
|
@ -411,7 +422,9 @@ class nsRefreshDriver final : public mozilla::layers::TransactionIdAllocator,
|
|||
|
||||
private:
|
||||
typedef nsTObserverArray<nsARefreshObserver*> ObserverArray;
|
||||
typedef nsTArray<RefPtr<VVPResizeEvent>> VisualViewportResizeEventArray;
|
||||
typedef nsTArray<RefPtr<mozilla::Runnable>> ScrollEventArray;
|
||||
typedef nsTArray<RefPtr<VVPScrollEvent>> VisualViewportScrollEventArray;
|
||||
typedef nsTHashtable<nsISupportsHashKey> RequestTable;
|
||||
struct ImageStartData {
|
||||
ImageStartData() {}
|
||||
|
@ -533,7 +546,9 @@ class nsRefreshDriver final : public mozilla::layers::TransactionIdAllocator,
|
|||
RequestTable mRequests;
|
||||
ImageStartTable mStartTable;
|
||||
AutoTArray<nsCOMPtr<nsIRunnable>, 16> mEarlyRunners;
|
||||
VisualViewportResizeEventArray mVisualViewportResizeEvents;
|
||||
ScrollEventArray mScrollEvents;
|
||||
VisualViewportScrollEventArray mVisualViewportScrollEvents;
|
||||
|
||||
// Scroll events on documents that might have events suppressed.
|
||||
ScrollEventArray mDelayedScrollEvents;
|
||||
|
|
|
@ -74,6 +74,7 @@
|
|||
#include "mozilla/layers/LayerTransactionChild.h"
|
||||
#include "mozilla/layers/ScrollLinkedEffectDetector.h"
|
||||
#include "mozilla/Unused.h"
|
||||
#include "VisualViewport.h"
|
||||
#include "LayersLogging.h" // for Stringify
|
||||
#include <algorithm>
|
||||
#include <cstdlib> // for std::abs(int/long)
|
||||
|
@ -2736,7 +2737,7 @@ void ScrollFrameHelper::ScrollToImpl(nsPoint aPt, const nsRect& aRange,
|
|||
// offset (e.g. by using nsIDOMWindowUtils.getVisualViewportOffset()
|
||||
// in chrome JS code) before it's updated by the next APZ repaint,
|
||||
// we could get incorrect results.
|
||||
presContext->PresShell()->SetVisualViewportOffset(pt);
|
||||
presContext->PresShell()->SetVisualViewportOffset(pt, curPos);
|
||||
}
|
||||
|
||||
ScrollVisual();
|
||||
|
@ -2856,6 +2857,16 @@ void ScrollFrameHelper::ScrollToImpl(nsPoint aPt, const nsRect& aRange,
|
|||
nsPresContext::InteractionType::eScrollInteraction, TimeStamp::Now());
|
||||
|
||||
PostScrollEvent();
|
||||
// If this is a viewport scroll, this could affect the relative offset
|
||||
// between layout and visual viewport, so we might have to fire a visual
|
||||
// viewport scroll event as well.
|
||||
if (mIsRoot) {
|
||||
if (auto* window = nsGlobalWindowInner::Cast(
|
||||
mOuter->PresContext()->Document()->GetInnerWindow())) {
|
||||
window->VisualViewport()->PostScrollEvent(
|
||||
presContext->PresShell()->GetVisualViewportOffset(), curPos);
|
||||
}
|
||||
}
|
||||
|
||||
// notify the listeners.
|
||||
for (uint32_t i = 0; i < mListeners.Length(); i++) {
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
<!--
|
||||
Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/
|
||||
-->
|
||||
<svg xmlns="http://www.w3.org/2000/svg" version="1.1"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<rect height="100%" width="100%" fill="yellow" />
|
||||
<rect height="50%" width="100%" fill="black" />
|
||||
</svg>
|
После Ширина: | Высота: | Размер: 330 B |
|
@ -0,0 +1,25 @@
|
|||
<!--
|
||||
Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/
|
||||
-->
|
||||
<svg xmlns="http://www.w3.org/2000/svg" version="1.1"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
|
||||
<!-- We include these <use> elements simply to be sure the SVG resource URLs
|
||||
get a chance to block 'onload', if they can be loaded. -->
|
||||
<use xlink:href="http://mochi.test:8888/tests/layout/svg/tests/filters.svg#empty" />
|
||||
<use xlink:href="http://example.org/tests/layout/svg/tests/filters.svg#empty" />
|
||||
|
||||
<!-- giant yellow rect in the background, just so you can visually tell
|
||||
that this SVG file has loaded/rendered. -->
|
||||
<rect height="100%" width="100%" fill="yellow" />
|
||||
|
||||
<!-- For both rects below: if it's black, its filter resolved successfully.
|
||||
If it's transparent, it means we failed to load the resource
|
||||
(e.g. because it was blocked as a cross-origin resource). -->
|
||||
<rect height="50%" width="100%" fill="red"
|
||||
filter="url(http://mochi.test:8888/tests/layout/svg/tests/filters.svg#NonWhiteToBlack)"/>
|
||||
<rect y="50%"
|
||||
height="50%" width="100%" fill="red"
|
||||
filter="url(http://example.org/tests/layout/svg/tests/filters.svg#NonWhiteToBlack)"/>
|
||||
</svg>
|
После Ширина: | Высота: | Размер: 1.2 KiB |
|
@ -0,0 +1,9 @@
|
|||
<!--
|
||||
Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/
|
||||
-->
|
||||
<svg xmlns="http://www.w3.org/2000/svg" version="1.1"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<rect height="100%" width="100%" fill="yellow" />
|
||||
<rect y="50%" height="50%" width="100%" fill="black" />
|
||||
</svg>
|
После Ширина: | Высота: | Размер: 338 B |
|
@ -0,0 +1,28 @@
|
|||
<?xml version="1.0"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" version="1.1">
|
||||
<defs>
|
||||
|
||||
<!-- so that other documents can svg:use this one and force it to
|
||||
load before onload -->
|
||||
<g id="empty" />
|
||||
|
||||
<!-- Keep all white pixels white, and change any others to black. -->
|
||||
<!-- NOTE: alpha is preserved, so it will not adjust alpha edges -->
|
||||
<filter id="NonWhiteToBlack" x="0%" y="0%" width="100%" height="100%">
|
||||
<feComponentTransfer>
|
||||
<feFuncR type="linear" slope="-1" intercept="1" />
|
||||
<feFuncG type="linear" slope="-1" intercept="1" />
|
||||
<feFuncB type="linear" slope="-1" intercept="1" />
|
||||
</feComponentTransfer>
|
||||
<feColorMatrix type="matrix" values="255 255 255 0 0
|
||||
255 255 255 0 0
|
||||
255 255 255 0 0
|
||||
0 0 0 1 0" />
|
||||
<feComponentTransfer>
|
||||
<feFuncR type="linear" slope="-1" intercept="1" />
|
||||
<feFuncG type="linear" slope="-1" intercept="1" />
|
||||
<feFuncB type="linear" slope="-1" intercept="1" />
|
||||
</feComponentTransfer>
|
||||
</filter>
|
||||
</defs>
|
||||
</svg>
|
После Ширина: | Высота: | Размер: 1.1 KiB |
|
@ -3,3 +3,9 @@ support-files =
|
|||
file_disabled_iframe.html
|
||||
|
||||
[test_disabled.html]
|
||||
[test_filter_crossorigin.html]
|
||||
support-files =
|
||||
filters.svg
|
||||
file_filter_crossorigin.svg
|
||||
file_black_yellow.svg
|
||||
file_yellow_black.svg
|
||||
|
|
|
@ -0,0 +1,46 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<!--
|
||||
https://bugzilla.mozilla.org/show_bug.cgi?id=695385
|
||||
-->
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Test for Bug 695385</title>
|
||||
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<script type="application/javascript" src="/tests/SimpleTest/WindowSnapshot.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
|
||||
</head>
|
||||
<body onload="run()">
|
||||
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=695385">Mozilla Bug 695385</a>
|
||||
<br>
|
||||
<!-- These iframes' renderings are expected to match: -->
|
||||
<iframe src="http://mochi.test:8888/tests/layout/svg/tests/file_filter_crossorigin.svg"></iframe>
|
||||
<iframe src="file_black_yellow.svg"></iframe>
|
||||
<br>
|
||||
<!-- These iframes' renderings are expected to match: -->
|
||||
<iframe src="http://example.org/tests/layout/svg/tests/file_filter_crossorigin.svg"></iframe>
|
||||
<iframe src="file_yellow_black.svg"></iframe>
|
||||
|
||||
<pre id="test">
|
||||
<script type="application/javascript">
|
||||
// Main Function
|
||||
function run() {
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
let snapshots = new Array(4);
|
||||
for (let i = 0; i < snapshots.length; i++) {
|
||||
snapshots[i] = snapshotWindow(frames[i].window, false);
|
||||
}
|
||||
|
||||
// Compare mochi.test iframe against its reference:
|
||||
assertSnapshots(snapshots[0], snapshots[1], true, null,
|
||||
"Testcase loaded from mochi.test", "Reference: black/yellow");
|
||||
|
||||
// Compare example.org iframe against its reference:
|
||||
assertSnapshots(snapshots[2], snapshots[3], true, null,
|
||||
"Testcase loaded from example.org", "Reference: yellow/black");
|
||||
SimpleTest.finish();
|
||||
}
|
||||
</script>
|
||||
</pre>
|
||||
</body>
|
||||
</html>
|
|
@ -58,7 +58,7 @@ public class SpeechSynthesisService {
|
|||
: sTTS.getLanguage();
|
||||
for (Locale locale : getAvailableLanguages()) {
|
||||
final Set<String> features = sTTS.getFeatures(locale);
|
||||
boolean isLocal = features.contains(TextToSpeech.Engine.KEY_FEATURE_EMBEDDED_SYNTHESIS);
|
||||
boolean isLocal = features != null && features.contains(TextToSpeech.Engine.KEY_FEATURE_EMBEDDED_SYNTHESIS);
|
||||
String localeStr = locale.toString();
|
||||
registerVoice("moz-tts:android:" + localeStr, locale.getDisplayName(), localeStr.replace("_", "-"), !isLocal, defaultLocale == locale);
|
||||
}
|
||||
|
|
|
@ -1557,6 +1557,12 @@ VARCACHE_PREF(
|
|||
RelaxedAtomicUint32, 1000
|
||||
)
|
||||
|
||||
VARCACHE_PREF(
|
||||
"media.test.video-suspend",
|
||||
MediaTestVideoSuspend,
|
||||
RelaxedAtomicBool, false
|
||||
)
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
// Network prefs
|
||||
//---------------------------------------------------------------------------
|
||||
|
|
|
@ -4,6 +4,7 @@ job-defaults:
|
|||
worker-type:
|
||||
by-platform:
|
||||
linux64.*: aws-provisioner-v1/gecko-t-linux-xlarge
|
||||
macosx64.*: releng-hardware/gecko-t-osx-1010
|
||||
windows10-64.*: aws-provisioner-v1/gecko-t-win10-64
|
||||
worker:
|
||||
by-platform:
|
||||
|
@ -97,6 +98,7 @@ mozbase:
|
|||
description: testing/mozbase unit tests
|
||||
platform:
|
||||
- linux64/opt
|
||||
- macosx64/opt
|
||||
- windows10-64/opt
|
||||
python-version: [2, 3]
|
||||
treeherder:
|
||||
|
@ -126,6 +128,7 @@ mozlint:
|
|||
description: python/mozlint unit tests
|
||||
platform:
|
||||
- linux64/opt
|
||||
- macosx64/opt
|
||||
- windows10-64/opt
|
||||
python-version: [2]
|
||||
treeherder:
|
||||
|
|
|
@ -92,6 +92,7 @@ or added to the task's docker image then added to the PATH.
|
|||
EXIT_PURGE_CACHE = 72
|
||||
|
||||
|
||||
IS_MACOSX = sys.platform == 'darwin'
|
||||
IS_POSIX = os.name == 'posix'
|
||||
IS_WINDOWS = os.name == 'nt'
|
||||
|
||||
|
@ -402,7 +403,9 @@ def vcs_checkout(source_repo, dest, store_path,
|
|||
print('revision is not specified for checkout')
|
||||
sys.exit(1)
|
||||
|
||||
if IS_POSIX:
|
||||
if IS_MACOSX:
|
||||
hg_bin = '/tools/python27-mercurial/bin/hg'
|
||||
elif IS_POSIX:
|
||||
hg_bin = 'hg'
|
||||
elif IS_WINDOWS:
|
||||
# This is where OCC installs it in the AMIs.
|
||||
|
@ -411,6 +414,7 @@ def vcs_checkout(source_repo, dest, store_path,
|
|||
print('could not find Mercurial executable: %s' % hg_bin)
|
||||
sys.exit(1)
|
||||
|
||||
store_path = os.path.abspath(store_path)
|
||||
args = [
|
||||
hg_bin,
|
||||
'robustcheckout',
|
||||
|
|
|
@ -64,12 +64,20 @@ def support_vcs_checkout(config, job, taskdesc, sparse=False):
|
|||
This can only be used with ``run-task`` tasks, as the cache name is
|
||||
reserved for ``run-task`` tasks.
|
||||
"""
|
||||
is_win = job['worker']['os'] == 'windows'
|
||||
worker = job['worker']
|
||||
is_mac = worker['os'] == 'macosx'
|
||||
is_win = worker['os'] == 'windows'
|
||||
is_linux = worker['os'] == 'linux'
|
||||
assert is_mac or is_win or is_linux
|
||||
|
||||
if is_win:
|
||||
checkoutdir = './build'
|
||||
geckodir = '{}/src'.format(checkoutdir)
|
||||
hgstore = 'y:/hg-shared'
|
||||
elif is_mac:
|
||||
checkoutdir = './checkouts'
|
||||
geckodir = '{}/gecko'.format(checkoutdir)
|
||||
hgstore = '{}/hg-shared'.format(checkoutdir)
|
||||
else:
|
||||
checkoutdir = '{workdir}/checkouts'.format(**job['run'])
|
||||
geckodir = '{}/gecko'.format(checkoutdir)
|
||||
|
@ -78,7 +86,7 @@ def support_vcs_checkout(config, job, taskdesc, sparse=False):
|
|||
level = config.params['level']
|
||||
# native-engine and generic-worker do not support caches (yet), so we just
|
||||
# do a full clone every time :(
|
||||
if job['worker']['implementation'] in ('docker-worker', 'docker-engine'):
|
||||
if worker['implementation'] in ('docker-worker', 'docker-engine'):
|
||||
name = 'level-%s-checkouts' % level
|
||||
|
||||
# comm-central checkouts need their own cache, because clobber won't
|
||||
|
|
|
@ -35,10 +35,15 @@ defaults = {
|
|||
@run_job_using('generic-worker', 'python-test', schema=python_test_schema, defaults=defaults)
|
||||
def configure_python_test(config, job, taskdesc):
|
||||
run = job['run']
|
||||
worker = job['worker']
|
||||
|
||||
if worker['os'] == 'macosx' and run['python-version'] == 3:
|
||||
# OSX hosts can't seem to find python 3 on their own
|
||||
run['python-version'] = '/usr/local/bin/python3'
|
||||
|
||||
# defer to the mach implementation
|
||||
run['mach'] = 'python-test --python {python-version} --subsuite {subsuite}'.format(**run)
|
||||
run['using'] = 'mach'
|
||||
del run['python-version']
|
||||
del run['subsuite']
|
||||
configure_taskdesc_for_run(config, job, taskdesc, job['worker']['implementation'])
|
||||
configure_taskdesc_for_run(config, job, taskdesc, worker['implementation'])
|
||||
|
|
|
@ -20,7 +20,7 @@ run_task_schema = Schema({
|
|||
# tend to hide their caches. This cache is never added for level-1 jobs.
|
||||
Required('cache-dotcache'): bool,
|
||||
|
||||
# if true (the default), perform a checkout in {workdir}/checkouts/gecko
|
||||
# if true (the default), perform a checkout of gecko on the worker
|
||||
Required('checkout'): bool,
|
||||
|
||||
# The sparse checkout profile to use. Value is the filename relative to the
|
||||
|
@ -41,12 +41,12 @@ run_task_schema = Schema({
|
|||
})
|
||||
|
||||
|
||||
def common_setup(config, job, taskdesc, command, geckodir):
|
||||
def common_setup(config, job, taskdesc, command):
|
||||
run = job['run']
|
||||
if run['checkout']:
|
||||
support_vcs_checkout(config, job, taskdesc,
|
||||
sparse=bool(run['sparse-profile']))
|
||||
command.append('--vcs-checkout={}'.format(geckodir))
|
||||
command.append('--vcs-checkout={}'.format(taskdesc['worker']['env']['GECKO_PATH']))
|
||||
|
||||
if run['sparse-profile']:
|
||||
command.append('--sparse-profile=build/sparse-profiles/%s' %
|
||||
|
@ -73,8 +73,7 @@ def docker_worker_run_task(config, job, taskdesc):
|
|||
run = job['run']
|
||||
worker = taskdesc['worker'] = job['worker']
|
||||
command = ['/builds/worker/bin/run-task']
|
||||
common_setup(config, job, taskdesc, command,
|
||||
geckodir='{workdir}/checkouts/gecko'.format(**run))
|
||||
common_setup(config, job, taskdesc, command)
|
||||
|
||||
if run.get('cache-dotcache'):
|
||||
worker['caches'].append({
|
||||
|
@ -101,8 +100,7 @@ def native_engine_run_task(config, job, taskdesc):
|
|||
run = job['run']
|
||||
worker = taskdesc['worker'] = job['worker']
|
||||
command = ['./run-task']
|
||||
common_setup(config, job, taskdesc, command,
|
||||
geckodir='{workdir}/checkouts/gecko'.format(**run))
|
||||
common_setup(config, job, taskdesc, command)
|
||||
|
||||
worker['context'] = run_task_url(config)
|
||||
|
||||
|
@ -122,15 +120,16 @@ def generic_worker_run_task(config, job, taskdesc):
|
|||
run = job['run']
|
||||
worker = taskdesc['worker'] = job['worker']
|
||||
is_win = worker['os'] == 'windows'
|
||||
is_mac = worker['os'] == 'macosx'
|
||||
|
||||
if is_win:
|
||||
command = ['C:/mozilla-build/python3/python3.exe', 'run-task']
|
||||
geckodir = './build/src'
|
||||
elif is_mac:
|
||||
command = ['/tools/python36/bin/python3.6', 'run-task']
|
||||
else:
|
||||
command = ['./run-task']
|
||||
geckodir = '{workdir}/checkouts/gecko'.format(**run)
|
||||
|
||||
common_setup(config, job, taskdesc, command, geckodir=geckodir)
|
||||
common_setup(config, job, taskdesc, command)
|
||||
|
||||
worker.setdefault('mounts', [])
|
||||
if run.get('cache-dotcache'):
|
||||
|
|
|
@ -2501,3 +2501,42 @@ var PluginUtils =
|
|||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
class EventCounter {
|
||||
constructor(aTarget, aType, aOptions = {}) {
|
||||
this.target = aTarget;
|
||||
this.type = aType;
|
||||
this.options = aOptions;
|
||||
|
||||
this.eventCount = 0;
|
||||
// Bug 1512817:
|
||||
// SpecialPowers is picky and needs to be passed an explicit reference to
|
||||
// the function to be called. To avoid having to bind "this", we therefore
|
||||
// define the method this way, via a property.
|
||||
this.handleEvent = (aEvent) => {
|
||||
this.eventCount++;
|
||||
};
|
||||
|
||||
if (aOptions.mozSystemGroup) {
|
||||
SpecialPowers.addSystemEventListener(aTarget, aType,
|
||||
this.handleEvent,
|
||||
aOptions.capture);
|
||||
} else {
|
||||
aTarget.addEventListener(aType, this, aOptions);
|
||||
}
|
||||
}
|
||||
|
||||
unregister() {
|
||||
if (this.options.mozSystemGroup) {
|
||||
SpecialPowers.removeSystemEventListener(this.target, this.type,
|
||||
this.handleEvent,
|
||||
this.options.capture);
|
||||
} else {
|
||||
this.target.removeEventListener(this.type, this, this.options);
|
||||
}
|
||||
}
|
||||
|
||||
get count() {
|
||||
return this.eventCount;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
subsuite = mozbase
|
||||
skip-if = python == 3
|
||||
[test_binary.py]
|
||||
skip-if = os == "mac"
|
||||
[test_install.py]
|
||||
skip-if = os == "mac" # intermittent
|
||||
[test_is_installer.py]
|
||||
[test_uninstall.py]
|
||||
|
|
|
@ -409,7 +409,7 @@ class TestMetadata(object):
|
|||
|
||||
candidate_paths = set()
|
||||
|
||||
if any(self.is_wpt_path(path) for path in paths):
|
||||
if flavor in (None, 'web-platform-tests') and any(self.is_wpt_path(p) for p in paths):
|
||||
self.add_wpt_manifest_data()
|
||||
|
||||
for path in sorted(paths):
|
||||
|
|
|
@ -3018,11 +3018,9 @@ WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|||
|
||||
<p>This license applies to parts of the code in:</p>
|
||||
<ul>
|
||||
#ifndef RELEASE_OR_BETA
|
||||
<li><code>browser/extensions/formautofill/content/heuristicsRegexp.js</code></li>
|
||||
<li><code>browser/extensions/formautofill/FormAutofillHeuristics.jsm</code></li>
|
||||
<li><code>browser/extensions/formautofill/FormAutofillNameUtils.jsm</code></li>
|
||||
#endif
|
||||
<li><code>editor/libeditor/EditorEventListener.cpp</code></li>
|
||||
<li><code>mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/StrictModeContext.java</code></li>
|
||||
<li><code>security/sandbox/</code></li>
|
||||
|
|
|
@ -103,6 +103,10 @@ uses-unsafe-cpows = true
|
|||
[browser_isSynthetic.js]
|
||||
[browser_keyevents_during_autoscrolling.js]
|
||||
[browser_label_textlink.js]
|
||||
[browser_suspend_videos_outside_viewport.js]
|
||||
support-files =
|
||||
file_outside_viewport_videos.html
|
||||
gizmo.mp4
|
||||
[browser_mediaPlayback.js]
|
||||
tags = audiochannel
|
||||
[browser_mediaPlayback_mute.js]
|
||||
|
|
|
@ -0,0 +1,33 @@
|
|||
/**
|
||||
* This test is used to ensure we suspend video decoding if video is not in the
|
||||
* viewport.
|
||||
*/
|
||||
"use strict";
|
||||
|
||||
const PAGE = "https://example.com/browser/toolkit/content/tests/browser/file_outside_viewport_videos.html";
|
||||
|
||||
async function test_suspend_video_decoding() {
|
||||
let videos = content.document.getElementsByTagName("video");
|
||||
for (let video of videos) {
|
||||
info(`- start video on the ${video.id} side and outside the viewport -`);
|
||||
await video.play();
|
||||
ok(true, `video started playing`);
|
||||
ok(video.isVideoDecodingSuspended, `video decoding is suspended`);
|
||||
}
|
||||
}
|
||||
|
||||
add_task(async function setup_test_preference() {
|
||||
await SpecialPowers.pushPrefEnv({"set": [
|
||||
["media.suspend-bkgnd-video.enabled", true],
|
||||
["media.suspend-bkgnd-video.delay-ms", 0],
|
||||
]});
|
||||
});
|
||||
|
||||
add_task(async function start_test() {
|
||||
await BrowserTestUtils.withNewTab({
|
||||
gBrowser,
|
||||
url: PAGE,
|
||||
}, async browser => {
|
||||
await ContentTask.spawn(browser, null, test_suspend_video_decoding);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,41 @@
|
|||
<html>
|
||||
<head>
|
||||
<title>outside viewport videos</title>
|
||||
<style>
|
||||
/**
|
||||
* These CSS would move elements to the far left/right/top/bottom where user
|
||||
* can not see elements in the viewport if user doesn't scroll the page.
|
||||
*/
|
||||
.outside-left {
|
||||
position: absolute;
|
||||
left: -1000%;
|
||||
}
|
||||
.outside-right {
|
||||
position: absolute;
|
||||
right: -1000%;
|
||||
}
|
||||
.outside-top {
|
||||
position: absolute;
|
||||
top: -1000%;
|
||||
}
|
||||
.outside-bottom {
|
||||
position: absolute;
|
||||
bottom: -1000%;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="outside-left">
|
||||
<video id="left" src="gizmo.mp4">
|
||||
</div>
|
||||
<div class="outside-right">
|
||||
<video id="right" src="gizmo.mp4">
|
||||
</div>
|
||||
<div class="outside-top">
|
||||
<video id="top" src="gizmo.mp4">
|
||||
</div>
|
||||
<div class="outside-bottom">
|
||||
<video id="bottom" src="gizmo.mp4">
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
|
@ -54,10 +54,10 @@
|
|||
<xul:hbox class="popup-notification-footer-container">
|
||||
<children includes="popupnotificationfooter"/>
|
||||
</xul:hbox>
|
||||
<xul:hbox class="popup-notification-button-container">
|
||||
<xul:hbox class="popup-notification-button-container panel-footer">
|
||||
<children includes="button"/>
|
||||
<xul:button anonid="secondarybutton"
|
||||
class="popup-notification-button"
|
||||
class="popup-notification-button popup-notification-secondary-button"
|
||||
xbl:inherits="oncommand=secondarybuttoncommand,label=secondarybuttonlabel,accesskey=secondarybuttonaccesskey,hidden=secondarybuttonhidden"/>
|
||||
<xul:toolbarseparator xbl:inherits="hidden=dropmarkerhidden"/>
|
||||
<xul:button anonid="menubutton"
|
||||
|
@ -73,11 +73,10 @@
|
|||
</xul:menupopup>
|
||||
</xul:button>
|
||||
<xul:button anonid="button"
|
||||
class="popup-notification-button"
|
||||
default="true"
|
||||
class="popup-notification-button popup-notification-primary-button"
|
||||
label="&defaultButton.label;"
|
||||
accesskey="&defaultButton.accesskey;"
|
||||
xbl:inherits="oncommand=buttoncommand,label=buttonlabel,accesskey=buttonaccesskey,highlight=buttonhighlight,disabled=mainactiondisabled"/>
|
||||
xbl:inherits="oncommand=buttoncommand,label=buttonlabel,accesskey=buttonaccesskey,default=buttonhighlight,disabled=mainactiondisabled"/>
|
||||
</xul:hbox>
|
||||
</content>
|
||||
<implementation>
|
||||
|
|
|
@ -838,7 +838,7 @@ PopupNotifications.prototype = {
|
|||
if (n.mainAction) {
|
||||
popupnotification.setAttribute("buttonlabel", n.mainAction.label);
|
||||
popupnotification.setAttribute("buttonaccesskey", n.mainAction.accessKey);
|
||||
popupnotification.setAttribute("buttonhighlight", !n.mainAction.disableHighlight);
|
||||
popupnotification.toggleAttribute("buttonhighlight", !n.mainAction.disableHighlight);
|
||||
popupnotification.setAttribute("buttoncommand", "PopupNotifications._onButtonEvent(event, 'buttoncommand');");
|
||||
popupnotification.setAttribute("dropmarkerpopupshown", "PopupNotifications._onButtonEvent(event, 'dropmarkerpopupshown');");
|
||||
popupnotification.setAttribute("learnmoreclick", "PopupNotifications._onButtonEvent(event, 'learnmoreclick');");
|
||||
|
@ -846,7 +846,7 @@ PopupNotifications.prototype = {
|
|||
} else {
|
||||
// Enable the default button to let the user close the popup if the close button is hidden
|
||||
popupnotification.setAttribute("buttoncommand", "PopupNotifications._onButtonEvent(event, 'buttoncommand');");
|
||||
popupnotification.setAttribute("buttonhighlight", "true");
|
||||
popupnotification.toggleAttribute("buttonhighlight", true);
|
||||
popupnotification.removeAttribute("buttonlabel");
|
||||
popupnotification.removeAttribute("buttonaccesskey");
|
||||
popupnotification.removeAttribute("dropmarkerpopupshown");
|
||||
|
|
|
@ -12,6 +12,8 @@
|
|||
|
||||
@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");
|
||||
|
||||
%include ../../shared/global.inc.css
|
||||
|
||||
/* ::::: XBL bindings ::::: */
|
||||
|
||||
menulist > menupopup {
|
||||
|
@ -48,14 +50,6 @@ wizard {
|
|||
font: message-box;
|
||||
}
|
||||
|
||||
/* deprecated */
|
||||
window.dialog {
|
||||
padding-top: 8px;
|
||||
padding-bottom: 10px;
|
||||
padding-inline-start: 8px;
|
||||
padding-inline-end: 10px;
|
||||
}
|
||||
|
||||
/* ::::: alert icons :::::*/
|
||||
|
||||
.message-icon {
|
||||
|
@ -288,6 +282,3 @@ popupnotificationcontent {
|
|||
background-image: url("chrome://global/skin/icons/autoscroll-horizontal.svg");
|
||||
}
|
||||
|
||||
/* :::::: Close button icons ::::: */
|
||||
|
||||
%include ../../shared/close-icon.inc.css
|
||||
|
|
|
@ -8,10 +8,10 @@
|
|||
%include ../../shared/popupnotification.inc.css
|
||||
|
||||
.popup-notification-button:-moz-focusring {
|
||||
outline: 1px -moz-dialogtext dotted;
|
||||
outline: 1px dotted;
|
||||
outline-offset: -5px;
|
||||
}
|
||||
|
||||
.popup-notification-button[anonid="secondarybutton"]:not([hidden="true"]) ~ .popup-notification-button[default]:not([highlight="true"]) {
|
||||
.popup-notification-secondary-button:not([hidden="true"]) ~ .popup-notification-primary-button:not([default]) {
|
||||
border-inline-start: 1px solid var(--panel-separator-color);
|
||||
}
|
||||
|
|
|
@ -9,6 +9,8 @@
|
|||
%include shared.inc
|
||||
@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");
|
||||
|
||||
%include ../../shared/global.inc.css
|
||||
|
||||
/* ::::: XBL bindings ::::: */
|
||||
|
||||
menulist > menupopup {
|
||||
|
@ -42,14 +44,6 @@ wizard {
|
|||
font: message-box;
|
||||
}
|
||||
|
||||
/* deprecated */
|
||||
window.dialog {
|
||||
padding-top: 8px;
|
||||
padding-bottom: 10px;
|
||||
padding-inline-start: 8px;
|
||||
padding-inline-end: 10px;
|
||||
}
|
||||
|
||||
/* ::::: alert icons :::::*/
|
||||
|
||||
.message-icon,
|
||||
|
@ -320,6 +314,3 @@ popupnotificationcontent {
|
|||
visibility: collapse;
|
||||
}
|
||||
|
||||
/* :::::: Close button icons ::::: */
|
||||
|
||||
%include ../../shared/close-icon.inc.css
|
||||
|
|
|
@ -17,6 +17,6 @@
|
|||
color: #aa1b08;
|
||||
}
|
||||
|
||||
.popup-notification-button[anonid="secondarybutton"]:not([hidden="true"]) ~ .popup-notification-button[default]:not([highlight="true"]) {
|
||||
.popup-notification-secondary-button:not([hidden="true"]) ~ .popup-notification-primary-button:not([default]) {
|
||||
border-inline-start: 1px solid var(--panel-separator-color);
|
||||
}
|
||||
|
|
|
@ -1,27 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
.close-icon {
|
||||
-moz-appearance: none;
|
||||
-moz-context-properties: fill, fill-opacity;
|
||||
list-style-image: url(chrome://global/skin/icons/close.svg);
|
||||
color: inherit !important;
|
||||
fill: currentColor;
|
||||
fill-opacity: 0;
|
||||
}
|
||||
|
||||
.close-icon:hover {
|
||||
fill-opacity: 0.1;
|
||||
}
|
||||
|
||||
.close-icon:hover:active {
|
||||
fill-opacity: 0.2;
|
||||
}
|
||||
|
||||
.close-icon > .button-icon,
|
||||
.close-icon > .button-box > .button-icon,
|
||||
.close-icon > .toolbarbutton-icon {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
|
@ -0,0 +1,67 @@
|
|||
/* 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/. */
|
||||
|
||||
/* Close icon */
|
||||
|
||||
.close-icon {
|
||||
-moz-appearance: none;
|
||||
-moz-context-properties: fill, fill-opacity;
|
||||
list-style-image: url(chrome://global/skin/icons/close.svg);
|
||||
color: inherit !important;
|
||||
fill: currentColor;
|
||||
fill-opacity: 0;
|
||||
}
|
||||
|
||||
.close-icon:hover {
|
||||
fill-opacity: 0.1;
|
||||
}
|
||||
|
||||
.close-icon:hover:active {
|
||||
fill-opacity: 0.2;
|
||||
}
|
||||
|
||||
.close-icon > .button-icon,
|
||||
.close-icon > .button-box > .button-icon,
|
||||
.close-icon > .toolbarbutton-icon {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
/* Panel footers */
|
||||
|
||||
.panel-footer {
|
||||
background-color: var(--arrowpanel-dimmed);
|
||||
}
|
||||
|
||||
.panel-footer > button {
|
||||
-moz-appearance: none;
|
||||
/* !important overrides :hover and :active colors from button.css: */
|
||||
color: inherit !important;
|
||||
}
|
||||
|
||||
.panel-footer > button[disabled] {
|
||||
color: var(--panel-disabled-color) !important;
|
||||
}
|
||||
|
||||
.panel-footer > button:not([disabled]):hover {
|
||||
background-color: var(--arrowpanel-dimmed);
|
||||
}
|
||||
|
||||
.panel-footer > button:not([disabled]):hover:active {
|
||||
background-color: var(--arrowpanel-dimmed-further);
|
||||
}
|
||||
|
||||
.panel-footer > button:not([disabled])[default] {
|
||||
color: white !important;
|
||||
background-color: #0996f8;
|
||||
}
|
||||
|
||||
.panel-footer > button:not([disabled])[default]:hover {
|
||||
background-color: #0675d3;
|
||||
}
|
||||
|
||||
.panel-footer > button:not([disabled])[default]:hover:active {
|
||||
background-color: #0568ba;
|
||||
}
|
||||
|
|
@ -41,7 +41,6 @@
|
|||
}
|
||||
|
||||
.popup-notification-button-container {
|
||||
background-color: var(--arrowpanel-dimmed);
|
||||
display: flex;
|
||||
}
|
||||
|
||||
|
@ -59,48 +58,21 @@
|
|||
|
||||
.popup-notification-button {
|
||||
flex: 1;
|
||||
-moz-appearance: none;
|
||||
color: inherit;
|
||||
margin: 0;
|
||||
min-width: 0;
|
||||
min-height: 41px;
|
||||
border-top: 1px solid var(--panel-separator-color);
|
||||
}
|
||||
|
||||
.popup-notification-button:hover:not([disabled]) {
|
||||
background-color: var(--arrowpanel-dimmed);
|
||||
color: inherit; /* override button.css on Linux */
|
||||
}
|
||||
|
||||
.popup-notification-button:hover:active:not([disabled]) {
|
||||
background-color: var(--arrowpanel-dimmed-further);
|
||||
color: inherit; /* override button.css on Mac */
|
||||
box-shadow: 0 1px 0 hsla(210,4%,10%,.05) inset;
|
||||
}
|
||||
|
||||
.popup-notification-button[disabled] {
|
||||
background-color: var(--arrowpanel-dimmed-further);
|
||||
color: var(--panel-disabled-color);
|
||||
}
|
||||
|
||||
.popup-notification-button[default]:not([alone]) {
|
||||
.popup-notification-primary-button:not([alone]) {
|
||||
flex: 0 50%;
|
||||
}
|
||||
|
||||
.popup-notification-button[default][highlight="true"]:not([disabled]) {
|
||||
background-color: #0996f8;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.popup-notification-button[default][highlight="true"]:hover:not([disabled]) {
|
||||
background-color: #0675d3;
|
||||
}
|
||||
|
||||
.popup-notification-button[default][highlight="true"]:hover:active:not([disabled]) {
|
||||
background-color: #0568ba;
|
||||
}
|
||||
|
||||
.popup-notification-button[anonid="secondarybutton"][hidden="true"] ~ .popup-notification-button[default] {
|
||||
.popup-notification-secondary-button[hidden="true"] ~ .popup-notification-primary-button {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
|
|
|
@ -12,6 +12,8 @@
|
|||
|
||||
@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");
|
||||
|
||||
%include ../../shared/global.inc.css
|
||||
|
||||
/* ::::: XBL bindings ::::: */
|
||||
|
||||
menulist > menupopup {
|
||||
|
@ -47,14 +49,6 @@ wizard {
|
|||
font: message-box;
|
||||
}
|
||||
|
||||
/* deprecated */
|
||||
window.dialog {
|
||||
padding-top: 8px;
|
||||
padding-bottom: 10px;
|
||||
padding-inline-start: 8px;
|
||||
padding-inline-end: 10px;
|
||||
}
|
||||
|
||||
/* ::::: alert icons :::::*/
|
||||
|
||||
.message-icon,
|
||||
|
@ -293,6 +287,3 @@ popupnotificationcontent {
|
|||
background-image: url("chrome://global/skin/icons/autoscroll-horizontal.svg");
|
||||
}
|
||||
|
||||
/* :::::: Close button icons ::::: */
|
||||
|
||||
%include ../../shared/close-icon.inc.css
|
||||
|
|
|
@ -19,12 +19,12 @@
|
|||
}
|
||||
}
|
||||
|
||||
/* Swap the default and secondary action, because Windows
|
||||
* platform conventions put the default action on the left. */
|
||||
.popup-notification-button[default] {
|
||||
/* Swap the primary and secondary action, because Windows
|
||||
* platform conventions put the primary action on the left. */
|
||||
.popup-notification-primary-button {
|
||||
order: -1;
|
||||
}
|
||||
|
||||
.popup-notification-button[anonid="secondarybutton"]:not([hidden="true"]) ~ .popup-notification-button[default]:not([highlight="true"]) {
|
||||
.popup-notification-secondary-button:not([hidden="true"]) ~ .popup-notification-primary-button:not([default]) {
|
||||
border-inline-end: 1px solid var(--panel-separator-color);
|
||||
}
|
||||
|
|
|
@ -3,5 +3,6 @@ subsuite = mozlint
|
|||
skip-if = python == 3
|
||||
|
||||
[test_eslint.py]
|
||||
skip-if = os == "win" # node not installed on worker
|
||||
skip-if = os == "win" || os == "mac" # node not installed on worker
|
||||
[test_flake8.py]
|
||||
skip-if = os == "mac" # pip unable to find 'flake8==3.5.0'
|
||||
|
|
|
@ -131,6 +131,9 @@ struct BaseEventFlags {
|
|||
// listener is added to chrome node, so, don't set this to true for the
|
||||
// events which are fired a lot of times like eMouseMove.
|
||||
bool mOnlySystemGroupDispatchInContent : 1;
|
||||
// If mOnlySystemGroupDispatch is true, the event will be dispatched only to
|
||||
// event listeners added in the system group.
|
||||
bool mOnlySystemGroupDispatch : 1;
|
||||
// The event's action will be handled by APZ. The main thread should not
|
||||
// perform its associated action.
|
||||
bool mHandledByAPZ : 1;
|
||||
|
@ -454,7 +457,8 @@ class WidgetEvent : public WidgetEventTime {
|
|||
mFlags.mBubbles = true;
|
||||
break;
|
||||
default:
|
||||
if (mMessage == eResize || mMessage == eEditorInput) {
|
||||
if (mMessage == eResize || mMessage == eMozVisualResize ||
|
||||
mMessage == eMozVisualScroll || mMessage == eEditorInput) {
|
||||
mFlags.mCancelable = false;
|
||||
} else {
|
||||
mFlags.mCancelable = true;
|
||||
|
|
|
@ -55,6 +55,8 @@ NS_EVENT_MESSAGE(eAccessKeyNotFound)
|
|||
|
||||
NS_EVENT_MESSAGE(eResize)
|
||||
NS_EVENT_MESSAGE(eScroll)
|
||||
NS_EVENT_MESSAGE(eMozVisualResize)
|
||||
NS_EVENT_MESSAGE(eMozVisualScroll)
|
||||
|
||||
// Application installation
|
||||
NS_EVENT_MESSAGE(eInstall)
|
||||
|
|
|
@ -1881,6 +1881,10 @@ STATIC_ATOMS = [
|
|||
# MediaDevices device change event
|
||||
Atom("ondevicechange", "ondevicechange"),
|
||||
|
||||
# Internal Visual Viewport events
|
||||
Atom("onmozvisualresize", "onmozvisualresize"),
|
||||
Atom("onmozvisualscroll", "onmozvisualscroll"),
|
||||
|
||||
# WebExtensions
|
||||
Atom("moz_extension", "moz-extension"),
|
||||
Atom("all_urlsPermission", "<all_urls>"),
|
||||
|
|
Загрузка…
Ссылка в новой задаче