Merge mozilla-central to inbound a=merge

This commit is contained in:
Coroiu Cristina 2018-09-28 07:37:58 +03:00
Родитель 48a77c4938 0ae7e1ad7b
Коммит 35b751d9cd
93 изменённых файлов: 10929 добавлений и 2913 удалений

Просмотреть файл

@ -604,6 +604,8 @@ class ContextMenuChild extends ActorChild {
parentAllowsMixedContent,
};
Services.obs.notifyObservers({wrappedJSObject: data}, "on-prepare-contextmenu");
if (isRemote) {
this.mm.sendAsyncMessage("contextmenu", data, {
targetAsCPOW,

Просмотреть файл

@ -47,10 +47,6 @@
min-width: -moz-fit-content;
}
searchbar {
-moz-binding: url("chrome://browser/content/search/search.xml#searchbar");
}
.searchbar-textbox {
-moz-binding: url("chrome://browser/content/search/search.xml#searchbar-textbox");
}

Просмотреть файл

@ -16,6 +16,7 @@ Components.utils.import("resource://gre/modules/Services.jsm");
for (let script of [
"chrome://browser/content/browser.js",
"chrome://browser/content/search/searchbar.js",
"chrome://browser/content/browser-captivePortal.js",
"chrome://browser/content/browser-compacttheme.js",

Просмотреть файл

@ -65,6 +65,7 @@ function openContextMenu(aMessage) {
loginFillInfo: data.loginFillInfo,
parentAllowsMixedContent: data.parentAllowsMixedContent,
userContextId: data.userContextId,
webExtContextData: data.webExtContextData,
};
let popup = browser.ownerDocument.getElementById("contentAreaContextMenu");
@ -128,6 +129,7 @@ nsContextMenu.prototype = {
linkUrl: this.linkURL,
selectionText: this.isTextSelected ? this.selectionInfo.fullText : undefined,
frameId: this.frameOuterWindowID,
webExtContextData: gContextMenuContentData ? gContextMenuContentData.webExtContextData : undefined,
};
subject.wrappedJSObject = subject;
Services.obs.notifyObservers(subject, "on-build-contextmenu");
@ -167,7 +169,7 @@ nsContextMenu.prototype = {
this.shouldDisplay = context.shouldDisplay;
this.timeStamp = context.timeStamp;
// Assign what's _possibly_ needed from `context` sent by ContextMenu.jsm
// Assign what's _possibly_ needed from `context` sent by ContextMenuChild.jsm
// Keep this consistent with the similar code in ContextMenu's _setContext
this.bgImageURL = context.bgImageURL;
this.imageDescURL = context.imageDescURL;

Просмотреть файл

@ -108,4 +108,4 @@ support-files =
[browser_iframe_navigation.js]
support-files =
iframe_navigation.html
[browser_navigation_failures.js]
[browser_tls_handshake_failure.js]

Просмотреть файл

@ -1,41 +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: */
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
// Tests that the site identity indicator is properly updated for navigations
// that fail for various reasons. In particular, we currently test TLS handshake
// failures and about: pages that don't actually exist.
// See bug 1492424 and bug 1493427.
const kSecureURI = getRootDirectory(gTestPath).replace("chrome://mochitests/content",
"https://example.com") + "dummy_page.html";
add_task(async function() {
await BrowserTestUtils.withNewTab(kSecureURI, async (browser) => {
let identityMode = window.document.getElementById("identity-box").className;
is(identityMode, "verifiedDomain", "identity should be secure before");
const TLS_HANDSHAKE_FAILURE_URI = "https://ssl3.example.com/";
// Try to connect to a server where the TLS handshake will fail.
BrowserTestUtils.loadURI(browser, TLS_HANDSHAKE_FAILURE_URI);
await BrowserTestUtils.browserLoaded(browser, false, TLS_HANDSHAKE_FAILURE_URI, true);
let newIdentityMode = window.document.getElementById("identity-box").className;
is(newIdentityMode, "unknownIdentity", "identity should be unknown (not secure) after");
});
});
add_task(async function() {
await BrowserTestUtils.withNewTab(kSecureURI, async (browser) => {
let identityMode = window.document.getElementById("identity-box").className;
is(identityMode, "verifiedDomain", "identity should be secure before");
const BAD_ABOUT_PAGE_URI = "about:somethingthatdoesnotexist";
// Try to load an about: page that doesn't exist
BrowserTestUtils.loadURI(browser, BAD_ABOUT_PAGE_URI);
await BrowserTestUtils.browserLoaded(browser, false, BAD_ABOUT_PAGE_URI, true);
let newIdentityMode = window.document.getElementById("identity-box").className;
is(newIdentityMode, "unknownIdentity", "identity should be unknown (not secure) after");
});
});

Просмотреть файл

@ -0,0 +1,25 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
// Tests that the site identity indicator is properly updated for connections
// where the TLS handshake fails.
// See bug 1492424.
add_task(async function() {
let rootURI = getRootDirectory(gTestPath).replace("chrome://mochitests/content",
"https://example.com");
await BrowserTestUtils.withNewTab(rootURI + "dummy_page.html", async (browser) => {
let identityMode = window.document.getElementById("identity-box").className;
is(identityMode, "verifiedDomain", "identity should be secure before");
const TLS_HANDSHAKE_FAILURE_URI = "https://ssl3.example.com/";
// Try to connect to a server where the TLS handshake will fail.
BrowserTestUtils.loadURI(browser, TLS_HANDSHAKE_FAILURE_URI);
await BrowserTestUtils.browserLoaded(browser, false, TLS_HANDSHAKE_FAILURE_URI, true);
let newIdentityMode = window.document.getElementById("identity-box").className;
is(newIdentityMode, "unknownIdentity", "identity should be unknown (not secure) after");
});
});

Просмотреть файл

@ -26,9 +26,8 @@ async function test_opensearch(shouldWork) {
let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, rootDir + "opensearch.html");
let searchPopup = document.getElementById("PopupSearchAutoComplete");
let promiseSearchPopupShown = BrowserTestUtils.waitForEvent(searchPopup, "popupshown");
let searchBarButton = document.getAnonymousElementByAttribute(searchBar,
"anonid",
"searchbar-search-button");
let searchBarButton = searchBar.querySelector(".searchbar-search-button");
searchBarButton.click();
await promiseSearchPopupShown;
let oneOffsContainer = document.getAnonymousElementByAttribute(searchPopup,

Просмотреть файл

@ -2,10 +2,17 @@
/* vim: set sts=2 sw=2 et tw=80: */
"use strict";
ChromeUtils.defineModuleGetter(this, "Services",
"resource://gre/modules/Services.jsm");
var {
withHandlingUserInput,
} = ExtensionCommon;
var {
ExtensionError,
} = ExtensionUtils;
// If id is not specified for an item we use an integer.
// This ID need only be unique within a single addon. Since all addon code that
// can use this API runs in the same process, this local variable suffices.
@ -108,6 +115,7 @@ class ContextMenusClickPropHandler {
this.menusInternal = class extends ExtensionAPI {
getAPI(context) {
let onClickedProp = new ContextMenusClickPropHandler(context);
let pendingMenuEvent;
let api = {
menus: {
@ -165,6 +173,72 @@ this.menusInternal = class extends ExtensionAPI {
return context.childManager.callParentAsyncFunction("menusInternal.removeAll", []);
},
overrideContext(contextOptions) {
let {event} = context.contentWindow;
if (!event || event.type !== "contextmenu" || !event.isTrusted) {
throw new ExtensionError("overrideContext must be called during a \"contextmenu\" event");
}
let checkValidArg = (contextType, propKey) => {
if (contextOptions.context !== contextType) {
if (contextOptions[propKey]) {
throw new ExtensionError(`Property "${propKey}" can only be used with context "${contextType}"`);
}
return false;
}
if (contextOptions.showDefaults) {
throw new ExtensionError(`Property "showDefaults" cannot be used with context "${contextType}"`);
}
if (!contextOptions[propKey]) {
throw new ExtensionError(`Property "${propKey}" is required for context "${contextType}"`);
}
return true;
};
if (checkValidArg("tab", "tabId")) {
if (!context.extension.hasPermission("tabs")) {
throw new ExtensionError(`The "tab" context requires the "tabs" permission.`);
}
}
if (checkValidArg("bookmark", "bookmarkId")) {
if (!context.extension.hasPermission("bookmarks")) {
throw new ExtensionError(`The "bookmark" context requires the "bookmarks" permission.`);
}
}
let webExtContextData = {
extensionId: context.extension.id,
showDefaults: contextOptions.showDefaults,
overrideContext: contextOptions.context,
bookmarkId: contextOptions.bookmarkId,
tabId: contextOptions.tabId,
};
if (pendingMenuEvent) {
// overrideContext is called more than once during the same event.
pendingMenuEvent.webExtContextData = webExtContextData;
return;
}
pendingMenuEvent = {
webExtContextData,
observe(subject, topic, data) {
pendingMenuEvent = null;
Services.obs.removeObserver(this, "on-prepare-contextmenu");
subject.wrappedJSObject.webExtContextData = this.webExtContextData;
},
run() {
// "on-prepare-contextmenu" is expected to be observed before the
// end of the "contextmenu" event dispatch. This task is queued
// in case that does not happen, e.g. when the menu is not shown.
if (pendingMenuEvent === this) {
pendingMenuEvent = null;
Services.obs.removeObserver(this, "on-prepare-contextmenu");
}
},
};
Services.obs.addObserver(pendingMenuEvent, "on-prepare-contextmenu");
Services.tm.dispatchToMainThread(pendingMenuEvent);
},
onClicked: new EventManager({
context,
name: "menus.onClicked",

Просмотреть файл

@ -47,44 +47,103 @@ var gMenuBuilder = {
// to be displayed. We always clear all the items again when
// popuphidden fires.
build(contextData) {
contextData = this.maybeOverrideContextData(contextData);
let xulMenu = contextData.menu;
xulMenu.addEventListener("popuphidden", this);
this.xulMenu = xulMenu;
for (let [, root] of gRootItems) {
let rootElement = this.createTopLevelElement(root, contextData);
if (rootElement) {
this.appendTopLevelElement(rootElement);
}
this.createAndInsertTopLevelElements(root, contextData, null);
}
this.afterBuildingMenu(contextData);
if (contextData.webExtContextData && !contextData.webExtContextData.showDefaults) {
// Wait until nsContextMenu.js has toggled the visibility of the default
// menu items before hiding the default items.
Promise.resolve().then(() => this.hideDefaultMenuItems());
}
},
// Builds a context menu for browserAction and pageAction buttons.
buildActionContextMenu(contextData) {
const {menu} = contextData;
maybeOverrideContextData(contextData) {
let {webExtContextData} = contextData;
if (!webExtContextData || !webExtContextData.overrideContext) {
return contextData;
}
if (webExtContextData.overrideContext === "bookmark") {
return {
menu: contextData.menu,
bookmarkId: webExtContextData.bookmarkId,
onBookmark: true,
webExtContextData,
};
}
if (webExtContextData.overrideContext === "tab") {
// TODO: Handle invalid tabs more gracefully (instead of throwing).
let tab = tabTracker.getTab(webExtContextData.tabId);
return {
menu: contextData.menu,
tab,
pageUrl: tab.linkedBrowser.currentURI.spec,
onTab: true,
webExtContextData,
};
}
throw new Error(`Unexpected overrideContext: ${webExtContextData.overrideContext}`);
},
const root = gRootItems.get(contextData.extension);
if (!root) {
createAndInsertTopLevelElements(root, contextData, nextSibling) {
let rootElements;
if (contextData.onBrowserAction || contextData.onPageAction) {
if (contextData.extension.id !== root.extension.id) {
return;
}
rootElements = this.buildTopLevelElements(root, contextData, ACTION_MENU_TOP_LEVEL_LIMIT, false);
// Action menu items are prepended to the menu, followed by a separator.
nextSibling = nextSibling || this.xulMenu.firstElementChild;
if (rootElements.length && !this.itemsToCleanUp.has(nextSibling)) {
rootElements.push(this.xulMenu.ownerDocument.createXULElement("menuseparator"));
}
} else if (contextData.webExtContextData) {
let {
extensionId,
showDefaults,
overrideContext,
} = contextData.webExtContextData;
if (extensionId === root.extension.id) {
rootElements = this.buildTopLevelElements(root, contextData, Infinity, false);
// The extension menu should be rendered at the top, but after the navigation buttons.
nextSibling = nextSibling || this.xulMenu.querySelector(":scope > #context-sep-navigation + *");
if (rootElements.length && showDefaults && !this.itemsToCleanUp.has(nextSibling)) {
rootElements.push(this.xulMenu.ownerDocument.createXULElement("menuseparator"));
}
} else if (!showDefaults && !overrideContext) {
// When the default menu items should be hidden, menu items from other
// extensions should be hidden too.
return;
}
// Fall through to show default extension menu items.
}
if (!rootElements) {
rootElements = this.buildTopLevelElements(root, contextData, 1, true);
if (rootElements.length && !this.itemsToCleanUp.has(this.xulMenu.lastElementChild)) {
// All extension menu items are appended at the end.
// Prepend separator if this is the first extension menu item.
rootElements.unshift(this.xulMenu.ownerDocument.createXULElement("menuseparator"));
}
}
if (!rootElements.length) {
return;
}
const children = this.buildChildren(root, contextData);
const visible = children.slice(0, ACTION_MENU_TOP_LEVEL_LIMIT);
this.xulMenu = menu;
menu.addEventListener("popuphidden", this);
if (visible.length) {
const separator = menu.ownerDocument.createXULElement("menuseparator");
menu.insertBefore(separator, menu.firstElementChild);
this.itemsToCleanUp.add(separator);
for (const child of visible) {
this.itemsToCleanUp.add(child);
menu.insertBefore(child, separator);
}
if (nextSibling) {
nextSibling.before(...rootElements);
} else {
this.xulMenu.append(...rootElements);
}
for (let item of rootElements) {
this.itemsToCleanUp.add(item);
}
this.afterBuildingMenu(contextData);
},
buildElementWithChildren(item, contextData) {
@ -116,63 +175,58 @@ var gMenuBuilder = {
return children;
},
createTopLevelElement(root, contextData) {
let rootElement = this.buildElementWithChildren(root, contextData);
if (!rootElement.firstElementChild || !rootElement.firstElementChild.children.length) {
// If the root has no visible children, there is no reason to show
// the root menu item itself either.
return null;
}
rootElement.setAttribute("ext-type", "top-level-menu");
rootElement = this.removeTopLevelMenuIfNeeded(rootElement);
buildTopLevelElements(root, contextData, maxCount, forceManifestIcons) {
let children = this.buildChildren(root, contextData);
// Display the extension icon on the root element.
if (root.extension.manifest.icons) {
this.setMenuItemIcon(rootElement, root.extension, contextData, root.extension.manifest.icons);
} else {
this.removeMenuItemIcon(rootElement);
}
return rootElement;
},
appendTopLevelElement(rootElement) {
if (this.itemsToCleanUp.size === 0) {
const separator = this.xulMenu.ownerDocument.createXULElement("menuseparator");
this.itemsToCleanUp.add(separator);
this.xulMenu.append(separator);
// TODO: Fix bug 1492969 and remove this whole if block.
if (children.length === 1 && maxCount === 1 && forceManifestIcons &&
AppConstants.platform === "linux" &&
children[0].getAttribute("type") === "checkbox") {
// Keep single checkbox items in the submenu on Linux since
// the extension icon overlaps the checkbox otherwise.
maxCount = 0;
}
this.xulMenu.appendChild(rootElement);
this.itemsToCleanUp.add(rootElement);
if (children.length > maxCount) {
// Move excess items into submenu.
let rootElement = this.buildSingleElement(root, contextData);
rootElement.setAttribute("ext-type", "top-level-menu");
rootElement.firstElementChild.append(...children.splice(maxCount - 1));
children.push(rootElement);
}
if (forceManifestIcons) {
for (let rootElement of children) {
// Display the extension icon on the root element.
if (root.extension.manifest.icons) {
this.setMenuItemIcon(rootElement, root.extension, contextData, root.extension.manifest.icons);
} else {
this.removeMenuItemIcon(rootElement);
}
}
}
return children;
},
removeSeparatorIfNoTopLevelItems() {
if (this.itemsToCleanUp.size === 1) {
// Remove the separator if all extension menu items have disappeared.
const separator = this.itemsToCleanUp.values().next().value;
separator.remove();
this.itemsToCleanUp.clear();
}
},
// Extension menu items always have have a non-empty ID.
let isNonExtensionSeparator =
item => item.nodeName === "menuseparator" && !item.id;
removeTopLevelMenuIfNeeded(element) {
// If there is only one visible top level element we don't need the
// root menu element for the extension.
let menuPopup = element.firstElementChild;
if (menuPopup && menuPopup.children.length == 1) {
let onlyChild = menuPopup.firstElementChild;
// itemsToCleanUp contains all top-level menu items. A separator should
// only be kept if it is next to an extension menu item.
let isExtensionMenuItemSibling =
item => item && this.itemsToCleanUp.has(item) && !isNonExtensionSeparator(item);
// Keep single checkbox items in the submenu on Linux since
// the extension icon overlaps the checkbox otherwise.
if (AppConstants.platform === "linux" && onlyChild.getAttribute("type") === "checkbox") {
return element;
for (let item of this.itemsToCleanUp) {
if (isNonExtensionSeparator(item)) {
if (!isExtensionMenuItemSibling(item.previousElementSibling) &&
!isExtensionMenuItemSibling(item.nextElementSibling)) {
item.remove();
this.itemsToCleanUp.delete(item);
}
}
onlyChild.remove();
return onlyChild;
}
return element;
},
buildSingleElement(item, contextData) {
@ -294,7 +348,11 @@ var gMenuBuilder = {
item.checked = true;
}
if (contextData.tab) {
let {webExtContextData} = contextData;
if (contextData.tab &&
// If the menu context was overridden by the extension, do not grant
// activeTab since the extension also controls the tabId.
(!webExtContextData || webExtContextData.extensionId !== item.extension.id)) {
item.tabManager.addActiveTabPermission(contextData.tab);
}
@ -377,56 +435,27 @@ var gMenuBuilder = {
return;
}
if (contextData.onBrowserAction || contextData.onPageAction) {
if (contextData.extension.id !== extension.id) {
// The extension that just called refresh() is not the owner of the
// action whose context menu is showing, so it can't have any items in
// the menu anyway and nothing will change.
return;
}
// The action menu can only have items from one extension, so remove all
// items (including the separator) and rebuild the action menu (if any).
for (let item of this.itemsToCleanUp) {
item.remove();
}
this.itemsToCleanUp.clear();
this.buildActionContextMenu(contextData);
return;
}
// First find the one and only top-level menu item for the extension.
// Find the group of existing top-level items (usually 0 or 1 items)
// and remember its position for when the new items are inserted.
let elementIdPrefix = `${makeWidgetId(extension.id)}-menuitem-`;
let oldRoot = null;
for (let item = this.xulMenu.lastElementChild; item !== null; item = item.previousElementSibling) {
let nextSibling = null;
for (let item of this.itemsToCleanUp) {
if (item.id && item.id.startsWith(elementIdPrefix)) {
oldRoot = item;
this.itemsToCleanUp.delete(oldRoot);
break;
nextSibling = item.nextSibling;
item.remove();
this.itemsToCleanUp.delete(item);
}
}
let root = gRootItems.get(extension);
let newRoot = root && this.createTopLevelElement(root, contextData);
if (newRoot) {
this.itemsToCleanUp.add(newRoot);
if (oldRoot) {
oldRoot.replaceWith(newRoot);
} else {
this.appendTopLevelElement(newRoot);
}
} else if (oldRoot) {
oldRoot.remove();
this.removeSeparatorIfNoTopLevelItems();
if (root) {
this.createAndInsertTopLevelElements(root, contextData, nextSibling);
}
this.removeSeparatorIfNoTopLevelItems();
},
// This should be called once, after constructing the top-level menus, if any.
afterBuildingMenu(contextData) {
if (this.contextData) {
// rebuildMenu can trigger us again, but the logic below should run only
// once per open menu.
return;
}
function dispatchOnShownEvent(extension) {
// Note: gShownMenuItems is a DefaultMap, so .get(extension) causes the
// extension to be stored in the map even if there are currently no
@ -445,6 +474,14 @@ var gMenuBuilder = {
this.contextData = contextData;
},
hideDefaultMenuItems() {
for (let item of this.xulMenu.children) {
if (!this.itemsToCleanUp.has(item)) {
item.hidden = true;
}
}
},
handleEvent(event) {
if (this.xulMenu != event.target || event.type != "popuphidden") {
return;
@ -472,7 +509,7 @@ var gMenuBuilder = {
global.actionContextMenu = function(contextData) {
contextData.tab = tabTracker.activeTab;
contextData.pageUrl = contextData.tab.linkedBrowser.currentURI.spec;
gMenuBuilder.buildActionContextMenu(contextData);
gMenuBuilder.build(contextData);
};
const contextsMap = {

Просмотреть файл

@ -15,6 +15,14 @@
"contextMenus"
]
}]
}, {
"$extend": "OptionalPermission",
"choices": [{
"type": "string",
"enum": [
"menus.overrideContext"
]
}]
}
]
},
@ -402,6 +410,44 @@
}
]
},
{
"name": "overrideContext",
"permissions": ["menus.overrideContext"],
"type": "function",
"description": "Show the matching menu items from this extension instead of the default menu. This should be called during a 'contextmenu' DOM event handler, and only applies to the menu that opens after this event.",
"parameters": [
{
"name": "contextOptions",
"type": "object",
"properties": {
"showDefaults": {
"type": "boolean",
"optional": true,
"default": false,
"description": "Whether to also include default menu items in the menu."
},
"context": {
"type": "string",
"enum": ["bookmark", "tab"],
"optional": true,
"description": "ContextType to override, to allow menu items from other extensions in the menu. Currently only 'bookmark' and 'tab' are supported. showDefaults cannot be used with this option."
},
"bookmarkId": {
"type": "string",
"minLength": 1,
"optional": true,
"description": "Required when context is 'bookmark'. Requires 'bookmark' permission."
},
"tabId": {
"type": "integer",
"minimum": 0,
"optional": true,
"description": "Required when context is 'tab'. Requires 'tabs' permission."
}
}
}
]
},
{
"name": "refresh",
"type": "function",

Просмотреть файл

@ -116,6 +116,9 @@ skip-if = (verify && (os == 'linux' || os == 'mac'))
[browser_ext_menus_event_order.js]
[browser_ext_menus_events.js]
[browser_ext_menus_refresh.js]
[browser_ext_menus_replace_menu.js]
[browser_ext_menus_replace_menu_context.js]
[browser_ext_menus_replace_menu_permissions.js]
[browser_ext_menus_targetElement.js]
[browser_ext_menus_targetElement_extension.js]
[browser_ext_menus_targetElement_shadow.js]

Просмотреть файл

@ -96,11 +96,18 @@ add_task(async function test_actionContextMenus() {
is(second.label, "click 1", "Second menu item title is correct");
is(second.id, `${idPrefix}1`, "Second menu item id is correct");
is(last.label, "click 5", "Last menu item title is correct");
is(last.id, `${idPrefix}5`, "Last menu item id is correct");
is(last.tagName, "menu", "Last menu item type is correct");
is(last.label, "Generated extension", "Last menu item title is correct");
is(last.getAttribute("ext-type"), "top-level-menu", "Last menu ext-type is correct");
is(separator.tagName, "menuseparator", "Separator after last menu item");
await closeActionContextMenu(popup.firstElementChild, kind);
// Verify that menu items exceeding ACTION_MENU_TOP_LEVEL_LIMIT are moved into a submenu.
let overflowPopup = await openSubmenu(last);
is(overflowPopup.children.length, 4, "Excess items should be moved into a submenu");
is(overflowPopup.firstElementChild.id, `${idPrefix}5`, "First submenu item ID is correct");
is(overflowPopup.lastElementChild.id, `${idPrefix}8`, "Last submenu item ID is correct");
await closeActionContextMenu(overflowPopup.firstElementChild, kind);
const {info, tab} = await extension.awaitMessage("click");
is(info.pageUrl, "http://example.com/", "Click info pageUrl is correct");
is(tab.id, tabId, "Click event tab ID is correct");

Просмотреть файл

@ -0,0 +1,325 @@
/* 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/. */
"use strict";
function getVisibleChildrenIds(menuElem) {
return Array.from(menuElem.children).filter(elem => !elem.hidden).map(elem => elem.id || elem.tagName);
}
function checkIsDefaultMenuItemVisible(visibleMenuItemIds) {
// In this whole test file, we open a menu on a link. Assume that all
// default menu items are shown if one link-specific menu item is shown.
ok(visibleMenuItemIds.includes("context-openlink"),
`The default 'Open Link in New Tab' menu item should be in ${visibleMenuItemIds}.`);
}
// Tests the following:
// - Calling overrideContext({}) during oncontextmenu forces the menu to only
// show an extension's own items.
// - These menu items all appear in the root menu.
// - The usual extension filtering behavior (e.g. documentUrlPatterns and
// targetUrlPatterns) is still applied; some menu items are therefore hidden.
// - Calling overrideContext({showDefaults:true}) causes the default menu items
// to be shown, but only after the extension's.
// - overrideContext expires after the menu is opened once.
add_task(async function overrideContext_in_extension_tab() {
function extensionTabScript() {
document.addEventListener("contextmenu", () => {
browser.menus.overrideContext({});
browser.test.sendMessage("oncontextmenu_in_dom_part_1");
}, {once: true});
browser.menus.create({
id: "tab_1",
title: "tab_1",
documentUrlPatterns: [document.URL],
onclick() {
document.addEventListener("contextmenu", () => {
// Verifies that last call takes precedence.
browser.menus.overrideContext({showDefaults: false});
browser.menus.overrideContext({showDefaults: true});
browser.test.sendMessage("oncontextmenu_in_dom_part_2");
}, {once: true});
browser.test.sendMessage("onClicked_tab_1");
},
});
browser.menus.create({
id: "tab_2",
title: "tab_2",
onclick() {
browser.test.sendMessage("onClicked_tab_2");
},
}, () => {
browser.test.sendMessage("menu-registered");
});
}
let extension = ExtensionTestUtils.loadExtension({
manifest: {
permissions: ["menus", "menus.overrideContext"],
},
files: {
"tab.html": `
<!DOCTYPE html><meta charset="utf-8">
<a href="http://example.com/">Link</a>
<script src="tab.js"></script>
`,
"tab.js": extensionTabScript,
},
background() {
// Expected to match and thus be visible.
browser.menus.create({id: "bg_1", title: "bg_1"});
browser.menus.create({id: "bg_2", title: "bg_2", targetUrlPatterns: ["*://example.com/*"]});
// Expected to not match and be hidden.
browser.menus.create({id: "bg_3", title: "bg_3", targetUrlPatterns: ["*://nomatch/*"]});
browser.menus.create({id: "bg_4", title: "bg_4", documentUrlPatterns: [document.URL]});
browser.menus.onShown.addListener(info => {
browser.test.assertEq("bg_1,bg_2,tab_1,tab_2", info.menuIds.join(","), "Expected menu items.");
browser.test.sendMessage("onShown");
});
browser.tabs.create({url: "tab.html"});
},
});
let otherExtension = ExtensionTestUtils.loadExtension({
manifest: {
permissions: ["menus"],
},
background() {
browser.menus.create({id: "other_extension_item", title: "other_extension_item"}, () => {
browser.test.sendMessage("other_extension_item_created");
});
},
});
await otherExtension.startup();
await otherExtension.awaitMessage("other_extension_item_created");
let extensionTabPromise = BrowserTestUtils.waitForNewTab(gBrowser, null, true);
await extension.startup();
// Must wait for the tab to have loaded completely before calling openContextMenu.
await extensionTabPromise;
await extension.awaitMessage("menu-registered");
const EXPECTED_EXTENSION_MENU_IDS = [
`${makeWidgetId(extension.id)}-menuitem-_bg_1`,
`${makeWidgetId(extension.id)}-menuitem-_bg_2`,
`${makeWidgetId(extension.id)}-menuitem-_tab_1`,
`${makeWidgetId(extension.id)}-menuitem-_tab_2`,
];
const OTHER_EXTENSION_MENU_ID =
`${makeWidgetId(otherExtension.id)}-menuitem-_other_extension_item`;
{
// Tests overrideContext({})
info("Expecting the menu to be replaced by overrideContext.");
let menu = await openContextMenu("a");
await extension.awaitMessage("oncontextmenu_in_dom_part_1");
await extension.awaitMessage("onShown");
Assert.deepEqual(
getVisibleChildrenIds(menu),
EXPECTED_EXTENSION_MENU_IDS,
"Expected only extension menu items");
let menuItems = menu.getElementsByAttribute("label", "tab_1");
await closeExtensionContextMenu(menuItems[0]);
await extension.awaitMessage("onClicked_tab_1");
}
{
// Tests overrideContext({showDefaults:true}))
info("Expecting the menu to be replaced by overrideContext, including default menu items.");
let menu = await openContextMenu("a");
await extension.awaitMessage("oncontextmenu_in_dom_part_2");
await extension.awaitMessage("onShown");
let visibleMenuItemIds = getVisibleChildrenIds(menu);
Assert.deepEqual(
visibleMenuItemIds.slice(0, EXPECTED_EXTENSION_MENU_IDS.length),
EXPECTED_EXTENSION_MENU_IDS,
"Expected extension menu items at the start.");
checkIsDefaultMenuItemVisible(visibleMenuItemIds);
is(visibleMenuItemIds[visibleMenuItemIds.length - 1], OTHER_EXTENSION_MENU_ID,
"Other extension menu item should be at the end.");
let menuItems = menu.getElementsByAttribute("label", "tab_2");
await closeExtensionContextMenu(menuItems[0]);
await extension.awaitMessage("onClicked_tab_2");
}
{
// Tests that previous overrideContext call has been forgotten,
// so the default behavior should occur (=move items into submenu).
info("Expecting the default menu to be used when overrideContext is not called.");
let menu = await openContextMenu("a");
await extension.awaitMessage("onShown");
checkIsDefaultMenuItemVisible(getVisibleChildrenIds(menu));
let menuItems = menu.getElementsByAttribute("ext-type", "top-level-menu");
is(menuItems.length, 1, "Expected top-level menu element for extension.");
let topLevelExtensionMenuItem = menuItems[0];
is(topLevelExtensionMenuItem.nextSibling, null, "Extension menu should be the last element.");
const submenu = await openSubmenu(topLevelExtensionMenuItem);
is(submenu, topLevelExtensionMenuItem.firstElementChild, "Correct submenu opened");
Assert.deepEqual(
getVisibleChildrenIds(submenu),
EXPECTED_EXTENSION_MENU_IDS,
"Extension menu items should be in the submenu by default.");
await closeContextMenu();
}
// Unloading the extension will automatically close the extension's tab.html
await extension.unload();
await otherExtension.unload();
});
// Tests some edge cases:
// - overrideContext() is called without any menu registrations,
// followed by a menu registration + menus.refresh..
// - overrideContext() is called and event.preventDefault() is also
// called to stop the menu from appearing.
// - Open menu again and verify that the default menu behavior occurs.
add_task(async function overrideContext_sidebar_edge_cases() {
function sidebarJs() {
const TIME_BEFORE_MENU_SHOWN = Date.now();
let count = 0;
// eslint-disable-next-line mozilla/balanced-listeners
document.addEventListener("contextmenu", event => {
++count;
if (count === 1) {
browser.menus.overrideContext({});
} else if (count === 2) {
browser.menus.overrideContext({});
event.preventDefault(); // Prevent menu from being shown.
// We are not expecting a menu. Wait for the time it took to show and
// hide the previous menu, to check that no new menu appears.
// eslint-disable-next-line mozilla/no-arbitrary-setTimeout
setTimeout(() => {
browser.test.sendMessage("stop_waiting_for_menu_shown", "timer_reached");
}, Date.now() - TIME_BEFORE_MENU_SHOWN);
} else if (count === 3) {
// The overrideContext from the previous call should be forgotten.
// Use the default behavior, i.e. show the default menu.
} else {
browser.test.fail(`Unexpected menu count: ${count}`);
}
browser.test.sendMessage("oncontextmenu_in_dom");
});
browser.menus.onShown.addListener(info => {
if (count === 1) {
browser.test.assertEq("", info.menuIds.join(","), "Expected no items");
browser.menus.create({id: "some_item", title: "some_item"}, () => {
browser.test.sendMessage("onShown_1_and_menu_item_created");
});
} else if (count === 2) {
browser.test.fail("onShown should not have fired when the menu is not shown.");
} else if (count === 3) {
browser.test.assertEq("some_item", info.menuIds.join(","), "Expected menu item");
browser.test.sendMessage("onShown_3");
} else {
browser.test.fail(`Unexpected onShown at count: ${count}`);
}
});
browser.test.onMessage.addListener(async msg => {
browser.test.assertEq("refresh_menus", msg, "Expected message");
browser.test.assertEq(1, count, "Expected at first menu test");
await browser.menus.refresh();
browser.test.sendMessage("menus_refreshed");
});
browser.menus.onHidden.addListener(() => {
browser.test.sendMessage("onHidden", count);
});
browser.test.sendMessage("sidebar_ready");
}
let extension = ExtensionTestUtils.loadExtension({
useAddonManager: "temporary", // To automatically show sidebar on load.
manifest: {
permissions: ["menus", "menus.overrideContext"],
sidebar_action: {
default_panel: "sidebar.html",
},
},
files: {
"sidebar.html": `
<!DOCTYPE html><meta charset="utf-8">
<a href="http://example.com/">Link</a>
<script src="sidebar.js"></script>
`,
"sidebar.js": sidebarJs,
},
background() {
browser.test.assertThrows(
() => { browser.menus.overrideContext({someInvalidParameter: true}); },
/Unexpected property "someInvalidParameter"/,
"overrideContext should be available and the parameters be validated.");
browser.test.assertThrows(
() => { browser.menus.overrideContext({}); },
/overrideContext must be called during a "contextmenu" event/,
"overrideContext should fail outside of a 'contextmenu' event.");
browser.test.sendMessage("bg_test_done");
},
});
await extension.startup();
await extension.awaitMessage("bg_test_done");
await extension.awaitMessage("sidebar_ready");
const EXPECTED_EXTENSION_MENU_ID =
`${makeWidgetId(extension.id)}-menuitem-_some_item`;
{
// Checks that a menu can initially be empty and be updated.
info("Expecting menu without items to appear and be updated after menus.refresh()");
let menu = await openContextMenuInSidebar("a");
await extension.awaitMessage("oncontextmenu_in_dom");
await extension.awaitMessage("onShown_1_and_menu_item_created");
Assert.deepEqual(getVisibleChildrenIds(menu), [], "Expected no items, initially");
extension.sendMessage("refresh_menus");
await extension.awaitMessage("menus_refreshed");
Assert.deepEqual(getVisibleChildrenIds(menu), [EXPECTED_EXTENSION_MENU_ID], "Expected updated menu");
await closeContextMenu(menu);
is(await extension.awaitMessage("onHidden"), 1, "Menu hidden");
}
{
// Trigger a context menu. The page has prevented the menu from being
// shown, so the promise should not resolve.
info("Expecting menu to not appear because of event.preventDefault()");
let popupShowingPromise = openContextMenuInSidebar("a");
await extension.awaitMessage("oncontextmenu_in_dom");
is(await Promise.race([
extension.awaitMessage("stop_waiting_for_menu_shown"),
popupShowingPromise.then(() => "popup_shown"),
]), "timer_reached", "The menu should not be shown.");
}
{
info("Expecting default menu to be shown when the menu is reopened after event.preventDefault()");
let menu = await openContextMenuInSidebar("a");
await extension.awaitMessage("oncontextmenu_in_dom");
await extension.awaitMessage("onShown_3");
let visibleMenuItemIds = getVisibleChildrenIds(menu);
checkIsDefaultMenuItemVisible(visibleMenuItemIds);
ok(visibleMenuItemIds.includes(EXPECTED_EXTENSION_MENU_ID), "Expected extension menu item");
await closeContextMenu(menu);
is(await extension.awaitMessage("onHidden"), 3, "Menu hidden");
}
await extension.unload();
});

Просмотреть файл

@ -0,0 +1,245 @@
/* 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/. */
"use strict";
function getVisibleChildrenIds(menuElem) {
return Array.from(menuElem.children).filter(elem => !elem.hidden).map(elem => elem.id || elem.tagName);
}
function checkIsDefaultMenuItemVisible(visibleMenuItemIds) {
// In this whole test file, we open a menu on a link. Assume that all
// default menu items are shown if one link-specific menu item is shown.
ok(visibleMenuItemIds.includes("context-openlink"),
`The default 'Open Link in New Tab' menu item should be in ${visibleMenuItemIds}.`);
}
// Tests that the context of an extension menu can be changed to:
// - tab
// - bookmark
add_task(async function overrideContext_with_context() {
// Background script of the main test extension and the auxilary other extension.
function background() {
browser.test.onMessage.addListener(async (msg, tabId) => {
browser.test.assertEq("testTabAccess", msg, `Expected message in ${browser.runtime.id}`);
let tab = await browser.tabs.get(tabId);
if (!tab.url) { // tabs or activeTab not active.
browser.test.sendMessage("testTabAccessDone", "tab_no_url");
return;
}
try {
let [url] = await browser.tabs.executeScript(tabId, {
code: "document.URL",
});
browser.test.assertEq("http://example.com/?SomeTab", url, "Expected successful executeScript");
browser.test.sendMessage("testTabAccessDone", "executeScript_ok");
return;
} catch (e) {
browser.test.assertEq("Missing host permission for the tab", e.message, "Expected error message");
browser.test.sendMessage("testTabAccessDone", "executeScript_failed");
}
});
browser.menus.onShown.addListener((info, tab) => {
browser.test.sendMessage("onShown", {
menuIds: info.menuIds,
contexts: info.contexts,
bookmarkId: info.bookmarkId,
tabId: tab && tab.id,
});
});
browser.menus.onClicked.addListener((info, tab) => {
browser.test.sendMessage("onClicked", {
menuItemId: info.menuItemId,
bookmarkId: info.bookmarkId,
tabId: tab && tab.id,
});
});
browser.menus.create({id: "tab_context", title: "tab_context", contexts: ["tab"]});
browser.menus.create({id: "bookmark_context", title: "bookmark_context", contexts: ["bookmark"]});
browser.menus.create({id: "link_context", title: "link_context"}, () => {
browser.test.sendMessage("menu_items_registered");
});
if (browser.runtime.id === "@menu-test-extension") {
browser.tabs.create({url: "tab.html"});
}
}
let extension = ExtensionTestUtils.loadExtension({
manifest: {
applications: {gecko: {id: "@menu-test-extension"}},
permissions: ["menus", "menus.overrideContext", "tabs", "bookmarks"],
},
files: {
"tab.html": `
<!DOCTYPE html><meta charset="utf-8">
<a href="http://example.com/">Link</a>
<script src="tab.js"></script>
`,
"tab.js": async () => {
let [tab] = await browser.tabs.query({
url: "http://example.com/?SomeTab",
});
let bookmark = await browser.bookmarks.create({
title: "Bookmark for menu test",
url: "http://example.com/bookmark",
});
let testCases = [{
context: "tab",
tabId: tab.id,
}, {
context: "tab",
tabId: tab.id,
}, {
context: "bookmark",
bookmarkId: bookmark.id,
}, {
context: "tab",
tabId: 123456789, // Some invalid tabId.
}];
// eslint-disable-next-line mozilla/balanced-listeners
document.addEventListener("contextmenu", () => {
browser.menus.overrideContext(testCases.shift());
browser.test.sendMessage("oncontextmenu_in_dom");
});
browser.test.sendMessage("setup_ready", {
bookmarkId: bookmark.id,
tabId: tab.id,
});
},
},
background,
});
let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, "http://example.com/?SomeTab");
let otherExtension = ExtensionTestUtils.loadExtension({
manifest: {
applications: {gecko: {id: "@other-test-extension"}},
permissions: ["menus", "bookmarks", "activeTab"],
},
background,
});
await otherExtension.startup();
await otherExtension.awaitMessage("menu_items_registered");
await extension.startup();
await extension.awaitMessage("menu_items_registered");
let {bookmarkId, tabId} = await extension.awaitMessage("setup_ready");
info(`Set up test with tabId=${tabId} and bookmarkId=${bookmarkId}.`);
{
// Test case 1: context=tab
let menu = await openContextMenu("a");
await extension.awaitMessage("oncontextmenu_in_dom");
for (let ext of [extension, otherExtension]) {
info(`Testing menu from ${ext.id} after changing context to tab`);
Assert.deepEqual(await ext.awaitMessage("onShown"), {
menuIds: ["tab_context"],
contexts: ["tab"],
bookmarkId: undefined,
tabId,
}, "Expected onShown details after changing context to tab");
}
Assert.deepEqual(getVisibleChildrenIds(menu), [
`${makeWidgetId(extension.id)}-menuitem-_tab_context`,
`menuseparator`,
`${makeWidgetId(otherExtension.id)}-menuitem-_tab_context`,
], "Expected menu items after changing context to tab");
extension.sendMessage("testTabAccess", tabId);
is(await extension.awaitMessage("testTabAccessDone"),
"executeScript_failed",
"executeScript should fail due to the lack of permissions.");
otherExtension.sendMessage("testTabAccess", tabId);
is(await otherExtension.awaitMessage("testTabAccessDone"),
"tab_no_url",
"Other extension should not have activeTab permissions yet.");
// Click on the menu item of the other extension to unlock host permissions.
let menuItems = menu.getElementsByAttribute("label", "tab_context");
is(menuItems.length, 2, "There are two menu items with label 'tab_context'");
await closeExtensionContextMenu(menuItems[1]);
Assert.deepEqual(await otherExtension.awaitMessage("onClicked"), {
menuItemId: "tab_context",
bookmarkId: undefined,
tabId,
}, "Expected onClicked details after changing context to tab");
extension.sendMessage("testTabAccess", tabId);
is(await extension.awaitMessage("testTabAccessDone"),
"executeScript_failed",
"executeScript of extension that created the menu should still fail.");
otherExtension.sendMessage("testTabAccess", tabId);
is(await otherExtension.awaitMessage("testTabAccessDone"),
"executeScript_ok",
"Other extension should have activeTab permissions.");
}
{
// Test case 2: context=tab, click on menu item of extension..
let menu = await openContextMenu("a");
await extension.awaitMessage("oncontextmenu_in_dom");
// The previous test has already verified the visible menu items,
// so we skip checking the onShown result and only test clicking.
await extension.awaitMessage("onShown");
await otherExtension.awaitMessage("onShown");
let menuItems = menu.getElementsByAttribute("label", "tab_context");
is(menuItems.length, 2, "There are two menu items with label 'tab_context'");
await closeExtensionContextMenu(menuItems[0]);
Assert.deepEqual(await extension.awaitMessage("onClicked"), {
menuItemId: "tab_context",
bookmarkId: undefined,
tabId,
}, "Expected onClicked details after changing context to tab");
extension.sendMessage("testTabAccess", tabId);
is(await extension.awaitMessage("testTabAccessDone"),
"executeScript_failed",
"activeTab permission should not be available to the extension that created the menu.");
}
{
// Test case 3: context=bookmark
let menu = await openContextMenu("a");
await extension.awaitMessage("oncontextmenu_in_dom");
for (let ext of [extension, otherExtension]) {
info(`Testing menu from ${ext.id} after changing context to bookmark`);
let shownInfo = await ext.awaitMessage("onShown");
Assert.deepEqual(shownInfo, {
menuIds: ["bookmark_context"],
contexts: ["bookmark"],
bookmarkId,
tabId: undefined,
}, "Expected onShown details after changing context to bookmark");
}
Assert.deepEqual(getVisibleChildrenIds(menu), [
`${makeWidgetId(extension.id)}-menuitem-_bookmark_context`,
`menuseparator`,
`${makeWidgetId(otherExtension.id)}-menuitem-_bookmark_context`,
], "Expected menu items after changing context to bookmark");
await closeContextMenu(menu);
}
{
// Test case 4: context=tab, invalid tabId.
let menu = await openContextMenu("a");
await extension.awaitMessage("oncontextmenu_in_dom");
// When an invalid tabId is used, all extension menu logic is skipped and
// the default menu is shown.
checkIsDefaultMenuItemVisible(getVisibleChildrenIds(menu));
await closeContextMenu(menu);
}
await extension.unload();
await otherExtension.unload();
BrowserTestUtils.removeTab(tab);
});

Просмотреть файл

@ -0,0 +1,161 @@
/* 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/. */
"use strict";
add_task(async function auto_approve_optional_permissions() {
// Auto-approve optional permission requests, without UI.
await SpecialPowers.pushPrefEnv({
set: [["extensions.webextOptionalPermissionPrompts", false]],
});
// TODO: Consider an observer for "webextension-optional-permission-prompt"
// once bug 1493396 is fixed.
});
add_task(async function overrideContext_permissions() {
function sidebarJs() {
// If the extension has the right permissions, calling
// menus.overrideContext with one of the following should not throw.
const CONTEXT_OPTIONS_TAB = {context: "tab", tabId: 1};
const CONTEXT_OPTIONS_BOOKMARK = {context: "bookmark", bookmarkId: "x"};
const E_PERM_TAB = /The "tab" context requires the "tabs" permission/;
const E_PERM_BOOKMARK = /The "bookmark" context requires the "bookmarks" permission/;
function assertAllowed(contextOptions) {
try {
let result = browser.menus.overrideContext(contextOptions);
browser.test.assertEq(undefined, result, `Allowed menu for context=${contextOptions.context}`);
} catch (e) {
browser.test.fail(`Unexpected error for context=${contextOptions.context}: ${e}`);
}
}
function assertNotAllowed(contextOptions, expectedError) {
browser.test.assertThrows(() => {
browser.menus.overrideContext(contextOptions);
}, expectedError, `Expected error for context=${contextOptions.context}`);
}
async function requestPermissions(permissions) {
try {
let permPromise;
window.withHandlingUserInputForPermissionRequestTest(() => {
permPromise = browser.permissions.request(permissions);
});
browser.test.assertTrue(await permPromise, `Should have granted ${JSON.stringify(permissions)}`);
} catch (e) {
browser.test.fail(`Failed to use permissions.request(${JSON.stringify(permissions)}): ${e}`);
}
}
// The menus.overrideContext method can only be called during a
// "contextmenu" event. So we use a generator to run tests, and yield
// before we call overrideContext after an asynchronous operation.
let testGenerator = (async function* () {
browser.test.assertEq(undefined, browser.menus.overrideContext,
"menus.overrideContext requires the 'menus.overrideContext' permission");
await requestPermissions({permissions: ["menus.overrideContext"]});
yield;
// context without required property.
browser.test.assertThrows(
() => { browser.menus.overrideContext({context: "tab"}); },
/Property "tabId" is required for context "tab"/,
"Required property for context tab");
browser.test.assertThrows(
() => { browser.menus.overrideContext({context: "bookmark"}); },
/Property "bookmarkId" is required for context "bookmark"/,
"Required property for context bookmarks");
// context with too many properties.
browser.test.assertThrows(
() => { browser.menus.overrideContext({context: "bookmark", bookmarkId: "x", tabId: 1}); },
/Property "tabId" can only be used with context "tab"/,
"Invalid property for context bookmarks");
browser.test.assertThrows(
() => { browser.menus.overrideContext({context: "bookmark", bookmarkId: "x", showDefaults: true}); },
/Property "showDefaults" cannot be used with context "bookmark"/,
"showDefaults cannot be used with context bookmark");
// context with right properties, but missing permissions.
assertNotAllowed(CONTEXT_OPTIONS_BOOKMARK, E_PERM_BOOKMARK);
assertNotAllowed(CONTEXT_OPTIONS_TAB, E_PERM_TAB);
await requestPermissions({permissions: ["bookmarks"]});
browser.test.log("Active permissions: bookmarks");
yield;
assertAllowed(CONTEXT_OPTIONS_BOOKMARK);
assertNotAllowed(CONTEXT_OPTIONS_TAB, E_PERM_TAB);
await requestPermissions({permissions: ["tabs"]});
await browser.permissions.remove({permissions: ["bookmarks"]});
browser.test.log("Active permissions: tabs");
yield;
assertNotAllowed(CONTEXT_OPTIONS_BOOKMARK, E_PERM_BOOKMARK);
assertAllowed(CONTEXT_OPTIONS_TAB);
await browser.permissions.remove({permissions: ["tabs"]});
browser.test.log("Active permissions: none");
yield;
assertNotAllowed(CONTEXT_OPTIONS_TAB, E_PERM_TAB);
await browser.permissions.remove({permissions: ["menus.overrideContext"]});
browser.test.assertEq(undefined, browser.menus.overrideContext,
"menus.overrideContext is unavailable after revoking the permission");
})();
// eslint-disable-next-line mozilla/balanced-listeners
document.addEventListener("contextmenu", async event => {
event.preventDefault();
try {
let {done} = await testGenerator.next();
browser.test.sendMessage("continue_test", !done);
} catch (e) {
browser.test.fail(`Unexpected error: ${e} :: ${e.stack}`);
browser.test.sendMessage("continue_test", false);
}
});
browser.test.sendMessage("sidebar_ready");
}
let extension = ExtensionTestUtils.loadExtension({
useAddonManager: "temporary", // To automatically show sidebar on load.
manifest: {
permissions: ["menus"],
optional_permissions: ["menus.overrideContext", "tabs", "bookmarks"],
sidebar_action: {
default_panel: "sidebar.html",
},
},
files: {
"sidebar.html": `
<!DOCTYPE html><meta charset="utf-8">
<a href="http://example.com/">Link</a>
<script src="sidebar.js"></script>
`,
"sidebar.js": sidebarJs,
},
});
await extension.startup();
await extension.awaitMessage("sidebar_ready");
// permissions.request requires user input, export helper.
await ContentTask.spawn(SidebarUI.browser.contentDocument.getElementById("webext-panels-browser"), null, () => {
let {withHandlingUserInput} = ChromeUtils.import("resource://gre/modules/ExtensionCommon.jsm", {}).ExtensionCommon;
Cu.exportFunction((fn) => {
return withHandlingUserInput(content, fn);
}, content, {
defineAs: "withHandlingUserInputForPermissionRequestTest",
});
});
do {
info(`Going to trigger "contextmenu" event.`);
// The menu is never shown, so don't await the returned promise.
openContextMenuInSidebar("a");
} while (await extension.awaitMessage("continue_test"));
await extension.unload();
});

Просмотреть файл

@ -16,486 +16,11 @@
xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
xmlns:xbl="http://www.mozilla.org/xbl">
<binding id="searchbar">
<content>
<xul:stringbundle src="chrome://browser/locale/search.properties"
anonid="searchbar-stringbundle"/>
<!--
There is a dependency between "maxrows" attribute and
"SuggestAutoComplete._historyLimit" (nsSearchSuggestions.js). Changing
one of them requires changing the other one.
-->
<xul:textbox class="searchbar-textbox"
anonid="searchbar-textbox"
type="autocomplete"
inputtype="search"
placeholder="&searchInput.placeholder;"
flex="1"
autocompletepopup="PopupSearchAutoComplete"
autocompletesearch="search-autocomplete"
autocompletesearchparam="searchbar-history"
maxrows="10"
completeselectedindex="true"
minresultsforpopup="0"
xbl:inherits="disabled,disableautocomplete,searchengine,src,newlines">
<!--
Empty <box> to properly position the icon within the autocomplete
binding's anonymous children (the autocomplete binding positions <box>
children differently)
-->
<xul:box>
<xul:hbox class="searchbar-search-button"
anonid="searchbar-search-button"
xbl:inherits="addengines"
tooltiptext="&searchIcon.tooltip;">
<xul:image class="searchbar-search-icon"/>
<xul:image class="searchbar-search-icon-overlay"/>
</xul:hbox>
</xul:box>
<xul:hbox class="search-go-container">
<xul:image class="search-go-button urlbar-icon" hidden="true"
anonid="search-go-button"
onclick="handleSearchCommand(event);"
tooltiptext="&contentSearchSubmit.tooltip;"/>
</xul:hbox>
</xul:textbox>
</content>
<implementation implements="nsIObserver">
<constructor><![CDATA[
if (this.parentNode.parentNode.localName == "toolbarpaletteitem")
return;
Services.obs.addObserver(this, "browser-search-engine-modified");
Services.obs.addObserver(this, "browser-search-service");
this._initialized = true;
(window.delayedStartupPromise || Promise.resolve()).then(() => {
window.requestIdleCallback(() => {
Services.search.init(aStatus => {
// Bail out if the binding's been destroyed
if (!this._initialized)
return;
if (Components.isSuccessCode(aStatus)) {
// Refresh the display (updating icon, etc)
this.updateDisplay();
BrowserSearch.updateOpenSearchBadge();
} else {
Cu.reportError("Cannot initialize search service, bailing out: " + aStatus);
}
});
});
});
// Wait until the popupshowing event to avoid forcing immediate
// attachment of the search-one-offs binding.
this.textbox.popup.addEventListener("popupshowing", () => {
let oneOffButtons = this.textbox.popup.oneOffButtons;
// Some accessibility tests create their own <searchbar> that doesn't
// use the popup binding below, so null-check oneOffButtons.
if (oneOffButtons) {
oneOffButtons.telemetryOrigin = "searchbar";
// Set .textbox first, since the popup setter will cause
// a _rebuild call that uses it.
oneOffButtons.textbox = this.textbox;
oneOffButtons.popup = this.textbox.popup;
}
}, {capture: true, once: true});
]]></constructor>
<destructor><![CDATA[
this.destroy();
]]></destructor>
<method name="destroy">
<body><![CDATA[
if (this._initialized) {
this._initialized = false;
Services.obs.removeObserver(this, "browser-search-engine-modified");
Services.obs.removeObserver(this, "browser-search-service");
}
// Make sure to break the cycle from _textbox to us. Otherwise we leak
// the world. But make sure it's actually pointing to us.
// Also make sure the textbox has ever been constructed, otherwise the
// _textbox getter will cause the textbox constructor to run, add an
// observer, and leak the world too.
if (this._textboxInitialized && this._textbox.mController.input == this)
this._textbox.mController.input = null;
]]></body>
</method>
<field name="_ignoreFocus">false</field>
<field name="_clickClosedPopup">false</field>
<field name="_stringBundle">document.getAnonymousElementByAttribute(this,
"anonid", "searchbar-stringbundle");</field>
<field name="_textboxInitialized">false</field>
<field name="_textbox">document.getAnonymousElementByAttribute(this,
"anonid", "searchbar-textbox");</field>
<field name="_engines">null</field>
<field name="FormHistory" readonly="true">
(ChromeUtils.import("resource://gre/modules/FormHistory.jsm", {})).FormHistory;
</field>
<property name="engines" readonly="true">
<getter><![CDATA[
if (!this._engines)
this._engines = Services.search.getVisibleEngines();
return this._engines;
]]></getter>
</property>
<property name="currentEngine">
<setter><![CDATA[
Services.search.currentEngine = val;
return val;
]]></setter>
<getter><![CDATA[
var currentEngine = Services.search.currentEngine;
// Return a dummy engine if there is no currentEngine
return currentEngine || {name: "", uri: null};
]]></getter>
</property>
<!-- textbox is used by sanitize.js to clear the undo history when
clearing form information. -->
<property name="textbox" readonly="true"
onget="return this._textbox;"/>
<property name="value" onget="return this._textbox.value;"
onset="return this._textbox.value = val;"/>
<method name="focus">
<body><![CDATA[
this._textbox.focus();
]]></body>
</method>
<method name="select">
<body><![CDATA[
this._textbox.select();
]]></body>
</method>
<method name="observe">
<parameter name="aEngine"/>
<parameter name="aTopic"/>
<parameter name="aVerb"/>
<body><![CDATA[
if (aTopic == "browser-search-engine-modified" ||
(aTopic == "browser-search-service" && aVerb == "init-complete")) {
// Make sure the engine list is refetched next time it's needed
this._engines = null;
// Update the popup header and update the display after any modification.
this._textbox.popup.updateHeader();
this.updateDisplay();
}
]]></body>
</method>
<method name="setIcon">
<parameter name="element"/>
<parameter name="uri"/>
<body><![CDATA[
element.setAttribute("src", uri);
]]></body>
</method>
<method name="updateDisplay">
<body><![CDATA[
var uri = this.currentEngine.iconURI;
this.setIcon(this, uri ? uri.spec : "");
var name = this.currentEngine.name;
var text = this._stringBundle.getFormattedString("searchtip", [name]);
this._textbox.label = text;
this._textbox.tooltipText = text;
]]></body>
</method>
<method name="updateGoButtonVisibility">
<body><![CDATA[
document.getAnonymousElementByAttribute(this, "anonid",
"search-go-button")
.hidden = !this._textbox.value;
]]></body>
</method>
<method name="openSuggestionsPanel">
<parameter name="aShowOnlySettingsIfEmpty"/>
<body><![CDATA[
if (this._textbox.open)
return;
this._textbox.showHistoryPopup();
if (this._textbox.value) {
// showHistoryPopup does a startSearch("") call, ensure the
// controller handles the text from the input box instead:
this._textbox.mController.handleText();
} else if (aShowOnlySettingsIfEmpty) {
this.setAttribute("showonlysettings", "true");
}
]]></body>
</method>
<method name="selectEngine">
<parameter name="aEvent"/>
<parameter name="isNextEngine"/>
<body><![CDATA[
// Find the new index
var newIndex = this.engines.indexOf(this.currentEngine);
newIndex += isNextEngine ? 1 : -1;
if (newIndex >= 0 && newIndex < this.engines.length) {
this.currentEngine = this.engines[newIndex];
}
aEvent.preventDefault();
aEvent.stopPropagation();
this.openSuggestionsPanel();
]]></body>
</method>
<method name="handleSearchCommand">
<parameter name="aEvent"/>
<parameter name="aEngine"/>
<parameter name="aForceNewTab"/>
<body><![CDATA[
var where = "current";
let params;
// Open ctrl/cmd clicks on one-off buttons in a new background tab.
if (aEvent && aEvent.originalTarget.getAttribute("anonid") == "search-go-button") {
if (aEvent.button == 2)
return;
where = whereToOpenLink(aEvent, false, true);
} else if (aForceNewTab) {
where = "tab";
if (Services.prefs.getBoolPref("browser.tabs.loadInBackground"))
where += "-background";
} else {
var newTabPref = Services.prefs.getBoolPref("browser.search.openintab");
if (((aEvent instanceof KeyboardEvent && aEvent.altKey) ^ newTabPref) &&
!isTabEmpty(gBrowser.selectedTab)) {
where = "tab";
}
if ((aEvent instanceof MouseEvent) &&
(aEvent.button == 1 || aEvent.getModifierState("Accel"))) {
where = "tab";
params = {
inBackground: true,
};
}
}
this.handleSearchCommandWhere(aEvent, aEngine, where, params);
]]></body>
</method>
<method name="handleSearchCommandWhere">
<parameter name="aEvent"/>
<parameter name="aEngine"/>
<parameter name="aWhere"/>
<parameter name="aParams"/>
<body><![CDATA[
var textBox = this._textbox;
var textValue = textBox.value;
let selection = this.telemetrySearchDetails;
let oneOffRecorded = false;
BrowserUsageTelemetry.recordSearchbarSelectedResultMethod(
aEvent,
selection ? selection.index : -1
);
if (!selection || (selection.index == -1)) {
oneOffRecorded = this.textbox.popup.oneOffButtons
.maybeRecordTelemetry(aEvent, aWhere, aParams);
if (!oneOffRecorded) {
let source = "unknown";
let type = "unknown";
let target = aEvent.originalTarget;
if (aEvent instanceof KeyboardEvent) {
type = "key";
} else if (aEvent instanceof MouseEvent) {
type = "mouse";
if (target.classList.contains("search-panel-header") ||
target.parentNode.classList.contains("search-panel-header")) {
source = "header";
}
} else if (aEvent instanceof XULCommandEvent) {
if (target.getAttribute("anonid") == "paste-and-search") {
source = "paste";
}
}
if (!aEngine) {
aEngine = this.currentEngine;
}
BrowserSearch.recordOneoffSearchInTelemetry(aEngine, source, type,
aWhere);
}
}
// This is a one-off search only if oneOffRecorded is true.
this.doSearch(textValue, aWhere, aEngine, aParams, oneOffRecorded);
if (aWhere == "tab" && aParams && aParams.inBackground)
this.focus();
]]></body>
</method>
<method name="doSearch">
<parameter name="aData"/>
<parameter name="aWhere"/>
<parameter name="aEngine"/>
<parameter name="aParams"/>
<parameter name="aOneOff"/>
<body><![CDATA[
var textBox = this._textbox;
// Save the current value in the form history
if (aData && !PrivateBrowsingUtils.isWindowPrivate(window) && this.FormHistory.enabled) {
this.FormHistory.update(
{ op: "bump",
fieldname: textBox.getAttribute("autocompletesearchparam"),
value: aData },
{ handleError(aError) {
Cu.reportError("Saving search to form history failed: " + aError.message);
}});
}
let engine = aEngine || this.currentEngine;
var submission = engine.getSubmission(aData, null, "searchbar");
let telemetrySearchDetails = this.telemetrySearchDetails;
this.telemetrySearchDetails = null;
if (telemetrySearchDetails && telemetrySearchDetails.index == -1) {
telemetrySearchDetails = null;
}
// If we hit here, we come either from a one-off, a plain search or a suggestion.
const details = {
isOneOff: aOneOff,
isSuggestion: (!aOneOff && telemetrySearchDetails),
selection: telemetrySearchDetails,
};
BrowserSearch.recordSearchInTelemetry(engine, "searchbar", details);
// null parameter below specifies HTML response for search
let params = {
postData: submission.postData,
};
if (aParams) {
for (let key in aParams) {
params[key] = aParams[key];
}
}
openTrustedLinkIn(submission.uri.spec, aWhere, params);
]]></body>
</method>
</implementation>
<handlers>
<handler event="command"><![CDATA[
const target = event.originalTarget;
if (target.engine) {
this.currentEngine = target.engine;
} else if (target.classList.contains("addengine-item")) {
// Select the installed engine if the installation succeeds
var installCallback = {
onSuccess: engine => this.currentEngine = engine,
};
Services.search.addEngine(target.getAttribute("uri"), null,
target.getAttribute("src"), false,
installCallback);
} else
return;
this.focus();
this.select();
]]></handler>
<handler event="DOMMouseScroll"
phase="capturing"
modifiers="accel"
action="this.selectEngine(event, (event.detail > 0));"/>
<handler event="input" action="this.updateGoButtonVisibility();"/>
<handler event="drop" action="this.updateGoButtonVisibility();"/>
<handler event="blur">
<![CDATA[
// If the input field is still focused then a different window has
// received focus, ignore the next focus event.
this._ignoreFocus = (document.activeElement == this._textbox.inputField);
]]></handler>
<handler event="focus">
<![CDATA[
// Speculatively connect to the current engine's search URI (and
// suggest URI, if different) to reduce request latency
this.currentEngine.speculativeConnect({window,
originAttributes: gBrowser.contentPrincipal
.originAttributes});
if (this._ignoreFocus) {
// This window has been re-focused, don't show the suggestions
this._ignoreFocus = false;
return;
}
// Don't open the suggestions if there is no text in the textbox.
if (!this._textbox.value)
return;
// Don't open the suggestions if the mouse was used to focus the
// textbox, that will be taken care of in the click handler.
if (Services.focus.getLastFocusMethod(window) & Services.focus.FLAG_BYMOUSE)
return;
this.openSuggestionsPanel();
]]></handler>
<handler event="mousedown" phase="capturing">
<![CDATA[
if (event.originalTarget.getAttribute("anonid") == "searchbar-search-button") {
this._clickClosedPopup = this._textbox.popup._isHiding;
}
]]></handler>
<handler event="mousedown" button="0">
<![CDATA[
// Ignore clicks on the search go button.
if (event.originalTarget.getAttribute("anonid") == "search-go-button") {
return;
}
let isIconClick = event.originalTarget.getAttribute("anonid") == "searchbar-search-button";
// Ignore clicks on the icon if they were made to close the popup
if (isIconClick && this._clickClosedPopup) {
return;
}
// Open the suggestions whenever clicking on the search icon or if there
// is text in the textbox.
if (isIconClick || this._textbox.value) {
this.openSuggestionsPanel(true);
}
]]></handler>
</handlers>
</binding>
<binding id="searchbar-textbox"
extends="chrome://global/content/bindings/autocomplete.xml#autocomplete">
<implementation>
<constructor><![CDATA[
if (document.getBindingParent(this).parentNode.parentNode.localName ==
if (this.closest("searchbar").parentNode.parentNode.localName ==
"toolbarpaletteitem")
return;
@ -513,7 +38,7 @@
{capture: true, once: true});
this.setAttribute("aria-owns", this.popup.id);
document.getBindingParent(this)._textboxInitialized = true;
this.closest("searchbar")._textboxInitialized = true;
]]></constructor>
<destructor><![CDATA[
@ -530,7 +55,7 @@
<method name="initContextMenu">
<parameter name="aMenu"/>
<body><![CDATA[
let stringBundle = document.getBindingParent(this)._stringBundle;
let stringBundle = this.closest("searchbar")._stringBundle;
let pasteAndSearch, suggestMenuItem;
let element, label, akey;
@ -686,7 +211,7 @@
<body>
<![CDATA[
if (!this.popupOpen) {
document.getBindingParent(this).openSuggestionsPanel();
this.closest("searchbar").openSuggestionsPanel();
return false;
}
return true;
@ -729,7 +254,7 @@
BrowserSearch.searchBar.telemetrySearchDetails = this._selectionDetails;
this._selectionDetails = null;
}
document.getBindingParent(this).handleSearchCommand(aEvent, engine);
this.closest("searchbar").handleSearchCommand(aEvent, engine);
]]></body>
</method>
@ -804,11 +329,11 @@
<handler event="keypress" keycode="VK_UP" modifiers="accel"
phase="capturing"
action="document.getBindingParent(this).selectEngine(event, false);"/>
action='this.closest("searchbar").selectEngine(event, false);'/>
<handler event="keypress" keycode="VK_DOWN" modifiers="accel"
phase="capturing"
action="document.getBindingParent(this).selectEngine(event, true);"/>
action='this.closest("searchbar").selectEngine(event, true);'/>
<handler event="keypress" keycode="VK_DOWN" modifiers="alt"
phase="capturing"
@ -835,7 +360,7 @@
if (data) {
event.preventDefault();
this.value = data;
document.getBindingParent(this).openSuggestionsPanel();
this.closest("searchbar").openSuggestionsPanel();
}
]]>
</handler>

Просмотреть файл

@ -0,0 +1,482 @@
/* 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/. */
"use strict";
/* eslint-env mozilla/browser-window */
/* globals XULCommandEvent */
// This is loaded into chrome windows with the subscript loader. Wrap in
// a block to prevent accidentally leaking globals onto `window`.
{
const inheritsMap = {
".searchbar-textbox": ["disabled", "disableautocomplete", "searchengine", "src", "newlines"],
".searchbar-search-button": ["addengines"],
};
function inheritAttribute(parent, child, attr) {
if (!parent.hasAttribute(attr)) {
child.removeAttribute(attr);
} else {
child.setAttribute(attr, parent.getAttribute(attr));
}
}
class MozSearchbar extends MozXULElement {
static get observedAttributes() {
let unique = new Set();
for (var i in inheritsMap) {
inheritsMap[i].forEach(attr => unique.add(attr));
}
return Array.from(unique);
}
attributeChangedCallback() {
this.inheritAttributes();
}
inheritAttributes() {
if (!this.isConnected) {
return;
}
for (let sel in inheritsMap) {
let node = this.querySelector(sel);
for (let attr of inheritsMap[sel]) {
inheritAttribute(this, node, attr);
}
}
}
constructor() {
super();
this.destroy = this.destroy.bind(this);
this._setupEventListeners();
let searchbar = this;
this.observer = {
observe(aEngine, aTopic, aVerb) {
if (aTopic == "browser-search-engine-modified" ||
(aTopic == "browser-search-service" && aVerb == "init-complete")) {
// Make sure the engine list is refetched next time it's needed
searchbar._engines = null;
// Update the popup header and update the display after any modification.
searchbar._textbox.popup.updateHeader();
searchbar.updateDisplay();
}
},
QueryInterface: ChromeUtils.generateQI([Ci.nsIObserver]),
};
this.content = MozXULElement.parseXULToFragment(`
<stringbundle src="chrome://browser/locale/search.properties"></stringbundle>
<textbox class="searchbar-textbox" type="autocomplete" inputtype="search" placeholder="&searchInput.placeholder;" flex="1" autocompletepopup="PopupSearchAutoComplete" autocompletesearch="search-autocomplete" autocompletesearchparam="searchbar-history" maxrows="10" completeselectedindex="true" minresultsforpopup="0" inherits="disabled,disableautocomplete,searchengine,src,newlines">
<box>
<hbox class="searchbar-search-button" inherits="addengines" tooltiptext="&searchIcon.tooltip;">
<image class="searchbar-search-icon"></image>
<image class="searchbar-search-icon-overlay"></image>
</hbox>
</box>
<hbox class="search-go-container">
<image class="search-go-button urlbar-icon" hidden="true" onclick="handleSearchCommand(event);" tooltiptext="&contentSearchSubmit.tooltip;"></image>
</hbox>
</textbox>
`, ["chrome://browser/locale/browser.dtd"]);
}
connectedCallback() {
// Don't initialize if this isn't going to be visible
if (this.closest("#BrowserToolbarPalette")) {
return;
}
this.appendChild(document.importNode(this.content, true));
this.inheritAttributes();
window.addEventListener("unload", this.destroy);
this._ignoreFocus = false;
this._clickClosedPopup = false;
this._stringBundle = this.querySelector("stringbundle");
this._textboxInitialized = false;
this._textbox = this.querySelector(".searchbar-textbox");
this._engines = null;
this.FormHistory = (ChromeUtils.import("resource://gre/modules/FormHistory.jsm", {})).FormHistory;
if (this.parentNode.parentNode.localName == "toolbarpaletteitem")
return;
Services.obs.addObserver(this.observer, "browser-search-engine-modified");
Services.obs.addObserver(this.observer, "browser-search-service");
this._initialized = true;
(window.delayedStartupPromise || Promise.resolve()).then(() => {
window.requestIdleCallback(() => {
Services.search.init(aStatus => {
// Bail out if the binding's been destroyed
if (!this._initialized)
return;
if (Components.isSuccessCode(aStatus)) {
// Refresh the display (updating icon, etc)
this.updateDisplay();
BrowserSearch.updateOpenSearchBadge();
} else {
Cu.reportError("Cannot initialize search service, bailing out: " + aStatus);
}
});
});
});
// Wait until the popupshowing event to avoid forcing immediate
// attachment of the search-one-offs binding.
this.textbox.popup.addEventListener("popupshowing", () => {
let oneOffButtons = this.textbox.popup.oneOffButtons;
// Some accessibility tests create their own <searchbar> that doesn't
// use the popup binding below, so null-check oneOffButtons.
if (oneOffButtons) {
oneOffButtons.telemetryOrigin = "searchbar";
// Set .textbox first, since the popup setter will cause
// a _rebuild call that uses it.
oneOffButtons.textbox = this.textbox;
oneOffButtons.popup = this.textbox.popup;
}
}, { capture: true, once: true });
}
get engines() {
if (!this._engines)
this._engines = Services.search.getVisibleEngines();
return this._engines;
}
set currentEngine(val) {
Services.search.currentEngine = val;
return val;
}
get currentEngine() {
var currentEngine = Services.search.currentEngine;
// Return a dummy engine if there is no currentEngine
return currentEngine || { name: "", uri: null };
}
/**
* textbox is used by sanitize.js to clear the undo history when
* clearing form information.
*/
get textbox() {
return this._textbox;
}
set value(val) {
return this._textbox.value = val;
}
get value() {
return this._textbox.value;
}
destroy() {
if (this._initialized) {
this._initialized = false;
window.removeEventListener("unload", this.destroy);
Services.obs.removeObserver(this.observer, "browser-search-engine-modified");
Services.obs.removeObserver(this.observer, "browser-search-service");
}
// Make sure to break the cycle from _textbox to us. Otherwise we leak
// the world. But make sure it's actually pointing to us.
// Also make sure the textbox has ever been constructed, otherwise the
// _textbox getter will cause the textbox constructor to run, add an
// observer, and leak the world too.
if (this._textboxInitialized && this._textbox.mController.input == this)
this._textbox.mController.input = null;
}
focus() {
this._textbox.focus();
}
select() {
this._textbox.select();
}
setIcon(element, uri) {
element.setAttribute("src", uri);
}
updateDisplay() {
var uri = this.currentEngine.iconURI;
this.setIcon(this, uri ? uri.spec : "");
var name = this.currentEngine.name;
var text = this._stringBundle.getFormattedString("searchtip", [name]);
this._textbox.label = text;
this._textbox.tooltipText = text;
}
updateGoButtonVisibility() {
this.querySelector(".search-go-button").hidden = !this._textbox.value;
}
openSuggestionsPanel(aShowOnlySettingsIfEmpty) {
if (this._textbox.open)
return;
this._textbox.showHistoryPopup();
if (this._textbox.value) {
// showHistoryPopup does a startSearch("") call, ensure the
// controller handles the text from the input box instead:
this._textbox.mController.handleText();
} else if (aShowOnlySettingsIfEmpty) {
this.setAttribute("showonlysettings", "true");
}
}
selectEngine(aEvent, isNextEngine) {
// Find the new index
var newIndex = this.engines.indexOf(this.currentEngine);
newIndex += isNextEngine ? 1 : -1;
if (newIndex >= 0 && newIndex < this.engines.length) {
this.currentEngine = this.engines[newIndex];
}
aEvent.preventDefault();
aEvent.stopPropagation();
this.openSuggestionsPanel();
}
handleSearchCommand(aEvent, aEngine, aForceNewTab) {
var where = "current";
let params;
// Open ctrl/cmd clicks on one-off buttons in a new background tab.
if (aEvent && aEvent.originalTarget.classList.contains("search-go-button")) {
if (aEvent.button == 2)
return;
where = whereToOpenLink(aEvent, false, true);
} else if (aForceNewTab) {
where = "tab";
if (Services.prefs.getBoolPref("browser.tabs.loadInBackground"))
where += "-background";
} else {
var newTabPref = Services.prefs.getBoolPref("browser.search.openintab");
if (((aEvent instanceof KeyboardEvent && aEvent.altKey) ^ newTabPref) &&
!isTabEmpty(gBrowser.selectedTab)) {
where = "tab";
}
if ((aEvent instanceof MouseEvent) &&
(aEvent.button == 1 || aEvent.getModifierState("Accel"))) {
where = "tab";
params = {
inBackground: true,
};
}
}
this.handleSearchCommandWhere(aEvent, aEngine, where, params);
}
handleSearchCommandWhere(aEvent, aEngine, aWhere, aParams) {
var textBox = this._textbox;
var textValue = textBox.value;
let selection = this.telemetrySearchDetails;
let oneOffRecorded = false;
BrowserUsageTelemetry.recordSearchbarSelectedResultMethod(
aEvent,
selection ? selection.index : -1
);
if (!selection || (selection.index == -1)) {
oneOffRecorded = this.textbox.popup.oneOffButtons
.maybeRecordTelemetry(aEvent, aWhere, aParams);
if (!oneOffRecorded) {
let source = "unknown";
let type = "unknown";
let target = aEvent.originalTarget;
if (aEvent instanceof KeyboardEvent) {
type = "key";
} else if (aEvent instanceof MouseEvent) {
type = "mouse";
if (target.classList.contains("search-panel-header") ||
target.parentNode.classList.contains("search-panel-header")) {
source = "header";
}
} else if (aEvent instanceof XULCommandEvent) {
if (target.getAttribute("anonid") == "paste-and-search") {
source = "paste";
}
}
if (!aEngine) {
aEngine = this.currentEngine;
}
BrowserSearch.recordOneoffSearchInTelemetry(aEngine, source, type,
aWhere);
}
}
// This is a one-off search only if oneOffRecorded is true.
this.doSearch(textValue, aWhere, aEngine, aParams, oneOffRecorded);
if (aWhere == "tab" && aParams && aParams.inBackground)
this.focus();
}
doSearch(aData, aWhere, aEngine, aParams, aOneOff) {
var textBox = this._textbox;
// Save the current value in the form history
if (aData && !PrivateBrowsingUtils.isWindowPrivate(window) && this.FormHistory.enabled) {
this.FormHistory.update({
op: "bump",
fieldname: textBox.getAttribute("autocompletesearchparam"),
value: aData,
}, {
handleError(aError) {
Cu.reportError("Saving search to form history failed: " + aError.message);
},
});
}
let engine = aEngine || this.currentEngine;
var submission = engine.getSubmission(aData, null, "searchbar");
let telemetrySearchDetails = this.telemetrySearchDetails;
this.telemetrySearchDetails = null;
if (telemetrySearchDetails && telemetrySearchDetails.index == -1) {
telemetrySearchDetails = null;
}
// If we hit here, we come either from a one-off, a plain search or a suggestion.
const details = {
isOneOff: aOneOff,
isSuggestion: (!aOneOff && telemetrySearchDetails),
selection: telemetrySearchDetails,
};
BrowserSearch.recordSearchInTelemetry(engine, "searchbar", details);
// null parameter below specifies HTML response for search
let params = {
postData: submission.postData,
};
if (aParams) {
for (let key in aParams) {
params[key] = aParams[key];
}
}
openTrustedLinkIn(submission.uri.spec, aWhere, params);
}
disconnectedCallback() {
this.destroy();
while (this.firstChild) {
this.firstChild.remove();
}
}
_setupEventListeners() {
this.addEventListener("command", (event) => {
const target = event.originalTarget;
if (target.engine) {
this.currentEngine = target.engine;
} else if (target.classList.contains("addengine-item")) {
// Select the installed engine if the installation succeeds
var installCallback = {
onSuccess: engine => this.currentEngine = engine,
};
Services.search.addEngine(target.getAttribute("uri"), null,
target.getAttribute("src"), false,
installCallback);
} else
return;
this.focus();
this.select();
});
this.addEventListener("DOMMouseScroll", (event) => { this.selectEngine(event, (event.detail > 0)); }, true);
this.addEventListener("input", (event) => { this.updateGoButtonVisibility(); });
this.addEventListener("drop", (event) => { this.updateGoButtonVisibility(); });
this.addEventListener("blur", (event) => {
// If the input field is still focused then a different window has
// received focus, ignore the next focus event.
this._ignoreFocus = (document.activeElement == this._textbox.inputField);
}, true);
this.addEventListener("focus", (event) => {
// Speculatively connect to the current engine's search URI (and
// suggest URI, if different) to reduce request latency
this.currentEngine.speculativeConnect({
window,
originAttributes: gBrowser.contentPrincipal
.originAttributes,
});
if (this._ignoreFocus) {
// This window has been re-focused, don't show the suggestions
this._ignoreFocus = false;
return;
}
// Don't open the suggestions if there is no text in the textbox.
if (!this._textbox.value)
return;
// Don't open the suggestions if the mouse was used to focus the
// textbox, that will be taken care of in the click handler.
if (Services.focus.getLastFocusMethod(window) & Services.focus.FLAG_BYMOUSE)
return;
this.openSuggestionsPanel();
}, true);
this.addEventListener("mousedown", (event) => {
if (event.originalTarget.classList.contains("searchbar-search-button")) {
this._clickClosedPopup = this._textbox.popup._isHiding;
}
}, true);
this.addEventListener("mousedown", (event) => {
// Ignore right clicks
if (event.button != 0) {
return;
}
// Ignore clicks on the search go button.
if (event.originalTarget.classList.contains("search-go-button")) {
return;
}
let isIconClick = event.originalTarget.classList.contains("searchbar-search-button");
// Ignore clicks on the icon if they were made to close the popup
if (isIconClick && this._clickClosedPopup) {
return;
}
// Open the suggestions whenever clicking on the search icon or if there
// is text in the textbox.
if (isIconClick || this._textbox.value) {
this.openSuggestionsPanel(true);
}
});
}
}
customElements.define("searchbar", MozSearchbar);
}

Просмотреть файл

@ -4,6 +4,7 @@
browser.jar:
content/browser/search/search.xml (content/search.xml)
content/browser/search/searchbar.js (content/searchbar.js)
content/browser/search/searchReset.xhtml (content/searchReset.xhtml)
content/browser/search/searchReset.js (content/searchReset.js)

Просмотреть файл

@ -70,8 +70,7 @@ function promiseSetEngine() {
case "engine-current":
ok(ss.currentEngine.name == "Bug 426329", "currentEngine set");
searchBar = BrowserSearch.searchBar;
searchButton = document.getAnonymousElementByAttribute(searchBar,
"anonid", "search-go-button");
searchButton = searchBar.querySelector(".search-go-button");
ok(searchButton, "got search-go-button");
searchBar.value = "test";
@ -117,7 +116,7 @@ async function prepareTest() {
if (document.activeElement == searchBar)
return;
let focusPromise = BrowserTestUtils.waitForEvent(searchBar, "focus");
let focusPromise = BrowserTestUtils.waitForEvent(searchBar.textbox, "focus");
gURLBar.focus();
searchBar.focus();
await focusPromise;

Просмотреть файл

@ -17,9 +17,7 @@ add_task(async function init() {
registerCleanupFunction(() => {
gCUITestUtils.removeSearchBar();
});
searchIcon = document.getAnonymousElementByAttribute(
searchbar, "anonid", "searchbar-search-button"
);
searchIcon = searchbar.querySelector(".searchbar-search-button");
let currentEngine = Services.search.currentEngine;
await promiseNewEngine("testEngine_diacritics.xml", {setAsCurrent: false});

Просмотреть файл

@ -25,9 +25,7 @@ add_task(async function init() {
registerCleanupFunction(() => {
gCUITestUtils.removeSearchBar();
});
searchIcon = document.getAnonymousElementByAttribute(
searchbar, "anonid", "searchbar-search-button"
);
searchIcon = searchbar.querySelector(".searchbar-search-button");
await promiseNewEngine(TEST_ENGINE_BASENAME, {
setAsCurrent: false,

Просмотреть файл

@ -31,9 +31,7 @@ add_task(async function init() {
registerCleanupFunction(() => {
gCUITestUtils.removeSearchBar();
});
searchIcon = document.getAnonymousElementByAttribute(
searchbar, "anonid", "searchbar-search-button"
);
searchIcon = searchbar.querySelector(".searchbar-search-button");
await promiseNewEngine(TEST_ENGINE_BASENAME, {
setAsCurrent: false,

Просмотреть файл

@ -58,9 +58,7 @@ add_task(async function init() {
registerCleanupFunction(() => {
gCUITestUtils.removeSearchBar();
});
searchIcon = document.getAnonymousElementByAttribute(
searchbar, "anonid", "searchbar-search-button"
);
searchIcon = searchbar.querySelector(".searchbar-search-button");
await promiseNewEngine("testEngine.xml");
});
@ -97,7 +95,7 @@ add_task(async function test_notext() {
});
add_task(async function test_text() {
searchbar._textbox.value = "foo";
searchbar.textbox.value = "foo";
let promise = promiseEvent(searchPopup, "popupshown");
info("Opening search panel");
@ -136,7 +134,7 @@ add_task(async function test_text() {
});
let url = Services.search.currentEngine
.getSubmission(searchbar._textbox.value).uri.spec;
.getSubmission(searchbar.textbox.value).uri.spec;
await promiseTabLoadEvent(gBrowser.selectedTab, url);
// Move the cursor out of the panel area to avoid messing with other tests.
@ -144,5 +142,5 @@ add_task(async function test_text() {
});
add_task(async function cleanup() {
searchbar._textbox.value = "";
searchbar.textbox.value = "";
});

Просмотреть файл

@ -28,7 +28,7 @@ add_task(async function init() {
registerCleanupFunction(() => {
gCUITestUtils.removeSearchBar();
});
textbox = searchbar._textbox;
textbox = searchbar.textbox;
await promiseNewEngine("testEngine.xml");

Просмотреть файл

@ -63,13 +63,9 @@ add_task(async function init() {
registerCleanupFunction(() => {
gCUITestUtils.removeSearchBar();
});
textbox = searchbar._textbox;
searchIcon = document.getAnonymousElementByAttribute(
searchbar, "anonid", "searchbar-search-button"
);
goButton = document.getAnonymousElementByAttribute(
searchbar, "anonid", "search-go-button"
);
textbox = searchbar.textbox;
searchIcon = searchbar.querySelector(".searchbar-search-button");
goButton = searchbar.querySelector(".search-go-button");
await promiseNewEngine("testEngine.xml");
@ -231,7 +227,7 @@ add_task(async function focus_change_closes_popup() {
is(textbox.selectionEnd, 3, "Should have selected all of the text");
promise = promiseEvent(searchPopup, "popuphidden");
let promise2 = promiseEvent(searchbar, "blur");
let promise2 = promiseEvent(searchbar.textbox, "blur");
EventUtils.synthesizeKey("KEY_Tab", {shiftKey: true});
await promise;
await promise2;
@ -254,7 +250,7 @@ add_task(async function focus_change_closes_small_popup() {
is(Services.focus.focusedElement, textbox.inputField, "Should have focused the search bar");
promise = promiseEvent(searchPopup, "popuphidden");
let promise2 = promiseEvent(searchbar, "blur");
let promise2 = promiseEvent(searchbar.textbox, "blur");
EventUtils.synthesizeKey("KEY_Tab", {shiftKey: true});
await promise;
await promise2;
@ -369,7 +365,7 @@ add_task(async function refocus_window_doesnt_open_popup_mouse() {
}
searchPopup.addEventListener("popupshowing", listener);
promise = promiseEvent(searchbar, "focus");
promise = promiseEvent(searchbar.textbox, "focus");
newWin.close();
await promise;
@ -406,7 +402,7 @@ add_task(async function refocus_window_doesnt_open_popup_keyboard() {
}
searchPopup.addEventListener("popupshowing", listener);
promise = promiseEvent(searchbar, "focus");
promise = promiseEvent(searchbar.textbox, "focus");
newWin.close();
await promise;

Просмотреть файл

@ -28,10 +28,8 @@ add_task(async function init() {
registerCleanupFunction(() => {
gCUITestUtils.removeSearchBar();
});
textbox = searchbar._textbox;
searchIcon = document.getAnonymousElementByAttribute(
searchbar, "anonid", "searchbar-search-button"
);
textbox = searchbar.textbox;
searchIcon = searchbar.querySelector(".searchbar-search-button");
await promiseNewEngine("testEngine.xml");

Просмотреть файл

@ -153,9 +153,7 @@ var UITour = {
["searchIcon", {
query: (aDocument) => {
let searchbar = aDocument.getElementById("searchbar");
return aDocument.getAnonymousElementByAttribute(searchbar,
"anonid",
"searchbar-search-button");
return searchbar.querySelector(".searchbar-search-button");
},
widgetName: "search-container",
}],

Просмотреть файл

@ -72,7 +72,7 @@ def rust_compiler(rustc_info, cargo_info):
You can install rust by running './mach bootstrap'
or by directly running the installer from https://rustup.rs/
'''))
rustc_min_version = Version('1.28.0')
rustc_min_version = Version('1.29.0')
cargo_min_version = rustc_min_version
version = rustc_info.version

Просмотреть файл

@ -35,6 +35,18 @@ function setupCommands(dependencies) {
};
}
function createObjectClient(grip) {
return debuggerClient.createObjectClient(grip);
}
function releaseActor(actor) {
if (!actor) {
return;
}
return debuggerClient.release(actor);
}
function sendPacket(packet, callback = r => r) {
return debuggerClient.request(packet).then(callback);
}
@ -406,6 +418,8 @@ async function fetchWorkers() {
const clientCommands = {
autocomplete,
blackBox,
createObjectClient,
releaseActor,
interrupt,
eventListeners,
pauseGrip,

Просмотреть файл

@ -47,19 +47,24 @@ const {
Rep
},
MODE,
ObjectInspector,
ObjectInspectorUtils
objectInspector
} = _devtoolsReps2.default;
const {
createNode,
getChildren,
getValue,
nodeIsPrimitive,
NODE_TYPES
} = ObjectInspectorUtils.node;
ObjectInspector,
utils
} = objectInspector;
const {
loadItemProperties
} = ObjectInspectorUtils.loadProperties;
node: {
createNode,
getChildren,
getValue,
nodeIsPrimitive,
NODE_TYPES
},
loadProperties: {
loadItemProperties
}
} = utils;
function inPreview(event) {
const relatedTarget = event.relatedTarget;

Просмотреть файл

@ -36,6 +36,10 @@ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { de
function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }
const {
ObjectInspector
} = _devtoolsReps.objectInspector;
class Expressions extends _react.Component {
constructor(props) {
super(props);
@ -157,7 +161,7 @@ class Expressions extends _react.Component {
onDoubleClick: (items, options) => this.editExpression(expression, index)
}, _react2.default.createElement("div", {
className: "expression-content"
}, _react2.default.createElement(_devtoolsReps.ObjectInspector, {
}, _react2.default.createElement(ObjectInspector, {
roots: [root],
autoExpandDepth: 0,
disableWrap: true,

Просмотреть файл

@ -28,12 +28,15 @@ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { de
* 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/>. */
const {
createNode,
getChildren
} = _devtoolsReps.ObjectInspectorUtils.node;
const {
loadItemProperties
} = _devtoolsReps.ObjectInspectorUtils.loadProperties;
component: ObjectInspector,
utils: {
createNode,
getChildren,
loadProperties: {
loadItemProperties
}
}
} = _devtoolsReps.objectInspector;
class FrameworkComponent extends _react.PureComponent {
async componentWillMount() {
@ -83,7 +86,7 @@ class FrameworkComponent extends _react.PureComponent {
roots = roots.filter(r => ["state", "props"].includes(r.name));
return _react2.default.createElement("div", {
className: "pane framework-component"
}, _react2.default.createElement(_devtoolsReps.ObjectInspector, {
}, _react2.default.createElement(ObjectInspector, {
roots: roots,
autoExpandAll: false,
autoExpandDepth: 0,

Просмотреть файл

@ -27,6 +27,10 @@ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { de
/* 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/>. */
const {
ObjectInspector
} = _devtoolsReps.objectInspector;
class Scopes extends _react.PureComponent {
constructor(props, ...args) {
const {
@ -79,7 +83,7 @@ class Scopes extends _react.PureComponent {
if (scopes && !isLoading) {
return _react2.default.createElement("div", {
className: "pane scopes-list"
}, _react2.default.createElement(_devtoolsReps.ObjectInspector, {
}, _react2.default.createElement(ObjectInspector, {
roots: scopes,
autoExpandAll: false,
autoExpandDepth: 1,

Просмотреть файл

@ -68,6 +68,8 @@ var _debuggee = require("./debuggee");
var _debuggee2 = _interopRequireDefault(_debuggee);
var _devtoolsReps = require("devtools/client/shared/components/reps/reps.js");
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
/* This Source Code Form is subject to the terms of the Mozilla Public
@ -94,5 +96,6 @@ exports.default = {
projectTextSearch: _projectTextSearch2.default,
quickOpen: _quickOpen2.default,
sourceTree: _sourceTree2.default,
debuggee: _debuggee2.default
debuggee: _debuggee2.default,
objectInspector: _devtoolsReps.objectInspector.reducer.default
};

Просмотреть файл

@ -257,4 +257,20 @@ Object.defineProperty(exports, "getBreakpointSources", {
get: function () {
return _breakpointSources.getBreakpointSources;
}
});
var _devtoolsReps = require("devtools/client/shared/components/reps/reps.js");
const {
reducer
} = _devtoolsReps.objectInspector;
Object.keys(reducer).forEach(function (key) {
if (key === "default" || key === "__esModule") {
return;
}
Object.defineProperty(exports, key, {
enumerable: true,
get: reducer[key]
});
});

Просмотреть файл

@ -16,6 +16,8 @@ var _frames = require("../../pause/frames/index");
/* 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/>. */
const { utils: { node: { NODE_TYPES } } } = _devtoolsReps.objectInspector;
function getScopeTitle(type, scope) {
if (type === "block" && scope.block && scope.block.displayName) {
return scope.block.displayName;
@ -65,7 +67,7 @@ function getScope(scope, selectedFrame, frameScopes, why, scopeIndex) {
name: title,
path: key,
contents: vars,
type: _devtoolsReps.ObjectInspectorUtils.node.NODE_TYPES.BLOCK
type: NODE_TYPES.BLOCK
};
}
} else if (type === "object" && scope.object) {

Просмотреть файл

@ -87,10 +87,12 @@ add_task(async function() {
// Test expanding properties when the debuggee is active
await resume(dbg);
await addExpression(dbg, "location");
await toggleExpressionNode(dbg, 1);
is(findAllElements(dbg, "expressionNodes").length, 17);
await toggleExpressionNode(dbg, 1);
is(findAllElements(dbg, "expressionNodes").length, 1);
await deleteExpression(dbg, "location");
is(findAllElements(dbg, "expressionNodes").length, 0);
});

Просмотреть файл

@ -24,5 +24,5 @@ add_task(async function() {
await stepOver(dbg);
is(getLabel(dbg, 4), "foo()");
is(getLabel(dbg, 5), "Window");
is(getLabel(dbg, 11), "Window");
});

Просмотреть файл

@ -7,7 +7,8 @@
const { createFactory, PureComponent } = require("devtools/client/shared/vendor/react");
const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
const { REPS, MODE } = require("devtools/client/shared/components/reps/reps");
// TODO: Upgrade to the current version reps https://bugzilla.mozilla.org/show_bug.cgi?id=1494680
const { REPS, MODE } = require("devtools/client/shared/components/reps/reps-old");
const { Rep } = REPS;
const TreeViewClass = require("devtools/client/shared/components/tree/TreeView");
const TreeView = createFactory(TreeViewClass);

Просмотреть файл

@ -8,7 +8,9 @@ const { createFactory, PureComponent } = require("devtools/client/shared/vendor/
const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
const Accordion = createFactory(require("devtools/client/inspector/layout/components/Accordion"));
const reps = require("devtools/client/shared/components/reps/reps");
// TODO: Upgrade to the current version reps https://bugzilla.mozilla.org/show_bug.cgi?id=1494680
const reps = require("devtools/client/shared/components/reps/reps-old");
const Types = require("../types");
const { REPS, MODE } = reps;

Просмотреть файл

@ -5,6 +5,7 @@
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
DevToolsModules(
'reps-old.js',
'reps.css',
'reps.js',
)

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Просмотреть файл

@ -26,7 +26,7 @@ function filterTextSet(text) {
}
function filterToggle(filter) {
return (dispatch, getState, {prefsService}) => {
return ({dispatch, getState, prefsService}) => {
dispatch({
type: FILTER_TOGGLE,
filter,
@ -37,7 +37,7 @@ function filterToggle(filter) {
}
function filtersClear() {
return (dispatch, getState, {prefsService}) => {
return ({dispatch, getState, prefsService}) => {
dispatch({
type: FILTERS_CLEAR,
});
@ -58,7 +58,7 @@ function filtersClear() {
* to keep non-default filters the user might have set.
*/
function defaultFiltersReset() {
return (dispatch, getState, {prefsService}) => {
return ({dispatch, getState, prefsService}) => {
// Get the state before dispatching so the action does not alter prefs reset.
const filterState = getAllFilters(getState());

Просмотреть файл

@ -78,7 +78,7 @@ function messageClose(id) {
}
function messageTableDataGet(id, client, dataType) {
return (dispatch) => {
return ({dispatch}) => {
let fetchObjectActorData;
if (["Map", "WeakMap", "Set", "WeakSet"].includes(dataType)) {
fetchObjectActorData = (cb) => client.enumEntries(cb);

Просмотреть файл

@ -23,7 +23,7 @@ const {
} = require("devtools/client/webconsole/constants");
function filterBarToggle() {
return (dispatch, getState, {prefsService}) => {
return ({dispatch, getState, prefsService}) => {
dispatch({
type: FILTER_BAR_TOGGLE,
});
@ -33,7 +33,7 @@ function filterBarToggle() {
}
function persistToggle() {
return (dispatch, getState, {prefsService}) => {
return ({dispatch, getState, prefsService}) => {
dispatch({
type: PERSIST_TOGGLE,
});
@ -83,7 +83,7 @@ function splitConsoleCloseButtonToggle(shouldDisplayButton) {
* @param {String} messageId: id of the message containing the {actor} parameter.
*/
function showMessageObjectInSidebar(actor, messageId) {
return (dispatch, getState) => {
return ({dispatch, getState}) => {
const { parameters } = getMessage(getState(), messageId);
if (Array.isArray(parameters)) {
for (const parameter of parameters) {

Просмотреть файл

@ -16,7 +16,7 @@ const { getObjectInspector } = require("devtools/client/webconsole/utils/object-
const actions = require("devtools/client/webconsole/actions/index");
const reps = require("devtools/client/shared/components/reps/reps");
const { MODE, ObjectInspectorUtils } = reps;
const { MODE, objectInspector: {utils} } = reps;
GripMessageBody.displayName = "GripMessageBody";
@ -66,7 +66,7 @@ function GripMessageBody(props) {
// fixed the issue (See Bug 1456060).
focusable: false,
onCmdCtrlClick: (node, { depth, event, focused, expanded }) => {
const value = ObjectInspectorUtils.node.getValue(node);
const value = utils.node.getValue(node);
if (value) {
dispatch(actions.showObjectInSidebar(value));
}

Просмотреть файл

@ -10,7 +10,7 @@
function thunk(options = {}, { dispatch, getState }) {
return next => action => {
return (typeof action === "function")
? action(dispatch, getState, options)
? action({dispatch, getState, ...options})
: next(action);
};
}

Просмотреть файл

@ -12,6 +12,8 @@ const { ui } = require("./ui");
const { notifications } = require("./notifications");
const { history } = require("./history");
const { objectInspector } = require("devtools/client/shared/components/reps/reps.js");
exports.reducers = {
filters,
messages,
@ -19,4 +21,5 @@ exports.reducers = {
ui,
notifications,
history,
objectInspector: objectInspector.reducer.default
};

Просмотреть файл

@ -79,7 +79,7 @@ function configureStore(hud, options = {}) {
// Prepare middleware.
const middleware = applyMiddleware(
thunk.bind(null, {prefsService}),
thunk.bind(null, {prefsService, client: (options.services || {})}),
historyPersistence,
eventTelemetry.bind(null, options.telemetry, options.sessionId),
);

Просмотреть файл

@ -223,7 +223,11 @@ describe("ConsoleAPICall component:", () => {
it("renders", () => {
const message = stubPreparedMessages.get(
"console.assert(false, {message: 'foobar'})");
const wrapper = render(ConsoleApiCall({ message, serviceContainer }));
// We need to wrap the ConsoleApiElement in a Provider in order for the
// ObjectInspector to work.
const wrapper = render(Provider({ store: setupStore() },
ConsoleApiCall({ message, serviceContainer })));
expect(wrapper.find(".message-body").text())
.toBe("Assertion failed: Object { message: \"foobar\" }");
@ -248,13 +252,19 @@ describe("ConsoleAPICall component:", () => {
describe("console.timeLog", () => {
it("renders as expected", () => {
let message = stubPreparedMessages.get("console.timeLog('bar') - 1");
let wrapper = render(ConsoleApiCall({ message, serviceContainer }));
// We need to wrap the ConsoleApiElement in a Provider in order for the
// ObjectInspector to work.
let wrapper = render(Provider({ store: setupStore() },
ConsoleApiCall({ message, serviceContainer })));
expect(wrapper.find(".message-body").text()).toBe(message.parameters[0]);
expect(wrapper.find(".message-body").text()).toMatch(/^bar: \d+(\.\d+)?ms$/);
message = stubPreparedMessages.get("console.timeLog('bar') - 2");
wrapper = render(ConsoleApiCall({ message, serviceContainer }));
// We need to wrap the ConsoleApiElement in a Provider in order for the
// ObjectInspector to work.
wrapper = render(Provider({ store: setupStore() },
ConsoleApiCall({ message, serviceContainer })));
expect(wrapper.find(".message-body").text())
.toMatch(/^bar: \d+(\.\d+)?ms second call Object \{ state\: 1 \}$/);
});
@ -519,7 +529,10 @@ describe("ConsoleAPICall component:", () => {
describe("console.dirxml", () => {
it("renders", () => {
const message = stubPreparedMessages.get("console.dirxml(window)");
const wrapper = render(ConsoleApiCall({ message, serviceContainer }));
// We need to wrap the ConsoleApiElement in a Provider in order for the
// ObjectInspector to work.
const wrapper = render(Provider({ store: setupStore() },
ConsoleApiCall({ message, serviceContainer })));
expect(wrapper.find(".message-body").text())
.toBe("Window http://example.com/browser/devtools/client/webconsole/test/fixtures/stub-generators/test-console-api.html");
@ -529,7 +542,11 @@ describe("ConsoleAPICall component:", () => {
describe("console.dir", () => {
it("renders", () => {
const message = stubPreparedMessages.get("console.dir({C, M, Y, K})");
const wrapper = render(ConsoleApiCall({ message, serviceContainer }));
// We need to wrap the ConsoleApiElement in a Provider in order for the
// ObjectInspector to work.
const wrapper = render(Provider({ store: setupStore() },
ConsoleApiCall({ message, serviceContainer })));
expect(wrapper.find(".message-body").text())
.toBe(`Object { cyan: "C", magenta: "M", yellow: "Y", black: "K" }`);

Просмотреть файл

@ -8,6 +8,7 @@ const {
// Test utils.
const expect = require("expect");
const { render } = require("enzyme");
const Provider = createFactory(require("react-redux").Provider);
const ConsoleOutput = createFactory(require("devtools/client/webconsole/components/ConsoleOutput"));
const serviceContainer = require("devtools/client/webconsole/test/fixtures/serviceContainer");
@ -37,13 +38,19 @@ function getDefaultProps(initialized) {
describe("ConsoleOutput component:", () => {
it("Render only the last messages that fits the viewport when non-initialized", () => {
const rendered = render(ConsoleOutput(getDefaultProps(false)));
// We need to wrap the ConsoleApiElement in a Provider in order for the
// ObjectInspector to work.
const rendered = render(Provider({ store: setupStore() },
ConsoleOutput(getDefaultProps(false))));
const messagesNumber = rendered.find(".message").length;
expect(messagesNumber).toBe(getInitialMessageCountForViewport(window));
});
it("Render every message when initialized", () => {
const rendered = render(ConsoleOutput(getDefaultProps(true)));
// We need to wrap the ConsoleApiElement in a Provider in order for the
// ObjectInspector to work.
const rendered = render(Provider({ store: setupStore() },
ConsoleOutput(getDefaultProps(true))));
expect(rendered.find(".message").length).toBe(MESSAGES_NUMBER);
});
});

Просмотреть файл

@ -23,7 +23,10 @@ const serviceContainer = require("devtools/client/webconsole/test/fixtures/servi
describe("EvaluationResult component:", () => {
it("renders a grip result", () => {
const message = stubPreparedMessages.get("new Date(0)");
const wrapper = render(EvaluationResult({ message, serviceContainer }));
// We need to wrap the ConsoleApiElement in a Provider in order for the
// ObjectInspector to work.
const wrapper = render(Provider({ store: setupStore() },
EvaluationResult({ message, serviceContainer })));
expect(wrapper.find(".message-body").text()).toBe("Date 1970-01-01T00:00:00.000Z");
@ -70,7 +73,10 @@ describe("EvaluationResult component:", () => {
it("renders an inspect command result", () => {
const message = stubPreparedMessages.get("inspect({a: 1})");
const wrapper = render(EvaluationResult({ message, serviceContainer }));
// We need to wrap the ConsoleApiElement in a Provider in order for the
// ObjectInspector to work.
const wrapper = render(Provider({ store: setupStore() },
EvaluationResult({ message, serviceContainer })));
expect(wrapper.find(".message-body").text()).toBe("Object { a: 1 }");
});
@ -108,15 +114,18 @@ describe("EvaluationResult component:", () => {
const message = stubPreparedMessages.get("new Date(0)");
const indent = 10;
let wrapper = render(EvaluationResult({
// We need to wrap the ConsoleApiElement in a Provider in order for the
// ObjectInspector to work.
let wrapper = render(Provider({ store: setupStore() }, EvaluationResult({
message: Object.assign({}, message, {indent}),
serviceContainer,
}));
})));
let indentEl = wrapper.find(".indent");
expect(indentEl.prop("style").width).toBe(`${indent * INDENT_WIDTH}px`);
expect(indentEl.prop("data-indent")).toBe(`${indent}`);
wrapper = render(EvaluationResult({ message, serviceContainer}));
wrapper = render(Provider({ store: setupStore() },
EvaluationResult({ message, serviceContainer})));
indentEl = wrapper.find(".indent");
expect(indentEl.prop("style").width).toBe(`0`);
expect(indentEl.prop("data-indent")).toBe(`0`);
@ -133,11 +142,13 @@ describe("EvaluationResult component:", () => {
it("has a timestamp when passed a truthy timestampsVisible prop", () => {
const message = stubPreparedMessages.get("new Date(0)");
const wrapper = render(EvaluationResult({
// We need to wrap the ConsoleApiElement in a Provider in order for the
// ObjectInspector to work.
const wrapper = render(Provider({ store: setupStore() }, EvaluationResult({
message,
serviceContainer,
timestampsVisible: true,
}));
})));
const { timestampString } = require("devtools/client/webconsole/webconsole-l10n");
expect(wrapper.find(".timestamp").text()).toBe(timestampString(message.timeStamp));
@ -145,11 +156,13 @@ describe("EvaluationResult component:", () => {
it("does not have a timestamp when timestampsVisible prop is falsy", () => {
const message = stubPreparedMessages.get("new Date(0)");
const wrapper = render(EvaluationResult({
// We need to wrap the ConsoleApiElement in a Provider in order for the
// ObjectInspector to work.
const wrapper = render(Provider({ store: setupStore() }, EvaluationResult({
message,
serviceContainer,
timestampsVisible: false,
}));
})));
expect(wrapper.find(".timestamp").length).toBe(0);
});

Просмотреть файл

@ -20,7 +20,7 @@ const WebConsoleOutputWrapper =
const { messagesAdd } =
require("devtools/client/webconsole/actions/messages");
function getWebConsoleOutputWrapper() {
async function getWebConsoleOutputWrapper() {
const hud = {
proxy: {
releaseActor: () => {},
@ -31,12 +31,16 @@ function getWebConsoleOutputWrapper() {
},
},
};
return new WebConsoleOutputWrapper(null, hud);
const owner = { target: {client: {} } };
const wcow = new WebConsoleOutputWrapper(null, hud, null, owner);
await wcow.init();
return wcow;
}
describe("WebConsoleOutputWrapper", () => {
it("clears queues when dispatchMessagesClear is called", () => {
const ncow = getWebConsoleOutputWrapper();
it("clears queues when dispatchMessagesClear is called", async () => {
const ncow = await getWebConsoleOutputWrapper();
ncow.queuedMessageAdds.push({fakePacket: "message"});
ncow.queuedMessageUpdates.push({fakePacket: "message-update"});
ncow.queuedRequestUpdates.push({fakePacket: "request-update"});
@ -48,24 +52,30 @@ describe("WebConsoleOutputWrapper", () => {
expect(ncow.queuedRequestUpdates.length).toBe(0);
});
it("removes private packets from message queue on dispatchPrivateMessagesClear", () => {
const ncow = getWebConsoleOutputWrapper();
it("removes private packets from message queue on dispatchPrivateMessagesClear",
async () => {
const ncow = await getWebConsoleOutputWrapper();
const publicLog = stubPackets.get("console.log('mymap')");
ncow.queuedMessageAdds.push(
getPrivatePacket("console.trace()"),
publicLog,
getPrivatePacket("XHR POST request"),
);
const publicLog = stubPackets.get("console.log('mymap')");
ncow.queuedMessageAdds.push(
getPrivatePacket("console.trace()"),
publicLog,
getPrivatePacket("XHR POST request"),
);
ncow.dispatchPrivateMessagesClear();
expect(ncow.queuedMessageAdds).toEqual([publicLog]);
});
ncow.dispatchPrivateMessagesClear();
expect(ncow.queuedMessageAdds).toEqual([publicLog]);
});
it("removes private packets from network update queue on dispatchPrivateMessagesClear",
() => {
const ncow = getWebConsoleOutputWrapper();
async () => {
const ncow = await getWebConsoleOutputWrapper();
const publicLog = stubPackets.get("console.log('mymap')");
ncow.queuedMessageAdds.push(
getPrivatePacket("console.trace()"),
publicLog,
getPrivatePacket("XHR POST request"),
);
const postId = Symbol();
const getId = Symbol();
@ -97,8 +107,8 @@ describe("WebConsoleOutputWrapper", () => {
});
it("removes private packets from network request queue on dispatchPrivateMessagesClear",
() => {
const ncow = getWebConsoleOutputWrapper();
async () => {
const ncow = await getWebConsoleOutputWrapper();
ncow.getStore().dispatch(messagesAdd([
stubPackets.get("GET request"),

Просмотреть файл

@ -5,12 +5,10 @@
"use strict";
const { createFactory } = require("devtools/client/shared/vendor/react");
const ObjectClient = require("devtools/shared/client/object-client");
const LongStringClient = require("devtools/shared/client/long-string-client");
const reps = require("devtools/client/shared/components/reps/reps");
const { REPS, MODE } = reps;
const ObjectInspector = createFactory(reps.ObjectInspector);
const { REPS, MODE, objectInspector } = reps;
const ObjectInspector = createFactory(objectInspector.ObjectInspector);
const { Grip } = REPS;
/**
@ -30,6 +28,7 @@ function getObjectInspector(grip, serviceContainer, override = {}) {
let onDOMNodeMouseOver;
let onDOMNodeMouseOut;
let onInspectIconClick;
if (serviceContainer) {
onDOMNodeMouseOver = serviceContainer.highlightDomElement
? (object) => serviceContainer.highlightDomElement(object)
@ -50,16 +49,6 @@ function getObjectInspector(grip, serviceContainer, override = {}) {
autoExpandDepth: 0,
mode: MODE.LONG,
roots,
createObjectClient: object =>
new ObjectClient(serviceContainer.hudProxy.client, object),
createLongStringClient: object =>
new LongStringClient(serviceContainer.hudProxy.client, object),
releaseActor: actor => {
if (!actor || !serviceContainer.hudProxy.releaseActor) {
return;
}
serviceContainer.hudProxy.releaseActor(actor);
},
onViewSourceInDebugger: serviceContainer.onViewSourceInDebugger,
recordTelemetryEvent: serviceContainer.recordTelemetryEvent,
openLink: serviceContainer.openLink,
@ -85,7 +74,7 @@ function getObjectInspector(grip, serviceContainer, override = {}) {
function createRootsFromGrip(grip) {
return [{
path: (grip && grip.actor) || JSON.stringify(grip),
path: Symbol((grip && grip.actor) || JSON.stringify(grip)),
contents: { value: grip }
}];
}

Просмотреть файл

@ -80,6 +80,10 @@ WebConsoleFrame.prototype = {
await this._initConnection();
await this.consoleOutput.init();
// Toggle the timestamp on preference change
Services.prefs.addObserver(PREF_MESSAGE_TIMESTAMP, this._onToolboxPrefChanged);
this._onToolboxPrefChanged();
const id = WebConsoleUtils.supportsString(this.hudId);
if (Services.obs) {
Services.obs.notifyObservers(id, "web-console-created");
@ -233,10 +237,8 @@ WebConsoleFrame.prototype = {
const toolbox = gDevTools.getToolbox(this.owner.target);
this.consoleOutput = new this.window.WebConsoleOutput(
this.outputNode, this, toolbox, this.owner, this.document);
// Toggle the timestamp on preference change
Services.prefs.addObserver(PREF_MESSAGE_TIMESTAMP, this._onToolboxPrefChanged);
this._onToolboxPrefChanged();
this.outputNode, this, toolbox, this.owner, this.document
);
this._initShortcuts();
this._initOutputSyntaxHighlighting();

Просмотреть файл

@ -12,12 +12,15 @@ const { Provider } = require("devtools/client/shared/vendor/react-redux");
const actions = require("devtools/client/webconsole/actions/index");
const { createContextMenu, createEditContextMenu } = require("devtools/client/webconsole/utils/context-menu");
const { configureStore } = require("devtools/client/webconsole/store");
const { isPacketPrivate } = require("devtools/client/webconsole/utils/messages");
const { getAllMessagesById, getMessage } = require("devtools/client/webconsole/selectors/messages");
const Telemetry = require("devtools/client/shared/telemetry");
const EventEmitter = require("devtools/shared/event-emitter");
const App = createFactory(require("devtools/client/webconsole/components/App"));
const ObjectClient = require("devtools/shared/client/object-client");
const LongStringClient = require("devtools/shared/client/long-string-client");
let store = null;
@ -38,12 +41,6 @@ function WebConsoleOutputWrapper(parentNode, hud, toolbox, owner, document) {
this.throttledDispatchPromise = null;
this.telemetry = new Telemetry();
store = configureStore(this.hud, {
// We may not have access to the toolbox (e.g. in the browser console).
sessionId: this.toolbox && this.toolbox.sessionId || -1,
telemetry: this.telemetry,
});
}
WebConsoleOutputWrapper.prototype = {
@ -53,6 +50,8 @@ WebConsoleOutputWrapper.prototype = {
this.hud[id] = node;
};
const { hud } = this;
const debuggerClient = this.owner.target.client;
const serviceContainer = {
attachRefToHud,
emitNewMessage: (node, messageId, timeStamp) => {
@ -85,6 +84,21 @@ WebConsoleOutputWrapper.prototype = {
...extra,
"session_id": this.toolbox && this.toolbox.sessionId || -1
});
},
createObjectClient: (object) => {
return new ObjectClient(debuggerClient, object);
},
createLongStringClient: (object) => {
return new LongStringClient(debuggerClient, object);
},
releaseActor: (actor) => {
if (!actor) {
return null;
}
return debuggerClient.release(actor);
}
};
@ -210,6 +224,13 @@ WebConsoleOutputWrapper.prototype = {
});
}
store = configureStore(this.hud, {
// We may not have access to the toolbox (e.g. in the browser console).
sessionId: this.toolbox && this.toolbox.sessionId || -1,
telemetry: this.telemetry,
services: serviceContainer
});
const {prefs} = store.getState();
const app = App({
attachRefToHud,
@ -223,8 +244,13 @@ WebConsoleOutputWrapper.prototype = {
});
// Render the root Application component.
const provider = createElement(Provider, { store }, app);
this.body = ReactDOM.render(provider, this.parentNode);
if (this.parentNode) {
const provider = createElement(Provider, { store }, app);
this.body = ReactDOM.render(provider, this.parentNode);
} else {
// If there's no parentNode, we are in a test. So we can resolve immediately.
resolve();
}
});
},

Просмотреть файл

@ -2211,6 +2211,14 @@ nsContentUtils::IsCallerChrome()
return xpc::IsUniversalXPConnectEnabled(GetCurrentJSContext());
}
#ifdef FUZZING
bool
nsContentUtils::IsFuzzingEnabled()
{
return StaticPrefs::fuzzing_enabled();
}
#endif
/* static */
bool
nsContentUtils::ShouldResistFingerprinting()

Просмотреть файл

@ -216,9 +216,22 @@ public:
// Strip off "wyciwyg://n/" part of a URL. aURI must have "wyciwyg" scheme.
static nsresult RemoveWyciwygScheme(nsIURI* aURI, nsIURI** aReturn);
static bool IsCallerChrome();
static bool ThreadsafeIsCallerChrome();
static bool IsCallerContentXBL();
static bool IsCallerChrome();
static bool ThreadsafeIsCallerChrome();
static bool IsCallerContentXBL();
static bool IsFuzzingEnabled()
#ifndef FUZZING
{
return false;
}
#else
;
#endif
static bool IsCallerChromeOrFuzzingEnabled(JSContext* aCx, JSObject*)
{
return ThreadsafeIsSystemCaller(aCx) || IsFuzzingEnabled();
}
// The APIs for checking whether the caller is system (in the sense of system
// principal) should only be used when the JSContext is known to accurately

Просмотреть файл

@ -105,6 +105,7 @@ class Configuration(DescriptorProvider):
(partialIface.location, iface.location))
if not (iface.getExtendedAttribute("ChromeOnly") or
iface.getExtendedAttribute("Func") == ["IsChromeOrXBL"] or
iface.getExtendedAttribute("Func") == ["nsContentUtils::IsCallerChromeOrFuzzingEnabled"] or
not (iface.hasInterfaceObject() or
iface.isNavigatorProperty()) or
isInWebIDLRoot(iface.filename())):

Просмотреть файл

@ -1809,7 +1809,7 @@ class IDLNamespace(IDLInterfaceOrNamespace):
if not attr.noArguments():
raise WebIDLError("[%s] must not have arguments" % identifier,
[attr.location])
elif identifier == "Pref":
elif identifier == "Pref" or identifier == "Func":
# Known extended attributes that take a string value
if not attr.hasValue():
raise WebIDLError("[%s] must have a value" % identifier,

Просмотреть файл

@ -9,7 +9,7 @@
*
* See InspectorUtils.h for documentation on these methods.
*/
[ChromeOnly]
[Func="nsContentUtils::IsCallerChromeOrFuzzingEnabled"]
namespace InspectorUtils {
// documentOnly tells whether user and UA sheets should get included.
sequence<StyleSheet> getAllStyleSheets(Document document, optional boolean documentOnly = false);
@ -129,7 +129,7 @@ dictionary InspectorFontFeature {
required DOMString languageSystem;
};
[ChromeOnly]
[Func="nsContentUtils::IsCallerChromeOrFuzzingEnabled"]
interface InspectorFontFace {
// An indication of how we found this font during font-matching.
// Note that the same physical font may have been found in multiple ways within a range.

Просмотреть файл

@ -7,6 +7,12 @@
#ifndef GFX_VR_EXTERNAL_API_H
#define GFX_VR_EXTERNAL_API_H
#define GFX_VR_EIGHTCC(c1, c2, c3, c4, c5, c6, c7, c8) \
((uint64_t)(c1) << 56 | (uint64_t)(c2) << 48 | \
(uint64_t)(c3) << 40 | (uint64_t)(c4) << 32 | \
(uint64_t)(c5) << 24 | (uint64_t)(c6) << 16 | \
(uint64_t)(c7) << 8 | (uint64_t)(c8))
#include <stddef.h>
#include <stdint.h>
#include <type_traits>
@ -29,7 +35,7 @@ namespace dom {
#endif // MOZILLA_INTERNAL_API
namespace gfx {
static const int32_t kVRExternalVersion = 3;
static const int32_t kVRExternalVersion = 4;
// We assign VR presentations to groups with a bitmask.
// Currently, we will only display either content or chrome.
@ -265,6 +271,9 @@ struct VRDisplayState
bool shutdown;
#endif // defined(__ANDROID__)
char mDisplayName[kVRDisplayNameMaxLen];
// eight byte character code identifier
// LSB first, so "ABCDEFGH" -> ('H'<<56) + ('G'<<48) + ('F'<<40) + ('E'<<32) + ('D'<<24) + ('C'<<16) + ('B'<<8) + 'A').
uint64_t mEightCC;
VRDisplayCapabilityFlags mCapabilityFlags;
VRFieldOfView mEyeFOV[VRDisplayState::NumEyes];
Point3D_POD mEyeTranslation[VRDisplayState::NumEyes];
@ -278,6 +287,9 @@ struct VRDisplayState
uint64_t mLastSubmittedFrameId;
bool mLastSubmittedFrameSuccessful;
uint32_t mPresentingGeneration;
// Telemetry
bool mReportsDroppedFrames;
uint64_t mDroppedFrameCount;
};
struct VRControllerState

Просмотреть файл

@ -133,7 +133,9 @@ VRDisplayExternal::StartPresentation()
PushState();
mDisplayInfo.mDisplayState.mLastSubmittedFrameId = 0;
// mTelemetry.mLastDroppedFrameCount = stats.m_nNumReprojectedFrames;
if (mDisplayInfo.mDisplayState.mReportsDroppedFrames) {
mTelemetry.mLastDroppedFrameCount = mDisplayInfo.mDisplayState.mDroppedFrameCount;
}
}
void
@ -149,20 +151,31 @@ VRDisplayExternal::StopPresentation()
PushState(true);
// TODO - Implement telemetry:
Telemetry::HistogramID timeSpentID = Telemetry::HistogramCount;
Telemetry::HistogramID droppedFramesID = Telemetry::HistogramCount;
int viewIn = 0;
/*
const TimeDuration duration = TimeStamp::Now() - mTelemetry.mPresentationStart;
Telemetry::Accumulate(Telemetry::WEBVR_USERS_VIEW_IN, 2);
Telemetry::Accumulate(Telemetry::WEBVR_TIME_SPENT_VIEWING_IN_OPENVR,
duration.ToMilliseconds());
if (mDisplayInfo.mDisplayState.mEightCC == GFX_VR_EIGHTCC('O', 'c', 'u', 'l', 'u', 's', ' ', 'D')) {
// Oculus Desktop API
timeSpentID = Telemetry::WEBVR_TIME_SPENT_VIEWING_IN_OCULUS;
droppedFramesID = Telemetry::WEBVR_DROPPED_FRAMES_IN_OCULUS;
viewIn = 1;
} else if (mDisplayInfo.mDisplayState.mEightCC == GFX_VR_EIGHTCC('O', 'p', 'e', 'n', 'V', 'R', ' ', ' ')) {
// OpenVR API
timeSpentID = Telemetry::WEBVR_TIME_SPENT_VIEWING_IN_OPENVR;
droppedFramesID = Telemetry::WEBVR_DROPPED_FRAMES_IN_OPENVR;
viewIn = 2;
}
::vr::Compositor_CumulativeStats stats;
mVRCompositor->GetCumulativeStats(&stats, sizeof(::vr::Compositor_CumulativeStats));
const uint32_t droppedFramesPerSec = (stats.m_nNumReprojectedFrames -
mTelemetry.mLastDroppedFrameCount) / duration.ToSeconds();
Telemetry::Accumulate(Telemetry::WEBVR_DROPPED_FRAMES_IN_OPENVR, droppedFramesPerSec);
*/
if (viewIn) {
const TimeDuration duration = TimeStamp::Now() - mTelemetry.mPresentationStart;
Telemetry::Accumulate(Telemetry::WEBVR_USERS_VIEW_IN, viewIn);
Telemetry::Accumulate(timeSpentID,
duration.ToMilliseconds());
const uint32_t droppedFramesPerSec = (mDisplayInfo.mDisplayState.mDroppedFrameCount -
mTelemetry.mLastDroppedFrameCount) / duration.ToSeconds();
Telemetry::Accumulate(droppedFramesID, droppedFramesPerSec);
}
}
void

Просмотреть файл

@ -211,6 +211,7 @@ OpenVRSession::InitState(VRSystemState& aSystemState)
{
VRDisplayState& state = aSystemState.displayState;
strncpy(state.mDisplayName, "OpenVR HMD", kVRDisplayNameMaxLen);
state.mEightCC = GFX_VR_EIGHTCC('O', 'p', 'e', 'n', 'V', 'R', ' ', ' ');
state.mIsConnected = mVRSystem->IsTrackedDeviceConnected(::vr::k_unTrackedDeviceIndex_Hmd);
state.mIsMounted = false;
state.mCapabilityFlags = (VRDisplayCapabilityFlags)((int)VRDisplayCapabilityFlags::Cap_None |
@ -219,6 +220,7 @@ OpenVRSession::InitState(VRSystemState& aSystemState)
(int)VRDisplayCapabilityFlags::Cap_External |
(int)VRDisplayCapabilityFlags::Cap_Present |
(int)VRDisplayCapabilityFlags::Cap_StageParameters);
state.mReportsDroppedFrames = true;
::vr::ETrackedPropertyError err;
bool bHasProximitySensor = mVRSystem->GetBoolTrackedDeviceProperty(::vr::k_unTrackedDeviceIndex_Hmd, ::vr::Prop_ContainsProximitySensor_Bool, &err);
@ -776,6 +778,7 @@ OpenVRSession::StartFrame(mozilla::gfx::VRSystemState& aSystemState)
EnumerateControllers(aSystemState);
UpdateControllerButtons(aSystemState);
UpdateControllerPoses(aSystemState);
UpdateTelemetry(aSystemState);
}
bool
@ -1093,5 +1096,13 @@ OpenVRSession::StopAllHaptics()
}
}
void
OpenVRSession::UpdateTelemetry(VRSystemState& aSystemState)
{
::vr::Compositor_CumulativeStats stats;
mVRCompositor->GetCumulativeStats(&stats, sizeof(::vr::Compositor_CumulativeStats));
aSystemState.displayState.mDroppedFrameCount = stats.m_nNumReprojectedFrames;
}
} // namespace mozilla
} // namespace gfx

Просмотреть файл

@ -65,6 +65,7 @@ private:
void EnumerateControllers(VRSystemState& aState);
void UpdateControllerPoses(VRSystemState& aState);
void UpdateControllerButtons(VRSystemState& aState);
void UpdateTelemetry(VRSystemState& aSystemState);
bool SubmitFrame(void* aTextureHandle,
::vr::ETextureType aTextureType,

Просмотреть файл

@ -1696,6 +1696,7 @@ NewRope(JSContext* cx, unsigned argc, Value* vp)
Rooted<JSRope*> str(cx, JSRope::new_<NoGC>(cx, left, right, length, heap));
if (!str) {
JS_ReportOutOfMemory(cx);
return false;
}

Просмотреть файл

@ -0,0 +1,20 @@
if (!('oomTest' in this)) {
quit();
}
function foo() {}
function foooooooooooooooooooooooooooooooo() {}
function fn(s) {
var o = {a:1}
eval(("f" + s) + "()");
if (!('a' in o)) {
print("unreachable");
}
}
for (var i = 0; i < 1100; i++) {
fn("oo");
}
oomTest(new Function(`
let a = newRope("oooooooooooooooo","oooooooooooooooo");
fn(a);
`))

Просмотреть файл

@ -5315,16 +5315,14 @@ CodeGenerator::visitGetDynamicName(LGetDynamicName* lir)
masm.passABIArg(envChain);
masm.passABIArg(name);
masm.passABIArg(temp2);
masm.callWithABI(JS_FUNC_TO_DATA_PTR(void*, GetDynamicName));
masm.callWithABI(JS_FUNC_TO_DATA_PTR(void*, GetDynamicNamePure));
const ValueOperand out = ToOutValue(lir);
masm.loadValue(Address(masm.getStackPointer(), 0), out);
masm.adjustStack(sizeof(Value));
Label undefined;
masm.branchTestUndefined(Assembler::Equal, out, &undefined);
bailoutFrom(&undefined, lir->snapshot());
bailoutIfFalseBool(ReturnReg, lir->snapshot());
}
typedef bool (*DirectEvalSFn)(JSContext*, HandleObject, HandleScript, HandleValue,

Просмотреть файл

@ -710,12 +710,12 @@ CreateThis(JSContext* cx, HandleObject callee, HandleObject newTarget, MutableHa
return true;
}
void
GetDynamicName(JSContext* cx, JSObject* envChain, JSString* str, Value* vp)
bool
GetDynamicNamePure(JSContext* cx, JSObject* envChain, JSString* str, Value* vp)
{
// Lookup a string on the env chain, returning either the value found or
// undefined through rval. This function is infallible, and cannot GC or
// invalidate.
// Lookup a string on the env chain, returning the value found through rval.
// This function is infallible, and cannot GC or invalidate.
// Returns false if the lookup could not be completed without GC.
AutoUnsafeCallWithABI unsafe;
@ -725,26 +725,22 @@ GetDynamicName(JSContext* cx, JSObject* envChain, JSString* str, Value* vp)
} else {
atom = AtomizeString(cx, str);
if (!atom) {
vp->setUndefined();
return;
cx->recoverFromOutOfMemory();
return false;
}
}
if (!frontend::IsIdentifier(atom) || frontend::IsKeyword(atom)) {
vp->setUndefined();
return;
return false;
}
PropertyResult prop;
JSObject* scope = nullptr;
JSObject* pobj = nullptr;
if (LookupNameNoGC(cx, atom->asPropertyName(), envChain, &scope, &pobj, &prop)) {
if (FetchNameNoGC(pobj, prop, MutableHandleValue::fromMarkedLocation(vp))) {
return;
}
return FetchNameNoGC(pobj, prop, vp);
}
vp->setUndefined();
return false;
}
void

Просмотреть файл

@ -751,7 +751,7 @@ GetIntrinsicValue(JSContext* cx, HandlePropertyName name, MutableHandleValue rva
MOZ_MUST_USE bool
CreateThis(JSContext* cx, HandleObject callee, HandleObject newTarget, MutableHandleValue rval);
void GetDynamicName(JSContext* cx, JSObject* scopeChain, JSString* str, Value* vp);
bool GetDynamicNamePure(JSContext* cx, JSObject* scopeChain, JSString* str, Value* vp);
void PostWriteBarrier(JSRuntime* rt, js::gc::Cell* cell);
void PostGlobalWriteBarrier(JSRuntime* rt, GlobalObject* obj);

Просмотреть файл

@ -228,7 +228,7 @@ FetchName(JSContext* cx, HandleObject receiver, HandleObject holder, HandlePrope
}
inline bool
FetchNameNoGC(JSObject* pobj, PropertyResult prop, MutableHandleValue vp)
FetchNameNoGC(JSObject* pobj, PropertyResult prop, Value* vp)
{
if (!prop || !pobj->isNative()) {
return false;
@ -239,8 +239,8 @@ FetchNameNoGC(JSObject* pobj, PropertyResult prop, MutableHandleValue vp)
return false;
}
vp.set(pobj->as<NativeObject>().getSlot(shape->slot()));
return !IsUninitializedLexical(vp);
*vp = pobj->as<NativeObject>().getSlot(shape->slot());
return !IsUninitializedLexical(*vp);
}
template <js::GetNameMode mode>
@ -253,7 +253,7 @@ GetEnvironmentName(JSContext* cx, HandleObject envChain, HandlePropertyName name
JSObject* obj = nullptr;
JSObject* pobj = nullptr;
if (LookupNameNoGC(cx, name, envChain, &obj, &pobj, &prop)) {
if (FetchNameNoGC(pobj, prop, vp)) {
if (FetchNameNoGC(pobj, prop, vp.address())) {
return true;
}
}

Просмотреть файл

@ -30,6 +30,7 @@
#include "mozilla/Preferences.h"
#include "mozilla/Telemetry.h"
#include "mozilla/Services.h"
#include "mozilla/StaticPrefs.h"
#include "mozilla/dom/ScriptSettings.h"
#include "nsContentUtils.h"
@ -861,7 +862,7 @@ ReloadPrefsCallback(const char* pref, XPCJSContext* xpccx)
#endif // JS_GC_ZEAL
#ifdef FUZZING
bool fuzzingEnabled = Preferences::GetBool("fuzzing.enabled");
bool fuzzingEnabled = StaticPrefs::fuzzing_enabled();
#endif
JS::ContextOptionsRef(cx).setBaseline(useBaseline)

Просмотреть файл

@ -87,6 +87,20 @@ VARCACHE_PREF(
RelaxedAtomicBool, false
)
//---------------------------------------------------------------------------
// Fuzzing prefs. It's important that these can only be checked in fuzzing
// builds (when FUZZING is defined), otherwise you could enable the fuzzing
// stuff on your regular build which would be bad :)
//---------------------------------------------------------------------------
#ifdef FUZZING
VARCACHE_PREF(
"fuzzing.enabled",
fuzzing_enabled,
bool, false
)
#endif
//---------------------------------------------------------------------------
// Clipboard prefs
//---------------------------------------------------------------------------

Просмотреть файл

@ -5813,10 +5813,6 @@ pref("dom.payments.loglevel", "Warn");
pref("dom.payments.defaults.saveCreditCard", false);
pref("dom.payments.defaults.saveAddress", true);
#ifdef FUZZING
pref("fuzzing.enabled", false);
#endif
#ifdef MOZ_ASAN_REPORTER
pref("asanreporter.apiurl", "https://anf1.fuzzing.mozilla.org/crashproxy/submit/");
pref("asanreporter.clientid", "unknown");

Просмотреть файл

@ -251,8 +251,7 @@ nsSecureBrowserUIImpl::UpdateStateAndSecurityInfo(nsIChannel* channel,
// CheckForBlockedContent).
// When we receive a notification from the top-level nsIWebProgress, we extract
// any relevant security information and set our state accordingly. We then call
// OnSecurityChange on the docShell corresponding to the nsIWebProgress we were
// initialized with to notify any downstream listeners of the security state.
// OnSecurityChange to notify any downstream listeners of the security state.
NS_IMETHODIMP
nsSecureBrowserUIImpl::OnLocationChange(nsIWebProgress* aWebProgress,
nsIRequest* aRequest,
@ -294,22 +293,15 @@ nsSecureBrowserUIImpl::OnLocationChange(nsIWebProgress* aWebProgress,
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
}
nsCOMPtr<nsIDocShell> docShell = do_QueryReferent(mDocShell);
if (docShell) {
nsCOMPtr<nsISecurityEventSink> eventSink = do_QueryInterface(docShell);
if (eventSink) {
MOZ_LOG(gSecureBrowserUILog, LogLevel::Debug,
(" calling OnSecurityChange %p %x\n", aRequest, mState));
Unused << eventSink->OnSecurityChange(aRequest, mState);
} else {
MOZ_LOG(gSecureBrowserUILog, LogLevel::Debug,
(" docShell doesn't implement nsISecurityEventSink? %p\n",
docShell.get()));
nsCOMPtr<nsISecurityEventSink> eventSink;
NS_QueryNotificationCallbacks(channel, eventSink);
if (NS_WARN_IF(!eventSink)) {
return NS_ERROR_INVALID_ARG;
}
} else {
MOZ_LOG(gSecureBrowserUILog, LogLevel::Debug, (" docShell went away?\n"));
MOZ_LOG(gSecureBrowserUILog, LogLevel::Debug,
(" calling OnSecurityChange %p %x\n", aRequest, mState));
Unused << eventSink->OnSecurityChange(aRequest, mState);
}
return NS_OK;

Просмотреть файл

@ -290,7 +290,7 @@ linux64-base-toolchains/opt:
toolchains:
- linux64-clang-3.9
- linux64-gcc-6
- linux64-rust-1.28
- linux64-rust-1.29
- linux64-cbindgen
- linux64-sccache
- linux64-node
@ -323,7 +323,7 @@ linux64-base-toolchains/debug:
toolchains:
- linux64-clang-3.9
- linux64-gcc-6
- linux64-rust-1.28
- linux64-rust-1.29
- linux64-cbindgen
- linux64-sccache
- linux64-node

Просмотреть файл

@ -939,11 +939,19 @@ class LocalAPIImplementation extends SchemaAPIInterface {
}
callFunction(args) {
return this.pathObj[this.name](...args);
try {
return this.pathObj[this.name](...args);
} catch (e) {
throw this.context.normalizeError(e);
}
}
callFunctionNoReturn(args) {
this.pathObj[this.name](...args);
try {
this.pathObj[this.name](...args);
} catch (e) {
throw this.context.normalizeError(e);
}
}
callAsyncFunction(args, callback, requireUserInput) {

Просмотреть файл

@ -19,6 +19,11 @@
* True if the error matches the expected error.
*/
const errorMatches = (error, expectedError, context) => {
if (typeof error === "object" && error !== null &&
!context.principal.subsumes(Cu.getObjectPrincipal(error))) {
Cu.reportError("Error object belongs to the wrong scope.");
return false;
}
if (expectedError === null) {
return true;
}

Просмотреть файл

@ -51,7 +51,7 @@ add_task(async function test_support_toolbar_field_properties() {
let toolbox = document.querySelector("#navigator-toolbox");
let fields = [
toolbox.querySelector("#urlbar"),
document.getAnonymousElementByAttribute(searchbar, "anonid", "searchbar-textbox"),
searchbar.querySelector(".searchbar-textbox"),
].filter(field => {
let bounds = field.getBoundingClientRect();
return bounds.width > 0 && bounds.height > 0;

Просмотреть файл

@ -376,6 +376,7 @@ const GRANTED_WITHOUT_USER_PROMPT = [
"identity",
"idle",
"menus",
"menus.overrideContext",
"mozillaAddons",
"search",
"storage",

Просмотреть файл

@ -6,8 +6,8 @@
"use strict";
// This is loaded into all XUL windows. Wrap in a block to prevent
// leaking to window scope.
// This is loaded into chrome windows with the subscript loader. Wrap in
// a block to prevent accidentally leaking globals onto `window`.
{
ChromeUtils.import("resource://gre/modules/Services.jsm");

Просмотреть файл

@ -4,6 +4,8 @@
"use strict";
// This is loaded into chrome windows with the subscript loader. Wrap in
// a block to prevent accidentally leaking globals onto `window`.
{
/* globals XULFrameElement */

Просмотреть файл

@ -5,7 +5,8 @@
"use strict";
// Wrap to prevent accidentally leaking to window scope:
// This is loaded into chrome windows with the subscript loader. Wrap in
// a block to prevent accidentally leaking globals onto `window`.
{
ChromeUtils.import("resource://gre/modules/Services.jsm");

Просмотреть файл

@ -4,6 +4,8 @@
"use strict";
// This is loaded into chrome windows with the subscript loader. Wrap in
// a block to prevent accidentally leaking globals onto `window`.
{
class MozDeck extends MozXULElement {

Просмотреть файл

@ -4,8 +4,8 @@
"use strict";
// This is loaded into all XUL windows. Wrap in a block to prevent
// leaking to window scope.
// This is loaded into chrome windows with the subscript loader. Wrap in
// a block to prevent accidentally leaking globals onto `window`.
{
ChromeUtils.import("resource://gre/modules/Services.jsm");

Просмотреть файл

@ -4,6 +4,8 @@
"use strict";
// This is loaded into chrome windows with the subscript loader. Wrap in
// a block to prevent accidentally leaking globals onto `window`.
{
class MozTabbox extends MozXULElement {

Просмотреть файл

@ -4,6 +4,8 @@
"use strict";
// This is loaded into chrome windows with the subscript loader. Wrap in
// a block to prevent accidentally leaking globals onto `window`.
{
const cachedFragments = {

Просмотреть файл

@ -96,6 +96,7 @@
#include "linux/minidump_writer/minidump_writer.h"
#include "common/linux/eintr_wrapper.h"
#include "third_party/lss/linux_syscall_support.h"
#include "prenv.h"
#if defined(__ANDROID__)
#include "linux/sched.h"
@ -105,6 +106,8 @@
#define PR_SET_PTRACER 0x59616d61
#endif
#define SKIP_SIGILL(sig) if (g_skip_sigill_ && (sig == SIGILL)) continue;
namespace google_breakpad {
namespace {
@ -213,6 +216,7 @@ pthread_mutex_t g_handler_stack_mutex_ = PTHREAD_MUTEX_INITIALIZER;
ExceptionHandler::CrashContext g_crash_context_;
FirstChanceHandler g_first_chance_handler_ = nullptr;
bool g_skip_sigill_ = false;
} // namespace
// Runs before crashing: normal context.
@ -227,6 +231,8 @@ ExceptionHandler::ExceptionHandler(const MinidumpDescriptor& descriptor,
callback_context_(callback_context),
minidump_descriptor_(descriptor),
crash_handler_(NULL) {
g_skip_sigill_ = PR_GetEnv("MOZ_DISABLE_EXCEPTION_HANDLER_SIGILL") ? true : false;
if (server_fd >= 0)
crash_generation_client_.reset(CrashGenerationClient::TryCreate(server_fd));
@ -278,6 +284,7 @@ bool ExceptionHandler::InstallHandlersLocked() {
// Fail if unable to store all the old handlers.
for (int i = 0; i < kNumHandledSignals; ++i) {
SKIP_SIGILL(kExceptionSignals[i]);
if (sigaction(kExceptionSignals[i], NULL, &old_handlers[i]) == -1)
return false;
}
@ -287,13 +294,16 @@ bool ExceptionHandler::InstallHandlersLocked() {
sigemptyset(&sa.sa_mask);
// Mask all exception signals when we're handling one of them.
for (int i = 0; i < kNumHandledSignals; ++i)
for (int i = 0; i < kNumHandledSignals; ++i) {
SKIP_SIGILL(kExceptionSignals[i]);
sigaddset(&sa.sa_mask, kExceptionSignals[i]);
}
sa.sa_sigaction = SignalHandler;
sa.sa_flags = SA_ONSTACK | SA_SIGINFO;
for (int i = 0; i < kNumHandledSignals; ++i) {
SKIP_SIGILL(kExceptionSignals[i]);
if (sigaction(kExceptionSignals[i], &sa, NULL) == -1) {
// At this point it is impractical to back out changes, and so failure to
// install a signal is intentionally ignored.
@ -311,6 +321,7 @@ void ExceptionHandler::RestoreHandlersLocked() {
return;
for (int i = 0; i < kNumHandledSignals; ++i) {
SKIP_SIGILL(kExceptionSignals[i]);
if (sigaction(kExceptionSignals[i], &old_handlers[i], NULL) == -1) {
InstallDefaultHandler(kExceptionSignals[i]);
}

Просмотреть файл

@ -72,6 +72,7 @@ function getGlobalScriptsIncludes() {
let match = line.match(globalScriptsRegExp);
if (match) {
let sourceFile = (match[1] || match[2])
.replace("chrome://browser/content/search/", "browser/components/search/content/")
.replace("chrome://browser/content/", "browser/base/content/")
.replace("chrome://global/content/", "toolkit/content/");

Просмотреть файл

@ -113,14 +113,16 @@ CreateIFoo( void** result )
void
set_a_IFoo( nsCOMPtr<IFoo>* result )
{
nsCOMPtr<IFoo> foop( do_QueryInterface(new IFoo) );
// Various places in this file do a static_cast to nsISupports* in order to
// make the QI non-trivial, to avoid hitting a static assert.
nsCOMPtr<IFoo> foop(do_QueryInterface(static_cast<nsISupports*>(new IFoo)));
*result = foop;
}
nsCOMPtr<IFoo>
return_a_IFoo()
{
nsCOMPtr<IFoo> foop( do_QueryInterface(new IFoo) );
nsCOMPtr<IFoo> foop(do_QueryInterface(static_cast<nsISupports*>(new IFoo)));
return foop;
}
@ -242,7 +244,7 @@ TEST(COMPtr, Bloat_Smart)
ASSERT_TRUE(NS_SUCCEEDED(rv));
ASSERT_TRUE(barP);
nsCOMPtr<IFoo> fooP( do_QueryInterface(barP, &rv) );
nsCOMPtr<IFoo> fooP(do_QueryInterface(static_cast<nsISupports*>(barP), &rv));
ASSERT_TRUE(NS_SUCCEEDED(rv));
ASSERT_TRUE(fooP);
}
@ -254,12 +256,12 @@ TEST(COMPtr, AddRefAndRelease)
IBar::total_destructions_ = 0;
{
nsCOMPtr<IFoo> foop( do_QueryInterface(new IFoo) );
nsCOMPtr<IFoo> foop(do_QueryInterface(static_cast<nsISupports*>(new IFoo)));
ASSERT_EQ(foop->refcount_, (unsigned int)1);
ASSERT_EQ(IFoo::total_constructions_, 1);
ASSERT_EQ(IFoo::total_destructions_, 0);
foop = do_QueryInterface(new IFoo);
foop = do_QueryInterface(static_cast<nsISupports*>(new IFoo));
ASSERT_EQ(foop->refcount_, (unsigned int)1);
ASSERT_EQ(IFoo::total_constructions_, 2);
ASSERT_EQ(IFoo::total_destructions_, 1);
@ -288,7 +290,7 @@ TEST(COMPtr, AddRefAndRelease)
ASSERT_EQ(IFoo::total_destructions_, 2);
{
nsCOMPtr<IFoo> foop( do_QueryInterface(new IBar) );
nsCOMPtr<IFoo> foop(do_QueryInterface(static_cast<nsISupports*>(new IBar)));
mozilla::Unused << foop;
}
@ -301,8 +303,8 @@ void Comparison()
IFoo::total_destructions_ = 0;
{
nsCOMPtr<IFoo> foo1p( do_QueryInterface(new IFoo) );
nsCOMPtr<IFoo> foo2p( do_QueryInterface(new IFoo) );
nsCOMPtr<IFoo> foo1p(do_QueryInterface(static_cast<nsISupports*>(new IFoo)));
nsCOMPtr<IFoo> foo2p(do_QueryInterface(static_cast<nsISupports*>(new IFoo)));
ASSERT_EQ(IFoo::total_constructions_, 2);
@ -394,7 +396,7 @@ TEST(COMPtr, AssignmentHelpers)
ASSERT_EQ(IFoo::total_destructions_, 4);
{
nsCOMPtr<IFoo> fooP( do_QueryInterface(new IFoo) );
nsCOMPtr<IFoo> fooP(do_QueryInterface(static_cast<nsISupports*>(new IFoo)));
ASSERT_TRUE(fooP);
ASSERT_EQ(IFoo::total_constructions_, 5);
@ -419,7 +421,7 @@ TEST(COMPtr, QueryInterface)
{
nsCOMPtr<IFoo> fooP;
ASSERT_FALSE(fooP);
fooP = do_QueryInterface(new IFoo);
fooP = do_QueryInterface(static_cast<nsISupports*>(new IFoo));
ASSERT_TRUE(fooP);
ASSERT_EQ(IFoo::total_queries_, 1);
@ -432,12 +434,12 @@ TEST(COMPtr, QueryInterface)
}
{
nsCOMPtr<IBar> barP( do_QueryInterface(new IBar) );
nsCOMPtr<IBar> barP(do_QueryInterface(static_cast<nsISupports*>(new IBar)));
ASSERT_EQ(IBar::total_queries_, 1);
// Test that |QueryInterface| is called when assigning a smart-pointer of
// a different type.
nsCOMPtr<IFoo> fooP( do_QueryInterface(barP) );
nsCOMPtr<IFoo> fooP(do_QueryInterface(static_cast<nsISupports*>(barP)));
ASSERT_EQ(IBar::total_queries_, 2);
ASSERT_EQ(IFoo::total_queries_, 1);
ASSERT_TRUE(fooP);

Просмотреть файл

@ -18,15 +18,22 @@
using namespace mozilla;
// Cast the pointer to nsISupports* before doing the QI in order to avoid
// a static assert intended to prevent trivial QIs.
template<typename TargetInterface, typename SourcePtr>
bool TestQITo(SourcePtr& aPtr1)
{
nsCOMPtr<TargetInterface> aPtr2 = do_QueryInterface(static_cast<nsISupports*>(aPtr1.get()));
return (bool) aPtr2;
}
TEST(TestEventTargetQI, ThreadPool)
{
nsCOMPtr<nsIThreadPool> thing = new nsThreadPool();
nsCOMPtr<nsISerialEventTarget> serial = do_QueryInterface(thing);
EXPECT_FALSE(serial);
EXPECT_FALSE(TestQITo<nsISerialEventTarget>(thing));
nsCOMPtr<nsIEventTarget> target = do_QueryInterface(thing);
EXPECT_TRUE(target);
EXPECT_TRUE(TestQITo<nsIEventTarget>(thing));
thing->Shutdown();
}
@ -36,11 +43,9 @@ TEST(TestEventTargetQI, SharedThreadPool)
nsCOMPtr<nsIThreadPool> thing = SharedThreadPool::Get(NS_LITERAL_CSTRING("TestPool"), 1);
EXPECT_TRUE(thing);
nsCOMPtr<nsISerialEventTarget> serial = do_QueryInterface(thing);
EXPECT_FALSE(serial);
EXPECT_FALSE(TestQITo<nsISerialEventTarget>(thing));
nsCOMPtr<nsIEventTarget> target = do_QueryInterface(thing);
EXPECT_TRUE(target);
EXPECT_TRUE(TestQITo<nsIEventTarget>(thing));
}
TEST(TestEventTargetQI, Thread)
@ -48,11 +53,9 @@ TEST(TestEventTargetQI, Thread)
nsCOMPtr<nsIThread> thing = do_GetCurrentThread();
EXPECT_TRUE(thing);
nsCOMPtr<nsISerialEventTarget> serial = do_QueryInterface(thing);
EXPECT_TRUE(serial);
EXPECT_TRUE(TestQITo<nsISerialEventTarget>(thing));
nsCOMPtr<nsIEventTarget> target = do_QueryInterface(thing);
EXPECT_TRUE(target);
EXPECT_TRUE(TestQITo<nsIEventTarget>(thing));
}
TEST(TestEventTargetQI, ThrottledEventQueue)
@ -61,11 +64,9 @@ TEST(TestEventTargetQI, ThrottledEventQueue)
RefPtr<ThrottledEventQueue> thing = ThrottledEventQueue::Create(thread);
EXPECT_TRUE(thing);
nsCOMPtr<nsISerialEventTarget> serial = do_QueryInterface(thing);
EXPECT_TRUE(serial);
EXPECT_TRUE(TestQITo<nsISerialEventTarget>(thing));
nsCOMPtr<nsIEventTarget> target = do_QueryInterface(thing);
EXPECT_TRUE(target);
EXPECT_TRUE(TestQITo<nsIEventTarget>(thing));
}
TEST(TestEventTargetQI, LazyIdleThread)
@ -73,11 +74,9 @@ TEST(TestEventTargetQI, LazyIdleThread)
nsCOMPtr<nsIThread> thing = new LazyIdleThread(0, NS_LITERAL_CSTRING("TestThread"));
EXPECT_TRUE(thing);
nsCOMPtr<nsISerialEventTarget> serial = do_QueryInterface(thing);
EXPECT_TRUE(serial);
EXPECT_TRUE(TestQITo<nsISerialEventTarget>(thing));
nsCOMPtr<nsIEventTarget> target = do_QueryInterface(thing);
EXPECT_TRUE(target);
EXPECT_TRUE(TestQITo<nsIEventTarget>(thing));
thing->Shutdown();
}
@ -87,9 +86,7 @@ TEST(TestEventTargetQI, SchedulerGroup)
nsCOMPtr<nsIEventTarget> thing = SystemGroup::EventTargetFor(TaskCategory::Other);
EXPECT_TRUE(thing);
nsCOMPtr<nsISerialEventTarget> serial = do_QueryInterface(thing);
EXPECT_TRUE(serial);
EXPECT_TRUE(TestQITo<nsISerialEventTarget>(thing));
nsCOMPtr<nsIEventTarget> target = do_QueryInterface(thing);
EXPECT_TRUE(target);
EXPECT_TRUE(TestQITo<nsIEventTarget>(thing));
}