--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -2831,18 +2831,17 @@ var BrowserOnClick = {
Services.telemetry.getHistogramById("TLS_ERROR_REPORT_UI")
.add(reportStatus);
break;
case "Browser:OverrideWeakCrypto":
let weakCryptoOverride = Cc["@mozilla.org/security/weakcryptooverride;1"]
.getService(Ci.nsIWeakCryptoOverride);
weakCryptoOverride.addWeakCryptoOverride(
msg.data.location.hostname,
- PrivateBrowsingUtils.isBrowserPrivate(gBrowser.selectedBrowser),
- true /* temporary */);
+ PrivateBrowsingUtils.isBrowserPrivate(gBrowser.selectedBrowser));
break;
}
},
onSSLErrorReport: function(browser, elementId, documentURI, location, securityInfo) {
function showReportStatus(reportStatus) {
gBrowser.selectedBrowser
.messageManager
@@ -7135,16 +7134,17 @@ var gIdentityHandler = {
// Then, update the user interface with the available data.
this.refreshIdentityBlock();
// Handle a location change while the Control Center is focused
// by closing the popup (bug 1207542)
if (shouldHidePopup) {
this._identityPopup.hidePopup();
}
+ this.showWeakCryptoInfoBar();
// NOTE: We do NOT update the identity popup (the control center) when
// we receive a new security state on the existing page (i.e. from a
// subframe). If the user opened the popup and looks at the provided
// information we don't want to suddenly change the panel contents.
},
/**
@@ -7284,16 +7284,65 @@ var gIdentityHandler = {
// Set cropping and direction
this._identityIconLabel.crop = icon_country_label ? "end" : "center";
this._identityIconLabel.parentNode.style.direction = icon_labels_dir;
// Hide completely if the organization label is empty
this._identityIconLabel.parentNode.collapsed = icon_label ? false : true;
},
/**
+ * Show the weak crypto notification bar.
+ */
+ showWeakCryptoInfoBar() {
+ if (!this._uriHasHost || !this._isBroken || !this._sslStatus.cipherName ||
+ this._sslStatus.cipherName.indexOf("_RC4_") < 0) {
+ return;
+ }
+
+ let notificationBox = gBrowser.getNotificationBox();
+ let notification = notificationBox.getNotificationWithValue("weak-crypto");
+ if (notification) {
+ return;
+ }
+
+ let brandBundle = document.getElementById("bundle_brand");
+ let brandShortName = brandBundle.getString("brandShortName");
+ let message = gNavigatorBundle.getFormattedString("weakCryptoOverriding.message",
+ [brandShortName]);
+
+ let host = this._uri.host;
+ let port = 443;
+ try {
+ if (this._uri.port > 0) {
+ port = this._uri.port;
+ }
+ } catch (e) {}
+
+ let buttons = [{
+ label: gNavigatorBundle.getString("revokeOverride.label"),
+ accessKey: gNavigatorBundle.getString("revokeOverride.accesskey"),
+ callback: function (aNotification, aButton) {
+ try {
+ let weakCryptoOverride = Cc["@mozilla.org/security/weakcryptooverride;1"]
+ .getService(Ci.nsIWeakCryptoOverride);
+ weakCryptoOverride.removeWeakCryptoOverride(host, port,
+ PrivateBrowsingUtils.isBrowserPrivate(gBrowser.selectedBrowser));
+ BrowserReloadWithFlags(nsIWebNavigation.LOAD_FLAGS_BYPASS_CACHE);
+ } catch (e) {
+ Cu.reportError(e);
+ }
+ }
+ }];
+
+ const priority = notificationBox.PRIORITY_WARNING_MEDIUM;
+ notificationBox.appendNotification(message, "weak-crypto", null,
+ priority, buttons);
+ },
+
+ /**
* Set up the title and content messages for the identity message popup,
* based on the specified mode, and the details of the SSL cert, where
* applicable
*/
refreshIdentityPopup() {
// Update "Learn More" for Mixed Content Blocking and Insecure Login Forms.
let baseURL = Services.urlFormatter.formatURLPref("app.support.baseURL");
this._identityPopupMixedContentLearnMore
--- a/browser/base/content/test/general/browser_insecureLoginForms.js
+++ b/browser/base/content/test/general/browser_insecureLoginForms.js
@@ -1,14 +1,13 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
// Load directly from the browser-chrome support files of login tests.
-const testUrlPath =
- "://example.com/browser/toolkit/components/passwordmgr/test/browser/";
+const TEST_URL_PATH = "/browser/toolkit/components/passwordmgr/test/browser/";
/**
* Waits for the given number of occurrences of InsecureLoginFormsStateChange
* on the given browser element.
*/
function waitForInsecureLoginFormsStateChange(browser, count) {
return BrowserTestUtils.waitForEvent(browser, "InsecureLoginFormsStateChange",
false, () => --count == 0);
@@ -17,31 +16,36 @@ function waitForInsecureLoginFormsStateC
/**
* Checks the insecure login forms logic for the identity block.
*/
add_task(function* test_simple() {
yield new Promise(resolve => SpecialPowers.pushPrefEnv({
"set": [["security.insecure_password.ui.enabled", true]],
}, resolve));
- for (let scheme of ["http", "https"]) {
- let tab = gBrowser.addTab(scheme + testUrlPath + "form_basic.html");
+ for (let [origin, expectWarning] of [
+ ["http://example.com", true],
+ ["http://127.0.0.1", false],
+ ["https://example.com", false],
+ ]) {
+ let testUrlPath = origin + TEST_URL_PATH;
+ let tab = gBrowser.addTab(testUrlPath + "form_basic.html");
let browser = tab.linkedBrowser;
yield Promise.all([
BrowserTestUtils.switchTab(gBrowser, tab),
BrowserTestUtils.browserLoaded(browser),
// One event is triggered by pageshow and one by DOMFormHasPassword.
waitForInsecureLoginFormsStateChange(browser, 2),
]);
let { gIdentityHandler } = gBrowser.ownerGlobal;
gIdentityHandler._identityBox.click();
document.getElementById("identity-popup-security-expander").click();
- if (scheme == "http") {
+ if (expectWarning) {
let identityBoxImage = gBrowser.ownerGlobal
.getComputedStyle(document.getElementById("page-proxy-favicon"), "")
.getPropertyValue("list-style-image");
let securityViewBG = gBrowser.ownerGlobal
.getComputedStyle(document.getElementById("identity-popup-securityView"), "")
.getPropertyValue("background-image");
let securityContentBG = gBrowser.ownerGlobal
.getComputedStyle(document.getElementById("identity-popup-security-content"), "")
@@ -59,34 +63,35 @@ add_task(function* test_simple() {
element => !is_hidden(element)).length, 1,
"The 'Learn more' link should be visible once.");
}
// Messages should be visible when the scheme is HTTP, and invisible when
// the scheme is HTTPS.
is(Array.every(document.querySelectorAll("[when-loginforms=insecure]"),
element => !is_hidden(element)),
- scheme == "http",
- "The relevant messages should visible or hidden.");
+ expectWarning,
+ "The relevant messages should be visible or hidden.");
gIdentityHandler._identityPopup.hidden = true;
gBrowser.removeTab(tab);
}
});
/**
* Checks that the insecure login forms logic does not regress mixed content
* blocking messages when mixed active content is loaded.
*/
add_task(function* test_mixedcontent() {
yield new Promise(resolve => SpecialPowers.pushPrefEnv({
"set": [["security.mixed_content.block_active_content", false]],
}, resolve));
// Load the page with the subframe in a new tab.
+ let testUrlPath = "://example.com" + TEST_URL_PATH;
let tab = gBrowser.addTab("https" + testUrlPath + "insecure_test.html");
let browser = tab.linkedBrowser;
yield Promise.all([
BrowserTestUtils.switchTab(gBrowser, tab),
BrowserTestUtils.browserLoaded(browser),
// Two events are triggered by pageshow and one by DOMFormHasPassword.
waitForInsecureLoginFormsStateChange(browser, 3),
]);
--- a/browser/components/loop/content/css/panel.css
+++ b/browser/components/loop/content/css/panel.css
@@ -386,18 +386,17 @@ html[dir="rtl"] .room-entry-context-acti
/* Keep ".room-list > .room-entry > h2" in sync with these. */
.room-entry-context-item {
display: inline-block;
-moz-margin-end: 1rem;
vertical-align: middle;
}
-.room-entry-context-item > img {
- height: 16px;
+.room-entry-context-item > a > img {
width: 16px;
}
.button-close {
background-color: transparent;
background-image: url(../shared/img/icons-10x10.svg#close);
background-repeat: no-repeat;
background-size: 8px 8px;
--- a/browser/locales/en-US/chrome/browser/browser.properties
+++ b/browser/locales/en-US/chrome/browser/browser.properties
@@ -793,13 +793,18 @@ usercontext.work.label = Work
usercontext.shopping.label = Shopping
usercontext.banking.label = Banking
muteTab.label = Mute Tab
muteTab.accesskey = M
unmuteTab.label = Unmute Tab
unmuteTab.accesskey = M
+# LOCALIZATION NOTE (weakCryptoOverriding.message): %S is brandShortName
+weakCryptoOverriding.message = %S recommends that you don't enter your password, credit card and other personal information on this website.
+revokeOverride.label = Don't Trust This Website
+revokeOverride.accesskey = D
+
# LOCALIZATION NOTE (tabgroups.deprecationwarning.description):
# %S is brandShortName
tabgroups.deprecationwarning.description = Heads up! Tab Groups will be removed from %S soon.
tabgroups.deprecationwarning.learnMore.label = Learn More
tabgroups.deprecationwarning.learnMore.accesskey = L
--- a/devtools/client/debugger/test/mochitest/head.js
+++ b/devtools/client/debugger/test/mochitest/head.js
@@ -1,47 +1,35 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
-var { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
-
-var { Services } = Cu.import("resource://gre/modules/Services.jsm", {});
+// shared-head.js handles imports, constants, and utility functions
+Services.scriptloader.loadSubScript("chrome://mochitests/content/browser/devtools/client/framework/test/shared-head.js", this);
// Disable logging for faster test runs. Set this pref to true if you want to
// debug a test in your try runs. Both the debugger server and frontend will
// be affected by this pref.
var gEnableLogging = Services.prefs.getBoolPref("devtools.debugger.log");
Services.prefs.setBoolPref("devtools.debugger.log", false);
-var { Task } = Cu.import("resource://gre/modules/Task.jsm", {});
-var { Promise: promise } = Cu.import("resource://devtools/shared/deprecated-sync-thenables.js", {});
-var { gDevTools } = Cu.import("resource://devtools/client/framework/gDevTools.jsm", {});
-var { require } = Cu.import("resource://devtools/shared/Loader.jsm", {});
-var DevToolsUtils = require("devtools/shared/DevToolsUtils");
var { BrowserToolboxProcess } = Cu.import("resource://devtools/client/framework/ToolboxProcess.jsm", {});
var { DebuggerServer } = require("devtools/server/main");
var { DebuggerClient, ObjectClient } = require("devtools/shared/client/main");
var { AddonManager } = Cu.import("resource://gre/modules/AddonManager.jsm", {});
var EventEmitter = require("devtools/shared/event-emitter");
const { promiseInvoke } = require("devtools/shared/async-utils");
-var { TargetFactory } = require("devtools/client/framework/target");
var { Toolbox } = require("devtools/client/framework/toolbox")
+// Override promise with deprecated-sync-thenables
+promise = Cu.import("resource://devtools/shared/deprecated-sync-thenables.js", {}).Promise;
+
const EXAMPLE_URL = "http://example.com/browser/devtools/client/debugger/test/mochitest/";
const FRAME_SCRIPT_URL = getRootDirectory(gTestPath) + "code_frame-script.js";
-DevToolsUtils.testing = true;
-SimpleTest.registerCleanupFunction(() => {
- DevToolsUtils.testing = false;
-});
-
-// All tests are asynchronous.
-waitForExplicitFinish();
-
registerCleanupFunction(function* () {
info("finish() was called, cleaning up...");
Services.prefs.setBoolPref("devtools.debugger.log", gEnableLogging);
while (gBrowser && gBrowser.tabs && gBrowser.tabs.length > 1) {
info("Destroying toolbox.");
let target = TargetFactory.forTab(gBrowser.selectedTab);
yield gDevTools.closeToolbox(target);
@@ -79,17 +67,19 @@ function addWindow(aUrl) {
function getChromeWindow(aWindow) {
return aWindow
.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIWebNavigation)
.QueryInterface(Ci.nsIDocShellTreeItem).rootTreeItem
.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindow);
}
-function addTab(aUrl, aWindow) {
+// Override addTab/removeTab as defined by shared-head, since these have
+// an extra window parameter and add a frame script
+this.addTab = function addTab(aUrl, aWindow) {
info("Adding tab: " + aUrl);
let deferred = promise.defer();
let targetWindow = aWindow || window;
let targetBrowser = targetWindow.gBrowser;
targetWindow.focus();
let tab = targetBrowser.selectedTab = targetBrowser.addTab(aUrl);
@@ -102,17 +92,17 @@ function addTab(aUrl, aWindow) {
linkedBrowser.removeEventListener("load", onLoad, true);
info("Tab added and finished loading: " + aUrl);
deferred.resolve(tab);
}, true);
return deferred.promise;
}
-function removeTab(aTab, aWindow) {
+this.removeTab = function removeTab(aTab, aWindow) {
info("Removing tab.");
let deferred = promise.defer();
let targetWindow = aWindow || window;
let targetBrowser = targetWindow.gBrowser;
let tabContainer = targetBrowser.tabContainer;
tabContainer.addEventListener("TabClose", function onClose(aEvent) {
@@ -1210,35 +1200,8 @@ function getSplitConsole(toolbox, win) {
return new Promise(resolve => {
toolbox.getPanelWhenReady("webconsole").then(() => {
ok(toolbox.splitConsole, "Split console is shown.");
let jsterm = toolbox.getPanel("webconsole").hud.jsterm;
resolve(jsterm);
});
});
}
-
-// This can be removed once debugger uses shared-head.js (bug 1181838)
-function synthesizeKeyFromKeyTag(key) {
- is(key && key.tagName, "key", "Successfully retrieved the <key> node");
-
- let modifiersAttr = key.getAttribute("modifiers");
-
- let name = null;
-
- if (key.getAttribute("keycode"))
- name = key.getAttribute("keycode");
- else if (key.getAttribute("key"))
- name = key.getAttribute("key");
-
- isnot(name, null, "Successfully retrieved keycode/key");
-
- let modifiers = {
- shiftKey: !!modifiersAttr.match("shift"),
- ctrlKey: !!modifiersAttr.match("control"),
- altKey: !!modifiersAttr.match("alt"),
- metaKey: !!modifiersAttr.match("meta"),
- accelKey: !!modifiersAttr.match("accel")
- };
-
- info("Synthesizing key " + name + " " + JSON.stringify(modifiers));
- EventUtils.synthesizeKey(name, modifiers);
-}
--- a/devtools/client/framework/selection.js
+++ b/devtools/client/framework/selection.js
@@ -157,16 +157,22 @@ Selection.prototype = {
return this.node.ownerDocument;
}
return null;
},
setNodeFront: function(value, reason="unknown") {
this.reason = reason;
+ // If a singleTextChild text node is being set, then set it's parent instead.
+ let parentNode = value && value.parentNode();
+ if (value && parentNode && parentNode.singleTextChild === value) {
+ value = parentNode;
+ }
+
// We used to return here if the node had not changed but we now need to
// set the node even if it is already set otherwise it is not possible to
// e.g. highlight the same node twice.
let rawValue = null;
if (value && value.isLocal_toBeDeprecated()) {
rawValue = value.rawNode();
}
this.emit("before-new-node", rawValue, reason);
--- a/devtools/client/framework/test/shared-head.js
+++ b/devtools/client/framework/test/shared-head.js
@@ -15,17 +15,17 @@ function scopedCuImport(path) {
const {Services} = scopedCuImport("resource://gre/modules/Services.jsm");
const {gDevTools} = scopedCuImport("resource://devtools/client/framework/gDevTools.jsm");
const {console} = scopedCuImport("resource://gre/modules/Console.jsm");
const {ScratchpadManager} = scopedCuImport("resource://devtools/client/scratchpad/scratchpad-manager.jsm");
const {require} = scopedCuImport("resource://devtools/shared/Loader.jsm");
const {TargetFactory} = require("devtools/client/framework/target");
const DevToolsUtils = require("devtools/shared/DevToolsUtils");
-const promise = require("promise");
+let promise = require("promise");
const TEST_DIR = gTestPath.substr(0, gTestPath.lastIndexOf("/"));
const CHROME_URL_ROOT = TEST_DIR + "/";
const URL_ROOT = CHROME_URL_ROOT.replace("chrome://mochitests/content/", "http://example.com/");
// All test are asynchronous
waitForExplicitFinish();
--- a/devtools/client/inspector/inspector-panel.js
+++ b/devtools/client/inspector/inspector-panel.js
@@ -11,17 +11,17 @@ Cu.import("resource://gre/modules/Servic
var promise = require("promise");
var EventEmitter = require("devtools/shared/event-emitter");
var clipboard = require("sdk/clipboard");
var {HostType} = require("devtools/client/framework/toolbox").Toolbox;
loader.lazyGetter(this, "MarkupView", () => require("devtools/client/markupview/markup-view").MarkupView);
loader.lazyGetter(this, "HTMLBreadcrumbs", () => require("devtools/client/inspector/breadcrumbs").HTMLBreadcrumbs);
loader.lazyGetter(this, "ToolSidebar", () => require("devtools/client/framework/sidebar").ToolSidebar);
-loader.lazyGetter(this, "SelectorSearch", () => require("devtools/client/inspector/selector-search").SelectorSearch);
+loader.lazyGetter(this, "InspectorSearch", () => require("devtools/client/inspector/inspector-search").InspectorSearch);
loader.lazyGetter(this, "strings", () => {
return Services.strings.createBundle("chrome://devtools/locale/inspector.properties");
});
loader.lazyGetter(this, "toolboxStrings", () => {
return Services.strings.createBundle("chrome://devtools/locale/toolbox.properties");
});
loader.lazyGetter(this, "clipboardHelper", () => {
@@ -73,17 +73,30 @@ const LAYOUT_CHANGE_TIMER = 250;
function InspectorPanel(iframeWindow, toolbox) {
this._toolbox = toolbox;
this._target = toolbox._target;
this.panelDoc = iframeWindow.document;
this.panelWin = iframeWindow;
this.panelWin.inspector = this;
this.nodeMenuTriggerInfo = null;
+
this._onBeforeNavigate = this._onBeforeNavigate.bind(this);
+ this.onNewRoot = this.onNewRoot.bind(this);
+ this._setupNodeMenu = this._setupNodeMenu.bind(this);
+ this._resetNodeMenu = this._resetNodeMenu.bind(this);
+ this._updateSearchResultsLabel = this._updateSearchResultsLabel.bind(this);
+ this.onNewSelection = this.onNewSelection.bind(this);
+ this.onBeforeNewSelection = this.onBeforeNewSelection.bind(this);
+ this.onDetached = this.onDetached.bind(this);
+ this.onToolboxHostChanged = this.onToolboxHostChanged.bind(this);
+ this.scheduleLayoutChange = this.scheduleLayoutChange.bind(this);
+ this.onPaneToggleButtonClicked = this.onPaneToggleButtonClicked.bind(this);
+ this._onMarkupFrameLoad = this._onMarkupFrameLoad.bind(this);
+
this._target.on("will-navigate", this._onBeforeNavigate);
EventEmitter.decorate(this);
}
exports.InspectorPanel = InspectorPanel;
InspectorPanel.prototype = {
@@ -134,41 +147,33 @@ InspectorPanel.prototype = {
get canPasteInnerOrAdjacentHTML() {
return this._target.client.traits.pasteHTML;
},
_deferredOpen: function(defaultSelection) {
let deferred = promise.defer();
- this.onNewRoot = this.onNewRoot.bind(this);
this.walker.on("new-root", this.onNewRoot);
this.nodemenu = this.panelDoc.getElementById("inspector-node-popup");
this.lastNodemenuItem = this.nodemenu.lastChild;
- this._setupNodeMenu = this._setupNodeMenu.bind(this);
- this._resetNodeMenu = this._resetNodeMenu.bind(this);
this.nodemenu.addEventListener("popupshowing", this._setupNodeMenu, true);
this.nodemenu.addEventListener("popuphiding", this._resetNodeMenu, true);
- this.onNewSelection = this.onNewSelection.bind(this);
this.selection.on("new-node-front", this.onNewSelection);
- this.onBeforeNewSelection = this.onBeforeNewSelection.bind(this);
this.selection.on("before-new-node-front", this.onBeforeNewSelection);
- this.onDetached = this.onDetached.bind(this);
this.selection.on("detached-front", this.onDetached);
this.breadcrumbs = new HTMLBreadcrumbs(this);
- this.onToolboxHostChanged = this.onToolboxHostChanged.bind(this);
this._toolbox.on("host-changed", this.onToolboxHostChanged);
if (this.target.isLocalTab) {
this.browser = this.target.tab.linkedBrowser;
- this.scheduleLayoutChange = this.scheduleLayoutChange.bind(this);
this.browser.addEventListener("resize", this.scheduleLayoutChange, true);
// Show a warning when the debugger is paused.
// We show the warning only when the inspector
// is selected.
this.updateDebuggerPausedWarning = () => {
let notificationBox = this._toolbox.getNotificationBox();
let notification = notificationBox.getNotificationWithValue("inspector-script-paused");
@@ -304,23 +309,41 @@ InspectorPanel.prototype = {
markDirty: function() {
this.isDirty = true;
},
/**
* Hooks the searchbar to show result and auto completion suggestions.
*/
setupSearchBox: function() {
- // Initiate the selectors search object.
- if (this.searchSuggestions) {
- this.searchSuggestions.destroy();
- this.searchSuggestions = null;
+ this.searchBox = this.panelDoc.getElementById("inspector-searchbox");
+ this.searchResultsLabel = this.panelDoc.getElementById("inspector-searchlabel");
+
+ this.search = new InspectorSearch(this, this.searchBox);
+ this.search.on("search-cleared", this._updateSearchResultsLabel);
+ this.search.on("search-result", this._updateSearchResultsLabel);
+ },
+
+ get searchSuggestions() {
+ return this.search.autocompleter;
+ },
+
+ _updateSearchResultsLabel: function(event, result) {
+ let str = "";
+ if (event !== "search-cleared") {
+ if (result) {
+ str = strings.formatStringFromName(
+ "inspector.searchResultsCount2",
+ [result.resultsIndex + 1, result.resultsLength], 2);
+ } else {
+ str = strings.GetStringFromName("inspector.searchResultsNone");
+ }
}
- this.searchBox = this.panelDoc.getElementById("inspector-searchbox");
- this.searchSuggestions = new SelectorSearch(this, this.searchBox);
+
+ this.searchResultsLabel.textContent = str;
},
/**
* Build the sidebar.
*/
setupSidebar: function() {
let tabbox = this.panelDoc.querySelector("#inspector-sidebar");
this.sidebar = new ToolSidebar(tabbox, this, "inspector", {
@@ -364,17 +387,16 @@ InspectorPanel.prototype = {
this.setupSidebarToggle();
},
/**
* Add the expand/collapse behavior for the sidebar panel.
*/
setupSidebarToggle: function() {
this._paneToggleButton = this.panelDoc.getElementById("inspector-pane-toggle");
- this.onPaneToggleButtonClicked = this.onPaneToggleButtonClicked.bind(this);
this._paneToggleButton.addEventListener("mousedown",
this.onPaneToggleButtonClicked);
this.updatePaneToggleButton();
},
/**
* Reset the inspector on new root mutation.
*/
@@ -394,17 +416,16 @@ InspectorPanel.prototype = {
this.selection.setNodeFront(defaultNode, "navigateaway");
this._initMarkup();
this.once("markuploaded", () => {
if (!this.markup) {
return;
}
this.markup.expandNode(this.selection.nodeFront);
- this.setupSearchBox();
this.emit("new-root");
});
};
this._pendingSelection = onNodeSelected;
this._getDefaultNodeForSelection().then(onNodeSelected, console.error);
},
_selectionCssSelector: null,
@@ -585,32 +606,32 @@ InspectorPanel.prototype = {
this.sidebar = null;
this.nodemenu.removeEventListener("popupshowing", this._setupNodeMenu, true);
this.nodemenu.removeEventListener("popuphiding", this._resetNodeMenu, true);
this.breadcrumbs.destroy();
this._paneToggleButton.removeEventListener("mousedown",
this.onPaneToggleButtonClicked);
this._paneToggleButton = null;
- this.searchSuggestions.destroy();
- this.searchBox = null;
this.selection.off("new-node-front", this.onNewSelection);
this.selection.off("before-new-node", this.onBeforeNewSelection);
this.selection.off("before-new-node-front", this.onBeforeNewSelection);
this.selection.off("detached-front", this.onDetached);
let markupDestroyer = this._destroyMarkup();
this.panelWin.inspector = null;
this.target = null;
this.panelDoc = null;
this.panelWin = null;
this.breadcrumbs = null;
- this.searchSuggestions = null;
this.lastNodemenuItem = null;
this.nodemenu = null;
this._toolbox = null;
+ this.search.destroy();
+ this.search = null;
+ this.searchBox = null;
this._panelDestroyer = promise.all([
sidebarDestroyer,
markupDestroyer
]);
return this._panelDestroyer;
},
@@ -909,45 +930,42 @@ InspectorPanel.prototype = {
// create tool iframe
this._markupFrame = doc.createElement("iframe");
this._markupFrame.setAttribute("flex", "1");
this._markupFrame.setAttribute("tooltip", "aHTMLTooltip");
this._markupFrame.setAttribute("context", "inspector-node-popup");
// This is needed to enable tooltips inside the iframe document.
- this._boundMarkupFrameLoad = this._onMarkupFrameLoad.bind(this);
- this._markupFrame.addEventListener("load", this._boundMarkupFrameLoad, true);
+ this._markupFrame.addEventListener("load", this._onMarkupFrameLoad, true);
this._markupBox.setAttribute("collapsed", true);
this._markupBox.appendChild(this._markupFrame);
this._markupFrame.setAttribute("src", "chrome://devtools/content/markupview/markup-view.xhtml");
this._markupFrame.setAttribute("aria-label", strings.GetStringFromName("inspector.panelLabel.markupView"));
},
_onMarkupFrameLoad: function() {
- this._markupFrame.removeEventListener("load", this._boundMarkupFrameLoad, true);
- delete this._boundMarkupFrameLoad;
+ this._markupFrame.removeEventListener("load", this._onMarkupFrameLoad, true);
this._markupFrame.contentWindow.focus();
this._markupBox.removeAttribute("collapsed");
let controllerWindow = this._toolbox.doc.defaultView;
this.markup = new MarkupView(this, this._markupFrame, controllerWindow);
this.emit("markuploaded");
},
_destroyMarkup: function() {
let destroyPromise;
- if (this._boundMarkupFrameLoad) {
- this._markupFrame.removeEventListener("load", this._boundMarkupFrameLoad, true);
- this._boundMarkupFrameLoad = null;
+ if (this._markupFrame) {
+ this._markupFrame.removeEventListener("load", this._onMarkupFrameLoad, true);
}
if (this.markup) {
destroyPromise = this.markup.destroy();
this.markup = null;
} else {
destroyPromise = promise.resolve();
}
rename from devtools/client/inspector/selector-search.js
rename to devtools/client/inspector/inspector-search.js
--- a/devtools/client/inspector/selector-search.js
+++ b/devtools/client/inspector/inspector-search.js
@@ -1,54 +1,146 @@
/* 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";
-const { Cu } = require("chrome");
+const {Cu, Ci} = require("chrome");
const promise = require("promise");
loader.lazyGetter(this, "EventEmitter", () => require("devtools/shared/event-emitter"));
loader.lazyGetter(this, "AutocompletePopup", () => require("devtools/client/shared/autocomplete-popup").AutocompletePopup);
// Maximum number of selector suggestions shown in the panel.
const MAX_SUGGESTIONS = 15;
+
+/**
+ * Converts any input field into a document search box.
+ *
+ * @param {InspectorPanel} inspector The InspectorPanel whose `walker` attribute
+ * should be used for document traversal.
+ * @param {DOMNode} input The input element to which the panel will be attached
+ * and from where search input will be taken.
+ *
+ * Emits the following events:
+ * - search-cleared: when the search box is emptied
+ * - search-result: when a search is made and a result is selected
+ */
+function InspectorSearch(inspector, input) {
+ this.inspector = inspector;
+ this.searchBox = input;
+ this._lastSearched = null;
+
+ this._onKeyDown = this._onKeyDown.bind(this);
+ this._onCommand = this._onCommand.bind(this);
+ this.searchBox.addEventListener("keydown", this._onKeyDown, true);
+ this.searchBox.addEventListener("command", this._onCommand, true);
+
+ // For testing, we need to be able to wait for the most recent node request
+ // to finish. Tests can watch this promise for that.
+ this._lastQuery = promise.resolve(null);
+
+ this.autocompleter = new SelectorAutocompleter(inspector, input);
+ EventEmitter.decorate(this);
+}
+
+exports.InspectorSearch = InspectorSearch;
+
+InspectorSearch.prototype = {
+ get walker() {
+ return this.inspector.walker;
+ },
+
+ destroy: function() {
+ this.searchBox.removeEventListener("keydown", this._onKeyDown, true);
+ this.searchBox.removeEventListener("command", this._onCommand, true);
+ this.searchBox = null;
+ this.autocompleter.destroy();
+ },
+
+ _onSearch: function(reverse = false) {
+ this.doFullTextSearch(this.searchBox.value, reverse)
+ .catch(e => console.error(e));
+ },
+
+ doFullTextSearch: Task.async(function*(query, reverse) {
+ let lastSearched = this._lastSearched;
+ this._lastSearched = query;
+
+ if (query.length === 0) {
+ this.searchBox.classList.remove("devtools-no-search-result");
+ if (!lastSearched || lastSearched.length > 0) {
+ this.emit("search-cleared");
+ }
+ return;
+ }
+
+ let res = yield this.walker.search(query, { reverse });
+
+ // Value has changed since we started this request, we're done.
+ if (query != this.searchBox.value) {
+ return;
+ }
+
+ if (res) {
+ this.inspector.selection.setNodeFront(res.node, "inspectorsearch");
+ this.searchBox.classList.remove("devtools-no-search-result");
+
+ res.query = query;
+ this.emit("search-result", res);
+ } else {
+ this.searchBox.classList.add("devtools-no-search-result");
+ this.emit("search-result");
+ }
+ }),
+
+ _onCommand: function() {
+ if (this.searchBox.value.length === 0) {
+ this._onSearch();
+ }
+ },
+
+ _onKeyDown: function(event) {
+ if (this.searchBox.value.length === 0) {
+ this.searchBox.removeAttribute("filled");
+ } else {
+ this.searchBox.setAttribute("filled", true);
+ }
+ if (event.keyCode === event.DOM_VK_RETURN) {
+ this._onSearch();
+ } if (event.keyCode === Ci.nsIDOMKeyEvent.DOM_VK_G && event.metaKey) {
+ this._onSearch(event.shiftKey);
+ event.preventDefault();
+ }
+ }
+};
+
/**
* Converts any input box on a page to a CSS selector search and suggestion box.
*
* Emits 'processing-done' event when it is done processing the current
* keypress, search request or selection from the list, whether that led to a
* search or not.
*
* @constructor
- * @param InspectorPanel aInspector
+ * @param InspectorPanel inspector
* The InspectorPanel whose `walker` attribute should be used for
* document traversal.
- * @param nsiInputElement aInputNode
+ * @param nsiInputElement inputNode
* The input element to which the panel will be attached and from where
* search input will be taken.
*/
-function SelectorSearch(aInspector, aInputNode) {
- this.inspector = aInspector;
- this.searchBox = aInputNode;
+function SelectorAutocompleter(inspector, inputNode) {
+ this.inspector = inspector;
+ this.searchBox = inputNode;
this.panelDoc = this.searchBox.ownerDocument;
- // initialize variables.
- this._lastSearched = null;
- this._lastValidSearch = "";
- this._lastToLastValidSearch = null;
- this._searchResults = null;
- this._searchSuggestions = {};
- this._searchIndex = 0;
-
- // bind!
- this._showPopup = this._showPopup.bind(this);
- this._onHTMLSearch = this._onHTMLSearch.bind(this);
+ this.showSuggestions = this.showSuggestions.bind(this);
this._onSearchKeypress = this._onSearchKeypress.bind(this);
this._onListBoxKeypress = this._onListBoxKeypress.bind(this);
this._onMarkupMutation = this._onMarkupMutation.bind(this);
// Options for the AutocompletePopup.
let options = {
panelId: "inspector-searchbox-panel",
listBoxId: "searchbox-panel-listbox",
@@ -56,30 +148,29 @@ function SelectorSearch(aInspector, aInp
position: "before_start",
direction: "ltr",
theme: "auto",
onClick: this._onListBoxKeypress,
onKeypress: this._onListBoxKeypress
};
this.searchPopup = new AutocompletePopup(this.panelDoc, options);
- // event listeners.
- this.searchBox.addEventListener("command", this._onHTMLSearch, true);
+ this.searchBox.addEventListener("input", this.showSuggestions, true);
this.searchBox.addEventListener("keypress", this._onSearchKeypress, true);
this.inspector.on("markupmutation", this._onMarkupMutation);
// For testing, we need to be able to wait for the most recent node request
// to finish. Tests can watch this promise for that.
this._lastQuery = promise.resolve(null);
EventEmitter.decorate(this);
}
-exports.SelectorSearch = SelectorSearch;
+exports.SelectorAutocompleter = SelectorAutocompleter;
-SelectorSearch.prototype = {
+SelectorAutocompleter.prototype = {
get walker() {
return this.inspector.walker;
},
// The possible states of the query.
States: {
CLASS: "class",
ID: "id",
@@ -164,268 +255,123 @@ SelectorSearch.prototype = {
}
return this._state;
},
/**
* Removes event listeners and cleans up references.
*/
destroy: function() {
- // event listeners.
- this.searchBox.removeEventListener("command", this._onHTMLSearch, true);
+ this.searchBox.removeEventListener("input", this.showSuggestions, true);
this.searchBox.removeEventListener("keypress", this._onSearchKeypress, true);
this.inspector.off("markupmutation", this._onMarkupMutation);
this.searchPopup.destroy();
this.searchPopup = null;
this.searchBox = null;
this.panelDoc = null;
- this._searchResults = null;
- this._searchSuggestions = null;
- },
-
- _selectResult: function(index) {
- return this._searchResults.item(index).then(node => {
- this.inspector.selection.setNodeFront(node, "selectorsearch");
- });
- },
-
- _queryNodes: Task.async(function*(query) {
- if (typeof this.hasMultiFrameSearch === "undefined") {
- let target = this.inspector.toolbox.target;
- this.hasMultiFrameSearch = yield target.actorHasMethod("domwalker",
- "multiFrameQuerySelectorAll");
- }
-
- if (this.hasMultiFrameSearch) {
- return yield this.walker.multiFrameQuerySelectorAll(query);
- } else {
- return yield this.walker.querySelectorAll(this.walker.rootNode, query);
- }
- }),
-
- /**
- * The command callback for the input box. This function is automatically
- * invoked as the user is typing if the input box type is search.
- */
- _onHTMLSearch: function() {
- let query = this.searchBox.value;
- if (query == this._lastSearched) {
- this.emit("processing-done");
- return;
- }
- this._lastSearched = query;
- this._searchResults = [];
- this._searchIndex = 0;
-
- if (query.length == 0) {
- this._lastValidSearch = "";
- this.searchBox.removeAttribute("filled");
- this.searchBox.classList.remove("devtools-no-search-result");
- if (this.searchPopup.isOpen) {
- this.searchPopup.hidePopup();
- }
- this.emit("processing-done");
- return;
- }
-
- this.searchBox.setAttribute("filled", true);
- let queryList = null;
-
- this._lastQuery = this._queryNodes(query).then(list => {
- return list;
- }, (err) => {
- // Failures are ok here, just use a null item list;
- return null;
- }).then(queryList => {
- // Value has changed since we started this request, we're done.
- if (query != this.searchBox.value) {
- if (queryList) {
- queryList.release();
- }
- return promise.reject(null);
- }
-
- this._searchResults = queryList || [];
- if (this._searchResults && this._searchResults.length > 0) {
- this._lastValidSearch = query;
- // Even though the selector matched atleast one node, there is still
- // possibility of suggestions.
- if (query.match(/[\s>+]$/)) {
- // If the query has a space or '>' at the end, create a selector to match
- // the children of the selector inside the search box by adding a '*'.
- this._lastValidSearch += "*";
- }
- else if (query.match(/[\s>+][\.#a-zA-Z][\.#>\s+]*$/)) {
- // If the query is a partial descendant selector which does not matches
- // any node, remove the last incomplete part and add a '*' to match
- // everything. For ex, convert 'foo > b' to 'foo > *' .
- let lastPart = query.match(/[\s>+][\.#a-zA-Z][^>\s+]*$/)[0];
- this._lastValidSearch = query.slice(0, -1 * lastPart.length + 1) + "*";
- }
-
- if (!query.slice(-1).match(/[\.#\s>+]/)) {
- // Hide the popup if we have some matching nodes and the query is not
- // ending with [.# >] which means that the selector is not at the
- // beginning of a new class, tag or id.
- if (this.searchPopup.isOpen) {
- this.searchPopup.hidePopup();
- }
- this.searchBox.classList.remove("devtools-no-search-result");
-
- return this._selectResult(0);
- }
- return this._selectResult(0).then(() => {
- this.searchBox.classList.remove("devtools-no-search-result");
- }).then(() => this.showSuggestions());
- }
- if (query.match(/[\s>+]$/)) {
- this._lastValidSearch = query + "*";
- }
- else if (query.match(/[\s>+][\.#a-zA-Z][\.#>\s+]*$/)) {
- let lastPart = query.match(/[\s+>][\.#a-zA-Z][^>\s+]*$/)[0];
- this._lastValidSearch = query.slice(0, -1 * lastPart.length + 1) + "*";
- }
- this.searchBox.classList.add("devtools-no-search-result");
- return this.showSuggestions();
- }).then(() => this.emit("processing-done"), Cu.reportError);
},
/**
* Handles keypresses inside the input box.
*/
- _onSearchKeypress: function(aEvent) {
+ _onSearchKeypress: function(event) {
let query = this.searchBox.value;
- switch(aEvent.keyCode) {
- case aEvent.DOM_VK_RETURN:
- if (query == this._lastSearched && this._searchResults) {
- this._searchIndex = (this._searchIndex + 1) % this._searchResults.length;
- }
- else {
- this._onHTMLSearch();
- return;
+ switch(event.keyCode) {
+ case event.DOM_VK_RETURN:
+ case event.DOM_VK_TAB:
+ if (this.searchPopup.isOpen &&
+ this.searchPopup.getItemAtIndex(this.searchPopup.itemCount - 1)
+ .preLabel == query) {
+ this.searchPopup.selectedIndex = this.searchPopup.itemCount - 1;
+ this.searchBox.value = this.searchPopup.selectedItem.label;
+ this.hidePopup();
}
break;
- case aEvent.DOM_VK_UP:
+ case event.DOM_VK_UP:
if (this.searchPopup.isOpen && this.searchPopup.itemCount > 0) {
this.searchPopup.focus();
if (this.searchPopup.selectedIndex == this.searchPopup.itemCount - 1) {
this.searchPopup.selectedIndex =
Math.max(0, this.searchPopup.itemCount - 2);
}
else {
this.searchPopup.selectedIndex = this.searchPopup.itemCount - 1;
}
this.searchBox.value = this.searchPopup.selectedItem.label;
}
- else if (--this._searchIndex < 0) {
- this._searchIndex = this._searchResults.length - 1;
- }
break;
- case aEvent.DOM_VK_DOWN:
+ case event.DOM_VK_DOWN:
if (this.searchPopup.isOpen && this.searchPopup.itemCount > 0) {
this.searchPopup.focus();
this.searchPopup.selectedIndex = 0;
this.searchBox.value = this.searchPopup.selectedItem.label;
}
- this._searchIndex = (this._searchIndex + 1) % this._searchResults.length;
break;
- case aEvent.DOM_VK_TAB:
- if (this.searchPopup.isOpen &&
- this.searchPopup.getItemAtIndex(this.searchPopup.itemCount - 1)
- .preLabel == query) {
- this.searchPopup.selectedIndex = this.searchPopup.itemCount - 1;
- this.searchBox.value = this.searchPopup.selectedItem.label;
- this._onHTMLSearch();
- }
- break;
-
- case aEvent.DOM_VK_BACK_SPACE:
- case aEvent.DOM_VK_DELETE:
- // need to throw away the lastValidSearch.
- this._lastToLastValidSearch = null;
- // This gets the most complete selector from the query. For ex.
- // '.foo.ba' returns '.foo' , '#foo > .bar.baz' returns '#foo > .bar'
- // '.foo +bar' returns '.foo +' and likewise.
- this._lastValidSearch = (query.match(/(.*)[\.#][^\.# ]{0,}$/) ||
- query.match(/(.*[\s>+])[a-zA-Z][^\.# ]{0,}$/) ||
- ["",""])[1];
- return;
-
default:
return;
}
- aEvent.preventDefault();
- aEvent.stopPropagation();
- if (this._searchResults && this._searchResults.length > 0) {
- this._lastQuery = this._selectResult(this._searchIndex).then(() => this.emit("processing-done"));
- }
- else {
- this.emit("processing-done");
- }
+ event.preventDefault();
+ event.stopPropagation();
+ this.emit("processing-done");
},
/**
* Handles keypress and mouse click on the suggestions richlistbox.
*/
- _onListBoxKeypress: function(aEvent) {
- switch(aEvent.keyCode || aEvent.button) {
- case aEvent.DOM_VK_RETURN:
- case aEvent.DOM_VK_TAB:
+ _onListBoxKeypress: function(event) {
+ switch(event.keyCode || event.button) {
+ case event.DOM_VK_RETURN:
+ case event.DOM_VK_TAB:
case 0: // left mouse button
- aEvent.stopPropagation();
- aEvent.preventDefault();
+ event.stopPropagation();
+ event.preventDefault();
this.searchBox.value = this.searchPopup.selectedItem.label;
this.searchBox.focus();
- this._onHTMLSearch();
+ this.hidePopup();
break;
- case aEvent.DOM_VK_UP:
+ case event.DOM_VK_UP:
if (this.searchPopup.selectedIndex == 0) {
this.searchPopup.selectedIndex = -1;
- aEvent.stopPropagation();
- aEvent.preventDefault();
+ event.stopPropagation();
+ event.preventDefault();
this.searchBox.focus();
}
else {
let index = this.searchPopup.selectedIndex;
this.searchBox.value = this.searchPopup.getItemAtIndex(index - 1).label;
}
break;
- case aEvent.DOM_VK_DOWN:
+ case event.DOM_VK_DOWN:
if (this.searchPopup.selectedIndex == this.searchPopup.itemCount - 1) {
this.searchPopup.selectedIndex = -1;
- aEvent.stopPropagation();
- aEvent.preventDefault();
+ event.stopPropagation();
+ event.preventDefault();
this.searchBox.focus();
}
else {
let index = this.searchPopup.selectedIndex;
this.searchBox.value = this.searchPopup.getItemAtIndex(index + 1).label;
}
break;
- case aEvent.DOM_VK_BACK_SPACE:
- aEvent.stopPropagation();
- aEvent.preventDefault();
+ case event.DOM_VK_BACK_SPACE:
+ event.stopPropagation();
+ event.preventDefault();
this.searchBox.focus();
if (this.searchBox.selectionStart > 0) {
this.searchBox.value =
this.searchBox.value.substring(0, this.searchBox.selectionStart - 1);
}
- this._lastToLastValidSearch = null;
- let query = this.searchBox.value;
- this._lastValidSearch = (query.match(/(.*)[\.#][^\.# ]{0,}$/) ||
- query.match(/(.*[\s>+])[a-zA-Z][^\.# ]{0,}$/) ||
- ["",""])[1];
- this._onHTMLSearch();
+ this.hidePopup();
break;
}
this.emit("processing-done");
},
/**
* Reset previous search results on markup-mutations to make sure we search
* again after nodes have been added/removed/changed.
@@ -433,22 +379,22 @@ SelectorSearch.prototype = {
_onMarkupMutation: function() {
this._searchResults = null;
this._lastSearched = null;
},
/**
* Populates the suggestions list and show the suggestion popup.
*/
- _showPopup: function(aList, aFirstPart, aState) {
+ _showPopup: function(list, firstPart, aState) {
let total = 0;
let query = this.searchBox.value;
let items = [];
- for (let [value, count, state] of aList) {
+ for (let [value, /*count*/, state] of list) {
// for cases like 'div ' or 'div >' or 'div+'
if (query.match(/[\s>+]$/)) {
value = query + value;
}
// for cases like 'div #a' or 'div .a' or 'div > d' and likewise
else if (query.match(/[\s>+][\.#a-zA-Z][^\s>+\.#]*$/)) {
let lastPart = query.match(/[\s>+][\.#a-zA-Z][^>\s+\.#]*$/)[0];
value = query.slice(0, -1 * lastPart.length + 1) + value;
@@ -456,18 +402,17 @@ SelectorSearch.prototype = {
// for cases like 'div.class' or '#foo.bar' and likewise
else if (query.match(/[a-zA-Z][#\.][^#\.\s+>]*$/)) {
let lastPart = query.match(/[a-zA-Z][#\.][^#\.\s>+]*$/)[0];
value = query.slice(0, -1 * lastPart.length + 1) + value;
}
let item = {
preLabel: query,
- label: value,
- count: count
+ label: value
};
// In case of tagNames, change the case to small
if (value.match(/.*[\.#][^\.#]{0,}$/) == null) {
item.label = value.toLowerCase();
}
// In case the query's state is tag and the item's state is id or class
@@ -484,63 +429,81 @@ SelectorSearch.prototype = {
break;
}
}
if (total > 0) {
this.searchPopup.setItems(items);
this.searchPopup.openPopup(this.searchBox);
}
else {
+ this.hidePopup();
+ }
+ },
+
+
+ /**
+ * Hide the suggestion popup if necessary.
+ */
+ hidePopup: function() {
+ if (this.searchPopup.isOpen) {
this.searchPopup.hidePopup();
}
},
/**
* Suggests classes,ids and tags based on the user input as user types in the
* searchbox.
*/
showSuggestions: function() {
let query = this.searchBox.value;
let state = this.state;
let firstPart = "";
- if (state == this.States.TAG) {
+ if (state === this.States.TAG) {
// gets the tag that is being completed. For ex. 'div.foo > s' returns 's',
// 'di' returns 'di' and likewise.
firstPart = (query.match(/[\s>+]?([a-zA-Z]*)$/) || ["", query])[1];
query = query.slice(0, query.length - firstPart.length);
}
- else if (state == this.States.CLASS) {
+ else if (state === this.States.CLASS) {
// gets the class that is being completed. For ex. '.foo.b' returns 'b'
firstPart = query.match(/\.([^\.]*)$/)[1];
query = query.slice(0, query.length - firstPart.length - 1);
}
- else if (state == this.States.ID) {
+ else if (state === this.States.ID) {
// gets the id that is being completed. For ex. '.foo#b' returns 'b'
firstPart = query.match(/#([^#]*)$/)[1];
query = query.slice(0, query.length - firstPart.length - 1);
}
// TODO: implement some caching so that over the wire request is not made
// everytime.
if (/[\s+>~]$/.test(query)) {
query += "*";
}
- this._currentSuggesting = query;
- return this.walker.getSuggestionsForQuery(query, firstPart, state).then(result => {
- if (this._currentSuggesting != result.query) {
+ this._lastQuery = this.walker.getSuggestionsForQuery(query, firstPart, state).then(result => {
+ this.emit("processing-done");
+ if (result.query !== query) {
// This means that this response is for a previous request and the user
// as since typed something extra leading to a new request.
return;
}
- this._lastToLastValidSearch = this._lastValidSearch;
- if (state == this.States.CLASS) {
+ if (state === this.States.CLASS) {
firstPart = "." + firstPart;
- }
- else if (state == this.States.ID) {
+ } else if (state === this.States.ID) {
firstPart = "#" + firstPart;
}
+ // If there is a single tag match and it's what the user typed, then
+ // don't need to show a popup.
+ if (result.suggestions.length === 1 &&
+ result.suggestions[0][0] === firstPart) {
+ result.suggestions = [];
+ }
+
+
this._showPopup(result.suggestions, firstPart, state);
});
+
+ return this._lastQuery;
}
};
--- a/devtools/client/inspector/inspector.xul
+++ b/devtools/client/inspector/inspector.xul
@@ -149,21 +149,22 @@
<vbox flex="1" class="devtools-main-content">
<toolbar id="inspector-toolbar"
class="devtools-toolbar"
nowindowdrag="true">
<arrowscrollbox id="inspector-breadcrumbs"
class="breadcrumbs-widget-container"
flex="1" orient="horizontal"
clicktoscroll="true"/>
+ <box id="inspector-searchlabel" />
<textbox id="inspector-searchbox"
type="search"
timeout="50"
class="devtools-searchinput"
- placeholder="&inspectorSearchHTML.label2;"/>
+ placeholder="&inspectorSearchHTML.label3;"/>
<toolbarbutton id="inspector-pane-toggle"
class="devtools-toolbarbutton"
tabindex="0" />
</toolbar>
<vbox flex="1" id="markup-box">
</vbox>
</vbox>
<splitter class="devtools-side-splitter"/>
--- a/devtools/client/inspector/moz.build
+++ b/devtools/client/inspector/moz.build
@@ -1,12 +1,12 @@
# 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/.
DevToolsModules(
'breadcrumbs.js',
'inspector-commands.js',
'inspector-panel.js',
- 'selector-search.js'
+ 'inspector-search.js'
)
BROWSER_CHROME_MANIFESTS += ['test/browser.ini']
--- a/devtools/client/inspector/test/browser_inspector_search-01.js
+++ b/devtools/client/inspector/test/browser_inspector_search-01.js
@@ -3,76 +3,85 @@
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Test that searching for nodes in the search field actually selects those
// nodes.
const TEST_URL = TEST_URL_ROOT + "doc_inspector_search.html";
-// Indexes of the keys in the KEY_STATES array that should listen to "keypress"
-// event instead of "command". These are keys that don't change the content of
-// the search field and thus don't trigger command event.
-const LISTEN_KEYPRESS = [3,4,8,18,19,20,21,22];
-
// The various states of the inspector: [key, id, isValid]
// [
// what key to press,
// what id should be selected after the keypress,
// is the searched text valid selector
// ]
const KEY_STATES = [
- ["d", "b1", false],
- ["i", "b1", false],
- ["v", "d1", true],
- ["VK_DOWN", "d2", true], // keypress
- ["VK_RETURN", "d1", true], //keypress
- [".", "d1", false],
- ["c", "d1", false],
- ["1", "d2", true],
- ["VK_DOWN", "d2", true], // keypress
- ["VK_BACK_SPACE", "d2", false],
- ["VK_BACK_SPACE", "d2", false],
- ["VK_BACK_SPACE", "d1", true],
- ["VK_BACK_SPACE", "d1", false],
- ["VK_BACK_SPACE", "d1", false],
- ["VK_BACK_SPACE", "d1", true],
- [".", "d1", false],
- ["c", "d1", false],
- ["1", "d2", true],
- ["VK_DOWN", "s2", true], // keypress
- ["VK_DOWN", "p1", true], // kepress
- ["VK_UP", "s2", true], // keypress
- ["VK_UP", "d2", true], // keypress
- ["VK_UP", "p1", true],
- ["VK_BACK_SPACE", "p1", false],
- ["2", "p3", true],
- ["VK_BACK_SPACE", "p3", false],
- ["VK_BACK_SPACE", "p3", false],
- ["VK_BACK_SPACE", "p3", true],
- ["r", "p3", false],
+ ["#", "b1", true], // #
+ ["d", "b1", true], // #d
+ ["1", "b1", true], // #d1
+ ["VK_RETURN", "d1", true], // #d1
+ ["VK_BACK_SPACE", "d1", true], // #d
+ ["2", "d1", true], // #d2
+ ["VK_RETURN", "d2", true], // #d2
+ ["2", "d2", true], // #d22
+ ["VK_RETURN", "d2", false], // #d22
+ ["VK_BACK_SPACE", "d2", false], // #d2
+ ["VK_RETURN", "d2", true], // #d2
+ ["VK_BACK_SPACE", "d2", true], // #d
+ ["1", "d2", true], // #d1
+ ["VK_RETURN", "d1", true], // #d1
+ ["VK_BACK_SPACE", "d1", true], // #d
+ ["VK_BACK_SPACE", "d1", true], // #
+ ["VK_BACK_SPACE", "d1", true], //
+ ["d", "d1", true], // d
+ ["i", "d1", true], // di
+ ["v", "d1", true], // div
+ [".", "d1", true], // div.
+ ["c", "d1", true], // div.c
+ ["VK_UP", "d1", true], // div.c1
+ ["VK_TAB", "d1", true], // div.c1
+ ["VK_RETURN", "d2", true], // div.c1
+ ["VK_BACK_SPACE", "d2", true], // div.c
+ ["VK_BACK_SPACE", "d2", true], // div.
+ ["VK_BACK_SPACE", "d2", true], // div
+ ["VK_BACK_SPACE", "d2", true], // di
+ ["VK_BACK_SPACE", "d2", true], // d
+ ["VK_BACK_SPACE", "d2", true], //
+ [".", "d2", true], // .
+ ["c", "d2", true], // .c
+ ["1", "d2", true], // .c1
+ ["VK_RETURN", "d2", true], // .c1
+ ["VK_RETURN", "s2", true], // .c1
+ ["VK_RETURN", "p1", true], // .c1
+ ["P", "p1", true], // .c1P
+ ["VK_RETURN", "p1", false], // .c1P
];
add_task(function* () {
let { inspector } = yield openInspectorForURL(TEST_URL);
let { searchBox } = inspector;
yield selectNode("#b1", inspector);
yield focusSearchBoxUsingShortcut(inspector.panelWin);
let index = 0;
for (let [ key, id, isValid ] of KEY_STATES) {
- let event = (LISTEN_KEYPRESS.indexOf(index) !== -1) ? "keypress" : "command";
- let eventHandled = once(searchBox, event, true);
+ info(index + ": Pressing key " + key + " to get id " + id + ".");
+ let done = inspector.searchSuggestions.once("processing-done");
+ EventUtils.synthesizeKey(key, {}, inspector.panelWin);
+ yield done;
+ info("Got processing-done event");
- info(index + ": Pressing key " + key + " to get id " + id);
- EventUtils.synthesizeKey(key, {}, inspector.panelWin);
- yield eventHandled;
+ if (key === "VK_RETURN") {
+ info ("Waiting for " + (isValid ? "NO " : "") + "results");
+ yield inspector.search.once("search-result");
+ }
- info("Got " + event + " event. Waiting for search query to complete");
+ info("Waiting for search query to complete");
yield inspector.searchSuggestions._lastQuery;
info(inspector.selection.nodeFront.id + " is selected with text " +
searchBox.value);
let nodeFront = yield getNodeFront("#" + id, inspector);
is(inspector.selection.nodeFront, nodeFront,
"Correct node is selected for state " + index);
--- a/devtools/client/inspector/test/browser_inspector_search-02.js
+++ b/devtools/client/inspector/test/browser_inspector_search-02.js
@@ -11,84 +11,89 @@ const TEST_URL = TEST_URL_ROOT + "doc_in
// An array of (key, suggestions) pairs where key is a key to press and
// suggestions is an array of suggestions that should be shown in the popup.
// Suggestion is an object with label of the entry and optional count
// (defaults to 1)
const TEST_DATA = [
{
key: "d",
suggestions: [
- {label: "div", count: 4},
- {label: "#d1", count: 1},
- {label: "#d2", count: 1}
+ {label: "div"},
+ {label: "#d1"},
+ {label: "#d2"}
]
},
{
key: "i",
- suggestions: [{label: "div", count: 4}]
+ suggestions: [{label: "div"}]
},
{
key: "v",
suggestions: []
},
{
key: " ",
suggestions: [
- {label: "div div", count: 2},
- {label: "div span", count: 2}
+ {label: "div div"},
+ {label: "div span"}
]
},
{
key: ">",
suggestions: [
- {label: "div >div", count: 2},
- {label: "div >span", count: 2}
+ {label: "div >div"},
+ {label: "div >span"}
]
},
{
key: "VK_BACK_SPACE",
suggestions: [
- {label: "div div", count: 2 },
- {label: "div span", count: 2}
+ {label: "div div"},
+ {label: "div span"}
]
},
{
key: "+",
suggestions: [{label: "div +span"}]
},
{
key: "VK_BACK_SPACE",
suggestions: [
- {label: "div div", count: 2 },
- {label: "div span", count: 2}
+ {label: "div div"},
+ {label: "div span"}
]
},
{
key: "VK_BACK_SPACE",
suggestions: []
},
{
key: "VK_BACK_SPACE",
- suggestions: [{label: "div", count: 4}]
+ suggestions: [{label: "div"}]
},
{
key: "VK_BACK_SPACE",
suggestions: [
- {label: "div", count: 4},
- {label: "#d1", count: 1},
- {label: "#d2", count: 1}
+ {label: "div"},
+ {label: "#d1"},
+ {label: "#d2"}
]
},
{
key: "VK_BACK_SPACE",
suggestions: []
},
{
key: "p",
- suggestions: []
+ suggestions: [
+ {label: "p"},
+ {label: "#p1"},
+ {label: "#p2"},
+ {label: "#p3"},
+ ]
},
{
key: " ",
suggestions: [{label: "p strong"}]
},
{
key: "+",
suggestions: [
@@ -148,19 +153,17 @@ add_task(function* () {
let actualSuggestions = popup.getItems().reverse();
is(popup.isOpen ? actualSuggestions.length: 0, suggestions.length,
"There are expected number of suggestions.");
for (let i = 0; i < suggestions.length; i++) {
is(actualSuggestions[i].label, suggestions[i].label,
"The suggestion at " + i + "th index is correct.");
- is(actualSuggestions[i].count, suggestions[i].count || 1,
- "The count for suggestion at " + i + "th index is correct.");
}
}
});
function formatSuggestions(suggestions) {
return "[" + suggestions
- .map(s => "'" + s.label + "' (" + (s.count || 1) + ")")
+ .map(s => "'" + s.label + "'")
.join(", ") + "]";
}
--- a/devtools/client/inspector/test/browser_inspector_search-03.js
+++ b/devtools/client/inspector/test/browser_inspector_search-03.js
@@ -11,24 +11,24 @@ const TEST_URL = TEST_URL_ROOT + "doc_in
// An array of (key, suggestions) pairs where key is a key to press and
// suggestions is an array of suggestions that should be shown in the popup.
// Suggestion is an object with label of the entry and optional count
// (defaults to 1)
var TEST_DATA = [
{
key: "d",
suggestions: [
- {label: "div", count: 2},
- {label: "#d1", count: 1},
- {label: "#d2", count: 1}
+ {label: "div"},
+ {label: "#d1"},
+ {label: "#d2"}
]
},
{
key: "i",
- suggestions: [{label: "div", count: 2}]
+ suggestions: [{label: "div"}]
},
{
key: "v",
suggestions: []
},
{
key: ".",
suggestions: [{label: "div.c1"}]
@@ -45,52 +45,52 @@ var TEST_DATA = [
]
},
{
key: "VK_BACK_SPACE",
suggestions: []
},
{
key: "VK_BACK_SPACE",
- suggestions: [{label: "div", count: 2}]
+ suggestions: [{label: "div"}]
},
{
key: "VK_BACK_SPACE",
suggestions: [
- {label: "div", count: 2},
- {label: "#d1", count: 1},
- {label: "#d2", count: 1}
+ {label: "div"},
+ {label: "#d1"},
+ {label: "#d2"}
]
},
{
key: "VK_BACK_SPACE",
suggestions: []
},
{
key: ".",
suggestions: [
- {label: ".c1", count: 3},
+ {label: ".c1"},
{label: ".c2"}
]
},
{
key: "c",
suggestions: [
- {label: ".c1", count: 3},
+ {label: ".c1"},
{label: ".c2"}
]
},
{
key: "2",
suggestions: []
},
{
key: "VK_BACK_SPACE",
suggestions: [
- {label: ".c1", count: 3},
+ {label: ".c1"},
{label: ".c2"}
]
},
{
key: "1",
suggestions: []
},
{
@@ -103,24 +103,24 @@ var TEST_DATA = [
},
{
key: "VK_BACK_SPACE",
suggestions: []
},
{
key: "VK_BACK_SPACE",
suggestions: [
- {label: ".c1", count: 3},
+ {label: ".c1"},
{label: ".c2"}
]
},
{
key: "VK_BACK_SPACE",
suggestions: [
- {label: ".c1", count: 3},
+ {label: ".c1"},
{label: ".c2"}
]
},
{
key: "VK_BACK_SPACE",
suggestions: []
},
{
@@ -184,19 +184,17 @@ add_task(function* () {
let actualSuggestions = popup.getItems().reverse();
is(popup.isOpen ? actualSuggestions.length: 0, suggestions.length,
"There are expected number of suggestions.");
for (let i = 0; i < suggestions.length; i++) {
is(actualSuggestions[i].label, suggestions[i].label,
"The suggestion at " + i + "th index is correct.");
- is(actualSuggestions[i].count, suggestions[i].count || 1,
- "The count for suggestion at " + i + "th index is correct.");
}
}
});
function formatSuggestions(suggestions) {
return "[" + suggestions
- .map(s => "'" + s.label + "' (" + (s.count || 1) + ")")
+ .map(s => "'" + s.label + "'")
.join(", ") + "]";
}
--- a/devtools/client/inspector/test/browser_inspector_search-04.js
+++ b/devtools/client/inspector/test/browser_inspector_search-04.js
@@ -14,67 +14,67 @@ const TEST_URL = "data:text/html;charset
// An array of (key, suggestions) pairs where key is a key to press and
// suggestions is an array of suggestions that should be shown in the popup.
// Suggestion is an object with label of the entry and optional count
// (defaults to 1)
var TEST_DATA = [
{
key: "d",
suggestions: [
- {label: "div", count: 5},
- {label: "#d1", count: 2},
- {label: "#d2", count: 2}
+ {label: "div"},
+ {label: "#d1"},
+ {label: "#d2"}
]
},
{
key: "i",
- suggestions: [{label: "div", count: 5}]
+ suggestions: [{label: "div"}]
},
{
key: "v",
suggestions: []
},
{
key: "VK_BACK_SPACE",
- suggestions: [{label: "div", count: 5}]
+ suggestions: [{label: "div"}]
},
{
key: "VK_BACK_SPACE",
suggestions: [
- {label: "div", count: 5},
- {label: "#d1", count: 2},
- {label: "#d2", count: 2}
+ {label: "div"},
+ {label: "#d1"},
+ {label: "#d2"}
]
},
{
key: "VK_BACK_SPACE",
suggestions: []
},
{
key: ".",
suggestions: [
- {label: ".c1", count: 7},
- {label: ".c2", count: 3}
+ {label: ".c1"},
+ {label: ".c2"}
]
},
{
key: "VK_BACK_SPACE",
suggestions: []
},
{
key: "#",
suggestions: [
- {label: "#b1", count: 2},
- {label: "#d1", count: 2},
- {label: "#d2", count: 2},
- {label: "#p1", count: 2},
- {label: "#p2", count: 2},
- {label: "#p3", count: 2},
- {label: "#s1", count: 2},
- {label: "#s2", count: 2}
+ {label: "#b1"},
+ {label: "#d1"},
+ {label: "#d2"},
+ {label: "#p1"},
+ {label: "#p2"},
+ {label: "#p3"},
+ {label: "#s1"},
+ {label: "#s2"}
]
},
];
add_task(function* () {
let {inspector} = yield openInspectorForURL(TEST_URL);
let searchBox = inspector.searchBox;
let popup = inspector.searchSuggestions.searchPopup;
@@ -95,19 +95,17 @@ add_task(function* () {
let actualSuggestions = popup.getItems().reverse();
is(popup.isOpen ? actualSuggestions.length: 0, suggestions.length,
"There are expected number of suggestions.");
for (let i = 0; i < suggestions.length; i++) {
is(actualSuggestions[i].label, suggestions[i].label,
"The suggestion at " + i + "th index is correct.");
- is(actualSuggestions[i].count, suggestions[i].count || 1,
- "The count for suggestion at " + i + "th index is correct.");
}
}
});
function formatSuggestions(suggestions) {
return "[" + suggestions
- .map(s => "'" + s.label + "' (" + (s.count || 1) + ")")
+ .map(s => "'" + s.label + "'")
.join(", ") + "]";
}
--- a/devtools/client/inspector/test/browser_inspector_search-05.js
+++ b/devtools/client/inspector/test/browser_inspector_search-05.js
@@ -6,59 +6,86 @@
// Testing that when search results contain suggestions for nodes in other
// frames, selecting these suggestions actually selects the right nodes.
const IFRAME_SRC = "doc_inspector_search.html";
const TEST_URL = "data:text/html;charset=utf-8," +
"<iframe id=\"iframe-1\" src=\"" +
TEST_URL_ROOT + IFRAME_SRC + "\"></iframe>" +
"<iframe id=\"iframe-2\" src=\"" +
- TEST_URL_ROOT + IFRAME_SRC + "\"></iframe>";
+ TEST_URL_ROOT + IFRAME_SRC + "\"></iframe>" +
+ "<iframe id='iframe-3' src='data:text/html," +
+ "<button id=\"b1\">Nested button</button>" +
+ "<iframe id=\"iframe-4\" src=" + TEST_URL_ROOT + IFRAME_SRC + "></iframe>'>" +
+ "</iframe>";
add_task(function* () {
let {inspector} = yield openInspectorForURL(TEST_URL);
- let {walker} = inspector;
let searchBox = inspector.searchBox;
let popup = inspector.searchSuggestions.searchPopup;
info("Focus the search box");
yield focusSearchBoxUsingShortcut(inspector.panelWin);
info("Enter # to search for all ids");
- let command = once(searchBox, "command");
+ let processingDone = once(inspector.searchSuggestions, "processing-done");
EventUtils.synthesizeKey("#", {}, inspector.panelWin);
- yield command;
+ yield processingDone;
info("Wait for search query to complete");
yield inspector.searchSuggestions._lastQuery;
- info("Press tab to fill the search input with the first suggestion and " +
- "expect a new selection");
+ info("Press tab to fill the search input with the first suggestion");
+ processingDone = once(inspector.searchSuggestions, "processing-done");
+ EventUtils.synthesizeKey("VK_TAB", {}, inspector.panelWin);
+ yield processingDone;
+
+ info("Press enter and expect a new selection");
let onSelect = inspector.once("inspector-updated");
- EventUtils.synthesizeKey("VK_TAB", {}, inspector.panelWin);
+ EventUtils.synthesizeKey("VK_RETURN", {}, inspector.panelWin);
yield onSelect;
- let node = inspector.selection.nodeFront;
- ok(node.id, "b1", "The selected node is #b1");
- ok(node.tagName.toLowerCase(), "button",
- "The selected node is <button>");
+ yield checkCorrectButton(inspector, "#iframe-1");
- let selectedNodeDoc = yield walker.document(node);
- let iframe1 = yield walker.querySelector(walker.rootNode, "#iframe-1");
- let iframe1Doc = (yield walker.children(iframe1)).nodes[0];
- is(selectedNodeDoc, iframe1Doc, "The selected node is in iframe 1");
+ info("Press enter to cycle through multiple nodes matching this suggestion");
+ onSelect = inspector.once("inspector-updated");
+ EventUtils.synthesizeKey("VK_RETURN", {}, inspector.panelWin);
+ yield onSelect;
+
+ yield checkCorrectButton(inspector, "#iframe-2");
info("Press enter to cycle through multiple nodes matching this suggestion");
onSelect = inspector.once("inspector-updated");
EventUtils.synthesizeKey("VK_RETURN", {}, inspector.panelWin);
yield onSelect;
- node = inspector.selection.nodeFront;
- ok(node.id, "b1", "The selected node is #b1 again");
- ok(node.tagName.toLowerCase(), "button",
- "The selected node is <button> again");
+ yield checkCorrectButton(inspector, "#iframe-3");
+
+ info("Press enter to cycle through multiple nodes matching this suggestion");
+ onSelect = inspector.once("inspector-updated");
+ EventUtils.synthesizeKey("VK_RETURN", {}, inspector.panelWin);
+ yield onSelect;
+
+ yield checkCorrectButton(inspector, "#iframe-4");
+
+ info("Press enter to cycle through multiple nodes matching this suggestion");
+ onSelect = inspector.once("inspector-updated");
+ EventUtils.synthesizeKey("VK_RETURN", {}, inspector.panelWin);
+ yield onSelect;
- selectedNodeDoc = yield walker.document(node);
- let iframe2 = yield walker.querySelector(walker.rootNode, "#iframe-2");
- let iframe2Doc = (yield walker.children(iframe2)).nodes[0];
- is(selectedNodeDoc, iframe2Doc, "The selected node is in iframe 2");
+ yield checkCorrectButton(inspector, "#iframe-1");
});
+
+let checkCorrectButton = Task.async(function*(inspector, frameSelector) {
+ let {walker} = inspector;
+ let node = inspector.selection.nodeFront;
+
+ ok(node.id, "b1", "The selected node is #b1");
+ ok(node.tagName.toLowerCase(), "button",
+ "The selected node is <button>");
+
+ let selectedNodeDoc = yield walker.document(node);
+ let iframe = yield walker.multiFrameQuerySelectorAll(frameSelector);
+ iframe = yield iframe.item(0);
+ let iframeDoc = (yield walker.children(iframe)).nodes[0];
+ is(selectedNodeDoc, iframeDoc, "The selected node is in " + frameSelector);
+});
--- a/devtools/client/inspector/test/browser_inspector_search-06.js
+++ b/devtools/client/inspector/test/browser_inspector_search-06.js
@@ -9,43 +9,48 @@
const TEST_URL = TEST_URL_ROOT + "doc_inspector_search.html";
add_task(function* () {
let { inspector, testActor } = yield openInspectorForURL(TEST_URL);
info("Searching for test node #d1");
yield focusSearchBoxUsingShortcut(inspector.panelWin);
- yield synthesizeKeys(["#", "d", "1"], inspector);
+ yield synthesizeKeys(["#", "d", "1", "VK_RETURN"], inspector);
+ yield inspector.search.once("search-result");
assertHasResult(inspector, true);
info("Removing node #d1");
yield mutatePage(inspector, testActor,
"document.getElementById(\"d1\").remove()");
info("Pressing return button to search again for node #d1.");
yield synthesizeKeys("VK_RETURN", inspector);
+ yield inspector.search.once("search-result");
assertHasResult(inspector, false);
info("Emptying the field and searching for a node that doesn't exist: #d3");
- let keys = ["VK_BACK_SPACE", "VK_BACK_SPACE", "VK_BACK_SPACE", "#", "d", "3"];
+ let keys = ["VK_BACK_SPACE", "VK_BACK_SPACE", "VK_BACK_SPACE", "#", "d", "3",
+ "VK_RETURN"];
yield synthesizeKeys(keys, inspector);
+ yield inspector.search.once("search-result");
assertHasResult(inspector, false);
info("Create the #d3 node in the page");
yield mutatePage(inspector, testActor,
`document.getElementById("d2").insertAdjacentHTML(
"afterend", "<div id=d3></div>")`);
info("Pressing return button to search again for node #d3.");
yield synthesizeKeys("VK_RETURN", inspector);
+ yield inspector.search.once("search-result");
assertHasResult(inspector, true);
// Catch-all event for remaining server requests when searching for the new
// node.
yield inspector.once("inspector-updated");
});
function* synthesizeKeys(keys, inspector) {
--- a/devtools/client/inspector/test/browser_inspector_search-reserved.js
+++ b/devtools/client/inspector/test/browser_inspector_search-reserved.js
@@ -8,95 +8,95 @@ const TEST_URL = TEST_URL_ROOT + "doc_in
// An array of (key, suggestions) pairs where key is a key to press and
// suggestions is an array of suggestions that should be shown in the popup.
// Suggestion is an object with label of the entry and optional count
// (defaults to 1)
const TEST_DATA = [
{
key: "#",
- suggestions: [{label: "#d1\\.d2", count: 1}]
+ suggestions: [{label: "#d1\\.d2"}]
},
{
key: "d",
- suggestions: [{label: "#d1\\.d2", count: 1}]
+ suggestions: [{label: "#d1\\.d2"}]
},
{
key: "VK_BACK_SPACE",
- suggestions: [{label: "#d1\\.d2", count: 1}]
+ suggestions: [{label: "#d1\\.d2"}]
},
{
key: "VK_BACK_SPACE",
suggestions: []
},
{
key: ".",
- suggestions: [{label: ".c1\\.c2", count: 1}]
+ suggestions: [{label: ".c1\\.c2"}]
},
{
key: "c",
- suggestions: [{label: ".c1\\.c2", count: 1}]
+ suggestions: [{label: ".c1\\.c2"}]
},
{
key: "VK_BACK_SPACE",
- suggestions: [{label: ".c1\\.c2", count: 1}]
+ suggestions: [{label: ".c1\\.c2"}]
},
{
key: "VK_BACK_SPACE",
suggestions: []
},
{
key: "d",
- suggestions: [{label: "div", count: 2},
- {label: "#d1\\.d2", count: 1}]
+ suggestions: [{label: "div"},
+ {label: "#d1\\.d2"}]
},
{
key: "VK_BACK_SPACE",
suggestions: []
},
{
key:"c",
- suggestions: [{label: ".c1\\.c2", count: 1}]
+ suggestions: [{label: ".c1\\.c2"}]
},
{
key: "VK_BACK_SPACE",
suggestions: []
},
{
key: "b",
- suggestions: [{label: "body", count: 1}]
+ suggestions: [{label: "body"}]
},
{
key: "o",
- suggestions: [{label: "body", count: 1}]
+ suggestions: [{label: "body"}]
},
{
key: "d",
- suggestions: [{label: "body", count: 1}]
+ suggestions: [{label: "body"}]
},
{
key: "y",
suggestions: []
},
{
key: " ",
- suggestions: [{label: "body div", count: 2}]
+ suggestions: [{label: "body div"}]
},
{
key: ".",
- suggestions: [{label: "body .c1\\.c2", count: 1}]
+ suggestions: [{label: "body .c1\\.c2"}]
},
{
key: "VK_BACK_SPACE",
- suggestions: [{label: "body div", count: 2}]
+ suggestions: [{label: "body div"}]
},
{
key: "#",
- suggestions: [{label: "body #", count: 1},
- {label: "body #d1\\.d2", count: 1}]
+ suggestions: [{label: "body #"},
+ {label: "body #d1\\.d2"}]
}
];
add_task(function* () {
let { inspector } = yield openInspectorForURL(TEST_URL);
let searchBox = inspector.searchBox;
let popup = inspector.searchSuggestions.searchPopup;
@@ -116,19 +116,17 @@ add_task(function* () {
let actualSuggestions = popup.getItems().reverse();
is(popup.isOpen ? actualSuggestions.length: 0, suggestions.length,
"There are expected number of suggestions.");
for (let i = 0; i < suggestions.length; i++) {
is(suggestions[i].label, actualSuggestions[i].label,
"The suggestion at " + i + "th index is correct.");
- is(suggestions[i].count || 1, actualSuggestions[i].count,
- "The count for suggestion at " + i + "th index is correct.");
}
}
});
function formatSuggestions(suggestions) {
return "[" + suggestions
- .map(s => "'" + s.label + "' (" + s.count || 1 + ")")
+ .map(s => "'" + s.label + "'")
.join(", ") + "]";
}
--- a/devtools/client/locales/en-US/inspector.dtd
+++ b/devtools/client/locales/en-US/inspector.dtd
@@ -108,19 +108,18 @@
<!ENTITY inspector.selectButton.tooltip "Select element with mouse">
<!-- LOCALIZATION NOTE (inspectorSearchHTML.label2): This is the label shown as
the placeholder in inspector search box -->
<!ENTITY inspectorSearchHTML.label2 "Search with CSS Selectors">
<!ENTITY inspectorSearchHTML.key "F">
-<!-- LOCALIZATION NOTE (inspectorSearchHTML.label3): This is the label that will
- be shown as the placeholder in the future, once the inspector search box
- supports the full text HTML search in Bug 835896. -->
+<!-- LOCALIZATION NOTE (inspectorSearchHTML.label3): This is the label that is
+ shown as the placeholder for the markup view search in the inspector. -->
<!ENTITY inspectorSearchHTML.label3 "Search HTML">
<!-- LOCALIZATION NOTE (inspectorCopyImageDataUri.label): This is the label
shown in the inspector contextual-menu for the item that lets users copy
the URL embedding the image data encoded in Base 64 (what we name
here Image Data URL). For more information:
https://developer.mozilla.org/en-US/docs/Web/HTTP/data_URIs -->
<!ENTITY inspectorCopyImageDataUri.label "Copy Image Data-URL">
--- a/devtools/client/memory/components/toolbar.js
+++ b/devtools/client/memory/components/toolbar.js
@@ -35,27 +35,27 @@ const Toolbar = module.exports = createC
filterString,
setFilterString
} = this.props;
return (
dom.div({ className: "devtools-toolbar" },
dom.button({
id: "take-snapshot",
- className: `take-snapshot devtools-button`,
+ className: "take-snapshot devtools-button",
onClick: onTakeSnapshotClick,
title: L10N.getStr("take-snapshot")
}),
dom.div({ className: "toolbar-group" },
dom.label({ className: "breakdown-by" },
L10N.getStr("toolbar.breakdownBy"),
dom.select({
id: "select-breakdown",
- className: `select-breakdown`,
+ className: "select-breakdown",
onChange: e => onBreakdownChange(e.target.value),
}, ...breakdowns.map(({ name, displayName }) => dom.option({ key: name, value: name }, displayName)))
),
dom.label({},
dom.input({
id: "invert-tree-checkbox",
type: "checkbox",
@@ -76,16 +76,17 @@ const Toolbar = module.exports = createC
L10N.getStr("checkbox.recordAllocationStacks")
),
dom.div({ id: "toolbar-spacer", className: "spacer" }),
dom.input({
id: "filter",
type: "search",
+ className: "devtools-searchinput",
placeholder: L10N.getStr("filter.placeholder"),
onChange: event => setFilterString(event.target.value),
value: !!filterString ? filterString : undefined,
})
)
)
);
}
--- a/devtools/client/shared/test/browser_filter-editor-01.js
+++ b/devtools/client/shared/test/browser_filter-editor-01.js
@@ -54,9 +54,19 @@ add_task(function *() {
widget.setCssValue("drop-shadow(whatever)");
is(widget.getCssValue(), "drop-shadow()",
"setCssValue should replace invalid drop-shadow argument with empty string");
info("Test parsing of mixed invalid argument");
widget.setCssValue("contrast(5%) whatever invert('xxx')");
is(widget.getCssValue(), "contrast(5%) invert(0%)",
"setCssValue should handle multiple errors");
+
+ info("Test parsing of 'unset'");
+ widget.setCssValue("unset");
+ is(widget.getCssValue(), "unset", "setCssValue should handle 'unset'");
+ info("Test parsing of 'initial'");
+ widget.setCssValue("initial");
+ is(widget.getCssValue(), "initial", "setCssValue should handle 'initial'");
+ info("Test parsing of 'inherit'");
+ widget.setCssValue("inherit");
+ is(widget.getCssValue(), "inherit", "setCssValue should handle 'inherit'");
});
--- a/devtools/client/shared/widgets/FilterWidget.js
+++ b/devtools/client/shared/widgets/FilterWidget.js
@@ -93,16 +93,19 @@ const filterList = [
},
{
"name": "url",
"placeholder": "example.svg#c1",
"type": "string"
}
];
+// Valid values that shouldn't be parsed for filters.
+const SPECIAL_VALUES = new Set(["none", "unset", "initial", "inherit"]);
+
/**
* A CSS Filter editor widget used to add/remove/modify
* filters.
*
* Normally, it takes a CSS filter value as input, parses it
* and creates the required elements / bindings.
*
* You can, however, use add/remove/update methods manually.
@@ -552,17 +555,17 @@ CSSFilterEditorWidget.prototype = {
},
_savePreset: function(e) {
e.preventDefault();
let name = this.addPresetInput.value;
let value = this.getCssValue();
- if (!name || !value || value === "none") {
+ if (!name || !value || SPECIAL_VALUES.has(value)) {
this.emit("preset-save-error");
return;
}
this.getPresets().then(presets => {
let index = presets.findIndex(preset => preset.name === name);
if (index > -1) {
@@ -701,17 +704,18 @@ CSSFilterEditorWidget.prototype = {
*/
setCssValue: function(cssValue) {
if (!cssValue) {
throw new Error("Missing CSS filter value in setCssValue");
}
this.filters = [];
- if (cssValue === "none") {
+ if (SPECIAL_VALUES.has(cssValue)) {
+ this._specialValue = cssValue;
this.emit("updated", this.getCssValue());
this.render();
return;
}
for (let {name, value} of tokenizeFilterValue(cssValue)) {
// If the specified value is invalid, replace it with the
// default.
@@ -820,17 +824,17 @@ CSSFilterEditorWidget.prototype = {
* Generates CSS filter value for filters of the widget
*
* @return {String}
* css value of filters
*/
getCssValue: function() {
return this.filters.map((filter, i) => {
return `${filter.name}(${this.getValueAt(i)})`;
- }).join(" ") || "none";
+ }).join(" ") || this._specialValue || "none";
},
/**
* Updates specified filter's value
*
* @param {Number} index
* The index of the filter in the current list of filters
* @param {number/string} value
@@ -901,17 +905,17 @@ function swapArrayIndices(array, a, b) {
*
* @param {String} css CSS Filter value to be parsed
* @return {Array} An array of {name, value} pairs
*/
function tokenizeFilterValue(css) {
let filters = [];
let depth = 0;
- if (css === "none") {
+ if (SPECIAL_VALUES.has(css)) {
return filters;
}
let state = "initial";
let name;
let contents;
for (let token of cssTokenizer(css)) {
switch (state) {
--- a/devtools/client/themes/inspector.css
+++ b/devtools/client/themes/inspector.css
@@ -10,16 +10,20 @@
}
#inspector-toolbar:-moz-locale-dir(rtl) {
padding-left: 4px;
}
%endif
+#inspector-searchlabel {
+ overflow: hidden;
+}
+
#inspector-searchbox {
transition-property: max-width, -moz-padding-end, -moz-padding-start;
transition-duration: 250ms;
transition-timing-function: ease;
}
#inspector-searchbox:not([focused]):not([filled]) > .textbox-input-box {
overflow: hidden;
--- a/devtools/client/themes/memory.css
+++ b/devtools/client/themes/memory.css
@@ -95,16 +95,21 @@ html, body, #app, #memory-tool {
background-image: url(images/command-screenshot@2x.png);
}
}
.spacer {
flex: 1;
}
+#filter {
+ align-self: stretch;
+ margin: 2px;
+}
+
/**
* TODO bug 1213100
* Once we figure out how to store invertable buttons (pseudo element like in
* this case?) we should add a .invertable class to handle this generally,
* rather than the definitions in toolbars.css.
*
* @see bug 1173397 for another inverted related bug
*/
--- a/devtools/server/actors/inspector.js
+++ b/devtools/server/actors/inspector.js
@@ -56,16 +56,17 @@ const protocol = require("devtools/serve
const {Arg, Option, method, RetVal, types} = protocol;
const {LongStringActor, ShortLongString} = require("devtools/server/actors/string");
const promise = require("promise");
const {Task} = Cu.import("resource://gre/modules/Task.jsm", {});
const object = require("sdk/util/object");
const events = require("sdk/event/core");
const {Unknown} = require("sdk/platform/xpcom");
const {Class} = require("sdk/core/heritage");
+const {WalkerSearch} = require("devtools/server/actors/utils/walker-search");
const {PageStyleActor, getFontPreviewData} = require("devtools/server/actors/styles");
const {
HighlighterActor,
CustomHighlighterActor,
isTypeRegistered,
} = require("devtools/server/actors/highlighters");
const {
isAnonymous,
@@ -1117,16 +1118,23 @@ types.addDictType("disconnectedNodeArray
nodes: "array:domnode",
// Nodes that are needed to connect those nodes to the root.
newParents: "array:domnode"
});
types.addDictType("dommutation", {});
+types.addDictType("searchresult", {
+ list: "domnodelist",
+ // Right now there is isn't anything required for metadata,
+ // but it's json so it can be extended with extra data.
+ metadata: "array:json"
+});
+
/**
* Server side of a node list as returned by querySelectorAll()
*/
var NodeListActor = exports.NodeListActor = protocol.ActorClass({
typeName: "domnodelist",
initialize: function(walker, nodeList) {
protocol.Actor.prototype.initialize.call(this);
@@ -1294,16 +1302,18 @@ var WalkerActor = protocol.ActorClass({
this.tabActor = tabActor;
this.rootWin = tabActor.window;
this.rootDoc = this.rootWin.document;
this._refMap = new Map();
this._pendingMutations = [];
this._activePseudoClassLocks = new Set();
this.showAllAnonymousContent = options.showAllAnonymousContent;
+ this.walkerSearch = new WalkerSearch(this);
+
// Nodes which have been removed from the client's known
// ownership tree are considered "orphaned", and stored in
// this set.
this._orphaned = new Set();
// The client can tell the walker that it is interested in a node
// even when it is orphaned with the `retainNode` method. This
// list contains orphaned nodes that were so retained.
@@ -1329,17 +1339,23 @@ var WalkerActor = protocol.ActorClass({
form: function() {
return {
actor: this.actorID,
root: this.rootNode.form(),
traits: {
// FF42+ Inspector starts managing the Walker, while the inspector also
// starts cleaning itself up automatically on client disconnection.
// So that there is no need to manually release the walker anymore.
- autoReleased: true
+ autoReleased: true,
+ // XXX: It seems silly that we need to tell the front which capabilities
+ // its actor has in this way when the target can use actorHasMethod. If
+ // this was ported to the protocol (Bug 1157048) we could call that inside
+ // of custom front methods and not need to do traits for this.
+ multiFrameQuerySelectorAll: true,
+ textSearch: true,
}
}
},
toString: function() {
return "[WalkerActor " + this.actorID + "]";
},
@@ -1371,16 +1387,17 @@ var WalkerActor = protocol.ActorClass({
this._refMap = null;
events.off(this.tabActor, "will-navigate", this.onFrameUnload);
events.off(this.tabActor, "navigate", this.onFrameLoad);
this.onFrameLoad = null;
this.onFrameUnload = null;
+ this.walkerSearch.destroy();
this.reflowObserver.off("reflows", this._onReflows);
this.reflowObserver = null;
this._onReflows = null;
releaseLayoutChangesObserver(this.tabActor);
this.onMutations = null;
this.tabActor = null;
@@ -2044,16 +2061,43 @@ var WalkerActor = protocol.ActorClass({
selector: Arg(0)
},
response: {
list: RetVal("domnodelist")
}
}),
/**
+ * Search the document for a given string.
+ * Results will be searched with the walker-search module (searches through
+ * tag names, attribute names and values, and text contents).
+ *
+ * @returns {searchresult}
+ * - {NodeList} list
+ * - {Array<Object>} metadata. Extra information with indices that
+ * match up with node list.
+ */
+ search: method(function(query) {
+ let results = this.walkerSearch.search(query);
+ let nodeList = new NodeListActor(this, results.map(r => r.node));
+
+ return {
+ list: nodeList,
+ metadata: []
+ }
+ }, {
+ request: {
+ query: Arg(0),
+ },
+ response: {
+ list: RetVal("searchresult"),
+ }
+ }),
+
+ /**
* Returns a list of matching results for CSS selector autocompletion.
*
* @param string query
* The selector query being completed
* @param string completing
* The exact token being completed out of the query
* @param string selectorState
* One of "pseudo", "id", "tag", "class", "null"
@@ -2833,16 +2877,21 @@ var WalkerActor = protocol.ActorClass({
/**
* Handles mutations from the DOM mutation observer API.
*
* @param array[MutationRecord] mutations
* See https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver#MutationRecord
*/
onMutations: function(mutations) {
+ // Notify any observers that want *all* mutations (even on nodes that aren't
+ // referenced). This is not sent over the protocol so can only be used by
+ // scripts running in the server process.
+ events.emit(this, "any-mutation");
+
for (let change of mutations) {
let targetActor = this._refMap.get(change.target);
if (!targetActor) {
continue;
}
let targetNode = change.target;
let type = change.type;
let mutation = {
@@ -3310,16 +3359,85 @@ var WalkerFront = exports.WalkerFront =
getNodeFromActor: protocol.custom(function(actorID, path) {
return this._getNodeFromActor(actorID, path).then(response => {
return response ? response.node : null;
});
}, {
impl: "_getNodeFromActor"
}),
+ /*
+ * Incrementally search the document for a given string.
+ * For modern servers, results will be searched with using the WalkerActor
+ * `search` function (includes tag names, attributes, and text contents).
+ * Only 1 result is sent back, and calling the method again with the same
+ * query will send the next result. When there are no more results to be sent
+ * back, null is sent.
+ * @param {String} query
+ * @param {Object} options
+ * - "reverse": search backwards
+ * - "selectorOnly": treat input as a selector string (don't search text
+ * tags, attributes, etc)
+ */
+ search: protocol.custom(Task.async(function*(query, options = { }) {
+ let nodeList;
+ let searchType;
+ let searchData = this.searchData = this.searchData || { };
+ let selectorOnly = !!options.selectorOnly;
+
+ // Backwards compat. Use selector only search if the new
+ // search functionality isn't implemented, or if the caller (tests)
+ // want it.
+ if (selectorOnly || !this.traits.textSearch) {
+ searchType = "selector";
+ if (this.traits.multiFrameQuerySelectorAll) {
+ nodeList = yield this.multiFrameQuerySelectorAll(query);
+ } else {
+ nodeList = yield this.querySelectorAll(this.rootNode, query);
+ }
+ } else {
+ searchType = "search";
+ let result = yield this._search(query, options);
+ nodeList = result.list;
+ }
+
+ // If this is a new search, start at the beginning.
+ if (searchData.query !== query ||
+ searchData.selectorOnly !== selectorOnly) {
+ searchData.selectorOnly = selectorOnly;
+ searchData.query = query;
+ searchData.index = -1;
+ }
+
+ if (!nodeList.length) {
+ return null;
+ }
+
+ // Move search result cursor and cycle if necessary.
+ searchData.index = options.reverse ? searchData.index - 1 :
+ searchData.index + 1;
+ if (searchData.index >= nodeList.length) {
+ searchData.index = 0;
+ }
+ if (searchData.index < 0) {
+ searchData.index = nodeList.length - 1;
+ }
+
+ // Send back the single node, along with any relevant search data
+ let node = yield nodeList.item(searchData.index);
+ return {
+ type: searchType,
+ node: node,
+ resultsLength: nodeList.length,
+ resultsIndex: searchData.index,
+ };
+ }), {
+ impl: "_search"
+ }),
+
_releaseFront: function(node, force) {
if (node.retained && !force) {
node.reparent(null);
this._retainedOrphans.add(node);
return;
}
if (node.retained) {
@@ -3870,33 +3988,49 @@ DocumentWalker.prototype = {
set currentNode(aVal) {
this.walker.currentNode = aVal;
},
parentNode: function() {
return this.walker.parentNode();
},
+ nextNode: function() {
+ let node = this.walker.currentNode;
+ if (!node) {
+ return null;
+ }
+
+ let nextNode = this.walker.nextNode();
+ while (nextNode && this.filter(nextNode) === Ci.nsIDOMNodeFilter.FILTER_SKIP) {
+ nextNode = this.walker.nextNode();
+ }
+
+ return nextNode;
+ },
+
firstChild: function() {
let node = this.walker.currentNode;
- if (!node)
+ if (!node) {
return null;
+ }
let firstChild = this.walker.firstChild();
while (firstChild && this.filter(firstChild) === Ci.nsIDOMNodeFilter.FILTER_SKIP) {
firstChild = this.walker.nextSibling();
}
return firstChild;
},
lastChild: function() {
let node = this.walker.currentNode;
- if (!node)
+ if (!node) {
return null;
+ }
let lastChild = this.walker.lastChild();
while (lastChild && this.filter(lastChild) === Ci.nsIDOMNodeFilter.FILTER_SKIP) {
lastChild = this.walker.previousSibling();
}
return lastChild;
},
--- a/devtools/server/actors/styles.js
+++ b/devtools/server/actors/styles.js
@@ -2079,16 +2079,23 @@ function getRuleText(initialText, line,
endOffset = token.endOffset;
}
// If the rule was of the form "selector {" with no closing brace
// and no properties, just return an empty string.
if (startOffset === undefined) {
return {offset: 0, text: ""};
}
+ // If the input didn't have any tokens between the braces (e.g.,
+ // "div {}"), then the endOffset won't have been set yet; so account
+ // for that here.
+ if (endOffset === undefined) {
+ endOffset = startOffset;
+ }
+
// Note that this approach will preserve comments, despite the fact
// that cssTokenizer skips them.
return {offset: textOffset + startOffset,
text: text.substring(startOffset, endOffset)};
}
exports.getRuleText = getRuleText;
--- a/devtools/server/actors/utils/moz.build
+++ b/devtools/server/actors/utils/moz.build
@@ -7,10 +7,11 @@
DevToolsModules(
'actor-registry-utils.js',
'audionodes.json',
'automation-timeline.js',
'make-debugger.js',
'map-uri-to-addon-id.js',
'ScriptStore.js',
'stack.js',
- 'TabSources.js'
+ 'TabSources.js',
+ 'walker-search.js'
)
new file mode 100644
--- /dev/null
+++ b/devtools/server/actors/utils/walker-search.js
@@ -0,0 +1,278 @@
+/* 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";
+
+/**
+ * The walker-search module provides a simple API to index and search strings
+ * and elements inside a given document.
+ * It indexes tag names, attribute names and values, and text contents.
+ * It provides a simple search function that returns a list of nodes that
+ * matched.
+ */
+
+const {Ci, Cu} = require("chrome");
+
+/**
+ * The WalkerIndex class indexes the document (and all subdocs) from
+ * a given walker.
+ *
+ * It is only indexed the first time the data is accessed and will be
+ * re-indexed if a mutation happens between requests.
+ *
+ * @param {Walker} walker The walker to be indexed
+ */
+function WalkerIndex(walker) {
+ this.walker = walker;
+ this.clearIndex = this.clearIndex.bind(this);
+
+ // Kill the index when mutations occur, the next data get will re-index.
+ this.walker.on("any-mutation", this.clearIndex);
+}
+
+WalkerIndex.prototype = {
+ /**
+ * Destroy this instance, releasing all data and references
+ */
+ destroy: function() {
+ this.walker.off("any-mutation", this.clearIndex);
+ },
+
+ clearIndex: function() {
+ if (!this.currentlyIndexing) {
+ this._data = null;
+ }
+ },
+
+ get doc() {
+ return this.walker.rootDoc;
+ },
+
+ /**
+ * Get the indexed data
+ * This getter also indexes if it hasn't been done yet or if the state is
+ * dirty
+ *
+ * @returns Map<String, Array<{type:String, node:DOMNode}>>
+ * A Map keyed on the searchable value, containing an array with
+ * objects containing the 'type' (one of ALL_RESULTS_TYPES), and
+ * the DOM Node.
+ */
+ get data() {
+ if (!this._data) {
+ this._data = new Map();
+ this.index();
+ }
+
+ return this._data;
+ },
+
+ _addToIndex: function(type, node, value) {
+ // Add an entry for this value if there isn't one
+ let entry = this._data.get(value);
+ if (!entry) {
+ this._data.set(value, []);
+ }
+
+ // Add the type/node to the list
+ this._data.get(value).push({
+ type: type,
+ node: node
+ });
+ },
+
+ index: function() {
+ // Handle case where iterating nextNode() with the deepTreeWalker triggers
+ // a mutation (Bug 1222558)
+ this.currentlyIndexing = true;
+
+ let documentWalker = this.walker.getDocumentWalker(this.doc);
+ while (documentWalker.nextNode()) {
+ let node = documentWalker.currentNode;
+
+ if (node.nodeType === 1) {
+ // For each element node, we get the tagname and all attributes names
+ // and values
+ let localName = node.localName;
+ if (localName === "_moz_generated_content_before") {
+ this._addToIndex("tag", node, "::before");
+ this._addToIndex("text", node, node.textContent.trim());
+ } else if (localName === "_moz_generated_content_after") {
+ this._addToIndex("tag", node, "::after");
+ this._addToIndex("text", node, node.textContent.trim());
+ } else {
+ this._addToIndex("tag", node, node.localName);
+ }
+
+ for (let {name, value} of node.attributes) {
+ this._addToIndex("attributeName", node, name);
+ this._addToIndex("attributeValue", node, value);
+ }
+ } else if (node.textContent && node.textContent.trim().length) {
+ // For comments and text nodes, we get the text
+ this._addToIndex("text", node, node.textContent.trim());
+ }
+ }
+
+ this.currentlyIndexing = false;
+ }
+};
+
+exports.WalkerIndex = WalkerIndex;
+
+/**
+ * The WalkerSearch class provides a way to search an indexed document as well
+ * as find elements that match a given css selector.
+ *
+ * Usage example:
+ * let s = new WalkerSearch(doc);
+ * let res = s.search("lang", index);
+ * for (let {matched, results} of res) {
+ * for (let {node, type} of results) {
+ * console.log("The query matched a node's " + type);
+ * console.log("Node that matched", node);
+ * }
+ * }
+ * s.destroy();
+ *
+ * @param {Walker} the walker to be searched
+ */
+function WalkerSearch(walker) {
+ this.walker = walker;
+ this.index = new WalkerIndex(this.walker);
+}
+
+WalkerSearch.prototype = {
+ destroy: function() {
+ this.index.destroy();
+ this.walker = null;
+ },
+
+ _addResult: function(node, type, results) {
+ if (!results.has(node)) {
+ results.set(node, []);
+ }
+
+ let matches = results.get(node);
+
+ // Do not add if the exact same result is already in the list
+ let isKnown = false;
+ for (let match of matches) {
+ if (match.type === type) {
+ isKnown = true;
+ break;
+ }
+ }
+
+ if (!isKnown) {
+ matches.push({type});
+ }
+ },
+
+ _searchIndex: function(query, options, results) {
+ for (let [matched, res] of this.index.data) {
+ if (!options.searchMethod(query, matched)) {
+ continue;
+ }
+
+ // Add any relevant results (skipping non-requested options).
+ res.filter(entry => {
+ return options.types.indexOf(entry.type) !== -1;
+ }).forEach(({node, type}) => {
+ this._addResult(node, type, results);
+ });
+ }
+ },
+
+ _searchSelectors: function(query, options, results) {
+ // If the query is just one "word", no need to search because _searchIndex
+ // will lead the same results since it has access to tagnames anyway
+ let isSelector = query && query.match(/[ >~.#\[\]]/);
+ if (options.types.indexOf("selector") === -1 || !isSelector) {
+ return;
+ }
+
+ let nodes = this.walker._multiFrameQuerySelectorAll(query);
+ for (let node of nodes) {
+ this._addResult(node, "selector", results);
+ }
+ },
+
+ /**
+ * Search the document
+ * @param {String} query What to search for
+ * @param {Object} options The following options are accepted:
+ * - searchMethod {String} one of WalkerSearch.SEARCH_METHOD_*
+ * defaults to WalkerSearch.SEARCH_METHOD_CONTAINS (does not apply to
+ * selector search type)
+ * - types {Array} a list of things to search for (tag, text, attributes, etc)
+ * defaults to WalkerSearch.ALL_RESULTS_TYPES
+ * @return {Array} An array is returned with each item being an object like:
+ * {
+ * node: <the dom node that matched>,
+ * type: <the type of match: one of WalkerSearch.ALL_RESULTS_TYPES>
+ * }
+ */
+ search: function(query, options={}) {
+ options.searchMethod = options.searchMethod || WalkerSearch.SEARCH_METHOD_CONTAINS;
+ options.types = options.types || WalkerSearch.ALL_RESULTS_TYPES;
+
+ // Empty strings will return no results, as will non-string input
+ if (typeof query !== "string") {
+ query = "";
+ }
+
+ // Store results in a map indexed by nodes to avoid duplicate results
+ let results = new Map();
+
+ // Search through the indexed data
+ this._searchIndex(query, options, results);
+
+ // Search with querySelectorAll
+ this._searchSelectors(query, options, results);
+
+ // Concatenate all results into an Array to return
+ let resultList = [];
+ for (let [node, matches] of results) {
+ for (let {type} of matches) {
+ resultList.push({
+ node: node,
+ type: type,
+ });
+
+ // For now, just do one result per node since the frontend
+ // doesn't have a way to highlight each result individually
+ // yet.
+ break;
+ }
+ }
+
+ let documents = this.walker.tabActor.windows.map(win=>win.document);
+
+ // Sort the resulting nodes by order of appearance in the DOM
+ resultList.sort((a,b) => {
+ // Disconnected nodes won't get good results from compareDocumentPosition
+ // so check the order of their document instead.
+ if (a.node.ownerDocument != b.node.ownerDocument) {
+ let indA = documents.indexOf(a.node.ownerDocument);
+ let indB = documents.indexOf(b.node.ownerDocument);
+ return indA - indB;
+ }
+ // If the same document, then sort on DOCUMENT_POSITION_FOLLOWING (4)
+ // which means B is after A.
+ return a.node.compareDocumentPosition(b.node) & 4 ? -1 : 1;
+ });
+
+ return resultList;
+ }
+};
+
+WalkerSearch.SEARCH_METHOD_CONTAINS = (query, candidate) => {
+ return query && candidate.toLowerCase().indexOf(query.toLowerCase()) !== -1;
+};
+
+WalkerSearch.ALL_RESULTS_TYPES = ["tag", "text", "attributeName",
+ "attributeValue", "selector"];
+
+exports.WalkerSearch = WalkerSearch;
--- a/devtools/server/tests/mochitest/chrome.ini
+++ b/devtools/server/tests/mochitest/chrome.ini
@@ -5,16 +5,17 @@ support-files =
Debugger.Source.prototype.element.js
Debugger.Source.prototype.element-2.js
Debugger.Source.prototype.element.html
director-helpers.js
hello-actor.js
inspector_getImageData.html
inspector-delay-image-response.sjs
inspector-helpers.js
+ inspector-search-data.html
inspector-styles-data.css
inspector-styles-data.html
inspector-traversal-data.html
large-image.jpg
memory-helpers.js
memprof-helpers.js
nonchrome_unsafeDereference.html
small-image.gif
@@ -72,16 +73,18 @@ skip-if = buildapp == 'mulet'
[test_inspector-mutations-frameload.html]
[test_inspector-mutations-value.html]
[test_inspector-pseudoclass-lock.html]
[test_inspector-release.html]
[test_inspector-reload.html]
[test_inspector-remove.html]
[test_inspector-resolve-url.html]
[test_inspector-retain.html]
+[test_inspector-search.html]
+[test_inspector-search-front.html]
[test_inspector-scroll-into-view.html]
[test_inspector-traversal.html]
[test_makeGlobalObjectReference.html]
[test_memory.html]
[test_memory_allocations_01.html]
[test_memory_allocations_02.html]
[test_memory_allocations_03.html]
[test_memory_allocations_04.html]
new file mode 100644
--- /dev/null
+++ b/devtools/server/tests/mochitest/inspector-search-data.html
@@ -0,0 +1,52 @@
+<html>
+<head>
+ <meta charset="UTF-8">
+ <title>Inspector Search Test Data</title>
+ <style>
+ #pseudo {
+ display: block;
+ margin: 0;
+ }
+ #pseudo:before {
+ content: "before element";
+ }
+ #pseudo:after {
+ content: "after element";
+ }
+ </style>
+ <script type="text/javascript">
+ window.onload = function() {
+ window.opener.postMessage('ready', '*');
+ };
+ </script>
+</head>
+</body>
+ <!-- A comment
+ spread across multiple lines -->
+
+ <img width="100" height="100" src="large-image.jpg" />
+
+ <h1 id="pseudo">Heading 1</h1>
+ <p>A p tag with the text 'h1' inside of it.
+ <strong>A strong h1 result</strong>
+ </p>
+
+ <div id="arrows" northwest="↖" northeast="↗" southeast="↘" southwest="↙">
+ Unicode arrows
+ </div>
+
+ <h2>Heading 2</h2>
+ <h2>Heading 2</h2>
+ <h2>Heading 2</h2>
+
+ <h3>Heading 3</h3>
+ <h3>Heading 3</h3>
+ <h3>Heading 3</h3>
+
+ <h4>Heading 4</h4>
+ <h4>Heading 4</h4>
+ <h4>Heading 4</h4>
+
+ <div class="💩" id="💩" 💩="💩"></div>
+</body>
+</html>
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/devtools/server/tests/mochitest/test_inspector-search-front.html
@@ -0,0 +1,220 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=835896
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 835896</title>
+ <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
+ <script type="application/javascript;version=1.8" src="inspector-helpers.js"></script>
+ <script type="application/javascript;version=1.8">
+window.onload = function() {
+ const Cu = Components.utils;
+ Cu.import("resource://gre/modules/devtools/Loader.jsm");
+ const {Promise: promise} =
+ Cu.import("resource://gre/modules/Promise.jsm", {});
+ const {InspectorFront} =
+ devtools.require("devtools/server/actors/inspector");
+ const {console} =
+ Cu.import("resource://gre/modules/devtools/shared/Console.jsm", {});
+
+ SimpleTest.waitForExplicitFinish();
+
+ let walkerFront = null;
+ let inspectee = null;
+ let inspector = null;
+
+ // WalkerFront specific tests. These aren't to excercise search
+ // edge cases so much as to test the state the Front maintains between
+ // searches.
+ // See also test_inspector-search.html
+
+ addAsyncTest(function* setup() {
+ info ("Setting up inspector and walker actors.");
+
+ let url = document.getElementById("inspectorContent").href;
+
+ yield new Promise(resolve => {
+ attachURL(url, function(err, client, tab, doc) {
+ inspectee = doc;
+ inspector = InspectorFront(client, tab);
+ resolve();
+ });
+ });
+
+ walkerFront = yield inspector.getWalker();
+ ok(walkerFront, "getWalker() should return an actor.");
+
+ runNextTest();
+ });
+
+ addAsyncTest(function* testWalkerFrontDefaults() {
+ info ("Testing search API using WalkerFront.");
+ let nodes = yield walkerFront.querySelectorAll(walkerFront.rootNode, "h2");
+ let fronts = yield nodes.items();
+
+ let frontResult = yield walkerFront.search("");
+ ok(!frontResult, "Null result on front when searching for ''");
+
+ let results = yield walkerFront.search("h2");
+ isDeeply(results, {
+ node: fronts[0],
+ type: "search",
+ resultsIndex: 0,
+ resultsLength: 3
+ }, "Default options work");
+
+ results = yield walkerFront.search("h2", { });
+ isDeeply(results, {
+ node: fronts[1],
+ type: "search",
+ resultsIndex: 1,
+ resultsLength: 3
+ }, "Search works with empty options");
+
+ // Clear search data to remove result state on the front
+ yield walkerFront.search("");
+ runNextTest();
+ });
+
+ addAsyncTest(function* testMultipleSearches() {
+ info ("Testing search API using WalkerFront (reverse=false)");
+ let nodes = yield walkerFront.querySelectorAll(walkerFront.rootNode, "h2");
+ let fronts = yield nodes.items();
+
+ let results = yield walkerFront.search("h2");
+ isDeeply(results, {
+ node: fronts[0],
+ type: "search",
+ resultsIndex: 0,
+ resultsLength: 3
+ }, "Search works with multiple results (reverse=false)");
+
+ results = yield walkerFront.search("h2");
+ isDeeply(results, {
+ node: fronts[1],
+ type: "search",
+ resultsIndex: 1,
+ resultsLength: 3
+ }, "Search works with multiple results (reverse=false)");
+
+ results = yield walkerFront.search("h2");
+ isDeeply(results, {
+ node: fronts[2],
+ type: "search",
+ resultsIndex: 2,
+ resultsLength: 3
+ }, "Search works with multiple results (reverse=false)");
+
+ results = yield walkerFront.search("h2");
+ isDeeply(results, {
+ node: fronts[0],
+ type: "search",
+ resultsIndex: 0,
+ resultsLength: 3
+ }, "Search works with multiple results (reverse=false)");
+
+ // Clear search data to remove result state on the front
+ yield walkerFront.search("");
+ runNextTest();
+ });
+
+ addAsyncTest(function* testMultipleSearchesReverse() {
+ info ("Testing search API using WalkerFront (reverse=true)");
+ let nodes = yield walkerFront.querySelectorAll(walkerFront.rootNode, "h2");
+ let fronts = yield nodes.items();
+
+ let results = yield walkerFront.search("h2", {reverse: true});
+ isDeeply(results, {
+ node: fronts[2],
+ type: "search",
+ resultsIndex: 2,
+ resultsLength: 3
+ }, "Search works with multiple results (reverse=true)");
+
+ results = yield walkerFront.search("h2", {reverse: true});
+ isDeeply(results, {
+ node: fronts[1],
+ type: "search",
+ resultsIndex: 1,
+ resultsLength: 3
+ }, "Search works with multiple results (reverse=true)");
+
+ results = yield walkerFront.search("h2", {reverse: true});
+ isDeeply(results, {
+ node: fronts[0],
+ type: "search",
+ resultsIndex: 0,
+ resultsLength: 3
+ }, "Search works with multiple results (reverse=true)");
+
+ results = yield walkerFront.search("h2", {reverse: true});
+ isDeeply(results, {
+ node: fronts[2],
+ type: "search",
+ resultsIndex: 2,
+ resultsLength: 3
+ }, "Search works with multiple results (reverse=true)");
+
+ results = yield walkerFront.search("h2", {reverse: false});
+ isDeeply(results, {
+ node: fronts[0],
+ type: "search",
+ resultsIndex: 0,
+ resultsLength: 3
+ }, "Search works with multiple results (reverse=false)");
+
+ // Clear search data to remove result state on the front
+ yield walkerFront.search("");
+ runNextTest();
+ });
+
+
+ addAsyncTest(function* testBackwardsCompat() {
+ info ("Simulating a server that doesn't have the new search functionality.");
+ walkerFront.traits.textSearch = false;
+ let front = yield walkerFront.querySelector(walkerFront.rootNode, "h1");
+
+ let results = yield walkerFront.search("h1");
+ isDeeply(results, {
+ node: front,
+ type: "selector",
+ resultsIndex: 0,
+ resultsLength: 1
+ }, "Only querySelectorAll results being returned");
+
+ // Clear search data to remove result state on the front
+ yield walkerFront.search("");
+
+ // Reset the normal textSearch behavior
+ walkerFront.traits.textSearch = true;
+
+ results = yield walkerFront.search("h1");
+ isDeeply(results, {
+ node: front,
+ type: "search",
+ resultsIndex: 0,
+ resultsLength: 3
+ }, "Other results being included");
+
+ // Clear search data to remove result state on the front
+ yield walkerFront.search("");
+ runNextTest();
+ });
+
+ runNextTest();
+};
+ </script>
+</head>
+<body>
+<a id="inspectorContent" target="_blank" href="inspector-search-data.html">Test Document</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/devtools/server/tests/mochitest/test_inspector-search.html
@@ -0,0 +1,300 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=835896
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 835896</title>
+ <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
+ <script type="application/javascript;version=1.8" src="inspector-helpers.js"></script>
+ <script type="application/javascript;version=1.8">
+window.onload = function() {
+ const Cu = Components.utils;
+ Cu.import("resource://gre/modules/devtools/Loader.jsm");
+ const {Promise: promise} =
+ Cu.import("resource://gre/modules/Promise.jsm", {});
+ const {InspectorFront} =
+ devtools.require("devtools/server/actors/inspector");
+ const {WalkerSearch, WalkerIndex} =
+ devtools.require("devtools/server/actors/utils/walker-search");
+ const {console} =
+ Cu.import("resource://gre/modules/devtools/shared/Console.jsm", {});
+
+ SimpleTest.waitForExplicitFinish();
+
+ let walkerActor = null;
+ let walkerSearch = null;
+ let inspectee = null;
+ let inspector = null;
+
+ // WalkerSearch specific tests. This is to make sure search results are
+ // coming back as expected.
+ // See also test_inspector-search-front.html.
+
+ addAsyncTest(function* setup() {
+ info ("Setting up inspector and walker actors.");
+
+ let url = document.getElementById("inspectorContent").href;
+
+ yield new Promise(resolve => {
+ attachURL(url, function(err, client, tab, doc) {
+ inspectee = doc;
+ inspector = InspectorFront(client, tab);
+ resolve();
+ });
+ });
+
+ let walkerFront = yield inspector.getWalker();
+ ok(walkerFront, "getWalker() should return an actor.");
+
+ let serverConnection = walkerFront.conn._transport._serverConnection;
+ walkerActor = serverConnection.getActor(walkerFront.actorID);
+ ok(walkerActor,
+ "Got a reference to the walker actor (" + walkerFront.actorID + ")");
+
+ walkerSearch = walkerActor.walkerSearch;
+
+ runNextTest();
+ });
+
+ addAsyncTest(function* testIndexExists() {
+ info ("Testing basic index APIs exist.");
+
+ let index = new WalkerIndex(walkerActor);
+ ok(index.data.size > 0, "public index is filled after getting");
+
+ index.clearIndex();
+ ok(!index._data, "private index is empty after clearing");
+ ok(index.data.size > 0, "public index is filled after getting");
+
+ index.destroy();
+ runNextTest();
+ });
+
+ addAsyncTest(function* testSearchExists() {
+ info ("Testing basic search APIs exist.");
+
+ ok(walkerSearch, "walker search exists on the WalkerActor");
+ ok(walkerSearch.search, "walker search has `search` method");
+ ok(walkerSearch.index, "walker search has `index` property");
+ is(walkerSearch.walker, walkerActor, "referencing the correct WalkerActor");
+
+ let search = new WalkerSearch(walkerActor);
+ ok(search, "a new search instance can be created");
+ ok(search.search, "new search instance has `search` method");
+ ok(search.index, "new search instance has `index` property");
+ isnot(search, walkerSearch, "new search instance differs from the WalkerActor's");
+
+ search.destroy();
+ runNextTest();
+ });
+
+ addAsyncTest(function* testEmptySearch() {
+ info ("Testing search with an empty query.");
+ results = walkerSearch.search("");
+ is(results.length, 0, "No results when searching for ''");
+
+ results = walkerSearch.search(null);
+ is(results.length, 0, "No results when searching for null");
+
+ results = walkerSearch.search(undefined);
+ is(results.length, 0, "No results when searching for undefined");
+
+ results = walkerSearch.search(10);
+ is(results.length, 0, "No results when searching for 10");
+
+ runNextTest();
+ });
+
+ addAsyncTest(function* testBasicSearchData() {
+ let testData = [
+ {
+ desc: "Search for tag with one result.",
+ search: "body",
+ expected: [
+ {node: inspectee.body, type: "tag"}
+ ]
+ },
+ {
+ desc: "Search for tag with multiple results",
+ search: "h2",
+ expected: [
+ {node: inspectee.querySelectorAll("h2")[0], type: "tag"},
+ {node: inspectee.querySelectorAll("h2")[1], type: "tag"},
+ {node: inspectee.querySelectorAll("h2")[2], type: "tag"},
+ ]
+ },
+ {
+ desc: "Search for selector with multiple results",
+ search: "body > h2",
+ expected: [
+ {node: inspectee.querySelectorAll("h2")[0], type: "selector"},
+ {node: inspectee.querySelectorAll("h2")[1], type: "selector"},
+ {node: inspectee.querySelectorAll("h2")[2], type: "selector"},
+ ]
+ },
+ {
+ desc: "Search for selector with multiple results",
+ search: ":root h2",
+ expected: [
+ {node: inspectee.querySelectorAll("h2")[0], type: "selector"},
+ {node: inspectee.querySelectorAll("h2")[1], type: "selector"},
+ {node: inspectee.querySelectorAll("h2")[2], type: "selector"},
+ ]
+ },
+ {
+ desc: "Search for selector with multiple results",
+ search: "* h2",
+ expected: [
+ {node: inspectee.querySelectorAll("h2")[0], type: "selector"},
+ {node: inspectee.querySelectorAll("h2")[1], type: "selector"},
+ {node: inspectee.querySelectorAll("h2")[2], type: "selector"},
+ ]
+ },
+ {
+ desc: "Search with multiple matches in a single tag expecting a single result",
+ search: "💩",
+ expected: [
+ {node: inspectee.getElementById("💩"), type: "attributeName"}
+ ]
+ },
+ {
+ desc: "Search that has tag and text results",
+ search: "h1",
+ expected: [
+ {node: inspectee.querySelector("h1"), type: "tag"},
+ {node: inspectee.querySelector("h1 + p").childNodes[0], type: "text"},
+ {node: inspectee.querySelector("h1 + p > strong").childNodes[0], type: "text"},
+ ]
+ },
+ ]
+
+ for (let {desc, search, expected} of testData) {
+ info("Running test: " + desc);
+ let results = walkerSearch.search(search);
+ isDeeply(results, expected,
+ "Search returns correct results with '" + search + "'");
+ }
+
+ runNextTest();
+ });
+
+ addAsyncTest(function* testPseudoElements() {
+ info ("Testing ::before and ::after element matching");
+
+ let beforeElt = new _documentWalker(inspectee.querySelector("#pseudo"),
+ inspectee.defaultView).firstChild();
+ let afterElt = new _documentWalker(inspectee.querySelector("#pseudo"),
+ inspectee.defaultView).lastChild();
+ let styleText = inspectee.querySelector("style").childNodes[0];
+
+ // ::before
+ let results = walkerSearch.search("::before");
+ isDeeply(results, [ {node: beforeElt, type: "tag"} ],
+ "Tag search works for pseudo element");
+
+ results = walkerSearch.search("_moz_generated_content_before");
+ is(results.length, 0, "No results for anon tag name");
+
+ results = walkerSearch.search("before element");
+ isDeeply(results, [
+ {node: styleText, type: "text"},
+ {node: beforeElt, type: "text"}
+ ], "Text search works for pseudo element");
+
+ // ::after
+ results = walkerSearch.search("::after");
+ isDeeply(results, [ {node: afterElt, type: "tag"} ],
+ "Tag search works for pseudo element");
+
+ results = walkerSearch.search("_moz_generated_content_after");
+ is(results.length, 0, "No results for anon tag name");
+
+ results = walkerSearch.search("after element");
+ isDeeply(results, [
+ {node: styleText, type: "text"},
+ {node: afterElt, type: "text"}
+ ], "Text search works for pseudo element");
+
+ runNextTest();
+ });
+
+ addAsyncTest(function* testSearchMutationChangeResults() {
+ info ("Testing search before and after a mutation.");
+ let expected = [
+ {node: inspectee.querySelectorAll("h3")[0], type: "tag"},
+ {node: inspectee.querySelectorAll("h3")[1], type: "tag"},
+ {node: inspectee.querySelectorAll("h3")[2], type: "tag"},
+ ];
+
+ let results = walkerSearch.search("h3");
+ isDeeply(results, expected, "Search works with tag results");
+
+ yield mutateDocumentAndWaitForMutation(() => {
+ expected[0].node.remove();
+ });
+
+ results = walkerSearch.search("h3");
+ isDeeply(results, [
+ expected[1],
+ expected[2]
+ ], "Results are updated after removal");
+
+ yield new Promise(resolve => {
+ info("Waiting for a mutation to happen");
+ let observer = new inspectee.defaultView.MutationObserver(() => {
+ resolve();
+ });
+ observer.observe(inspectee, {attributes: true, subtree: true});
+ inspectee.body.setAttribute("h3", "true");
+ });
+
+ results = walkerSearch.search("h3");
+ isDeeply(results, [
+ {node: inspectee.body, type: "attributeName"},
+ expected[1],
+ expected[2]
+ ], "Results are updated after addition");
+
+ yield new Promise(resolve => {
+ info("Waiting for a mutation to happen");
+ let observer = new inspectee.defaultView.MutationObserver(() => {
+ resolve();
+ });
+ observer.observe(inspectee, {attributes: true, childList: true, subtree: true});
+ inspectee.body.removeAttribute("h3");
+ expected[1].node.remove();
+ expected[2].node.remove();
+ });
+
+ results = walkerSearch.search("h3");
+ is(results.length, 0, "Results are updated after removal");
+
+ runNextTest();
+ });
+
+ runNextTest();
+
+ function mutateDocumentAndWaitForMutation(mutationFn) {
+ return new Promise(resolve => {
+ info("Listening to markup mutation on the inspectee");
+ let observer = new inspectee.defaultView.MutationObserver(resolve);
+ observer.observe(inspectee, {childList: true, subtree: true});
+ mutationFn();
+ });
+ }
+};
+ </script>
+</head>
+<body>
+<a id="inspectorContent" target="_blank" href="inspector-search-data.html">Test Document</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
--- a/devtools/server/tests/unit/test_getRuleText.js
+++ b/devtools/server/tests/unit/test_getRuleText.js
@@ -95,16 +95,23 @@ const TEST_DATA = [
},
{
desc: "Content string containing a } character",
input: " #id{border:1px solid red;content: '}';color:red;}",
line: 1,
column: 4,
expected: {offset: 7, text: "border:1px solid red;content: '}';color:red;"}
},
+ {
+ desc: "Rule contains no tokens",
+ input: "div{}",
+ line: 1,
+ column: 1,
+ expected: {offset: 4, text: ""}
+ },
];
function run_test() {
for (let test of TEST_DATA) {
do_print("Starting test: " + test.desc);
do_print("Input string " + test.input);
let output;
try {
--- a/devtools/shared/heapsnapshot/DeserializedNode.cpp
+++ b/devtools/shared/heapsnapshot/DeserializedNode.cpp
@@ -70,55 +70,41 @@ Concrete<DeserializedNode>::typeName() c
Node::Size
Concrete<DeserializedNode>::size(mozilla::MallocSizeOf mallocSizeof) const
{
return get().size;
}
class DeserializedEdgeRange : public EdgeRange
{
- EdgeVector edges;
- size_t i;
+ DeserializedNode* node;
+ Edge currentEdge;
+ size_t i;
void settle() {
- front_ = i < edges.length() ? &edges[i] : nullptr;
+ if (i >= node->edges.length()) {
+ front_ = nullptr;
+ return;
+ }
+
+ auto& edge = node->edges[i];
+ auto referent = node->getEdgeReferent(edge);
+ currentEdge = mozilla::Move(Edge(edge.name ? NS_strdup(edge.name) : nullptr,
+ referent));
+ front_ = ¤tEdge;
}
public:
- explicit DeserializedEdgeRange()
- : edges()
+ explicit DeserializedEdgeRange(DeserializedNode& node)
+ : node(&node)
, i(0)
{
settle();
}
- bool init(DeserializedNode& node)
- {
- if (!edges.reserve(node.edges.length()))
- return false;
-
- for (DeserializedEdge* edgep = node.edges.begin();
- edgep != node.edges.end();
- edgep++)
- {
- char16_t* name = nullptr;
- if (edgep->name) {
- name = NS_strdup(edgep->name);
- if (!name)
- return false;
- }
-
- auto referent = node.getEdgeReferent(*edgep);
- edges.infallibleAppend(mozilla::Move(Edge(name, referent)));
- }
-
- settle();
- return true;
- }
-
void popFront() override
{
i++;
settle();
}
};
StackFrame
@@ -133,19 +119,19 @@ Concrete<DeserializedNode>::allocationSt
return JS::ubi::StackFrame(const_cast<DeserializedStackFrame*>(&*ptr));
}
UniquePtr<EdgeRange>
Concrete<DeserializedNode>::edges(JSRuntime* rt, bool) const
{
UniquePtr<DeserializedEdgeRange, JS::DeletePolicy<DeserializedEdgeRange>> range(
- js_new<DeserializedEdgeRange>());
+ js_new<DeserializedEdgeRange>(get()));
- if (!range || !range->init(get()))
+ if (!range)
return nullptr;
return UniquePtr<EdgeRange>(range.release());
}
StackFrame
ConcreteStackFrame<DeserializedStackFrame>::parent() const
{
--- a/devtools/shared/heapsnapshot/tests/gtest/DeserializedNodeUbiNodes.cpp
+++ b/devtools/shared/heapsnapshot/tests/gtest/DeserializedNodeUbiNodes.cpp
@@ -63,38 +63,38 @@ DEF_TEST(DeserializedNodeUbiNodes, {
// Test the ubi::Node's edges.
UniquePtr<DeserializedNode> referent1(new MockDeserializedNode(1,
nullptr,
10));
DeserializedEdge edge1(referent1->id);
mocked.addEdge(Move(edge1));
- EXPECT_CALL(mocked,
- getEdgeReferent(Field(&DeserializedEdge::referent,
- referent1->id)))
+ EXPECT_CALL(mocked, getEdgeReferent(EdgeTo(referent1->id)))
.Times(1)
.WillOnce(Return(JS::ubi::Node(referent1.get())));
UniquePtr<DeserializedNode> referent2(new MockDeserializedNode(2,
nullptr,
20));
DeserializedEdge edge2(referent2->id);
mocked.addEdge(Move(edge2));
- EXPECT_CALL(mocked,
- getEdgeReferent(Field(&DeserializedEdge::referent,
- referent2->id)))
+ EXPECT_CALL(mocked, getEdgeReferent(EdgeTo(referent2->id)))
.Times(1)
.WillOnce(Return(JS::ubi::Node(referent2.get())));
UniquePtr<DeserializedNode> referent3(new MockDeserializedNode(3,
nullptr,
30));
DeserializedEdge edge3(referent3->id);
mocked.addEdge(Move(edge3));
- EXPECT_CALL(mocked,
- getEdgeReferent(Field(&DeserializedEdge::referent,
- referent3->id)))
+ EXPECT_CALL(mocked, getEdgeReferent(EdgeTo(referent3->id)))
.Times(1)
.WillOnce(Return(JS::ubi::Node(referent3.get())));
- ubi.edges(rt);
+ auto range = ubi.edges(rt);
+ ASSERT_TRUE(!!range);
+
+ for ( ; !range->empty(); range->popFront()) {
+ // Nothing to do here. This loop ensures that we get each edge referent
+ // that we expect above.
+ }
});
--- a/devtools/shared/heapsnapshot/tests/gtest/DevTools.h
+++ b/devtools/shared/heapsnapshot/tests/gtest/DevTools.h
@@ -273,16 +273,22 @@ MATCHER_P(UTF16StrEq, str, "") {
MATCHER_P(UniqueUTF16StrEq, str, "") {
return NS_strcmp(arg.get(), str) == 0;
}
MATCHER(UniqueIsNull, "") {
return arg.get() == nullptr;
}
+// Matches an edge whose referent is the node with the given id.
+MATCHER_P(EdgeTo, id, "") {
+ return Matcher<const DeserializedEdge&>(Field(&DeserializedEdge::referent, id))
+ .MatchAndExplain(arg, result_listener);
+}
+
} // namespace testing
// A mock `Writer` class to be used with testing `WriteHeapGraph`.
class MockWriter : public CoreDumpWriter
{
public:
virtual ~MockWriter() override { }
--- a/dom/interfaces/security/nsIContentSecurityManager.idl
+++ b/dom/interfaces/security/nsIContentSecurityManager.idl
@@ -41,14 +41,14 @@ interface nsIContentSecurityManager : ns
nsIStreamListener performSecurityCheck(in nsIChannel aChannel,
in nsIStreamListener aStreamListener);
/**
* Implementation of
* https://w3c.github.io/webappsec-secure-contexts/#is-origin-trustworthy
*
* This method should only be used when the context of the URI isn't available
- * since isSecureContext is preffered as it handles parent contexts.
+ * since isSecureContext is preferred as it handles parent contexts.
*
* This method returns false instead of throwing upon errors.
*/
boolean isURIPotentiallyTrustworthy(in nsIURI aURI);
};
--- a/dom/media/mediasource/MediaSourceDecoder.cpp
+++ b/dom/media/mediasource/MediaSourceDecoder.cpp
@@ -80,17 +80,18 @@ MediaSourceDecoder::GetSeekable()
media::TimeIntervals seekable;
double duration = mMediaSource->Duration();
if (IsNaN(duration)) {
// Return empty range.
} else if (duration > 0 && mozilla::IsInfinite(duration)) {
media::TimeIntervals buffered = GetBuffered();
if (buffered.Length()) {
- seekable += media::TimeInterval(buffered.GetStart(), buffered.GetEnd());
+ seekable +=
+ media::TimeInterval(media::TimeUnit::FromSeconds(0), buffered.GetEnd());
}
} else {
seekable += media::TimeInterval(media::TimeUnit::FromSeconds(0),
media::TimeUnit::FromSeconds(duration));
}
MSE_DEBUG("ranges=%s", DumpTimeRanges(seekable).get());
return seekable;
}
--- a/dom/media/webaudio/AudioContext.cpp
+++ b/dom/media/webaudio/AudioContext.cpp
@@ -522,18 +522,17 @@ already_AddRefed<PeriodicWave>
AudioContext::CreatePeriodicWave(const Float32Array& aRealData,
const Float32Array& aImagData,
ErrorResult& aRv)
{
aRealData.ComputeLengthAndData();
aImagData.ComputeLengthAndData();
if (aRealData.Length() != aImagData.Length() ||
- aRealData.Length() == 0 ||
- aRealData.Length() > 4096) {
+ aRealData.Length() == 0) {
aRv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
return nullptr;
}
RefPtr<PeriodicWave> periodicWave =
new PeriodicWave(this, aRealData.Data(), aImagData.Data(),
aImagData.Length(), aRv);
if (aRv.Failed()) {
--- a/dom/media/webaudio/PeriodicWave.cpp
+++ b/dom/media/webaudio/PeriodicWave.cpp
@@ -22,17 +22,16 @@ PeriodicWave::PeriodicWave(AudioContext*
const uint32_t aLength,
ErrorResult& aRv)
: mContext(aContext)
{
MOZ_ASSERT(aContext);
// Caller should have checked this and thrown.
MOZ_ASSERT(aLength > 0);
- MOZ_ASSERT(aLength <= 4096);
mLength = aLength;
// Copy coefficient data. The two arrays share an allocation.
mCoefficients = new ThreadSharedFloatArrayBufferList(2);
float* buffer = static_cast<float*>(malloc(aLength*sizeof(float)*2));
if (buffer == nullptr) {
aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
return;
--- a/dom/media/webaudio/blink/PeriodicWave.cpp
+++ b/dom/media/webaudio/blink/PeriodicWave.cpp
@@ -26,86 +26,92 @@
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "PeriodicWave.h"
#include <algorithm>
#include <cmath>
#include "mozilla/FFTBlock.h"
-const unsigned PeriodicWaveSize = 4096; // This must be a power of two.
-const unsigned NumberOfRanges = 36; // There should be 3 * log2(PeriodicWaveSize) 1/3 octave ranges.
+const unsigned MinPeriodicWaveSize = 4096; // This must be a power of two.
+const unsigned MaxPeriodicWaveSize = 8192; // This must be a power of two.
const float CentsPerRange = 1200 / 3; // 1/3 Octave.
using namespace mozilla;
using mozilla::dom::OscillatorType;
namespace WebCore {
already_AddRefed<PeriodicWave>
PeriodicWave::create(float sampleRate,
const float* real,
const float* imag,
size_t numberOfComponents)
{
- bool isGood = real && imag && numberOfComponents > 0 &&
- numberOfComponents <= PeriodicWaveSize;
+ bool isGood = real && imag && numberOfComponents > 0;
MOZ_ASSERT(isGood);
if (isGood) {
RefPtr<PeriodicWave> periodicWave =
- new PeriodicWave(sampleRate);
+ new PeriodicWave(sampleRate, numberOfComponents);
periodicWave->createBandLimitedTables(real, imag, numberOfComponents);
return periodicWave.forget();
}
return nullptr;
}
already_AddRefed<PeriodicWave>
PeriodicWave::createSine(float sampleRate)
{
RefPtr<PeriodicWave> periodicWave =
- new PeriodicWave(sampleRate);
+ new PeriodicWave(sampleRate, MinPeriodicWaveSize);
periodicWave->generateBasicWaveform(OscillatorType::Sine);
return periodicWave.forget();
}
already_AddRefed<PeriodicWave>
PeriodicWave::createSquare(float sampleRate)
{
RefPtr<PeriodicWave> periodicWave =
- new PeriodicWave(sampleRate);
+ new PeriodicWave(sampleRate, MinPeriodicWaveSize);
periodicWave->generateBasicWaveform(OscillatorType::Square);
return periodicWave.forget();
}
already_AddRefed<PeriodicWave>
PeriodicWave::createSawtooth(float sampleRate)
{
RefPtr<PeriodicWave> periodicWave =
- new PeriodicWave(sampleRate);
+ new PeriodicWave(sampleRate, MinPeriodicWaveSize);
periodicWave->generateBasicWaveform(OscillatorType::Sawtooth);
return periodicWave.forget();
}
already_AddRefed<PeriodicWave>
PeriodicWave::createTriangle(float sampleRate)
{
RefPtr<PeriodicWave> periodicWave =
- new PeriodicWave(sampleRate);
+ new PeriodicWave(sampleRate, MinPeriodicWaveSize);
periodicWave->generateBasicWaveform(OscillatorType::Triangle);
return periodicWave.forget();
}
-PeriodicWave::PeriodicWave(float sampleRate)
+PeriodicWave::PeriodicWave(float sampleRate, size_t numberOfComponents)
: m_sampleRate(sampleRate)
- , m_periodicWaveSize(PeriodicWaveSize)
- , m_numberOfRanges(NumberOfRanges)
, m_centsPerRange(CentsPerRange)
{
float nyquist = 0.5 * m_sampleRate;
+
+ if (numberOfComponents <= MinPeriodicWaveSize) {
+ m_periodicWaveSize = MinPeriodicWaveSize;
+ } else {
+ unsigned npow2 = powf(2.0f, floorf(logf(numberOfComponents - 1.0)/logf(2.0f) + 1.0f));
+ m_periodicWaveSize = std::min(MaxPeriodicWaveSize, npow2);
+ }
+
+ m_numberOfRanges = (unsigned)(3.0f*logf(m_periodicWaveSize)/logf(2.0f));
m_lowestFundamentalFrequency = nyquist / maxNumberOfPartials();
m_rateScale = m_periodicWaveSize / m_sampleRate;
}
size_t PeriodicWave::sizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const
{
size_t amount = aMallocSizeOf(this);
--- a/dom/media/webaudio/blink/PeriodicWave.h
+++ b/dom/media/webaudio/blink/PeriodicWave.h
@@ -71,17 +71,17 @@ public:
float rateScale() const { return m_rateScale; }
unsigned periodicWaveSize() const { return m_periodicWaveSize; }
float sampleRate() const { return m_sampleRate; }
size_t sizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
private:
- explicit PeriodicWave(float sampleRate);
+ explicit PeriodicWave(float sampleRate, size_t numberOfComponents);
~PeriodicWave() {}
void generateBasicWaveform(mozilla::dom::OscillatorType);
float m_sampleRate;
unsigned m_periodicWaveSize;
unsigned m_numberOfRanges;
float m_centsPerRange;
--- a/dom/media/webaudio/test/test_periodicWave.html
+++ b/dom/media/webaudio/test/test_periodicWave.html
@@ -32,19 +32,19 @@ addLoadEvent(function() {
var ac = new AudioContext();
ac.createPeriodicWave(new Float32Array(4096), new Float32Array(4096));
expectException(function() {
ac.createPeriodicWave(new Float32Array(512), imag);
}, DOMException.NOT_SUPPORTED_ERR);
expectException(function() {
ac.createPeriodicWave(new Float32Array(0), new Float32Array(0));
}, DOMException.NOT_SUPPORTED_ERR);
- expectException(function() {
+ expectNoException(function() {
ac.createPeriodicWave(new Float32Array(4097), new Float32Array(4097));
- }, DOMException.NOT_SUPPORTED_ERR);
+ });
runTest();
});
var gTest = {
createGraph: function(context) {
var merger = context.createChannelMerger();
--- a/dom/media/webrtc/MediaEngineDefault.h
+++ b/dom/media/webrtc/MediaEngineDefault.h
@@ -131,17 +131,18 @@ public:
bool aNoiseOn, uint32_t aNoise,
int32_t aPlayoutDelay) override { return NS_OK; };
void AppendToSegment(AudioSegment& aSegment, TrackTicks aSamples);
virtual void NotifyPull(MediaStreamGraph* aGraph,
SourceMediaStream *aSource,
TrackID aId,
StreamTime aDesiredTime) override
{
- NS_WARN_IF_FALSE(aDesiredTime <= aSource->GetEndOfAppendedData(aId),
+ NS_WARN_IF_FALSE(!aSource->FindTrack(aId) ||
+ aDesiredTime <= aSource->GetEndOfAppendedData(aId),
"MediaEngineDefaultAudioSource data underrun");
}
virtual bool IsFake() override {
return true;
}
virtual const dom::MediaSourceEnum GetMediaSource() override {
--- a/dom/tests/browser/browser.ini
+++ b/dom/tests/browser/browser.ini
@@ -3,16 +3,19 @@ skip-if = e10s # Bug ?????? - most of th
support-files =
browser_frame_elements.html
page_privatestorageevent.html
position.html
test-console-api.html
test_bug1004814.html
worker_bug1004814.js
+[browser_test_toolbars_visibility.js]
+support-files =
+ test_new_window_from_content_child.html
[browser_bug1008941_dismissGeolocationHanger.js]
skip-if = buildapp == 'mulet'
[browser_test__content.js]
[browser_ConsoleAPITests.js]
[browser_ConsoleStorageAPITests.js]
[browser_ConsoleStoragePBTest_perwindowpb.js]
[browser_autofocus_background.js]
skip-if= buildapp == 'mulet'
new file mode 100644
--- /dev/null
+++ b/dom/tests/browser/browser_test_toolbars_visibility.js
@@ -0,0 +1,187 @@
+// Tests that toolbars have proper visibility when opening a new window
+// in either content or chrome context.
+
+const CONTENT_PAGE = "http://www.example.com/browser/dom/tests/browser/test_new_window_from_content_child.html";
+
+/**
+ * This function retrieves the visibility state of the toolbars of a
+ * window within the content context.
+ *
+ * @param aBrowser (<xul:browser>)
+ * The browser to query for toolbar visibility states
+ * @returns Promise
+ * A promise that resolves when the toolbar state is retrieved
+ * within the content context, which value is an object that holds
+ * the visibility state of the toolbars
+ */
+function getToolbarsFromBrowserContent(aBrowser) {
+ return ContentTask.spawn(aBrowser, {}, function*() {
+ return {
+ toolbar: content.toolbar.visible,
+ menubar: content.menubar.visible,
+ personalbar: content.personalbar.visible,
+ statusbar: content.statusbar.visible,
+ locationbar: content.locationbar.visible,
+ };
+ });
+}
+
+/**
+ * This function retrieves the visibility state of the toolbars of a
+ * window within the chrome context.
+ *
+ * @param win
+ * the chrome privileged window
+ * @returns object
+ * an object that holds the visibility state of the toolbars
+ */
+function getToolbarsFromWindowChrome(win) {
+ return {
+ toolbar: win.toolbar.visible,
+ menubar: win.menubar.visible,
+ personalbar: win.personalbar.visible,
+ statusbar: win.statusbar.visible,
+ locationbar: win.locationbar.visible,
+ }
+}
+
+/**
+ * Tests toolbar visibility when opening a window with default parameters.
+ *
+ * @param toolbars
+ * the visibility state of the toolbar elements
+ */
+function testDefaultToolbars(toolbars) {
+ ok(toolbars.locationbar,
+ "locationbar should be visible on default window.open()");
+ ok(toolbars.menubar,
+ "menubar be visible on default window.open()");
+ ok(toolbars.personalbar,
+ "personalbar should be visible on default window.open()");
+ ok(toolbars.statusbar,
+ "statusbar should be visible on default window.open()");
+ ok(toolbars.toolbar,
+ "toolbar should be visible on default window.open()");
+}
+
+/**
+ * Tests toolbar visibility when opening a window with non default parameters
+ * on the content context.
+ *
+ * Ensure that locationbar can't be hidden in the content context, see bug#337344.
+ *
+ * @param toolbars
+ * the visibility state of the toolbar elements
+ */
+function testNonDefaultContentToolbars(toolbars) {
+ // Locationbar should always be visible on content context
+ ok(toolbars.locationbar,
+ "locationbar should be visible even with location=no");
+ ok(!toolbars.menubar,
+ "menubar shouldn't be visible when menubar=no");
+ ok(!toolbars.personalbar,
+ "personalbar shouldn't be visible when personalbar=no");
+ // statusbar will report visible=true even when it's hidden because of bug#55820
+ todo(!toolbars.statusbar,
+ "statusbar shouldn't be visible when status=no");
+ ok(!toolbars.toolbar,
+ "toolbar shouldn't be visible when toolbar=no");
+}
+
+/**
+ * Tests toolbar visibility when opening a window with non default parameters
+ * on the chrome context.
+ *
+ * @param toolbars
+ * the visibility state of the toolbar elements
+ */
+function testNonDefaultChromeToolbars(toolbars) {
+ // None of the toolbars should be visible if hidden with chrome privileges
+ ok(!toolbars.locationbar,
+ "locationbar should be visible on default window.open()");
+ ok(!toolbars.menubar,
+ "menubar be visible on default window.open()");
+ ok(!toolbars.personalbar,
+ "personalbar should be visible on default window.open()");
+ ok(!toolbars.statusbar,
+ "statusbar should be visible on default window.open()");
+ ok(!toolbars.toolbar,
+ "toolbar should be visible on default window.open()");
+}
+
+/**
+ * Ensure that toolbars of a window opened in the content context have the
+ * correct visibility.
+ *
+ * A window opened with default parameters should have all toolbars visible.
+ *
+ * A window opened with "location=no, personalbar=no, toolbar=no, scrollbars=no,
+ * menubar=no, status=no", should only have location visible.
+ */
+add_task(function*() {
+ yield BrowserTestUtils.withNewTab({
+ gBrowser,
+ url: CONTENT_PAGE,
+ }, function*(browser) {
+ // First, call the default window.open() which will open a new tab
+ let newTabPromise = BrowserTestUtils.waitForNewTab(gBrowser);
+ yield BrowserTestUtils.synthesizeMouseAtCenter("#winOpenDefault", {}, browser);
+ let tab = yield newTabPromise;
+
+ // Check that all toolbars are visible
+ let toolbars = yield getToolbarsFromBrowserContent(gBrowser.selectedBrowser);
+ testDefaultToolbars(toolbars);
+
+ // Cleanup
+ yield BrowserTestUtils.removeTab(tab);
+
+ // Now let's open a window with toolbars hidden
+ let winPromise = BrowserTestUtils.waitForNewWindow();
+ yield BrowserTestUtils.synthesizeMouseAtCenter("#winOpenNonDefault", {}, browser);
+ let popupWindow = yield winPromise;
+
+ let popupBrowser = popupWindow.gBrowser.selectedBrowser;
+ yield BrowserTestUtils.browserLoaded(popupBrowser);
+
+ // Test toolbars visibility
+ let popupToolbars = yield getToolbarsFromBrowserContent(popupBrowser);
+ testNonDefaultContentToolbars(popupToolbars);
+
+ // Cleanup
+ yield BrowserTestUtils.closeWindow(popupWindow);
+ });
+});
+
+/**
+ * Ensure that toolbars of a window opened in the chrome context have the
+ * correct visibility.
+ *
+ * A window opened with default parameters should have all toolbars visible.
+ *
+ * A window opened with "location=no, personalbar=no, toolbar=no, scrollbars=no,
+ * menubar=no, status=no", should not have any toolbar visible.
+ */
+add_task(function* () {
+ // First open a default window from this chrome context
+ let defaultWindowPromise = BrowserTestUtils.waitForNewWindow();
+ window.open("about:robots", "_blank");
+ let defaultWindow = yield defaultWindowPromise;
+
+ // Check that all toolbars are visible
+ let toolbars = getToolbarsFromWindowChrome(defaultWindow);
+ testDefaultToolbars(toolbars);
+
+ // Now lets open a window with toolbars hidden from this chrome context
+ let features = "location=no, personalbar=no, toolbar=no, scrollbars=no, menubar=no, status=no";
+ let popupWindowPromise = BrowserTestUtils.waitForNewWindow();
+ window.open("about:robots", "_blank", features);
+ let popupWindow = yield popupWindowPromise;
+
+ // Test none of the tooolbars are visible
+ let hiddenToolbars = getToolbarsFromWindowChrome(popupWindow);
+ testNonDefaultChromeToolbars(hiddenToolbars);
+
+ // Cleanup
+ yield BrowserTestUtils.closeWindow(defaultWindow);
+ yield BrowserTestUtils.closeWindow(popupWindow);
+});
--- a/dom/tests/browser/test_new_window_from_content_child.html
+++ b/dom/tests/browser/test_new_window_from_content_child.html
@@ -1,19 +1,19 @@
<!DOCTYPE html>
<head>
<meta charset="utf-8">
<title>Test popup window opening behaviour</title>
</head>
<body>
<p><a id="winOpenDefault" href="#" onclick="return openWindow();">Open a new window via window.open with default features.</a></p>
- <p><a id="winOpenNonDefault" href="#" onclick="return openWindow('resizable=no, toolbar=no, scrollbars=no, menubar=no, status=no, directories=no, height=100, width=500');">Open a new window via window.open with non-default features.</a></p>
+ <p><a id="winOpenNonDefault" href="#" onclick="return openWindow('resizable=no, location=no, personalbar=no, toolbar=no, scrollbars=no, menubar=no, status=no, directories=no, height=100, width=500');">Open a new window via window.open with non-default features.</a></p>
<p><a id="winOpenDialog" href="#" onclick="return openWindow('dialog=yes');">Open a new window via window.open with dialog=1.</a></p>
<p><a id="targetBlank" href="about:robots" target="_blank">Open a new window via target="_blank".</a></p>
</body>
</html>
<script>
function openWindow(aFeatures="") {
window.open("about:robots", "_blank", aFeatures);
return false;
}
-</script>
\ No newline at end of file
+</script>
--- a/dom/workers/ServiceWorkerManager.cpp
+++ b/dom/workers/ServiceWorkerManager.cpp
@@ -1521,17 +1521,17 @@ ServiceWorkerManager::Register(nsIDOMWin
nsCString cleanedScope;
rv = aScopeURI->GetSpecIgnoringRef(cleanedScope);
if (NS_WARN_IF(NS_FAILED(rv))) {
return NS_ERROR_FAILURE;
}
nsAutoCString spec;
- rv = aScriptURI->GetSpec(spec);
+ rv = aScriptURI->GetSpecIgnoringRef(spec);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
nsCOMPtr<nsIGlobalObject> sgo = do_QueryInterface(window);
ErrorResult result;
RefPtr<Promise> promise = Promise::Create(sgo, result);
if (result.Failed()) {
--- a/gfx/2d/Logging.h
+++ b/gfx/2d/Logging.h
@@ -129,17 +129,22 @@ private:
/// we will still send a printf.
// The range is due to the values set in Histograms.json
enum class LogReason : int {
MustBeMoreThanThis = -1,
// Start. Do not insert, always add at end. If you remove items,
// make sure the other items retain their values.
D3D11InvalidCallDeviceRemoved = 0,
- D3D11InvalidCall = 1,
+ D3D11InvalidCall,
+ D3DLockTimeout,
+ D3D10FinalizeFrame,
+ D3D11FinalizeFrame,
+ D3D10SyncLock,
+ D3D11SyncLock,
// End
MustBeLessThanThis = 101,
};
struct BasicLogger
{
// For efficiency, this method exists and copies the logic of the
// OutputMessage below. If making any changes here, also make it
@@ -560,17 +565,17 @@ typedef Log<LOG_CRITICAL, CriticalLogger
#define gfxWarningOnce static gfxWarning GFX_LOGGING_GLUE(sOnceAtLine,__LINE__) = gfxWarning
// In the debug build, this is equivalent to the default gfxCriticalError.
// In the non-debug build, on nightly and dev edition, it will MOZ_CRASH.
// On beta and release versions, it will telemetry count, but proceed.
//
// You should create a (new) enum in the LogReason and use it for the reason
// parameter to ensure uniqueness.
-#define gfxDevCrash(reason) gfxCriticalError(int(LogOptions::AutoPrefix) | int(LogOptions::AssertOnCall) | int(LogOptions::CrashAction), (reason))
+#define gfxDevCrash(reason) gfxCriticalError(int(gfx::LogOptions::AutoPrefix) | int(gfx::LogOptions::AssertOnCall) | int(gfx::LogOptions::CrashAction), (reason))
// See nsDebug.h and the NS_WARN_IF macro
#ifdef __cplusplus
// For now, have MOZ2D_ERROR_IF available in debug and non-debug builds
inline bool MOZ2D_error_if_impl(bool aCondition, const char* aExpr,
const char* aFile, int32_t aLine)
{
--- a/gfx/layers/ImageContainer.cpp
+++ b/gfx/layers/ImageContainer.cpp
@@ -645,22 +645,21 @@ CairoImage::GetTextureClient(Compositabl
surface->GetSize(),
BackendSelector::Content,
TextureFlags::DEFAULT);
}
if (!textureClient) {
return nullptr;
}
- if (!textureClient->Lock(OpenMode::OPEN_WRITE_ONLY)) {
+ TextureClientAutoLock autoLock(textureClient, OpenMode::OPEN_WRITE_ONLY);
+ if (!autoLock.Succeeded()) {
return nullptr;
}
- TextureClientAutoUnlock autoUnlock(textureClient);
-
textureClient->UpdateFromSurface(surface);
textureClient->SyncWithObject(forwarder->GetSyncObject());
mTextureClients.Put(forwarder->GetSerial(), textureClient);
return textureClient;
}
--- a/gfx/layers/client/CanvasClient.cpp
+++ b/gfx/layers/client/CanvasClient.cpp
@@ -93,32 +93,30 @@ CanvasClient2D::Update(gfx::IntSize aSiz
NS_WARNING("Failed to allocate the TextureClient");
return;
}
MOZ_ASSERT(mBuffer->CanExposeDrawTarget());
bufferCreated = true;
}
- if (!mBuffer->Lock(OpenMode::OPEN_WRITE_ONLY)) {
- mBuffer = nullptr;
- return;
- }
-
bool updated = false;
{
- // Restrict drawTarget to a scope so that terminates before Unlock.
- RefPtr<DrawTarget> target =
- mBuffer->BorrowDrawTarget();
+ TextureClientAutoLock autoLock(mBuffer, OpenMode::OPEN_WRITE_ONLY);
+ if (!autoLock.Succeeded()) {
+ mBuffer = nullptr;
+ return;
+ }
+
+ RefPtr<DrawTarget> target = mBuffer->BorrowDrawTarget();
if (target) {
aLayer->UpdateTarget(target);
updated = true;
}
}
- mBuffer->Unlock();
if (bufferCreated && !AddTextureClient(mBuffer)) {
mBuffer = nullptr;
return;
}
if (updated) {
nsAutoTArray<CompositableForwarder::TimedTextureClient,1> textures;
@@ -281,17 +279,19 @@ TexClientFromReadback(SharedSurface* src
MOZ_CRASH("Bad `read{Format,Type}`.");
}
MOZ_ASSERT(texClient);
if (!texClient)
return nullptr;
// With a texClient, we can lock for writing.
- MOZ_ALWAYS_TRUE( texClient->Lock(OpenMode::OPEN_WRITE) );
+ TextureClientAutoLock autoLock(texClient, OpenMode::OPEN_WRITE);
+ DebugOnly<bool> succeeded = autoLock.Succeeded();
+ MOZ_ASSERT(succeeded, "texture should have locked");
uint8_t* lockedBytes = texClient->GetLockedData();
// ReadPixels from the current FB into lockedBits.
auto width = src->mSize.width;
auto height = src->mSize.height;
{
@@ -313,18 +313,16 @@ TexClientFromReadback(SharedSurface* src
uint8_t* itr = lockedBytes;
for (size_t i = 0; i < pixels; i++) {
SwapRB_R8G8B8A8(itr);
itr += 4;
}
texClient->RemoveFlags(TextureFlags::RB_SWAPPED);
}
-
- texClient->Unlock();
}
return texClient.forget();
}
////////////////////////////////////////
static already_AddRefed<SharedSurfaceTextureClient>
--- a/gfx/layers/client/ContentClient.cpp
+++ b/gfx/layers/client/ContentClient.cpp
@@ -9,16 +9,17 @@
#include "gfxPlatform.h" // for gfxPlatform
#include "gfxEnv.h" // for gfxEnv
#include "gfxPrefs.h" // for gfxPrefs
#include "gfxPoint.h" // for IntSize, gfxPoint
#include "gfxTeeSurface.h" // for gfxTeeSurface
#include "gfxUtils.h" // for gfxUtils
#include "ipc/ShadowLayers.h" // for ShadowLayerForwarder
#include "mozilla/ArrayUtils.h" // for ArrayLength
+#include "mozilla/Maybe.h"
#include "mozilla/gfx/2D.h" // for DrawTarget, Factory
#include "mozilla/gfx/BasePoint.h" // for BasePoint
#include "mozilla/gfx/BaseSize.h" // for BaseSize
#include "mozilla/gfx/Rect.h" // for Rect
#include "mozilla/gfx/Types.h"
#include "mozilla/layers/CompositorChild.h" // for CompositorChild
#include "mozilla/layers/LayerManagerComposite.h"
#include "mozilla/layers/LayersMessages.h" // for ThebesBufferData
@@ -565,43 +566,40 @@ ContentClientDoubleBuffered::FinalizeFra
// nothing to sync at all, there is nothing to do and we can go home early.
updateRegion.Sub(updateRegion, aRegionToDraw);
if (updateRegion.IsEmpty()) {
return;
}
// We need to ensure that we lock these two buffers in the same
// order as the compositor to prevent deadlocks.
- if (!mFrontClient->Lock(OpenMode::OPEN_READ_ONLY)) {
- return;
- }
- if (mFrontClientOnWhite &&
- !mFrontClientOnWhite->Lock(OpenMode::OPEN_READ_ONLY)) {
- mFrontClient->Unlock();
+ TextureClientAutoLock frontLock(mFrontClient, OpenMode::OPEN_READ_ONLY);
+ if (!frontLock.Succeeded()) {
return;
}
- {
- // Restrict the DrawTargets and frontBuffer to a scope to make
- // sure there is no more external references to the DrawTargets
- // when we Unlock the TextureClients.
- RefPtr<SourceSurface> surf = mFrontClient->BorrowDrawTarget()->Snapshot();
- RefPtr<SourceSurface> surfOnWhite = mFrontClientOnWhite
- ? mFrontClientOnWhite->BorrowDrawTarget()->Snapshot()
- : nullptr;
- SourceRotatedBuffer frontBuffer(surf,
- surfOnWhite,
- mFrontBufferRect,
- mFrontBufferRotation);
- UpdateDestinationFrom(frontBuffer, updateRegion);
+ Maybe<TextureClientAutoLock> frontOnWhiteLock;
+ if (mFrontClientOnWhite) {
+ frontOnWhiteLock.emplace(mFrontClientOnWhite, OpenMode::OPEN_READ_ONLY);
+ if (!frontOnWhiteLock->Succeeded()) {
+ return;
+ }
}
- mFrontClient->Unlock();
- if (mFrontClientOnWhite) {
- mFrontClientOnWhite->Unlock();
- }
+ // Restrict the DrawTargets and frontBuffer to a scope to make
+ // sure there is no more external references to the DrawTargets
+ // when we Unlock the TextureClients.
+ RefPtr<SourceSurface> surf = mFrontClient->BorrowDrawTarget()->Snapshot();
+ RefPtr<SourceSurface> surfOnWhite = mFrontClientOnWhite
+ ? mFrontClientOnWhite->BorrowDrawTarget()->Snapshot()
+ : nullptr;
+ SourceRotatedBuffer frontBuffer(surf,
+ surfOnWhite,
+ mFrontBufferRect,
+ mFrontBufferRotation);
+ UpdateDestinationFrom(frontBuffer, updateRegion);
}
void
ContentClientDoubleBuffered::EnsureBackBufferIfFrontBuffer()
{
if (!mTextureClient && mFrontClient) {
CreateBackBuffer(mFrontBufferRect);
--- a/gfx/layers/client/ImageClient.cpp
+++ b/gfx/layers/client/ImageClient.cpp
@@ -180,27 +180,30 @@ ImageClientSingle::UpdateImage(ImageCont
const PlanarYCbCrData* data = ycbcr->GetData();
if (!data) {
return false;
}
texture = TextureClient::CreateForYCbCr(GetForwarder(),
data->mYSize, data->mCbCrSize, data->mStereoMode,
TextureFlags::DEFAULT | mTextureFlags
);
- if (!texture || !texture->Lock(OpenMode::OPEN_WRITE_ONLY)) {
+ if (!texture) {
return false;
}
+
+ TextureClientAutoLock autoLock(texture, OpenMode::OPEN_WRITE_ONLY);
+ if (!autoLock.Succeeded()) {
+ return false;
+ }
+
bool status = texture->AsTextureClientYCbCr()->UpdateYCbCr(*data);
MOZ_ASSERT(status);
-
- texture->Unlock();
if (!status) {
return false;
}
-
} else if (image->GetFormat() == ImageFormat::SURFACE_TEXTURE ||
image->GetFormat() == ImageFormat::EGLIMAGE) {
gfx::IntSize size = image->GetSize();
if (image->GetFormat() == ImageFormat::EGLIMAGE) {
EGLImageImage* typedImage = static_cast<EGLImageImage*>(image);
texture = new EGLImageTextureClient(GetForwarder(),
mTextureFlags,
--- a/gfx/layers/client/TextureClient.h
+++ b/gfx/layers/client/TextureClient.h
@@ -7,16 +7,17 @@
#define MOZILLA_GFX_TEXTURECLIENT_H
#include <stddef.h> // for size_t
#include <stdint.h> // for uint32_t, uint8_t, uint64_t
#include "GLTextureImage.h" // for TextureImage
#include "ImageTypes.h" // for StereoMode
#include "mozilla/Assertions.h" // for MOZ_ASSERT, etc
#include "mozilla/Attributes.h" // for override
+#include "mozilla/DebugOnly.h"
#include "mozilla/RefPtr.h" // for RefPtr, RefCounted
#include "mozilla/gfx/2D.h" // for DrawTarget
#include "mozilla/gfx/Point.h" // for IntSize
#include "mozilla/gfx/Types.h" // for SurfaceFormat
#include "mozilla/layers/FenceUtils.h" // for FenceHandle
#include "mozilla/ipc/Shmem.h" // for Shmem
#include "mozilla/layers/AtomicRefCountedWithFinalize.h"
#include "mozilla/layers/CompositorTypes.h" // for TextureFlags, etc
@@ -715,27 +716,49 @@ public:
virtual bool HasInternalBuffer() const override { return true; }
protected:
uint8_t* mBuffer;
size_t mBufSize;
};
-struct TextureClientAutoUnlock
+// Automatically lock and unlock a texture. Since texture locking is fallible,
+// Succeeded() must be checked on the guard object before proceeding.
+class MOZ_RAII TextureClientAutoLock
{
- TextureClient* mTexture;
+ MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER;
+
+public:
+ TextureClientAutoLock(TextureClient* aTexture, OpenMode aMode
+ MOZ_GUARD_OBJECT_NOTIFIER_PARAM)
+ : mTexture(aTexture),
+ mSucceeded(false)
+ {
+ MOZ_GUARD_OBJECT_NOTIFIER_INIT;
- explicit TextureClientAutoUnlock(TextureClient* aTexture)
- : mTexture(aTexture) {}
+ mSucceeded = mTexture->Lock(aMode);
+ mChecked = false;
+ }
+ ~TextureClientAutoLock() {
+ MOZ_ASSERT(mChecked);
+ if (mSucceeded) {
+ mTexture->Unlock();
+ }
+ }
- ~TextureClientAutoUnlock()
- {
- mTexture->Unlock();
+ bool Succeeded() {
+ mChecked = true;
+ return mSucceeded;
}
+
+private:
+ TextureClient* mTexture;
+ DebugOnly<bool> mChecked;
+ bool mSucceeded;
};
class KeepAlive
{
public:
virtual ~KeepAlive() {}
};
--- a/gfx/layers/client/TiledContentClient.cpp
+++ b/gfx/layers/client/TiledContentClient.cpp
@@ -596,30 +596,29 @@ TileClient::Flip()
mInvalidBack = invalidFront;
}
static bool
CopyFrontToBack(TextureClient* aFront,
TextureClient* aBack,
const gfx::IntRect& aRectToCopy)
{
- if (!aFront->Lock(OpenMode::OPEN_READ)) {
+ TextureClientAutoLock frontLock(aFront, OpenMode::OPEN_READ);
+ if (!frontLock.Succeeded()) {
gfxCriticalError() << "[Tiling:Client] Failed to lock the tile's front buffer";
return false;
}
if (!aBack->Lock(OpenMode::OPEN_READ_WRITE)) {
gfxCriticalError() << "[Tiling:Client] Failed to lock the tile's back buffer";
return false;
}
gfx::IntPoint rectToCopyTopLeft = aRectToCopy.TopLeft();
aFront->CopyToTextureClient(aBack, &aRectToCopy, &rectToCopyTopLeft);
-
- aFront->Unlock();
return true;
}
void
TileClient::ValidateBackBufferFromFront(const nsIntRegion& aDirtyRegion,
nsIntRegion& aAddPaintedRegion)
{
if (mBackBuffer && mFrontBuffer) {
--- a/gfx/layers/d3d11/TextureD3D11.cpp
+++ b/gfx/layers/d3d11/TextureD3D11.cpp
@@ -127,17 +127,17 @@ static bool LockD3DTexture(T* aTexture)
{
MOZ_ASSERT(aTexture);
RefPtr<IDXGIKeyedMutex> mutex;
aTexture->QueryInterface((IDXGIKeyedMutex**)getter_AddRefs(mutex));
// Textures created by the DXVA decoders don't have a mutex for synchronization
if (mutex) {
HRESULT hr = mutex->AcquireSync(0, 10000);
if (hr == WAIT_TIMEOUT) {
- MOZ_CRASH();
+ gfxDevCrash(LogReason::D3DLockTimeout) << "D3D lock mutex timeout";
}
if (FAILED(hr)) {
NS_WARNING("Failed to lock the texture");
return false;
}
}
return true;
@@ -1240,65 +1240,69 @@ SyncObjectD3D11::FinalizeFrame()
if (FAILED(hr) || !mD3D10Texture) {
gfxCriticalError() << "Failed to D3D10 OpenSharedResource for frame finalization: " << hexa(hr);
if (gfxWindowsPlatform::GetPlatform()->DidRenderingDeviceReset()) {
return;
}
- MOZ_CRASH();
+ gfxDevCrash(LogReason::D3D10FinalizeFrame) << "Without device reset: " << hexa(hr);
}
// test QI
RefPtr<IDXGIKeyedMutex> mutex;
hr = mD3D10Texture->QueryInterface((IDXGIKeyedMutex**)getter_AddRefs(mutex));
if (FAILED(hr) || !mutex) {
+ // Leave both the critical error and MOZ_CRASH for now; the critical error lets
+ // us "save" the hr value. We will probably eventuall replace this with gfxDevCrash.
gfxCriticalError() << "Failed to get KeyedMutex: " << hexa(hr);
- MOZ_CRASH();
+ MOZ_CRASH("GFX: Cannot get D3D10 KeyedMutex");
}
}
if (!mD3D11Texture && mD3D11SyncedTextures.size()) {
ID3D11Device* device = gfxWindowsPlatform::GetPlatform()->GetD3D11ContentDevice();
hr = device->OpenSharedResource(mHandle, __uuidof(ID3D11Texture2D), (void**)(ID3D11Texture2D**)getter_AddRefs(mD3D11Texture));
if (FAILED(hr) || !mD3D11Texture) {
gfxCriticalError() << "Failed to D3D11 OpenSharedResource for frame finalization: " << hexa(hr);
if (gfxWindowsPlatform::GetPlatform()->DidRenderingDeviceReset()) {
return;
}
- MOZ_CRASH();
+ gfxDevCrash(LogReason::D3D11FinalizeFrame) << "Without device reset: " << hexa(hr);
}
// test QI
RefPtr<IDXGIKeyedMutex> mutex;
hr = mD3D11Texture->QueryInterface((IDXGIKeyedMutex**)getter_AddRefs(mutex));
if (FAILED(hr) || !mutex) {
+ // Leave both the critical error and MOZ_CRASH for now; the critical error lets
+ // us "save" the hr value. We will probably eventuall replace this with gfxDevCrash.
gfxCriticalError() << "Failed to get KeyedMutex: " << hexa(hr);
- MOZ_CRASH();
+ MOZ_CRASH("GFX: Cannot get D3D11 KeyedMutex");
}
}
if (mD3D10SyncedTextures.size()) {
RefPtr<IDXGIKeyedMutex> mutex;
hr = mD3D10Texture->QueryInterface((IDXGIKeyedMutex**)getter_AddRefs(mutex));
hr = mutex->AcquireSync(0, 20000);
if (hr == WAIT_TIMEOUT) {
if (gfxWindowsPlatform::GetPlatform()->DidRenderingDeviceReset()) {
gfxWarning() << "AcquireSync timed out because of device reset.";
return;
}
- MOZ_CRASH();
+ gfxDevCrash(LogReason::D3D10SyncLock) << "Timeout on the D3D10 sync lock";
}
D3D10_BOX box;
box.front = box.top = box.left = 0;
box.back = box.bottom = box.right = 1;
ID3D10Device* device = gfxWindowsPlatform::GetPlatform()->GetD3D10Device();
@@ -1316,30 +1320,30 @@ SyncObjectD3D11::FinalizeFrame()
hr = mD3D11Texture->QueryInterface((IDXGIKeyedMutex**)getter_AddRefs(mutex));
hr = mutex->AcquireSync(0, 20000);
if (hr == WAIT_TIMEOUT) {
if (gfxWindowsPlatform::GetPlatform()->DidRenderingDeviceReset()) {
gfxWarning() << "AcquireSync timed out because of device reset.";
return;
}
- MOZ_CRASH();
+ gfxDevCrash(LogReason::D3D11SyncLock) << "Timeout on the D3D11 sync lock";
}
D3D11_BOX box;
box.front = box.top = box.left = 0;
box.back = box.bottom = box.right = 1;
ID3D11Device* dev = gfxWindowsPlatform::GetPlatform()->GetD3D11ContentDevice();
if (!dev) {
if (gfxWindowsPlatform::GetPlatform()->DidRenderingDeviceReset()) {
return;
}
- MOZ_CRASH();
+ MOZ_CRASH("GFX: Invalid D3D11 content device");
}
RefPtr<ID3D11DeviceContext> ctx;
dev->GetImmediateContext(getter_AddRefs(ctx));
for (auto iter = mD3D11SyncedTextures.begin(); iter != mD3D11SyncedTextures.end(); iter++) {
ctx->CopySubresourceRegion(mD3D11Texture, 0, 0, 0, 0, *iter, 0, &box);
}
--- a/gfx/layers/ipc/SharedPlanarYCbCrImage.cpp
+++ b/gfx/layers/ipc/SharedPlanarYCbCrImage.cpp
@@ -85,21 +85,23 @@ SharedPlanarYCbCrImage::SetData(const Pl
// allocate it. This code path is slower than the one used when Allocate has
// been called since it will trigger a full copy.
PlanarYCbCrData data = aData;
if (!mTextureClient && !Allocate(data)) {
return false;
}
MOZ_ASSERT(mTextureClient->AsTextureClientYCbCr());
- if (!mTextureClient->Lock(OpenMode::OPEN_WRITE_ONLY)) {
+
+ TextureClientAutoLock autoLock(mTextureClient, OpenMode::OPEN_WRITE_ONLY);
+ if (!autoLock.Succeeded()) {
MOZ_ASSERT(false, "Failed to lock the texture.");
return false;
}
- TextureClientAutoUnlock unlock(mTextureClient);
+
if (!mTextureClient->AsTextureClientYCbCr()->UpdateYCbCr(aData)) {
MOZ_ASSERT(false, "Failed to copy YCbCr data into the TextureClient");
return false;
}
mTextureClient->MarkImmutable();
return true;
}
--- a/gfx/thebes/gfx2DGlue.h
+++ b/gfx/thebes/gfx2DGlue.h
@@ -46,21 +46,31 @@ inline gfxMatrix ThebesMatrix(const Matr
aMatrix._22, aMatrix._31, aMatrix._32);
}
inline Point ToPoint(const gfxPoint &aPoint)
{
return Point(Float(aPoint.x), Float(aPoint.y));
}
+inline Size ToSize(const gfxSize &aSize)
+{
+ return Size(Float(aSize.width), Float(aSize.height));
+}
+
inline gfxPoint ThebesPoint(const Point &aPoint)
{
return gfxPoint(aPoint.x, aPoint.y);
}
+inline gfxSize ThebesSize(const Size &aSize)
+{
+ return gfxSize(aSize.width, aSize.height);
+}
+
inline gfxRect ThebesRect(const Rect &aRect)
{
return gfxRect(aRect.x, aRect.y, aRect.width, aRect.height);
}
inline gfxRect ThebesRect(const RectDouble &aRect)
{
return gfxRect(aRect.x, aRect.y, aRect.width, aRect.height);
--- a/gfx/thebes/gfxFcPlatformFontList.cpp
+++ b/gfx/thebes/gfxFcPlatformFontList.cpp
@@ -842,41 +842,54 @@ gfxFontconfigFontFamily::FindStyleVariat
{
if (mHasStyles) {
return;
}
// add font entries for each of the faces
uint32_t numFonts = mFontPatterns.Length();
NS_ASSERTION(numFonts, "font family containing no faces!!");
+ uint32_t numRegularFaces = 0;
for (uint32_t i = 0; i < numFonts; i++) {
FcPattern* face = mFontPatterns[i];
// figure out the psname/fullname and choose which to use as the facename
nsAutoString psname, fullname;
GetFaceNames(face, mName, psname, fullname);
const nsAutoString& faceName = !psname.IsEmpty() ? psname : fullname;
gfxFontconfigFontEntry *fontEntry =
new gfxFontconfigFontEntry(faceName, face);
AddFontEntry(fontEntry);
+ if (fontEntry->IsUpright() &&
+ fontEntry->Weight() == NS_FONT_WEIGHT_NORMAL &&
+ fontEntry->Stretch() == NS_FONT_STRETCH_NORMAL) {
+ numRegularFaces++;
+ }
+
if (LOG_FONTLIST_ENABLED()) {
LOG_FONTLIST(("(fontlist) added (%s) to family (%s)"
" with style: %s weight: %d stretch: %d"
" psname: %s fullname: %s",
NS_ConvertUTF16toUTF8(fontEntry->Name()).get(),
NS_ConvertUTF16toUTF8(Name()).get(),
(fontEntry->IsItalic()) ?
"italic" : (fontEntry->IsOblique() ? "oblique" : "normal"),
fontEntry->Weight(), fontEntry->Stretch(),
NS_ConvertUTF16toUTF8(psname).get(),
NS_ConvertUTF16toUTF8(fullname).get()));
}
}
+
+ // somewhat arbitrary, but define a family with two or more regular
+ // faces as a family for which intra-family fallback should be used
+ if (numRegularFaces > 1) {
+ mCheckForFallbackFaces = true;
+ }
mFaceNamesInitialized = true;
mFontPatterns.Clear();
SetHasStyles(true);
}
void
gfxFontconfigFontFamily::AddFontPattern(FcPattern* aFontPattern)
{
--- a/gfx/thebes/gfxFontEntry.h
+++ b/gfx/thebes/gfxFontEntry.h
@@ -658,17 +658,18 @@ public:
mName(aName),
mOtherFamilyNamesInitialized(false),
mHasOtherFamilyNames(false),
mFaceNamesInitialized(false),
mHasStyles(false),
mIsSimpleFamily(false),
mIsBadUnderlineFamily(false),
mFamilyCharacterMapInitialized(false),
- mSkipDefaultFeatureSpaceCheck(false)
+ mSkipDefaultFeatureSpaceCheck(false),
+ mCheckForFallbackFaces(false)
{ }
const nsString& Name() { return mName; }
virtual void LocalizedName(nsAString& aLocalizedName);
virtual bool HasOtherFamilyNames();
nsTArray<RefPtr<gfxFontEntry> >& GetFontList() { return mAvailableFonts; }
@@ -762,16 +763,17 @@ public:
void SetBadUnderlineFamily() {
mIsBadUnderlineFamily = true;
if (mHasStyles) {
SetBadUnderlineFonts();
}
}
bool IsBadUnderlineFamily() const { return mIsBadUnderlineFamily; }
+ bool CheckForFallbackFaces() const { return mCheckForFallbackFaces; }
// sort available fonts to put preferred (standard) faces towards the end
void SortAvailableFonts();
// check whether the family fits into the simple 4-face model,
// so we can use simplified style-matching;
// if so set the mIsSimpleFamily flag (defaults to False before we've checked)
void CheckForSimpleFamily();
@@ -817,16 +819,17 @@ protected:
bool mOtherFamilyNamesInitialized : 1;
bool mHasOtherFamilyNames : 1;
bool mFaceNamesInitialized : 1;
bool mHasStyles : 1;
bool mIsSimpleFamily : 1;
bool mIsBadUnderlineFamily : 1;
bool mFamilyCharacterMapInitialized : 1;
bool mSkipDefaultFeatureSpaceCheck : 1;
+ bool mCheckForFallbackFaces : 1; // check other faces for character
enum {
// for "simple" families, the faces are stored in mAvailableFonts
// with fixed positions:
kRegularFaceIndex = 0,
kBoldFaceIndex = 1,
kItalicFaceIndex = 2,
kBoldItalicFaceIndex = 3,
--- a/gfx/thebes/gfxTextRun.cpp
+++ b/gfx/thebes/gfxTextRun.cpp
@@ -1643,16 +1643,22 @@ gfxFontGroup::AddFamilyToFontList(gfxFon
if (!HasFont(fe)) {
FamilyFace ff(aFamily, fe, needsBold);
if (fe->mIsUserFontContainer) {
ff.CheckState(mSkipDrawing);
}
mFonts.AppendElement(ff);
}
}
+ // for a family marked as "check fallback faces", only mark the last
+ // entry so that fallbacks for a family are only checked once
+ if (aFamily->CheckForFallbackFaces() &&
+ !fontEntryList.IsEmpty() && !mFonts.IsEmpty()) {
+ mFonts.LastElement().SetCheckForFallbackFaces();
+ }
}
bool
gfxFontGroup::HasFont(const gfxFontEntry *aFontEntry)
{
uint32_t count = mFonts.Length();
for (uint32_t i = 0; i < count; ++i) {
if (mFonts[i].FontEntry() == aFontEntry) {
@@ -2550,16 +2556,33 @@ gfxFontGroup::FindNonItalicFaceForChar(g
if (!fe->HasCharacter(aCh)) {
return nullptr;
}
RefPtr<gfxFont> font = fe->FindOrMakeFont(&mStyle, needsBold);
return font.forget();
}
+already_AddRefed<gfxFont>
+gfxFontGroup::FindFallbackFaceForChar(gfxFontFamily* aFamily, uint32_t aCh,
+ int32_t aRunScript)
+{
+ GlobalFontMatch data(aCh, aRunScript, &mStyle);
+ aFamily->SearchAllFontsForChar(&data);
+ gfxFontEntry* fe = data.mBestMatch;
+ if (!fe) {
+ return nullptr;
+ }
+
+ bool needsBold = mStyle.weight >= 600 && !fe->IsBold() &&
+ mStyle.allowSyntheticWeight;
+ RefPtr<gfxFont> font = fe->FindOrMakeFont(&mStyle, needsBold);
+ return font.forget();
+}
+
gfxFloat
gfxFontGroup::GetUnderlineOffset()
{
if (mUnderlineOffset == UNDERLINE_OFFSET_NOT_SET) {
// if the fontlist contains a bad underline font, make the underline
// offset the min of the first valid font and bad font underline offsets
uint32_t len = mFonts.Length();
for (uint32_t i = 0; i < len; i++) {
@@ -2614,20 +2637,27 @@ gfxFontGroup::FindFontForChar(uint32_t a
if (!isJoinControl && !wasJoinCauser && !isVarSelector) {
RefPtr<gfxFont> firstFont = GetFontAt(0, aCh);
if (firstFont) {
if (firstFont->HasCharacter(aCh)) {
*aMatchType = gfxTextRange::kFontGroup;
return firstFont.forget();
}
- // If italic, test the regular face to see if it supports character.
- // Only do this for platform fonts, not userfonts.
- if (mStyle.style != NS_FONT_STYLE_NORMAL &&
- !firstFont->GetFontEntry()->IsUserFont()) {
+ if (mFonts[0].CheckForFallbackFaces()) {
+ RefPtr<gfxFont> font =
+ FindFallbackFaceForChar(mFonts[0].Family(), aCh, aRunScript);
+ if (font) {
+ *aMatchType = gfxTextRange::kFontGroup;
+ return font.forget();
+ }
+ } else if (mStyle.style != NS_FONT_STYLE_NORMAL &&
+ !firstFont->GetFontEntry()->IsUserFont()) {
+ // If italic, test the regular face to see if it supports
+ // character. Only do this for platform fonts, not userfonts.
RefPtr<gfxFont> font =
FindNonItalicFaceForChar(mFonts[0].Family(), aCh);
if (font) {
*aMatchType = gfxTextRange::kFontGroup;
return font.forget();
}
}
}
@@ -2717,27 +2747,40 @@ gfxFontGroup::FindFontForChar(uint32_t a
// build the font via GetFontAt
font = GetFontAt(i, aCh);
if (font) {
*aMatchType = gfxTextRange::kFontGroup;
return font.forget();
}
}
- // If italic, test the regular face to see if it supports the character.
- // Only do this for platform fonts, not userfonts.
- fe = ff.FontEntry();
- if (mStyle.style != NS_FONT_STYLE_NORMAL &&
- !fe->mIsUserFontContainer &&
- !fe->IsUserFont()) {
- font = FindNonItalicFaceForChar(ff.Family(), aCh);
+ // check other family faces if needed
+ if (ff.CheckForFallbackFaces()) {
+ NS_ASSERTION(i == 0 ? true :
+ !mFonts[i-1].CheckForFallbackFaces() ||
+ !mFonts[i-1].Family()->Name().Equals(ff.Family()->Name()),
+ "should only do fallback once per font family");
+ font = FindFallbackFaceForChar(ff.Family(), aCh, aRunScript);
if (font) {
*aMatchType = gfxTextRange::kFontGroup;
return font.forget();
}
+ } else {
+ // If italic, test the regular face to see if it supports the
+ // character. Only do this for platform fonts, not userfonts.
+ fe = ff.FontEntry();
+ if (mStyle.style != NS_FONT_STYLE_NORMAL &&
+ !fe->mIsUserFontContainer &&
+ !fe->IsUserFont()) {
+ font = FindNonItalicFaceForChar(ff.Family(), aCh);
+ if (font) {
+ *aMatchType = gfxTextRange::kFontGroup;
+ return font.forget();
+ }
+ }
}
}
if (fontListLength == 0) {
RefPtr<gfxFont> defaultFont = GetDefaultFont();
if (defaultFont->HasCharacter(aCh)) {
*aMatchType = gfxTextRange::kFontGroup;
return defaultFont.forget();
--- a/gfx/thebes/gfxTextRun.h
+++ b/gfx/thebes/gfxTextRun.h
@@ -877,50 +877,52 @@ protected:
void ComputeRanges(nsTArray<gfxTextRange>& mRanges,
const T *aString, uint32_t aLength,
int32_t aRunScript, uint16_t aOrientation);
class FamilyFace {
public:
FamilyFace() : mFamily(nullptr), mFontEntry(nullptr),
mNeedsBold(false), mFontCreated(false),
- mLoading(false), mInvalid(false)
+ mLoading(false), mInvalid(false),
+ mCheckForFallbackFaces(false)
{ }
FamilyFace(gfxFontFamily* aFamily, gfxFont* aFont)
: mFamily(aFamily), mNeedsBold(false), mFontCreated(true),
- mLoading(false), mInvalid(false)
+ mLoading(false), mInvalid(false), mCheckForFallbackFaces(false)
{
NS_ASSERTION(aFont, "font pointer must not be null");
NS_ASSERTION(!aFamily ||
aFamily->ContainsFace(aFont->GetFontEntry()),
"font is not a member of the given family");
mFont = aFont;
NS_ADDREF(aFont);
}
FamilyFace(gfxFontFamily* aFamily, gfxFontEntry* aFontEntry,
bool aNeedsBold)
: mFamily(aFamily), mNeedsBold(aNeedsBold), mFontCreated(false),
- mLoading(false), mInvalid(false)
+ mLoading(false), mInvalid(false), mCheckForFallbackFaces(false)
{
NS_ASSERTION(aFontEntry, "font entry pointer must not be null");
NS_ASSERTION(!aFamily ||
aFamily->ContainsFace(aFontEntry),
"font is not a member of the given family");
mFontEntry = aFontEntry;
NS_ADDREF(aFontEntry);
}
FamilyFace(const FamilyFace& aOtherFamilyFace)
: mFamily(aOtherFamilyFace.mFamily),
mNeedsBold(aOtherFamilyFace.mNeedsBold),
mFontCreated(aOtherFamilyFace.mFontCreated),
mLoading(aOtherFamilyFace.mLoading),
- mInvalid(aOtherFamilyFace.mInvalid)
+ mInvalid(aOtherFamilyFace.mInvalid),
+ mCheckForFallbackFaces(aOtherFamilyFace.mCheckForFallbackFaces)
{
if (mFontCreated) {
mFont = aOtherFamilyFace.mFont;
NS_ADDREF(mFont);
} else {
mFontEntry = aOtherFamilyFace.mFontEntry;
NS_IF_ADDREF(mFontEntry);
}
@@ -973,16 +975,18 @@ protected:
bool IsUserFontContainer() const {
return FontEntry()->mIsUserFontContainer;
}
bool IsLoading() const { return mLoading; }
bool IsInvalid() const { return mInvalid; }
void CheckState(bool& aSkipDrawing);
void SetLoading(bool aIsLoading) { mLoading = aIsLoading; }
void SetInvalid() { mInvalid = true; }
+ bool CheckForFallbackFaces() const { return mCheckForFallbackFaces; }
+ void SetCheckForFallbackFaces() { mCheckForFallbackFaces = true; }
void SetFont(gfxFont* aFont)
{
NS_ASSERTION(aFont, "font pointer must not be null");
NS_ADDREF(aFont);
if (mFontCreated) {
NS_RELEASE(mFont);
} else {
@@ -1001,16 +1005,17 @@ protected:
union {
gfxFont* mFont;
gfxFontEntry* mFontEntry;
};
bool mNeedsBold : 1;
bool mFontCreated : 1;
bool mLoading : 1;
bool mInvalid : 1;
+ bool mCheckForFallbackFaces : 1;
};
// List of font families, either named or generic.
// Generic names map to system pref fonts based on language.
mozilla::FontFamilyList mFamilyList;
// Fontlist containing a font entry for each family found. gfxFont objects
// are created as needed and userfont loads are initiated when needed.
@@ -1097,17 +1102,23 @@ protected:
// Helper for font-matching:
// When matching the italic case, allow use of the regular face
// if it supports a character but the italic one doesn't.
// Return null if regular face doesn't support aCh
already_AddRefed<gfxFont>
FindNonItalicFaceForChar(gfxFontFamily* aFamily, uint32_t aCh);
- // helper methods for looking up fonts
+ // search all faces in a family for a fallback in cases where it's unclear
+ // whether the family might have a font for a given character
+ already_AddRefed<gfxFont>
+ FindFallbackFaceForChar(gfxFontFamily* aFamily, uint32_t aCh,
+ int32_t aRunScript);
+
+ // helper methods for looking up fonts
// lookup and add a font with a given name (i.e. *not* a generic!)
void AddPlatformFont(const nsAString& aName,
nsTArray<gfxFontFamily*>& aFamilyList);
// do style selection and add entries to list
void AddFamilyToFontList(gfxFontFamily* aFamily);
deleted file mode 100644
--- a/ipc/chromium/Makefile.in
+++ /dev/null
@@ -1,40 +0,0 @@
-# This Source Code Form is subject to the terms of the Mozilla Public
-# License, v. 2.0. If a copy of the MPL was not distributed with this
-# file, You can obtain one at http://mozilla.org/MPL/2.0/.
-
-OS_CXXFLAGS := $(filter-out -fshort-wchar,$(OS_CXXFLAGS))
-
-ACDEFINES =
-
-ifndef MOZ_NATIVE_LIBEVENT # {
-vpath %.c \
- $(srcdir)/src/third_party/libevent \
- $(NULL)
-endif # }
-
-vpath %.cc \
- $(srcdir)/src/base \
- $(srcdir)/src/chrome/common \
- $(NULL)
-
-vpath %.mm \
- $(srcdir)/src/base \
- $(srcdir)/src/chrome/common \
- $(NULL)
-
-OS_CXXFLAGS += $(TK_CFLAGS)
-
-include $(topsrcdir)/config/rules.mk
-
-ifdef MOZ_NATIVE_LIBEVENT # {
-
-export-preqs = \
- $(call mkdir_deps,$(CURDIR)/third_party/libevent) \
- $(NULL)
-
-export:: $(DIST)/third_party/libevent/event.h
-
-$(DIST)/third_party/libevent/event.h:: $(export-preqs)
- echo '#include <event.h>' > $(CURDIR)/third_party/libevent/event.h
-
-endif # }
--- a/ipc/chromium/moz.build
+++ b/ipc/chromium/moz.build
@@ -162,11 +162,13 @@ if os_bsd or os_linux:
]
ost = CONFIG['OS_TEST']
if '86' not in ost and 'arm' not in ost and 'mips' not in ost:
SOURCES += [
'src/base/atomicops_internals_mutex.cc',
]
+CXXFLAGS += CONFIG['TK_CFLAGS']
+
include('/ipc/chromium/chromium-config.mozbuild')
FINAL_LIBRARY = 'xul'
--- a/ipc/chromium/src/base/message_pump_libevent.cc
+++ b/ipc/chromium/src/base/message_pump_libevent.cc
@@ -10,17 +10,17 @@
#include <unistd.h>
#endif
#include "eintr_wrapper.h"
#include "base/logging.h"
#include "base/scoped_nsautorelease_pool.h"
#include "base/time.h"
#include "nsDependentSubstring.h"
-#include "third_party/libevent/event.h"
+#include "event.h"
#include "mozilla/UniquePtr.h"
// This macro checks that the _EVENT_SIZEOF_* constants defined in
// ipc/chromiume/src/third_party/<platform>/event2/event-config.h are correct.
#define CHECK_EVENT_SIZEOF(TYPE, type) \
static_assert(_EVENT_SIZEOF_##TYPE == sizeof(type), \
"bad _EVENT_SIZEOF_"#TYPE);
deleted file mode 100644
--- a/ipc/glue/Makefile.in
+++ /dev/null
@@ -1,1 +0,0 @@
-%/SharedMemoryBasic_mach.cpp: ;
--- a/js/src/frontend/BytecodeEmitter.cpp
+++ b/js/src/frontend/BytecodeEmitter.cpp
@@ -3186,17 +3186,21 @@ BytecodeEmitter::emitSwitch(ParseNode* p
if (switchOp == JSOP_CONDSWITCH) {
unsigned caseNoteIndex;
bool beforeCases = true;
ptrdiff_t prevCaseOffset;
/* Emit code for evaluating cases and jumping to case statements. */
for (ParseNode* caseNode = cases->pn_head; caseNode; caseNode = caseNode->pn_next) {
ParseNode* caseValue = caseNode->pn_left;
- if (caseValue && !emitTree(caseValue))
+ // If the expression is a literal, suppress line number
+ // emission so that debugging works more naturally.
+ if (caseValue &&
+ !emitTree(caseValue, caseValue->isLiteral() ? SUPPRESS_LINENOTE :
+ EMIT_LINENOTE))
return false;
if (!beforeCases) {
/* prevCaseOffset is the previous JSOP_CASE's bytecode offset. */
if (!setSrcNoteOffset(caseNoteIndex, 0, offset() - prevCaseOffset))
return false;
}
if (!caseValue) {
MOZ_ASSERT(caseNode->isKind(PNK_DEFAULT));
@@ -7549,30 +7553,30 @@ BytecodeEmitter::emitClass(ParseNode* pn
}
MOZ_ALWAYS_TRUE(sc->setLocalStrictMode(savedStrictness));
return true;
}
bool
-BytecodeEmitter::emitTree(ParseNode* pn)
+BytecodeEmitter::emitTree(ParseNode* pn, EmitLineNumberNote emitLineNote)
{
JS_CHECK_RECURSION(cx, return false);
EmitLevelManager elm(this);
bool ok = true;
ptrdiff_t top = offset();
pn->pn_offset = top;
/* Emit notes to tell the current bytecode's source line number.
However, a couple trees require special treatment; see the
relevant emitter functions for details. */
- if (pn->getKind() != PNK_WHILE && pn->getKind() != PNK_FOR &&
+ if (emitLineNote == EMIT_LINENOTE && pn->getKind() != PNK_WHILE && pn->getKind() != PNK_FOR &&
!updateLineNumberNotes(pn->pn_pos.begin))
return false;
switch (pn->getKind()) {
case PNK_FUNCTION:
ok = emitFunction(pn);
break;
--- a/js/src/frontend/BytecodeEmitter.h
+++ b/js/src/frontend/BytecodeEmitter.h
@@ -331,18 +331,24 @@ struct BytecodeEmitter
bool addToSrcNoteDelta(jssrcnote* sn, ptrdiff_t delta);
// Finish taking source notes in cx's notePool. If successful, the final
// source note count is stored in the out outparam.
bool finishTakingSrcNotes(uint32_t* out);
void setJumpOffsetAt(ptrdiff_t off);
+ // Control whether emitTree emits a line number note.
+ enum EmitLineNumberNote {
+ EMIT_LINENOTE,
+ SUPPRESS_LINENOTE
+ };
+
// Emit code for the tree rooted at pn.
- bool emitTree(ParseNode* pn);
+ bool emitTree(ParseNode* pn, EmitLineNumberNote emitLineNote = EMIT_LINENOTE);
// Emit function code for the tree rooted at body.
bool emitFunctionScript(ParseNode* body);
// Emit module code for the tree rooted at body.
bool emitModuleScript(ParseNode* body);
// Report an error if we are not processing a module.
--- a/js/src/gc/Tracer.h
+++ b/js/src/gc/Tracer.h
@@ -118,17 +118,17 @@ TraceProcessGlobalRoot(JSTracer* trc, T*
void
TraceGenericPointerRoot(JSTracer* trc, gc::Cell** thingp, const char* name);
// Trace a non-root edge that uses the base GC thing type, instead of a more
// specific type.
void
TraceManuallyBarrieredGenericPointerEdge(JSTracer* trc, gc::Cell** thingp, const char* name);
-// Depricated. Please use one of the strongly typed variants above.
+// Deprecated. Please use one of the strongly typed variants above.
void
TraceChildren(JSTracer* trc, void* thing, JS::TraceKind kind);
namespace gc {
// Trace through a shape or group iteratively during cycle collection to avoid
// deep or infinite recursion.
void
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Frame-onStep-14.js
@@ -0,0 +1,46 @@
+// Test how stepping interacts with switch statements.
+
+var g = newGlobal();
+
+g.eval('function bob() { return "bob"; }');
+
+// Stepping into a sparse switch should not stop on literal cases.
+evaluate(`function lit(x) { // 1
+ debugger; // 2
+ switch(x) { // 3
+ case "nope": // 4
+ break; // 5
+ case "bob": // 6
+ break; // 7
+ } // 8
+}`, {lineNumber: 1, global: g});
+
+// Stepping into a sparse switch should stop on non-literal cases.
+evaluate(`function nonlit(x) { // 1
+ debugger; // 2
+ switch(x) { // 3
+ case bob(): // 4
+ break; // 5
+ } // 6
+}`, {lineNumber: 1, global: g});
+
+var dbg = Debugger(g);
+var badStep = false;
+
+function test(s, okLine) {
+ dbg.onDebuggerStatement = function(frame) {
+ frame.onStep = function() {
+ let thisLine = this.script.getOffsetLocation(this.offset).lineNumber;
+ // The stop at line 3 is the switch.
+ if (thisLine > 3) {
+ assertEq(thisLine, okLine)
+ frame.onStep = undefined;
+ }
+ };
+ };
+ g.eval(s);
+}
+
+test("lit('bob');", 7);
+
+test("nonlit('bob');", 4);
--- a/js/src/jit/IonBuilder.cpp
+++ b/js/src/jit/IonBuilder.cpp
@@ -808,17 +808,17 @@ IonBuilder::build()
return false;
#ifdef JS_JITSPEW
if (info().isAnalysis()) {
JitSpew(JitSpew_IonScripts, "Analyzing script %s:%" PRIuSIZE " (%p) %s",
script()->filename(), script()->lineno(), (void*)script(),
AnalysisModeString(info().analysisMode()));
} else {
- JitSpew(JitSpew_IonScripts, "%sompiling script %s:%" PRIuSIZE " (%p) (warmup-counter=%" PRIuSIZE ", level=%s)",
+ JitSpew(JitSpew_IonScripts, "%sompiling script %s:%" PRIuSIZE " (%p) (warmup-counter=%" PRIu32 ", level=%s)",
(script()->hasIonScript() ? "Rec" : "C"),
script()->filename(), script()->lineno(), (void*)script(),
script()->getWarmUpCount(), OptimizationLevelString(optimizationInfo().level()));
}
#endif
initParameters();
initLocals();
--- a/js/src/jit/JitSpewer.h
+++ b/js/src/jit/JitSpewer.h
@@ -3,16 +3,17 @@
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef jit_JitSpewer_h
#define jit_JitSpewer_h
#include "mozilla/DebugOnly.h"
+#include "mozilla/IntegerPrintfMacros.h"
#include <stdarg.h>
#include "jit/C1Spewer.h"
#include "jit/JSONSpewer.h"
#include "js/RootingAPI.h"
namespace js {
--- a/js/src/vm/ArgumentsObject.h
+++ b/js/src/vm/ArgumentsObject.h
@@ -71,17 +71,17 @@ struct ArgumentsData
const HeapValue* begin() const { return args; }
HeapValue* end() { return args + numArgs; }
const HeapValue* end() const { return args + numArgs; }
};
// Maximum supported value of arguments.length. This bounds the maximum
// number of arguments that can be supplied to Function.prototype.apply.
// This value also bounds the number of elements parsed in an array
-// initialiser.
+// initializer.
static const unsigned ARGS_LENGTH_MAX = 500 * 1000;
/*
* ArgumentsObject instances represent |arguments| objects created to store
* function arguments when a function is called. It's expensive to create such
* objects if they're never used, so they're only created when they are
* potentially used.
*
--- a/js/src/vm/PIC.h
+++ b/js/src/vm/PIC.h
@@ -161,22 +161,22 @@ struct ForOfPIC
* To ensure that Array.prototype has not been modified.
*
* ArrayIterator.prototype (arrayIteratorProto_)
* ArrayIterator.prototype's shape (arrayIteratorProtoShape_)
* To ensure that an ArrayIterator.prototype has not been modified.
*
* Array.prototype's slot number for @@iterator (arrayProtoIteratorSlot_)
* Array.prototype's canonical value for @@iterator (canonicalIteratorFunc_)
- * To quickly retreive and ensure that the iterator constructor
+ * To quickly retrieve and ensure that the iterator constructor
* stored in the slot has not changed.
*
* ArrayIterator.prototype's slot number for 'next' (arrayIteratorProtoNextSlot_)
* ArrayIterator.prototype's canonical value for 'next' (canonicalNextFunc_)
- * To quickly retreive and ensure that the 'next' method for ArrayIterator
+ * To quickly retrieve and ensure that the 'next' method for ArrayIterator
* objects has not changed.
*/
class Chain : public BaseChain
{
private:
// Pointer to canonical Array.prototype and ArrayIterator.prototype
HeapPtrNativeObject arrayProto_;
HeapPtrNativeObject arrayIteratorProto_;
--- a/js/src/vm/Runtime.cpp
+++ b/js/src/vm/Runtime.cpp
@@ -612,17 +612,17 @@ InvokeInterruptCallback(JSContext* cx)
return false;
}
void
JSRuntime::resetJitStackLimit()
{
// Note that, for now, we use the untrusted limit for ion. This is fine,
// because it's the most conservative limit, and if we hit it, we'll bail
- // out of ion into the interpeter, which will do a proper recursion check.
+ // out of ion into the interpreter, which will do a proper recursion check.
#ifdef JS_SIMULATOR
jitStackLimit_ = jit::Simulator::StackLimit();
#else
jitStackLimit_ = mainThread.nativeStackLimit[StackForUntrustedScript];
#endif
jitStackLimitNoInterrupt_ = jitStackLimit_;
}
--- a/js/src/vm/Runtime.h
+++ b/js/src/vm/Runtime.h
@@ -1027,17 +1027,17 @@ struct JSRuntime : public JS::shadow::Ru
* duty (in debug builds) to verify that it matches the cx being used.
*/
JSContext* activeContext;
#endif
/* Garbage collector state, used by jsgc.c. */
js::gc::GCRuntime gc;
- /* Garbage collector state has been sucessfully initialized. */
+ /* Garbage collector state has been successfully initialized. */
bool gcInitialized;
int gcZeal() { return gc.zeal(); }
void lockGC() {
assertCanLock(js::GCLock);
gc.lockGC();
}
@@ -1097,17 +1097,17 @@ struct JSRuntime : public JS::shadow::Ru
}
void enableProfilerSampling() {
suppressProfilerSampling = false;
}
/* Had an out-of-memory error which did not populate an exception. */
bool hadOutOfMemory;
- /* We are curently deleting an object due to an initialization failure. */
+ /* We are currently deleting an object due to an initialization failure. */
mozilla::DebugOnly<bool> handlingInitFailure;
/* A context has been created on this runtime. */
bool haveCreatedContext;
/*
* Allow relazifying functions in compartments that are active. This is
* only used by the relazifyFunctions() testing function.
--- a/js/src/vm/TraceLogging.cpp
+++ b/js/src/vm/TraceLogging.cpp
@@ -680,31 +680,35 @@ TraceLoggerThreadState::init()
}
if (ContainsFlag(env, "IonCompiler")) {
enabledTextIds[TraceLogger_IonCompilation] = true;
enabledTextIds[TraceLogger_IonLinking] = true;
enabledTextIds[TraceLogger_FoldTests] = true;
enabledTextIds[TraceLogger_SplitCriticalEdges] = true;
enabledTextIds[TraceLogger_RenumberBlocks] = true;
+ enabledTextIds[TraceLogger_ScalarReplacement] = true;
enabledTextIds[TraceLogger_DominatorTree] = true;
enabledTextIds[TraceLogger_PhiAnalysis] = true;
- enabledTextIds[TraceLogger_ScalarReplacement] = true;
+ enabledTextIds[TraceLogger_MakeLoopsContiguous] = true;
enabledTextIds[TraceLogger_ApplyTypes] = true;
enabledTextIds[TraceLogger_EagerSimdUnbox] = true;
enabledTextIds[TraceLogger_AliasAnalysis] = true;
enabledTextIds[TraceLogger_GVN] = true;
enabledTextIds[TraceLogger_LICM] = true;
+ enabledTextIds[TraceLogger_Sincos] = true;
enabledTextIds[TraceLogger_RangeAnalysis] = true;
enabledTextIds[TraceLogger_LoopUnrolling] = true;
enabledTextIds[TraceLogger_EffectiveAddressAnalysis] = true;
enabledTextIds[TraceLogger_AlignmentMaskAnalysis] = true;
enabledTextIds[TraceLogger_EliminateDeadCode] = true;
+ enabledTextIds[TraceLogger_ReorderInstructions] = true;
enabledTextIds[TraceLogger_EdgeCaseAnalysis] = true;
enabledTextIds[TraceLogger_EliminateRedundantChecks] = true;
+ enabledTextIds[TraceLogger_AddKeepAliveInstructions] = true;
enabledTextIds[TraceLogger_GenerateLIR] = true;
enabledTextIds[TraceLogger_RegisterAllocation] = true;
enabledTextIds[TraceLogger_GenerateCode] = true;
enabledTextIds[TraceLogger_Scripts] = true;
}
enabledTextIds[TraceLogger_Interpreter] = enabledTextIds[TraceLogger_Engine];
enabledTextIds[TraceLogger_Baseline] = enabledTextIds[TraceLogger_Engine];
--- a/js/src/vm/TraceLogging.h
+++ b/js/src/vm/TraceLogging.h
@@ -30,39 +30,39 @@ class PerThreadData;
namespace jit {
class CompileRuntime;
} // namespace jit
/*
* Tracelogging overview.
*
- * Tracelogging makes it possible to trace the occurence of a single event and/or
+ * Tracelogging makes it possible to trace the occurrence of a single event and/or
* the start and stop of an event. This is implemented to give an as low overhead as
* possible so it doesn't interfere with running.
*
*
* Logging something is done in 3 stages.
* 1) Get the tracelogger of the current thread.
* - TraceLoggerForMainThread(JSRuntime*)
* - TraceLoggerForCurrentThread(); // Should NOT be used for the mainthread.
*
* 2) Optionally create a TraceLoggerEvent for the text that needs to get logged. This
* step takes some time, so try to do this beforehand, outside the hot
- * path and don't do unnecessary repetitions, since it will criple
+ * path and don't do unnecessary repetitions, since it will cripple
* performance.
* - TraceLoggerEvent event(logger, "foo");
*
* There are also some predefined events. They are located in
* TraceLoggerTextId. They don't require to create an TraceLoggerEvent and
* can also be used as an argument to these functions.
- * 3) Log the occurence of a single event:
+ * 3) Log the occurrence of a single event:
* - TraceLogTimestamp(logger, TraceLoggerTextId);
* Note: it is temporarily not supported to provide an TraceLoggerEvent as
- * argument to log the occurence of a single event.
+ * argument to log the occurrence of a single event.
*
* or log the start and stop of an event:
* - TraceLogStartEvent(logger, TraceLoggerTextId);
* - TraceLogStartEvent(logger, TraceLoggerEvent);
* - TraceLogStopEvent(logger, TraceLoggerTextId);
* - TraceLogStopEvent(logger, TraceLoggerEvent);
*
* or the start/stop of an event with a RAII class:
--- a/js/src/vm/TraceLoggingGraph.h
+++ b/js/src/vm/TraceLoggingGraph.h
@@ -23,17 +23,17 @@
* - events: Name of the file containing a flat list of log events saved
* in binary format.
* (64bit: Time Stamp Counter, 32bit index to dict)
* - tree: Name of the file containing the events with duration. The content
* is already in a tree data structure. This is also saved in a
* binary file.
* - treeFormat: The format used to encode the tree. By default "64,64,31,1,32".
* There are currently no other formats to save the tree.
- * - 64,64,31,1,31 signifies how many bytes are used for the different
+ * - 64,64,31,1,32 signifies how many bytes are used for the different
* parts of the tree.
* => 64 bits: Time Stamp Counter of start of event.
* => 64 bits: Time Stamp Counter of end of event.
* => 31 bits: Index to dict file containing the log text.
* => 1 bit: Boolean signifying if this entry has children.
* When true, the child can be found just right after this entry.
* => 32 bits: Containing the ID of the next event on the same depth
* or 0 if there isn't an event on the same depth anymore.
--- a/js/src/vm/TypedArrayObject.cpp
+++ b/js/src/vm/TypedArrayObject.cpp
@@ -1826,17 +1826,17 @@ const Class TypedArrayObject::classes[Sc
// Thus we need one class with cached prototype per kind of typed array, with a
// delegated ClassSpec.
#define IMPL_TYPED_ARRAY_PROTO_CLASS(typedArray, i) \
{ \
/*
* Actually ({}).toString.call(Uint8Array.prototype) should throw, because
* Uint8Array.prototype lacks the the typed array internal slots. (Same as
* with %TypedArray%.prototype.) It's not clear this is desirable (see
- * above), but it's what we've always done, so keep doing it til we
+ * above), but it's what we've always done, so keep doing it till we
* implement @@toStringTag or ES6 changes.
*/ \
#typedArray "Prototype", \
JSCLASS_HAS_CACHED_PROTO(JSProto_##typedArray), \
nullptr, /* addProperty */ \
nullptr, /* delProperty */ \
nullptr, /* getProperty */ \
nullptr, /* setProperty */ \
--- a/layout/base/FrameLayerBuilder.cpp
+++ b/layout/base/FrameLayerBuilder.cpp
@@ -1056,17 +1056,17 @@ public:
mAppUnitsPerDevPixel = presContext->AppUnitsPerDevPixel();
mContainerReferenceFrame =
const_cast<nsIFrame*>(aContainerItem ? aContainerItem->ReferenceFrameForChildren() :
mBuilder->FindReferenceFrameFor(mContainerFrame));
bool isAtRoot = !aContainerItem || (aContainerItem->Frame() == mBuilder->RootReferenceFrame());
MOZ_ASSERT_IF(isAtRoot, mContainerReferenceFrame == mBuilder->RootReferenceFrame());
mContainerAnimatedGeometryRoot = isAtRoot
? mContainerReferenceFrame
- : nsLayoutUtils::GetAnimatedGeometryRootFor(aContainerItem, aBuilder);
+ : aContainerItem->AnimatedGeometryRoot();
MOZ_ASSERT(!mBuilder->IsPaintingToWindow() ||
nsLayoutUtils::IsAncestorFrameCrossDoc(mBuilder->RootReferenceFrame(),
mContainerAnimatedGeometryRoot));
NS_ASSERTION(!aContainerItem || !aContainerItem->ShouldFixToViewport(mBuilder),
"Container items never return true for ShouldFixToViewport");
mContainerFixedPosFrame =
FindFixedPosFrameForLayerData(mContainerAnimatedGeometryRoot, false);
// When AllowResidualTranslation is false, display items will be drawn
@@ -3711,18 +3711,17 @@ ContainerState::ChooseAnimatedGeometryRo
// Don't use an item that won't be part of any PaintedLayers to pick the
// active scrolled root.
if (layerState == LAYER_ACTIVE_FORCE) {
continue;
}
// Try using the actual active scrolled root of the backmost item, as that
// should result in the least invalidation when scrolling.
- *aAnimatedGeometryRoot =
- nsLayoutUtils::GetAnimatedGeometryRootFor(item, mBuilder);
+ *aAnimatedGeometryRoot = item->AnimatedGeometryRoot();
return true;
}
return false;
}
nsIntRegion
ContainerState::ComputeOpaqueRect(nsDisplayItem* aItem,
const nsIFrame* aAnimatedGeometryRoot,
@@ -3887,18 +3886,17 @@ ContainerState::ProcessDisplayItems(nsDi
if (layerState == LAYER_INACTIVE &&
nsDisplayItem::ForceActiveLayers()) {
layerState = LAYER_ACTIVE;
}
bool forceInactive;
const nsIFrame* animatedGeometryRoot;
const nsIFrame* animatedGeometryRootForScrollMetadata = nullptr;
- const nsIFrame* realAnimatedGeometryRootOfItem =
- nsLayoutUtils::GetAnimatedGeometryRootFor(item, mBuilder);
+ const nsIFrame* realAnimatedGeometryRootOfItem = item->AnimatedGeometryRoot();
if (mFlattenToSingleLayer) {
forceInactive = true;
animatedGeometryRoot = lastAnimatedGeometryRoot;
} else {
forceInactive = false;
if (mManager->IsWidgetLayerManager()) {
animatedGeometryRoot = realAnimatedGeometryRootOfItem;
// Unlike GetAnimatedGeometryRootFor(), GetAnimatedGeometryRootForFrame() does not
--- a/layout/base/nsDisplayList.cpp
+++ b/layout/base/nsDisplayList.cpp
@@ -1991,21 +1991,27 @@ void nsDisplayList::SortByCSSOrder(nsDis
void nsDisplayList::Sort(nsDisplayListBuilder* aBuilder,
SortLEQ aCmp, void* aClosure) {
::Sort(this, Count(), aCmp, aClosure);
}
nsDisplayItem::nsDisplayItem(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame)
: mFrame(aFrame)
, mClip(aBuilder->ClipState().GetCurrentCombinedClip(aBuilder))
+ , mAnimatedGeometryRoot(nullptr)
#ifdef MOZ_DUMP_PAINTING
, mPainted(false)
#endif
{
mReferenceFrame = aBuilder->FindReferenceFrameFor(aFrame, &mToReferenceFrame);
+ // This can return the wrong result if the item override ShouldFixToViewport(),
+ // the item needs to set it again in its constructor.
+ mAnimatedGeometryRoot = nsLayoutUtils::GetAnimatedGeometryRootForInit(this, aBuilder);
+ MOZ_ASSERT(nsLayoutUtils::IsAncestorFrameCrossDoc(aBuilder->RootReferenceFrame(),
+ mAnimatedGeometryRoot), "Bad");
NS_ASSERTION(aBuilder->GetDirtyRect().width >= 0 ||
!aBuilder->IsForPainting(), "dirty rect not set");
// The dirty rect is for mCurrentFrame, so we have to use
// mCurrentOffsetToReferenceFrame
mVisibleRect = aBuilder->GetDirtyRect() +
aBuilder->GetCurrentFrameOffsetToReferenceFrame();
}
@@ -2136,16 +2142,19 @@ nsDisplayBackgroundImage::nsDisplayBackg
: nsDisplayImageContainer(aBuilder, aFrame)
, mBackgroundStyle(aBackgroundStyle)
, mLayer(aLayer)
{
MOZ_COUNT_CTOR(nsDisplayBackgroundImage);
mBounds = GetBoundsInternal(aBuilder);
mDestArea = GetDestAreaInternal(aBuilder);
+ if (ShouldFixToViewport(aBuilder)) {
+ mAnimatedGeometryRoot = nsLayoutUtils::GetAnimatedGeometryRootFor(this, aBuilder);
+ }
}
nsRect
nsDisplayBackgroundImage::GetDestAreaInternal(nsDisplayListBuilder* aBuilder)
{
if (!mBackgroundStyle) {
return nsRect();
}
@@ -3781,18 +3790,17 @@ RequiredLayerStateForChildren(nsDisplayL
LayerManager* aManager,
const ContainerLayerParameters& aParameters,
const nsDisplayList& aList,
nsIFrame* aExpectedAnimatedGeometryRootForChildren)
{
LayerState result = LAYER_INACTIVE;
for (nsDisplayItem* i = aList.GetBottom(); i; i = i->GetAbove()) {
if (result == LAYER_INACTIVE &&
- nsLayoutUtils::GetAnimatedGeometryRootFor(i, aBuilder) !=
- aExpectedAnimatedGeometryRootForChildren) {
+ i->AnimatedGeometryRoot() != aExpectedAnimatedGeometryRootForChildren) {
result = LAYER_ACTIVE;
}
LayerState state = i->GetLayerState(aBuilder, aManager, aParameters);
if ((state == LAYER_ACTIVE || state == LAYER_ACTIVE_FORCE) &&
state > result) {
result = state;
}
@@ -4056,18 +4064,17 @@ nsDisplayOpacity::GetLayerState(nsDispla
if (mForEventsOnly) {
MOZ_ASSERT(mOpacity == 0);
return LAYER_INACTIVE;
}
if (NeedsActiveLayer(aBuilder))
return LAYER_ACTIVE;
- return RequiredLayerStateForChildren(aBuilder, aManager, aParameters, mList,
- nsLayoutUtils::GetAnimatedGeometryRootFor(this, aBuilder));
+ return RequiredLayerStateForChildren(aBuilder, aManager, aParameters, mList, AnimatedGeometryRoot());
}
bool
nsDisplayOpacity::ComputeVisibility(nsDisplayListBuilder* aBuilder,
nsRegion* aVisibleRegion) {
// Our children are translucent so we should not allow them to subtract
// area from aVisibleRegion. We do need to find out what is visible under
// our children in the temporary compositing buffer, because if our children
@@ -4789,16 +4796,17 @@ nsDisplayTransform::SetReferenceFrameToA
{
if (mFrame == aBuilder->RootReferenceFrame()) {
return;
}
nsIFrame *outerFrame = nsLayoutUtils::GetCrossDocParentFrame(mFrame);
mReferenceFrame =
aBuilder->FindReferenceFrameFor(outerFrame);
mToReferenceFrame = mFrame->GetOffsetToCrossDoc(mReferenceFrame);
+ mAnimatedGeometryRoot = nsLayoutUtils::GetAnimatedGeometryRootForFrame(aBuilder, outerFrame);
mVisibleRect = aBuilder->GetDirtyRect() + mToReferenceFrame;
}
void
nsDisplayTransform::Init(nsDisplayListBuilder* aBuilder)
{
mHasBounds = false;
mStoredList.SetClip(aBuilder, DisplayItemClip::NoClip());
--- a/layout/base/nsDisplayList.h
+++ b/layout/base/nsDisplayList.h
@@ -1226,16 +1226,17 @@ public:
/**
* This constructor is only used in rare cases when we need to construct
* temporary items.
*/
explicit nsDisplayItem(nsIFrame* aFrame)
: mFrame(aFrame)
, mClip(nullptr)
, mReferenceFrame(nullptr)
+ , mAnimatedGeometryRoot(nullptr)
#ifdef MOZ_DUMP_PAINTING
, mPainted(false)
#endif
{
}
virtual ~nsDisplayItem() {}
void* operator new(size_t aSize,
@@ -1687,16 +1688,21 @@ public:
*/
const nsIFrame* ReferenceFrame() const { return mReferenceFrame; }
/**
* Returns the reference frame for display item children of this item.
*/
virtual const nsIFrame* ReferenceFrameForChildren() const { return mReferenceFrame; }
+ nsIFrame* AnimatedGeometryRoot() const {
+ MOZ_ASSERT(mAnimatedGeometryRoot, "Must have cached AGR before accessing it!");
+ return mAnimatedGeometryRoot;
+ }
+
/**
* Checks if this display item (or any children) contains content that might
* be rendered with component alpha (e.g. subpixel antialiasing). Returns the
* bounds of the area that needs component alpha, or an empty rect if nothing
* in the item does.
*/
virtual nsRect GetComponentAlphaBounds(nsDisplayListBuilder* aBuilder) { return nsRect(); }
@@ -1746,16 +1752,17 @@ protected:
friend class nsDisplayList;
nsDisplayItem() { mAbove = nullptr; }
nsIFrame* mFrame;
const DisplayItemClip* mClip;
// Result of FindReferenceFrameFor(mFrame), if mFrame is non-null
const nsIFrame* mReferenceFrame;
+ nsIFrame* mAnimatedGeometryRoot;
// Result of ToReferenceFrame(mFrame), if mFrame is non-null
nsPoint mToReferenceFrame;
// This is the rectangle that needs to be painted.
// Display item construction sets this to the dirty rect.
// nsDisplayList::ComputeVisibility sets this to the visible region
// of the item by intersecting the current visible region with the bounds
// of the item. Paint implementations can use this to limit their drawing.
// Guaranteed to be contained in GetBounds().
--- a/layout/base/nsLayoutDebugger.cpp
+++ b/layout/base/nsLayoutDebugger.cpp
@@ -157,42 +157,41 @@ PrintDisplayItemTo(nsDisplayListBuilder*
if (content->GetClasses()) {
content->GetClasses()->ToString(tmp);
contentData.AppendLiteral(" class:");
contentData.Append(tmp);
}
}
bool snap;
nsRect rect = aItem->GetBounds(aBuilder, &snap);
- nsRect layerRect = rect -
- nsLayoutUtils::GetAnimatedGeometryRootFor(aItem, aBuilder)->
- GetOffsetToCrossDoc(aItem->ReferenceFrame());
+ nsRect layerRect = rect - aItem->AnimatedGeometryRoot()->GetOffsetToCrossDoc(aItem->ReferenceFrame());
nscolor color;
nsRect vis = aItem->GetVisibleRect();
nsRect component = aItem->GetComponentAlphaBounds(aBuilder);
nsDisplayList* list = aItem->GetChildren();
const DisplayItemClip& clip = aItem->GetClip();
nsRegion opaque = aItem->GetOpaqueRegion(aBuilder, &snap);
#ifdef MOZ_DUMP_PAINTING
if (aDumpHtml && aItem->Painted()) {
nsCString string(aItem->Name());
string.Append('-');
string.AppendInt((uint64_t)aItem);
aStream << nsPrintfCString("<a href=\"javascript:ViewImage('%s')\">", string.BeginReading());
}
#endif
- aStream << nsPrintfCString("%s p=0x%p f=0x%p(%s) %sbounds(%d,%d,%d,%d) layerBounds(%d,%d,%d,%d) visible(%d,%d,%d,%d) componentAlpha(%d,%d,%d,%d) clip(%s) %s",
+ aStream << nsPrintfCString("%s p=0x%p f=0x%p(%s) %sbounds(%d,%d,%d,%d) layerBounds(%d,%d,%d,%d) visible(%d,%d,%d,%d) componentAlpha(%d,%d,%d,%d) clip(%s) %s ref=0x%p agr=0x%p",
aItem->Name(), aItem, (void*)f, NS_ConvertUTF16toUTF8(contentData).get(),
(aItem->ZIndex() ? nsPrintfCString("z=%d ", aItem->ZIndex()).get() : ""),
rect.x, rect.y, rect.width, rect.height,
layerRect.x, layerRect.y, layerRect.width, layerRect.height,
vis.x, vis.y, vis.width, vis.height,
component.x, component.y, component.width, component.height,
clip.ToString().get(),
- aItem->IsUniform(aBuilder, &color) ? " uniform" : "");
+ aItem->IsUniform(aBuilder, &color) ? " uniform" : "",
+ aItem->ReferenceFrame(), aItem->AnimatedGeometryRoot());
nsRegionRectIterator iter(opaque);
for (const nsRect* r = iter.Next(); r; r = iter.Next()) {
aStream << nsPrintfCString(" (opaque %d,%d,%d,%d)", r->x, r->y, r->width, r->height);
}
if (aItem->ShouldFixToViewport(aBuilder)) {
aStream << " fixed";
--- a/layout/base/nsLayoutUtils.cpp
+++ b/layout/base/nsLayoutUtils.cpp
@@ -1062,22 +1062,16 @@ GetDisplayPortFromMarginsData(nsIContent
float left = std::min(margins.left, float(budget));
float right = std::min(margins.right, budget - left);
screenRect.x -= left;
screenRect.width += left + right;
}
}
- // Inflate the rectangle by 1 so that we always push to the next tile
- // boundary. This is desirable to stop from having a rectangle with a
- // moving origin occasionally being smaller when it coincidentally lines
- // up to tile boundaries.
- screenRect.Inflate(1);
-
ScreenPoint scrollPosScreen = LayoutDevicePoint::FromAppUnits(scrollPos, auPerDevPixel)
* res;
// Round-out the display port to the nearest alignment (tiles)
screenRect += scrollPosScreen;
float x = alignment.width * floor(screenRect.x / alignment.width);
float y = alignment.height * floor(screenRect.y / alignment.height);
float w = alignment.width * ceil(screenRect.width / alignment.width + 1);
@@ -1956,16 +1950,35 @@ nsLayoutUtils::GetAnimatedGeometryRootFo
nsIFrame* parent = nsLayoutUtils::GetCrossDocParentFrame(f);
if (parent) {
return GetAnimatedGeometryRootForFrame(aBuilder, parent);
}
}
return GetAnimatedGeometryRootForFrame(aBuilder, f);
}
+nsIFrame*
+nsLayoutUtils::GetAnimatedGeometryRootForInit(nsDisplayItem* aItem,
+ nsDisplayListBuilder* aBuilder)
+{
+ nsIFrame* f = aItem->Frame();
+ if (aItem->ShouldFixToViewport(aBuilder)) {
+ // Make its active scrolled root be the active scrolled root of
+ // the enclosing viewport, since it shouldn't be scrolled by scrolled
+ // frames in its document. InvalidateFixedBackgroundFramesFromList in
+ // nsGfxScrollFrame will not repaint this item when scrolling occurs.
+ nsIFrame* viewportFrame =
+ nsLayoutUtils::GetClosestFrameOfType(f, nsGkAtoms::viewportFrame, aBuilder->RootReferenceFrame());
+ if (viewportFrame) {
+ return GetAnimatedGeometryRootForFrame(aBuilder, viewportFrame);
+ }
+ }
+ return GetAnimatedGeometryRootForFrame(aBuilder, f);
+}
+
// static
nsIScrollableFrame*
nsLayoutUtils::GetNearestScrollableFrameForDirection(nsIFrame* aFrame,
Direction aDirection)
{
NS_ASSERTION(aFrame, "GetNearestScrollableFrameForDirection expects a non-null frame");
for (nsIFrame* f = aFrame; f; f = nsLayoutUtils::GetCrossDocParentFrame(f)) {
nsIScrollableFrame* scrollableFrame = do_QueryFrame(f);
--- a/layout/base/nsLayoutUtils.h
+++ b/layout/base/nsLayoutUtils.h
@@ -558,16 +558,23 @@ public:
* do not do any special processing for background attachment fixed items,
* instead treating them like any other frame.
*/
AGR_IGNORE_BACKGROUND_ATTACHMENT_FIXED = 0x01
};
static nsIFrame* GetAnimatedGeometryRootFor(nsDisplayItem* aItem,
nsDisplayListBuilder* aBuilder,
uint32_t aFlags = 0);
+ /**
+ * Version of GetAnimatedGeometryRootFor that can be called in nsDisplayItem
+ * constructor, and only from there. Not valid for transform items, but they
+ * set their AGR in their constructor.
+ */
+ static nsIFrame* GetAnimatedGeometryRootForInit(nsDisplayItem* aItem,
+ nsDisplayListBuilder* aBuilder);
/**
* Finds the nearest ancestor frame to aFrame that is considered to have (or
* will have) "animated geometry". This could be aFrame.
*/
static nsIFrame* GetAnimatedGeometryRootForFrame(nsDisplayListBuilder* aBuilder,
nsIFrame* aFrame);
--- a/layout/generic/nsCanvasFrame.h
+++ b/layout/generic/nsCanvasFrame.h
@@ -234,17 +234,21 @@ public:
#endif
};
class nsDisplayCanvasBackgroundImage : public nsDisplayBackgroundImage {
public:
nsDisplayCanvasBackgroundImage(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
uint32_t aLayer, const nsStyleBackground* aBg)
: nsDisplayBackgroundImage(aBuilder, aFrame, aLayer, aBg)
- {}
+ {
+ if (ShouldFixToViewport(aBuilder)) {
+ mAnimatedGeometryRoot = nsLayoutUtils::GetAnimatedGeometryRootFor(this, aBuilder);
+ }
+ }
virtual void Paint(nsDisplayListBuilder* aBuilder, nsRenderingContext* aCtx) override;
virtual void NotifyRenderingChanged() override
{
mFrame->Properties().Delete(nsIFrame::CachedBackgroundImage());
mFrame->Properties().Delete(nsIFrame::CachedBackgroundImageDT());
}
--- a/layout/generic/nsGfxScrollFrame.cpp
+++ b/layout/generic/nsGfxScrollFrame.cpp
@@ -4747,35 +4747,41 @@ ScrollFrameHelper::FinishReflowForScroll
SetCoordAttribute(aContent, nsGkAtoms::maxpos, aMaxXY - aMinXY);
SetCoordAttribute(aContent, nsGkAtoms::pageincrement, aPageIncrement);
SetCoordAttribute(aContent, nsGkAtoms::increment, aIncrement);
}
bool
ScrollFrameHelper::ReflowFinished()
{
+ mPostedReflowCallback = false;
+
+ if (NS_SUBTREE_DIRTY(mOuter)) {
+ // We will get another call after the next reflow and scrolling
+ // later is less janky.
+ return false;
+ }
+
nsAutoScriptBlocker scriptBlocker;
- mPostedReflowCallback = false;
-
ScrollToRestoredPosition();
// Clamp current scroll position to new bounds. Normally this won't
// do anything.
nsPoint currentScrollPos = GetScrollPosition();
ScrollToImpl(currentScrollPos, nsRect(currentScrollPos, nsSize(0, 0)));
if (!mAsyncScroll && !mAsyncSmoothMSDScroll) {
// We need to have mDestination track the current scroll position,
// in case it falls outside the new reflow area. mDestination is used
// by ScrollBy as its starting position.
mDestination = GetScrollPosition();
}
- if (NS_SUBTREE_DIRTY(mOuter) || !mUpdateScrollbarAttributes)
+ if (!mUpdateScrollbarAttributes) {
return false;
-
+ }
mUpdateScrollbarAttributes = false;
// Update scrollbar attributes.
nsPresContext* presContext = mOuter->PresContext();
if (mMayHaveDirtyFixedChildren) {
mMayHaveDirtyFixedChildren = false;
nsIFrame* parentFrame = mOuter->GetParent();
--- a/media/libstagefright/binding/MP4Metadata.cpp
+++ b/media/libstagefright/binding/MP4Metadata.cpp
@@ -97,17 +97,19 @@ MP4Metadata::MP4Metadata(Stream* aSource
, mSource(aSource)
{
mPrivate->mMetadataExtractor =
new MPEG4Extractor(new DataSourceAdapter(mSource));
mPrivate->mCanSeek =
mPrivate->mMetadataExtractor->flags() & MediaExtractor::CAN_SEEK;
sp<MetaData> metaData = mPrivate->mMetadataExtractor->getMetaData();
- UpdateCrypto(metaData.get());
+ if (metaData.get()) {
+ UpdateCrypto(metaData.get());
+ }
}
MP4Metadata::~MP4Metadata()
{
}
#ifdef MOZ_RUST_MP4PARSE
#include "mp4parse.h"
@@ -312,16 +314,19 @@ MP4Metadata::ReadTrackIndex(FallibleTArr
}
int32_t
MP4Metadata::GetTrackNumber(mozilla::TrackID aTrackID)
{
size_t numTracks = mPrivate->mMetadataExtractor->countTracks();
for (size_t i = 0; i < numTracks; i++) {
sp<MetaData> metaData = mPrivate->mMetadataExtractor->getTrackMetaData(i);
+ if (!metaData.get()) {
+ continue;
+ }
int32_t value;
if (metaData->findInt32(kKeyTrackID, &value) && value == aTrackID) {
return i;
}
}
return -1;
}
--- a/media/libstagefright/frameworks/av/include/media/stagefright/MetaData.h
+++ b/media/libstagefright/frameworks/av/include/media/stagefright/MetaData.h
@@ -243,17 +243,17 @@ private:
void *ext_data;
float reservoir;
} u;
bool usesReservoir() const {
return mSize <= sizeof(u.reservoir);
}
- void allocateStorage(size_t size);
+ bool allocateStorage(size_t size);
void freeStorage();
void *storage() {
return usesReservoir() ? &u.reservoir : u.ext_data;
}
const void *storage() const {
return usesReservoir() ? &u.reservoir : u.ext_data;
--- a/media/libstagefright/frameworks/av/media/libstagefright/MPEG4Extractor.cpp
+++ b/media/libstagefright/frameworks/av/media/libstagefright/MPEG4Extractor.cpp
@@ -415,17 +415,17 @@ MPEG4Extractor::~MPEG4Extractor() {
uint32_t MPEG4Extractor::flags() const {
return CAN_PAUSE | CAN_SEEK_BACKWARD | CAN_SEEK_FORWARD | CAN_SEEK;
}
sp<MetaData> MPEG4Extractor::getMetaData() {
status_t err;
if ((err = readMetaData()) != OK) {
- return new MetaData;
+ return NULL;
}
return mFileMetaData;
}
size_t MPEG4Extractor::countTracks() {
status_t err;
if ((err = readMetaData()) != OK) {
@@ -509,24 +509,25 @@ status_t MPEG4Extractor::readMetaData()
}
CHECK_NE(err, (status_t)NO_INIT);
// copy pssh data into file metadata
uint64_t psshsize = 0;
for (size_t i = 0; i < mPssh.Length(); i++) {
psshsize += 20 + mPssh[i].datalen;
- }
- if (psshsize > kMAX_ALLOCATION) {
- return ERROR_MALFORMED;
+ if (mPssh[i].datalen > kMAX_ALLOCATION - 20 ||
+ psshsize > kMAX_ALLOCATION) {
+ return ERROR_MALFORMED;
+ }
}
if (psshsize) {
char *buf = (char*)malloc(psshsize);
if (!buf) {
- return ERROR_MALFORMED;
+ return -ENOMEM;
}
char *ptr = buf;
for (size_t i = 0; i < mPssh.Length(); i++) {
memcpy(ptr, mPssh[i].uuid, 20); // uuid + length
memcpy(ptr + 20, mPssh[i].data, mPssh[i].datalen);
ptr += (20 + mPssh[i].datalen);
}
mFileMetaData->setData(kKeyPssh, 'pssh', buf, psshsize);
@@ -680,17 +681,20 @@ status_t MPEG4Extractor::parseDrmSINF(of
SINF *sinf = mFirstSINF;
while (sinf && (sinf->IPMPDescriptorID != id)) {
sinf = sinf->next;
}
if (sinf == NULL) {
return ERROR_MALFORMED;
}
sinf->len = dataLen - 3;
- sinf->IPMPData = new char[sinf->len];
+ sinf->IPMPData = new (fallible) char[sinf->len];
+ if (!sinf->IPMPData) {
+ return -ENOMEM;
+ }
if (mDataSource->readAt(data_offset + 2, sinf->IPMPData, sinf->len) < sinf->len) {
return ERROR_IO;
}
data_offset += sinf->len;
size -= (dataLen + numOfBytes + 1);
}
@@ -1141,17 +1145,20 @@ status_t MPEG4Extractor::parseChunk(off6
}
if (mDataSource->readAt(data_offset + 4, &pssh.uuid, 16) < 16) {
return ERROR_IO;
}
// Copy the contents of the box (including header) verbatim.
pssh.datalen = chunk_data_size + 8;
- pssh.data = new uint8_t[pssh.datalen];
+ pssh.data = new (fallible) uint8_t[pssh.datalen];
+ if (!pssh.data) {
+ return -ENOMEM;
+ }
if (mDataSource->readAt(data_offset - 8, pssh.data, pssh.datalen) < pssh.datalen) {
return ERROR_IO;
}
mPssh.AppendElement(pssh);
*offset += chunk_size;
break;
@@ -1757,17 +1764,20 @@ status_t MPEG4Extractor::parseChunk(off6
case FOURCC('a', 'v', 'c', 'C'):
{
if (chunk_data_size < 7) {
ALOGE("short avcC chunk (%d bytes)", chunk_data_size);
return ERROR_MALFORMED;
}
- sp<ABuffer> buffer = new ABuffer(chunk_data_size);
+ sp<ABuffer> buffer = new (fallible) ABuffer(chunk_data_size);
+ if (!buffer.get()) {
+ return -ENOMEM;
+ }
if (mDataSource->readAt(
data_offset, buffer->data(), chunk_data_size) < chunk_data_size) {
return ERROR_IO;
}
if (!mLastTrack) {
return ERROR_MALFORMED;
@@ -1991,17 +2001,20 @@ status_t MPEG4Extractor::parseChunk(off6
kKeyTextFormatData, &type, &data, &size)) {
size = 0;
}
// Make sure (size + chunk_size) isn't going to overflow.
if (size >= kMAX_ALLOCATION - chunk_size) {
return ERROR_MALFORMED;
}
- uint8_t *buffer = new uint8_t[size + chunk_size];
+ uint8_t *buffer = new (fallible) uint8_t[size + chunk_size];
+ if (!buffer) {
+ return -ENOMEM;
+ }
if (size > 0) {
memcpy(buffer, data, size);
}
if ((size_t)(mDataSource->readAt(*offset, buffer + size, chunk_size))
< chunk_size) {
delete[] buffer;
@@ -2019,22 +2032,28 @@ status_t MPEG4Extractor::parseChunk(off6
break;
}
case FOURCC('c', 'o', 'v', 'r'):
{
if (mFileMetaData != NULL) {
ALOGV("chunk_data_size = %lld and data_offset = %lld",
chunk_data_size, data_offset);
- sp<ABuffer> buffer = new ABuffer(chunk_data_size + 1);
+ const int kSkipBytesOfDataBox = 16;
+ if (chunk_data_size <= kSkipBytesOfDataBox) {
+ return ERROR_MALFORMED;
+ }
+ sp<ABuffer> buffer = new (fallible) ABuffer(chunk_data_size + 1);
+ if (!buffer.get()) {
+ return -ENOMEM;
+ }
if (mDataSource->readAt(
data_offset, buffer->data(), chunk_data_size) != (ssize_t)chunk_data_size) {
return ERROR_IO;
}
- const int kSkipBytesOfDataBox = 16;
mFileMetaData->setData(
kKeyAlbumArt, MetaData::TYPE_NONE,
buffer->data() + kSkipBytesOfDataBox, chunk_data_size - kSkipBytesOfDataBox);
}
*offset += chunk_size;
break;
}
--- a/media/libstagefright/frameworks/av/media/libstagefright/MetaData.cpp
+++ b/media/libstagefright/frameworks/av/media/libstagefright/MetaData.cpp
@@ -215,73 +215,84 @@ bool MetaData::findData(uint32_t key, ui
const typed_data &item = mItems.valueAt(i);
item.getData(type, data, size);
return true;
}
MetaData::typed_data::typed_data()
- : mType(0),
+ : mType(TYPE_NONE),
mSize(0) {
}
MetaData::typed_data::~typed_data() {
clear();
}
MetaData::typed_data::typed_data(const typed_data &from)
: mType(from.mType),
mSize(0) {
- allocateStorage(from.mSize);
- memcpy(storage(), from.storage(), mSize);
+ if (allocateStorage(from.mSize)) {
+ memcpy(storage(), from.storage(), mSize);
+ }
}
MetaData::typed_data &MetaData::typed_data::operator=(
const MetaData::typed_data &from) {
if (this != &from) {
clear();
- mType = from.mType;
- allocateStorage(from.mSize);
- memcpy(storage(), from.storage(), mSize);
+ if (allocateStorage(from.mSize)) {
+ mType = from.mType;
+ memcpy(storage(), from.storage(), mSize);
+ }
}
return *this;
}
void MetaData::typed_data::clear() {
freeStorage();
- mType = 0;
+ mType = TYPE_NONE;
}
void MetaData::typed_data::setData(
uint32_t type, const void *data, size_t size) {
clear();
- mType = type;
- allocateStorage(size);
- memcpy(storage(), data, size);
+ if (allocateStorage(size)) {
+ mType = type;
+ memcpy(storage(), data, size);
+ }
}
void MetaData::typed_data::getData(
uint32_t *type, const void **data, size_t *size) const {
*type = mType;
*size = mSize;
*data = storage();
}
-void MetaData::typed_data::allocateStorage(size_t size) {
+bool MetaData::typed_data::allocateStorage(size_t size) {
+ // Update mSize now, as it is needed by usesReservoir() below.
+ // (mSize will be reset if the allocation fails further below.)
mSize = size;
if (usesReservoir()) {
- return;
+ return true;
}
u.ext_data = malloc(mSize);
+ if (!u.ext_data) {
+ mType = TYPE_NONE;
+ mSize = 0;
+ return false;
+ }
+ return true;
}
void MetaData::typed_data::freeStorage() {
if (!usesReservoir()) {
if (u.ext_data) {
free(u.ext_data);
u.ext_data = NULL;
}
--- a/media/libstagefright/gtest/TestParser.cpp
+++ b/media/libstagefright/gtest/TestParser.cpp
@@ -169,17 +169,18 @@ static const TestFileData testFiles[] =
{ "test_case_1181213.mp4", 0, 0, 0, 0 },
{ "test_case_1181215.mp4", 0, 0, 0, 0 },
{ "test_case_1181220.mp4", 0, 0, 0, 0 },
{ "test_case_1181223.mp4", 0, 0, 0, 0 },
{ "test_case_1181719.mp4", 0, 0, 0, 0 },
{ "test_case_1185230.mp4", 1, 320, 240, 1 },
{ "test_case_1187067.mp4", 1, 160, 90, 0 },
{ "test_case_1200326.mp4", 0, 0, 0, 0 },
- { "test_case_1204580.mp4", 1, 320, 180, 0 }
+ { "test_case_1204580.mp4", 1, 320, 180, 0 },
+ { "test_case_1216748.mp4", 0, 0, 0, 0 }
};
TEST(stagefright_MPEG4Metadata, test_case_mp4)
{
for (size_t test = 0; test < ArrayLength(testFiles); ++test) {
nsTArray<uint8_t> buffer = ReadTestFile(testFiles[test].mFilename);
ASSERT_FALSE(buffer.IsEmpty());
RefPtr<Stream> stream = new TestStream(buffer.Elements(), buffer.Length());
--- a/media/libstagefright/gtest/moz.build
+++ b/media/libstagefright/gtest/moz.build
@@ -17,16 +17,17 @@ TEST_HARNESS_FILES.gtest += [
'test_case_1181215.mp4',
'test_case_1181220.mp4',
'test_case_1181223.mp4',
'test_case_1181719.mp4',
'test_case_1185230.mp4',
'test_case_1187067.mp4',
'test_case_1200326.mp4',
'test_case_1204580.mp4',
+ 'test_case_1216748.mp4',
]
if CONFIG['MOZ_RUST']:
UNIFIED_SOURCES += ['TestMP4Rust.cpp',]
TEST_HARNESS_FILES.gtest += [
'../../../dom/media/test/street.mp4',
]
LOCAL_INCLUDES += [
new file mode 100755
index 0000000000000000000000000000000000000000..7072f53bec645e38947b1b2322c4f0704d9d31d5
GIT binary patch
literal 296
zc${NkV30^FsVvAXFfn3aU|<B%Kx_zPH{|B$mjUUV+_H=m1{A=(m4U%z4+A3u2q6F?
zsw#B80|Q>b1jH>RMTyx!IwCp0tO#VnSqs}15Js2=v=<#P;$TDVWDR3rV1{s2Qd3Hb
efwTzJe5ekvnH*3tj<llGR5&df|2{V*u>=6D*&+`B
--- a/media/mtransport/third_party/nICEr/src/stun/stun_client_ctx.c
+++ b/media/mtransport/third_party/nICEr/src/stun/stun_client_ctx.c
@@ -68,43 +68,44 @@ int nr_stun_client_ctx_create(char *labe
if(!(ctx->label=r_strdup(label)))
ABORT(R_NO_MEMORY);
ctx->sock=sock;
nr_socket_getaddr(sock,&ctx->my_addr);
nr_transport_addr_copy(&ctx->peer_addr,peer);
- if (NR_reg_get_uint4(NR_STUN_REG_PREF_CLNT_RETRANSMIT_TIMEOUT, &ctx->rto_ms)) {
- if (RTO != 0)
- ctx->rto_ms = RTO;
- else
- ctx->rto_ms = 100;
+ if (RTO != 0) {
+ ctx->rto_ms = RTO;
+ } else if (NR_reg_get_uint4(NR_STUN_REG_PREF_CLNT_RETRANSMIT_TIMEOUT, &ctx->rto_ms)) {
+ ctx->rto_ms = 100;
}
if (NR_reg_get_double(NR_STUN_REG_PREF_CLNT_RETRANSMIT_BACKOFF, &ctx->retransmission_backoff_factor))
- ctx->retransmission_backoff_factor = 2.0;
+ ctx->retransmission_backoff_factor = 2.0;
if (NR_reg_get_uint4(NR_STUN_REG_PREF_CLNT_MAXIMUM_TRANSMITS, &ctx->maximum_transmits))
- ctx->maximum_transmits = 7;
+ ctx->maximum_transmits = 7;
- if (NR_reg_get_uint4(NR_STUN_REG_PREF_CLNT_FINAL_RETRANSMIT_BACKOFF, &ctx->final_retransmit_backoff_ms))
- ctx->final_retransmit_backoff_ms = 16 * ctx->rto_ms;
+ if (NR_reg_get_uint4(NR_STUN_REG_PREF_CLNT_FINAL_RETRANSMIT_BACKOFF, &ctx->maximum_transmits_timeout_ms))
+ ctx->maximum_transmits_timeout_ms = 16 * ctx->rto_ms;
- ctx->mapped_addr_check_mask = NR_STUN_TRANSPORT_ADDR_CHECK_WILDCARD;
- if (NR_reg_get_char(NR_STUN_REG_PREF_ALLOW_LOOPBACK_ADDRS, &allow_loopback) ||
- !allow_loopback) {
- ctx->mapped_addr_check_mask |= NR_STUN_TRANSPORT_ADDR_CHECK_LOOPBACK;
- }
+ ctx->mapped_addr_check_mask = NR_STUN_TRANSPORT_ADDR_CHECK_WILDCARD;
+ if (NR_reg_get_char(NR_STUN_REG_PREF_ALLOW_LOOPBACK_ADDRS, &allow_loopback) ||
+ !allow_loopback) {
+ ctx->mapped_addr_check_mask |= NR_STUN_TRANSPORT_ADDR_CHECK_LOOPBACK;
+ }
- /* If we are doing TCP, compute the maximum timeout as if
- we retransmitted and then set the maximum number of
- transmits to 1 and the timeout to maximum timeout*/
if (ctx->my_addr.protocol == IPPROTO_TCP) {
- ctx->timeout_ms = ctx->final_retransmit_backoff_ms;
+ /* Because TCP is reliable there is only one final timeout value.
+ * We store the timeout value for TCP in here, because timeout_ms gets
+ * reset to 0 in client_reset() which gets called from client_start() */
+ ctx->maximum_transmits_timeout_ms = ctx->rto_ms *
+ pow(ctx->retransmission_backoff_factor,
+ ctx->maximum_transmits);
ctx->maximum_transmits = 1;
}
*ctxp=ctx;
_status=0;
abort:
if(_status){
@@ -388,28 +389,35 @@ static int nr_stun_client_send_request(n
ctx->request_ct++;
if (NR_STUN_GET_TYPE_CLASS(ctx->request->header.type) == NR_CLASS_INDICATION) {
/* no need to set the timer because indications don't receive a
* response */
}
else {
- if (ctx->request_ct < ctx->maximum_transmits) {
- ctx->timeout_ms *= ctx->retransmission_backoff_factor;
- ctx->timeout_ms += ctx->rto_ms;
+ if (ctx->request_ct >= ctx->maximum_transmits) {
+ /* Reliable transport only get here once. Unreliable get here for
+ * their final timeout. */
+ ctx->timeout_ms += ctx->maximum_transmits_timeout_ms;
+ }
+ else if (ctx->timeout_ms) {
+ /* exponential backoff */
+ ctx->timeout_ms *= ctx->retransmission_backoff_factor;
}
else {
- ctx->timeout_ms += ctx->final_retransmit_backoff_ms;
+ /* initial timeout unreliable transports */
+ ctx->timeout_ms = ctx->rto_ms;
}
r_log(NR_LOG_STUN,LOG_DEBUG,"STUN-CLIENT(%s): Next timer will fire in %u ms",ctx->label, ctx->timeout_ms);
gettimeofday(&ctx->timer_set, 0);
+ assert(ctx->timeout_ms);
NR_ASYNC_TIMER_SET(ctx->timeout_ms, nr_stun_client_timer_expired_cb, ctx, &ctx->timer_handle);
}
_status=0;
abort:
if (_status) {
nr_stun_client_failed(ctx);
}
--- a/media/mtransport/third_party/nICEr/src/stun/stun_client_ctx.h
+++ b/media/mtransport/third_party/nICEr/src/stun/stun_client_ctx.h
@@ -165,17 +165,17 @@ struct nr_stun_client_ctx_ {
nr_stun_client_results results;
char *nonce;
char *realm;
void *timer_handle;
int request_ct;
UINT4 rto_ms; /* retransmission time out */
double retransmission_backoff_factor;
UINT4 maximum_transmits;
- UINT4 final_retransmit_backoff_ms;
+ UINT4 maximum_transmits_timeout_ms;
UINT4 mapped_addr_check_mask; /* What checks to run on mapped addresses */
int timeout_ms;
struct timeval timer_set;
int retry_ct;
NR_async_cb finished_cb;
void *cb_arg;
nr_stun_message *request;
nr_stun_message *response;
--- a/mobile/android/base/gfx/DisplayPortCalculator.java
+++ b/mobile/android/base/gfx/DisplayPortCalculator.java
@@ -17,19 +17,16 @@ import android.util.Log;
import java.util.HashMap;
import java.util.Map;
final class DisplayPortCalculator {
private static final String LOGTAG = "GeckoDisplayPort";
private static final PointF ZERO_VELOCITY = new PointF(0, 0);
- // Keep this in sync with the TILEDLAYERBUFFER_TILE_SIZE defined in gfx/layers/TiledLayerBuffer.h
- private static final int TILE_SIZE = 256;
-
private static final String PREF_DISPLAYPORT_STRATEGY = "gfx.displayport.strategy";
private static final String PREF_DISPLAYPORT_FM_MULTIPLIER = "gfx.displayport.strategy_fm.multiplier";
private static final String PREF_DISPLAYPORT_FM_DANGER_X = "gfx.displayport.strategy_fm.danger_x";
private static final String PREF_DISPLAYPORT_FM_DANGER_Y = "gfx.displayport.strategy_fm.danger_y";
private static final String PREF_DISPLAYPORT_VB_MULTIPLIER = "gfx.displayport.strategy_vb.multiplier";
private static final String PREF_DISPLAYPORT_VB_VELOCITY_THRESHOLD = "gfx.displayport.strategy_vb.threshold";
private static final String PREF_DISPLAYPORT_VB_REVERSE_BUFFER = "gfx.displayport.strategy_vb.reverse_buffer";
private static final String PREF_DISPLAYPORT_VB_DANGER_X_BASE = "gfx.displayport.strategy_vb.danger_x_base";
@@ -168,30 +165,29 @@ final class DisplayPortCalculator {
float dangerZoneX = metrics.getWidth() * dangerZoneXMultiplier;
float dangerZoneY = metrics.getHeight() * dangerZoneYMultiplier;
rect = RectUtils.expand(rect, dangerZoneX, dangerZoneY);
// clamp to page bounds
return clampToPageBounds(rect, metrics);
}
/**
- * Expand the given margins such that when they are applied on the viewport, the resulting rect
- * does not have any partial tiles, except when it is clipped by the page bounds. This assumes
- * the tiles are TILE_SIZE by TILE_SIZE and start at the origin, such that there will always be
- * a tile at (0,0)-(TILE_SIZE,TILE_SIZE)).
+ * Calculate the display port by expanding the viewport by the specified
+ * margins, then clamping to the page size.
*/
- private static DisplayPortMetrics getTileAlignedDisplayPortMetrics(RectF margins, float zoom, ImmutableViewportMetrics metrics) {
+ private static DisplayPortMetrics getPageClampedDisplayPortMetrics(RectF margins, float zoom, ImmutableViewportMetrics metrics) {
float left = metrics.viewportRectLeft - margins.left;
float top = metrics.viewportRectTop - margins.top;
float right = metrics.viewportRectRight() + margins.right;
float bottom = metrics.viewportRectBottom() + margins.bottom;
- left = (float) Math.max(metrics.pageRectLeft, TILE_SIZE * Math.floor(left / TILE_SIZE));
- top = (float) Math.max(metrics.pageRectTop, TILE_SIZE * Math.floor(top / TILE_SIZE));
- right = (float) Math.min(metrics.pageRectRight, TILE_SIZE * Math.ceil(right / TILE_SIZE));
- bottom = (float) Math.min(metrics.pageRectBottom, TILE_SIZE * Math.ceil(bottom / TILE_SIZE));
+ left = Math.max(metrics.pageRectLeft, left);
+ top = Math.max(metrics.pageRectTop, top);
+ right = Math.min(metrics.pageRectRight, right);
+ bottom = Math.min(metrics.pageRectBottom, bottom);
+
return new DisplayPortMetrics(left, top, right, bottom, zoom);
}
/**
* Adjust the given margins so if they are applied on the viewport in the metrics, the resulting rect
* does not exceed the page bounds. This code will maintain the total margin amount for a given axis;
* it assumes that margins.left + metrics.getWidth() + margins.right is less than or equal to
* metrics.getPageWidth(); and the same for the y axis.
@@ -306,17 +302,17 @@ final class DisplayPortCalculator {
// is split).
RectF margins = new RectF();
margins.left = horizontalBuffer / 2.0f;
margins.right = horizontalBuffer - margins.left;
margins.top = verticalBuffer / 2.0f;
margins.bottom = verticalBuffer - margins.top;
margins = shiftMarginsForPageBounds(margins, metrics);
- return getTileAlignedDisplayPortMetrics(margins, metrics.zoomFactor, metrics);
+ return getPageClampedDisplayPortMetrics(margins, metrics.zoomFactor, metrics);
}
@Override
public boolean aboutToCheckerboard(ImmutableViewportMetrics metrics, PointF velocity, DisplayPortMetrics displayPort) {
// Increase the size of the viewport based on the danger zone multiplier (and clamp to page
// boundaries), and intersect it with the current displayport to determine whether we're
// close to checkerboarding.
RectF adjustedViewport = expandByDangerZone(metrics.getViewport(), DANGER_ZONE_X_MULTIPLIER, DANGER_ZONE_Y_MULTIPLIER, metrics);
@@ -417,17 +413,17 @@ final class DisplayPortCalculator {
float horizontalBuffer = displayPortWidth - metrics.getWidth();
float verticalBuffer = displayPortHeight - metrics.getHeight();
// split the buffer amounts into margins based on velocity, and shift it to
// take into account the page bounds
RectF margins = velocityBiasedMargins(horizontalBuffer, verticalBuffer, velocity);
margins = shiftMarginsForPageBounds(margins, metrics);
- return getTileAlignedDisplayPortMetrics(margins, metrics.zoomFactor, metrics);
+ return getPageClampedDisplayPortMetrics(margins, metrics.zoomFactor, metrics);
}
@Override
public boolean aboutToCheckerboard(ImmutableViewportMetrics metrics, PointF velocity, DisplayPortMetrics displayPort) {
// calculate the danger zone amounts based on the prefs
float dangerZoneX = metrics.getWidth() * (DANGER_ZONE_BASE_X_MULTIPLIER + (velocity.x * DANGER_ZONE_INCR_X_MULTIPLIER));
float dangerZoneY = metrics.getHeight() * (DANGER_ZONE_BASE_Y_MULTIPLIER + (velocity.y * DANGER_ZONE_INCR_Y_MULTIPLIER));
// clamp it such that when added to the viewport, they don't exceed page size.
@@ -684,17 +680,17 @@ final class DisplayPortCalculator {
public DisplayPortMetrics calculate(ImmutableViewportMetrics metrics, PointF velocity) {
float width = metrics.getWidth();
float height = metrics.getHeight();
mPixelArea = (int)(width * height);
if (velocity.length() < VELOCITY_THRESHOLD) {
// if we're going slow, expand the displayport to 9x viewport size
RectF margins = new RectF(width, height, width, height);
- return getTileAlignedDisplayPortMetrics(margins, metrics.zoomFactor, metrics);
+ return getPageClampedDisplayPortMetrics(margins, metrics.zoomFactor, metrics);
}
// figure out how far we expect to be
float minDx = velocity.x * mMinFramesToDraw;
float minDy = velocity.y * mMinFramesToDraw;
float maxDx = velocity.x * mMaxFramesToDraw;
float maxDy = velocity.y * mMaxFramesToDraw;
@@ -709,17 +705,17 @@ final class DisplayPortCalculator {
// and finally generate the displayport. the min/max stuff takes care of
// negative velocities as well as positive.
RectF margins = new RectF(
-Math.min(minDx, maxDx),
-Math.min(minDy, maxDy),
Math.max(minDx, maxDx),
Math.max(minDy, maxDy));
- return getTileAlignedDisplayPortMetrics(margins, metrics.zoomFactor, metrics);
+ return getPageClampedDisplayPortMetrics(margins, metrics.zoomFactor, metrics);
}
@Override
public boolean aboutToCheckerboard(ImmutableViewportMetrics metrics, PointF velocity, DisplayPortMetrics displayPort) {
// the code below is the same as in calculate() but is awkward to refactor since it has multiple outputs.
// refer to the comments in calculate() to understand what this is doing.
float minDx = velocity.x * mMinFramesToDraw;
float minDy = velocity.y * mMinFramesToDraw;
--- a/mobile/android/base/locales/en-US/android_strings.dtd
+++ b/mobile/android/base/locales/en-US/android_strings.dtd
@@ -730,16 +730,17 @@ just addresses the organization to follo
a restricted profile (e.g. child). Used inside the Android settings UI. -->
<!ENTITY restriction_disallow_addons_title2 "Disable add-on installation">
<!ENTITY restriction_disallow_private_browsing_title2 "Disable Private Browsing">
<!ENTITY restriction_disallow_location_services_title2 "Disable Location Services">
<!ENTITY restriction_disallow_clear_history_title2 "Disable \'Clear browsing history\'">
<!ENTITY restriction_disallow_master_password_title2 "Disable master password">
<!ENTITY restriction_disallow_guest_browsing_title2 "Disable Guest Browsing">
<!ENTITY restriction_disallow_advanced_settings_title "Disable Advanced Settings">
+<!ENTITY restriction_disallow_camera_microphone_title "Block camera and microphone">
<!-- Default Bookmarks titles-->
<!-- LOCALIZATION NOTE (bookmarks_about_browser): link title for about:fennec -->
<!ENTITY bookmarks_about_browser "Firefox: About your browser">
<!-- LOCALIZATION NOTE (bookmarks_addons): link title for https://addons.mozilla.org/en-US/mobile -->
<!ENTITY bookmarks_addons "Firefox: Customize with add-ons">
<!-- LOCALIZATION NOTE (bookmarks_support): link title for https://support.mozilla.org/ -->
<!ENTITY bookmarks_support "Firefox: Support">
--- a/mobile/android/base/restrictions/RestrictedProfileConfiguration.java
+++ b/mobile/android/base/restrictions/RestrictedProfileConfiguration.java
@@ -23,17 +23,18 @@ public class RestrictedProfileConfigurat
static List<Restriction> DEFAULT_RESTRICTIONS = Arrays.asList(
Restriction.DISALLOW_INSTALL_EXTENSION,
Restriction.DISALLOW_PRIVATE_BROWSING,
Restriction.DISALLOW_LOCATION_SERVICE,
Restriction.DISALLOW_CLEAR_HISTORY,
Restriction.DISALLOW_MASTER_PASSWORD,
Restriction.DISALLOW_GUEST_BROWSING,
Restriction.DISALLOW_DEFAULT_THEME,
- Restriction.DISALLOW_ADVANCED_SETTINGS
+ Restriction.DISALLOW_ADVANCED_SETTINGS,
+ Restriction.DISALLOW_CAMERA_MICROPHONE
);
private Context context;
private Bundle cachedRestrictions;
private boolean isCacheInvalid = true;
public RestrictedProfileConfiguration(Context context) {
this.context = context.getApplicationContext();
--- a/mobile/android/base/restrictions/Restriction.java
+++ b/mobile/android/base/restrictions/Restriction.java
@@ -47,17 +47,19 @@ public enum Restriction {
DISALLOW_CLEAR_HISTORY(14, "no_clear_history", R.string.restriction_disallow_clear_history_title),
DISALLOW_MASTER_PASSWORD(15, "no_master_password", R.string.restriction_disallow_master_password_title),
DISALLOW_GUEST_BROWSING(16, "no_guest_browsing", R.string.restriction_disallow_guest_browsing_title),
DISALLOW_DEFAULT_THEME(17, "no_default_theme", 0),
- DISALLOW_ADVANCED_SETTINGS(18, "no_advanced_settings", R.string.restriction_disallow_advanced_settings_title);
+ DISALLOW_ADVANCED_SETTINGS(18, "no_advanced_settings", R.string.restriction_disallow_advanced_settings_title),
+
+ DISALLOW_CAMERA_MICROPHONE(19, "no_camera_microphone", R.string.restriction_disallow_camera_microphone_title);
public final int id;
public final String name;
@StringRes
public final int title;
Restriction(final int id, final String name, @StringRes int title) {
--- a/mobile/android/base/strings.xml.in
+++ b/mobile/android/base/strings.xml.in
@@ -574,16 +574,17 @@
<!-- Restrictions -->
<string name="restriction_disallow_addons_title">&restriction_disallow_addons_title2;</string>
<string name="restriction_disallow_private_browsing_title">&restriction_disallow_private_browsing_title2;</string>
<string name="restriction_disallow_location_services_title">&restriction_disallow_location_services_title2;</string>
<string name="restriction_disallow_clear_history_title">&restriction_disallow_clear_history_title2;</string>
<string name="restriction_disallow_master_password_title">&restriction_disallow_master_password_title2;</string>
<string name="restriction_disallow_guest_browsing_title">&restriction_disallow_guest_browsing_title2;</string>
<string name="restriction_disallow_advanced_settings_title">&restriction_disallow_advanced_settings_title;</string>
+ <string name="restriction_disallow_camera_microphone_title">&restriction_disallow_camera_microphone_title;</string>
<!-- Miscellaneous -->
<string name="ellipsis">&ellipsis;</string>
<string name="colon">:</string>
<string name="percent">&percent;</string>
--- a/mobile/android/chrome/content/WebrtcUI.js
+++ b/mobile/android/chrome/content/WebrtcUI.js
@@ -1,16 +1,17 @@
/* 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";
this.EXPORTED_SYMBOLS = ["WebrtcUI"];
XPCOMUtils.defineLazyModuleGetter(this, "Notifications", "resource://gre/modules/Notifications.jsm");
+XPCOMUtils.defineLazyServiceGetter(this, "ParentalControls", "@mozilla.org/parental-controls-service;1", "nsIParentalControlsService");
var WebrtcUI = {
_notificationId: null,
// Add-ons can override stock permission behavior by doing:
//
// var stockObserve = WebrtcUI.observe;
//
@@ -103,16 +104,22 @@ var WebrtcUI = {
handleGumRequest: function handleGumRequest(aSubject, aTopic, aData) {
let constraints = aSubject.getConstraints();
let contentWindow = Services.wm.getOuterWindowWithId(aSubject.windowID);
contentWindow.navigator.mozGetUserMediaDevices(
constraints,
function (devices) {
+ if (!ParentalControls.isAllowed(ParentalControls.CAMERA_MICROPHONE)) {
+ Services.obs.notifyObservers(null, "getUserMedia:response:deny", aSubject.callID);
+ WebrtcUI.showBlockMessage(devices);
+ return;
+ }
+
WebrtcUI.prompt(contentWindow, aSubject.callID, constraints.audio,
constraints.video, devices);
},
function (error) {
Cu.reportError(error);
},
aSubject.innerWindowID);
},
@@ -182,16 +189,41 @@ var WebrtcUI = {
label: Strings.browser.GetStringFromName("getUserMedia." + aType + ".prompt"),
values: list
});
}
}
},
+ showBlockMessage: function(aDevices) {
+ let microphone = false;
+ let camera = false;
+
+ for (let device of aDevices) {
+ device = device.QueryInterface(Ci.nsIMediaDevice);
+ if (device.type == "audio") {
+ microphone = true;
+ } else if (device.type == "video") {
+ camera = true;
+ }
+ }
+
+ let message;
+ if (microphone && !camera) {
+ message = Strings.browser.GetStringFromName("getUserMedia.blockedMicrophoneAccess");
+ } else if (camera && !microphone) {
+ message = Strings.browser.GetStringFromName("getUserMedia.blockedCameraAccess");
+ } else {
+ message = Strings.browser.GetStringFromName("getUserMedia.blockedCameraAndMicrophoneAccess");
+ }
+
+ NativeWindow.doorhanger.show(message, "webrtc-blocked", [], BrowserApp.selectedTab.id, {});
+ },
+
prompt: function prompt(aContentWindow, aCallID, aAudioRequested,
aVideoRequested, aDevices) {
let audioDevices = [];
let videoDevices = [];
for (let device of aDevices) {
device = device.QueryInterface(Ci.nsIMediaDevice);
switch (device.type) {
case "audio":
--- a/mobile/android/locales/en-US/chrome/browser.properties
+++ b/mobile/android/locales/en-US/chrome/browser.properties
@@ -392,16 +392,19 @@ getUserMedia.videoSource.none = No Video
getUserMedia.videoSource.tabShare = Choose a tab to stream
getUserMedia.videoSource.prompt = Video source
getUserMedia.audioDevice.default = Microphone %S
getUserMedia.audioDevice.none = No Audio
getUserMedia.audioDevice.prompt = Microphone to use
getUserMedia.sharingCamera.message2 = Camera is on
getUserMedia.sharingMicrophone.message2 = Microphone is on
getUserMedia.sharingCameraAndMicrophone.message2 = Camera and microphone are on
+getUserMedia.blockedCameraAccess = Camera has been blocked.
+getUserMedia.blockedMicrophoneAccess = Microphone has been blocked.
+getUserMedia.blockedCameraAndMicrophoneAccess = Camera and microphone have been blocked.
# LOCALIZATION NOTE (readerMode.toolbarTip):
# Tip shown to users the first time we hide the reader mode toolbar.
readerMode.toolbarTip=Tap the screen to show reader options
#Open in App
openInApp.pageAction = Open in App
openInApp.ok = OK
--- a/security/manager/ssl/WeakCryptoOverride.cpp
+++ b/security/manager/ssl/WeakCryptoOverride.cpp
@@ -3,16 +3,17 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "WeakCryptoOverride.h"
#include "MainThreadUtils.h"
#include "SharedSSLState.h"
+#include "nss.h"
using namespace mozilla;
using namespace mozilla::psm;
NS_IMPL_ISUPPORTS(WeakCryptoOverride,
nsIWeakCryptoOverride)
WeakCryptoOverride::WeakCryptoOverride()
@@ -53,10 +54,14 @@ WeakCryptoOverride::RemoveWeakCryptoOver
SharedSSLState* sharedState = aPrivate ? PrivateSSLState()
: PublicSSLState();
if (!sharedState) {
return NS_ERROR_NOT_AVAILABLE;
}
const nsPromiseFlatCString& host = PromiseFlatCString(aHostName);
sharedState->IOLayerHelpers().removeInsecureFallbackSite(host, aPort);
+ // Some servers will fail with SSL_ERROR_ILLEGAL_PARAMETER_ALERT
+ // unless the session cache is cleared.
+ SSL_ClearSessionCache();
+
return NS_OK;
}
--- a/testing/marionette/client/marionette/tests/unit/test_emulator.py
+++ b/testing/marionette/client/marionette/tests/unit/test_emulator.py
@@ -7,40 +7,37 @@ from unittest import skip
from marionette.marionette_test import MarionetteTestCase, skip_if_desktop, skip_unless_protocol
from marionette_driver.errors import MarionetteException, JavascriptException
class TestEmulatorContent(MarionetteTestCase):
@skip_if_desktop
def test_emulator_cmd(self):
self.marionette.set_script_timeout(10000)
- expected = ["<build>",
- "OK"]
- result = self.marionette.execute_async_script("""
- runEmulatorCmd("avd name", marionetteScriptFinished)
- """);
- self.assertEqual(result, expected)
+ expected = ["<build>", "OK"]
+ res = self.marionette.execute_async_script(
+ "runEmulatorCmd('avd name', marionetteScriptFinished)");
+ self.assertEqual(res, expected)
@skip_if_desktop
def test_emulator_shell(self):
self.marionette.set_script_timeout(10000)
expected = ["Hello World!"]
- result = self.marionette.execute_async_script("""
- runEmulatorShell(["echo", "Hello World!"], marionetteScriptFinished)
- """);
- self.assertEqual(result, expected)
+ res = self.marionette.execute_async_script(
+ "runEmulatorShell(['echo', 'Hello World!'], marionetteScriptFinished)")
+ self.assertEqual(res, expected)
@skip_if_desktop
def test_emulator_order(self):
self.marionette.set_script_timeout(10000)
- self.assertRaises(MarionetteException,
- self.marionette.execute_async_script,
- """runEmulatorCmd("gsm status", function(result) {});
- marionetteScriptFinished(true);
- """);
+ with self.assertRaises(MarionetteException):
+ self.marionette.execute_async_script("""
+ runEmulatorCmd("gsm status", function(res) {});
+ marionetteScriptFinished(true);
+ """)
class TestEmulatorChrome(TestEmulatorContent):
def setUp(self):
super(TestEmulatorChrome, self).setUp()
self.marionette.set_context("chrome")
@@ -127,11 +124,29 @@ class TestEmulatorCallbacks(MarionetteTe
self.assertEqual("shell response", res)
@skip_unless_protocol(lambda level: level >= 3)
def test_emulator_result_error_chrome(self):
with self.marionette.using_context("chrome"):
with self.assertRaisesRegexp(JavascriptException, "TypeError"):
self.marionette.execute_async_script("runEmulatorCmd()")
+ def test_multiple_callbacks(self):
+ res = self.marionette.execute_async_script("""
+ runEmulatorCmd("what");
+ runEmulatorCmd("ho");
+ marionetteScriptFinished("Frobisher");
+ """)
+ self.assertEqual("Frobisher", res)
+
+ # This should work, but requires work on emulator callbacks:
+ """
+ def test_multiple_nested_callbacks(self):
+ res = self.marionette.execute_async_script('''
+ runEmulatorCmd("what", function(res) {
+ runEmulatorCmd("ho", marionetteScriptFinished);
+ });''')
+ self.assertEqual("cmd response", res)
+ """
+
def escape(word):
return "'%s'" % word
--- a/testing/marionette/driver/marionette_driver/marionette.py
+++ b/testing/marionette/driver/marionette_driver/marionette.py
@@ -666,16 +666,37 @@ class Marionette(object):
finally:
s.close()
def wait_for_port(self, timeout=60):
return transport.wait_for_port(self.host, self.port, timeout=timeout)
@do_crash_check
def _send_message(self, name, params=None, key=None):
+ """Send a blocking message to the server.
+
+ Marionette provides an asynchronous, non-blocking interface and
+ this attempts to paper over this by providing a synchronous API
+ to the user.
+
+ In particular, the Python client can be instructed to carry out
+ a sequence of instructions on the connected emulator. For this
+ reason, if ``execute_script``, ``execute_js_script``, or
+ ``execute_async_script`` is called, it will loop until all
+ commands requested from the server have been exhausted, and we
+ receive our expected response.
+
+ :param name: Requested command key.
+ :param params: Optional dictionary of key/value arguments.
+ :param key: Optional key to extract from response.
+
+ :returns: Full response from the server, or if `key` is given,
+ the value of said key in the response.
+ """
+
if not self.session_id and name != "newSession":
raise errors.MarionetteException("Please start a session")
try:
if self.protocol < 3:
data = {"name": name}
if params:
data["parameters"] = params
@@ -687,23 +708,26 @@ class Marionette(object):
except IOError:
if self.instance and not hasattr(self.instance, 'detached'):
# If we've launched the binary we've connected to, wait
# for it to shut down.
returncode = self.instance.runner.wait(timeout=self.DEFAULT_STARTUP_TIMEOUT)
raise IOError("process died with returncode %s" % returncode)
raise
+
except socket.timeout:
self.session = None
self.window = None
self.client.close()
raise errors.TimeoutException("Connection timed out")
- if isinstance(msg, transport.Command):
+ # support execution of commands on the client,
+ # loop until we receive our expected response
+ while isinstance(msg, transport.Command):
if msg.name == "runEmulatorCmd":
self.emulator_callback_id = msg.params.get("id")
msg = self._emulator_cmd(msg.params["emulator_cmd"])
elif msg.name == "runEmulatorShell":
self.emulator_callback_id = msg.params.get("id")
msg = self._emulator_shell(msg.params["emulator_shell"])
else:
raise IOError("Unknown command: %s" % msg)
--- a/testing/marionette/proxy.js
+++ b/testing/marionette/proxy.js
@@ -109,17 +109,17 @@ ContentSender.prototype.send = function(
this.curId = uuidgen.generateUUID().toString();
let proxy = new Promise((resolve, reject) => {
let removeListeners = (n, fn) => {
let rmFn = msg => {
if (this.curId !== msg.json.command_id) {
logger.warn("Skipping out-of-sync response from listener: " +
- `Expected response to \`${name}' with ID ${this.curId}, ` +
+ `Expected response to ${name} with ID ${this.curId}, ` +
"but got: " + msg.name + msg.json.toSource());
return;
}
this.removeListeners();
modal.removeHandler(handleDialog);
fn(msg);
--- a/testing/web-platform/mozilla/tests/service-workers/service-worker/serviceworkerobject-scripturl.https.html
+++ b/testing/web-platform/mozilla/tests/service-workers/service-worker/serviceworkerobject-scripturl.https.html
@@ -16,12 +16,11 @@ function url_test(name, url) {
'Returned ServiceWorker object should have scriptURL');
service_worker_unregister_and_done(t, scope);
})
.catch(unreached_rejection(t));
}, 'Verify the scriptURL property: ' + name);
}
url_test('relative', 'resources/empty-worker.js');
-url_test('with-fragment', 'resources/empty-worker.js#ref');
url_test('absolute', (new URL('./resources/empty-worker.js', window.location)).href);
</script>
--- a/toolkit/components/parentalcontrols/nsIParentalControlsService.idl
+++ b/toolkit/components/parentalcontrols/nsIParentalControlsService.idl
@@ -6,17 +6,17 @@
#include "nsISupports.idl"
interface nsIURI;
interface nsIFile;
interface nsIInterfaceRequestor;
interface nsIArray;
-[scriptable, uuid(8c4962aa-e0e0-482e-b1db-7846cb78b3d6)]
+[scriptable, uuid(ca8eb4f8-89bc-4479-91c9-1f381e045ed7)]
interface nsIParentalControlsService : nsISupports
{
/**
* Action types that can be blocked for users.
*/
const short DOWNLOAD = 1; // Downloading files
const short INSTALL_EXTENSION = 2; // Installing extensions
const short INSTALL_APP = 3; // Installing webapps
@@ -30,16 +30,17 @@ interface nsIParentalControlsService : n
const short IMPORT_SETTINGS = 11; // Importing settings from other apps
const short PRIVATE_BROWSING = 12; // Disallow usage of private browsing
const short LOCATION_SERVICE = 13; // Sharing of location data to location service
const short CLEAR_HISTORY = 14; // Clear browsing history
const short MASTER_PASSWORD = 15; // Setting master password for logins
const short GUEST_BROWSING = 16; // Disallow usage of guest browsing
const short DEFAULT_THEME = 17; // Use default theme or a special parental controls theme
const short ADVANCED_SETTINGS = 18; // Advanced settings
+ const short CAMERA_MICROPHONE = 19; // Camera and microphone (WebRTC)
/**
* @returns true if the current user account has parental controls
* restrictions enabled.
*/
readonly attribute boolean parentalControlsEnabled;
/**
--- a/toolkit/components/passwordmgr/LoginManagerContent.jsm
+++ b/toolkit/components/passwordmgr/LoginManagerContent.jsm
@@ -17,16 +17,20 @@ Cu.import("resource://gre/modules/Promis
XPCOMUtils.defineLazyModuleGetter(this, "DeferredTask", "resource://gre/modules/DeferredTask.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "LoginRecipesContent",
"resource://gre/modules/LoginRecipes.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "LoginHelper",
"resource://gre/modules/LoginHelper.jsm");
+XPCOMUtils.defineLazyServiceGetter(this, "gContentSecurityManager",
+ "@mozilla.org/contentsecuritymanager;1",
+ "nsIContentSecurityManager");
+
XPCOMUtils.defineLazyGetter(this, "log", () => {
let logger = LoginHelper.createLogger("LoginManagerContent");
return logger.log.bind(logger);
});
// These mirror signon.* prefs.
var gEnabled, gAutofillForms, gStoreWhenAutocompleteOff;
@@ -1125,17 +1129,19 @@ var LoginManagerContent = {
* both places. Look at
* https://bugzilla.mozilla.org/show_bug.cgi?id=899099 for more info.
*/
checkIfURIisSecure : function(uri) {
let isSafe = false;
let netutil = Cc["@mozilla.org/network/util;1"].getService(Ci.nsINetUtil);
let ph = Ci.nsIProtocolHandler;
- if (netutil.URIChainHasFlags(uri, ph.URI_IS_LOCAL_RESOURCE) ||
+ // Is the connection to localhost? Consider localhost safe for passwords.
+ if (gContentSecurityManager.isURIPotentiallyTrustworthy(uri) ||
+ netutil.URIChainHasFlags(uri, ph.URI_IS_LOCAL_RESOURCE) ||
netutil.URIChainHasFlags(uri, ph.URI_DOES_NOT_RETURN_DATA) ||
netutil.URIChainHasFlags(uri, ph.URI_INHERITS_SECURITY_CONTEXT) ||
netutil.URIChainHasFlags(uri, ph.URI_SAFE_TO_LOAD_IN_SECURE_CONTEXT)) {
isSafe = true;
}
return isSafe;
--- a/toolkit/components/telemetry/TelemetryController.jsm
+++ b/toolkit/components/telemetry/TelemetryController.jsm
@@ -759,17 +759,16 @@ var Impl = {
// Load the ClientID.
this._clientID = yield ClientID.getClientID();
// Purge the pings archive by removing outdated pings. We don't wait for this
// task to complete, but TelemetryStorage blocks on it during shutdown.
TelemetryStorage.runCleanPingArchiveTask();
- Telemetry.asyncFetchTelemetryData(function () {});
this._delayedInitTaskDeferred.resolve();
} catch (e) {
this._delayedInitTaskDeferred.reject(e);
} finally {
this._delayedInitTask = null;
}
}.bind(this), this._testMode ? TELEMETRY_TEST_DELAY : TELEMETRY_DELAY);
deleted file mode 100644
--- a/toolkit/crashreporter/google-breakpad/src/common/mac/Makefile.in
+++ /dev/null
@@ -1,1 +0,0 @@
-%/dump_syms.mm: ;
deleted file mode 100644
--- a/toolkit/crashreporter/google-breakpad/src/tools/mac/dump_syms/Makefile.in
+++ /dev/null
@@ -1,1 +0,0 @@
-%/dump_syms_tool.mm: ;
--- a/toolkit/modules/ClientID.jsm
+++ b/toolkit/modules/ClientID.jsm
@@ -169,16 +169,19 @@ var ClientIDImpl = {
getCachedClientID: function() {
if (this._clientID) {
// Already loaded the client id from disk.
return this._clientID;
}
// Not yet loaded, return the cached client id if we have one.
let id = Preferences.get(PREF_CACHED_CLIENTID, null);
+ if (id === null) {
+ return null;
+ }
if (!isValidClientID(id)) {
this._log.error("getCachedClientID - invalid client id in preferences, resetting", id);
Preferences.reset(PREF_CACHED_CLIENTID);
return null;
}
return id;
},
--- a/xpcom/idl-parser/xpidl/header.py
+++ b/xpcom/idl-parser/xpidl/header.py
@@ -106,16 +106,29 @@ def paramlistAsNative(m, empty='void'):
if not m.notxpcom and m.realtype.name != 'void':
l.append(paramAsNative(xpidl.Param(paramtype='out',
type=None,
name='_retval',
attlist=[],
location=None,
realtype=m.realtype)))
+ # Set any optional out params to default to nullptr. Skip if we just added
+ # extra non-optional args to l.
+ if len(l) == len(m.params):
+ paramIter = len(m.params) - 1
+ while (paramIter >= 0 and m.params[paramIter].optional and
+ m.params[paramIter].paramtype == "out"):
+ t = m.params[paramIter].type
+ # Strings can't be optional, so this shouldn't happen, but let's make sure:
+ if t == "AString" or t == "ACString" or t == "DOMString" or t == "AUTF8String":
+ break
+ l[paramIter] += " = nullptr"
+ paramIter -= 1
+
if len(l) == 0:
return empty
return ", ".join(l)
def paramAsNative(p):
return "%s%s" % (p.nativeType(),