зеркало из https://github.com/mozilla/gecko-dev.git
Merge mozilla-central to inbound a=merge
This commit is contained in:
Коммит
35b751d9cd
|
@ -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));
|
||||
}
|
||||
|
|
Загрузка…
Ссылка в новой задаче