зеркало из https://github.com/mozilla/gecko-dev.git
merge autoland to mozilla-central a=merge
This commit is contained in:
Коммит
025e8b8084
|
@ -47,18 +47,88 @@ add_task(async function() {
|
|||
focusedId = await performAccessKeyForChrome("z");
|
||||
is(focusedId, "chromebutton", "chromebutton accesskey");
|
||||
|
||||
newButton.remove();
|
||||
|
||||
gBrowser.removeTab(tab1);
|
||||
gBrowser.removeTab(tab2);
|
||||
|
||||
// Test whether access key for the newButton isn't available when content
|
||||
// consumes the key event.
|
||||
|
||||
// When content in the tab3 consumes all keydown events.
|
||||
const gPageURL3 = "data:text/html,<body id='tab3body'>" +
|
||||
"<button id='tab3button' accesskey='y'>Button in Tab 3</button>" +
|
||||
"<script>" +
|
||||
"document.body.addEventListener('keydown', (event)=>{ event.preventDefault(); });" +
|
||||
"</script></body>";
|
||||
let tab3 = await BrowserTestUtils.openNewForegroundTab(gBrowser, gPageURL3);
|
||||
tab3.linkedBrowser.messageManager.loadFrameScript("data:,(" + childHandleFocus.toString() + ")();", false);
|
||||
|
||||
Services.focus.clearFocus(window);
|
||||
|
||||
focusedId = await performAccessKey("y");
|
||||
is(focusedId, "tab3button", "button accesskey in tab3 should be focused");
|
||||
|
||||
newButton.onfocus = () => {
|
||||
ok(false, "chromebutton shouldn't get focus during testing with tab3");
|
||||
}
|
||||
|
||||
// Press the accesskey for the chrome element while the content document is focused.
|
||||
focusedId = await performAccessKey("z");
|
||||
is(focusedId, "tab3body", "button accesskey in tab3 should keep having focus");
|
||||
|
||||
newButton.onfocus = null;
|
||||
|
||||
gBrowser.removeTab(tab3);
|
||||
|
||||
// When content in the tab4 consumes all keypress events.
|
||||
const gPageURL4 = "data:text/html,<body id='tab4body'>" +
|
||||
"<button id='tab4button' accesskey='y'>Button in Tab 4</button>" +
|
||||
"<script>" +
|
||||
"document.body.addEventListener('keypress', (event)=>{ event.preventDefault(); });" +
|
||||
"</script></body>";
|
||||
let tab4 = await BrowserTestUtils.openNewForegroundTab(gBrowser, gPageURL4);
|
||||
tab4.linkedBrowser.messageManager.loadFrameScript("data:,(" + childHandleFocus.toString() + ")();", false);
|
||||
|
||||
Services.focus.clearFocus(window);
|
||||
|
||||
focusedId = await performAccessKey("y");
|
||||
is(focusedId, "tab4button", "button accesskey in tab4 should be focused");
|
||||
|
||||
newButton.onfocus = () => {
|
||||
// EventStateManager handles accesskey before dispatching keypress event
|
||||
// into the DOM tree, therefore, chrome accesskey always wins focus from
|
||||
// content. However, this is different from shortcut keys.
|
||||
todo(false, "chromebutton shouldn't get focus during testing with tab4");
|
||||
}
|
||||
|
||||
// Press the accesskey for the chrome element while the content document is focused.
|
||||
focusedId = await performAccessKey("z");
|
||||
is(focusedId, "tab4body", "button accesskey in tab4 should keep having focus");
|
||||
|
||||
newButton.onfocus = null;
|
||||
|
||||
gBrowser.removeTab(tab4);
|
||||
|
||||
newButton.remove();
|
||||
});
|
||||
|
||||
function childHandleFocus() {
|
||||
var sent = false;
|
||||
content.document.body.firstChild.addEventListener("focus", function focused(event) {
|
||||
sent = true;
|
||||
let focusedElement = content.document.activeElement;
|
||||
focusedElement.blur();
|
||||
sendAsyncMessage("Test:FocusFromAccessKey", { focus: focusedElement.id })
|
||||
}, true);
|
||||
content.document.body.addEventListener("keydown", function keydown(event) {
|
||||
sent = false;
|
||||
}, true);
|
||||
content.document.body.addEventListener("keyup", function keyup(event) {
|
||||
if (!sent) {
|
||||
sent = true;
|
||||
let focusedElement = content.document.activeElement;
|
||||
sendAsyncMessage("Test:FocusFromAccessKey", { focus: focusedElement.id });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function performAccessKey(key) {
|
||||
|
|
|
@ -303,51 +303,29 @@ global.actionContextMenu = function(contextData) {
|
|||
gMenuBuilder.buildActionContextMenu(contextData);
|
||||
};
|
||||
|
||||
const contextsMap = {
|
||||
onAudio: "audio",
|
||||
onEditableArea: "editable",
|
||||
inFrame: "frame",
|
||||
onImage: "image",
|
||||
onLink: "link",
|
||||
onPassword: "password",
|
||||
isTextSelected: "selection",
|
||||
onVideo: "video",
|
||||
|
||||
onBrowserAction: "browser_action",
|
||||
onPageAction: "page_action",
|
||||
onTab: "tab",
|
||||
inToolsMenu: "tools_menu",
|
||||
};
|
||||
|
||||
const getMenuContexts = contextData => {
|
||||
let contexts = new Set();
|
||||
|
||||
if (contextData.inFrame) {
|
||||
contexts.add("frame");
|
||||
}
|
||||
|
||||
if (contextData.isTextSelected) {
|
||||
contexts.add("selection");
|
||||
}
|
||||
|
||||
if (contextData.onLink) {
|
||||
contexts.add("link");
|
||||
}
|
||||
|
||||
if (contextData.onEditableArea) {
|
||||
contexts.add("editable");
|
||||
}
|
||||
|
||||
if (contextData.onPassword) {
|
||||
contexts.add("password");
|
||||
}
|
||||
|
||||
if (contextData.onImage) {
|
||||
contexts.add("image");
|
||||
}
|
||||
|
||||
if (contextData.onVideo) {
|
||||
contexts.add("video");
|
||||
}
|
||||
|
||||
if (contextData.onAudio) {
|
||||
contexts.add("audio");
|
||||
}
|
||||
|
||||
if (contextData.onPageAction) {
|
||||
contexts.add("page_action");
|
||||
}
|
||||
|
||||
if (contextData.onBrowserAction) {
|
||||
contexts.add("browser_action");
|
||||
}
|
||||
|
||||
if (contextData.onTab) {
|
||||
contexts.add("tab");
|
||||
for (const [key, value] of Object.entries(contextsMap)) {
|
||||
if (contextData[key]) {
|
||||
contexts.add(value);
|
||||
}
|
||||
}
|
||||
|
||||
if (contexts.size === 0) {
|
||||
|
@ -355,7 +333,7 @@ const getMenuContexts = contextData => {
|
|||
}
|
||||
|
||||
// New non-content contexts supported in Firefox are not part of "all".
|
||||
if (!contextData.onTab) {
|
||||
if (!contextData.onTab && !contextData.inToolsMenu) {
|
||||
contexts.add("all");
|
||||
}
|
||||
|
||||
|
@ -582,8 +560,10 @@ MenuItem.prototype = {
|
|||
};
|
||||
|
||||
// While any extensions are active, this Tracker registers to observe/listen
|
||||
// for contex-menu events from both content and chrome.
|
||||
// for menu events from both Tools and context menus, both content and chrome.
|
||||
const menuTracker = {
|
||||
menuIds: ["menu_ToolsPopup", "tabContextMenu"],
|
||||
|
||||
register() {
|
||||
Services.obs.addObserver(this, "on-build-contextmenu");
|
||||
for (const window of windowTracker.browserWindows()) {
|
||||
|
@ -595,8 +575,10 @@ const menuTracker = {
|
|||
unregister() {
|
||||
Services.obs.removeObserver(this, "on-build-contextmenu");
|
||||
for (const window of windowTracker.browserWindows()) {
|
||||
const menu = window.document.getElementById("tabContextMenu");
|
||||
menu.removeEventListener("popupshowing", this);
|
||||
for (const id of this.menuIds) {
|
||||
const menu = window.document.getElementById(id);
|
||||
menu.removeEventListener("popupshowing", this);
|
||||
}
|
||||
}
|
||||
windowTracker.removeOpenListener(this.onWindowOpen);
|
||||
},
|
||||
|
@ -607,12 +589,19 @@ const menuTracker = {
|
|||
},
|
||||
|
||||
onWindowOpen(window) {
|
||||
const menu = window.document.getElementById("tabContextMenu");
|
||||
menu.addEventListener("popupshowing", menuTracker);
|
||||
for (const id of this.menuIds) {
|
||||
const menu = window.document.getElementById(id);
|
||||
menu.addEventListener("popupshowing", menuTracker);
|
||||
}
|
||||
},
|
||||
|
||||
handleEvent(event) {
|
||||
const menu = event.target;
|
||||
if (menu.id === "menu_ToolsPopup") {
|
||||
const tab = tabTracker.activeTab;
|
||||
const pageUrl = tab.linkedBrowser.currentURI.spec;
|
||||
gMenuBuilder.build({menu, tab, pageUrl, inToolsMenu: true});
|
||||
}
|
||||
if (menu.id === "tabContextMenu") {
|
||||
const trigger = menu.triggerNode;
|
||||
const tab = trigger.localName === "tab" ? trigger : tabTracker.activeTab;
|
||||
|
|
|
@ -22,7 +22,15 @@
|
|||
"namespace": "contextMenus",
|
||||
"permissions": ["contextMenus"],
|
||||
"description": "Use the browser.contextMenus API to add items to the browser's context menu. You can choose what types of objects your context menu additions apply to, such as images, hyperlinks, and pages.",
|
||||
"$import": "menus"
|
||||
"$import": "menus",
|
||||
"types": [
|
||||
{
|
||||
"id": "ContextType",
|
||||
"type": "string",
|
||||
"enum": ["all", "page", "frame", "selection", "link", "editable", "password", "image", "video", "audio", "launcher", "browser_action", "page_action", "tab"],
|
||||
"description": "The different contexts a menu can appear in. Specifying 'all' is equivalent to the combination of all other contexts except for 'tab' and 'tools_menu'."
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"namespace": "menus",
|
||||
|
@ -38,8 +46,8 @@
|
|||
{
|
||||
"id": "ContextType",
|
||||
"type": "string",
|
||||
"enum": ["all", "page", "frame", "selection", "link", "editable", "password", "image", "video", "audio", "launcher", "browser_action", "page_action", "tab"],
|
||||
"description": "The different contexts a menu can appear in. Specifying 'all' is equivalent to the combination of all other contexts except for 'tab'."
|
||||
"enum": ["all", "page", "frame", "selection", "link", "editable", "password", "image", "video", "audio", "launcher", "browser_action", "page_action", "tab", "tools_menu"],
|
||||
"description": "The different contexts a menu can appear in. Specifying 'all' is equivalent to the combination of all other contexts except for 'tab' and 'tools_menu'."
|
||||
},
|
||||
{
|
||||
"id": "ItemType",
|
||||
|
|
|
@ -255,3 +255,59 @@ add_task(async function test_multiple_contexts_init() {
|
|||
await BrowserTestUtils.removeTab(tab);
|
||||
await extension.unload();
|
||||
});
|
||||
|
||||
add_task(async function test_tools_menu() {
|
||||
const first = ExtensionTestUtils.loadExtension({
|
||||
manifest: {
|
||||
permissions: ["menus"],
|
||||
},
|
||||
async background() {
|
||||
await browser.menus.create({title: "alpha", contexts: ["tools_menu"]});
|
||||
await browser.menus.create({title: "beta", contexts: ["tools_menu"]});
|
||||
browser.test.sendMessage("ready");
|
||||
},
|
||||
});
|
||||
|
||||
const second = ExtensionTestUtils.loadExtension({
|
||||
manifest: {
|
||||
permissions: ["menus"],
|
||||
},
|
||||
async background() {
|
||||
await browser.menus.create({title: "gamma", contexts: ["tools_menu"]});
|
||||
browser.menus.onClicked.addListener((info, tab) => {
|
||||
browser.test.sendMessage("click", {info, tab});
|
||||
});
|
||||
|
||||
const [tab] = await browser.tabs.query({active: true});
|
||||
browser.test.sendMessage("ready", tab.id);
|
||||
},
|
||||
});
|
||||
|
||||
const tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, "http://example.com/");
|
||||
await first.startup();
|
||||
await second.startup();
|
||||
|
||||
await first.awaitMessage("ready");
|
||||
const tabId = await second.awaitMessage("ready");
|
||||
const menu = await openToolsMenu();
|
||||
|
||||
const [separator, submenu, gamma] = Array.from(menu.children).slice(-3);
|
||||
is(separator.tagName, "menuseparator", "Separator before first extension item");
|
||||
|
||||
is(submenu.tagName, "menu", "Correct submenu type");
|
||||
is(submenu.getAttribute("label"), "Generated extension", "Correct submenu title");
|
||||
is(submenu.firstChild.children.length, 2, "Correct number of submenu items");
|
||||
|
||||
is(gamma.tagName, "menuitem", "Third menu item type is correct");
|
||||
is(gamma.getAttribute("label"), "gamma", "Third menu item label is correct");
|
||||
|
||||
closeToolsMenu(gamma);
|
||||
|
||||
const click = await second.awaitMessage("click");
|
||||
is(click.info.pageUrl, "http://example.com/", "Click info pageUrl is correct");
|
||||
is(click.tab.id, tabId, "Click event tab ID is correct");
|
||||
|
||||
await BrowserTestUtils.removeTab(tab);
|
||||
await first.unload();
|
||||
await second.unload();
|
||||
});
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
* openExtensionContextMenu closeExtensionContextMenu
|
||||
* openActionContextMenu openSubmenu closeActionContextMenu
|
||||
* openTabContextMenu closeTabContextMenu
|
||||
* openToolsMenu closeToolsMenu
|
||||
* imageBuffer imageBufferFromDataURI
|
||||
* getListStyleImage getPanelForNode
|
||||
* awaitExtensionPanel awaitPopupResize
|
||||
|
@ -336,6 +337,35 @@ async function closeExtensionContextMenu(itemToSelect, modifiers = {}) {
|
|||
contentAreaContextMenu.hidePopup();
|
||||
}
|
||||
|
||||
async function openToolsMenu(win = window) {
|
||||
const node = win.document.getElementById("tools-menu");
|
||||
const menu = win.document.getElementById("menu_ToolsPopup");
|
||||
const shown = BrowserTestUtils.waitForEvent(menu, "popupshown");
|
||||
if (AppConstants.platform === "macosx") {
|
||||
// We can't open menubar items on OSX, so mocking instead.
|
||||
menu.dispatchEvent(new MouseEvent("popupshowing"));
|
||||
menu.dispatchEvent(new MouseEvent("popupshown"));
|
||||
} else {
|
||||
node.open = true;
|
||||
}
|
||||
await shown;
|
||||
return menu;
|
||||
}
|
||||
|
||||
function closeToolsMenu(itemToSelect, win = window) {
|
||||
const menu = win.document.getElementById("menu_ToolsPopup");
|
||||
const hidden = BrowserTestUtils.waitForEvent(menu, "popuphidden");
|
||||
if (AppConstants.platform === "macosx") {
|
||||
// Mocking on OSX, see above.
|
||||
itemToSelect.doCommand();
|
||||
menu.dispatchEvent(new MouseEvent("popuphiding"));
|
||||
menu.dispatchEvent(new MouseEvent("popuphidden"));
|
||||
} else {
|
||||
EventUtils.synthesizeMouseAtCenter(itemToSelect, {}, win);
|
||||
}
|
||||
return hidden;
|
||||
}
|
||||
|
||||
async function openChromeContextMenu(menuId, target, win = window) {
|
||||
const node = win.document.querySelector(target);
|
||||
const menu = win.document.getElementById(menuId);
|
||||
|
|
|
@ -461,6 +461,8 @@ class Onboarding {
|
|||
// Let's toggle the overlay.
|
||||
case "onboarding-overlay":
|
||||
this.toggleOverlay();
|
||||
let selectedTour = this._tours.find(tour => !this.isTourCompleted(tour.id)) || this._tours[0];
|
||||
this.gotoPage(selectedTour.id);
|
||||
break;
|
||||
case "onboarding-notification-close-btn":
|
||||
this.hideNotification();
|
||||
|
@ -833,15 +835,15 @@ class Onboarding {
|
|||
// Cache elements in arrays for later use to avoid cost of querying elements
|
||||
this._tourItems.push(li);
|
||||
this._tourPages.push(div);
|
||||
|
||||
this.markTourCompletionState(tour.id);
|
||||
}
|
||||
tours.forEach(tour => this.markTourCompletionState(tour.id));
|
||||
|
||||
let dialog = this._window.document.getElementById("onboarding-overlay-dialog");
|
||||
let ul = this._window.document.getElementById("onboarding-tour-list");
|
||||
ul.appendChild(itemsFrag);
|
||||
let footer = this._window.document.getElementById("onboarding-footer");
|
||||
dialog.insertBefore(pagesFrag, footer);
|
||||
this.gotoPage(tours[0].id);
|
||||
}
|
||||
|
||||
_loadCSS() {
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
@import url("chrome://global/skin/in-content/info-pages.css");
|
||||
|
||||
:root {
|
||||
--color-grey: #b1b1b1;
|
||||
--color-grey: #505473;
|
||||
}
|
||||
|
||||
html.private {
|
||||
|
@ -122,18 +122,18 @@ a.button {
|
|||
.toggle + .toggle-btn {
|
||||
box-sizing: border-box;
|
||||
cursor: pointer;
|
||||
min-width: 42px;
|
||||
height: 26px;
|
||||
min-width: 48px;
|
||||
height: 27px;
|
||||
border-radius: 13px;
|
||||
background-color: var(--color-grey);
|
||||
padding: 1px;
|
||||
border: 1px solid #202340;
|
||||
}
|
||||
|
||||
.toggle + .toggle-btn::after {
|
||||
position: relative;
|
||||
display: block;
|
||||
content: "";
|
||||
width: 24px;
|
||||
width: 25px;
|
||||
height: 100%;
|
||||
left: 0;
|
||||
border-radius: 50%;
|
||||
|
@ -149,10 +149,11 @@ a.button {
|
|||
|
||||
.toggle:checked + .toggle-btn {
|
||||
background: #16da00;
|
||||
border-color: #0CA700;
|
||||
}
|
||||
|
||||
.toggle:checked + .toggle-btn::after {
|
||||
left: 16px;
|
||||
left: 21px;
|
||||
}
|
||||
|
||||
.toggle:checked + .toggle-btn:dir(rtl)::after {
|
||||
|
|
|
@ -24,6 +24,7 @@ var { helpers, assert } = (function () {
|
|||
|
||||
var { require } = Cu.import("resource://devtools/shared/Loader.jsm", {});
|
||||
var { TargetFactory } = require("devtools/client/framework/target");
|
||||
var { gDevToolsBrowser } = require("devtools/client/framework/devtools-browser");
|
||||
var Services = require("Services");
|
||||
|
||||
var assert = { ok: ok, is: is, log: info };
|
||||
|
@ -211,8 +212,8 @@ var { helpers, assert } = (function () {
|
|||
options = options || {};
|
||||
options.chromeWindow = options.chromeWindow || window;
|
||||
|
||||
return options.chromeWindow.DeveloperToolbar.show(true).then(function () {
|
||||
var toolbar = options.chromeWindow.DeveloperToolbar;
|
||||
var toolbar = gDevToolsBrowser.getDeveloperToolbar(options.chromeWindow);
|
||||
return toolbar.show(true).then(function () {
|
||||
options.automator = createDeveloperToolbarAutomator(toolbar);
|
||||
options.requisition = toolbar.requisition;
|
||||
return options;
|
||||
|
@ -243,7 +244,8 @@ var { helpers, assert } = (function () {
|
|||
* @return A promise resolved (with undefined) when the toolbar is closed
|
||||
*/
|
||||
helpers.closeToolbar = function (options) {
|
||||
return options.chromeWindow.DeveloperToolbar.hide().then(function () {
|
||||
var toolbar = gDevToolsBrowser.getDeveloperToolbar(options.chromeWindow).hide();
|
||||
return toolbar.then(function () {
|
||||
delete options.automator;
|
||||
delete options.requisition;
|
||||
});
|
||||
|
@ -323,8 +325,8 @@ var { helpers, assert } = (function () {
|
|||
return helpers.addTab(url, function (innerOptions) {
|
||||
var win = innerOptions.chromeWindow;
|
||||
|
||||
return win.DeveloperToolbar.show(true).then(function () {
|
||||
var toolbar = win.DeveloperToolbar;
|
||||
var toolbar = gDevToolsBrowser.getDeveloperToolbar(win);
|
||||
return toolbar.show(true).then(function () {
|
||||
innerOptions.automator = createDeveloperToolbarAutomator(toolbar);
|
||||
innerOptions.requisition = toolbar.requisition;
|
||||
|
||||
|
@ -334,7 +336,7 @@ var { helpers, assert } = (function () {
|
|||
ok(false, error);
|
||||
console.error(error);
|
||||
}).then(function () {
|
||||
win.DeveloperToolbar.hide().then(function () {
|
||||
toolbar.hide().then(function () {
|
||||
delete innerOptions.automator;
|
||||
});
|
||||
});
|
||||
|
|
|
@ -24,6 +24,7 @@ loader.lazyRequireGetter(this, "DebuggerServer", "devtools/server/main", true);
|
|||
loader.lazyRequireGetter(this, "DebuggerClient", "devtools/shared/client/main", true);
|
||||
loader.lazyRequireGetter(this, "BrowserMenus", "devtools/client/framework/browser-menus");
|
||||
loader.lazyRequireGetter(this, "appendStyleSheet", "devtools/client/shared/stylesheet-utils", true);
|
||||
loader.lazyRequireGetter(this, "DeveloperToolbar", "devtools/client/shared/developer-toolbar", true);
|
||||
|
||||
loader.lazyImporter(this, "CustomizableUI", "resource:///modules/CustomizableUI.jsm");
|
||||
loader.lazyImporter(this, "CustomizableWidgets", "resource:///modules/CustomizableWidgets.jsm");
|
||||
|
@ -55,6 +56,11 @@ var gDevToolsBrowser = exports.gDevToolsBrowser = {
|
|||
*/
|
||||
_browserStyleSheets: new WeakMap(),
|
||||
|
||||
/**
|
||||
* WeakMap keeping track of DeveloperToolbar instances for each firefox window.
|
||||
*/
|
||||
_toolbars: new WeakMap(),
|
||||
|
||||
_tabStats: {
|
||||
peakOpen: 0,
|
||||
peakPinned: 0,
|
||||
|
@ -108,7 +114,7 @@ var gDevToolsBrowser = exports.gDevToolsBrowser = {
|
|||
focusEl.setAttribute("disabled", "true");
|
||||
}
|
||||
if (devToolbarEnabled && Services.prefs.getBoolPref("devtools.toolbar.visible")) {
|
||||
win.DeveloperToolbar.show(false).catch(console.error);
|
||||
this.getDeveloperToolbar(win).show(false).catch(console.error);
|
||||
}
|
||||
|
||||
// Enable WebIDE?
|
||||
|
@ -499,12 +505,6 @@ var gDevToolsBrowser = exports.gDevToolsBrowser = {
|
|||
// only once menus are registered as it depends on it.
|
||||
gDevToolsBrowser.installDeveloperWidget();
|
||||
|
||||
// Inject lazily DeveloperToolbar on the chrome window
|
||||
loader.lazyGetter(win, "DeveloperToolbar", function () {
|
||||
let { DeveloperToolbar } = require("devtools/client/shared/developer-toolbar");
|
||||
return new DeveloperToolbar(win);
|
||||
});
|
||||
|
||||
this.updateCommandAvailability(win);
|
||||
this.updateDevtoolsThemeAttribute(win);
|
||||
this.ensurePrefObserver();
|
||||
|
@ -518,6 +518,22 @@ var gDevToolsBrowser = exports.gDevToolsBrowser = {
|
|||
tabContainer.addEventListener("TabUnpinned", this);
|
||||
},
|
||||
|
||||
/**
|
||||
* Create singleton instance of the developer toolbar for a given top level window.
|
||||
*
|
||||
* @param {Window} win
|
||||
* The window to which the toolbar should be created.
|
||||
*/
|
||||
getDeveloperToolbar(win) {
|
||||
let toolbar = this._toolbars.get(win);
|
||||
if (toolbar) {
|
||||
return toolbar;
|
||||
}
|
||||
toolbar = new DeveloperToolbar(win);
|
||||
this._toolbars.set(win, toolbar);
|
||||
return toolbar;
|
||||
},
|
||||
|
||||
/**
|
||||
* Hook the JS debugger tool to the "Debug Script" button of the slow script
|
||||
* dialog.
|
||||
|
@ -727,11 +743,7 @@ var gDevToolsBrowser = exports.gDevToolsBrowser = {
|
|||
this._browserStyleSheets.delete(win);
|
||||
}
|
||||
|
||||
// Destroy the Developer toolbar if it has been accessed
|
||||
let desc = Object.getOwnPropertyDescriptor(win, "DeveloperToolbar");
|
||||
if (desc && !desc.get) {
|
||||
win.DeveloperToolbar.destroy();
|
||||
}
|
||||
this._toolbars.delete(win);
|
||||
|
||||
let tabContainer = win.gBrowser.tabContainer;
|
||||
tabContainer.removeEventListener("TabSelect", this);
|
||||
|
|
|
@ -75,9 +75,9 @@ exports.menuitems = [
|
|||
// or close the toolbar and when hitting the key shortcut where we just
|
||||
// focus the toolbar if it doesn't already has it.
|
||||
if (event.target.tagName.toLowerCase() == "menuitem") {
|
||||
window.DeveloperToolbar.toggle();
|
||||
gDevToolsBrowser.getDeveloperToolbar(window).toggle();
|
||||
} else {
|
||||
window.DeveloperToolbar.focusToggle();
|
||||
gDevToolsBrowser.getDeveloperToolbar(window).focusToggle();
|
||||
}
|
||||
},
|
||||
key: {
|
||||
|
|
|
@ -230,7 +230,9 @@ DeveloperToolbar.prototype.createToolbar = function () {
|
|||
let close = this._doc.createElement("toolbarbutton");
|
||||
close.setAttribute("id", "developer-toolbar-closebutton");
|
||||
close.setAttribute("class", "close-icon");
|
||||
close.setAttribute("oncommand", "DeveloperToolbar.hide();");
|
||||
close.addEventListener("command", (event) => {
|
||||
this.hide();
|
||||
});
|
||||
let closeTooltip = L10N.getStr("toolbar.closeButton.tooltip");
|
||||
close.setAttribute("tooltiptext", closeTooltip);
|
||||
|
||||
|
|
|
@ -3,7 +3,4 @@
|
|||
module.exports = {
|
||||
// Extend from the shared list of defined globals for mochitests.
|
||||
"extends": "../../../.eslintrc.mochitests.js",
|
||||
"globals": {
|
||||
"DeveloperToolbar": true
|
||||
}
|
||||
};
|
||||
|
|
|
@ -5,18 +5,21 @@
|
|||
|
||||
// Tests that the developer toolbar works properly
|
||||
|
||||
const {gDevToolsBrowser} = require("devtools/client/framework/devtools-browser");
|
||||
|
||||
const TEST_URI = TEST_URI_ROOT + "doc_toolbar_basic.html";
|
||||
|
||||
add_task(function* () {
|
||||
info("Starting browser_toolbar_basic.js");
|
||||
yield addTab(TEST_URI);
|
||||
|
||||
ok(!DeveloperToolbar.visible, "DeveloperToolbar is not visible in to start");
|
||||
let toolbar = gDevToolsBrowser.getDeveloperToolbar(window);
|
||||
ok(!toolbar.visible, "DeveloperToolbar is not visible in to start");
|
||||
|
||||
let shown = oneTimeObserve(DeveloperToolbar.NOTIFICATIONS.SHOW);
|
||||
let shown = oneTimeObserve(toolbar.NOTIFICATIONS.SHOW);
|
||||
document.getElementById("menu_devToolbar").doCommand();
|
||||
yield shown;
|
||||
ok(DeveloperToolbar.visible, "DeveloperToolbar is visible in checkOpen");
|
||||
ok(toolbar.visible, "DeveloperToolbar is visible in checkOpen");
|
||||
|
||||
let close = document.getElementById("developer-toolbar-closebutton");
|
||||
ok(close, "Close button exists");
|
||||
|
@ -36,23 +39,23 @@ add_task(function* () {
|
|||
|
||||
gBrowser.removeCurrentTab();
|
||||
|
||||
let hidden = oneTimeObserve(DeveloperToolbar.NOTIFICATIONS.HIDE);
|
||||
let hidden = oneTimeObserve(toolbar.NOTIFICATIONS.HIDE);
|
||||
document.getElementById("menu_devToolbar").doCommand();
|
||||
yield hidden;
|
||||
ok(!DeveloperToolbar.visible, "DeveloperToolbar is not visible in hidden");
|
||||
ok(!toolbar.visible, "DeveloperToolbar is not visible in hidden");
|
||||
|
||||
shown = oneTimeObserve(DeveloperToolbar.NOTIFICATIONS.SHOW);
|
||||
shown = oneTimeObserve(toolbar.NOTIFICATIONS.SHOW);
|
||||
document.getElementById("menu_devToolbar").doCommand();
|
||||
yield shown;
|
||||
ok(DeveloperToolbar.visible, "DeveloperToolbar is visible in after open");
|
||||
ok(toolbar.visible, "DeveloperToolbar is visible in after open");
|
||||
|
||||
ok(isChecked(toggleToolbox), "toggle toolbox button is checked");
|
||||
|
||||
hidden = oneTimeObserve(DeveloperToolbar.NOTIFICATIONS.HIDE);
|
||||
hidden = oneTimeObserve(toolbar.NOTIFICATIONS.HIDE);
|
||||
document.getElementById("developer-toolbar-closebutton").doCommand();
|
||||
yield hidden;
|
||||
|
||||
ok(!DeveloperToolbar.visible, "DeveloperToolbar is not visible after re-close");
|
||||
ok(!toolbar.visible, "DeveloperToolbar is not visible after re-close");
|
||||
});
|
||||
|
||||
function isChecked(b) {
|
||||
|
|
|
@ -5,6 +5,8 @@
|
|||
|
||||
// Tests that the developer toolbar works properly
|
||||
|
||||
const {gDevToolsBrowser} = require("devtools/client/framework/devtools-browser");
|
||||
|
||||
const TEST_URI = "data:text/html;charset=utf-8,<p>Tooltip Tests</p>";
|
||||
const PREF_DEVTOOLS_THEME = "devtools.theme";
|
||||
|
||||
|
@ -13,82 +15,84 @@ registerCleanupFunction(() => {
|
|||
Services.prefs.clearUserPref(PREF_DEVTOOLS_THEME);
|
||||
});
|
||||
|
||||
let toolbar = gDevToolsBrowser.getDeveloperToolbar(window);
|
||||
|
||||
add_task(function* showToolbar() {
|
||||
yield addTab(TEST_URI);
|
||||
|
||||
info("Starting browser_toolbar_tooltip.js");
|
||||
|
||||
ok(!DeveloperToolbar.visible, "DeveloperToolbar is not visible in runTest");
|
||||
ok(!toolbar.visible, "DeveloperToolbar is not visible in runTest");
|
||||
|
||||
let showPromise = observeOnce(DeveloperToolbar.NOTIFICATIONS.SHOW);
|
||||
let showPromise = observeOnce(toolbar.NOTIFICATIONS.SHOW);
|
||||
document.getElementById("menu_devToolbar").doCommand();
|
||||
yield showPromise;
|
||||
});
|
||||
|
||||
add_task(function* testDimensions() {
|
||||
let tooltipPanel = DeveloperToolbar.tooltipPanel;
|
||||
let tooltipPanel = toolbar.tooltipPanel;
|
||||
|
||||
DeveloperToolbar.focusManager.helpRequest();
|
||||
yield DeveloperToolbar.inputter.setInput("help help");
|
||||
toolbar.focusManager.helpRequest();
|
||||
yield toolbar.inputter.setInput("help help");
|
||||
|
||||
DeveloperToolbar.inputter.setCursor({ start: "help help".length });
|
||||
toolbar.inputter.setCursor({ start: "help help".length });
|
||||
is(tooltipPanel._dimensions.start, "help ".length,
|
||||
"search param start, when cursor at end");
|
||||
ok(getLeftMargin() > 30, "tooltip offset, when cursor at end");
|
||||
|
||||
DeveloperToolbar.inputter.setCursor({ start: "help".length });
|
||||
toolbar.inputter.setCursor({ start: "help".length });
|
||||
is(tooltipPanel._dimensions.start, 0,
|
||||
"search param start, when cursor at end of command");
|
||||
ok(getLeftMargin() > 9, "tooltip offset, when cursor at end of command");
|
||||
|
||||
DeveloperToolbar.inputter.setCursor({ start: "help help".length - 1 });
|
||||
toolbar.inputter.setCursor({ start: "help help".length - 1 });
|
||||
is(tooltipPanel._dimensions.start, "help ".length,
|
||||
"search param start, when cursor at penultimate position");
|
||||
ok(getLeftMargin() > 30, "tooltip offset, when cursor at penultimate position");
|
||||
|
||||
DeveloperToolbar.inputter.setCursor({ start: 0 });
|
||||
toolbar.inputter.setCursor({ start: 0 });
|
||||
is(tooltipPanel._dimensions.start, 0,
|
||||
"search param start, when cursor at start");
|
||||
ok(getLeftMargin() > 9, "tooltip offset, when cursor at start");
|
||||
});
|
||||
|
||||
add_task(function* testThemes() {
|
||||
let tooltipPanel = DeveloperToolbar.tooltipPanel;
|
||||
let tooltipPanel = toolbar.tooltipPanel;
|
||||
ok(tooltipPanel.document, "Tooltip panel is initialized");
|
||||
|
||||
Services.prefs.setCharPref(PREF_DEVTOOLS_THEME, "dark");
|
||||
|
||||
yield DeveloperToolbar.inputter.setInput("");
|
||||
yield DeveloperToolbar.inputter.setInput("help help");
|
||||
yield toolbar.inputter.setInput("");
|
||||
yield toolbar.inputter.setInput("help help");
|
||||
is(tooltipPanel.document.documentElement.getAttribute("devtoolstheme"),
|
||||
"dark", "Tooltip panel has correct theme");
|
||||
|
||||
Services.prefs.setCharPref(PREF_DEVTOOLS_THEME, "light");
|
||||
|
||||
yield DeveloperToolbar.inputter.setInput("");
|
||||
yield DeveloperToolbar.inputter.setInput("help help");
|
||||
yield toolbar.inputter.setInput("");
|
||||
yield toolbar.inputter.setInput("help help");
|
||||
is(tooltipPanel.document.documentElement.getAttribute("devtoolstheme"),
|
||||
"light", "Tooltip panel has correct theme");
|
||||
});
|
||||
|
||||
add_task(function* hideToolbar() {
|
||||
info("Ending browser_toolbar_tooltip.js");
|
||||
yield DeveloperToolbar.inputter.setInput("");
|
||||
yield toolbar.inputter.setInput("");
|
||||
|
||||
ok(DeveloperToolbar.visible, "DeveloperToolbar is visible in hideToolbar");
|
||||
ok(toolbar.visible, "DeveloperToolbar is visible in hideToolbar");
|
||||
|
||||
info("Hide toolbar");
|
||||
let hidePromise = observeOnce(DeveloperToolbar.NOTIFICATIONS.HIDE);
|
||||
let hidePromise = observeOnce(toolbar.NOTIFICATIONS.HIDE);
|
||||
document.getElementById("menu_devToolbar").doCommand();
|
||||
yield hidePromise;
|
||||
|
||||
ok(!DeveloperToolbar.visible, "DeveloperToolbar is not visible in hideToolbar");
|
||||
ok(!toolbar.visible, "DeveloperToolbar is not visible in hideToolbar");
|
||||
|
||||
info("Done test");
|
||||
});
|
||||
|
||||
function getLeftMargin() {
|
||||
let style = DeveloperToolbar.tooltipPanel._panel.style.marginLeft;
|
||||
let style = toolbar.tooltipPanel._panel.style.marginLeft;
|
||||
return parseInt(style.slice(0, -2), 10);
|
||||
}
|
||||
|
||||
|
|
|
@ -7,6 +7,8 @@
|
|||
|
||||
// Tests that the developer toolbar errors count works properly.
|
||||
|
||||
const {gDevToolsBrowser} = require("devtools/client/framework/devtools-browser");
|
||||
|
||||
// Use the old webconsole since this is directly accessing old DOM, and
|
||||
// the error count isn't reset when pressing the clear button in new one
|
||||
// See Bug 1304794.
|
||||
|
@ -15,6 +17,8 @@ registerCleanupFunction(function* () {
|
|||
Services.prefs.clearUserPref("devtools.webconsole.new-frontend-enabled");
|
||||
});
|
||||
|
||||
let toolbar = gDevToolsBrowser.getDeveloperToolbar(window);
|
||||
|
||||
function test() {
|
||||
const TEST_URI = TEST_URI_ROOT + "doc_toolbar_webconsole_errors_count.html";
|
||||
|
||||
|
@ -35,15 +39,15 @@ function test() {
|
|||
|
||||
expectUncaughtException();
|
||||
|
||||
if (!DeveloperToolbar.visible) {
|
||||
DeveloperToolbar.show(true).then(onOpenToolbar);
|
||||
if (!toolbar.visible) {
|
||||
toolbar.show(true).then(onOpenToolbar);
|
||||
} else {
|
||||
onOpenToolbar();
|
||||
}
|
||||
}
|
||||
|
||||
function onOpenToolbar() {
|
||||
ok(DeveloperToolbar.visible, "DeveloperToolbar is visible");
|
||||
ok(toolbar.visible, "DeveloperToolbar is visible");
|
||||
webconsole = document.getElementById("developer-toolbar-toolbox-button");
|
||||
|
||||
waitForButtonUpdate({
|
||||
|
@ -240,9 +244,9 @@ function test() {
|
|||
|
||||
if (!check()) {
|
||||
info("wait for: " + options.name);
|
||||
DeveloperToolbar.on("errors-counter-updated", function onUpdate(event) {
|
||||
toolbar.on("errors-counter-updated", function onUpdate(event) {
|
||||
if (check()) {
|
||||
DeveloperToolbar.off(event, onUpdate);
|
||||
toolbar.off(event, onUpdate);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
var WebConsoleUtils = require("devtools/client/webconsole/utils").Utils;
|
||||
const {extend} = require("devtools/shared/extend");
|
||||
var {TargetFactory} = require("devtools/client/framework/target");
|
||||
var {gDevToolsBrowser} = require("devtools/client/framework/devtools-browser");
|
||||
var {Tools} = require("devtools/client/definitions");
|
||||
const { Task } = require("devtools/shared/task");
|
||||
var promise = require("promise");
|
||||
|
@ -389,7 +390,8 @@ WebConsole.prototype = {
|
|||
_onClearButton: function WC__onClearButton()
|
||||
{
|
||||
if (this.target.isLocalTab) {
|
||||
this.browserWindow.DeveloperToolbar.resetErrorsCount(this.target.tab);
|
||||
gDevToolsBrowser.getDeveloperToolbar(this.browserWindow)
|
||||
.resetErrorsCount(this.target.tab);
|
||||
}
|
||||
},
|
||||
|
||||
|
|
|
@ -8,6 +8,11 @@
|
|||
}
|
||||
}
|
||||
|
||||
div.pseudo::before {
|
||||
animation: move 0.01s;
|
||||
content: 'content';
|
||||
}
|
||||
|
||||
</style>
|
||||
<body>
|
||||
<script>
|
||||
|
@ -157,6 +162,35 @@ test(function(t) {
|
|||
}, 'CSS Animation which has already finished starts playing when its parent ' +
|
||||
'element is shown from "display:none" state');
|
||||
|
||||
promise_test(function(t) {
|
||||
var div = addDiv(t, { 'class': 'pseudo' });
|
||||
var eventWatcher = new EventWatcher(t, div, 'animationend');
|
||||
|
||||
assert_equals(document.getAnimations().length, 1,
|
||||
'CSS animation on pseudo element');
|
||||
|
||||
return eventWatcher.wait_for('animationend').then(function() {
|
||||
assert_equals(document.getAnimations().length, 0,
|
||||
'No CSS animation on pseudo element after the animation ' +
|
||||
'finished');
|
||||
|
||||
// Remove the class which generated this pseudo element.
|
||||
div.classList.remove('pseudo');
|
||||
|
||||
// We need to wait for two frames to process re-framing.
|
||||
// The callback of 'animationend' is processed just before rAF callbacks,
|
||||
// and rAF callbacks are processed before re-framing process, so waiting for
|
||||
// one rAF callback is not sufficient.
|
||||
return waitForAnimationFrames(2);
|
||||
}).then(function() {
|
||||
// Add the class again to re-generate pseudo element.
|
||||
div.classList.add('pseudo');
|
||||
assert_equals(document.getAnimations().length, 1,
|
||||
'A new CSS animation on pseudo element');
|
||||
});
|
||||
}, 'CSS animation on pseudo element restarts after the pseudo element that ' +
|
||||
'had a finished CSS animation is re-generated');
|
||||
|
||||
done();
|
||||
</script>
|
||||
</body>
|
||||
|
|
|
@ -186,13 +186,6 @@ PrintDocTreeAll(nsIDocShellTreeItem* aItem)
|
|||
}
|
||||
#endif
|
||||
|
||||
// mask values for ui.key.chromeAccess and ui.key.contentAccess
|
||||
#define NS_MODIFIER_SHIFT 1
|
||||
#define NS_MODIFIER_CONTROL 2
|
||||
#define NS_MODIFIER_ALT 4
|
||||
#define NS_MODIFIER_META 8
|
||||
#define NS_MODIFIER_OS 16
|
||||
|
||||
/******************************************************************/
|
||||
/* mozilla::UITimerCallback */
|
||||
/******************************************************************/
|
||||
|
@ -768,30 +761,34 @@ EventStateManager::PreHandleEvent(nsPresContext* aPresContext,
|
|||
case eKeyPress:
|
||||
{
|
||||
WidgetKeyboardEvent* keyEvent = aEvent->AsKeyboardEvent();
|
||||
|
||||
int32_t modifierMask = 0;
|
||||
if (keyEvent->IsShift())
|
||||
modifierMask |= NS_MODIFIER_SHIFT;
|
||||
if (keyEvent->IsControl())
|
||||
modifierMask |= NS_MODIFIER_CONTROL;
|
||||
if (keyEvent->IsAlt())
|
||||
modifierMask |= NS_MODIFIER_ALT;
|
||||
if (keyEvent->IsMeta())
|
||||
modifierMask |= NS_MODIFIER_META;
|
||||
if (keyEvent->IsOS())
|
||||
modifierMask |= NS_MODIFIER_OS;
|
||||
|
||||
// Prevent keyboard scrolling while an accesskey modifier is in use.
|
||||
if (modifierMask) {
|
||||
bool matchesContentAccessKey = (modifierMask == Prefs::ContentAccessModifierMask());
|
||||
|
||||
if (modifierMask == Prefs::ChromeAccessModifierMask() ||
|
||||
matchesContentAccessKey) {
|
||||
if (keyEvent->ModifiersMatchWithAccessKey(AccessKeyType::eChrome) ||
|
||||
keyEvent->ModifiersMatchWithAccessKey(AccessKeyType::eContent)) {
|
||||
// If the eKeyPress event will be sent to a remote process, this
|
||||
// process needs to wait reply from the remote process for checking if
|
||||
// preceding eKeyDown event is consumed. If preceding eKeyDown event
|
||||
// is consumed in the remote process, TabChild won't send the event
|
||||
// back to this process. So, only when this process receives a reply
|
||||
// eKeyPress event in TabParent, we should handle accesskey in this
|
||||
// process.
|
||||
if (IsRemoteTarget(GetFocusedContent())) {
|
||||
// However, if there is no accesskey target for the key combination,
|
||||
// we don't need to wait reply from the remote process. Otherwise,
|
||||
// Mark the event as waiting reply from remote process and stop
|
||||
// propagation in this process.
|
||||
if (CheckIfEventMatchesAccessKey(keyEvent, aPresContext)) {
|
||||
keyEvent->StopPropagation();
|
||||
keyEvent->MarkAsWaitingReplyFromRemoteProcess();
|
||||
}
|
||||
}
|
||||
// If the event target is in this process, we can handle accesskey now
|
||||
// since if preceding eKeyDown event was consumed, eKeyPress event
|
||||
// won't be dispatched by widget. So, coming eKeyPress event means
|
||||
// that the preceding eKeyDown event wasn't consumed in this case.
|
||||
else {
|
||||
AutoTArray<uint32_t, 10> accessCharCodes;
|
||||
keyEvent->GetAccessKeyCandidates(accessCharCodes);
|
||||
|
||||
if (HandleAccessKey(keyEvent, aPresContext, accessCharCodes,
|
||||
modifierMask, matchesContentAccessKey)) {
|
||||
if (HandleAccessKey(keyEvent, aPresContext, accessCharCodes)) {
|
||||
*aStatus = nsEventStatus_eConsumeNoDefault;
|
||||
}
|
||||
}
|
||||
|
@ -817,6 +814,18 @@ EventStateManager::PreHandleEvent(nsPresContext* aPresContext,
|
|||
RefPtr<TextComposition> composition =
|
||||
IMEStateManager::GetTextCompositionFor(aPresContext);
|
||||
aEvent->AsKeyboardEvent()->mIsComposing = !!composition;
|
||||
|
||||
// Widget may need to perform default action for specific keyboard
|
||||
// event if it's not consumed. In this case, widget has already marked
|
||||
// the event as "waiting reply from remote process". However, we need
|
||||
// to reset it if the target (focused content) isn't in a remote process
|
||||
// because PresShell needs to check if it's marked as so before
|
||||
// dispatching events into the DOM tree.
|
||||
if (aEvent->IsWaitingReplyFromRemoteProcess() &&
|
||||
!aEvent->PropagationStopped() &&
|
||||
!IsRemoteTarget(content)) {
|
||||
aEvent->ResetWaitingReplyFromRemoteProcessState();
|
||||
}
|
||||
}
|
||||
break;
|
||||
case eWheel:
|
||||
|
@ -924,23 +933,21 @@ EventStateManager::HandleQueryContentEvent(WidgetQueryContentEvent* aEvent)
|
|||
handler.HandleQueryContentEvent(aEvent);
|
||||
}
|
||||
|
||||
// static
|
||||
int32_t
|
||||
EventStateManager::GetAccessModifierMaskFor(nsISupports* aDocShell)
|
||||
static AccessKeyType
|
||||
GetAccessKeyTypeFor(nsISupports* aDocShell)
|
||||
{
|
||||
nsCOMPtr<nsIDocShellTreeItem> treeItem(do_QueryInterface(aDocShell));
|
||||
if (!treeItem)
|
||||
return -1; // invalid modifier
|
||||
if (!treeItem) {
|
||||
return AccessKeyType::eNone;
|
||||
}
|
||||
|
||||
switch (treeItem->ItemType()) {
|
||||
case nsIDocShellTreeItem::typeChrome:
|
||||
return Prefs::ChromeAccessModifierMask();
|
||||
|
||||
case nsIDocShellTreeItem::typeContent:
|
||||
return Prefs::ContentAccessModifierMask();
|
||||
|
||||
default:
|
||||
return -1; // invalid modifier
|
||||
case nsIDocShellTreeItem::typeChrome:
|
||||
return AccessKeyType::eChrome;
|
||||
case nsIDocShellTreeItem::typeContent:
|
||||
return AccessKeyType::eContent;
|
||||
default:
|
||||
return AccessKeyType::eNone;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -992,8 +999,22 @@ IsAccessKeyTarget(nsIContent* aContent, nsIFrame* aFrame, nsAString& aKey)
|
|||
}
|
||||
|
||||
bool
|
||||
EventStateManager::ExecuteAccessKey(nsTArray<uint32_t>& aAccessCharCodes,
|
||||
bool aIsTrustedEvent)
|
||||
EventStateManager::CheckIfEventMatchesAccessKey(WidgetKeyboardEvent* aEvent,
|
||||
nsPresContext* aPresContext)
|
||||
{
|
||||
AutoTArray<uint32_t, 10> accessCharCodes;
|
||||
aEvent->GetAccessKeyCandidates(accessCharCodes);
|
||||
return WalkESMTreeToHandleAccessKey(const_cast<WidgetKeyboardEvent*>(aEvent),
|
||||
aPresContext, accessCharCodes,
|
||||
nullptr, eAccessKeyProcessingNormal,
|
||||
false);
|
||||
}
|
||||
|
||||
bool
|
||||
EventStateManager::LookForAccessKeyAndExecute(
|
||||
nsTArray<uint32_t>& aAccessCharCodes,
|
||||
bool aIsTrustedEvent,
|
||||
bool aExecute)
|
||||
{
|
||||
int32_t count, start = -1;
|
||||
nsIContent* focusedContent = GetFocusedContent();
|
||||
|
@ -1013,6 +1034,9 @@ EventStateManager::ExecuteAccessKey(nsTArray<uint32_t>& aAccessCharCodes,
|
|||
content = mAccessKeys[(start + count) % length];
|
||||
frame = content->GetPrimaryFrame();
|
||||
if (IsAccessKeyTarget(content, frame, accessKey)) {
|
||||
if (!aExecute) {
|
||||
return true;
|
||||
}
|
||||
bool shouldActivate = Prefs::KeyCausesActivation();
|
||||
while (shouldActivate && ++count <= length) {
|
||||
nsIContent *oc = mAccessKeys[(start + count) % length];
|
||||
|
@ -1059,30 +1083,33 @@ EventStateManager::GetAccessKeyLabelPrefix(Element* aElement, nsAString& aPrefix
|
|||
nsAutoString separator, modifierText;
|
||||
nsContentUtils::GetModifierSeparatorText(separator);
|
||||
|
||||
nsCOMPtr<nsISupports> container = aElement->OwnerDoc()->GetDocShell();
|
||||
int32_t modifierMask = GetAccessModifierMaskFor(container);
|
||||
|
||||
if (modifierMask == -1) {
|
||||
AccessKeyType accessKeyType =
|
||||
GetAccessKeyTypeFor(aElement->OwnerDoc()->GetDocShell());
|
||||
if (accessKeyType == AccessKeyType::eNone) {
|
||||
return;
|
||||
}
|
||||
Modifiers modifiers = WidgetKeyboardEvent::AccessKeyModifiers(accessKeyType);
|
||||
if (modifiers == MODIFIER_NONE) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (modifierMask & NS_MODIFIER_CONTROL) {
|
||||
if (modifiers & MODIFIER_CONTROL) {
|
||||
nsContentUtils::GetControlText(modifierText);
|
||||
aPrefix.Append(modifierText + separator);
|
||||
}
|
||||
if (modifierMask & NS_MODIFIER_META) {
|
||||
if (modifiers & MODIFIER_META) {
|
||||
nsContentUtils::GetMetaText(modifierText);
|
||||
aPrefix.Append(modifierText + separator);
|
||||
}
|
||||
if (modifierMask & NS_MODIFIER_OS) {
|
||||
if (modifiers & MODIFIER_OS) {
|
||||
nsContentUtils::GetOSText(modifierText);
|
||||
aPrefix.Append(modifierText + separator);
|
||||
}
|
||||
if (modifierMask & NS_MODIFIER_ALT) {
|
||||
if (modifiers & MODIFIER_ALT) {
|
||||
nsContentUtils::GetAltText(modifierText);
|
||||
aPrefix.Append(modifierText + separator);
|
||||
}
|
||||
if (modifierMask & NS_MODIFIER_SHIFT) {
|
||||
if (modifiers & MODIFIER_SHIFT) {
|
||||
nsContentUtils::GetShiftText(modifierText);
|
||||
aPrefix.Append(modifierText + separator);
|
||||
}
|
||||
|
@ -1092,12 +1119,11 @@ struct MOZ_STACK_CLASS AccessKeyInfo
|
|||
{
|
||||
WidgetKeyboardEvent* event;
|
||||
nsTArray<uint32_t>& charCodes;
|
||||
int32_t modifierMask;
|
||||
|
||||
AccessKeyInfo(WidgetKeyboardEvent* aEvent, nsTArray<uint32_t>& aCharCodes, int32_t aModifierMask)
|
||||
AccessKeyInfo(WidgetKeyboardEvent* aEvent,
|
||||
nsTArray<uint32_t>& aCharCodes)
|
||||
: event(aEvent)
|
||||
, charCodes(aCharCodes)
|
||||
, modifierMask(aModifierMask)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
@ -1111,10 +1137,15 @@ HandleAccessKeyInRemoteChild(TabParent* aTabParent, void* aArg)
|
|||
bool active;
|
||||
aTabParent->GetDocShellIsActive(&active);
|
||||
if (active) {
|
||||
accessKeyInfo->event->mAccessKeyForwardedToChild = true;
|
||||
// Even if there is no target for the accesskey in this process,
|
||||
// the event may match with a content accesskey. If so, the keyboard
|
||||
// event should be handled with reply event for preventing double action.
|
||||
// (e.g., Alt+Shift+F on Windows may focus a content in remote and open
|
||||
// "File" menu.)
|
||||
accessKeyInfo->event->StopPropagation();
|
||||
accessKeyInfo->event->MarkAsWaitingReplyFromRemoteProcess();
|
||||
aTabParent->HandleAccessKey(*accessKeyInfo->event,
|
||||
accessKeyInfo->charCodes,
|
||||
accessKeyInfo->modifierMask);
|
||||
accessKeyInfo->charCodes);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -1122,25 +1153,29 @@ HandleAccessKeyInRemoteChild(TabParent* aTabParent, void* aArg)
|
|||
}
|
||||
|
||||
bool
|
||||
EventStateManager::HandleAccessKey(WidgetKeyboardEvent* aEvent,
|
||||
nsPresContext* aPresContext,
|
||||
nsTArray<uint32_t>& aAccessCharCodes,
|
||||
bool aMatchesContentAccessKey,
|
||||
nsIDocShellTreeItem* aBubbledFrom,
|
||||
ProcessingAccessKeyState aAccessKeyState,
|
||||
int32_t aModifierMask)
|
||||
EventStateManager::WalkESMTreeToHandleAccessKey(
|
||||
WidgetKeyboardEvent* aEvent,
|
||||
nsPresContext* aPresContext,
|
||||
nsTArray<uint32_t>& aAccessCharCodes,
|
||||
nsIDocShellTreeItem* aBubbledFrom,
|
||||
ProcessingAccessKeyState aAccessKeyState,
|
||||
bool aExecute)
|
||||
{
|
||||
EnsureDocument(mPresContext);
|
||||
nsCOMPtr<nsIDocShell> docShell = aPresContext->GetDocShell();
|
||||
if (NS_WARN_IF(!docShell) || NS_WARN_IF(!mDocument)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
AccessKeyType accessKeyType = GetAccessKeyTypeFor(docShell);
|
||||
if (accessKeyType == AccessKeyType::eNone) {
|
||||
return false;
|
||||
}
|
||||
// Alt or other accesskey modifier is down, we may need to do an accesskey.
|
||||
if (mAccessKeys.Count() > 0 &&
|
||||
aModifierMask == GetAccessModifierMaskFor(docShell)) {
|
||||
aEvent->ModifiersMatchWithAccessKey(accessKeyType)) {
|
||||
// Someone registered an accesskey. Find and activate it.
|
||||
if (ExecuteAccessKey(aAccessCharCodes, aEvent->IsTrusted())) {
|
||||
if (LookForAccessKeyAndExecute(aAccessCharCodes,
|
||||
aEvent->IsTrusted(), aExecute)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@ -1173,9 +1208,9 @@ EventStateManager::HandleAccessKey(WidgetKeyboardEvent* aEvent,
|
|||
static_cast<EventStateManager*>(subPC->EventStateManager());
|
||||
|
||||
if (esm &&
|
||||
esm->HandleAccessKey(aEvent, subPC, aAccessCharCodes,
|
||||
aMatchesContentAccessKey, nullptr,
|
||||
eAccessKeyProcessingDown, aModifierMask)) {
|
||||
esm->WalkESMTreeToHandleAccessKey(aEvent, subPC, aAccessCharCodes,
|
||||
nullptr, eAccessKeyProcessingDown,
|
||||
aExecute)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@ -1196,29 +1231,38 @@ EventStateManager::HandleAccessKey(WidgetKeyboardEvent* aEvent,
|
|||
EventStateManager* esm =
|
||||
static_cast<EventStateManager*>(parentPC->EventStateManager());
|
||||
if (esm &&
|
||||
esm->HandleAccessKey(aEvent, parentPC, aAccessCharCodes,
|
||||
aMatchesContentAccessKey, docShell,
|
||||
eAccessKeyProcessingDown, aModifierMask)) {
|
||||
esm->WalkESMTreeToHandleAccessKey(aEvent, parentPC, aAccessCharCodes,
|
||||
docShell, eAccessKeyProcessingDown,
|
||||
aExecute)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}// if end. bubble up process
|
||||
|
||||
// If the content access key modifier is pressed, try remote children
|
||||
if (aMatchesContentAccessKey && mDocument && mDocument->GetWindow()) {
|
||||
// If the focus is currently on a node with a TabParent, the key event will
|
||||
// get forwarded to the child process and HandleAccessKey called from there.
|
||||
if (aExecute &&
|
||||
aEvent->ModifiersMatchWithAccessKey(AccessKeyType::eContent) &&
|
||||
mDocument && mDocument->GetWindow()) {
|
||||
// If the focus is currently on a node with a TabParent, the key event
|
||||
// should've gotten forwarded to the child process and HandleAccessKey
|
||||
// called from there.
|
||||
if (TabParent::GetFrom(GetFocusedContent())) {
|
||||
// If access key may be only in remote contents, this method won't handle
|
||||
// access key synchronously. In this case, only reply event should reach
|
||||
// here.
|
||||
MOZ_ASSERT(aEvent->IsHandledInRemoteProcess() ||
|
||||
!aEvent->IsWaitingReplyFromRemoteProcess());
|
||||
}
|
||||
// If focus is somewhere else, then we need to check the remote children.
|
||||
nsFocusManager* fm = nsFocusManager::GetFocusManager();
|
||||
nsIContent* focusedContent = fm ? fm->GetFocusedContent() : nullptr;
|
||||
if (TabParent::GetFrom(focusedContent)) {
|
||||
// A remote child process is focused. The key event should get sent to
|
||||
// the child process.
|
||||
aEvent->mAccessKeyForwardedToChild = true;
|
||||
} else {
|
||||
AccessKeyInfo accessKeyInfo(aEvent, aAccessCharCodes, aModifierMask);
|
||||
// However, if the event has already been handled in a remote process,
|
||||
// then, focus is moved from the remote process after posting the event.
|
||||
// In such case, we shouldn't retry to handle access keys in remote
|
||||
// processes.
|
||||
else if (!aEvent->IsHandledInRemoteProcess()) {
|
||||
AccessKeyInfo accessKeyInfo(aEvent, aAccessCharCodes);
|
||||
nsContentUtils::CallOnAllRemoteChildren(mDocument->GetWindow(),
|
||||
HandleAccessKeyInRemoteChild, &accessKeyInfo);
|
||||
HandleAccessKeyInRemoteChild,
|
||||
&accessKeyInfo);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -5819,9 +5863,6 @@ EventStateManager::WheelPrefs::IsOverOnePageScrollAllowedY(
|
|||
|
||||
bool EventStateManager::Prefs::sKeyCausesActivation = true;
|
||||
bool EventStateManager::Prefs::sClickHoldContextMenu = false;
|
||||
int32_t EventStateManager::Prefs::sGenericAccessModifierKey = -1;
|
||||
int32_t EventStateManager::Prefs::sChromeAccessModifierMask = 0;
|
||||
int32_t EventStateManager::Prefs::sContentAccessModifierMask = 0;
|
||||
|
||||
// static
|
||||
void
|
||||
|
@ -5846,21 +5887,6 @@ EventStateManager::Prefs::Init()
|
|||
sClickHoldContextMenu);
|
||||
MOZ_ASSERT(NS_SUCCEEDED(rv),
|
||||
"Failed to observe \"ui.click_hold_context_menus\"");
|
||||
rv = Preferences::AddIntVarCache(&sGenericAccessModifierKey,
|
||||
"ui.key.generalAccessKey",
|
||||
sGenericAccessModifierKey);
|
||||
MOZ_ASSERT(NS_SUCCEEDED(rv),
|
||||
"Failed to observe \"ui.key.generalAccessKey\"");
|
||||
rv = Preferences::AddIntVarCache(&sChromeAccessModifierMask,
|
||||
"ui.key.chromeAccess",
|
||||
sChromeAccessModifierMask);
|
||||
MOZ_ASSERT(NS_SUCCEEDED(rv),
|
||||
"Failed to observe \"ui.key.chromeAccess\"");
|
||||
rv = Preferences::AddIntVarCache(&sContentAccessModifierMask,
|
||||
"ui.key.contentAccess",
|
||||
sContentAccessModifierMask);
|
||||
MOZ_ASSERT(NS_SUCCEEDED(rv),
|
||||
"Failed to observe \"ui.key.contentAccess\"");
|
||||
sPrefsAlreadyCached = true;
|
||||
}
|
||||
|
||||
|
@ -5881,44 +5907,6 @@ EventStateManager::Prefs::Shutdown()
|
|||
Preferences::UnregisterCallback(OnChange, "dom.popup_allowed_events");
|
||||
}
|
||||
|
||||
// static
|
||||
int32_t
|
||||
EventStateManager::Prefs::ChromeAccessModifierMask()
|
||||
{
|
||||
return GetAccessModifierMask(nsIDocShellTreeItem::typeChrome);
|
||||
}
|
||||
|
||||
// static
|
||||
int32_t
|
||||
EventStateManager::Prefs::ContentAccessModifierMask()
|
||||
{
|
||||
return GetAccessModifierMask(nsIDocShellTreeItem::typeContent);
|
||||
}
|
||||
|
||||
// static
|
||||
int32_t
|
||||
EventStateManager::Prefs::GetAccessModifierMask(int32_t aItemType)
|
||||
{
|
||||
switch (sGenericAccessModifierKey) {
|
||||
case -1: break; // use the individual prefs
|
||||
case nsIDOMKeyEvent::DOM_VK_SHIFT: return NS_MODIFIER_SHIFT;
|
||||
case nsIDOMKeyEvent::DOM_VK_CONTROL: return NS_MODIFIER_CONTROL;
|
||||
case nsIDOMKeyEvent::DOM_VK_ALT: return NS_MODIFIER_ALT;
|
||||
case nsIDOMKeyEvent::DOM_VK_META: return NS_MODIFIER_META;
|
||||
case nsIDOMKeyEvent::DOM_VK_WIN: return NS_MODIFIER_OS;
|
||||
default: return 0;
|
||||
}
|
||||
|
||||
switch (aItemType) {
|
||||
case nsIDocShellTreeItem::typeChrome:
|
||||
return sChromeAccessModifierMask;
|
||||
case nsIDocShellTreeItem::typeContent:
|
||||
return sContentAccessModifierMask;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/******************************************************************/
|
||||
/* mozilla::AutoHandlingUserInputStatePusher */
|
||||
/******************************************************************/
|
||||
|
|
|
@ -184,17 +184,47 @@ public:
|
|||
|
||||
static void GetAccessKeyLabelPrefix(dom::Element* aElement, nsAString& aPrefix);
|
||||
|
||||
/**
|
||||
* HandleAccessKey() looks for access keys which matches with aEvent and
|
||||
* execute when it matches with a chrome access key or some content access
|
||||
* keys.
|
||||
* If the event may match chrome access keys, this handles the access key
|
||||
* synchronously (if there are nested ESMs, their HandleAccessKey() are
|
||||
* also called recursively).
|
||||
* If the event may match content access keys and focused target is a remote
|
||||
* process, this does nothing for the content because when this is called,
|
||||
* it should already have been handled in the remote process.
|
||||
* If the event may match content access keys and focused target is not in
|
||||
* remote process but there are some remote children, this will post
|
||||
* HandleAccessKey messages to all remote children.
|
||||
*
|
||||
* @return true if there is accesskey which aEvent and
|
||||
* aAccessCharCodes match with. Otherwise, false.
|
||||
* I.e., when this returns true, a target is executed
|
||||
* or focused.
|
||||
* Note that even if this returns false, a target in
|
||||
* remote process may be executed or focused
|
||||
* asynchronously.
|
||||
*/
|
||||
bool HandleAccessKey(WidgetKeyboardEvent* aEvent,
|
||||
nsPresContext* aPresContext,
|
||||
nsTArray<uint32_t>& aAccessCharCodes,
|
||||
int32_t aModifierMask,
|
||||
bool aMatchesContentAccessKey)
|
||||
nsTArray<uint32_t>& aAccessCharCodes)
|
||||
{
|
||||
return HandleAccessKey(aEvent, aPresContext, aAccessCharCodes,
|
||||
aMatchesContentAccessKey, nullptr,
|
||||
eAccessKeyProcessingNormal, aModifierMask);
|
||||
return WalkESMTreeToHandleAccessKey(aEvent, aPresContext, aAccessCharCodes,
|
||||
nullptr, eAccessKeyProcessingNormal,
|
||||
true);
|
||||
}
|
||||
|
||||
/**
|
||||
* CheckIfEventMatchesAccessKey() looks for access key which matches with
|
||||
* aEvent in the process but won't execute it.
|
||||
*
|
||||
* @return true if there is accesskey which aEvent matches with
|
||||
* in this process. Otherwise, false.
|
||||
*/
|
||||
bool CheckIfEventMatchesAccessKey(WidgetKeyboardEvent* aEvent,
|
||||
nsPresContext* aPresContext);
|
||||
|
||||
nsresult SetCursor(int32_t aCursor, imgIContainer* aContainer,
|
||||
bool aHaveHotspot, float aHotspotX, float aHotspotY,
|
||||
nsIWidget* aWidget, bool aLockCursor);
|
||||
|
@ -316,8 +346,6 @@ protected:
|
|||
public:
|
||||
static bool KeyCausesActivation() { return sKeyCausesActivation; }
|
||||
static bool ClickHoldContextMenu() { return sClickHoldContextMenu; }
|
||||
static int32_t ChromeAccessModifierMask();
|
||||
static int32_t ContentAccessModifierMask();
|
||||
|
||||
static void Init();
|
||||
static void OnChange(const char* aPrefName, void*);
|
||||
|
@ -326,19 +354,10 @@ protected:
|
|||
private:
|
||||
static bool sKeyCausesActivation;
|
||||
static bool sClickHoldContextMenu;
|
||||
static int32_t sGenericAccessModifierKey;
|
||||
static int32_t sChromeAccessModifierMask;
|
||||
static int32_t sContentAccessModifierMask;
|
||||
|
||||
static int32_t GetAccessModifierMask(int32_t aItemType);
|
||||
};
|
||||
|
||||
/**
|
||||
* Get appropriate access modifier mask for the aDocShell. Returns -1 if
|
||||
* access key isn't available.
|
||||
*/
|
||||
static int32_t GetAccessModifierMaskFor(nsISupports* aDocShell);
|
||||
|
||||
/*
|
||||
* If aTargetFrame's widget has a cached cursor value, resets the cursor
|
||||
* such that the next call to SetCursor on the widget will force an update
|
||||
|
@ -431,7 +450,7 @@ protected:
|
|||
void FlushPendingEvents(nsPresContext* aPresContext);
|
||||
|
||||
/**
|
||||
* The phases of HandleAccessKey processing. See below.
|
||||
* The phases of WalkESMTreeToHandleAccessKey processing. See below.
|
||||
*/
|
||||
typedef enum {
|
||||
eAccessKeyProcessingNormal = 0,
|
||||
|
@ -440,35 +459,55 @@ protected:
|
|||
} ProcessingAccessKeyState;
|
||||
|
||||
/**
|
||||
* Access key handling. If there is registered content for the accesskey
|
||||
* given by the key event and modifier mask then call
|
||||
* content.PerformAccesskey(), otherwise call HandleAccessKey() recursively,
|
||||
* on descendant docshells first, then on the ancestor (with |aBubbledFrom|
|
||||
* set to the docshell associated with |this|), until something matches.
|
||||
* Walk EMS to look for access key and execute found access key when aExecute
|
||||
* is true.
|
||||
* If there is registered content for the accesskey given by the key event
|
||||
* and modifier mask then call content.PerformAccesskey(), otherwise call
|
||||
* WalkESMTreeToHandleAccessKey() recursively, on descendant docshells first,
|
||||
* then on the ancestor (with |aBubbledFrom| set to the docshell associated
|
||||
* with |this|), until something matches.
|
||||
*
|
||||
* @param aEvent the keyboard event triggering the acccess key
|
||||
* @param aPresContext the presentation context
|
||||
* @param aAccessCharCodes list of charcode candidates
|
||||
* @param aMatchesContentAccessKey true if the content accesskey modifier is pressed
|
||||
* @param aBubbledFrom is used by an ancestor to avoid calling HandleAccessKey()
|
||||
* on the child the call originally came from, i.e. this is the child
|
||||
* that recursively called us in its Up phase. The initial caller
|
||||
* passes |nullptr| here. This is to avoid an infinite loop.
|
||||
* @param aBubbledFrom is used by an ancestor to avoid calling
|
||||
* WalkESMTreeToHandleAccessKey() on the child the call originally
|
||||
* came from, i.e. this is the child that recursively called us in
|
||||
* its Up phase. The initial caller passes |nullptr| here. This is to
|
||||
* avoid an infinite loop.
|
||||
* @param aAccessKeyState Normal, Down or Up processing phase (see enums
|
||||
* above). The initial event receiver uses 'normal', then 'down' when
|
||||
* processing children and Up when recursively calling its ancestor.
|
||||
* @param aModifierMask modifier mask for the key event
|
||||
* @param aExecute is true, execute an accesskey if it's found. Otherwise,
|
||||
* found accesskey won't be executed.
|
||||
*
|
||||
* @return true if there is a target which aEvent and
|
||||
* aAccessCharCodes match with in this process.
|
||||
* Otherwise, false. I.e., when this returns true and
|
||||
* aExecute is true, a target is executed or focused.
|
||||
* Note that even if this returns false, a target in
|
||||
* remote process may be executed or focused
|
||||
* asynchronously.
|
||||
*/
|
||||
bool HandleAccessKey(WidgetKeyboardEvent* aEvent,
|
||||
nsPresContext* aPresContext,
|
||||
nsTArray<uint32_t>& aAccessCharCodes,
|
||||
bool aMatchesContentAccessKey,
|
||||
nsIDocShellTreeItem* aBubbledFrom,
|
||||
ProcessingAccessKeyState aAccessKeyState,
|
||||
int32_t aModifierMask);
|
||||
bool WalkESMTreeToHandleAccessKey(WidgetKeyboardEvent* aEvent,
|
||||
nsPresContext* aPresContext,
|
||||
nsTArray<uint32_t>& aAccessCharCodes,
|
||||
nsIDocShellTreeItem* aBubbledFrom,
|
||||
ProcessingAccessKeyState aAccessKeyState,
|
||||
bool aExecute);
|
||||
|
||||
bool ExecuteAccessKey(nsTArray<uint32_t>& aAccessCharCodes,
|
||||
bool aIsTrustedEvent);
|
||||
/**
|
||||
* Look for access key and execute found access key if aExecute is true in
|
||||
* the instance.
|
||||
*
|
||||
* @return true if there is a target which matches with
|
||||
* aAccessCharCodes and aIsTrustedEvent. Otherwise,
|
||||
* false. I.e., when this returns true and aExecute
|
||||
* is true, a target is executed or focused.
|
||||
*/
|
||||
bool LookForAccessKeyAndExecute(nsTArray<uint32_t>& aAccessCharCodes,
|
||||
bool aIsTrustedEvent,
|
||||
bool aExecute);
|
||||
|
||||
//---------------------------------------------
|
||||
// DocShell Focus Traversal Methods
|
||||
|
|
|
@ -23,6 +23,7 @@
|
|||
|
||||
#include "nsITimer.h"
|
||||
|
||||
#include "FrameStatistics.h"
|
||||
#include "MediaError.h"
|
||||
#include "MediaDecoder.h"
|
||||
#include "mozilla/Preferences.h"
|
||||
|
|
|
@ -806,10 +806,9 @@ child:
|
|||
*
|
||||
* @param event keyboard event
|
||||
* @param isTrusted true if triggered by a trusted key event
|
||||
* @param modifierMask indicates which accesskey modifiers are pressed
|
||||
*/
|
||||
async HandleAccessKey(WidgetKeyboardEvent event,
|
||||
uint32_t[] charCodes, int32_t modifierMask);
|
||||
uint32_t[] charCodes);
|
||||
|
||||
/**
|
||||
* Tells the root child docShell whether or not to use
|
||||
|
|
|
@ -1599,7 +1599,7 @@ TabChild::RecvRealMouseButtonEvent(const WidgetMouseEvent& aEvent,
|
|||
localEvent.mWidget = mPuppetWidget;
|
||||
APZCCallbackHelper::ApplyCallbackTransform(localEvent, aGuid,
|
||||
mPuppetWidget->GetDefaultScale());
|
||||
APZCCallbackHelper::DispatchWidgetEvent(localEvent);
|
||||
DispatchWidgetEventViaAPZ(localEvent);
|
||||
|
||||
if (aInputBlockId && aEvent.mFlags.mHandledByAPZ) {
|
||||
mAPZEventState->ProcessMouseEvent(aEvent, aGuid, aInputBlockId);
|
||||
|
@ -1648,6 +1648,13 @@ TabChild::MaybeCoalesceWheelEvent(const WidgetWheelEvent& aEvent,
|
|||
return false;
|
||||
}
|
||||
|
||||
nsEventStatus
|
||||
TabChild::DispatchWidgetEventViaAPZ(WidgetGUIEvent& aEvent)
|
||||
{
|
||||
aEvent.ResetWaitingReplyFromRemoteProcessState();
|
||||
return APZCCallbackHelper::DispatchWidgetEvent(aEvent);
|
||||
}
|
||||
|
||||
void
|
||||
TabChild::MaybeDispatchCoalescedWheelEvent()
|
||||
{
|
||||
|
@ -1678,7 +1685,7 @@ TabChild::DispatchWheelEvent(const WidgetWheelEvent& aEvent,
|
|||
localEvent.mWidget = mPuppetWidget;
|
||||
APZCCallbackHelper::ApplyCallbackTransform(localEvent, aGuid,
|
||||
mPuppetWidget->GetDefaultScale());
|
||||
APZCCallbackHelper::DispatchWidgetEvent(localEvent);
|
||||
DispatchWidgetEventViaAPZ(localEvent);
|
||||
|
||||
if (localEvent.mCanTriggerSwipe) {
|
||||
SendRespondStartSwipeEvent(aInputBlockId, localEvent.TriggersSwipe());
|
||||
|
@ -1745,7 +1752,7 @@ TabChild::RecvRealTouchEvent(const WidgetTouchEvent& aEvent,
|
|||
}
|
||||
|
||||
// Dispatch event to content (potentially a long-running operation)
|
||||
nsEventStatus status = APZCCallbackHelper::DispatchWidgetEvent(localEvent);
|
||||
nsEventStatus status = DispatchWidgetEventViaAPZ(localEvent);
|
||||
|
||||
if (!AsyncPanZoomEnabled()) {
|
||||
// We shouldn't have any e10s platforms that have touch events enabled
|
||||
|
@ -1805,7 +1812,7 @@ TabChild::RecvRealDragEvent(const WidgetDragEvent& aEvent,
|
|||
}
|
||||
}
|
||||
|
||||
APZCCallbackHelper::DispatchWidgetEvent(localEvent);
|
||||
DispatchWidgetEventViaAPZ(localEvent);
|
||||
return IPC_OK();
|
||||
}
|
||||
|
||||
|
@ -1814,7 +1821,7 @@ TabChild::RecvPluginEvent(const WidgetPluginEvent& aEvent)
|
|||
{
|
||||
WidgetPluginEvent localEvent(aEvent);
|
||||
localEvent.mWidget = mPuppetWidget;
|
||||
nsEventStatus status = APZCCallbackHelper::DispatchWidgetEvent(localEvent);
|
||||
nsEventStatus status = DispatchWidgetEventViaAPZ(localEvent);
|
||||
if (status != nsEventStatus_eConsumeNoDefault) {
|
||||
// If not consumed, we should call default action
|
||||
SendDefaultProcOfPluginEvent(aEvent);
|
||||
|
@ -1916,7 +1923,7 @@ TabChild::RecvRealKeyEvent(const WidgetKeyboardEvent& aEvent)
|
|||
WidgetKeyboardEvent localEvent(aEvent);
|
||||
localEvent.mWidget = mPuppetWidget;
|
||||
localEvent.mUniqueId = aEvent.mUniqueId;
|
||||
nsEventStatus status = APZCCallbackHelper::DispatchWidgetEvent(localEvent);
|
||||
nsEventStatus status = DispatchWidgetEventViaAPZ(localEvent);
|
||||
|
||||
// Update the end time of the possible repeated event so that we can skip
|
||||
// some incoming events in case event handling took long time.
|
||||
|
@ -1931,16 +1938,21 @@ TabChild::RecvRealKeyEvent(const WidgetKeyboardEvent& aEvent)
|
|||
}
|
||||
|
||||
// If a response is desired from the content process, resend the key event.
|
||||
// If mAccessKeyForwardedToChild is set, then don't resend the key event yet
|
||||
// as RecvHandleAccessKey will do this.
|
||||
if (localEvent.WantReplyFromContentProcess()) {
|
||||
if (aEvent.WantReplyFromContentProcess()) {
|
||||
// If the event's default isn't prevented but the status is no default,
|
||||
// That means that the event was consumed by EventStateManager or something
|
||||
// which is not a usual event handler. In such case, prevent its default
|
||||
// as a default handler. For example, when an eKeyPress event matches
|
||||
// with a content accesskey, and it's executed, peventDefault() of the
|
||||
// event won't be called but the status is set to "no default". Then,
|
||||
// the event shouldn't be handled by nsMenuBarListener in the main process.
|
||||
if (!localEvent.DefaultPrevented() &&
|
||||
status == nsEventStatus_eConsumeNoDefault) {
|
||||
localEvent.PreventDefault();
|
||||
}
|
||||
SendReplyKeyEvent(localEvent);
|
||||
}
|
||||
|
||||
if (localEvent.mAccessKeyForwardedToChild) {
|
||||
SendAccessKeyNotHandled(localEvent);
|
||||
}
|
||||
|
||||
return IPC_OK();
|
||||
}
|
||||
|
||||
|
@ -1962,7 +1974,7 @@ TabChild::RecvCompositionEvent(const WidgetCompositionEvent& aEvent)
|
|||
{
|
||||
WidgetCompositionEvent localEvent(aEvent);
|
||||
localEvent.mWidget = mPuppetWidget;
|
||||
APZCCallbackHelper::DispatchWidgetEvent(localEvent);
|
||||
DispatchWidgetEventViaAPZ(localEvent);
|
||||
Unused << SendOnEventNeedingAckHandled(aEvent.mMessage);
|
||||
return IPC_OK();
|
||||
}
|
||||
|
@ -1972,7 +1984,7 @@ TabChild::RecvSelectionEvent(const WidgetSelectionEvent& aEvent)
|
|||
{
|
||||
WidgetSelectionEvent localEvent(aEvent);
|
||||
localEvent.mWidget = mPuppetWidget;
|
||||
APZCCallbackHelper::DispatchWidgetEvent(localEvent);
|
||||
DispatchWidgetEventViaAPZ(localEvent);
|
||||
Unused << SendOnEventNeedingAckHandled(aEvent.mMessage);
|
||||
return IPC_OK();
|
||||
}
|
||||
|
@ -2244,8 +2256,7 @@ TabChild::RecvSwappedWithOtherRemoteLoader(const IPCTabContext& aContext)
|
|||
|
||||
mozilla::ipc::IPCResult
|
||||
TabChild::RecvHandleAccessKey(const WidgetKeyboardEvent& aEvent,
|
||||
nsTArray<uint32_t>&& aCharCodes,
|
||||
const int32_t& aModifierMask)
|
||||
nsTArray<uint32_t>&& aCharCodes)
|
||||
{
|
||||
nsCOMPtr<nsIDocument> document(GetDocument());
|
||||
nsCOMPtr<nsIPresShell> presShell = document->GetShell();
|
||||
|
@ -2254,8 +2265,7 @@ TabChild::RecvHandleAccessKey(const WidgetKeyboardEvent& aEvent,
|
|||
if (pc) {
|
||||
if (!pc->EventStateManager()->
|
||||
HandleAccessKey(&(const_cast<WidgetKeyboardEvent&>(aEvent)),
|
||||
pc, aCharCodes,
|
||||
aModifierMask, true)) {
|
||||
pc, aCharCodes)) {
|
||||
// If no accesskey was found, inform the parent so that accesskeys on
|
||||
// menus can be handled.
|
||||
WidgetKeyboardEvent localEvent(aEvent);
|
||||
|
|
|
@ -595,9 +595,9 @@ public:
|
|||
virtual mozilla::ipc::IPCResult
|
||||
RecvThemeChanged(nsTArray<LookAndFeelInt>&& aLookAndFeelIntCache) override;
|
||||
|
||||
virtual mozilla::ipc::IPCResult RecvHandleAccessKey(const WidgetKeyboardEvent& aEvent,
|
||||
nsTArray<uint32_t>&& aCharCodes,
|
||||
const int32_t& aModifierMask) override;
|
||||
virtual mozilla::ipc::IPCResult
|
||||
RecvHandleAccessKey(const WidgetKeyboardEvent& aEvent,
|
||||
nsTArray<uint32_t>&& aCharCodes) override;
|
||||
|
||||
virtual mozilla::ipc::IPCResult RecvSetUseGlobalHistory(const bool& aUse) override;
|
||||
|
||||
|
@ -802,6 +802,11 @@ private:
|
|||
|
||||
void MaybeDispatchCoalescedWheelEvent();
|
||||
|
||||
/**
|
||||
* Dispatch aEvent on aEvent.mWidget.
|
||||
*/
|
||||
nsEventStatus DispatchWidgetEventViaAPZ(WidgetGUIEvent& aEvent);
|
||||
|
||||
void DispatchWheelEvent(const WidgetWheelEvent& aEvent,
|
||||
const ScrollableLayerGuid& aGuid,
|
||||
const uint64_t& aInputBlockId);
|
||||
|
|
|
@ -817,15 +817,14 @@ TabParent::ThemeChanged()
|
|||
|
||||
void
|
||||
TabParent::HandleAccessKey(const WidgetKeyboardEvent& aEvent,
|
||||
nsTArray<uint32_t>& aCharCodes,
|
||||
const int32_t& aModifierMask)
|
||||
nsTArray<uint32_t>& aCharCodes)
|
||||
{
|
||||
if (!mIsDestroyed) {
|
||||
// Note that we don't need to mark aEvent is posted to a remote process
|
||||
// because the event may be dispatched to it as normal keyboard event.
|
||||
// Therefore, we should use local copy to send it.
|
||||
WidgetKeyboardEvent localEvent(aEvent);
|
||||
Unused << SendHandleAccessKey(localEvent, aCharCodes, aModifierMask);
|
||||
Unused << SendHandleAccessKey(localEvent, aCharCodes);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2047,7 +2046,23 @@ TabParent::RecvReplyKeyEvent(const WidgetKeyboardEvent& aEvent)
|
|||
AutoHandlingUserInputStatePusher userInpStatePusher(localEvent.IsTrusted(),
|
||||
&localEvent, doc);
|
||||
|
||||
EventDispatcher::Dispatch(mFrameElement, presContext, &localEvent);
|
||||
nsEventStatus status = nsEventStatus_eIgnore;
|
||||
|
||||
// Handle access key in this process before dispatching reply event because
|
||||
// ESM handles it before dispatching the event to the DOM tree.
|
||||
if (localEvent.mMessage == eKeyPress &&
|
||||
(localEvent.ModifiersMatchWithAccessKey(AccessKeyType::eChrome) ||
|
||||
localEvent.ModifiersMatchWithAccessKey(AccessKeyType::eContent))) {
|
||||
RefPtr<EventStateManager> esm = presContext->EventStateManager();
|
||||
AutoTArray<uint32_t, 10> accessCharCodes;
|
||||
localEvent.GetAccessKeyCandidates(accessCharCodes);
|
||||
if (esm->HandleAccessKey(&localEvent, presContext, accessCharCodes)) {
|
||||
status = nsEventStatus_eConsumeNoDefault;
|
||||
}
|
||||
}
|
||||
|
||||
EventDispatcher::Dispatch(mFrameElement, presContext, &localEvent, nullptr,
|
||||
&status);
|
||||
|
||||
if (!localEvent.DefaultPrevented() &&
|
||||
!localEvent.mFlags.mIsSynthesizedForTests) {
|
||||
|
@ -2066,10 +2081,16 @@ TabParent::RecvAccessKeyNotHandled(const WidgetKeyboardEvent& aEvent)
|
|||
{
|
||||
NS_ENSURE_TRUE(mFrameElement, IPC_OK());
|
||||
|
||||
// This is called only when this process had focus and HandleAccessKey
|
||||
// message was posted to all remote process and each remote process didn't
|
||||
// execute any content access keys.
|
||||
// XXX If there were two or more remote processes, this may be called
|
||||
// twice or more for a keyboard event, that must be a bug. But how to
|
||||
// detect if received event has already been handled?
|
||||
|
||||
WidgetKeyboardEvent localEvent(aEvent);
|
||||
localEvent.MarkAsHandledInRemoteProcess();
|
||||
localEvent.mMessage = eAccessKeyNotFound;
|
||||
localEvent.mAccessKeyForwardedToChild = false;
|
||||
|
||||
// Here we convert the WidgetEvent that we received to an nsIDOMEvent
|
||||
// to be able to dispatch it to the <browser> element as the target element.
|
||||
|
|
|
@ -361,8 +361,7 @@ public:
|
|||
void ThemeChanged();
|
||||
|
||||
void HandleAccessKey(const WidgetKeyboardEvent& aEvent,
|
||||
nsTArray<uint32_t>& aCharCodes,
|
||||
const int32_t& aModifierMask);
|
||||
nsTArray<uint32_t>& aCharCodes);
|
||||
|
||||
void Activate();
|
||||
|
||||
|
|
|
@ -25,8 +25,9 @@ ADTSDecoder::Clone(MediaDecoderInit& aInit)
|
|||
MediaDecoderStateMachine*
|
||||
ADTSDecoder::CreateStateMachine()
|
||||
{
|
||||
MediaFormatReaderInit init(this);
|
||||
MediaFormatReaderInit init;
|
||||
init.mCrashHelper = GetOwner()->CreateGMPCrashHelper();
|
||||
init.mFrameStats = mFrameStats;
|
||||
mReader = new MediaFormatReader(init, new ADTSDemuxer(mResource));
|
||||
return new MediaDecoderStateMachine(this, mReader);
|
||||
}
|
||||
|
|
|
@ -1,95 +0,0 @@
|
|||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim:set ts=2 sw=2 sts=2 et cindent: */
|
||||
/* 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/. */
|
||||
|
||||
#ifndef AbstractMediaDecoder_h_
|
||||
#define AbstractMediaDecoder_h_
|
||||
|
||||
#include "mozilla/Attributes.h"
|
||||
#include "mozilla/StateMirroring.h"
|
||||
|
||||
#include "FrameStatistics.h"
|
||||
#include "MediaEventSource.h"
|
||||
#include "MediaInfo.h"
|
||||
#include "nsISupports.h"
|
||||
#include "nsDataHashtable.h"
|
||||
#include "nsThreadUtils.h"
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
namespace layers {
|
||||
class ImageContainer;
|
||||
class KnowsCompositor;
|
||||
} // namespace layers
|
||||
|
||||
class AbstractThread;
|
||||
class MediaResource;
|
||||
class ReentrantMonitor;
|
||||
class VideoFrameContainer;
|
||||
class MediaDecoderOwner;
|
||||
class CDMProxy;
|
||||
class GMPCrashHelper;
|
||||
|
||||
/**
|
||||
* The AbstractMediaDecoder class describes the public interface for a media decoder
|
||||
* and is used by the MediaReader classes.
|
||||
*/
|
||||
class AbstractMediaDecoder : public nsIObserver
|
||||
{
|
||||
public:
|
||||
// Increments the parsed, decoded and dropped frame counters by the passed in
|
||||
// counts.
|
||||
// Can be called on any thread.
|
||||
virtual void NotifyDecodedFrames(const FrameStatisticsData& aStats) = 0;
|
||||
|
||||
// Return an abstract thread on which to run main thread runnables.
|
||||
virtual AbstractThread* AbstractMainThread() const = 0;
|
||||
virtual VideoFrameContainer* GetVideoFrameContainer() = 0;
|
||||
virtual mozilla::layers::ImageContainer* GetImageContainer() = 0;
|
||||
|
||||
// Returns the owner of this decoder or null when the decoder is shutting
|
||||
// down. The owner should only be used on the main thread.
|
||||
virtual MediaDecoderOwner* GetOwner() const = 0;
|
||||
|
||||
// Set by Reader if the current audio track can be offloaded
|
||||
virtual void SetPlatformCanOffloadAudio(bool aCanOffloadAudio) { }
|
||||
|
||||
// Stack based class to assist in notifying the frame statistics of
|
||||
// parsed and decoded frames. Use inside video demux & decode functions
|
||||
// to ensure all parsed and decoded frames are reported on all return paths.
|
||||
class AutoNotifyDecoded
|
||||
{
|
||||
public:
|
||||
explicit AutoNotifyDecoded(AbstractMediaDecoder* aDecoder)
|
||||
: mDecoder(aDecoder)
|
||||
{
|
||||
}
|
||||
~AutoNotifyDecoded()
|
||||
{
|
||||
if (mDecoder) {
|
||||
mDecoder->NotifyDecodedFrames(mStats);
|
||||
}
|
||||
}
|
||||
|
||||
FrameStatisticsData mStats;
|
||||
|
||||
private:
|
||||
AbstractMediaDecoder* mDecoder;
|
||||
};
|
||||
|
||||
// Classes directly inheriting from AbstractMediaDecoder do not support
|
||||
// Observe and it should never be called directly.
|
||||
NS_IMETHOD Observe(nsISupports* aSubject, const char* aTopic,
|
||||
const char16_t* aData) override
|
||||
{
|
||||
MOZ_CRASH("Forbidden method");
|
||||
return NS_OK;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace mozilla
|
||||
|
||||
#endif
|
||||
|
|
@ -296,11 +296,6 @@ DecoderTraits::CreateReader(const MediaContainerType& aType,
|
|||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
MediaFormatReader* decoderReader = nullptr;
|
||||
|
||||
if (!aInit.mDecoder) {
|
||||
return decoderReader;
|
||||
}
|
||||
|
||||
MediaResource* resource = aInit.mResource;
|
||||
|
||||
#ifdef MOZ_FMP4
|
||||
|
|
|
@ -14,7 +14,6 @@ class nsACString;
|
|||
|
||||
namespace mozilla {
|
||||
|
||||
class AbstractMediaDecoder;
|
||||
class ChannelMediaDecoder;
|
||||
class DecoderDoctorDiagnostics;
|
||||
class MediaContainerType;
|
||||
|
|
|
@ -129,6 +129,29 @@ public:
|
|||
++mFrameStatisticsData.mPresentedFrames;
|
||||
}
|
||||
|
||||
// Stack based class to assist in notifying the frame statistics of
|
||||
// parsed and decoded frames. Use inside video demux & decode functions
|
||||
// to ensure all parsed and decoded frames are reported on all return paths.
|
||||
class AutoNotifyDecoded
|
||||
{
|
||||
public:
|
||||
explicit AutoNotifyDecoded(FrameStatistics* aFrameStats)
|
||||
: mFrameStats(aFrameStats)
|
||||
{
|
||||
}
|
||||
~AutoNotifyDecoded()
|
||||
{
|
||||
if (mFrameStats) {
|
||||
mFrameStats->NotifyDecodedFrames(mStats);
|
||||
}
|
||||
}
|
||||
|
||||
FrameStatisticsData mStats;
|
||||
|
||||
private:
|
||||
FrameStatistics* mFrameStats;
|
||||
};
|
||||
|
||||
private:
|
||||
~FrameStatistics() {}
|
||||
|
||||
|
|
|
@ -284,8 +284,6 @@ MediaDecoder::InitStatics()
|
|||
|
||||
NS_IMPL_ISUPPORTS(MediaMemoryTracker, nsIMemoryReporter)
|
||||
|
||||
NS_IMPL_ISUPPORTS0(MediaDecoder)
|
||||
|
||||
void
|
||||
MediaDecoder::NotifyOwnerActivityChanged(bool aIsDocumentVisible,
|
||||
Visibility aElementVisibility,
|
||||
|
|
|
@ -7,7 +7,6 @@
|
|||
#if !defined(MediaDecoder_h_)
|
||||
#define MediaDecoder_h_
|
||||
|
||||
#include "AbstractMediaDecoder.h"
|
||||
#include "DecoderDoctorDiagnostics.h"
|
||||
#include "MediaDecoderOwner.h"
|
||||
#include "MediaEventSource.h"
|
||||
|
@ -36,6 +35,7 @@ class nsIPrincipal;
|
|||
namespace mozilla {
|
||||
|
||||
class AbstractThread;
|
||||
class FrameStatistics;
|
||||
class VideoFrameContainer;
|
||||
class MediaFormatReader;
|
||||
class MediaDecoderStateMachine;
|
||||
|
@ -83,14 +83,14 @@ struct MOZ_STACK_CLASS MediaDecoderInit
|
|||
}
|
||||
};
|
||||
|
||||
class MediaDecoder : public AbstractMediaDecoder
|
||||
class MediaDecoder
|
||||
{
|
||||
public:
|
||||
typedef MozPromise<bool /* aIgnored */, bool /* aIgnored */,
|
||||
/* IsExclusive = */ true>
|
||||
SeekPromise;
|
||||
|
||||
NS_DECL_THREADSAFE_ISUPPORTS
|
||||
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(MediaDecoder)
|
||||
|
||||
// Enumeration for the valid play states (see mPlayState)
|
||||
enum PlayState
|
||||
|
@ -295,11 +295,12 @@ private:
|
|||
|
||||
virtual void AddSizeOfResources(ResourceSizes* aSizes);
|
||||
|
||||
VideoFrameContainer* GetVideoFrameContainer() final override
|
||||
VideoFrameContainer* GetVideoFrameContainer()
|
||||
{
|
||||
return mVideoFrameContainer;
|
||||
}
|
||||
layers::ImageContainer* GetImageContainer() override;
|
||||
|
||||
layers::ImageContainer* GetImageContainer();
|
||||
|
||||
// Fire timeupdate events if needed according to the time constraints
|
||||
// outlined in the specification.
|
||||
|
@ -385,9 +386,9 @@ private:
|
|||
// Indicate whether the media is same-origin with the element.
|
||||
void UpdateSameOriginStatus(bool aSameOrigin);
|
||||
|
||||
MediaDecoderOwner* GetOwner() const override;
|
||||
MediaDecoderOwner* GetOwner() const;
|
||||
|
||||
AbstractThread* AbstractMainThread() const final override
|
||||
AbstractThread* AbstractMainThread() const
|
||||
{
|
||||
return mAbstractMainThread;
|
||||
}
|
||||
|
@ -422,13 +423,6 @@ private:
|
|||
// Return the frame decode/paint related statistics.
|
||||
FrameStatistics& GetFrameStatistics() { return *mFrameStats; }
|
||||
|
||||
// Increments the parsed and decoded frame counters by the passed in counts.
|
||||
// Can be called on any thread.
|
||||
virtual void NotifyDecodedFrames(const FrameStatisticsData& aStats) override
|
||||
{
|
||||
GetFrameStatistics().NotifyDecodedFrames(aStats);
|
||||
}
|
||||
|
||||
void UpdateReadyState()
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
|
|
|
@ -5,7 +5,8 @@
|
|||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
#ifndef MediaDecoderOwner_h_
|
||||
#define MediaDecoderOwner_h_
|
||||
#include "AbstractMediaDecoder.h"
|
||||
|
||||
#include "MediaInfo.h"
|
||||
#include "nsAutoPtr.h"
|
||||
|
||||
namespace mozilla {
|
||||
|
|
|
@ -1957,15 +1957,12 @@ public:
|
|||
|
||||
void Enter()
|
||||
{
|
||||
// TODO : use more approriate way to decide whether need to release
|
||||
// resource in bug1367983.
|
||||
#ifndef MOZ_WIDGET_ANDROID
|
||||
if (!mMaster->mLooping) {
|
||||
// We've decoded all samples.
|
||||
// We don't need decoders anymore if not looping.
|
||||
Reader()->ReleaseResources();
|
||||
}
|
||||
#endif
|
||||
|
||||
bool hasNextFrame = (!mMaster->HasAudio() || !mMaster->mAudioCompleted)
|
||||
&& (!mMaster->HasVideo() || !mMaster->mVideoCompleted);
|
||||
|
||||
|
|
|
@ -1113,10 +1113,10 @@ MediaFormatReader::MediaFormatReader(MediaFormatReaderInit& aInit,
|
|||
, mCrashHelper(aInit.mCrashHelper)
|
||||
, mDecoderFactory(new DecoderFactory(this))
|
||||
, mShutdownPromisePool(new ShutdownPromisePool())
|
||||
, mDecoder(aInit.mDecoder)
|
||||
, mBuffered(mTaskQueue,
|
||||
TimeIntervals(),
|
||||
"MediaFormatReader::mBuffered (Canonical)")
|
||||
, mFrameStats(aInit.mFrameStats)
|
||||
{
|
||||
MOZ_ASSERT(aDemuxer);
|
||||
MOZ_COUNT_CTOR(MediaFormatReader);
|
||||
|
@ -1212,7 +1212,6 @@ MediaFormatReader::TearDownDecoders()
|
|||
|
||||
ReleaseResources();
|
||||
mBuffered.DisconnectAll();
|
||||
mDecoder = nullptr;
|
||||
return mTaskQueue->BeginShutdown();
|
||||
}
|
||||
|
||||
|
@ -1924,7 +1923,7 @@ MediaFormatReader::DecodeDemuxedSamples(TrackType aTrack,
|
|||
|
||||
void
|
||||
MediaFormatReader::HandleDemuxedSamples(
|
||||
TrackType aTrack, AbstractMediaDecoder::AutoNotifyDecoded& aA)
|
||||
TrackType aTrack, FrameStatistics::AutoNotifyDecoded& aA)
|
||||
{
|
||||
MOZ_ASSERT(OnTaskQueue());
|
||||
|
||||
|
@ -2150,7 +2149,7 @@ MediaFormatReader::Update(TrackType aTrack)
|
|||
|
||||
// Record number of frames decoded and parsed. Automatically update the
|
||||
// stats counters using the AutoNotifyDecoded stack-based class.
|
||||
AbstractMediaDecoder::AutoNotifyDecoded a(mDecoder);
|
||||
FrameStatistics::AutoNotifyDecoded a(mFrameStats);
|
||||
|
||||
// Drop any frames found prior our internal seek target.
|
||||
while (decoder.mTimeThreshold && decoder.mOutput.Length()) {
|
||||
|
@ -2520,8 +2519,8 @@ MediaFormatReader::DropDecodedSamples(TrackType aTrack)
|
|||
}
|
||||
decoder.mOutput.Clear();
|
||||
decoder.mSizeOfQueue -= lengthDecodedQueue;
|
||||
if (aTrack == TrackInfo::kVideoTrack && mDecoder) {
|
||||
mDecoder->NotifyDecodedFrames({ 0, 0, lengthDecodedQueue });
|
||||
if (aTrack == TrackInfo::kVideoTrack && mFrameStats) {
|
||||
mFrameStats->NotifyDecodedFrames({ 0, 0, lengthDecodedQueue });
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2552,16 +2551,16 @@ MediaFormatReader::VideoSkipReset(uint32_t aSkipped)
|
|||
// videoskip process and we know they would be late.
|
||||
DropDecodedSamples(TrackInfo::kVideoTrack);
|
||||
// Report the pending frames as dropped.
|
||||
if (mDecoder) {
|
||||
mDecoder->NotifyDecodedFrames({ 0, 0, SizeOfVideoQueueInFrames() });
|
||||
if (mFrameStats) {
|
||||
mFrameStats->NotifyDecodedFrames({ 0, 0, SizeOfVideoQueueInFrames() });
|
||||
}
|
||||
|
||||
// Cancel any pending demux request and pending demuxed samples.
|
||||
mVideo.mDemuxRequest.DisconnectIfExists();
|
||||
Reset(TrackType::kVideoTrack);
|
||||
|
||||
if (mDecoder) {
|
||||
mDecoder->NotifyDecodedFrames({ aSkipped, 0, aSkipped });
|
||||
if (mFrameStats) {
|
||||
mFrameStats->NotifyDecodedFrames({ aSkipped, 0, aSkipped });
|
||||
}
|
||||
|
||||
mVideo.mNumSamplesSkippedTotal += aSkipped;
|
||||
|
|
|
@ -9,9 +9,11 @@
|
|||
|
||||
#include "mozilla/Atomics.h"
|
||||
#include "mozilla/Maybe.h"
|
||||
#include "mozilla/StateMirroring.h"
|
||||
#include "mozilla/TaskQueue.h"
|
||||
#include "mozilla/Mutex.h"
|
||||
|
||||
#include "FrameStatistics.h"
|
||||
#include "MediaEventSource.h"
|
||||
#include "MediaDataDemuxer.h"
|
||||
#include "MediaMetadataManager.h"
|
||||
|
@ -22,7 +24,6 @@
|
|||
|
||||
namespace mozilla {
|
||||
|
||||
class AbstractMediaDecoder;
|
||||
class CDMProxy;
|
||||
class GMPCrashHelper;
|
||||
class MediaResource;
|
||||
|
@ -74,16 +75,11 @@ struct MetadataHolder
|
|||
|
||||
struct MOZ_STACK_CLASS MediaFormatReaderInit
|
||||
{
|
||||
AbstractMediaDecoder* const mDecoder;
|
||||
MediaResource* mResource = nullptr;
|
||||
VideoFrameContainer* mVideoFrameContainer = nullptr;
|
||||
FrameStatistics* mFrameStats = nullptr;
|
||||
already_AddRefed<layers::KnowsCompositor> mKnowsCompositor;
|
||||
already_AddRefed<GMPCrashHelper> mCrashHelper;
|
||||
|
||||
explicit MediaFormatReaderInit(AbstractMediaDecoder* aDecoder)
|
||||
: mDecoder(aDecoder)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
class MediaFormatReader final
|
||||
|
@ -280,7 +276,7 @@ private:
|
|||
void RequestDemuxSamples(TrackType aTrack);
|
||||
// Handle demuxed samples by the input behavior.
|
||||
void HandleDemuxedSamples(TrackType aTrack,
|
||||
AbstractMediaDecoder::AutoNotifyDecoded& aA);
|
||||
FrameStatistics::AutoNotifyDecoded& aA);
|
||||
// Decode any pending already demuxed samples.
|
||||
void DecodeDemuxedSamples(TrackType aTrack,
|
||||
MediaRawData* aSample);
|
||||
|
@ -745,9 +741,6 @@ private:
|
|||
void ShutdownDecoder(TrackType aTrack);
|
||||
RefPtr<ShutdownPromise> TearDownDecoders();
|
||||
|
||||
// Reference to the owning decoder object.
|
||||
AbstractMediaDecoder* mDecoder;
|
||||
|
||||
bool mShutdown = false;
|
||||
|
||||
// Buffered range.
|
||||
|
@ -767,6 +760,8 @@ private:
|
|||
MediaEventProducer<void> mOnWaitingForKey;
|
||||
|
||||
MediaEventProducer<MediaResult> mOnDecodeWarning;
|
||||
|
||||
RefPtr<FrameStatistics> mFrameStats;
|
||||
};
|
||||
|
||||
} // namespace mozilla
|
||||
|
|
|
@ -11,7 +11,6 @@
|
|||
#include "mozilla/LinkedList.h"
|
||||
|
||||
#include "nsAutoPtr.h"
|
||||
#include "AbstractMediaDecoder.h"
|
||||
#include "MediaEventSource.h"
|
||||
#include "TimeUnits.h"
|
||||
#include "VideoUtils.h"
|
||||
|
|
|
@ -26,8 +26,9 @@ FlacDecoder::Clone(MediaDecoderInit& aInit)
|
|||
MediaDecoderStateMachine*
|
||||
FlacDecoder::CreateStateMachine()
|
||||
{
|
||||
MediaFormatReaderInit init(this);
|
||||
MediaFormatReaderInit init;
|
||||
init.mCrashHelper = GetOwner()->CreateGMPCrashHelper();
|
||||
init.mFrameStats = mFrameStats;
|
||||
mReader = new MediaFormatReader(init, new FlacDemuxer(mResource));
|
||||
return new MediaDecoderStateMachine(this, mReader);
|
||||
}
|
||||
|
|
|
@ -32,10 +32,11 @@ MP4Decoder::MP4Decoder(MediaDecoderInit& aInit)
|
|||
|
||||
MediaDecoderStateMachine* MP4Decoder::CreateStateMachine()
|
||||
{
|
||||
MediaFormatReaderInit init(this);
|
||||
MediaFormatReaderInit init;
|
||||
init.mVideoFrameContainer = GetVideoFrameContainer();
|
||||
init.mKnowsCompositor = GetCompositor();
|
||||
init.mCrashHelper = GetOwner()->CreateGMPCrashHelper();
|
||||
init.mFrameStats = mFrameStats;
|
||||
mReader = new MediaFormatReader(init, new MP4Demuxer(mResource));
|
||||
return new MediaDecoderStateMachine(this, mReader);
|
||||
}
|
||||
|
|
|
@ -28,10 +28,11 @@ HLSDecoder::CreateStateMachine()
|
|||
MOZ_ASSERT(resource);
|
||||
auto resourceWrapper = static_cast<HLSResource*>(resource)->GetResourceWrapper();
|
||||
MOZ_ASSERT(resourceWrapper);
|
||||
MediaFormatReaderInit init(this);
|
||||
MediaFormatReaderInit init;
|
||||
init.mVideoFrameContainer = GetVideoFrameContainer();
|
||||
init.mKnowsCompositor = GetCompositor();
|
||||
init.mCrashHelper = GetOwner()->CreateGMPCrashHelper();
|
||||
init.mFrameStats = mFrameStats;
|
||||
mReader =
|
||||
new MediaFormatReader(init, new HLSDemuxer(resourceWrapper->GetPlayerId()));
|
||||
|
||||
|
|
|
@ -39,10 +39,11 @@ MediaSourceDecoder::CreateStateMachine()
|
|||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
mDemuxer = new MediaSourceDemuxer(AbstractMainThread());
|
||||
MediaFormatReaderInit init(this);
|
||||
MediaFormatReaderInit init;
|
||||
init.mVideoFrameContainer = GetVideoFrameContainer();
|
||||
init.mKnowsCompositor = GetCompositor();
|
||||
init.mCrashHelper = GetOwner()->CreateGMPCrashHelper();
|
||||
init.mFrameStats = mFrameStats;
|
||||
mReader = new MediaFormatReader(init, mDemuxer);
|
||||
return new MediaDecoderStateMachine(this, mReader);
|
||||
}
|
||||
|
|
|
@ -62,7 +62,7 @@ static Atomic<uint32_t> sStreamSourceID(0u);
|
|||
|
||||
class DispatchKeyNeededEvent : public Runnable {
|
||||
public:
|
||||
DispatchKeyNeededEvent(AbstractMediaDecoder* aDecoder,
|
||||
DispatchKeyNeededEvent(MediaSourceDecoder* aDecoder,
|
||||
const nsTArray<uint8_t>& aInitData,
|
||||
const nsString& aInitDataType)
|
||||
: Runnable("DispatchKeyNeededEvent")
|
||||
|
@ -82,7 +82,7 @@ public:
|
|||
return NS_OK;
|
||||
}
|
||||
private:
|
||||
RefPtr<AbstractMediaDecoder> mDecoder;
|
||||
RefPtr<MediaSourceDecoder> mDecoder;
|
||||
nsTArray<uint8_t> mInitData;
|
||||
nsString mInitDataType;
|
||||
};
|
||||
|
|
|
@ -84,7 +84,6 @@ XPIDL_SOURCES += [
|
|||
XPIDL_MODULE = 'dom_media'
|
||||
|
||||
EXPORTS += [
|
||||
'AbstractMediaDecoder.h',
|
||||
'ADTSDecoder.h',
|
||||
'ADTSDemuxer.h',
|
||||
'AudioBufferUtils.h',
|
||||
|
|
|
@ -26,8 +26,9 @@ MP3Decoder::Clone(MediaDecoderInit& aInit)
|
|||
|
||||
MediaDecoderStateMachine*
|
||||
MP3Decoder::CreateStateMachine() {
|
||||
MediaFormatReaderInit init(this);
|
||||
MediaFormatReaderInit init;
|
||||
init.mCrashHelper = GetOwner()->CreateGMPCrashHelper();
|
||||
init.mFrameStats = mFrameStats;
|
||||
mReader = new MediaFormatReader(init, new MP3Demuxer(mResource));
|
||||
return new MediaDecoderStateMachine(this, mReader);
|
||||
}
|
||||
|
|
|
@ -16,10 +16,11 @@ namespace mozilla {
|
|||
MediaDecoderStateMachine* OggDecoder::CreateStateMachine()
|
||||
{
|
||||
RefPtr<OggDemuxer> demuxer = new OggDemuxer(mResource);
|
||||
MediaFormatReaderInit init(this);
|
||||
MediaFormatReaderInit init;
|
||||
init.mVideoFrameContainer = GetVideoFrameContainer();
|
||||
init.mKnowsCompositor = GetCompositor();
|
||||
init.mCrashHelper = GetOwner()->CreateGMPCrashHelper();
|
||||
init.mFrameStats = mFrameStats;
|
||||
mReader = new MediaFormatReader(init, demuxer);
|
||||
demuxer->SetChainingEvents(&mReader->TimedMetadataProducer(),
|
||||
&mReader->MediaNotSeekableProducer());
|
||||
|
|
|
@ -6,7 +6,6 @@
|
|||
|
||||
#include "nsError.h"
|
||||
#include "MediaDecoderStateMachine.h"
|
||||
#include "AbstractMediaDecoder.h"
|
||||
#include "OggDemuxer.h"
|
||||
#include "OggCodecState.h"
|
||||
#include "mozilla/AbstractThread.h"
|
||||
|
|
|
@ -15,25 +15,26 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=489415
|
|||
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1018299">Test for MediaRecorder Principal Handling</a>
|
||||
</div>
|
||||
|
||||
<video id="v1" preload="auto"></video>
|
||||
<video id="v2" preload="auto"></video>
|
||||
<video id="v1" preload="metadata"></video>
|
||||
<video id="v2" preload="metadata"></video>
|
||||
|
||||
<pre id="test">
|
||||
<script type="text/javascript">
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
|
||||
var pushPrefs = (...p) => SpecialPowers.pushPrefEnv({set: p});
|
||||
var throwOutside = e => setTimeout(() => { throw e; });
|
||||
let throwOutside = e => setTimeout(() => { throw e; });
|
||||
|
||||
// Generate a random key. The first load with that key will return
|
||||
// data, the second and subsequent loads with that key will return a redirect
|
||||
// to a different origin ('localhost:8888' will be redirected to 'example.org',
|
||||
// and 'example.org' will be redirected to 'localhost:8888'). We rely on the
|
||||
// fact that Ogg will do a seek to the end of the resource, triggering a new
|
||||
// load with the same key which will return a same-origin resource.
|
||||
// and 'example.org' will be redirected to 'localhost:8888').
|
||||
// Loading data from two different origins should be detected by the media
|
||||
// cache and result in a null principal so that the MediaRecorder usages below
|
||||
// fail.
|
||||
// This test relies on that preloading the metadata then loading a sufficiently
|
||||
// long video will result in two separate requests to load the resource. This
|
||||
// ends up relying on the impl of MediaCache and friends and we should probably
|
||||
// replace this test with a more robust gtest or the like.
|
||||
let key = Math.floor(Math.random()*100000000);
|
||||
let interval;
|
||||
|
||||
|
@ -42,31 +43,26 @@ function testPrincipals(resource) {
|
|||
todo(false, "No types supported");
|
||||
return;
|
||||
}
|
||||
// Reduce cache size and cache-ahead to make HTMLMediaElement do partial requests.
|
||||
return pushPrefs(['media.cache_readahead_limit', 2],
|
||||
['media.cache_size', 192])
|
||||
.then(() => {
|
||||
// First test: Load file from same-origin first, then get redirected to
|
||||
// another origin before attempting to record stream.
|
||||
let video = document.getElementById("v1");
|
||||
video.src =
|
||||
"http://mochi.test:8888/tests/dom/media/test/dynamic_redirect.sjs?key=v1_" +
|
||||
key + "&res=" + resource.name;
|
||||
video.load();
|
||||
// To limit readahead, avoid racing with playback and "catching up" mode.
|
||||
return new Promise(resolve => video.oncanplaythrough = resolve).then(() => {
|
||||
video.play();
|
||||
interval = setInterval(() => info("video.currentTime = "+ video.currentTime), 1000);
|
||||
// First test: Load file from same-origin first, then get redirected to
|
||||
// another origin before attempting to record stream.
|
||||
let video = document.getElementById("v1");
|
||||
video.src =
|
||||
"http://mochi.test:8888/tests/dom/media/test/dynamic_redirect.sjs?key=v1_" +
|
||||
key + "&res=" + resource.name;
|
||||
video.load();
|
||||
// To limit readahead, avoid racing with playback and "catching up" mode.
|
||||
return new Promise(resolve => video.onloadeddata = resolve).then(() => {
|
||||
video.play();
|
||||
interval = setInterval(() => info("video.currentTime = "+ video.currentTime), 1000);
|
||||
|
||||
let msg = "mediaRecorder.start() must throw SecurityError";
|
||||
return new Promise(resolve => video.onplaying = resolve)
|
||||
.then(() => waitUntil(() => video.currentTime > resource.duration / 2))
|
||||
// Test failure of the next step only, so "catch-bypass" any errors above.
|
||||
.then(() => Promise.resolve()
|
||||
.then(() => new MediaRecorder(video.mozCaptureStreamUntilEnded()).start())
|
||||
.then(() => ok(false, msg), e => is(e.name, "SecurityError", msg)), 0)
|
||||
.then(() => clearInterval(interval));
|
||||
});
|
||||
let msg = "mediaRecorder.start() must throw SecurityError";
|
||||
return new Promise(resolve => video.onplaying = resolve)
|
||||
.then(() => waitUntil(() => video.currentTime > resource.duration / 5))
|
||||
// Test failure of the next step only, so "catch-bypass" any errors above.
|
||||
.then(() => Promise.resolve()
|
||||
.then(() => new MediaRecorder(video.mozCaptureStreamUntilEnded()).start())
|
||||
.then(() => ok(false, msg), e => is(e.name, "SecurityError", msg)), 0)
|
||||
.then(() => clearInterval(interval));
|
||||
})
|
||||
.then(() => {
|
||||
// Second test: Load file from same-origin first, but record ASAP, before
|
||||
|
@ -82,7 +78,7 @@ function testPrincipals(resource) {
|
|||
let msgNoThrow = "mediaRecorder.start() should not throw here";
|
||||
let msgSecErr = "mediaRecorder.onerror must fire SecurityError";
|
||||
let msgOnStop = "mediaRecorder.onstop must also have fired";
|
||||
return new Promise(resolve => video.onloadedmetadata = resolve).then(() => {
|
||||
return new Promise(resolve => video.onloadeddata = resolve).then(() => {
|
||||
rec = new MediaRecorder(video.mozCaptureStreamUntilEnded());
|
||||
rec.ondataavailable = e => data.push(e.data);
|
||||
rec.start();
|
||||
|
@ -101,15 +97,15 @@ function testPrincipals(resource) {
|
|||
});
|
||||
}
|
||||
|
||||
testPrincipals(getPlayableVideo(gSeekTests))
|
||||
testPrincipals({ name:"pixel_aspect_ratio.mp4", type:"video/mp4", duration:28 })
|
||||
.catch(e => throwOutside(e))
|
||||
.then(() => SimpleTest.finish())
|
||||
.catch(e => throwOutside(e));
|
||||
|
||||
var stop = stream => stream.getTracks().forEach(track => track.stop());
|
||||
var wait = ms => new Promise(resolve => setTimeout(resolve, ms));
|
||||
var waitUntil = f => new Promise(resolve => {
|
||||
var ival = setInterval(() => f() && resolve(clearInterval(ival)), 100);
|
||||
let stop = stream => stream.getTracks().forEach(track => track.stop());
|
||||
let wait = ms => new Promise(resolve => setTimeout(resolve, ms));
|
||||
let waitUntil = f => new Promise(resolve => {
|
||||
let ival = setInterval(() => f() && resolve(clearInterval(ival)), 100);
|
||||
});
|
||||
|
||||
</script>
|
||||
|
|
|
@ -22,8 +22,9 @@ WaveDecoder::Clone(MediaDecoderInit& aInit)
|
|||
MediaDecoderStateMachine*
|
||||
WaveDecoder::CreateStateMachine()
|
||||
{
|
||||
MediaFormatReaderInit init(this);
|
||||
MediaFormatReaderInit init;
|
||||
init.mCrashHelper = GetOwner()->CreateGMPCrashHelper();
|
||||
init.mFrameStats = mFrameStats;
|
||||
mReader = new MediaFormatReader(init, new WAVDemuxer(mResource));
|
||||
return new MediaDecoderStateMachine(this, mReader);
|
||||
}
|
||||
|
|
|
@ -1,69 +0,0 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
||||
/* 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/. */
|
||||
|
||||
#include "BufferDecoder.h"
|
||||
|
||||
#include "nsISupports.h"
|
||||
#include "MediaResource.h"
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
NS_IMPL_ISUPPORTS0(BufferDecoder)
|
||||
|
||||
BufferDecoder::BufferDecoder(MediaResource* aResource,
|
||||
AbstractThread* aMainThread)
|
||||
: mResource(aResource)
|
||||
, mAbstractMainThread(aMainThread)
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
}
|
||||
|
||||
BufferDecoder::~BufferDecoder()
|
||||
{
|
||||
// The dtor may run on any thread, we cannot be sure.
|
||||
}
|
||||
|
||||
void
|
||||
BufferDecoder::BeginDecoding(TaskQueue* aTaskQueueIdentity)
|
||||
{
|
||||
MOZ_ASSERT(!mTaskQueueIdentity && aTaskQueueIdentity);
|
||||
mTaskQueueIdentity = aTaskQueueIdentity;
|
||||
}
|
||||
|
||||
void
|
||||
BufferDecoder::NotifyDecodedFrames(const FrameStatisticsData& aStats)
|
||||
{
|
||||
// ignore
|
||||
}
|
||||
|
||||
VideoFrameContainer*
|
||||
BufferDecoder::GetVideoFrameContainer()
|
||||
{
|
||||
// no video frame
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
layers::ImageContainer*
|
||||
BufferDecoder::GetImageContainer()
|
||||
{
|
||||
// no image container
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
MediaDecoderOwner*
|
||||
BufferDecoder::GetOwner() const
|
||||
{
|
||||
// unknown
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
AbstractThread*
|
||||
BufferDecoder::AbstractMainThread() const
|
||||
{
|
||||
return mAbstractMainThread;
|
||||
}
|
||||
|
||||
} // namespace mozilla
|
|
@ -1,53 +0,0 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
||||
/* 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/. */
|
||||
|
||||
#ifndef BUFFER_DECODER_H_
|
||||
#define BUFFER_DECODER_H_
|
||||
|
||||
#include "mozilla/Attributes.h"
|
||||
#include "mozilla/ReentrantMonitor.h"
|
||||
#include "mozilla/TaskQueue.h"
|
||||
|
||||
#include "AbstractMediaDecoder.h"
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
/**
|
||||
* This class provides a decoder object which decodes a media file that lives in
|
||||
* a memory buffer.
|
||||
*/
|
||||
class BufferDecoder final : public AbstractMediaDecoder
|
||||
{
|
||||
public:
|
||||
// This class holds a weak pointer to MediaResource. It's the responsibility
|
||||
// of the caller to manage the memory of the MediaResource object.
|
||||
explicit BufferDecoder(MediaResource* aResource,
|
||||
AbstractThread* aMainThread);
|
||||
|
||||
NS_DECL_THREADSAFE_ISUPPORTS
|
||||
|
||||
// This has to be called before decoding begins
|
||||
void BeginDecoding(TaskQueue* aTaskQueueIdentity);
|
||||
|
||||
void NotifyDecodedFrames(const FrameStatisticsData& aStats) final override;
|
||||
|
||||
VideoFrameContainer* GetVideoFrameContainer() final override;
|
||||
layers::ImageContainer* GetImageContainer() final override;
|
||||
|
||||
MediaDecoderOwner* GetOwner() const final override;
|
||||
|
||||
AbstractThread* AbstractMainThread() const final override;
|
||||
|
||||
private:
|
||||
virtual ~BufferDecoder();
|
||||
RefPtr<TaskQueue> mTaskQueueIdentity;
|
||||
RefPtr<MediaResource> mResource;
|
||||
const RefPtr<AbstractThread> mAbstractMainThread;
|
||||
};
|
||||
|
||||
} // namespace mozilla
|
||||
|
||||
#endif /* BUFFER_DECODER_H_ */
|
|
@ -5,7 +5,6 @@
|
|||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "MediaBufferDecoder.h"
|
||||
#include "BufferDecoder.h"
|
||||
#include "mozilla/dom/AudioContextBinding.h"
|
||||
#include "mozilla/dom/BaseAudioContextBinding.h"
|
||||
#include "mozilla/dom/DOMException.h"
|
||||
|
@ -133,10 +132,7 @@ private:
|
|||
void Cleanup()
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
// MediaFormatReader expects that BufferDecoder is alive.
|
||||
// Destruct MediaFormatReader first.
|
||||
mDecoderReader = nullptr;
|
||||
mBufferDecoder = nullptr;
|
||||
JS_free(nullptr, mBuffer);
|
||||
}
|
||||
|
||||
|
@ -146,7 +142,6 @@ private:
|
|||
uint32_t mLength;
|
||||
WebAudioDecodeJob& mDecodeJob;
|
||||
PhaseEnum mPhase;
|
||||
RefPtr<BufferDecoder> mBufferDecoder;
|
||||
RefPtr<MediaFormatReader> mDecoderReader;
|
||||
MediaInfo mMediaInfo;
|
||||
MediaQueue<AudioData> mAudioQueue;
|
||||
|
@ -157,7 +152,6 @@ private:
|
|||
NS_IMETHODIMP
|
||||
MediaDecodeTask::Run()
|
||||
{
|
||||
MOZ_ASSERT(mBufferDecoder);
|
||||
MOZ_ASSERT(mDecoderReader);
|
||||
switch (mPhase) {
|
||||
case PhaseEnum::Decode:
|
||||
|
@ -190,15 +184,13 @@ MediaDecodeTask::CreateReader()
|
|||
RefPtr<BufferMediaResource> resource =
|
||||
new BufferMediaResource(static_cast<uint8_t*>(mBuffer), mLength, principal);
|
||||
|
||||
MOZ_ASSERT(!mBufferDecoder);
|
||||
mMainThread =
|
||||
mDecodeJob.mContext->GetOwnerGlobal()->AbstractMainThreadFor(TaskCategory::Other);
|
||||
mBufferDecoder = new BufferDecoder(resource, mMainThread);
|
||||
|
||||
// If you change this list to add support for new decoders, please consider
|
||||
// updating HTMLMediaElement::CreateDecoder as well.
|
||||
|
||||
MediaFormatReaderInit init(mBufferDecoder);
|
||||
MediaFormatReaderInit init;
|
||||
init.mResource = resource;
|
||||
mDecoderReader = DecoderTraits::CreateReader(mContainerType, init);
|
||||
|
||||
|
@ -245,7 +237,6 @@ MediaDecodeTask::Decode()
|
|||
{
|
||||
MOZ_ASSERT(!NS_IsMainThread());
|
||||
|
||||
mBufferDecoder->BeginDecoding(mDecoderReader->OwnerThread());
|
||||
|
||||
mDecoderReader->AsyncReadMetadata()->Then(mDecoderReader->OwnerThread(), __func__, this,
|
||||
&MediaDecodeTask::OnMetadataRead,
|
||||
|
|
|
@ -88,7 +88,6 @@ UNIFIED_SOURCES += [
|
|||
'AudioProcessingEvent.cpp',
|
||||
'AudioScheduledSourceNode.cpp',
|
||||
'BiquadFilterNode.cpp',
|
||||
'BufferDecoder.cpp',
|
||||
'ChannelMergerNode.cpp',
|
||||
'ChannelSplitterNode.cpp',
|
||||
'ConstantSourceNode.cpp',
|
||||
|
|
|
@ -18,10 +18,11 @@ namespace mozilla {
|
|||
|
||||
MediaDecoderStateMachine* WebMDecoder::CreateStateMachine()
|
||||
{
|
||||
MediaFormatReaderInit init(this);
|
||||
MediaFormatReaderInit init;
|
||||
init.mVideoFrameContainer = GetVideoFrameContainer();
|
||||
init.mKnowsCompositor = GetCompositor();
|
||||
init.mCrashHelper = GetOwner()->CreateGMPCrashHelper();
|
||||
init.mFrameStats = mFrameStats;
|
||||
mReader = new MediaFormatReader(init, new WebMDemuxer(mResource));
|
||||
return new MediaDecoderStateMachine(this, mReader);
|
||||
}
|
||||
|
|
|
@ -6,7 +6,6 @@
|
|||
|
||||
#include "nsError.h"
|
||||
#include "MediaDecoderStateMachine.h"
|
||||
#include "AbstractMediaDecoder.h"
|
||||
#include "MediaResource.h"
|
||||
#ifdef MOZ_AV1
|
||||
#include "AOMDecoder.h"
|
||||
|
|
|
@ -533,14 +533,10 @@ nsXBLWindowKeyHandler::HandleEventOnCaptureInSystemEventGroup(
|
|||
WidgetKeyboardEvent* widgetEvent =
|
||||
aEvent->AsEvent()->WidgetEventPtr()->AsKeyboardEvent();
|
||||
|
||||
if (widgetEvent->IsCrossProcessForwardingStopped() ||
|
||||
widgetEvent->mFlags.mOnlySystemGroupDispatchInContent) {
|
||||
return;
|
||||
}
|
||||
|
||||
nsCOMPtr<mozilla::dom::Element> originalTarget =
|
||||
do_QueryInterface(aEvent->AsEvent()->WidgetEventPtr()->mOriginalTarget);
|
||||
if (!EventStateManager::IsRemoteTarget(originalTarget)) {
|
||||
// If the event won't be sent to remote process, this listener needs to do
|
||||
// nothing.
|
||||
if (widgetEvent->mFlags.mOnlySystemGroupDispatchInContent ||
|
||||
!widgetEvent->WillBeSentToRemoteProcess()) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -419,7 +419,6 @@ function ignoreContents(entry)
|
|||
|
||||
// Needs main thread assertions or other fixes.
|
||||
/UndisplayedMap::GetEntryFor/,
|
||||
/nsStyleContext::CalcStyleDifferenceInternal/,
|
||||
/EffectCompositor::GetServoAnimationRule/,
|
||||
/LookAndFeel::GetColor/,
|
||||
"Gecko_CopyStyleContentsFrom",
|
||||
|
|
|
@ -8161,7 +8161,19 @@ PresShell::HandleEventInternal(WidgetEvent* aEvent,
|
|||
if (aEvent->mClass == eKeyboardEventClass) {
|
||||
nsContentUtils::SetIsHandlingKeyBoardEvent(true);
|
||||
}
|
||||
if (aEvent->IsAllowedToDispatchDOMEvent()) {
|
||||
// If EventStateManager or something wants reply from remote process and
|
||||
// needs to win any other event listeners in chrome, the event is both
|
||||
// stopped its propagation and marked as "waiting reply from remote
|
||||
// process". In this case, PresShell shouldn't dispatch the event into
|
||||
// the DOM tree because they don't have a chance to stop propagation in
|
||||
// the system event group. On the other hand, if its propagation is not
|
||||
// stopped, that means that the event may be reserved by chrome. If it's
|
||||
// reserved by chrome, the event shouldn't be sent to any remote
|
||||
// processes. In this case, PresShell needs to dispatch the event to
|
||||
// the DOM tree for checking if it's reserved.
|
||||
if (aEvent->IsAllowedToDispatchDOMEvent() &&
|
||||
!(aEvent->PropagationStopped() &&
|
||||
aEvent->IsWaitingReplyFromRemoteProcess())) {
|
||||
MOZ_ASSERT(nsContentUtils::IsSafeToRunScript(),
|
||||
"Somebody changed aEvent to cause a DOM event!");
|
||||
nsPresShellEventCB eventCB(this);
|
||||
|
|
|
@ -1814,8 +1814,18 @@ RestyleManager::AnimationsWithDestroyedFrame
|
|||
nsTransitionManager* transitionManager =
|
||||
mRestyleManager->PresContext()->TransitionManager();
|
||||
for (nsIContent* content : aArray) {
|
||||
if (content->GetPrimaryFrame()) {
|
||||
continue;
|
||||
if (aPseudoType == CSSPseudoElementType::NotPseudo) {
|
||||
if (content->GetPrimaryFrame()) {
|
||||
continue;
|
||||
}
|
||||
} else if (aPseudoType == CSSPseudoElementType::before) {
|
||||
if (nsLayoutUtils::GetBeforeFrame(content)) {
|
||||
continue;
|
||||
}
|
||||
} else if (aPseudoType == CSSPseudoElementType::after) {
|
||||
if (nsLayoutUtils::GetAfterFrame(content)) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
dom::Element* element = content->AsElement();
|
||||
|
||||
|
|
|
@ -603,12 +603,9 @@ ServoRestyleManager::ProcessPostTraversal(
|
|||
RefPtr<ServoStyleContext> newContext = nullptr;
|
||||
if (wasRestyled && oldStyleContext) {
|
||||
MOZ_ASSERT(styleFrame || displayContentsNode);
|
||||
RefPtr<ServoStyleContext> currentContext =
|
||||
newContext =
|
||||
aRestyleState.StyleSet().ResolveServoStyle(aElement, aRestyleBehavior);
|
||||
MOZ_ASSERT(oldStyleContext->ComputedData() != currentContext->ComputedData());
|
||||
|
||||
newContext = currentContext;
|
||||
newContext->UpdateWithElementState(aElement);
|
||||
MOZ_ASSERT(oldStyleContext->ComputedData() != newContext->ComputedData());
|
||||
|
||||
newContext->ResolveSameStructsAs(oldStyleContext);
|
||||
|
||||
|
|
|
@ -51,4 +51,6 @@ fails == background-position-important.html background-position-ref.html # This
|
|||
== mask-size-in-delay-1a.html mask-anim-ref.html
|
||||
== mask-size-in-delay-1b.html mask-anim-ref.html
|
||||
|
||||
== stop-animation-on-discarded-pseudo-element.html about:blank
|
||||
|
||||
== updating-animation-on-pseudo-element.html updating-animation-on-pseudo-element-ref.html
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
<!DOCTYPE html>
|
||||
<html class="reftest-wait">
|
||||
<style>
|
||||
@keyframes anim {
|
||||
0% { background-color: red; }
|
||||
100% { background-color: red; }
|
||||
}
|
||||
#target.x::before,
|
||||
#target.y::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
}
|
||||
#target.x::before {
|
||||
animation: anim 100s infinite;
|
||||
}
|
||||
</style>
|
||||
<div id="target"></div>
|
||||
<script>
|
||||
var target = document.getElementById('target');
|
||||
requestAnimationFrame(() => {
|
||||
// Create ::before, start animation
|
||||
target.className = 'x';
|
||||
requestAnimationFrame(() => {
|
||||
// Remove ::before, stop animation
|
||||
target.className = '';
|
||||
|
||||
requestAnimationFrame(() => {
|
||||
// Create ::before, should not be animating
|
||||
target.className = 'y';
|
||||
document.documentElement.classList.remove('reftest-wait');
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
|
@ -26,6 +26,7 @@
|
|||
#include "nsIContentInlines.h"
|
||||
#include "nsIDOMNode.h"
|
||||
#include "nsIDocumentInlines.h"
|
||||
#include "nsILoadContext.h"
|
||||
#include "nsIFrame.h"
|
||||
#include "nsINode.h"
|
||||
#include "nsIPresShell.h"
|
||||
|
@ -282,6 +283,22 @@ Gecko_GetNextStyleChild(RawGeckoStyleChildrenIteratorBorrowedMut aIterator)
|
|||
return aIterator->GetNextChild();
|
||||
}
|
||||
|
||||
bool
|
||||
Gecko_IsPrivateBrowsingEnabled(const nsIDocument* aDoc)
|
||||
{
|
||||
MOZ_ASSERT(aDoc);
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
|
||||
nsILoadContext* loadContext = aDoc->GetLoadContext();
|
||||
return loadContext && loadContext->UsePrivateBrowsing();
|
||||
}
|
||||
|
||||
bool
|
||||
Gecko_AreVisitedLinksEnabled()
|
||||
{
|
||||
return nsCSSRuleProcessor::VisitedLinksEnabled();
|
||||
}
|
||||
|
||||
EventStates::ServoType
|
||||
Gecko_ElementState(RawGeckoElementBorrowed aElement)
|
||||
{
|
||||
|
|
|
@ -229,6 +229,13 @@ Gecko_GetVisitedLinkAttrDeclarationBlock(RawGeckoElementBorrowed element);
|
|||
RawServoDeclarationBlockStrongBorrowedOrNull
|
||||
Gecko_GetActiveLinkAttrDeclarationBlock(RawGeckoElementBorrowed element);
|
||||
|
||||
// Visited handling.
|
||||
|
||||
// Returns whether private browsing is enabled for a given element.
|
||||
bool Gecko_IsPrivateBrowsingEnabled(const nsIDocument* aDoc);
|
||||
// Returns whether visited links are enabled.
|
||||
bool Gecko_AreVisitedLinksEnabled();
|
||||
|
||||
// Animations
|
||||
bool
|
||||
Gecko_GetAnimationRule(RawGeckoElementBorrowed aElementOrPseudo,
|
||||
|
|
|
@ -22,53 +22,14 @@ ServoStyleContext::ServoStyleContext(
|
|||
CSSPseudoElementType aPseudoType,
|
||||
ServoComputedDataForgotten aComputedValues)
|
||||
: nsStyleContext(aParent, aPseudoTag, aPseudoType)
|
||||
, mPresContext(aPresContext)
|
||||
, mSource(aComputedValues)
|
||||
{
|
||||
mPresContext = aPresContext;
|
||||
AddStyleBit(Servo_ComputedValues_GetStyleBits(this));
|
||||
|
||||
FinishConstruction();
|
||||
|
||||
// No need to call ApplyStyleFixups here, since fixups are handled by Servo when
|
||||
// producing the ServoComputedData.
|
||||
}
|
||||
|
||||
void
|
||||
ServoStyleContext::UpdateWithElementState(Element* aElementForAnimation)
|
||||
{
|
||||
bool isLink = false;
|
||||
bool isVisitedLink = false;
|
||||
// If we need visited styles for callers where `aElementForAnimation` is null,
|
||||
// we can precompute these and pass them as flags, similar to nsStyleSet.cpp.
|
||||
if (aElementForAnimation) {
|
||||
isLink = nsCSSRuleProcessor::IsLink(aElementForAnimation);
|
||||
isVisitedLink = nsCSSRuleProcessor::GetContentState(aElementForAnimation)
|
||||
.HasState(NS_EVENT_STATE_VISITED);
|
||||
}
|
||||
|
||||
|
||||
auto parent = GetParentAllowServo();
|
||||
|
||||
// The true visited state of the relevant link is used to decided whether
|
||||
// visited styles should be consulted for all visited dependent properties.
|
||||
bool relevantLinkVisited = isLink ? isVisitedLink :
|
||||
(parent && parent->RelevantLinkVisited());
|
||||
|
||||
if (relevantLinkVisited && GetStyleIfVisited()) {
|
||||
AddStyleBit(NS_STYLE_RELEVANT_LINK_VISITED);
|
||||
}
|
||||
|
||||
// Set the body color on the pres context. See nsStyleSet::GetContext
|
||||
if (aElementForAnimation &&
|
||||
aElementForAnimation->IsHTMLElement(nsGkAtoms::body) &&
|
||||
GetPseudoType() == CSSPseudoElementType::NotPseudo &&
|
||||
mPresContext->CompatibilityMode() == eCompatibility_NavQuirks) {
|
||||
nsIDocument* doc = aElementForAnimation->GetUncomposedDoc();
|
||||
if (doc && doc->GetBodyElement() == aElementForAnimation) {
|
||||
// Update the prescontext's body color
|
||||
mPresContext->SetBodyTextColor(StyleColor()->mColor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace mozilla
|
||||
|
|
|
@ -35,10 +35,6 @@ public:
|
|||
return ComputedData()->visited_style.mPtr;
|
||||
}
|
||||
|
||||
// Update visited state for a given element, and set the prescontext's
|
||||
// body text color if applicable.
|
||||
void UpdateWithElementState(dom::Element* aElement);
|
||||
|
||||
/**
|
||||
* Makes this context match |aOther| in terms of which style structs have
|
||||
* been resolved.
|
||||
|
|
|
@ -175,33 +175,14 @@ ServoStyleSet::ResolveStyleFor(Element* aElement,
|
|||
ServoStyleContext* aParentContext,
|
||||
LazyComputeBehavior aMayCompute)
|
||||
{
|
||||
return GetContext(aElement, aParentContext, nullptr,
|
||||
CSSPseudoElementType::NotPseudo, aMayCompute);
|
||||
}
|
||||
|
||||
already_AddRefed<ServoStyleContext>
|
||||
ServoStyleSet::GetContext(nsIContent* aContent,
|
||||
ServoStyleContext* aParentContext,
|
||||
nsIAtom* aPseudoTag,
|
||||
CSSPseudoElementType aPseudoType,
|
||||
LazyComputeBehavior aMayCompute)
|
||||
{
|
||||
MOZ_ASSERT(aContent->IsElement());
|
||||
Element* element = aContent->AsElement();
|
||||
|
||||
RefPtr<ServoStyleContext> computedValues;
|
||||
if (aMayCompute == LazyComputeBehavior::Allow) {
|
||||
PreTraverseSync();
|
||||
computedValues =
|
||||
ResolveStyleLazily(element, CSSPseudoElementType::NotPseudo, aPseudoTag, aParentContext);
|
||||
computedValues->UpdateWithElementState(element);
|
||||
} else {
|
||||
computedValues = ResolveServoStyle(element,
|
||||
TraversalRestyleBehavior::Normal);
|
||||
return ResolveStyleLazily(
|
||||
aElement, CSSPseudoElementType::NotPseudo, nullptr, aParentContext);
|
||||
}
|
||||
|
||||
MOZ_ASSERT(computedValues);
|
||||
return computedValues.forget();
|
||||
return ResolveServoStyle(aElement, TraversalRestyleBehavior::Normal);
|
||||
}
|
||||
|
||||
|
||||
|
@ -384,7 +365,6 @@ ServoStyleSet::ResolveStyleForText(nsIContent* aTextNode,
|
|||
nsCSSAnonBoxes::mozText,
|
||||
aParentContext,
|
||||
InheritTarget::Text).Consume();
|
||||
computedValues->UpdateWithElementState(nullptr);
|
||||
return computedValues.forget();
|
||||
}
|
||||
|
||||
|
@ -398,8 +378,6 @@ ServoStyleSet::ResolveStyleForFirstLetterContinuation(ServoStyleContext* aParent
|
|||
InheritTarget::FirstLetterContinuation)
|
||||
.Consume();
|
||||
MOZ_ASSERT(computedValues);
|
||||
|
||||
computedValues->UpdateWithElementState(nullptr);
|
||||
return computedValues.forget();
|
||||
}
|
||||
|
||||
|
@ -421,7 +399,6 @@ ServoStyleSet::ResolveStyleForPlaceholder()
|
|||
.Consume();
|
||||
MOZ_ASSERT(computedValues);
|
||||
|
||||
computedValues->UpdateWithElementState(nullptr);
|
||||
cache = computedValues;
|
||||
return computedValues.forget();
|
||||
}
|
||||
|
@ -454,10 +431,6 @@ ServoStyleSet::ResolvePseudoElementStyle(Element* aOriginatingElement,
|
|||
}
|
||||
|
||||
MOZ_ASSERT(computedValues);
|
||||
|
||||
bool isBeforeOrAfter = aType == CSSPseudoElementType::before ||
|
||||
aType == CSSPseudoElementType::after;
|
||||
computedValues->UpdateWithElementState(isBeforeOrAfter ? aOriginatingElement : nullptr);
|
||||
return computedValues.forget();
|
||||
}
|
||||
|
||||
|
@ -469,7 +442,6 @@ ServoStyleSet::ResolveTransientStyle(Element* aElement,
|
|||
{
|
||||
RefPtr<ServoStyleContext> result =
|
||||
ResolveTransientServoStyle(aElement, aPseudoType, aPseudoTag, aRuleInclusion);
|
||||
result->UpdateWithElementState(nullptr);
|
||||
return result.forget();
|
||||
}
|
||||
|
||||
|
@ -507,7 +479,6 @@ ServoStyleSet::ResolveInheritingAnonymousBoxStyle(nsIAtom* aPseudoTag,
|
|||
}
|
||||
#endif
|
||||
|
||||
computedValues->UpdateWithElementState(nullptr);
|
||||
return computedValues.forget();
|
||||
}
|
||||
|
||||
|
@ -552,7 +523,6 @@ ServoStyleSet::ResolveNonInheritingAnonymousBoxStyle(nsIAtom* aPseudoTag)
|
|||
}
|
||||
#endif
|
||||
|
||||
computedValues->UpdateWithElementState(nullptr);
|
||||
cache = computedValues;
|
||||
return computedValues.forget();
|
||||
}
|
||||
|
@ -784,14 +754,12 @@ ServoStyleSet::ProbePseudoElementStyle(Element* aOriginatingElement,
|
|||
if (isBeforeOrAfter) {
|
||||
const nsStyleDisplay* display = computedValues->ComputedData()->GetStyleDisplay();
|
||||
const nsStyleContent* content = computedValues->ComputedData()->GetStyleContent();
|
||||
// XXXldb What is contentCount for |content: ""|?
|
||||
if (display->mDisplay == StyleDisplay::None ||
|
||||
content->ContentCount() == 0) {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
computedValues->UpdateWithElementState(isBeforeOrAfter ? aOriginatingElement : nullptr);
|
||||
return computedValues.forget();
|
||||
}
|
||||
|
||||
|
@ -806,8 +774,8 @@ ServoStyleSet::HasStateDependentStyle(dom::Element* aElement,
|
|||
nsRestyleHint
|
||||
ServoStyleSet::HasStateDependentStyle(dom::Element* aElement,
|
||||
CSSPseudoElementType aPseudoType,
|
||||
dom::Element* aPseudoElement,
|
||||
EventStates aStateMask)
|
||||
dom::Element* aPseudoElement,
|
||||
EventStates aStateMask)
|
||||
{
|
||||
NS_WARNING("stylo: HasStateDependentStyle always returns zero!");
|
||||
return nsRestyleHint(0);
|
||||
|
@ -1088,14 +1056,45 @@ ServoStyleSet::CompatibilityModeChanged()
|
|||
Servo_StyleSet_CompatModeChanged(mRawSet.get());
|
||||
}
|
||||
|
||||
inline static void
|
||||
UpdateBodyTextColorIfNeeded(
|
||||
const Element& aElement,
|
||||
ServoStyleContext& aStyleContext,
|
||||
nsPresContext& aPresContext)
|
||||
{
|
||||
if (aPresContext.CompatibilityMode() != eCompatibility_NavQuirks) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!aElement.IsHTMLElement(nsGkAtoms::body)) {
|
||||
return;
|
||||
}
|
||||
|
||||
nsIDocument* doc = aElement.GetUncomposedDoc();
|
||||
if (!doc || doc->GetBodyElement() != &aElement) {
|
||||
return;
|
||||
}
|
||||
|
||||
MOZ_ASSERT(!aStyleContext.GetPseudo());
|
||||
|
||||
// NOTE(emilio): We do the ComputedData() dance to avoid triggering the
|
||||
// IsInServoTraversal() assertion in StyleColor(), which seems useful enough
|
||||
// in the general case, I guess...
|
||||
aPresContext.SetBodyTextColor(
|
||||
aStyleContext.ComputedData()->GetStyleColor()->mColor);
|
||||
}
|
||||
|
||||
already_AddRefed<ServoStyleContext>
|
||||
ServoStyleSet::ResolveServoStyle(Element* aElement,
|
||||
TraversalRestyleBehavior aRestyleBehavior)
|
||||
{
|
||||
UpdateStylistIfNeeded();
|
||||
return Servo_ResolveStyle(aElement,
|
||||
mRawSet.get(),
|
||||
aRestyleBehavior).Consume();
|
||||
RefPtr<ServoStyleContext> result =
|
||||
Servo_ResolveStyle(aElement,
|
||||
mRawSet.get(),
|
||||
aRestyleBehavior).Consume();
|
||||
UpdateBodyTextColorIfNeeded(*aElement, *result, *mPresContext);
|
||||
return result.forget();
|
||||
}
|
||||
|
||||
void
|
||||
|
@ -1159,6 +1158,10 @@ ServoStyleSet::ResolveStyleLazily(Element* aElement,
|
|||
mRawSet.get()).Consume();
|
||||
}
|
||||
|
||||
if (aPseudoType == CSSPseudoElementType::NotPseudo) {
|
||||
UpdateBodyTextColorIfNeeded(*aElement, *computedValues, *mPresContext);
|
||||
}
|
||||
|
||||
return computedValues.forget();
|
||||
}
|
||||
|
||||
|
|
|
@ -481,12 +481,6 @@ private:
|
|||
ServoStyleSet* mSet;
|
||||
};
|
||||
|
||||
already_AddRefed<ServoStyleContext> GetContext(nsIContent* aContent,
|
||||
ServoStyleContext* aParentContext,
|
||||
nsIAtom* aPseudoTag,
|
||||
CSSPseudoElementType aPseudoType,
|
||||
LazyComputeBehavior aMayCompute);
|
||||
|
||||
/**
|
||||
* Rebuild the style data. This will force a stylesheet flush, and also
|
||||
* recompute the default computed styles.
|
||||
|
|
|
@ -1068,6 +1068,12 @@ nsCSSRuleProcessor::Startup()
|
|||
true);
|
||||
}
|
||||
|
||||
/* static */ bool
|
||||
nsCSSRuleProcessor::VisitedLinksEnabled()
|
||||
{
|
||||
return gSupportVisitedPseudo;
|
||||
}
|
||||
|
||||
/* static */ void
|
||||
nsCSSRuleProcessor::InitSystemMetrics()
|
||||
{
|
||||
|
@ -1240,7 +1246,8 @@ nsCSSRuleProcessor::GetWindowsThemeIdentifier()
|
|||
|
||||
/* static */
|
||||
EventStates
|
||||
nsCSSRuleProcessor::GetContentState(Element* aElement, bool aUsingPrivateBrowsing)
|
||||
nsCSSRuleProcessor::GetContentState(const Element* aElement,
|
||||
bool aUsingPrivateBrowsing)
|
||||
{
|
||||
EventStates state = aElement->StyleState();
|
||||
|
||||
|
@ -1260,7 +1267,8 @@ nsCSSRuleProcessor::GetContentState(Element* aElement, bool aUsingPrivateBrowsin
|
|||
|
||||
/* static */
|
||||
EventStates
|
||||
nsCSSRuleProcessor::GetContentState(Element* aElement, const TreeMatchContext& aTreeMatchContext)
|
||||
nsCSSRuleProcessor::GetContentState(const Element* aElement,
|
||||
const TreeMatchContext& aTreeMatchContext)
|
||||
{
|
||||
return nsCSSRuleProcessor::GetContentState(
|
||||
aElement,
|
||||
|
@ -1270,7 +1278,7 @@ nsCSSRuleProcessor::GetContentState(Element* aElement, const TreeMatchContext& a
|
|||
|
||||
/* static */
|
||||
EventStates
|
||||
nsCSSRuleProcessor::GetContentState(Element* aElement)
|
||||
nsCSSRuleProcessor::GetContentState(const Element* aElement)
|
||||
{
|
||||
nsILoadContext* loadContext = aElement->OwnerDoc()->GetLoadContext();
|
||||
bool usingPrivateBrowsing = loadContext && loadContext->UsePrivateBrowsing();
|
||||
|
@ -1288,7 +1296,7 @@ nsCSSRuleProcessor::IsLink(const Element* aElement)
|
|||
/* static */
|
||||
EventStates
|
||||
nsCSSRuleProcessor::GetContentStateForVisitedHandling(
|
||||
Element* aElement,
|
||||
const Element* aElement,
|
||||
nsRuleWalker::VisitedHandlingType aVisitedHandling,
|
||||
bool aIsRelevantLink)
|
||||
{
|
||||
|
|
|
@ -81,6 +81,7 @@ public:
|
|||
public:
|
||||
nsresult ClearRuleCascades();
|
||||
|
||||
static bool VisitedLinksEnabled();
|
||||
static void Startup();
|
||||
static void InitSystemMetrics();
|
||||
static void Shutdown();
|
||||
|
@ -103,19 +104,19 @@ public:
|
|||
* slightly adjusted from IntrinsicState().
|
||||
*/
|
||||
static mozilla::EventStates GetContentState(
|
||||
mozilla::dom::Element* aElement,
|
||||
const mozilla::dom::Element* aElement,
|
||||
bool aUsingPrivateBrowsing);
|
||||
static mozilla::EventStates GetContentState(
|
||||
mozilla::dom::Element* aElement,
|
||||
const mozilla::dom::Element* aElement,
|
||||
const TreeMatchContext& aTreeMatchContext);
|
||||
static mozilla::EventStates GetContentState(
|
||||
mozilla::dom::Element* aElement);
|
||||
const mozilla::dom::Element* aElement);
|
||||
|
||||
/*
|
||||
* Helper to get the content state for :visited handling for an element
|
||||
*/
|
||||
static mozilla::EventStates GetContentStateForVisitedHandling(
|
||||
mozilla::dom::Element* aElement,
|
||||
const mozilla::dom::Element* aElement,
|
||||
nsRuleWalker::VisitedHandlingType aVisitedHandling,
|
||||
bool aIsRelevantLink);
|
||||
|
||||
|
|
|
@ -2892,31 +2892,35 @@ css::URLValueData::IsLocalRef() const
|
|||
bool
|
||||
css::URLValueData::HasRef() const
|
||||
{
|
||||
bool result = false;
|
||||
|
||||
if (IsLocalRef()) {
|
||||
return true;
|
||||
result = true;
|
||||
} else {
|
||||
if (nsIURI* uri = GetURI()) {
|
||||
nsAutoCString ref;
|
||||
nsresult rv = uri->GetRef(ref);
|
||||
if (NS_SUCCEEDED(rv) && !ref.IsEmpty()) {
|
||||
result = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
nsIURI* uri = GetURI();
|
||||
if (!uri) {
|
||||
return false;
|
||||
}
|
||||
mMightHaveRef = Some(result);
|
||||
|
||||
nsAutoCString ref;
|
||||
nsresult rv = uri->GetRef(ref);
|
||||
if (NS_SUCCEEDED(rv) && !ref.IsEmpty()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
return result;
|
||||
}
|
||||
|
||||
bool
|
||||
css::URLValueData::MightHaveRef() const
|
||||
{
|
||||
if (mMightHaveRef.isNothing()) {
|
||||
// ::MightHaveRef is O(N), use it only use it only when MightHaveRef is
|
||||
// called.
|
||||
mMightHaveRef.emplace(::MightHaveRef(mString));
|
||||
bool result = ::MightHaveRef(mString);
|
||||
if (!ServoStyleSet::IsInServoTraversal()) {
|
||||
// Can only cache the result if we're not on a style worker thread.
|
||||
mMightHaveRef.emplace(result);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
return mMightHaveRef.value();
|
||||
|
|
|
@ -1755,6 +1755,9 @@ private:
|
|||
public:
|
||||
bool operator==(const nsCSSValueTokenStream& aOther) const
|
||||
{
|
||||
// This is not safe to call OMT, due to the URI/Principal Equals calls.
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
|
||||
bool eq;
|
||||
return mPropertyID == aOther.mPropertyID &&
|
||||
mShorthandPropertyID == aOther.mShorthandPropertyID &&
|
||||
|
|
|
@ -172,6 +172,10 @@ nsStyleContext::CalcStyleDifference(nsStyleContext* aNewContext,
|
|||
DebugOnly<uint32_t> structsFound = 0;
|
||||
|
||||
if (IsGecko()) {
|
||||
// CalcStyleDifference is always called on the main thread for Gecko
|
||||
// style contexts. This assertion helps the heap write static analysis.
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
|
||||
// FIXME(heycam): We should just do the comparison in
|
||||
// nsStyleVariables::CalcDifference, returning NeutralChange if there are
|
||||
// any Variables differences.
|
||||
|
|
|
@ -271,8 +271,7 @@ nsMenuBarListener::KeyPress(nsIDOMEvent* aKeyEvent)
|
|||
// the mozaccesskeynotfound event before handling accesskeys.
|
||||
WidgetKeyboardEvent* nativeKeyEvent =
|
||||
aKeyEvent->WidgetEventPtr()->AsKeyboardEvent();
|
||||
if (!nativeKeyEvent ||
|
||||
(nativeKeyEvent && nativeKeyEvent->mAccessKeyForwardedToChild)) {
|
||||
if (!nativeKeyEvent) {
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
|
@ -301,6 +300,17 @@ nsMenuBarListener::KeyPress(nsIDOMEvent* aKeyEvent)
|
|||
// so, we'll know the menu got activated.
|
||||
nsMenuFrame* result = mMenuBarFrame->FindMenuWithShortcut(keyEvent);
|
||||
if (result) {
|
||||
// If the keyboard event matches with a menu item's accesskey and
|
||||
// will be sent to a remote process, it should be executed with
|
||||
// reply event from the focused remote process. Note that if the
|
||||
// menubar is active, the event is already marked as "stop cross
|
||||
// process dispatching". So, in that case, this won't wait
|
||||
// reply from the remote content.
|
||||
if (nativeKeyEvent->WillBeSentToRemoteProcess()) {
|
||||
nativeKeyEvent->StopImmediatePropagation();
|
||||
nativeKeyEvent->MarkAsWaitingReplyFromRemoteProcess();
|
||||
return NS_OK;
|
||||
}
|
||||
mMenuBarFrame->SetActiveByKeyboard();
|
||||
mMenuBarFrame->SetActive(true);
|
||||
result->OpenMenu(true);
|
||||
|
@ -317,6 +327,17 @@ nsMenuBarListener::KeyPress(nsIDOMEvent* aKeyEvent)
|
|||
// Also need to handle F10 specially on Non-Mac platform.
|
||||
else if (nativeKeyEvent->mMessage == eKeyPress && keyCode == NS_VK_F10) {
|
||||
if ((GetModifiersForAccessKey(keyEvent) & ~MODIFIER_CONTROL) == 0) {
|
||||
// If the keyboard event should activate the menubar and will be
|
||||
// sent to a remote process, it should be executed with reply
|
||||
// event from the focused remote process. Note that if the menubar
|
||||
// is active, the event is already marked as "stop cross
|
||||
// process dispatching". So, in that case, this won't wait
|
||||
// reply from the remote content.
|
||||
if (nativeKeyEvent->WillBeSentToRemoteProcess()) {
|
||||
nativeKeyEvent->StopImmediatePropagation();
|
||||
nativeKeyEvent->MarkAsWaitingReplyFromRemoteProcess();
|
||||
return NS_OK;
|
||||
}
|
||||
// The F10 key just went down by itself or with ctrl pressed.
|
||||
// In Windows, both of these activate the menu bar.
|
||||
mMenuBarFrame->SetActiveByKeyboard();
|
||||
|
|
|
@ -2245,6 +2245,9 @@ public class BrowserProvider extends SharedBrowserDatabaseProvider {
|
|||
values.putNull(Bookmarks.TAGS);
|
||||
values.putNull(Bookmarks.FAVICON_ID);
|
||||
|
||||
// Bump the lastModified timestamp for sync to know to update bookmark records.
|
||||
values.put(Bookmarks.DATE_MODIFIED, System.currentTimeMillis());
|
||||
|
||||
// Leave space for variables in values.
|
||||
final int maxVariableNumber = DBUtils.SQLITE_MAX_VARIABLE_NUMBER - values.size();
|
||||
|
||||
|
|
|
@ -36,6 +36,7 @@ import static org.junit.Assert.assertTrue;
|
|||
public class BrowserProviderBookmarksTest {
|
||||
|
||||
private static final long INVALID_ID = -1;
|
||||
private static final long INVALID_TIMESTAMP = -1;
|
||||
|
||||
private ContentProviderClient bookmarksClient;
|
||||
private Uri bookmarksTestUri;
|
||||
|
@ -141,6 +142,50 @@ public class BrowserProviderBookmarksTest {
|
|||
assertEquals(4, changed);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBookmarkFolderLastModifiedOnDeletion() throws RemoteException {
|
||||
final long rootId = getBookmarkIdFromGuid(BrowserContract.Bookmarks.MOBILE_FOLDER_GUID);
|
||||
|
||||
// root
|
||||
// -> sibling
|
||||
// -> parent -- timestamp must change
|
||||
// ---> child-1
|
||||
// ---> child-2 -- delete this one
|
||||
|
||||
final Uri parentUri = insertBookmark("parent", null, "parent", rootId,
|
||||
BrowserContract.Bookmarks.TYPE_FOLDER);
|
||||
final Uri siblingUri = insertBookmark("sibling", null, "sibling", rootId,
|
||||
BrowserContract.Bookmarks.TYPE_FOLDER);
|
||||
|
||||
final long parentId = Long.parseLong(parentUri.getLastPathSegment());
|
||||
final Uri child1Uri = insertBookmark("child-1", null, "child-1", parentId,
|
||||
BrowserContract.Bookmarks.TYPE_FOLDER);
|
||||
final Uri child2Uri = insertBookmark("child-2", null, "child-2", parentId,
|
||||
BrowserContract.Bookmarks.TYPE_FOLDER);
|
||||
|
||||
final long parentLastModifiedBeforeDeletion = getLastModified(parentUri);
|
||||
final long siblingLastModifiedBeforeDeletion = getLastModified(siblingUri);
|
||||
|
||||
final long child1LastModifiedBeforeDeletion = getLastModified(child1Uri);
|
||||
final long child2LastModifiedBeforeDeletion = getLastModified(child2Uri);
|
||||
|
||||
bookmarksClient.delete(child2Uri, null, null);
|
||||
|
||||
final long parentLastModifiedAfterDeletion = getLastModified(parentUri);
|
||||
final long siblingLastModifiedAfterDeletion = getLastModified(siblingUri);
|
||||
|
||||
final long child1LastModifiedAfterDeletion = getLastModified(child1Uri);
|
||||
final long child2LastModifiedAfterDeletion = getLastModified(withDeleted(child2Uri));
|
||||
|
||||
// Check last modified timestamp of parent and child-2 is increased.
|
||||
assertTrue(parentLastModifiedAfterDeletion > parentLastModifiedBeforeDeletion);
|
||||
assertTrue(child2LastModifiedAfterDeletion > child2LastModifiedBeforeDeletion);
|
||||
|
||||
// Check last modified timestamp of sibling and child-1 is not changed.
|
||||
assertTrue(siblingLastModifiedBeforeDeletion == siblingLastModifiedAfterDeletion);
|
||||
assertTrue(child1LastModifiedBeforeDeletion == child1LastModifiedAfterDeletion);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDeleteBookmarkFolder() throws RemoteException {
|
||||
final long rootId = getBookmarkIdFromGuid(BrowserContract.Bookmarks.MOBILE_FOLDER_GUID);
|
||||
|
@ -240,6 +285,26 @@ public class BrowserProviderBookmarksTest {
|
|||
return baseUri.buildUpon().appendQueryParameter(BrowserContract.PARAM_SHOW_DELETED, "true").build();
|
||||
}
|
||||
|
||||
private long getLastModified(final Uri uri) throws RemoteException {
|
||||
final Cursor cursor = bookmarksClient.query(uri,
|
||||
new String[] { BrowserContract.Bookmarks.DATE_MODIFIED },
|
||||
null,
|
||||
null,
|
||||
null);
|
||||
assertNotNull(cursor);
|
||||
|
||||
long lastModified = INVALID_TIMESTAMP;
|
||||
try {
|
||||
assertTrue(cursor.moveToFirst());
|
||||
assertEquals(1, cursor.getCount());
|
||||
lastModified = cursor.getLong(cursor.getColumnIndexOrThrow(BrowserContract.Bookmarks.DATE_MODIFIED));
|
||||
} finally {
|
||||
cursor.close();
|
||||
}
|
||||
assertNotEquals(lastModified, INVALID_TIMESTAMP);
|
||||
return lastModified;
|
||||
}
|
||||
|
||||
private long getBookmarkIdFromGuid(String guid) throws RemoteException {
|
||||
Cursor cursor = bookmarksClient.query(BrowserContract.Bookmarks.CONTENT_URI,
|
||||
new String[] { BrowserContract.Bookmarks._ID },
|
||||
|
|
|
@ -3465,7 +3465,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "webrender"
|
||||
version = "0.48.0"
|
||||
source = "git+https://github.com/servo/webrender#b83c200c657f6b6fb17d09f329ba77803420b46a"
|
||||
source = "git+https://github.com/servo/webrender#8fd634882111415a65da67e947f26eb170234f2f"
|
||||
dependencies = [
|
||||
"app_units 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"bincode 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
|
@ -3494,7 +3494,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "webrender_api"
|
||||
version = "0.48.0"
|
||||
source = "git+https://github.com/servo/webrender#b83c200c657f6b6fb17d09f329ba77803420b46a"
|
||||
source = "git+https://github.com/servo/webrender#8fd634882111415a65da67e947f26eb170234f2f"
|
||||
dependencies = [
|
||||
"app_units 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"bincode 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
|
|
|
@ -1146,7 +1146,8 @@ pub struct LineDisplayItem {
|
|||
pub color: ColorF,
|
||||
|
||||
/// The line segment style.
|
||||
pub style: border_style::T
|
||||
#[ignore_heap_size_of = "enum type in webrender"]
|
||||
pub style: webrender_api::LineStyle,
|
||||
}
|
||||
|
||||
/// Paints a box shadow per CSS-BACKGROUNDS.
|
||||
|
|
|
@ -71,7 +71,7 @@ use style_traits::CSSPixel;
|
|||
use style_traits::cursor::Cursor;
|
||||
use table_cell::CollapsedBordersForCell;
|
||||
use webrender_api::{ClipId, ColorF, ComplexClipRegion, GradientStop, LocalClip, RepeatMode};
|
||||
use webrender_api::{ScrollPolicy, TransformStyle};
|
||||
use webrender_api::{LineStyle, ScrollPolicy, TransformStyle};
|
||||
use webrender_helpers::{ToBorderRadius, ToMixBlendMode, ToRectF, ToTransformStyle};
|
||||
|
||||
trait ResolvePercentage {
|
||||
|
@ -1651,7 +1651,7 @@ impl FragmentDisplayListBuilding for Fragment {
|
|||
state.add_display_item(DisplayItem::Line(box LineDisplayItem {
|
||||
base: base,
|
||||
color: ColorF::rgb(0, 200, 0),
|
||||
style: border_style::T::dashed,
|
||||
style: LineStyle::Dashed,
|
||||
}));
|
||||
}
|
||||
|
||||
|
@ -2217,9 +2217,10 @@ impl FragmentDisplayListBuilding for Fragment {
|
|||
self.style.get_cursor(Cursor::Default),
|
||||
DisplayListSection::Content);
|
||||
|
||||
state.add_display_item(DisplayItem::SolidColor(box SolidColorDisplayItem {
|
||||
state.add_display_item(DisplayItem::Line(box LineDisplayItem {
|
||||
base: base,
|
||||
color: color.to_gfx_color(),
|
||||
style: LineStyle::Solid,
|
||||
}));
|
||||
}
|
||||
|
||||
|
|
|
@ -432,8 +432,16 @@ impl WebRenderDisplayItemConverter for DisplayItem {
|
|||
rect.size,
|
||||
webrender_api::LayoutSize::zero());
|
||||
}
|
||||
DisplayItem::Line(..) => {
|
||||
println!("TODO DisplayItem::Line");
|
||||
DisplayItem::Line(ref item) => {
|
||||
let box_bounds = item.base.bounds.to_rectf();
|
||||
builder.push_line(Some(item.base.local_clip),
|
||||
box_bounds.origin.y + box_bounds.size.height,
|
||||
box_bounds.origin.x,
|
||||
box_bounds.origin.x + box_bounds.size.width,
|
||||
webrender_api::LineOrientation::Horizontal,
|
||||
box_bounds.size.height,
|
||||
item.color,
|
||||
item.style);
|
||||
}
|
||||
DisplayItem::BoxShadow(ref item) => {
|
||||
let rect = item.base.bounds.to_rectf();
|
||||
|
|
|
@ -594,6 +594,7 @@ impl LayoutThread {
|
|||
stylist: &self.stylist,
|
||||
options: StyleSystemOptions::default(),
|
||||
guards: guards,
|
||||
visited_styles_enabled: false,
|
||||
running_animations: self.running_animations.clone(),
|
||||
expired_animations: self.expired_animations.clone(),
|
||||
local_context_creation_data: Mutex::new(thread_local_style_context_creation_data),
|
||||
|
|
|
@ -342,9 +342,11 @@ impl HTMLScriptElement {
|
|||
return;
|
||||
}
|
||||
|
||||
// TODO(#4577): Step 11: CSP.
|
||||
// TODO: Step 11: nomodule content attribute
|
||||
|
||||
// Step 12.
|
||||
// TODO(#4577): Step 12: CSP.
|
||||
|
||||
// Step 13.
|
||||
let for_attribute = element.get_attribute(&ns!(), &local_name!("for"));
|
||||
let event_attribute = element.get_attribute(&ns!(), &local_name!("event"));
|
||||
match (for_attribute.r(), event_attribute.r()) {
|
||||
|
@ -364,19 +366,19 @@ impl HTMLScriptElement {
|
|||
(_, _) => (),
|
||||
}
|
||||
|
||||
// Step 13.
|
||||
// Step 14.
|
||||
let encoding = element.get_attribute(&ns!(), &local_name!("charset"))
|
||||
.and_then(|charset| encoding_from_whatwg_label(&charset.value()))
|
||||
.unwrap_or_else(|| doc.encoding());
|
||||
|
||||
// Step 14.
|
||||
// Step 15.
|
||||
let cors_setting = cors_setting_for_element(element);
|
||||
|
||||
// TODO: Step 15: Module script credentials mode.
|
||||
// TODO: Step 16: Module script credentials mode.
|
||||
|
||||
// TODO: Step 16: Nonce.
|
||||
// TODO: Step 17: Nonce.
|
||||
|
||||
// Step 17: Integrity metadata.
|
||||
// Step 18: Integrity metadata.
|
||||
let im_attribute = element.get_attribute(&ns!(), &local_name!("integrity"));
|
||||
let integrity_val = im_attribute.r().map(|a| a.value());
|
||||
let integrity_metadata = match integrity_val {
|
||||
|
@ -384,26 +386,26 @@ impl HTMLScriptElement {
|
|||
None => "",
|
||||
};
|
||||
|
||||
// TODO: Step 18: parser state.
|
||||
// TODO: Step 19: parser state.
|
||||
|
||||
// TODO: Step 19: environment settings object.
|
||||
// TODO: Step 20: environment settings object.
|
||||
|
||||
let base_url = doc.base_url();
|
||||
if let Some(src) = element.get_attribute(&ns!(), &local_name!("src")) {
|
||||
// Step 20.
|
||||
// Step 21.
|
||||
|
||||
// Step 20.1.
|
||||
// Step 21.1.
|
||||
let src = src.value();
|
||||
|
||||
// Step 20.2.
|
||||
// Step 21.2.
|
||||
if src.is_empty() {
|
||||
self.queue_error_event();
|
||||
return;
|
||||
}
|
||||
|
||||
// Step 20.3: The "from an external file"" flag is stored in ClassicScript.
|
||||
// Step 21.3: The "from an external file"" flag is stored in ClassicScript.
|
||||
|
||||
// Step 20.4-20.5.
|
||||
// Step 21.4-21.5.
|
||||
let url = match base_url.join(&src) {
|
||||
Ok(url) => url,
|
||||
Err(_) => {
|
||||
|
@ -413,25 +415,25 @@ impl HTMLScriptElement {
|
|||
},
|
||||
};
|
||||
|
||||
// Preparation for step 22.
|
||||
// Preparation for step 23.
|
||||
let kind = if element.has_attribute(&local_name!("defer")) && was_parser_inserted && !async {
|
||||
// Step 22.a: classic, has src, has defer, was parser-inserted, is not async.
|
||||
// Step 23.a: classic, has src, has defer, was parser-inserted, is not async.
|
||||
ExternalScriptKind::Deferred
|
||||
} else if was_parser_inserted && !async {
|
||||
// Step 22.b: classic, has src, was parser-inserted, is not async.
|
||||
// Step 23.c: classic, has src, was parser-inserted, is not async.
|
||||
ExternalScriptKind::ParsingBlocking
|
||||
} else if !async && !self.non_blocking.get() {
|
||||
// Step 22.c: classic, has src, is not async, is not non-blocking.
|
||||
// Step 23.d: classic, has src, is not async, is not non-blocking.
|
||||
ExternalScriptKind::AsapInOrder
|
||||
} else {
|
||||
// Step 22.d: classic, has src.
|
||||
// Step 23.f: classic, has src.
|
||||
ExternalScriptKind::Asap
|
||||
};
|
||||
|
||||
// Step 20.6.
|
||||
// Step 21.6.
|
||||
fetch_a_classic_script(self, kind, url, cors_setting, integrity_metadata.to_owned(), encoding);
|
||||
|
||||
// Step 22.
|
||||
// Step 23.
|
||||
match kind {
|
||||
ExternalScriptKind::Deferred => doc.add_deferred_script(self),
|
||||
ExternalScriptKind::ParsingBlocking => doc.set_pending_parsing_blocking_script(self, None),
|
||||
|
@ -439,18 +441,18 @@ impl HTMLScriptElement {
|
|||
ExternalScriptKind::Asap => doc.add_asap_script(self),
|
||||
}
|
||||
} else {
|
||||
// Step 21.
|
||||
// Step 22.
|
||||
assert!(!text.is_empty());
|
||||
let result = Ok(ClassicScript::internal(text, base_url));
|
||||
|
||||
// Step 22.
|
||||
// Step 23.
|
||||
if was_parser_inserted &&
|
||||
doc.get_current_parser().map_or(false, |parser| parser.script_nesting_level() <= 1) &&
|
||||
doc.get_script_blocking_stylesheets_count() > 0 {
|
||||
// Step 22.e: classic, has no src, was parser-inserted, is blocked on stylesheet.
|
||||
// Step 23.h: classic, has no src, was parser-inserted, is blocked on stylesheet.
|
||||
doc.set_pending_parsing_blocking_script(self, Some(result));
|
||||
} else {
|
||||
// Step 22.f: otherwise.
|
||||
// Step 23.i: otherwise.
|
||||
self.execute(result);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -117,6 +117,12 @@ pub struct SharedStyleContext<'a> {
|
|||
/// The CSS selector stylist.
|
||||
pub stylist: &'a Stylist,
|
||||
|
||||
/// Whether visited styles are enabled.
|
||||
///
|
||||
/// They may be disabled when Gecko's pref layout.css.visited_links_enabled
|
||||
/// is false, or when in private browsing mode.
|
||||
pub visited_styles_enabled: bool,
|
||||
|
||||
/// Configuration options.
|
||||
pub options: StyleSystemOptions,
|
||||
|
||||
|
|
|
@ -550,6 +550,11 @@ pub trait TElement : Eq + PartialEq + Debug + Hash + Sized + Copy + Clone +
|
|||
unsafe fn unset_animation_only_dirty_descendants(&self) {
|
||||
}
|
||||
|
||||
/// Returns true if this element is a visited link.
|
||||
///
|
||||
/// Servo doesn't support visited styles yet.
|
||||
fn is_visited_link(&self) -> bool { false }
|
||||
|
||||
/// Returns true if this element is native anonymous (only Gecko has native
|
||||
/// anonymous content).
|
||||
fn is_native_anonymous(&self) -> bool { false }
|
||||
|
|
|
@ -187,6 +187,13 @@ impl PerDocumentStyleDataImpl {
|
|||
);
|
||||
}
|
||||
|
||||
/// Returns whether private browsing is enabled.
|
||||
pub fn is_private_browsing_enabled(&self) -> bool {
|
||||
let doc =
|
||||
self.stylist.device().pres_context().mDocument.raw::<nsIDocument>();
|
||||
unsafe { bindings::Gecko_IsPrivateBrowsingEnabled(doc) }
|
||||
}
|
||||
|
||||
/// Get the default computed values for this document.
|
||||
pub fn default_computed_values(&self) -> &Arc<ComputedValues> {
|
||||
self.stylist.device().default_computed_values_arc()
|
||||
|
|
|
@ -746,6 +746,12 @@ extern "C" {
|
|||
RawGeckoElementBorrowed)
|
||||
-> RawServoDeclarationBlockStrongBorrowedOrNull;
|
||||
}
|
||||
extern "C" {
|
||||
pub fn Gecko_IsPrivateBrowsingEnabled(aDoc: *const nsIDocument) -> bool;
|
||||
}
|
||||
extern "C" {
|
||||
pub fn Gecko_AreVisitedLinksEnabled() -> bool;
|
||||
}
|
||||
extern "C" {
|
||||
pub fn Gecko_GetAnimationRule(aElementOrPseudo: RawGeckoElementBorrowed,
|
||||
aCascadeLevel:
|
||||
|
|
|
@ -1011,6 +1011,11 @@ impl<'le> TElement for GeckoElement<'le> {
|
|||
self.unset_flags(ELEMENT_HAS_ANIMATION_ONLY_DIRTY_DESCENDANTS_FOR_SERVO as u32)
|
||||
}
|
||||
|
||||
fn is_visited_link(&self) -> bool {
|
||||
use element_state::IN_VISITED_STATE;
|
||||
self.get_state().intersects(IN_VISITED_STATE)
|
||||
}
|
||||
|
||||
fn is_native_anonymous(&self) -> bool {
|
||||
self.flags() & (NODE_IS_NATIVE_ANONYMOUS as u32) != 0
|
||||
}
|
||||
|
|
|
@ -33,5 +33,9 @@ bitflags! {
|
|||
///
|
||||
/// This is used from Gecko's layout engine.
|
||||
const IS_TEXT_COMBINED = 1 << 2,
|
||||
|
||||
/// A flag used to mark styles under a relevant link that is also
|
||||
/// visited.
|
||||
const IS_RELEVANT_LINK_VISITED = 1 << 3,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2650,6 +2650,11 @@ impl<'a> StyleBuilder<'a> {
|
|||
)
|
||||
}
|
||||
|
||||
/// Returns whether we have a visited style.
|
||||
pub fn has_visited_style(&self) -> bool {
|
||||
self.visited_style.is_some()
|
||||
}
|
||||
|
||||
/// Returns the style we're inheriting from.
|
||||
pub fn inherited_style(&self) -> &'a ComputedValues {
|
||||
self.inherited_style
|
||||
|
@ -2805,14 +2810,14 @@ bitflags! {
|
|||
pub flags CascadeFlags: u8 {
|
||||
/// Whether to inherit all styles from the parent. If this flag is not
|
||||
/// present, non-inherited styles are reset to their initial values.
|
||||
const INHERIT_ALL = 0x01,
|
||||
const INHERIT_ALL = 1,
|
||||
|
||||
/// Whether to skip any display style fixup for root element, flex/grid
|
||||
/// item, and ruby descendants.
|
||||
const SKIP_ROOT_AND_ITEM_BASED_DISPLAY_FIXUP = 0x02,
|
||||
const SKIP_ROOT_AND_ITEM_BASED_DISPLAY_FIXUP = 1 << 1,
|
||||
|
||||
/// Whether to only cascade properties that are visited dependent.
|
||||
const VISITED_DEPENDENT_ONLY = 0x04,
|
||||
const VISITED_DEPENDENT_ONLY = 1 << 2,
|
||||
|
||||
/// Whether the given element we're styling is the document element,
|
||||
/// that is, matches :root.
|
||||
|
@ -2822,15 +2827,23 @@ bitflags! {
|
|||
///
|
||||
/// This affects some style adjustments, like blockification, and means
|
||||
/// that it may affect global state, like the Device's root font-size.
|
||||
const IS_ROOT_ELEMENT = 0x08,
|
||||
const IS_ROOT_ELEMENT = 1 << 3,
|
||||
|
||||
/// Whether to convert display:contents into display:inline. This
|
||||
/// is used by Gecko to prevent display:contents on generated
|
||||
/// content.
|
||||
const PROHIBIT_DISPLAY_CONTENTS = 0x10,
|
||||
const PROHIBIT_DISPLAY_CONTENTS = 1 << 4,
|
||||
|
||||
/// Whether we're styling the ::-moz-fieldset-content anonymous box.
|
||||
const IS_FIELDSET_CONTENT = 0x20,
|
||||
const IS_FIELDSET_CONTENT = 1 << 5,
|
||||
|
||||
/// Whether we're computing the style of a link, either visited or
|
||||
/// unvisited.
|
||||
const IS_LINK = 1 << 6,
|
||||
|
||||
/// Whether we're computing the style of a link element that happens to
|
||||
/// be visited.
|
||||
const IS_VISITED_LINK = 1 << 7,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -426,14 +426,44 @@ impl<'a, 'b: 'a> StyleAdjuster<'a, 'b> {
|
|||
}
|
||||
}
|
||||
|
||||
/// Computes the RELEVANT_LINK_VISITED flag based on the parent style and on
|
||||
/// whether we're a relevant link.
|
||||
///
|
||||
/// NOTE(emilio): We don't do this for text styles, which is... dubious, but
|
||||
/// Gecko doesn't seem to do it either. It's extremely easy to do if needed
|
||||
/// though.
|
||||
///
|
||||
/// FIXME(emilio): This isn't technically a style adjustment thingie, could
|
||||
/// it move somewhere else?
|
||||
fn adjust_for_visited(&mut self, flags: CascadeFlags) {
|
||||
use properties::{IS_LINK, IS_VISITED_LINK};
|
||||
use properties::computed_value_flags::IS_RELEVANT_LINK_VISITED;
|
||||
|
||||
if !self.style.has_visited_style() {
|
||||
return;
|
||||
}
|
||||
|
||||
let relevant_link_visited = if flags.contains(IS_LINK) {
|
||||
flags.contains(IS_VISITED_LINK)
|
||||
} else {
|
||||
self.style.inherited_style().flags.contains(IS_RELEVANT_LINK_VISITED)
|
||||
};
|
||||
|
||||
if relevant_link_visited {
|
||||
self.style.flags.insert(IS_RELEVANT_LINK_VISITED);
|
||||
}
|
||||
}
|
||||
|
||||
/// Adjusts the style to account for various fixups that don't fit naturally
|
||||
/// into the cascade.
|
||||
///
|
||||
/// When comparing to Gecko, this is similar to the work done by
|
||||
/// `nsStyleContext::ApplyStyleFixups`.
|
||||
/// `nsStyleContext::ApplyStyleFixups`, plus some parts of
|
||||
/// `nsStyleSet::GetContext`.
|
||||
pub fn adjust(&mut self,
|
||||
layout_parent_style: &ComputedValues,
|
||||
flags: CascadeFlags) {
|
||||
self.adjust_for_visited(flags);
|
||||
#[cfg(feature = "gecko")]
|
||||
{
|
||||
self.adjust_for_prohibited_display_contents(flags);
|
||||
|
|
|
@ -12,7 +12,8 @@ use dom::TElement;
|
|||
use log::LogLevel::Trace;
|
||||
use matching::{CascadeVisitedMode, MatchMethods};
|
||||
use properties::{AnimationRules, CascadeFlags, ComputedValues};
|
||||
use properties::{IS_ROOT_ELEMENT, PROHIBIT_DISPLAY_CONTENTS, SKIP_ROOT_AND_ITEM_BASED_DISPLAY_FIXUP};
|
||||
use properties::{IS_LINK, IS_ROOT_ELEMENT, IS_VISITED_LINK};
|
||||
use properties::{PROHIBIT_DISPLAY_CONTENTS, SKIP_ROOT_AND_ITEM_BASED_DISPLAY_FIXUP};
|
||||
use properties::{VISITED_DEPENDENT_ONLY, cascade};
|
||||
use rule_tree::StrongRuleNode;
|
||||
use selector_parser::{PseudoElement, SelectorImpl};
|
||||
|
@ -473,6 +474,15 @@ where
|
|||
if self.element.skip_root_and_item_based_display_fixup() {
|
||||
cascade_flags.insert(SKIP_ROOT_AND_ITEM_BASED_DISPLAY_FIXUP);
|
||||
}
|
||||
|
||||
if pseudo.is_none() && self.element.is_link() {
|
||||
cascade_flags.insert(IS_LINK);
|
||||
if self.element.is_visited_link() &&
|
||||
self.context.shared.visited_styles_enabled {
|
||||
cascade_flags.insert(IS_VISITED_LINK);
|
||||
}
|
||||
}
|
||||
|
||||
if cascade_visited.visited_dependent_only() {
|
||||
// If this element is a link, we want its visited style to inherit
|
||||
// from the regular style of its parent, because only the
|
||||
|
|
|
@ -182,8 +182,13 @@ fn create_shared_context<'a>(global_style_data: &GlobalStyleData,
|
|||
traversal_flags: TraversalFlags,
|
||||
snapshot_map: &'a ServoElementSnapshotTable)
|
||||
-> SharedStyleContext<'a> {
|
||||
let visited_styles_enabled =
|
||||
unsafe { bindings::Gecko_AreVisitedLinksEnabled() } &&
|
||||
!per_doc_data.is_private_browsing_enabled();
|
||||
|
||||
SharedStyleContext {
|
||||
stylist: &per_doc_data.stylist,
|
||||
visited_styles_enabled: visited_styles_enabled,
|
||||
options: global_style_data.options.clone(),
|
||||
guards: StylesheetGuards::same(guard),
|
||||
timer: Timer::new(),
|
||||
|
@ -1736,6 +1741,9 @@ pub extern "C" fn Servo_ComputedValues_GetStyleBits(values: ServoStyleContextBor
|
|||
use style::properties::computed_value_flags::*;
|
||||
let flags = values.flags;
|
||||
let mut result = 0;
|
||||
if flags.contains(IS_RELEVANT_LINK_VISITED) {
|
||||
result |= structs::NS_STYLE_RELEVANT_LINK_VISITED as u64;
|
||||
}
|
||||
if flags.contains(HAS_TEXT_DECORATION_LINES) {
|
||||
result |= structs::NS_STYLE_HAS_TEXT_DECORATION_LINES as u64;
|
||||
}
|
||||
|
|
|
@ -160,7 +160,7 @@ function check_hazards () {
|
|||
exit 1
|
||||
fi
|
||||
|
||||
NUM_ALLOWED_WRITE_HAZARDS=4
|
||||
NUM_ALLOWED_WRITE_HAZARDS=3
|
||||
if [ $NUM_WRITE_HAZARDS -gt $NUM_ALLOWED_WRITE_HAZARDS ]; then
|
||||
echo "TEST-UNEXPECTED-FAIL $NUM_WRITE_HAZARDS heap write hazards detected out of $NUM_ALLOWED_WRITE_HAZARDS allowed" >&2
|
||||
echo "TinderboxPrint: documentation<br/><a href='https://wiki.mozilla.org/Javascript:Hazard_Builds#Diagnosing_a_heap_write_hazard_failure'>heap write hazard analysis failures</a>, visit \"Inspect Task\" link for hazard details"
|
||||
|
|
|
@ -240,18 +240,34 @@ public:
|
|||
}
|
||||
/**
|
||||
* Mark the event as waiting reply from remote process.
|
||||
* If the caller needs to win other keyboard event handlers in chrome,
|
||||
* the caller should call StopPropagation() too.
|
||||
* Otherwise, if the caller just needs to know if the event is consumed by
|
||||
* either content or chrome, it should just call this because the event
|
||||
* may be reserved by chrome and it needs to be dispatched into the DOM
|
||||
* tree in chrome for checking if it's reserved before being sent to any
|
||||
* remote processes.
|
||||
*/
|
||||
inline void MarkAsWaitingReplyFromRemoteProcess()
|
||||
{
|
||||
MOZ_ASSERT(!mPostedToRemoteProcess);
|
||||
// When this is called, it means that event handlers in this process need
|
||||
// a reply from content in a remote process. So, callers should stop
|
||||
// propagation in this process first.
|
||||
NS_ASSERTION(PropagationStopped(),
|
||||
"Why didn't you stop propagation in this process?");
|
||||
mNoRemoteProcessDispatch = false;
|
||||
mWantReplyFromContentProcess = true;
|
||||
}
|
||||
/**
|
||||
* Reset "waiting reply from remote process" state. This is useful when
|
||||
* you dispatch a copy of an event coming from different process.
|
||||
*/
|
||||
inline void ResetWaitingReplyFromRemoteProcessState()
|
||||
{
|
||||
if (IsWaitingReplyFromRemoteProcess()) {
|
||||
// FYI: mWantReplyFromContentProcess is also used for indicating
|
||||
// "handled in remote process" state. Therefore, only when
|
||||
// IsWaitingReplyFromRemoteProcess() returns true, this should
|
||||
// reset the flag.
|
||||
mWantReplyFromContentProcess = false;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Return true if the event handler should wait reply event. I.e., if this
|
||||
* returns true, any event handler should do nothing with the event.
|
||||
|
@ -301,6 +317,12 @@ public:
|
|||
{
|
||||
MOZ_ASSERT(!IsCrossProcessForwardingStopped());
|
||||
mPostedToRemoteProcess = false;
|
||||
// Ignore propagation state in the different process if it's marked as
|
||||
// "waiting reply from remote process" because the process needs to
|
||||
// stop propagation in the process until receiving a reply event.
|
||||
if (IsWaitingReplyFromRemoteProcess()) {
|
||||
mPropagationStopped = mImmediatePropagationStopped = false;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Return true if the event has been posted to a remote process.
|
||||
|
@ -629,6 +651,14 @@ public:
|
|||
{
|
||||
mFlags.MarkAsWaitingReplyFromRemoteProcess();
|
||||
}
|
||||
/**
|
||||
* Reset "waiting reply from remote process" state. This is useful when
|
||||
* you dispatch a copy of an event coming from different process.
|
||||
*/
|
||||
inline void ResetWaitingReplyFromRemoteProcessState()
|
||||
{
|
||||
mFlags.ResetWaitingReplyFromRemoteProcessState();
|
||||
}
|
||||
/**
|
||||
* Return true if the event handler should wait reply event. I.e., if this
|
||||
* returns true, any event handler should do nothing with the event.
|
||||
|
@ -760,6 +790,11 @@ public:
|
|||
* Returns true if the event can be sent to remote process.
|
||||
*/
|
||||
bool CanBeSentToRemoteProcess() const;
|
||||
/**
|
||||
* Returns true if the original target is a remote process and the event
|
||||
* will be posted to the remote process later.
|
||||
*/
|
||||
bool WillBeSentToRemoteProcess() const;
|
||||
/**
|
||||
* Returns true if the event is native event deliverer event for plugin and
|
||||
* it should be retarted to focused document.
|
||||
|
|
|
@ -150,6 +150,8 @@ class WidgetEventTime;
|
|||
class NativeEventData;
|
||||
|
||||
// TextEvents.h
|
||||
enum class AccessKeyType;
|
||||
|
||||
struct AlternativeCharCode;
|
||||
struct ShortcutKeyCandidate;
|
||||
|
||||
|
|
|
@ -59,6 +59,16 @@ namespace plugins {
|
|||
class PPluginInstanceChild;
|
||||
} // namespace plugins
|
||||
|
||||
enum class AccessKeyType
|
||||
{
|
||||
// Handle access key for chrome.
|
||||
eChrome,
|
||||
// Handle access key for content.
|
||||
eContent,
|
||||
// Don't handle access key.
|
||||
eNone
|
||||
};
|
||||
|
||||
/******************************************************************************
|
||||
* mozilla::AlternativeCharCode
|
||||
*
|
||||
|
@ -144,7 +154,6 @@ protected:
|
|||
, mCharCode(0)
|
||||
, mPseudoCharCode(0)
|
||||
, mLocation(eKeyLocationStandard)
|
||||
, mAccessKeyForwardedToChild(false)
|
||||
, mUniqueId(0)
|
||||
#ifdef XP_MACOSX
|
||||
, mNativeModifierFlags(0)
|
||||
|
@ -173,7 +182,6 @@ public:
|
|||
, mCharCode(0)
|
||||
, mPseudoCharCode(0)
|
||||
, mLocation(eKeyLocationStandard)
|
||||
, mAccessKeyForwardedToChild(false)
|
||||
, mUniqueId(0)
|
||||
#ifdef XP_MACOSX
|
||||
, mNativeModifierFlags(0)
|
||||
|
@ -269,11 +277,6 @@ public:
|
|||
uint32_t mPseudoCharCode;
|
||||
// One of eKeyLocation*
|
||||
uint32_t mLocation;
|
||||
// True if accesskey handling was forwarded to the child via
|
||||
// TabParent::HandleAccessKey. In this case, parent process menu access key
|
||||
// handling should be delayed until it is determined that there exists no
|
||||
// overriding access key in the content process.
|
||||
bool mAccessKeyForwardedToChild;
|
||||
// Unique id associated with a keydown / keypress event. It's ok if this wraps
|
||||
// over long periods.
|
||||
uint32_t mUniqueId;
|
||||
|
@ -431,6 +434,24 @@ public:
|
|||
*/
|
||||
void GetAccessKeyCandidates(nsTArray<uint32_t>& aCandidates) const;
|
||||
|
||||
/**
|
||||
* Check whether the modifiers match with chrome access key or
|
||||
* content access key.
|
||||
*/
|
||||
bool ModifiersMatchWithAccessKey(AccessKeyType aType) const;
|
||||
|
||||
/**
|
||||
* Return active modifiers which may match with access key.
|
||||
* For example, even if Alt is access key modifier, then, when Control,
|
||||
* CapseLock and NumLock are active, this returns only MODIFIER_CONTROL.
|
||||
*/
|
||||
Modifiers ModifiersForAccessKeyMatching() const;
|
||||
|
||||
/**
|
||||
* Return access key modifiers.
|
||||
*/
|
||||
static Modifiers AccessKeyModifiers(AccessKeyType aType);
|
||||
|
||||
static void Shutdown();
|
||||
|
||||
/**
|
||||
|
@ -481,7 +502,6 @@ public:
|
|||
mAlternativeCharCodes = aEvent.mAlternativeCharCodes;
|
||||
mIsRepeat = aEvent.mIsRepeat;
|
||||
mIsComposing = aEvent.mIsComposing;
|
||||
mAccessKeyForwardedToChild = aEvent.mAccessKeyForwardedToChild;
|
||||
mKeyNameIndex = aEvent.mKeyNameIndex;
|
||||
mCodeNameIndex = aEvent.mCodeNameIndex;
|
||||
mKeyValue = aEvent.mKeyValue;
|
||||
|
@ -565,6 +585,10 @@ private:
|
|||
"Invalid native key binding type");
|
||||
}
|
||||
}
|
||||
|
||||
static int32_t GenericAccessModifierKeyPref();
|
||||
static int32_t ChromeAccessModifierMaskPref();
|
||||
static int32_t ContentAccessModifierMaskPref();
|
||||
};
|
||||
|
||||
/******************************************************************************
|
||||
|
|
|
@ -6,12 +6,14 @@
|
|||
#include "gfxPrefs.h"
|
||||
#include "mozilla/BasicEvents.h"
|
||||
#include "mozilla/ContentEvents.h"
|
||||
#include "mozilla/EventStateManager.h"
|
||||
#include "mozilla/InternalMutationEvent.h"
|
||||
#include "mozilla/MiscEvents.h"
|
||||
#include "mozilla/MouseEvents.h"
|
||||
#include "mozilla/Preferences.h"
|
||||
#include "mozilla/TextEvents.h"
|
||||
#include "mozilla/TouchEvents.h"
|
||||
#include "nsIContent.h"
|
||||
#include "nsIDOMEventTarget.h"
|
||||
#include "nsPrintfCString.h"
|
||||
|
||||
|
@ -349,6 +351,24 @@ WidgetEvent::CanBeSentToRemoteProcess() const
|
|||
}
|
||||
}
|
||||
|
||||
bool
|
||||
WidgetEvent::WillBeSentToRemoteProcess() const
|
||||
{
|
||||
// This event won't be posted to remote process if it's already explicitly
|
||||
// stopped.
|
||||
if (IsCrossProcessForwardingStopped()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// When mOriginalTarget is nullptr, this method shouldn't be used.
|
||||
if (NS_WARN_IF(!mOriginalTarget)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
nsCOMPtr<nsIContent> originalTarget = do_QueryInterface(mOriginalTarget);
|
||||
return EventStateManager::IsRemoteTarget(originalTarget);
|
||||
}
|
||||
|
||||
bool
|
||||
WidgetEvent::IsRetargetedNativeEventDelivererForPlugin() const
|
||||
{
|
||||
|
@ -869,6 +889,128 @@ WidgetKeyboardEvent::GetAccessKeyCandidates(nsTArray<uint32_t>& aCandidates) con
|
|||
}
|
||||
}
|
||||
|
||||
// mask values for ui.key.chromeAccess and ui.key.contentAccess
|
||||
#define NS_MODIFIER_SHIFT 1
|
||||
#define NS_MODIFIER_CONTROL 2
|
||||
#define NS_MODIFIER_ALT 4
|
||||
#define NS_MODIFIER_META 8
|
||||
#define NS_MODIFIER_OS 16
|
||||
|
||||
static Modifiers PrefFlagsToModifiers(int32_t aPrefFlags)
|
||||
{
|
||||
Modifiers result = 0;
|
||||
if (aPrefFlags & NS_MODIFIER_SHIFT) {
|
||||
result |= MODIFIER_SHIFT;
|
||||
}
|
||||
if (aPrefFlags & NS_MODIFIER_CONTROL) {
|
||||
result |= MODIFIER_CONTROL;
|
||||
}
|
||||
if (aPrefFlags & NS_MODIFIER_ALT) {
|
||||
result |= MODIFIER_ALT;
|
||||
}
|
||||
if (aPrefFlags & NS_MODIFIER_META) {
|
||||
result |= MODIFIER_META;
|
||||
}
|
||||
if (aPrefFlags & NS_MODIFIER_OS) {
|
||||
result |= MODIFIER_OS;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
bool
|
||||
WidgetKeyboardEvent::ModifiersMatchWithAccessKey(AccessKeyType aType) const
|
||||
{
|
||||
if (!ModifiersForAccessKeyMatching()) {
|
||||
return false;
|
||||
}
|
||||
return ModifiersForAccessKeyMatching() == AccessKeyModifiers(aType);
|
||||
}
|
||||
|
||||
Modifiers
|
||||
WidgetKeyboardEvent::ModifiersForAccessKeyMatching() const
|
||||
{
|
||||
static const Modifiers kModifierMask =
|
||||
MODIFIER_SHIFT | MODIFIER_CONTROL |
|
||||
MODIFIER_ALT | MODIFIER_META | MODIFIER_OS;
|
||||
return mModifiers & kModifierMask;
|
||||
}
|
||||
|
||||
/* static */
|
||||
Modifiers
|
||||
WidgetKeyboardEvent::AccessKeyModifiers(AccessKeyType aType)
|
||||
{
|
||||
switch (GenericAccessModifierKeyPref()) {
|
||||
case -1:
|
||||
break; // use the individual prefs
|
||||
case NS_VK_SHIFT:
|
||||
return MODIFIER_SHIFT;
|
||||
case NS_VK_CONTROL:
|
||||
return MODIFIER_CONTROL;
|
||||
case NS_VK_ALT:
|
||||
return MODIFIER_ALT;
|
||||
case NS_VK_META:
|
||||
return MODIFIER_META;
|
||||
case NS_VK_WIN:
|
||||
return MODIFIER_OS;
|
||||
default:
|
||||
return MODIFIER_NONE;
|
||||
}
|
||||
|
||||
switch (aType) {
|
||||
case AccessKeyType::eChrome:
|
||||
return PrefFlagsToModifiers(ChromeAccessModifierMaskPref());
|
||||
case AccessKeyType::eContent:
|
||||
return PrefFlagsToModifiers(ContentAccessModifierMaskPref());
|
||||
default:
|
||||
return MODIFIER_NONE;
|
||||
}
|
||||
}
|
||||
|
||||
/* static */
|
||||
int32_t
|
||||
WidgetKeyboardEvent::GenericAccessModifierKeyPref()
|
||||
{
|
||||
static bool sInitialized = false;
|
||||
static int32_t sValue = -1;
|
||||
if (!sInitialized) {
|
||||
nsresult rv =
|
||||
Preferences::AddIntVarCache(&sValue, "ui.key.generalAccessKey", sValue);
|
||||
sInitialized = NS_SUCCEEDED(rv);
|
||||
MOZ_ASSERT(sInitialized);
|
||||
}
|
||||
return sValue;
|
||||
}
|
||||
|
||||
/* static */
|
||||
int32_t
|
||||
WidgetKeyboardEvent::ChromeAccessModifierMaskPref()
|
||||
{
|
||||
static bool sInitialized = false;
|
||||
static int32_t sValue = 0;
|
||||
if (!sInitialized) {
|
||||
nsresult rv =
|
||||
Preferences::AddIntVarCache(&sValue, "ui.key.chromeAccess", sValue);
|
||||
sInitialized = NS_SUCCEEDED(rv);
|
||||
MOZ_ASSERT(sInitialized);
|
||||
}
|
||||
return sValue;
|
||||
}
|
||||
|
||||
/* static */
|
||||
int32_t
|
||||
WidgetKeyboardEvent::ContentAccessModifierMaskPref()
|
||||
{
|
||||
static bool sInitialized = false;
|
||||
static int32_t sValue = 0;
|
||||
if (!sInitialized) {
|
||||
nsresult rv =
|
||||
Preferences::AddIntVarCache(&sValue, "ui.key.contentAccess", sValue);
|
||||
sInitialized = NS_SUCCEEDED(rv);
|
||||
MOZ_ASSERT(sInitialized);
|
||||
}
|
||||
return sValue;
|
||||
}
|
||||
|
||||
/* static */ void
|
||||
WidgetKeyboardEvent::Shutdown()
|
||||
{
|
||||
|
|
|
@ -2815,7 +2815,12 @@ IMEInputHandler::WillDispatchKeyboardEvent(
|
|||
(!insertString || insertString->IsEmpty())) {
|
||||
// Inform the child process that this is an event that we want a reply
|
||||
// from.
|
||||
aKeyboardEvent.mFlags.mWantReplyFromContentProcess = true;
|
||||
// XXX This should be called only when the target is a remote process.
|
||||
// However, it's difficult to check it under widget/.
|
||||
// So, let's do this here for now, then,
|
||||
// EventStateManager::PreHandleEvent() will reset the flags if
|
||||
// the event target isn't in remote process.
|
||||
aKeyboardEvent.MarkAsWaitingReplyFromRemoteProcess();
|
||||
}
|
||||
if (KeyboardLayoutOverrideRef().mOverrideEnabled) {
|
||||
TISInputSourceWrapper tis;
|
||||
|
|
|
@ -449,7 +449,6 @@ struct ParamTraits<mozilla::WidgetKeyboardEvent>
|
|||
WriteParam(aMsg, aParam.mPseudoCharCode);
|
||||
WriteParam(aMsg, aParam.mAlternativeCharCodes);
|
||||
WriteParam(aMsg, aParam.mIsRepeat);
|
||||
WriteParam(aMsg, aParam.mAccessKeyForwardedToChild);
|
||||
WriteParam(aMsg, aParam.mLocation);
|
||||
WriteParam(aMsg, aParam.mUniqueId);
|
||||
WriteParam(aMsg, aParam.mIsSynthesizedByTIP);
|
||||
|
@ -487,7 +486,6 @@ struct ParamTraits<mozilla::WidgetKeyboardEvent>
|
|||
ReadParam(aMsg, aIter, &aResult->mPseudoCharCode) &&
|
||||
ReadParam(aMsg, aIter, &aResult->mAlternativeCharCodes) &&
|
||||
ReadParam(aMsg, aIter, &aResult->mIsRepeat) &&
|
||||
ReadParam(aMsg, aIter, &aResult->mAccessKeyForwardedToChild) &&
|
||||
ReadParam(aMsg, aIter, &aResult->mLocation) &&
|
||||
ReadParam(aMsg, aIter, &aResult->mUniqueId) &&
|
||||
ReadParam(aMsg, aIter, &aResult->mIsSynthesizedByTIP) &&
|
||||
|
|
Загрузка…
Ссылка в новой задаче