зеркало из https://github.com/mozilla/gecko-dev.git
Merge m-c to b2ginbound, a=merge
--HG-- extra : commitid : FcH9KT7v2Ab
This commit is contained in:
Коммит
20d7e77456
|
@ -2836,8 +2836,7 @@ var BrowserOnClick = {
|
|||
.getService(Ci.nsIWeakCryptoOverride);
|
||||
weakCryptoOverride.addWeakCryptoOverride(
|
||||
msg.data.location.hostname,
|
||||
PrivateBrowsingUtils.isBrowserPrivate(gBrowser.selectedBrowser),
|
||||
true /* temporary */);
|
||||
PrivateBrowsingUtils.isBrowserPrivate(gBrowser.selectedBrowser));
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
@ -7140,6 +7139,7 @@ var gIdentityHandler = {
|
|||
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
|
||||
|
@ -7288,6 +7288,55 @@ var gIdentityHandler = {
|
|||
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
|
||||
|
|
|
@ -2,8 +2,7 @@
|
|||
* 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
|
||||
|
@ -22,8 +21,13 @@ add_task(function* test_simple() {
|
|||
"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),
|
||||
|
@ -36,7 +40,7 @@ add_task(function* test_simple() {
|
|||
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");
|
||||
|
@ -64,8 +68,8 @@ add_task(function* test_simple() {
|
|||
// 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);
|
||||
|
@ -82,6 +86,7 @@ add_task(function* test_mixedcontent() {
|
|||
}, 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([
|
||||
|
|
|
@ -391,8 +391,7 @@ html[dir="rtl"] .room-entry-context-actions > .dropdown-menu {
|
|||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.room-entry-context-item > img {
|
||||
height: 16px;
|
||||
.room-entry-context-item > a > img {
|
||||
width: 16px;
|
||||
}
|
||||
|
||||
|
|
|
@ -798,6 +798,11 @@ 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.
|
||||
|
|
|
@ -2,9 +2,8 @@
|
|||
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
|
||||
|
@ -12,31 +11,20 @@ var { Services } = Cu.import("resource://gre/modules/Services.jsm", {});
|
|||
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);
|
||||
|
@ -84,7 +72,9 @@ function getChromeWindow(aWindow) {
|
|||
.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();
|
||||
|
@ -107,7 +97,7 @@ function addTab(aUrl, aWindow) {
|
|||
return deferred.promise;
|
||||
}
|
||||
|
||||
function removeTab(aTab, aWindow) {
|
||||
this.removeTab = function removeTab(aTab, aWindow) {
|
||||
info("Removing tab.");
|
||||
|
||||
let deferred = promise.defer();
|
||||
|
@ -1215,30 +1205,3 @@ function getSplitConsole(toolbox, win) {
|
|||
});
|
||||
});
|
||||
}
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
|
|
@ -162,6 +162,12 @@ Selection.prototype = {
|
|||
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.
|
||||
|
|
|
@ -20,7 +20,7 @@ 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 + "/";
|
||||
|
|
|
@ -16,7 +16,7 @@ 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");
|
||||
|
@ -78,7 +78,20 @@ function InspectorPanel(iframeWindow, toolbox) {
|
|||
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);
|
||||
|
@ -139,31 +152,23 @@ InspectorPanel.prototype = {
|
|||
_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.
|
||||
|
@ -309,13 +314,31 @@ InspectorPanel.prototype = {
|
|||
* 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.searchSuggestions = new SelectorSearch(this, this.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.searchResultsLabel.textContent = str;
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -369,7 +392,6 @@ InspectorPanel.prototype = {
|
|||
*/
|
||||
setupSidebarToggle: function() {
|
||||
this._paneToggleButton = this.panelDoc.getElementById("inspector-pane-toggle");
|
||||
this.onPaneToggleButtonClicked = this.onPaneToggleButtonClicked.bind(this);
|
||||
this._paneToggleButton.addEventListener("mousedown",
|
||||
this.onPaneToggleButtonClicked);
|
||||
this.updatePaneToggleButton();
|
||||
|
@ -399,7 +421,6 @@ InspectorPanel.prototype = {
|
|||
return;
|
||||
}
|
||||
this.markup.expandNode(this.selection.nodeFront);
|
||||
this.setupSearchBox();
|
||||
this.emit("new-root");
|
||||
});
|
||||
};
|
||||
|
@ -590,8 +611,6 @@ InspectorPanel.prototype = {
|
|||
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);
|
||||
|
@ -602,10 +621,12 @@ InspectorPanel.prototype = {
|
|||
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,
|
||||
|
@ -914,8 +935,7 @@ InspectorPanel.prototype = {
|
|||
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);
|
||||
|
@ -924,8 +944,7 @@ InspectorPanel.prototype = {
|
|||
},
|
||||
|
||||
_onMarkupFrameLoad: function() {
|
||||
this._markupFrame.removeEventListener("load", this._boundMarkupFrameLoad, true);
|
||||
delete this._boundMarkupFrameLoad;
|
||||
this._markupFrame.removeEventListener("load", this._onMarkupFrameLoad, true);
|
||||
|
||||
this._markupFrame.contentWindow.focus();
|
||||
|
||||
|
@ -940,9 +959,8 @@ InspectorPanel.prototype = {
|
|||
_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) {
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
"use strict";
|
||||
|
||||
const { Cu } = require("chrome");
|
||||
const {Cu, Ci} = require("chrome");
|
||||
|
||||
const promise = require("promise");
|
||||
loader.lazyGetter(this, "EventEmitter", () => require("devtools/shared/event-emitter"));
|
||||
|
@ -13,6 +13,108 @@ loader.lazyGetter(this, "AutocompletePopup", () => require("devtools/client/shar
|
|||
// 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.
|
||||
*
|
||||
|
@ -21,29 +123,19 @@ const MAX_SUGGESTIONS = 15;
|
|||
* 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);
|
||||
|
@ -61,8 +153,7 @@ function SelectorSearch(aInspector, aInputNode) {
|
|||
};
|
||||
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);
|
||||
|
||||
|
@ -72,9 +163,9 @@ function SelectorSearch(aInspector, aInputNode) {
|
|||
EventEmitter.decorate(this);
|
||||
}
|
||||
|
||||
exports.SelectorSearch = SelectorSearch;
|
||||
exports.SelectorAutocompleter = SelectorAutocompleter;
|
||||
|
||||
SelectorSearch.prototype = {
|
||||
SelectorAutocompleter.prototype = {
|
||||
get walker() {
|
||||
return this.inspector.walker;
|
||||
},
|
||||
|
@ -169,142 +260,33 @@ SelectorSearch.prototype = {
|
|||
* 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) {
|
||||
|
@ -316,76 +298,45 @@ SelectorSearch.prototype = {
|
|||
}
|
||||
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 {
|
||||
|
@ -394,11 +345,11 @@ SelectorSearch.prototype = {
|
|||
}
|
||||
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 {
|
||||
|
@ -407,20 +358,15 @@ SelectorSearch.prototype = {
|
|||
}
|
||||
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");
|
||||
|
@ -438,12 +384,12 @@ SelectorSearch.prototype = {
|
|||
/**
|
||||
* 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;
|
||||
|
@ -461,8 +407,7 @@ SelectorSearch.prototype = {
|
|||
|
||||
let item = {
|
||||
preLabel: query,
|
||||
label: value,
|
||||
count: count
|
||||
label: value
|
||||
};
|
||||
|
||||
// In case of tagNames, change the case to small
|
||||
|
@ -489,6 +434,16 @@ SelectorSearch.prototype = {
|
|||
this.searchPopup.openPopup(this.searchBox);
|
||||
}
|
||||
else {
|
||||
this.hidePopup();
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* Hide the suggestion popup if necessary.
|
||||
*/
|
||||
hidePopup: function() {
|
||||
if (this.searchPopup.isOpen) {
|
||||
this.searchPopup.hidePopup();
|
||||
}
|
||||
},
|
||||
|
@ -502,18 +457,18 @@ SelectorSearch.prototype = {
|
|||
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);
|
||||
|
@ -524,23 +479,31 @@ SelectorSearch.prototype = {
|
|||
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;
|
||||
}
|
||||
};
|
|
@ -154,11 +154,12 @@
|
|||
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" />
|
||||
|
|
|
@ -6,7 +6,7 @@ DevToolsModules(
|
|||
'breadcrumbs.js',
|
||||
'inspector-commands.js',
|
||||
'inspector-panel.js',
|
||||
'selector-search.js'
|
||||
'inspector-search.js'
|
||||
)
|
||||
|
||||
BROWSER_CHROME_MANIFESTS += ['test/browser.ini']
|
||||
|
|
|
@ -8,11 +8,6 @@
|
|||
|
||||
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,
|
||||
|
@ -20,35 +15,45 @@ const LISTEN_KEYPRESS = [3,4,8,18,19,20,21,22];
|
|||
// 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* () {
|
||||
|
@ -60,14 +65,18 @@ add_task(function* () {
|
|||
|
||||
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);
|
||||
info(index + ": Pressing key " + key + " to get id " + id + ".");
|
||||
let done = inspector.searchSuggestions.once("processing-done");
|
||||
EventUtils.synthesizeKey(key, {}, inspector.panelWin);
|
||||
yield eventHandled;
|
||||
yield done;
|
||||
info("Got processing-done event");
|
||||
|
||||
info("Got " + event + " event. Waiting for search query to complete");
|
||||
if (key === "VK_RETURN") {
|
||||
info ("Waiting for " + (isValid ? "NO " : "") + "results");
|
||||
yield inspector.search.once("search-result");
|
||||
}
|
||||
|
||||
info("Waiting for search query to complete");
|
||||
yield inspector.searchSuggestions._lastQuery;
|
||||
|
||||
info(inspector.selection.nodeFront.id + " is selected with text " +
|
||||
|
|
|
@ -16,14 +16,14 @@ 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",
|
||||
|
@ -32,22 +32,22 @@ const TEST_DATA = [
|
|||
{
|
||||
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"}
|
||||
]
|
||||
},
|
||||
{
|
||||
|
@ -57,8 +57,8 @@ const TEST_DATA = [
|
|||
{
|
||||
key: "VK_BACK_SPACE",
|
||||
suggestions: [
|
||||
{label: "div div", count: 2 },
|
||||
{label: "div span", count: 2}
|
||||
{label: "div div"},
|
||||
{label: "div span"}
|
||||
]
|
||||
},
|
||||
{
|
||||
|
@ -67,14 +67,14 @@ const TEST_DATA = [
|
|||
},
|
||||
{
|
||||
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"}
|
||||
]
|
||||
},
|
||||
{
|
||||
|
@ -83,7 +83,12 @@ const TEST_DATA = [
|
|||
},
|
||||
{
|
||||
key: "p",
|
||||
suggestions: []
|
||||
suggestions: [
|
||||
{label: "p"},
|
||||
{label: "#p1"},
|
||||
{label: "#p2"},
|
||||
{label: "#p3"},
|
||||
]
|
||||
},
|
||||
{
|
||||
key: " ",
|
||||
|
@ -153,14 +158,12 @@ add_task(function* () {
|
|||
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(", ") + "]";
|
||||
}
|
||||
|
|
|
@ -16,14 +16,14 @@ 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",
|
||||
|
@ -50,14 +50,14 @@ var TEST_DATA = [
|
|||
},
|
||||
{
|
||||
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"}
|
||||
]
|
||||
},
|
||||
{
|
||||
|
@ -67,14 +67,14 @@ var TEST_DATA = [
|
|||
{
|
||||
key: ".",
|
||||
suggestions: [
|
||||
{label: ".c1", count: 3},
|
||||
{label: ".c1"},
|
||||
{label: ".c2"}
|
||||
]
|
||||
},
|
||||
{
|
||||
key: "c",
|
||||
suggestions: [
|
||||
{label: ".c1", count: 3},
|
||||
{label: ".c1"},
|
||||
{label: ".c2"}
|
||||
]
|
||||
},
|
||||
|
@ -85,7 +85,7 @@ var TEST_DATA = [
|
|||
{
|
||||
key: "VK_BACK_SPACE",
|
||||
suggestions: [
|
||||
{label: ".c1", count: 3},
|
||||
{label: ".c1"},
|
||||
{label: ".c2"}
|
||||
]
|
||||
},
|
||||
|
@ -108,14 +108,14 @@ var TEST_DATA = [
|
|||
{
|
||||
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"}
|
||||
]
|
||||
},
|
||||
|
@ -189,14 +189,12 @@ add_task(function* () {
|
|||
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(", ") + "]";
|
||||
}
|
||||
|
|
|
@ -19,14 +19,14 @@ 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",
|
||||
|
@ -34,14 +34,14 @@ var TEST_DATA = [
|
|||
},
|
||||
{
|
||||
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"}
|
||||
]
|
||||
},
|
||||
{
|
||||
|
@ -51,8 +51,8 @@ var TEST_DATA = [
|
|||
{
|
||||
key: ".",
|
||||
suggestions: [
|
||||
{label: ".c1", count: 7},
|
||||
{label: ".c2", count: 3}
|
||||
{label: ".c1"},
|
||||
{label: ".c2"}
|
||||
]
|
||||
},
|
||||
{
|
||||
|
@ -62,14 +62,14 @@ var TEST_DATA = [
|
|||
{
|
||||
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"}
|
||||
]
|
||||
},
|
||||
];
|
||||
|
@ -100,14 +100,12 @@ add_task(function* () {
|
|||
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(", ") + "]";
|
||||
}
|
||||
|
|
|
@ -11,11 +11,14 @@ 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;
|
||||
|
@ -24,41 +27,65 @@ add_task(function* () {
|
|||
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");
|
||||
let onSelect = inspector.once("inspector-updated");
|
||||
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_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>");
|
||||
|
||||
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");
|
||||
yield checkCorrectButton(inspector, "#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;
|
||||
|
||||
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-2");
|
||||
|
||||
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");
|
||||
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-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;
|
||||
|
||||
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);
|
||||
});
|
||||
|
|
|
@ -14,8 +14,9 @@ add_task(function* () {
|
|||
|
||||
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");
|
||||
|
@ -25,12 +26,15 @@ add_task(function* () {
|
|||
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");
|
||||
|
@ -41,6 +45,7 @@ add_task(function* () {
|
|||
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
|
||||
|
|
|
@ -13,15 +13,15 @@ const TEST_URL = TEST_URL_ROOT + "doc_inspector_search-reserved.html";
|
|||
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",
|
||||
|
@ -29,15 +29,15 @@ const TEST_DATA = [
|
|||
},
|
||||
{
|
||||
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",
|
||||
|
@ -45,8 +45,8 @@ const TEST_DATA = [
|
|||
},
|
||||
{
|
||||
key: "d",
|
||||
suggestions: [{label: "div", count: 2},
|
||||
{label: "#d1\\.d2", count: 1}]
|
||||
suggestions: [{label: "div"},
|
||||
{label: "#d1\\.d2"}]
|
||||
},
|
||||
{
|
||||
key: "VK_BACK_SPACE",
|
||||
|
@ -54,7 +54,7 @@ const TEST_DATA = [
|
|||
},
|
||||
{
|
||||
key:"c",
|
||||
suggestions: [{label: ".c1\\.c2", count: 1}]
|
||||
suggestions: [{label: ".c1\\.c2"}]
|
||||
},
|
||||
{
|
||||
key: "VK_BACK_SPACE",
|
||||
|
@ -62,15 +62,15 @@ const TEST_DATA = [
|
|||
},
|
||||
{
|
||||
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",
|
||||
|
@ -78,20 +78,20 @@ const TEST_DATA = [
|
|||
},
|
||||
{
|
||||
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"}]
|
||||
}
|
||||
];
|
||||
|
||||
|
@ -121,14 +121,12 @@ add_task(function* () {
|
|||
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(", ") + "]";
|
||||
}
|
||||
|
|
|
@ -113,9 +113,8 @@
|
|||
<!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
|
||||
|
|
|
@ -40,7 +40,7 @@ const Toolbar = module.exports = createClass({
|
|||
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")
|
||||
}),
|
||||
|
@ -50,7 +50,7 @@ const Toolbar = module.exports = createClass({
|
|||
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)))
|
||||
),
|
||||
|
@ -81,6 +81,7 @@ const Toolbar = module.exports = createClass({
|
|||
dom.input({
|
||||
id: "filter",
|
||||
type: "search",
|
||||
className: "devtools-searchinput",
|
||||
placeholder: L10N.getStr("filter.placeholder"),
|
||||
onChange: event => setFilterString(event.target.value),
|
||||
value: !!filterString ? filterString : undefined,
|
||||
|
|
|
@ -59,4 +59,14 @@ add_task(function *() {
|
|||
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'");
|
||||
});
|
||||
|
|
|
@ -98,6 +98,9 @@ const filterList = [
|
|||
}
|
||||
];
|
||||
|
||||
// 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.
|
||||
|
@ -557,7 +560,7 @@ CSSFilterEditorWidget.prototype = {
|
|||
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;
|
||||
}
|
||||
|
@ -706,7 +709,8 @@ CSSFilterEditorWidget.prototype = {
|
|||
|
||||
this.filters = [];
|
||||
|
||||
if (cssValue === "none") {
|
||||
if (SPECIAL_VALUES.has(cssValue)) {
|
||||
this._specialValue = cssValue;
|
||||
this.emit("updated", this.getCssValue());
|
||||
this.render();
|
||||
return;
|
||||
|
@ -825,7 +829,7 @@ CSSFilterEditorWidget.prototype = {
|
|||
getCssValue: function() {
|
||||
return this.filters.map((filter, i) => {
|
||||
return `${filter.name}(${this.getValueAt(i)})`;
|
||||
}).join(" ") || "none";
|
||||
}).join(" ") || this._specialValue || "none";
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -906,7 +910,7 @@ function tokenizeFilterValue(css) {
|
|||
let filters = [];
|
||||
let depth = 0;
|
||||
|
||||
if (css === "none") {
|
||||
if (SPECIAL_VALUES.has(css)) {
|
||||
return filters;
|
||||
}
|
||||
|
||||
|
|
|
@ -15,6 +15,10 @@
|
|||
|
||||
%endif
|
||||
|
||||
#inspector-searchlabel {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
#inspector-searchbox {
|
||||
transition-property: max-width, -moz-padding-end, -moz-padding-start;
|
||||
transition-duration: 250ms;
|
||||
|
|
|
@ -100,6 +100,11 @@ html, body, #app, #memory-tool {
|
|||
flex: 1;
|
||||
}
|
||||
|
||||
#filter {
|
||||
align-self: stretch;
|
||||
margin: 2px;
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO bug 1213100
|
||||
* Once we figure out how to store invertable buttons (pseudo element like in
|
||||
|
|
|
@ -61,6 +61,7 @@ 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,
|
||||
|
@ -1122,6 +1123,13 @@ types.addDictType("disconnectedNodeArray", {
|
|||
|
||||
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()
|
||||
*/
|
||||
|
@ -1299,6 +1307,8 @@ var WalkerActor = protocol.ActorClass({
|
|||
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.
|
||||
|
@ -1334,7 +1344,13 @@ var WalkerActor = protocol.ActorClass({
|
|||
// 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,
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -1376,6 +1392,7 @@ var WalkerActor = protocol.ActorClass({
|
|||
this.onFrameLoad = null;
|
||||
this.onFrameUnload = null;
|
||||
|
||||
this.walkerSearch.destroy();
|
||||
this.reflowObserver.off("reflows", this._onReflows);
|
||||
this.reflowObserver = null;
|
||||
this._onReflows = null;
|
||||
|
@ -2048,6 +2065,33 @@ var WalkerActor = protocol.ActorClass({
|
|||
}
|
||||
}),
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
|
@ -2838,6 +2882,11 @@ var WalkerActor = protocol.ActorClass({
|
|||
* 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) {
|
||||
|
@ -3315,6 +3364,75 @@ var WalkerFront = exports.WalkerFront = protocol.FrontClass(WalkerActor, {
|
|||
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);
|
||||
|
@ -3875,10 +3993,25 @@ DocumentWalker.prototype = {
|
|||
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) {
|
||||
|
@ -3890,8 +4023,9 @@ DocumentWalker.prototype = {
|
|||
|
||||
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) {
|
||||
|
|
|
@ -2084,6 +2084,13 @@ function getRuleText(initialText, line, column) {
|
|||
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,
|
||||
|
|
|
@ -12,5 +12,6 @@ DevToolsModules(
|
|||
'map-uri-to-addon-id.js',
|
||||
'ScriptStore.js',
|
||||
'stack.js',
|
||||
'TabSources.js'
|
||||
'TabSources.js',
|
||||
'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;
|
|
@ -10,6 +10,7 @@ support-files =
|
|||
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
|
||||
|
@ -77,6 +78,8 @@ skip-if = buildapp == 'mulet'
|
|||
[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]
|
||||
|
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -100,6 +100,13 @@ const TEST_DATA = [
|
|||
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() {
|
||||
|
|
|
@ -75,45 +75,31 @@ Concrete<DeserializedNode>::size(mozilla::MallocSizeOf mallocSizeof) const
|
|||
|
||||
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++;
|
||||
|
@ -138,9 +124,9 @@ 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());
|
||||
|
|
|
@ -68,9 +68,7 @@ DEF_TEST(DeserializedNodeUbiNodes, {
|
|||
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())));
|
||||
|
||||
|
@ -79,9 +77,7 @@ DEF_TEST(DeserializedNodeUbiNodes, {
|
|||
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())));
|
||||
|
||||
|
@ -90,11 +86,15 @@ DEF_TEST(DeserializedNodeUbiNodes, {
|
|||
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.
|
||||
}
|
||||
});
|
||||
|
|
|
@ -278,6 +278,12 @@ 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
|
||||
|
||||
|
||||
|
|
|
@ -46,7 +46,7 @@ interface nsIContentSecurityManager : nsISupports
|
|||
* 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.
|
||||
*/
|
||||
|
|
|
@ -85,7 +85,8 @@ MediaSourceDecoder::GetSeekable()
|
|||
} 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),
|
||||
|
|
|
@ -527,8 +527,7 @@ AudioContext::CreatePeriodicWave(const Float32Array& aRealData,
|
|||
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;
|
||||
}
|
||||
|
|
|
@ -27,7 +27,6 @@ PeriodicWave::PeriodicWave(AudioContext* 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.
|
||||
|
|
|
@ -31,8 +31,8 @@
|
|||
#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;
|
||||
|
@ -46,12 +46,11 @@ PeriodicWave::create(float sampleRate,
|
|||
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();
|
||||
}
|
||||
|
@ -62,7 +61,7 @@ already_AddRefed<PeriodicWave>
|
|||
PeriodicWave::createSine(float sampleRate)
|
||||
{
|
||||
RefPtr<PeriodicWave> periodicWave =
|
||||
new PeriodicWave(sampleRate);
|
||||
new PeriodicWave(sampleRate, MinPeriodicWaveSize);
|
||||
periodicWave->generateBasicWaveform(OscillatorType::Sine);
|
||||
return periodicWave.forget();
|
||||
}
|
||||
|
@ -71,7 +70,7 @@ already_AddRefed<PeriodicWave>
|
|||
PeriodicWave::createSquare(float sampleRate)
|
||||
{
|
||||
RefPtr<PeriodicWave> periodicWave =
|
||||
new PeriodicWave(sampleRate);
|
||||
new PeriodicWave(sampleRate, MinPeriodicWaveSize);
|
||||
periodicWave->generateBasicWaveform(OscillatorType::Square);
|
||||
return periodicWave.forget();
|
||||
}
|
||||
|
@ -80,7 +79,7 @@ already_AddRefed<PeriodicWave>
|
|||
PeriodicWave::createSawtooth(float sampleRate)
|
||||
{
|
||||
RefPtr<PeriodicWave> periodicWave =
|
||||
new PeriodicWave(sampleRate);
|
||||
new PeriodicWave(sampleRate, MinPeriodicWaveSize);
|
||||
periodicWave->generateBasicWaveform(OscillatorType::Sawtooth);
|
||||
return periodicWave.forget();
|
||||
}
|
||||
|
@ -89,18 +88,25 @@ 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;
|
||||
}
|
||||
|
|
|
@ -76,7 +76,7 @@ public:
|
|||
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);
|
||||
|
|
|
@ -37,9 +37,9 @@ addLoadEvent(function() {
|
|||
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();
|
||||
});
|
||||
|
|
|
@ -136,7 +136,8 @@ public:
|
|||
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");
|
||||
}
|
||||
|
||||
|
|
|
@ -8,6 +8,9 @@ support-files =
|
|||
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]
|
||||
|
|
|
@ -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);
|
||||
});
|
|
@ -5,7 +5,7 @@
|
|||
</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>
|
||||
|
|
|
@ -1526,7 +1526,7 @@ ServiceWorkerManager::Register(nsIDOMWindow* aWindow,
|
|||
}
|
||||
|
||||
nsAutoCString spec;
|
||||
rv = aScriptURI->GetSpec(spec);
|
||||
rv = aScriptURI->GetSpecIgnoringRef(spec);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return rv;
|
||||
}
|
||||
|
|
|
@ -134,7 +134,12 @@ enum class LogReason : int {
|
|||
// 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,
|
||||
};
|
||||
|
@ -565,7 +570,7 @@ typedef Log<LOG_CRITICAL, CriticalLogger> CriticalLog;
|
|||
//
|
||||
// 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
|
||||
|
||||
|
|
|
@ -650,12 +650,11 @@ CairoImage::GetTextureClient(CompositableClient *aClient)
|
|||
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());
|
||||
|
|
|
@ -98,22 +98,20 @@ CanvasClient2D::Update(gfx::IntSize aSize, ClientCanvasLayer* aLayer)
|
|||
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;
|
||||
|
@ -286,7 +284,9 @@ TexClientFromReadback(SharedSurface* src, ISurfaceAllocator* allocator,
|
|||
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();
|
||||
|
||||
|
@ -318,8 +318,6 @@ TexClientFromReadback(SharedSurface* src, ISurfaceAllocator* allocator,
|
|||
|
||||
texClient->RemoveFlags(TextureFlags::RB_SWAPPED);
|
||||
}
|
||||
|
||||
texClient->Unlock();
|
||||
}
|
||||
|
||||
return texClient.forget();
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
#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
|
||||
|
@ -570,33 +571,30 @@ ContentClientDoubleBuffered::FinalizeFrame(const nsIntRegion& aRegionToDraw)
|
|||
|
||||
// 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)) {
|
||||
TextureClientAutoLock frontLock(mFrontClient, OpenMode::OPEN_READ_ONLY);
|
||||
if (!frontLock.Succeeded()) {
|
||||
return;
|
||||
}
|
||||
if (mFrontClientOnWhite &&
|
||||
!mFrontClientOnWhite->Lock(OpenMode::OPEN_READ_ONLY)) {
|
||||
mFrontClient->Unlock();
|
||||
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
|
||||
|
|
|
@ -185,17 +185,20 @@ ImageClientSingle::UpdateImage(ImageContainer* aContainer, uint32_t aContentFlag
|
|||
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();
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
#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
|
||||
|
@ -720,17 +721,39 @@ protected:
|
|||
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;
|
||||
|
||||
explicit TextureClientAutoUnlock(TextureClient* aTexture)
|
||||
: mTexture(aTexture) {}
|
||||
|
||||
~TextureClientAutoUnlock()
|
||||
public:
|
||||
TextureClientAutoLock(TextureClient* aTexture, OpenMode aMode
|
||||
MOZ_GUARD_OBJECT_NOTIFIER_PARAM)
|
||||
: mTexture(aTexture),
|
||||
mSucceeded(false)
|
||||
{
|
||||
mTexture->Unlock();
|
||||
MOZ_GUARD_OBJECT_NOTIFIER_INIT;
|
||||
|
||||
mSucceeded = mTexture->Lock(aMode);
|
||||
mChecked = false;
|
||||
}
|
||||
~TextureClientAutoLock() {
|
||||
MOZ_ASSERT(mChecked);
|
||||
if (mSucceeded) {
|
||||
mTexture->Unlock();
|
||||
}
|
||||
}
|
||||
|
||||
bool Succeeded() {
|
||||
mChecked = true;
|
||||
return mSucceeded;
|
||||
}
|
||||
|
||||
private:
|
||||
TextureClient* mTexture;
|
||||
DebugOnly<bool> mChecked;
|
||||
bool mSucceeded;
|
||||
};
|
||||
|
||||
class KeepAlive
|
||||
|
|
|
@ -601,7 +601,8 @@ 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;
|
||||
}
|
||||
|
@ -613,8 +614,6 @@ CopyFrontToBack(TextureClient* aFront,
|
|||
|
||||
gfx::IntPoint rectToCopyTopLeft = aRectToCopy.TopLeft();
|
||||
aFront->CopyToTextureClient(aBack, &aRectToCopy, &rectToCopyTopLeft);
|
||||
|
||||
aFront->Unlock();
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
@ -132,7 +132,7 @@ static bool LockD3DTexture(T* aTexture)
|
|||
if (mutex) {
|
||||
HRESULT hr = mutex->AcquireSync(0, 10000);
|
||||
if (hr == WAIT_TIMEOUT) {
|
||||
MOZ_CRASH();
|
||||
gfxDevCrash(LogReason::D3DLockTimeout) << "D3D lock mutex timeout";
|
||||
}
|
||||
|
||||
if (FAILED(hr)) {
|
||||
|
@ -1245,7 +1245,7 @@ SyncObjectD3D11::FinalizeFrame()
|
|||
return;
|
||||
}
|
||||
|
||||
MOZ_CRASH();
|
||||
gfxDevCrash(LogReason::D3D10FinalizeFrame) << "Without device reset: " << hexa(hr);
|
||||
}
|
||||
|
||||
// test QI
|
||||
|
@ -1253,8 +1253,10 @@ SyncObjectD3D11::FinalizeFrame()
|
|||
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");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1270,7 +1272,7 @@ SyncObjectD3D11::FinalizeFrame()
|
|||
return;
|
||||
}
|
||||
|
||||
MOZ_CRASH();
|
||||
gfxDevCrash(LogReason::D3D11FinalizeFrame) << "Without device reset: " << hexa(hr);
|
||||
}
|
||||
|
||||
// test QI
|
||||
|
@ -1278,8 +1280,10 @@ SyncObjectD3D11::FinalizeFrame()
|
|||
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");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1293,7 +1297,7 @@ SyncObjectD3D11::FinalizeFrame()
|
|||
gfxWarning() << "AcquireSync timed out because of device reset.";
|
||||
return;
|
||||
}
|
||||
MOZ_CRASH();
|
||||
gfxDevCrash(LogReason::D3D10SyncLock) << "Timeout on the D3D10 sync lock";
|
||||
}
|
||||
|
||||
D3D10_BOX box;
|
||||
|
@ -1321,7 +1325,7 @@ SyncObjectD3D11::FinalizeFrame()
|
|||
gfxWarning() << "AcquireSync timed out because of device reset.";
|
||||
return;
|
||||
}
|
||||
MOZ_CRASH();
|
||||
gfxDevCrash(LogReason::D3D11SyncLock) << "Timeout on the D3D11 sync lock";
|
||||
}
|
||||
|
||||
D3D11_BOX box;
|
||||
|
@ -1334,7 +1338,7 @@ SyncObjectD3D11::FinalizeFrame()
|
|||
if (gfxWindowsPlatform::GetPlatform()->DidRenderingDeviceReset()) {
|
||||
return;
|
||||
}
|
||||
MOZ_CRASH();
|
||||
MOZ_CRASH("GFX: Invalid D3D11 content device");
|
||||
}
|
||||
|
||||
RefPtr<ID3D11DeviceContext> ctx;
|
||||
|
|
|
@ -90,11 +90,13 @@ SharedPlanarYCbCrImage::SetData(const PlanarYCbCrData& aData)
|
|||
}
|
||||
|
||||
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;
|
||||
|
|
|
@ -51,11 +51,21 @@ 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);
|
||||
|
|
|
@ -847,6 +847,7 @@ gfxFontconfigFontFamily::FindStyleVariations(FontInfoData *aFontInfoData)
|
|||
// 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];
|
||||
|
||||
|
@ -859,6 +860,12 @@ gfxFontconfigFontFamily::FindStyleVariations(FontInfoData *aFontInfoData)
|
|||
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"
|
||||
|
@ -872,6 +879,12 @@ gfxFontconfigFontFamily::FindStyleVariations(FontInfoData *aFontInfoData)
|
|||
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);
|
||||
|
|
|
@ -663,7 +663,8 @@ public:
|
|||
mIsSimpleFamily(false),
|
||||
mIsBadUnderlineFamily(false),
|
||||
mFamilyCharacterMapInitialized(false),
|
||||
mSkipDefaultFeatureSpaceCheck(false)
|
||||
mSkipDefaultFeatureSpaceCheck(false),
|
||||
mCheckForFallbackFaces(false)
|
||||
{ }
|
||||
|
||||
const nsString& Name() { return mName; }
|
||||
|
@ -767,6 +768,7 @@ public:
|
|||
}
|
||||
|
||||
bool IsBadUnderlineFamily() const { return mIsBadUnderlineFamily; }
|
||||
bool CheckForFallbackFaces() const { return mCheckForFallbackFaces; }
|
||||
|
||||
// sort available fonts to put preferred (standard) faces towards the end
|
||||
void SortAvailableFonts();
|
||||
|
@ -822,6 +824,7 @@ protected:
|
|||
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
|
||||
|
|
|
@ -1648,6 +1648,12 @@ gfxFontGroup::AddFamilyToFontList(gfxFontFamily* aFamily)
|
|||
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
|
||||
|
@ -2555,6 +2561,23 @@ gfxFontGroup::FindNonItalicFaceForChar(gfxFontFamily* aFamily, uint32_t aCh)
|
|||
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()
|
||||
{
|
||||
|
@ -2619,10 +2642,17 @@ gfxFontGroup::FindFontForChar(uint32_t aCh, uint32_t aPrevCh, uint32_t aNextCh,
|
|||
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) {
|
||||
|
@ -2722,17 +2752,30 @@ gfxFontGroup::FindFontForChar(uint32_t aCh, uint32_t aPrevCh, uint32_t aNextCh,
|
|||
}
|
||||
}
|
||||
|
||||
// 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -882,12 +882,13 @@ protected:
|
|||
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 ||
|
||||
|
@ -900,7 +901,7 @@ protected:
|
|||
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 ||
|
||||
|
@ -915,7 +916,8 @@ protected:
|
|||
mNeedsBold(aOtherFamilyFace.mNeedsBold),
|
||||
mFontCreated(aOtherFamilyFace.mFontCreated),
|
||||
mLoading(aOtherFamilyFace.mLoading),
|
||||
mInvalid(aOtherFamilyFace.mInvalid)
|
||||
mInvalid(aOtherFamilyFace.mInvalid),
|
||||
mCheckForFallbackFaces(aOtherFamilyFace.mCheckForFallbackFaces)
|
||||
{
|
||||
if (mFontCreated) {
|
||||
mFont = aOtherFamilyFace.mFont;
|
||||
|
@ -978,6 +980,8 @@ protected:
|
|||
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)
|
||||
{
|
||||
|
@ -1006,6 +1010,7 @@ protected:
|
|||
bool mFontCreated : 1;
|
||||
bool mLoading : 1;
|
||||
bool mInvalid : 1;
|
||||
bool mCheckForFallbackFaces : 1;
|
||||
};
|
||||
|
||||
// List of font families, either named or generic.
|
||||
|
@ -1102,7 +1107,13 @@ protected:
|
|||
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,
|
||||
|
|
|
@ -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 # }
|
|
@ -167,6 +167,8 @@ if '86' not in ost and 'arm' not in ost and 'mips' not in ost:
|
|||
'src/base/atomicops_internals_mutex.cc',
|
||||
]
|
||||
|
||||
CXXFLAGS += CONFIG['TK_CFLAGS']
|
||||
|
||||
include('/ipc/chromium/chromium-config.mozbuild')
|
||||
|
||||
FINAL_LIBRARY = 'xul'
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
#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
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
%/SharedMemoryBasic_mach.cpp: ;
|
|
@ -3191,7 +3191,11 @@ BytecodeEmitter::emitSwitch(ParseNode* pn)
|
|||
/* 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. */
|
||||
|
@ -7554,7 +7558,7 @@ BytecodeEmitter::emitClass(ParseNode* pn)
|
|||
}
|
||||
|
||||
bool
|
||||
BytecodeEmitter::emitTree(ParseNode* pn)
|
||||
BytecodeEmitter::emitTree(ParseNode* pn, EmitLineNumberNote emitLineNote)
|
||||
{
|
||||
JS_CHECK_RECURSION(cx, return false);
|
||||
|
||||
|
@ -7567,7 +7571,7 @@ BytecodeEmitter::emitTree(ParseNode* pn)
|
|||
/* 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;
|
||||
|
||||
|
|
|
@ -336,8 +336,14 @@ struct BytecodeEmitter
|
|||
|
||||
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);
|
||||
|
|
|
@ -123,7 +123,7 @@ TraceGenericPointerRoot(JSTracer* trc, gc::Cell** thingp, const char* name);
|
|||
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);
|
||||
|
||||
|
|
|
@ -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);
|
|
@ -813,7 +813,7 @@ IonBuilder::build()
|
|||
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()));
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
#define jit_JitSpewer_h
|
||||
|
||||
#include "mozilla/DebugOnly.h"
|
||||
#include "mozilla/IntegerPrintfMacros.h"
|
||||
|
||||
#include <stdarg.h>
|
||||
|
||||
|
|
|
@ -76,7 +76,7 @@ struct ArgumentsData
|
|||
// 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;
|
||||
|
||||
/*
|
||||
|
|
|
@ -166,12 +166,12 @@ struct ForOfPIC
|
|||
*
|
||||
* 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
|
||||
|
|
|
@ -617,7 +617,7 @@ 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
|
||||
|
|
|
@ -1032,7 +1032,7 @@ struct JSRuntime : public JS::shadow::Runtime,
|
|||
/* 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(); }
|
||||
|
@ -1102,7 +1102,7 @@ struct JSRuntime : public JS::shadow::Runtime,
|
|||
/* 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. */
|
||||
|
|
|
@ -685,21 +685,25 @@ TraceLoggerThreadState::init()
|
|||
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;
|
||||
|
|
|
@ -35,7 +35,7 @@ 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.
|
||||
*
|
||||
|
@ -47,17 +47,17 @@ namespace jit {
|
|||
*
|
||||
* 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);
|
||||
|
|
|
@ -28,7 +28,7 @@
|
|||
* 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.
|
||||
|
|
|
@ -1831,7 +1831,7 @@ const Class TypedArrayObject::classes[Scalar::MaxTypedArrayViewType] = {
|
|||
* 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", \
|
||||
|
|
|
@ -1061,7 +1061,7 @@ public:
|
|||
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));
|
||||
|
@ -3716,8 +3716,7 @@ ContainerState::ChooseAnimatedGeometryRoot(const nsDisplayList& aList,
|
|||
|
||||
// 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;
|
||||
|
@ -3892,8 +3891,7 @@ ContainerState::ProcessDisplayItems(nsDisplayList* aList)
|
|||
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;
|
||||
|
|
|
@ -1996,11 +1996,17 @@ void nsDisplayList::Sort(nsDisplayListBuilder* aBuilder,
|
|||
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
|
||||
|
@ -2141,6 +2147,9 @@ nsDisplayBackgroundImage::nsDisplayBackgroundImage(nsDisplayListBuilder* aBuilde
|
|||
|
||||
mBounds = GetBoundsInternal(aBuilder);
|
||||
mDestArea = GetDestAreaInternal(aBuilder);
|
||||
if (ShouldFixToViewport(aBuilder)) {
|
||||
mAnimatedGeometryRoot = nsLayoutUtils::GetAnimatedGeometryRootFor(this, aBuilder);
|
||||
}
|
||||
}
|
||||
|
||||
nsRect
|
||||
|
@ -3786,8 +3795,7 @@ RequiredLayerStateForChildren(nsDisplayListBuilder* aBuilder,
|
|||
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;
|
||||
}
|
||||
|
||||
|
@ -4061,8 +4069,7 @@ nsDisplayOpacity::GetLayerState(nsDisplayListBuilder* aBuilder,
|
|||
if (NeedsActiveLayer(aBuilder))
|
||||
return LAYER_ACTIVE;
|
||||
|
||||
return RequiredLayerStateForChildren(aBuilder, aManager, aParameters, mList,
|
||||
nsLayoutUtils::GetAnimatedGeometryRootFor(this, aBuilder));
|
||||
return RequiredLayerStateForChildren(aBuilder, aManager, aParameters, mList, AnimatedGeometryRoot());
|
||||
}
|
||||
|
||||
bool
|
||||
|
@ -4794,6 +4801,7 @@ nsDisplayTransform::SetReferenceFrameToAncestor(nsDisplayListBuilder* aBuilder)
|
|||
mReferenceFrame =
|
||||
aBuilder->FindReferenceFrameFor(outerFrame);
|
||||
mToReferenceFrame = mFrame->GetOffsetToCrossDoc(mReferenceFrame);
|
||||
mAnimatedGeometryRoot = nsLayoutUtils::GetAnimatedGeometryRootForFrame(aBuilder, outerFrame);
|
||||
mVisibleRect = aBuilder->GetDirtyRect() + mToReferenceFrame;
|
||||
}
|
||||
|
||||
|
|
|
@ -1231,6 +1231,7 @@ public:
|
|||
: mFrame(aFrame)
|
||||
, mClip(nullptr)
|
||||
, mReferenceFrame(nullptr)
|
||||
, mAnimatedGeometryRoot(nullptr)
|
||||
#ifdef MOZ_DUMP_PAINTING
|
||||
, mPainted(false)
|
||||
#endif
|
||||
|
@ -1692,6 +1693,11 @@ public:
|
|||
*/
|
||||
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
|
||||
|
@ -1751,6 +1757,7 @@ protected:
|
|||
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.
|
||||
|
|
|
@ -162,9 +162,7 @@ PrintDisplayItemTo(nsDisplayListBuilder* aBuilder, nsDisplayItem* aItem,
|
|||
}
|
||||
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);
|
||||
|
@ -179,7 +177,7 @@ PrintDisplayItemTo(nsDisplayListBuilder* aBuilder, nsDisplayItem* 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,
|
||||
|
@ -187,7 +185,8 @@ PrintDisplayItemTo(nsDisplayListBuilder* aBuilder, nsDisplayItem* aItem,
|
|||
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()) {
|
||||
|
|
|
@ -1067,12 +1067,6 @@ GetDisplayPortFromMarginsData(nsIContent* aContent,
|
|||
}
|
||||
}
|
||||
|
||||
// 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;
|
||||
|
||||
|
@ -1961,6 +1955,25 @@ nsLayoutUtils::GetAnimatedGeometryRootFor(nsDisplayItem* aItem,
|
|||
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,
|
||||
|
|
|
@ -563,6 +563,13 @@ public:
|
|||
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
|
||||
|
|
|
@ -239,7 +239,11 @@ 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;
|
||||
|
||||
|
|
|
@ -4752,9 +4752,15 @@ ScrollFrameHelper::FinishReflowForScrollbar(nsIContent* aContent,
|
|||
bool
|
||||
ScrollFrameHelper::ReflowFinished()
|
||||
{
|
||||
nsAutoScriptBlocker scriptBlocker;
|
||||
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;
|
||||
ScrollToRestoredPosition();
|
||||
|
||||
// Clamp current scroll position to new bounds. Normally this won't
|
||||
|
@ -4768,9 +4774,9 @@ ScrollFrameHelper::ReflowFinished()
|
|||
mDestination = GetScrollPosition();
|
||||
}
|
||||
|
||||
if (NS_SUBTREE_DIRTY(mOuter) || !mUpdateScrollbarAttributes)
|
||||
if (!mUpdateScrollbarAttributes) {
|
||||
return false;
|
||||
|
||||
}
|
||||
mUpdateScrollbarAttributes = false;
|
||||
|
||||
// Update scrollbar attributes.
|
||||
|
|
|
@ -102,7 +102,9 @@ MP4Metadata::MP4Metadata(Stream* aSource)
|
|||
mPrivate->mMetadataExtractor->flags() & MediaExtractor::CAN_SEEK;
|
||||
sp<MetaData> metaData = mPrivate->mMetadataExtractor->getMetaData();
|
||||
|
||||
UpdateCrypto(metaData.get());
|
||||
if (metaData.get()) {
|
||||
UpdateCrypto(metaData.get());
|
||||
}
|
||||
}
|
||||
|
||||
MP4Metadata::~MP4Metadata()
|
||||
|
@ -317,6 +319,9 @@ 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;
|
||||
|
|
|
@ -248,7 +248,7 @@ private:
|
|||
return mSize <= sizeof(u.reservoir);
|
||||
}
|
||||
|
||||
void allocateStorage(size_t size);
|
||||
bool allocateStorage(size_t size);
|
||||
void freeStorage();
|
||||
|
||||
void *storage() {
|
||||
|
|
|
@ -420,7 +420,7 @@ uint32_t MPEG4Extractor::flags() const {
|
|||
sp<MetaData> MPEG4Extractor::getMetaData() {
|
||||
status_t err;
|
||||
if ((err = readMetaData()) != OK) {
|
||||
return new MetaData;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return mFileMetaData;
|
||||
|
@ -514,14 +514,15 @@ status_t MPEG4Extractor::readMetaData() {
|
|||
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++) {
|
||||
|
@ -685,7 +686,10 @@ status_t MPEG4Extractor::parseDrmSINF(off64_t *offset, off64_t data_offset) {
|
|||
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;
|
||||
|
@ -1146,7 +1150,10 @@ status_t MPEG4Extractor::parseChunk(off64_t *offset, int depth) {
|
|||
|
||||
// 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;
|
||||
}
|
||||
|
@ -1762,7 +1769,10 @@ status_t MPEG4Extractor::parseChunk(off64_t *offset, int depth) {
|
|||
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) {
|
||||
|
@ -1996,7 +2006,10 @@ status_t MPEG4Extractor::parseChunk(off64_t *offset, int depth) {
|
|||
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);
|
||||
|
@ -2024,12 +2037,18 @@ status_t MPEG4Extractor::parseChunk(off64_t *offset, int depth) {
|
|||
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);
|
||||
|
|
|
@ -220,7 +220,7 @@ bool MetaData::findData(uint32_t key, uint32_t *type,
|
|||
}
|
||||
|
||||
MetaData::typed_data::typed_data()
|
||||
: mType(0),
|
||||
: mType(TYPE_NONE),
|
||||
mSize(0) {
|
||||
}
|
||||
|
||||
|
@ -231,17 +231,19 @@ MetaData::typed_data::~typed_data() {
|
|||
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;
|
||||
|
@ -250,16 +252,17 @@ MetaData::typed_data &MetaData::typed_data::operator=(
|
|||
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(
|
||||
|
@ -269,14 +272,22 @@ void MetaData::typed_data::getData(
|
|||
*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() {
|
||||
|
|
|
@ -174,7 +174,8 @@ static const TestFileData testFiles[] = {
|
|||
{ "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)
|
||||
|
|
|
@ -22,6 +22,7 @@ TEST_HARNESS_FILES.gtest += [
|
|||
'test_case_1187067.mp4',
|
||||
'test_case_1200326.mp4',
|
||||
'test_case_1204580.mp4',
|
||||
'test_case_1216748.mp4',
|
||||
]
|
||||
|
||||
if CONFIG['MOZ_RUST']:
|
||||
|
|
Двоичный файл не отображается.
|
@ -73,33 +73,34 @@ int nr_stun_client_ctx_create(char *label, nr_socket *sock, nr_transport_addr *p
|
|||
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;
|
||||
}
|
||||
|
||||
|
@ -393,18 +394,25 @@ static int nr_stun_client_send_request(nr_stun_client_ctx *ctx)
|
|||
* 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);
|
||||
}
|
||||
|
||||
|
|
|
@ -170,7 +170,7 @@ struct nr_stun_client_ctx_ {
|
|||
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;
|
||||
|
|
|
@ -22,9 +22,6 @@ 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";
|
||||
|
@ -173,20 +170,19 @@ final class DisplayPortCalculator {
|
|||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
|
||||
|
@ -311,7 +307,7 @@ final class DisplayPortCalculator {
|
|||
margins.bottom = verticalBuffer - margins.top;
|
||||
margins = shiftMarginsForPageBounds(margins, metrics);
|
||||
|
||||
return getTileAlignedDisplayPortMetrics(margins, metrics.zoomFactor, metrics);
|
||||
return getPageClampedDisplayPortMetrics(margins, metrics.zoomFactor, metrics);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -422,7 +418,7 @@ final class DisplayPortCalculator {
|
|||
RectF margins = velocityBiasedMargins(horizontalBuffer, verticalBuffer, velocity);
|
||||
margins = shiftMarginsForPageBounds(margins, metrics);
|
||||
|
||||
return getTileAlignedDisplayPortMetrics(margins, metrics.zoomFactor, metrics);
|
||||
return getPageClampedDisplayPortMetrics(margins, metrics.zoomFactor, metrics);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -689,7 +685,7 @@ final class DisplayPortCalculator {
|
|||
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
|
||||
|
@ -714,7 +710,7 @@ final class DisplayPortCalculator {
|
|||
-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
|
||||
|
|
|
@ -735,6 +735,7 @@ just addresses the organization to follow, e.g. "This site is run by " -->
|
|||
<!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 -->
|
||||
|
|
|
@ -28,7 +28,8 @@ public class RestrictedProfileConfiguration implements RestrictionConfiguration
|
|||
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;
|
||||
|
|
Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше
Загрузка…
Ссылка в новой задаче