author | Ciure Andrei <aciure@mozilla.com> |
Wed, 14 Aug 2019 12:34:46 +0300 | |
changeset 487847 | a6ba020c9f7cd1abecd8eb2287020468ec1da6e8 |
parent 487833 | d3deef805f92d4a899d7b6de76b347328f33e54b (current diff) |
parent 487846 | 214fac6eb1c05177c7ef8eb29b87cceb09eb4110 (diff) |
child 487851 | 62094e9b468a0903e56b7af177d7590d616a8d5b |
child 488058 | b18e834a1dafe9e1b8e36585239027030e3d07e3 |
push id | 36431 |
push user | aciure@mozilla.com |
push date | Wed, 14 Aug 2019 09:42:16 +0000 |
treeherder | mozilla-central@a6ba020c9f7c [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
reviewers | merge |
milestone | 70.0a1 |
first release with | nightly linux32
a6ba020c9f7c
/
70.0a1
/
20190814094216
/
files
nightly linux64
a6ba020c9f7c
/
70.0a1
/
20190814094216
/
files
nightly mac
a6ba020c9f7c
/
70.0a1
/
20190814094216
/
files
nightly win32
a6ba020c9f7c
/
70.0a1
/
20190814094216
/
files
nightly win64
a6ba020c9f7c
/
70.0a1
/
20190814094216
/
files
|
last release without | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
releases | nightly linux32
70.0a1
/
20190814094216
/
pushlog to previous
nightly linux64
70.0a1
/
20190814094216
/
pushlog to previous
nightly mac
70.0a1
/
20190814094216
/
pushlog to previous
nightly win32
70.0a1
/
20190814094216
/
pushlog to previous
nightly win64
70.0a1
/
20190814094216
/
pushlog to previous
|
--- a/browser/base/content/test/sanitize/browser_purgehistory_clears_sh.js +++ b/browser/base/content/test/sanitize/browser_purgehistory_clears_sh.js @@ -27,16 +27,19 @@ add_task(async function purgeHistoryTest ok(backButton.hasAttribute("disabled"), "Back button is disabled"); ok(forwardButton.hasAttribute("disabled"), "Forward button is disabled"); await ContentTask.spawn(browser, null, async function() { let startHistory = content.history.length; content.history.pushState({}, ""); content.history.pushState({}, ""); content.history.back(); + await new Promise(function(r) { + setTimeout(r); + }); let newHistory = content.history.length; Assert.equal(startHistory, 1, "Initial SHistory size"); Assert.equal(newHistory, 3, "New SHistory size"); }); ok( browser.webNavigation.canGoBack, "New value for webNavigation.canGoBack"
--- a/browser/components/aboutlogins/content/components/login-item.js +++ b/browser/components/aboutlogins/content/components/login-item.js @@ -141,37 +141,21 @@ export default class LoginItem extends H this.setLogin(event.detail, { skipFocusChange: true }); break; } case "AboutLoginsLoadInitialFavicon": { this.render(); break; } case "AboutLoginsLoginSelected": { - let login = event.detail; - if (this.hasPendingChanges()) { - event.preventDefault(); - this.showConfirmationDialog("discard-changes", () => { - // Clear any pending changes - this.setLogin(login); - - window.dispatchEvent( - new CustomEvent("AboutLoginsLoginSelected", { - detail: login, - cancelable: true, - }) - ); - }); - } else { - this.setLogin(login); - } + this.confirmPendingChangesOnEvent(event, event.detail); break; } case "AboutLoginsShowBlankLogin": { - this.setLogin({}); + this.confirmPendingChangesOnEvent(event, {}); break; } case "blur": { // Add https:// prefix if one was not provided. let originValue = this._originInput.value.trim(); if (!originValue) { return; } @@ -318,16 +302,41 @@ export default class LoginItem extends H recordTelemetryEvent({ object: "new_login", method: "save" }); } } } } /** + * Helper to show the "Discard changes" confirmation dialog and delay the + * received event after confirmation. + * @param {object} event The event to be delayed. + * @param {object} login The login to be shown on confirmation. + */ + confirmPendingChangesOnEvent(event, login) { + if (this.hasPendingChanges()) { + event.preventDefault(); + this.showConfirmationDialog("discard-changes", () => { + // Clear any pending changes + this.setLogin(login); + + window.dispatchEvent( + new CustomEvent(event.type, { + detail: login, + cancelable: false, + }) + ); + }); + } else { + this.setLogin(login); + } + } + + /** * Shows a confirmation dialog. * @param {string} type The type of confirmation dialog to display. * @param {boolean} onConfirm Optional, the function to execute when the confirm button is clicked. */ showConfirmationDialog(type, onConfirm = () => {}) { const dialog = document.querySelector("confirmation-dialog"); let options; switch (type) {
--- a/browser/components/aboutlogins/content/components/login-list.js +++ b/browser/components/aboutlogins/content/components/login-list.js @@ -118,17 +118,21 @@ export default class LoginList extends H ); } } handleEvent(event) { switch (event.type) { case "click": { if (event.originalTarget == this._createLoginButton) { - window.dispatchEvent(new CustomEvent("AboutLoginsShowBlankLogin")); + window.dispatchEvent( + new CustomEvent("AboutLoginsShowBlankLogin", { + cancelable: true, + }) + ); recordTelemetryEvent({ object: "new_login", method: "new" }); return; } let listItem = event.originalTarget.closest(".login-list-item"); if (!listItem || !listItem.dataset.guid) { return; } @@ -187,18 +191,20 @@ export default class LoginList extends H if (listItem) { this._setListItemAsSelected(listItem); } else { this.render(); } break; } case "AboutLoginsShowBlankLogin": { - this._selectedGuid = null; - this._setListItemAsSelected(this._blankLoginListItem); + if (!event.defaultPrevented) { + this._selectedGuid = null; + this._setListItemAsSelected(this._blankLoginListItem); + } break; } case "keydown": { this._handleKeyboardNav(event); break; } } }
--- a/browser/components/aboutlogins/tests/browser/browser_updateLogin.js +++ b/browser/components/aboutlogins/tests/browser/browser_updateLogin.js @@ -55,64 +55,67 @@ add_task(async function test_login_item( let usernameInput = loginItem.shadowRoot.querySelector( "input[name='username']" ); let passwordInput = loginItem.shadowRoot.querySelector( "input[name='password']" ); let editButton = loginItem.shadowRoot.querySelector(".edit-button"); - editButton.click(); - await Promise.resolve(); + + async function test_discard_dialog(exitPoint) { + editButton.click(); + await Promise.resolve(); + + usernameInput.value += "-undome"; + passwordInput.value += "-undome"; + + let dialog = content.document.querySelector("confirmation-dialog"); + ok(dialog.hidden, "Confirm dialog should initially be hidden"); + + exitPoint.click(); + + ok(!dialog.hidden, "Confirm dialog should be visible"); + + let confirmDiscardButton = dialog.shadowRoot.querySelector( + ".confirm-button" + ); + await content.document.l10n.translateElements([ + dialog.shadowRoot.querySelector(".title"), + dialog.shadowRoot.querySelector(".message"), + confirmDiscardButton, + ]); - usernameInput.value += "-undome"; - passwordInput.value += "-undome"; + confirmDiscardButton.click(); + + ok(dialog.hidden, "Confirm dialog should be hidden after confirming"); + + await Promise.resolve(); + loginListItem.click(); + + await ContentTaskUtils.waitForCondition( + () => usernameInput.value == login.username + ); - let dialog = content.document.querySelector("confirmation-dialog"); - ok(dialog.hidden, "Confirm dialog should initially be hidden"); + is( + usernameInput.value, + login.username, + "Username change should be reverted" + ); + is( + passwordInput.value, + login.password, + "Password change should be reverted" + ); + } + + await test_discard_dialog(loginList._createLoginButton); let cancelButton = loginItem.shadowRoot.querySelector(".cancel-button"); - cancelButton.click(); - - ok(!dialog.hidden, "Confirm dialog should be visible"); - - let confirmDiscardButton = dialog.shadowRoot.querySelector( - ".confirm-button" - ); - await content.document.l10n.translateElements([ - dialog.shadowRoot.querySelector(".title"), - dialog.shadowRoot.querySelector(".message"), - confirmDiscardButton, - ]); - - confirmDiscardButton.click(); - - ok(dialog.hidden, "Confirm dialog should be hidden after confirming"); - - usernameInput = loginItem.shadowRoot.querySelector( - "input[name='username']" - ); - passwordInput = loginItem.shadowRoot.querySelector( - "input[name='password']" - ); - - await ContentTaskUtils.waitForCondition( - () => usernameInput.value == login.username - ); - - is( - usernameInput.value, - login.username, - "Username change should be reverted" - ); - is( - passwordInput.value, - login.password, - "Password change should be reverted" - ); + await test_discard_dialog(cancelButton); editButton.click(); await Promise.resolve(); usernameInput.value += "-saveme"; passwordInput.value += "-saveme"; ok(loginItem.dataset.editing, "LoginItem should be in 'edit' mode");
--- a/devtools/client/framework/selection.js +++ b/devtools/client/framework/selection.js @@ -1,26 +1,35 @@ /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ /* vim: set ft=javascript ts=2 et sw=2 tw=80: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ "use strict"; -const nodeConstants = require("devtools/shared/dom-node-constants"); -var EventEmitter = require("devtools/shared/event-emitter"); +const EventEmitter = require("devtools/shared/event-emitter"); + +loader.lazyRequireGetter( + this, + "nodeConstants", + "devtools/shared/dom-node-constants" +); /** + * Selection is a singleton belonging to the Toolbox that manages the current selected + * NodeFront. In addition, it provides some helpers about the context of the selected + * node. + * * API * - * new Selection(walker=null) + * new Selection() * destroy() - * node (readonly) - * setNode(node, origin="unknown") + * nodeFront (readonly) + * setNodeFront(node, origin="unknown") * * Helpers: * * window * document * isRoot() * isNode() * isHTMLNode() @@ -43,39 +52,31 @@ var EventEmitter = require("devtools/sha * Events: * "new-node-front" when the inner node changed * "attribute-changed" when an attribute is changed * "detached-front" when the node (or one of its parents) is removed from * the document * "reparented" when the node (or one of its parents) is moved under * a different node */ - -/** - * A Selection object. Hold a reference to a node. - * Includes some helpers, fire some helpful events. - */ -function Selection(walker) { +function Selection() { EventEmitter.decorate(this); + // The WalkerFront is dynamic and is always set to the selected NodeFront's WalkerFront. + this._walker = null; // A single node front can be represented twice on the client when the node is a slotted // element. It will be displayed once as a direct child of the host element, and once as // a child of a slot in the "shadow DOM". The latter is called the slotted version. this._isSlotted = false; this._onMutations = this._onMutations.bind(this); this.setNodeFront = this.setNodeFront.bind(this); - this.setWalker(walker); } -exports.Selection = Selection; - Selection.prototype = { - _walker: null, - _onMutations: function(mutations) { let attributeChange = false; let pseudoChange = false; let detached = false; let parentNode = null; for (const m of mutations) { if (!attributeChange && m.type == "attributes") { @@ -102,20 +103,20 @@ Selection.prototype = { this.emit("pseudoclass"); } if (detached) { this.emit("detached-front", parentNode); } }, destroy: function() { - this.setWalker(null); + this.setWalker(); }, - setWalker: function(walker) { + setWalker: function(walker = null) { if (this._walker) { this._walker.off("mutations", this._onMutations); } this._walker = walker; if (this._walker) { this._walker.on("mutations", this._onMutations); } }, @@ -148,21 +149,23 @@ Selection.prototype = { // (e.g. once when the webpage start to navigate away from the current webpage, // and then again while the new page is being loaded). return; } this._isSlotted = isSlotted; this._nodeFront = nodeFront; - this.emit("new-node-front", nodeFront, this.reason); - }, + if (nodeFront) { + this.setWalker(nodeFront.walkerFront); + } else { + this.setWalker(); + } - get documentFront() { - return this._walker.document(this._nodeFront); + this.emit("new-node-front", nodeFront, this.reason); }, get nodeFront() { return this._nodeFront; }, isRoot: function() { return ( @@ -305,8 +308,10 @@ Selection.prototype = { isSlotted: function() { return this._isSlotted; }, isShadowRootNode: function() { return this.isNode() && this.nodeFront.isShadowRoot; }, }; + +module.exports = Selection;
--- a/devtools/client/framework/test/browser.ini +++ b/devtools/client/framework/test/browser.ini @@ -58,16 +58,17 @@ support-files = [browser_about-devtools-toolbox_reload.js] [browser_browser_toolbox.js] skip-if = coverage # Bug 1387827 [browser_browser_toolbox_debugger.js] skip-if = os == 'win' || debug # Bug 1282269, 1448084 [browser_devtools_api.js] [browser_devtools_api_destroy.js] [browser_dynamic_tool_enabling.js] +[browser_front_parentFront.js] [browser_ignore_toolbox_network_requests.js] [browser_keybindings_01.js] [browser_keybindings_02.js] [browser_keybindings_03.js] [browser_menu_api.js] [browser_new_activation_workflow.js] [browser_source_map-01.js] [browser_source_map-absolute.js]
new file mode 100644 --- /dev/null +++ b/devtools/client/framework/test/browser_front_parentFront.js @@ -0,0 +1,42 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Test the Front's parentFront attribute returns the correct parent front. + +const TEST_URL = `data:text/html;charset=utf-8,<div id="test"></div>`; + +add_task(async function() { + gBrowser.selectedTab = BrowserTestUtils.addTab(gBrowser); + + const tab = await addTab(TEST_URL); + const target = await TargetFactory.forTab(tab); + await target.attach(); + + const inspectorFront = await target.getFront("inspector"); + const walker = inspectorFront.walker; + const pageStyleFront = await inspectorFront.getPageStyle(); + const nodeFront = await walker.querySelector(walker.rootNode, "#test"); + + is( + inspectorFront.parentFront, + target, + "Got the correct parentFront from the InspectorFront." + ); + is( + walker.parentFront, + inspectorFront, + "Got the correct parentFront from the WalkerFront." + ); + is( + pageStyleFront.parentFront, + inspectorFront, + "Got the correct parentFront from the PageStyleFront." + ); + is( + nodeFront.parentFront, + walker, + "Got the correct parentFront from the NodeFront." + ); +});
--- a/devtools/client/framework/test/browser_target_get-front.js +++ b/devtools/client/framework/test/browser_target_get-front.js @@ -14,16 +14,21 @@ add_task(async function() { info("Test the targetFront attribute for the root"); const { client } = target; is( client.mainRoot.targetFront, null, "got null from the targetFront attribute for the root" ); + is( + client.mainRoot.parentFront, + null, + "got null from the parentFront attribute for the root" + ); info("Test getting a front twice"); const getAccessibilityFront = await target.getFront("accessibility"); const getAccessibilityFront2 = await target.getFront("accessibility"); is( getAccessibilityFront, getAccessibilityFront2, "got the same front when calling getFront twice" @@ -33,16 +38,26 @@ add_task(async function() { target, "got the correct targetFront attribute from the front" ); is( getAccessibilityFront2.targetFront, target, "got the correct targetFront attribute from the front" ); + is( + getAccessibilityFront.parentFront, + target, + "got the correct parentFront attribute from the front" + ); + is( + getAccessibilityFront2.parentFront, + target, + "got the correct parentFront attribute from the front" + ); info("Test getting a front on different targets"); const target1Front = await target.getFront("accessibility"); const target2Front = await target2.getFront("accessibility"); is( target1Front !== target2Front, true, "got different fronts when calling getFront on different targets"
--- a/devtools/client/framework/toolbox.js +++ b/devtools/client/framework/toolbox.js @@ -19,31 +19,18 @@ const HTML_NS = "http://www.w3.org/1999/ var { Ci, Cc } = require("chrome"); var promise = require("promise"); const { debounce } = require("devtools/shared/debounce"); var Services = require("Services"); var ChromeUtils = require("ChromeUtils"); var { gDevTools } = require("devtools/client/framework/devtools"); var EventEmitter = require("devtools/shared/event-emitter"); +const Selection = require("devtools/client/framework/selection"); var Telemetry = require("devtools/client/shared/telemetry"); - -loader.lazyRequireGetter( - this, - "createToolboxStore", - "devtools/client/framework/store", - true -); -loader.lazyRequireGetter( - this, - "registerWalkerListeners", - "devtools/client/framework/actions/index", - true -); - const { getUnicodeUrl } = require("devtools/client/shared/unicode-url"); var { DOMHelpers, } = require("resource://devtools/client/shared/DOMHelpers.jsm"); const { KeyCodes } = require("devtools/client/shared/keycodes"); var Startup = Cc["@mozilla.org/devtools/startup-clh;1"].getService( Ci.nsISupports ).wrappedJSObject; @@ -54,16 +41,28 @@ const { BrowserLoader } = ChromeUtils.im const { LocalizationHelper } = require("devtools/shared/l10n"); const L10N = new LocalizationHelper( "devtools/client/locales/toolbox.properties" ); loader.lazyRequireGetter( this, + "createToolboxStore", + "devtools/client/framework/store", + true +); +loader.lazyRequireGetter( + this, + "registerWalkerListeners", + "devtools/client/framework/actions/index", + true +); +loader.lazyRequireGetter( + this, "AppConstants", "resource://gre/modules/AppConstants.jsm", true ); loader.lazyRequireGetter(this, "flags", "devtools/shared/flags"); loader.lazyRequireGetter( this, "KeyShortcuts", @@ -191,16 +190,17 @@ function Toolbox( hostType, contentWindow, frameId, msSinceProcessStart ) { this._target = target; this._win = contentWindow; this.frameId = frameId; + this.selection = new Selection(); this.telemetry = new Telemetry(); // The session ID is used to determine which telemetry events belong to which // toolbox session. Because we use Amplitude to analyse the telemetry data we // must use the time since the system wide epoch as the session ID. this.sessionId = msSinceProcessStart; // Map of the available DevTools WebExtensions: @@ -250,16 +250,18 @@ function Toolbox( this._onToolbarFocus = this._onToolbarFocus.bind(this); this._onToolbarArrowKeypress = this._onToolbarArrowKeypress.bind(this); this._onPickerClick = this._onPickerClick.bind(this); this._onPickerKeypress = this._onPickerKeypress.bind(this); this._onPickerStarting = this._onPickerStarting.bind(this); this._onPickerStarted = this._onPickerStarted.bind(this); this._onPickerStopped = this._onPickerStopped.bind(this); this._onPickerCanceled = this._onPickerCanceled.bind(this); + this._onPickerPicked = this._onPickerPicked.bind(this); + this._onPickerPreviewed = this._onPickerPreviewed.bind(this); this._onInspectObject = this._onInspectObject.bind(this); this._onNewSelectedNodeFront = this._onNewSelectedNodeFront.bind(this); this._onToolSelected = this._onToolSelected.bind(this); this._onTargetClosed = this._onTargetClosed.bind(this); this._onContextMenu = this._onContextMenu.bind(this); this.updateToolboxButtonsVisibility = this.updateToolboxButtonsVisibility.bind( this ); @@ -294,16 +296,18 @@ function Toolbox( this._target.on("will-navigate", this._onWillNavigate); this._target.on("navigate", this._refreshHostTitle); this._target.on("frame-update", this._updateFrames); this._target.on("inspect-object", this._onInspectObject); this.on("host-changed", this._refreshHostTitle); this.on("select", this._onToolSelected); + this.selection.on("new-node-front", this._onNewSelectedNodeFront); + gDevTools.on("tool-registered", this._toolRegistered); gDevTools.on("tool-unregistered", this._toolUnregistered); /** * Get text direction for the current locale direction. * * `getComputedStyle` forces a synchronous reflow, so use a lazy getter in order to * call it only once. @@ -523,24 +527,16 @@ Toolbox.prototype = { * Get the toolbox's walker front. Note that it may not always have been * initialized first. Use `initInspector()` if needed. */ get walker() { return this._walker; }, /** - * Get the toolbox's node selection. Note that it may not always have been - * initialized first. Use `initInspector()` if needed. - */ - get selection() { - return this._selection; - }, - - /** * Get the toggled state of the split console */ get splitConsole() { return this._splitConsole; }, /** * Get the focused state of the split console @@ -1807,16 +1803,34 @@ Toolbox.prototype = { _onPickerStopped: function() { this.tellRDMAboutPickerState(false); this.off("select", this.inspectorFront.nodePicker.stop); this.doc.removeEventListener("keypress", this._onPickerKeypress, true); this.pickerButton.isChecked = false; }, /** + * When the picker is canceled, make sure the toolbox + * gets the focus. + */ + _onPickerCanceled: function() { + if (this.hostType !== Toolbox.HostType.WINDOW) { + this.win.focus(); + } + }, + + _onPickerPicked: function(nodeFront) { + this.selection.setNodeFront(nodeFront, { reason: "picker-node-picked" }); + }, + + _onPickerPreviewed: function(nodeFront) { + this.selection.setNodeFront(nodeFront, { reason: "picker-node-previewed" }); + }, + + /** * RDM sometimes simulates touch events. For this to work correctly at all times, it * needs to know when the picker is active or not. * This method communicates with the RDM Manager if it exists. * * @param {Boolean} state */ tellRDMAboutPickerState: async function(state) { const { tab } = this.target; @@ -1828,26 +1842,16 @@ Toolbox.prototype = { return; } const ui = ResponsiveUIManager.getResponsiveUIForTab(tab); await ui.emulationFront.setElementPickerState(state); }, /** - * When the picker is canceled, make sure the toolbox - * gets the focus. - */ - _onPickerCanceled: function() { - if (this.hostType !== Toolbox.HostType.WINDOW) { - this.win.focus(); - } - }, - - /** * The element picker button enables the ability to select a DOM node by clicking * it on the page. */ _buildPickerButton() { this.pickerButton = this._createButtonState({ id: "command-button-pick", description: this._getPickerTooltip(), onClick: this._onPickerClick, @@ -3314,17 +3318,16 @@ Toolbox.prototype = { this._initInspector = async function() { // Temporary fix for bug #1493131 - inspector has a different life cycle // than most other fronts because it is closely related to the toolbox. // TODO: replace with getFront once inspector is separated from the toolbox // TODO: remove these bindings this._inspector = await this.target.getFront("inspector"); this._walker = this.inspectorFront.walker; this._highlighter = this.inspectorFront.highlighter; - this._selection = this.inspectorFront.selection; this.inspectorFront.nodePicker.on( "picker-starting", this._onPickerStarting ); this.inspectorFront.nodePicker.on( "picker-started", this._onPickerStarted @@ -3332,17 +3335,24 @@ Toolbox.prototype = { this.inspectorFront.nodePicker.on( "picker-stopped", this._onPickerStopped ); this.inspectorFront.nodePicker.on( "picker-node-canceled", this._onPickerCanceled ); - this._selection.on("new-node-front", this._onNewSelectedNodeFront); + this.inspectorFront.nodePicker.on( + "picker-node-picked", + this._onPickerPicked + ); + this.inspectorFront.nodePicker.on( + "picker-node-previewed", + this._onPickerPreviewed + ); registerWalkerListeners(this); }.bind(this)(); } return this._initInspector; }, /** * An helper function that returns an object contain a highlighter and unhighlighter @@ -3451,17 +3461,16 @@ Toolbox.prototype = { } // Temporary fix for bug #1493131 - inspector has a different life cycle // than most other fronts because it is closely related to the toolbox. this._inspector.destroy(); this._inspector = null; this._highlighter = null; - this._selection = null; this._walker = null; }, /** * Get the toolbox's notification component * * @return The notification box component. */ @@ -3619,17 +3628,20 @@ Toolbox.prototype = { // then destroying the host, successfully or not) before destroying the // target. const onceDestroyed = new Promise(resolve => { resolve( settleAll(outstanding) .catch(console.error) .then(async () => { // Destroying the walker and inspector fronts - await this.destroyInspector(); + this.destroyInspector(); + + this.selection.destroy(); + this.selection = null; if (this._netMonitorAPI) { this._netMonitorAPI.destroy(); this._netMonitorAPI = null; } this._removeWindowListeners(); this._removeChromeEventHandlerEvents();
--- a/devtools/client/inspector/inspector.js +++ b/devtools/client/inspector/inspector.js @@ -224,17 +224,16 @@ Inspector.prototype = { this._markupBox = this.panelDoc.getElementById("markup-box"); return this._deferredOpen(); }, async initInspectorFront() { this.inspectorFront = await this.target.getFront("inspector"); this.highlighter = this.inspectorFront.highlighter; - this.selection = this.inspectorFront.selection; this.walker = this.inspectorFront.walker; }, get toolbox() { return this._toolbox; }, get highlighters() { @@ -292,16 +291,20 @@ Inspector.prototype = { this.searchBox, this.searchClearButton ); } return this._search; }, + get selection() { + return this.toolbox.selection; + }, + get cssProperties() { return this._cssProperties.cssProperties; }, /** * Handle promise rejections for various asynchronous actions, and only log errors if * the inspector panel still exists. * This is useful to silence useless errors that happen when the inspector is closed
--- a/devtools/client/shared/widgets/ShapesInContextEditor.js +++ b/devtools/client/shared/widgets/ShapesInContextEditor.js @@ -149,16 +149,21 @@ class ShapesInContextEditor { */ async hide() { try { await this.highlighter.hide(); } catch (err) { // silent error } + // Stop if the panel has been destroyed during the call to hide. + if (this.destroyed) { + return; + } + if (this.swatch) { this.swatch.classList.remove("active"); } this.swatch = null; this.rule = null; this.textPropIndex = -1; this.textPropName = null; @@ -329,12 +334,14 @@ class ShapesInContextEditor { this.textProperty.setValue(value); } destroy() { this.highlighter.off("highlighter-event", this.onHighlighterEvent); this.ruleView.off("ruleview-changed", this.onRuleViewChanged); this.highligherEventHandlers = {}; + + this.destroyed = true; } } module.exports = ShapesInContextEditor;
--- a/devtools/shared/fronts/accessibility.js +++ b/devtools/shared/fronts/accessibility.js @@ -1,27 +1,28 @@ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + "use strict"; const { FrontClassWithSpec, registerFront, } = require("devtools/shared/protocol.js"); const { accessibleSpec, accessibleWalkerSpec, accessibilitySpec, } = require("devtools/shared/specs/accessibility"); const events = require("devtools/shared/event-emitter"); class AccessibleFront extends FrontClassWithSpec(accessibleSpec) { - constructor(client) { - super(client); + constructor(client, targetFront, parentFront) { + super(client, targetFront, parentFront); this.before("audited", this.audited.bind(this)); this.before("name-change", this.nameChange.bind(this)); this.before("value-change", this.valueChange.bind(this)); this.before("description-change", this.descriptionChange.bind(this)); this.before("shortcut-change", this.shortcutChange.bind(this)); this.before("reorder", this.reorder.bind(this)); this.before("text-change", this.textChange.bind(this)); @@ -151,18 +152,18 @@ class AccessibleFront extends FrontClass hydrate() { return super.hydrate().then(properties => { Object.assign(this._form, properties); }); } } class AccessibleWalkerFront extends FrontClassWithSpec(accessibleWalkerSpec) { - constructor(client) { - super(client); + constructor(client, targetFront, parentFront) { + super(client, targetFront, parentFront); this.before("accessible-destroy", this.accessibleDestroy.bind(this)); } accessibleDestroy(accessible) { accessible.destroy(); } form(json) { @@ -174,18 +175,18 @@ class AccessibleWalkerFront extends Fron return this.pickAndFocus(); } return super.pick(); } } class AccessibilityFront extends FrontClassWithSpec(accessibilitySpec) { - constructor(client) { - super(client); + constructor(client, targetFront, parentFront) { + super(client, targetFront, parentFront); this.before("init", this.init.bind(this)); this.before("shutdown", this.shutdown.bind(this)); this.before("can-be-enabled-change", this.canBeEnabled.bind(this)); this.before("can-be-disabled-change", this.canBeDisabled.bind(this)); // Attribute name from which to retrieve the actorID out of the target actor's form this.formAttributeName = "accessibilityActor";
--- a/devtools/shared/fronts/addon/addons.js +++ b/devtools/shared/fronts/addon/addons.js @@ -1,22 +1,23 @@ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + "use strict"; const { addonsSpec } = require("devtools/shared/specs/addon/addons"); const { FrontClassWithSpec, registerFront, } = require("devtools/shared/protocol"); class AddonsFront extends FrontClassWithSpec(addonsSpec) { - constructor(client) { - super(client); + constructor(client, targetFront, parentFront) { + super(client, targetFront, parentFront); // Attribute name from which to retrieve the actorID out of the target actor's form this.formAttributeName = "addonsActor"; } } exports.AddonsFront = AddonsFront; registerFront(AddonsFront);
--- a/devtools/shared/fronts/addon/webextension-inspected-window.js +++ b/devtools/shared/fronts/addon/webextension-inspected-window.js @@ -1,11 +1,12 @@ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + "use strict"; const { webExtensionInspectedWindowSpec, } = require("devtools/shared/specs/addon/webextension-inspected-window"); const { FrontClassWithSpec, @@ -13,18 +14,18 @@ const { } = require("devtools/shared/protocol"); /** * The corresponding Front object for the WebExtensionInspectedWindowActor. */ class WebExtensionInspectedWindowFront extends FrontClassWithSpec( webExtensionInspectedWindowSpec ) { - constructor(client) { - super(client); + constructor(client, targetFront, parentFront) { + super(client, targetFront, parentFront); // Attribute name from which to retrieve the actorID out of the target actor's form this.formAttributeName = "webExtensionInspectedWindowActor"; } } exports.WebExtensionInspectedWindowFront = WebExtensionInspectedWindowFront; registerFront(WebExtensionInspectedWindowFront);
--- a/devtools/shared/fronts/animation.js +++ b/devtools/shared/fronts/animation.js @@ -1,25 +1,26 @@ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + "use strict"; const { FrontClassWithSpec, registerFront, } = require("devtools/shared/protocol"); const { animationPlayerSpec, animationsSpec, } = require("devtools/shared/specs/animation"); class AnimationPlayerFront extends FrontClassWithSpec(animationPlayerSpec) { - constructor(conn, form) { - super(conn, form); + constructor(conn, targetFront, parentFront) { + super(conn, targetFront, parentFront); this.state = {}; this.before("changed", this.onChanged.bind(this)); } form(form) { this._form = form; this.state = this.initialState; @@ -195,18 +196,18 @@ class AnimationPlayerFront extends Front }; } } exports.AnimationPlayerFront = AnimationPlayerFront; registerFront(AnimationPlayerFront); class AnimationsFront extends FrontClassWithSpec(animationsSpec) { - constructor(client) { - super(client); + constructor(client, targetFront, parentFront) { + super(client, targetFront, parentFront); // Attribute name from which to retrieve the actorID out of the target actor's form this.formAttributeName = "animationsActor"; } } exports.AnimationsFront = AnimationsFront; registerFront(AnimationsFront);
--- a/devtools/shared/fronts/changes.js +++ b/devtools/shared/fronts/changes.js @@ -9,18 +9,18 @@ const { registerFront, } = require("devtools/shared/protocol"); const { changesSpec } = require("devtools/shared/specs/changes"); /** * ChangesFront, the front object for the ChangesActor */ class ChangesFront extends FrontClassWithSpec(changesSpec) { - constructor(client) { - super(client); + constructor(client, targetFront, parentFront) { + super(client, targetFront, parentFront); // Attribute name from which to retrieve the actorID out of the target actor's form this.formAttributeName = "changesActor"; } } exports.ChangesFront = ChangesFront; registerFront(ChangesFront);
--- a/devtools/shared/fronts/css-properties.js +++ b/devtools/shared/fronts/css-properties.js @@ -1,97 +1,72 @@ /* 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"; -loader.lazyRequireGetter( - this, - "CSS_PROPERTIES_DB", - "devtools/shared/css/properties-db", - true -); +const { + FrontClassWithSpec, + registerFront, +} = require("devtools/shared/protocol"); +const { cssPropertiesSpec } = require("devtools/shared/specs/css-properties"); loader.lazyRequireGetter( this, "cssColors", "devtools/shared/css/color-db", true ); - -const { - FrontClassWithSpec, - registerFront, -} = require("devtools/shared/protocol"); -const { cssPropertiesSpec } = require("devtools/shared/specs/css-properties"); +loader.lazyRequireGetter( + this, + "CSS_PROPERTIES_DB", + "devtools/shared/css/properties-db", + true +); +loader.lazyRequireGetter( + this, + "CSS_TYPES", + "devtools/shared/css/constants", + true +); /** * Build up a regular expression that matches a CSS variable token. This is an * ident token that starts with two dashes "--". * * https://www.w3.org/TR/css-syntax-3/#ident-token-diagram */ var NON_ASCII = "[^\\x00-\\x7F]"; var ESCAPE = "\\\\[^\n\r]"; var FIRST_CHAR = ["[_a-z]", NON_ASCII, ESCAPE].join("|"); var TRAILING_CHAR = ["[_a-z0-9-]", NON_ASCII, ESCAPE].join("|"); var IS_VARIABLE_TOKEN = new RegExp( `^--(${FIRST_CHAR})(${TRAILING_CHAR})*$`, "i" ); -loader.lazyRequireGetter( - this, - "CSS_TYPES", - "devtools/shared/css/constants", - true -); - -/** - * Check that this is a CSS variable. - * - * @param {String} input - * @return {Boolean} - */ -function isCssVariable(input) { - return !!input.match(IS_VARIABLE_TOKEN); -} - var cachedCssProperties = new WeakMap(); /** * The CssProperties front provides a mechanism to have a one-time asynchronous * load of a CSS properties database. This is then fed into the CssProperties * interface that provides synchronous methods for finding out what CSS * properties the current server supports. */ class CssPropertiesFront extends FrontClassWithSpec(cssPropertiesSpec) { - constructor(client) { - super(client); + constructor(client, targetFront) { + super(client, targetFront); // Attribute name from which to retrieve the actorID out of the target actor's form this.formAttributeName = "cssPropertiesActor"; } } /** - * Query the feature supporting status in the featureSet. - * - * @param {Hashmap} featureSet the feature set hashmap - * @param {String} feature the feature name string - * @return {Boolean} has the feature or not - */ -function hasFeature(featureSet, feature) { - if (feature in featureSet) { - return featureSet[feature]; - } - return false; -} - -/** * Ask questions to a CSS database. This class does not care how the database * gets loaded in, only the questions that you can ask to it. * Prototype functions are bound to 'this' so they can be passed around as helper * functions. * * @param {Object} db * A database of CSS properties * @param {Object} inheritedList @@ -205,16 +180,40 @@ CssProperties.prototype = { * @return {Boolean} Return true if the server supports css-color-4 color function. */ supportsCssColor4ColorFunction() { return this.cssColor4ColorFunction; }, }; /** + * Check that this is a CSS variable. + * + * @param {String} input + * @return {Boolean} + */ +function isCssVariable(input) { + return !!input.match(IS_VARIABLE_TOKEN); +} + +/** + * Query the feature supporting status in the featureSet. + * + * @param {Hashmap} featureSet the feature set hashmap + * @param {String} feature the feature name string + * @return {Boolean} has the feature or not + */ +function hasFeature(featureSet, feature) { + if (feature in featureSet) { + return featureSet[feature]; + } + return false; +} + +/** * Create a CssProperties object with a fully loaded CSS database. The * CssProperties interface can be queried synchronously, but the initialization * is potentially async and should be handled up-front when the tool is created. * * The front is returned only with this function so that it can be destroyed * once the toolbox is destroyed. * * @param {Toolbox} The current toolbox.
--- a/devtools/shared/fronts/device.js +++ b/devtools/shared/fronts/device.js @@ -1,24 +1,25 @@ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + "use strict"; const { Cu } = require("chrome"); const { deviceSpec } = require("devtools/shared/specs/device"); const { FrontClassWithSpec, registerFront, } = require("devtools/shared/protocol"); const defer = require("devtools/shared/defer"); class DeviceFront extends FrontClassWithSpec(deviceSpec) { - constructor(client) { - super(client); + constructor(client, targetFront, parentFront) { + super(client, targetFront, parentFront); // Attribute name from which to retrieve the actorID out of the target actor's form this.formAttributeName = "deviceActor"; // Backward compatibility when connected to Firefox 69 or older. // Re-emit the "multi-e10s-updated" event as "can-debug-sw-updated". // Front events are all cleared via EventEmitter::clearEvents in the Front // base class destroy.
--- a/devtools/shared/fronts/emulation.js +++ b/devtools/shared/fronts/emulation.js @@ -1,25 +1,26 @@ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + "use strict"; const { FrontClassWithSpec, registerFront, } = require("devtools/shared/protocol"); const { emulationSpec } = require("devtools/shared/specs/emulation"); /** * The corresponding Front object for the EmulationActor. */ class EmulationFront extends FrontClassWithSpec(emulationSpec) { - constructor(client) { - super(client); + constructor(client, targetFront, parentFront) { + super(client, targetFront, parentFront); // Attribute name from which to retrieve the actorID out of the target actor's form this.formAttributeName = "emulationActor"; } } exports.EmulationFront = EmulationFront; registerFront(EmulationFront);
--- a/devtools/shared/fronts/framerate.js +++ b/devtools/shared/fronts/framerate.js @@ -1,25 +1,26 @@ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + "use strict"; const { FrontClassWithSpec, registerFront, } = require("devtools/shared/protocol"); const { framerateSpec } = require("devtools/shared/specs/framerate"); /** * The corresponding Front object for the FramerateActor. */ class FramerateFront extends FrontClassWithSpec(framerateSpec) { - constructor(client) { - super(client); + constructor(client, targetFront, parentFront) { + super(client, targetFront, parentFront); // Attribute name from which to retrieve the actorID out of the target actor's form this.formAttributeName = "framerateActor"; } } exports.FramerateFront = FramerateFront; registerFront(FramerateFront);
--- a/devtools/shared/fronts/highlighters.js +++ b/devtools/shared/fronts/highlighters.js @@ -1,26 +1,27 @@ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + "use strict"; const { FrontClassWithSpec, registerFront, } = require("devtools/shared/protocol"); const flags = require("devtools/shared/flags"); const { customHighlighterSpec, highlighterSpec, } = require("devtools/shared/specs/highlighters"); class HighlighterFront extends FrontClassWithSpec(highlighterSpec) { - constructor(client) { - super(client); + constructor(client, targetFront, parentFront) { + super(client, targetFront, parentFront); this.isNodeFrontHighlighted = false; this.isPicking = false; } // Update the object given a form representation off the wire. form(json) { this.actorID = json.actor; @@ -97,18 +98,18 @@ class HighlighterFront extends FrontClas this.emit("node-unhighlight"); } } exports.HighlighterFront = HighlighterFront; registerFront(HighlighterFront); class CustomHighlighterFront extends FrontClassWithSpec(customHighlighterSpec) { - constructor(client) { - super(client); + constructor(client, targetFront, parentFront) { + super(client, targetFront, parentFront); this._isShown = false; } show(...args) { this._isShown = true; return super.show(...args); }
--- a/devtools/shared/fronts/inspector.js +++ b/devtools/shared/fronts/inspector.js @@ -1,64 +1,60 @@ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + "use strict"; +const Services = require("Services"); +const defer = require("devtools/shared/defer"); const Telemetry = require("devtools/client/shared/telemetry"); -const telemetry = new Telemetry(); const { NodePicker } = require("devtools/shared/fronts/inspector/node-picker"); -const TELEMETRY_EYEDROPPER_OPENED = "DEVTOOLS_EYEDROPPER_OPENED_COUNT"; -const TELEMETRY_EYEDROPPER_OPENED_MENU = - "DEVTOOLS_MENU_EYEDROPPER_OPENED_COUNT"; -const SHOW_ALL_ANONYMOUS_CONTENT_PREF = - "devtools.inspector.showAllAnonymousContent"; -const SHOW_UA_SHADOW_ROOTS_PREF = "devtools.inspector.showUserAgentShadowRoots"; - const { FrontClassWithSpec, types, registerFront, } = require("devtools/shared/protocol.js"); const { inspectorSpec, walkerSpec, } = require("devtools/shared/specs/inspector"); -const Services = require("Services"); -const defer = require("devtools/shared/defer"); loader.lazyRequireGetter( this, "nodeConstants", "devtools/shared/dom-node-constants" ); -loader.lazyRequireGetter( - this, - "Selection", - "devtools/client/framework/selection", - true -); loader.lazyRequireGetter(this, "flags", "devtools/shared/flags"); +const TELEMETRY_EYEDROPPER_OPENED = "DEVTOOLS_EYEDROPPER_OPENED_COUNT"; +const TELEMETRY_EYEDROPPER_OPENED_MENU = + "DEVTOOLS_MENU_EYEDROPPER_OPENED_COUNT"; +const SHOW_ALL_ANONYMOUS_CONTENT_PREF = + "devtools.inspector.showAllAnonymousContent"; +const SHOW_UA_SHADOW_ROOTS_PREF = "devtools.inspector.showUserAgentShadowRoots"; + +const telemetry = new Telemetry(); + /** * Client side of the DOM walker. */ class WalkerFront extends FrontClassWithSpec(walkerSpec) { /** * This is kept for backward-compatibility reasons with older remote target. * Targets previous to bug 916443 */ pick() { return super.pick().then(response => { return response.node; }); } - constructor(client) { - super(client); + constructor(client, targetFront, parentFront) { + super(client, targetFront, parentFront); this._createRootNodePromise(); this._orphaned = new Set(); this._retainedOrphans = new Set(); // Set to true if cleanup should be requested after every mutation list. this.autoCleanup = true; this.before("new-mutations", this.onMutations.bind(this)); @@ -476,36 +472,30 @@ class WalkerFront extends FrontClassWith exports.WalkerFront = WalkerFront; registerFront(WalkerFront); /** * Client side of the inspector actor, which is used to create * inspector-related actors, including the walker. */ class InspectorFront extends FrontClassWithSpec(inspectorSpec) { - constructor(client) { - super(client); + constructor(client, targetFront, parentFront) { + super(client, targetFront, parentFront); this._client = client; this._highlighters = new Map(); // Attribute name from which to retrieve the actorID out of the target actor's form this.formAttributeName = "inspectorActor"; } // async initialization async initialize() { await Promise.all([this._getWalker(), this._getHighlighter()]); - - this.selection = new Selection(this.walker); - this.nodePicker = new NodePicker( - this.highlighter, - this.walker, - this.selection - ); + this.nodePicker = new NodePicker(this.highlighter, this.walker); } async _getWalker() { const showAllAnonymousContent = Services.prefs.getBoolPref( SHOW_ALL_ANONYMOUS_CONTENT_PREF ); const showUserAgentShadowRoots = Services.prefs.getBoolPref( SHOW_UA_SHADOW_ROOTS_PREF @@ -521,19 +511,16 @@ class InspectorFront extends FrontClassW this.highlighter = await this.getHighlighter(autohide); } hasHighlighter(type) { return this._highlighters.has(type); } destroy() { - // Selection isn't a Front and so isn't managed by InspectorFront - // and has to be destroyed manually - this.selection.destroy(); // Highlighter fronts are managed by InspectorFront and so will be // automatically destroyed. But we have to clear the `_highlighters` // Map as well as explicitly call `finalize` request on all of them. this.destroyHighlighters(); super.destroy(); } destroyHighlighters() {
--- a/devtools/shared/fronts/inspector/node-picker.js +++ b/devtools/shared/fronts/inspector/node-picker.js @@ -1,37 +1,36 @@ /* 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"; + loader.lazyRequireGetter(this, "EventEmitter", "devtools/shared/event-emitter"); /** * Client-side NodePicker module. * To be used by inspector front when it needs to select DOM elements. */ /** * Get the NodePicker instance for an inspector front. * The NodePicker wraps the highlighter so that it can interact with the * walkerFront and selection api. The nodeFront is stateless, with the * HighlighterFront managing it's own state. * * @param {highlighter} highlighterFront * @param {walker} walkerFront - * @param {selection} selection api * @return {Object} the NodePicker public API */ class NodePicker extends EventEmitter { - constructor(highlighter, walker, selection) { + constructor(highlighter, walker) { super(); this.highlighter = highlighter; this.walker = walker; - this.selection = selection; this.cancel = this.cancel.bind(this); this.start = this.start.bind(this); this.stop = this.stop.bind(this); this.togglePicker = this.togglePicker.bind(this); this._onHovered = this._onHovered.bind(this); this._onPicked = this._onPicked.bind(this); @@ -112,27 +111,27 @@ class NodePicker extends EventEmitter { this.emit("picker-node-hovered", data.node); } /** * When a node has been picked while the highlighter is in picker mode * @param {Object} data Information about the picked node */ _onPicked(data) { - this.selection.setNodeFront(data.node, { reason: "picker-node-picked" }); + this.emit("picker-node-picked", data.node); return this.stop(); } /** * When a node has been shift-clicked (previewed) while the highlighter is in * picker mode * @param {Object} data Information about the picked node */ _onPreviewed(data) { - this.selection.setNodeFront(data.node, { reason: "picker-node-previewed" }); + this.emit("picker-node-previewed", data.node); } /** * When the picker is canceled, stop the picker, and make sure the toolbox * gets the focus. */ _onCanceled() { return this.cancel();
--- a/devtools/shared/fronts/memory.js +++ b/devtools/shared/fronts/memory.js @@ -1,11 +1,12 @@ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + "use strict"; const { memorySpec } = require("devtools/shared/specs/memory"); const { FrontClassWithSpec, registerFront, } = require("devtools/shared/protocol"); @@ -17,18 +18,18 @@ loader.lazyRequireGetter( ); loader.lazyRequireGetter( this, "HeapSnapshotFileUtils", "devtools/shared/heapsnapshot/HeapSnapshotFileUtils" ); class MemoryFront extends FrontClassWithSpec(memorySpec) { - constructor(client) { - super(client); + constructor(client, targetFront, parentFront) { + super(client, targetFront, parentFront); this._client = client; this.heapSnapshotFileActorID = null; // Attribute name from which to retrieve the actorID out of the target actor's form this.formAttributeName = "memoryActor"; } /**
--- a/devtools/shared/fronts/node.js +++ b/devtools/shared/fronts/node.js @@ -1,22 +1,21 @@ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + "use strict"; +const promise = require("promise"); const { FrontClassWithSpec, types, registerFront, } = require("devtools/shared/protocol.js"); - const { nodeSpec, nodeListSpec } = require("devtools/shared/specs/node"); - -const promise = require("promise"); const { SimpleStringFront } = require("devtools/shared/fronts/string"); loader.lazyRequireGetter( this, "nodeConstants", "devtools/shared/dom-node-constants" ); @@ -109,18 +108,18 @@ class AttributeModificationList { * Children are stored in a doubly-linked list, to make addition/removal * and traversal quick. * * Due to the order/incompleteness of the child list, it is safe to use * the parent node from clients, but the `children` request should be used * to traverse children. */ class NodeFront extends FrontClassWithSpec(nodeSpec) { - constructor(conn, form) { - super(conn, form); + constructor(conn, targetFront, parentFront) { + super(conn, targetFront, parentFront); // The parent node this._parent = null; // The first child of this node. this._child = null; // The next sibling of this node. this._next = null; // The previous sibling of this node. this._prev = null; @@ -395,16 +394,20 @@ class NodeFront extends FrontClassWithSp if (!parent.isDisplayed) { return false; } parent = parent.parentNode(); } return true; } + get walkerFront() { + return this.parentFront; + } + getNodeValue() { // backward-compatibility: if nodevalue is null and shortValue is defined, the actual // value of the node needs to be fetched on the server. if (this._form.nodeValue === null && this._form.shortValue) { return super.getNodeValue(); } const str = this._form.nodeValue || "";
--- a/devtools/shared/fronts/perf.js +++ b/devtools/shared/fronts/perf.js @@ -1,21 +1,22 @@ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + "use strict"; const { FrontClassWithSpec, registerFront, } = require("devtools/shared/protocol"); const { perfSpec } = require("devtools/shared/specs/perf"); class PerfFront extends FrontClassWithSpec(perfSpec) { - constructor(client) { - super(client); + constructor(client, targetFront, parentFront) { + super(client, targetFront, parentFront); // Attribute name from which to retrieve the actorID out of the target actor's form this.formAttributeName = "perfActor"; } } registerFront(PerfFront);
--- a/devtools/shared/fronts/performance-recording.js +++ b/devtools/shared/fronts/performance-recording.js @@ -58,18 +58,18 @@ class PerformanceRecordingFront extends // just finished. This is because GC/Compositing markers can come into the array out // of order with the other markers, leading to strange collapsing in waterfall view. if (this._completed && !this._markersSorted) { this._markers = this._markers.sort((a, b) => a.start > b.start); this._markersSorted = true; } } - constructor(client) { - super(client); + constructor(client, targetFront, parentFront) { + super(client, targetFront, parentFront); this._markers = []; this._frames = []; this._memory = []; this._ticks = []; this._allocations = { sites: [], timestamps: [], frames: [], sizes: [] }; } /**
--- a/devtools/shared/fronts/performance.js +++ b/devtools/shared/fronts/performance.js @@ -1,11 +1,12 @@ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + "use strict"; const { Cu } = require("chrome"); const { FrontClassWithSpec, registerFront, } = require("devtools/shared/protocol"); const { @@ -16,18 +17,18 @@ const { performanceSpec } = require("dev loader.lazyRequireGetter( this, "PerformanceIO", "devtools/client/performance/modules/io" ); loader.lazyRequireGetter(this, "getSystemInfo", "devtools/shared/system", true); class PerformanceFront extends FrontClassWithSpec(performanceSpec) { - constructor(client) { - super(client); + constructor(client, targetFront, parentFront) { + super(client, targetFront, parentFront); this._queuedRecordings = []; this._onRecordingStartedEvent = this._onRecordingStartedEvent.bind(this); this.flushQueuedRecordings = this.flushQueuedRecordings.bind(this); this.before("profiler-status", this._onProfilerStatus.bind(this)); this.before("timeline-data", this._onTimelineEvent.bind(this)); this.on("recording-started", this._onRecordingStartedEvent);
--- a/devtools/shared/fronts/preference.js +++ b/devtools/shared/fronts/preference.js @@ -1,22 +1,23 @@ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + "use strict"; const { preferenceSpec } = require("devtools/shared/specs/preference"); const { FrontClassWithSpec, registerFront, } = require("devtools/shared/protocol"); class PreferenceFront extends FrontClassWithSpec(preferenceSpec) { - constructor(client) { - super(client); + constructor(client, targetFront, parentFront) { + super(client, targetFront, parentFront); // Attribute name from which to retrieve the actorID out of the target actor's form this.formAttributeName = "preferenceActor"; } } exports.PreferenceFront = PreferenceFront; registerFront(PreferenceFront);
--- a/devtools/shared/fronts/promises.js +++ b/devtools/shared/fronts/promises.js @@ -1,25 +1,26 @@ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + "use strict"; const { FrontClassWithSpec, registerFront, } = require("devtools/shared/protocol"); const { promisesSpec } = require("devtools/shared/specs/promises"); /** * PromisesFront, the front for the PromisesActor. */ class PromisesFront extends FrontClassWithSpec(promisesSpec) { - constructor(client) { - super(client); + constructor(client, targetFront, parentFront) { + super(client, targetFront, parentFront); // Attribute name from which to retrieve the actorID out of the target actor's form this.formAttributeName = "promisesActor"; } } exports.PromisesFront = PromisesFront; registerFront(PromisesFront);
--- a/devtools/shared/fronts/reflow.js +++ b/devtools/shared/fronts/reflow.js @@ -14,18 +14,18 @@ const { * Usage example of the reflow front: * * let front = await target.getFront("reflow"); * front.on("reflows", this._onReflows); * front.start(); * // now wait for events to come */ class ReflowFront extends FrontClassWithSpec(reflowSpec) { - constructor(client) { - super(client); + constructor(client, targetFront, parentFront) { + super(client, targetFront, parentFront); // Attribute name from which to retrieve the actorID out of the target actor's form this.formAttributeName = "reflowActor"; } } exports.ReflowFront = ReflowFront; registerFront(ReflowFront);
--- a/devtools/shared/fronts/screenshot.js +++ b/devtools/shared/fronts/screenshot.js @@ -1,23 +1,24 @@ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + "use strict"; const { screenshotSpec } = require("devtools/shared/specs/screenshot"); const saveScreenshot = require("devtools/shared/screenshot/save"); const { FrontClassWithSpec, registerFront, } = require("devtools/shared/protocol"); class ScreenshotFront extends FrontClassWithSpec(screenshotSpec) { - constructor(client) { - super(client); + constructor(client, targetFront, parentFront) { + super(client, targetFront, parentFront); // Attribute name from which to retrieve the actorID out of the target actor's form this.formAttributeName = "screenshotActor"; } async captureAndSave(window, args) { const screenshot = await this.capture(args); return saveScreenshot(window, args, screenshot);
--- a/devtools/shared/fronts/storage.js +++ b/devtools/shared/fronts/storage.js @@ -1,11 +1,12 @@ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + "use strict"; const { FrontClassWithSpec, registerFront, } = require("devtools/shared/protocol"); const { childSpecs, storageSpec } = require("devtools/shared/specs/storage"); @@ -16,18 +17,18 @@ for (const childSpec of Object.values(ch this.hosts = form.hosts; return null; } } registerFront(ChildStorageFront); } class StorageFront extends FrontClassWithSpec(storageSpec) { - constructor(client) { - super(client); + constructor(client, targetFront, parentFront) { + super(client, targetFront, parentFront); // Attribute name from which to retrieve the actorID out of the target actor's form this.formAttributeName = "storageActor"; } } exports.StorageFront = StorageFront; registerFront(StorageFront);
--- a/devtools/shared/fronts/styles.js +++ b/devtools/shared/fronts/styles.js @@ -1,11 +1,12 @@ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + "use strict"; const { FrontClassWithSpec, registerFront, } = require("devtools/shared/protocol"); const { pageStyleSpec, @@ -18,18 +19,18 @@ loader.lazyRequireGetter( "RuleRewriter", "devtools/shared/fronts/inspector/rule-rewriter" ); /** * PageStyleFront, the front object for the PageStyleActor */ class PageStyleFront extends FrontClassWithSpec(pageStyleSpec) { - constructor(conn) { - super(conn); + constructor(conn, targetFront, parentFront) { + super(conn, targetFront, parentFront); this.inspector = this.parent(); } form(form) { this._form = form; } get walker() { @@ -79,18 +80,18 @@ class PageStyleFront extends FrontClassW exports.PageStyleFront = PageStyleFront; registerFront(PageStyleFront); /** * StyleRuleFront, the front for the StyleRule actor. */ class StyleRuleFront extends FrontClassWithSpec(styleRuleSpec) { - constructor(client) { - super(client); + constructor(client, targetFront, parentFront) { + super(client, targetFront, parentFront); this.before("location-changed", this._locationChangedPre.bind(this)); } form(form) { this.actorID = form.actor; this._form = form; if (this._mediaText) {
--- a/devtools/shared/fronts/stylesheets.js +++ b/devtools/shared/fronts/stylesheets.js @@ -1,11 +1,12 @@ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + "use strict"; const { FrontClassWithSpec, registerFront, } = require("devtools/shared/protocol"); const { mediaRuleSpec, @@ -26,18 +27,18 @@ loader.lazyRequireGetter( "devtools/shared/indentation", true ); /** * Corresponding client-side front for a MediaRuleActor. */ class MediaRuleFront extends FrontClassWithSpec(mediaRuleSpec) { - constructor(client) { - super(client); + constructor(client, targetFront, parentFront) { + super(client, targetFront, parentFront); this._onMatchesChange = this._onMatchesChange.bind(this); this.on("matches-change", this._onMatchesChange); } _onMatchesChange(matches) { this._form.matches = matches; } @@ -69,18 +70,18 @@ class MediaRuleFront extends FrontClassW exports.MediaRuleFront = MediaRuleFront; registerFront(MediaRuleFront); /** * StyleSheetFront is the client-side counterpart to a StyleSheetActor. */ class StyleSheetFront extends FrontClassWithSpec(styleSheetSpec) { - constructor(conn, form) { - super(conn, form); + constructor(conn, targetFront, parentFront) { + super(conn, targetFront, parentFront); this._onPropertyChange = this._onPropertyChange.bind(this); this.on("property-change", this._onPropertyChange); } destroy() { this.off("property-change", this._onPropertyChange); super.destroy(); @@ -146,18 +147,18 @@ class StyleSheetFront extends FrontClass exports.StyleSheetFront = StyleSheetFront; registerFront(StyleSheetFront); /** * The corresponding Front object for the StyleSheetsActor. */ class StyleSheetsFront extends FrontClassWithSpec(styleSheetsSpec) { - constructor(client) { - super(client); + constructor(client, targetFront, parentFront) { + super(client, targetFront, parentFront); // Attribute name from which to retrieve the actorID out of the target actor's form this.formAttributeName = "styleSheetsActor"; } } exports.StyleSheetsFront = StyleSheetsFront; registerFront(StyleSheetsFront);
--- a/devtools/shared/fronts/targets/addon.js +++ b/devtools/shared/fronts/targets/addon.js @@ -11,18 +11,18 @@ const { loader.lazyRequireGetter( this, "BrowsingContextTargetFront", "devtools/shared/fronts/targets/browsing-context", true ); class AddonTargetFront extends FrontClassWithSpec(addonTargetSpec) { - constructor(client) { - super(client); + constructor(client, targetFront, parentFront) { + super(client, targetFront, parentFront); this.client = client; this.traits = {}; } form(json) { this.actorID = json.actor;
--- a/devtools/shared/fronts/thread.js +++ b/devtools/shared/fronts/thread.js @@ -29,18 +29,18 @@ loader.lazyRequireGetter( * is a front to the thread actor created in the server side, hiding the * protocol details in a traditional JavaScript API. * * @param client DebuggerClient * @param actor string * The actor ID for this thread. */ class ThreadFront extends FrontClassWithSpec(threadSpec) { - constructor(client) { - super(client); + constructor(client, targetFront, parentFront) { + super(client, targetFront, parentFront); this.client = client; this._pauseGrips = {}; this._threadGrips = {}; this._state = "paused"; this._beforePaused = this._beforePaused.bind(this); this._beforeResumed = this._beforeResumed.bind(this); this._beforeDetached = this._beforeDetached.bind(this); this.before("paused", this._beforePaused);
--- a/devtools/shared/fronts/webconsole.js +++ b/devtools/shared/fronts/webconsole.js @@ -13,25 +13,22 @@ const { registerFront, } = require("devtools/shared/protocol"); const { webconsoleSpec } = require("devtools/shared/specs/webconsole"); /** * A WebConsoleClient is used as a front end for the WebConsoleActor that is * created on the server, hiding implementation details. * - * @param object debuggerClient + * @param object client * The DebuggerClient instance we live for. - * @param object response - * The response packet received from the "startListeners" request sent to - * the WebConsoleActor. */ class WebConsoleFront extends FrontClassWithSpec(webconsoleSpec) { - constructor(client) { - super(client); + constructor(client, targetFront, parentFront) { + super(client, targetFront, parentFront); this._client = client; this.traits = {}; this._longStrings = {}; this.events = []; // Attribute name from which to retrieve the actorID out of the target actor's form this.formAttributeName = "consoleActor"; /**
--- a/devtools/shared/fronts/websocket.js +++ b/devtools/shared/fronts/websocket.js @@ -1,26 +1,27 @@ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + "use strict"; const { FrontClassWithSpec, registerFront, } = require("devtools/shared/protocol"); const { webSocketSpec } = require("devtools/shared/specs/websocket"); /** * A WebSocketFront is used as a front end for the WebSocketActor that is * created on the server, hiding implementation details. */ class WebSocketFront extends FrontClassWithSpec(webSocketSpec) { - constructor(client) { - super(client); + constructor(client, targetFront, parentFront) { + super(client, targetFront, parentFront); this._onWebSocketOpened = this._onWebSocketOpened.bind(this); this._onWebSocketClosed = this._onWebSocketClosed.bind(this); this._onFrameSent = this._onFrameSent.bind(this); this._onFrameReceived = this._onFrameReceived.bind(this); // Attribute name from which to retrieve the actorID // out of the target actor's form
--- a/devtools/shared/protocol/Front.js +++ b/devtools/shared/protocol/Front.js @@ -25,29 +25,38 @@ function defer() { reject: reject, promise: promise, }; } /** * Base class for client-side actor fronts. * - * @param optional conn - * Either a DebuggerServerConnection or a DebuggerClient. Must have + * @param [DebuggerClient|null] conn + * The conn must either be DebuggerClient or null. Must have * addActorPool, removeActorPool, and poolFor. * conn can be null if the subclass provides a conn property. + * @param [Target|null] target + * If we are instantiating a target-scoped front, this is a reference to the front's + * Target instance, otherwise this is null. + * @param [Front|null] parentFront + * The parent front. This is only available if the Front being initialized is a child + * of a parent front. * @constructor */ class Front extends Pool { - constructor(conn = null) { + constructor(conn = null, targetFront = null, parentFront = null) { super(conn); this.actorID = null; // The targetFront attribute represents the debuggable context. Only target-scoped // fronts and their children fronts will have the targetFront attribute set. - this.targetFront = null; + this.targetFront = targetFront; + // The parentFront attribute points to its parent front. Only children of + // target-scoped fronts will have the parentFront attribute set. + this.parentFront = parentFront; this._requests = []; // Front listener functions registered via `onFront` get notified // of new fronts via this dedicated EventEmitter object. this._frontListeners = new EventEmitter(); // List of optional listener for each event, that is processed immediatly on packet // receival, before emitting event via EventEmitter on the Front. @@ -70,16 +79,17 @@ class Front extends Pool { "\n\nRequest stack:\n" + stack.formattedStack; deferred.reject(new Error(msg)); } super.destroy(); this.clearEvents(); this.actorID = null; this.targetFront = null; + this.parentFront = null; this._frontListeners = null; this._beforeListeners = null; } manage(front) { if (!front.actorID) { throw new Error( "Can't manage front without an actor ID.\n" +
--- a/devtools/shared/protocol/types.js +++ b/devtools/shared/protocol/types.js @@ -320,25 +320,24 @@ types.addActorType = function(name) { // If front isn't instantiated yet, create one. // Try lazy loading front if not already loaded. // The front module will synchronously call `FrontClassWithSpec` and // augment `type` with the `frontClass` attribute. if (!type.frontClass) { lazyLoadFront(name); } + const parentFront = ctx.marshallPool(); + const targetFront = parentFront.targetFront; + // Use intermediate Class variable to please eslint requiring // a capital letter for all constructors. const Class = type.frontClass; - front = new Class(ctx.conn); + front = new Class(ctx.conn, targetFront, parentFront); front.actorID = actorID; - const parentFront = ctx.marshallPool(); - // If this is a child of a target-scoped front, propagate the target front to the - // child front that it manages. - front.targetFront = parentFront.targetFront; parentFront.manage(front); } // When the type `${name}#actorid` is used, `v` is a string refering to the // actor ID. We only set the actorID just before and so do not need anything else. if (detail != "actorid") { v = identityWrite(v); front.form(v, ctx); @@ -497,26 +496,24 @@ exports.registerFront = function(cls) { * @param [Target|null] target * If we are instantiating a target-scoped front, this is a reference to the front's * Target instance, otherwise this is null. */ function getFront(client, typeName, form, target = null) { const type = types.getType(typeName); if (!type) { throw new Error(`No spec for front type '${typeName}'.`); - } - if (!type.frontClass) { + } else if (!type.frontClass) { lazyLoadFront(typeName); } + // Use intermediate Class variable to please eslint requiring // a capital letter for all constructors. const Class = type.frontClass; - const instance = new Class(client); - // Set the targetFront for target-scoped fronts. - instance.targetFront = target; + const instance = new Class(client, target, target); const { formAttributeName } = instance; if (!formAttributeName) { throw new Error(`Can't find the form attribute name for ${typeName}`); } // Retrive the actor ID from root or target actor's form instance.actorID = form[formAttributeName]; if (!instance.actorID) { throw new Error(
--- a/docshell/base/nsDocShell.cpp +++ b/docshell/base/nsDocShell.cpp @@ -16,16 +16,17 @@ #endif #include "mozilla/ArrayUtils.h" #include "mozilla/Attributes.h" #include "mozilla/AutoRestore.h" #include "mozilla/BasePrincipal.h" #include "mozilla/Casting.h" #include "mozilla/Components.h" +#include "mozilla/DebugOnly.h" #include "mozilla/Encoding.h" #include "mozilla/EventStateManager.h" #include "mozilla/HTMLEditor.h" #include "mozilla/LoadInfo.h" #include "mozilla/Logging.h" #include "mozilla/MediaFeatureChange.h" #include "mozilla/Preferences.h" #include "mozilla/PresShell.h" @@ -8928,80 +8929,75 @@ nsresult nsDocShell::PerformRetargeting( } // Else we ran out of memory, or were a popup and got blocked, // or something. return rv; } -nsresult nsDocShell::MaybeHandleSameDocumentNavigation( - nsDocShellLoadState* aLoadState, bool* aWasSameDocument) { +bool nsDocShell::IsSameDocumentNavigation(nsDocShellLoadState* aLoadState, + SameDocumentNavigationState& aState) { MOZ_ASSERT(aLoadState); - MOZ_ASSERT(aWasSameDocument); - *aWasSameDocument = false; if (!(aLoadState->LoadType() == LOAD_NORMAL || aLoadState->LoadType() == LOAD_STOP_CONTENT || LOAD_TYPE_HAS_FLAGS(aLoadState->LoadType(), LOAD_FLAGS_REPLACE_HISTORY) || aLoadState->LoadType() == LOAD_HISTORY || aLoadState->LoadType() == LOAD_LINK)) { - return NS_OK; - } - nsresult rv; + return false; + } + nsCOMPtr<nsIURI> currentURI = mCurrentURI; - nsAutoCString curHash, newHash; - bool curURIHasRef = false, newURIHasRef = false; - - nsresult rvURINew = aLoadState->URI()->GetRef(newHash); + nsresult rvURINew = aLoadState->URI()->GetRef(aState.mNewHash); if (NS_SUCCEEDED(rvURINew)) { - rvURINew = aLoadState->URI()->GetHasRef(&newURIHasRef); - } - - bool sameExceptHashes = false; + rvURINew = aLoadState->URI()->GetHasRef(&aState.mNewURIHasRef); + } + if (currentURI && NS_SUCCEEDED(rvURINew)) { - nsresult rvURIOld = currentURI->GetRef(curHash); + nsresult rvURIOld = currentURI->GetRef(aState.mCurrentHash); if (NS_SUCCEEDED(rvURIOld)) { - rvURIOld = currentURI->GetHasRef(&curURIHasRef); + rvURIOld = currentURI->GetHasRef(&aState.mCurrentURIHasRef); } if (NS_SUCCEEDED(rvURIOld)) { if (NS_FAILED(currentURI->EqualsExceptRef(aLoadState->URI(), - &sameExceptHashes))) { - sameExceptHashes = false; - } - } - } - - if (!sameExceptHashes && sURIFixup && currentURI && NS_SUCCEEDED(rvURINew)) { + &aState.mSameExceptHashes))) { + aState.mSameExceptHashes = false; + } + } + } + + if (!aState.mSameExceptHashes && sURIFixup && currentURI && + NS_SUCCEEDED(rvURINew)) { // Maybe aLoadState->URI() came from the exposable form of currentURI? nsCOMPtr<nsIURI> currentExposableURI; - rv = sURIFixup->CreateExposableURI(currentURI, - getter_AddRefs(currentExposableURI)); - NS_ENSURE_SUCCESS(rv, rv); - nsresult rvURIOld = currentExposableURI->GetRef(curHash); + DebugOnly<nsresult> rv = sURIFixup->CreateExposableURI( + currentURI, getter_AddRefs(currentExposableURI)); + MOZ_ASSERT(NS_SUCCEEDED(rv), "CreateExposableURI should not fail, ever!"); + nsresult rvURIOld = currentExposableURI->GetRef(aState.mCurrentHash); if (NS_SUCCEEDED(rvURIOld)) { - rvURIOld = currentExposableURI->GetHasRef(&curURIHasRef); + rvURIOld = currentExposableURI->GetHasRef(&aState.mCurrentURIHasRef); } if (NS_SUCCEEDED(rvURIOld)) { - if (NS_FAILED(currentExposableURI->EqualsExceptRef(aLoadState->URI(), - &sameExceptHashes))) { - sameExceptHashes = false; - } - } - } - - bool historyNavBetweenSameDoc = false; + if (NS_FAILED(currentExposableURI->EqualsExceptRef( + aLoadState->URI(), &aState.mSameExceptHashes))) { + aState.mSameExceptHashes = false; + } + } + } + if (mOSHE && aLoadState->SHEntry()) { // We're doing a history load. - mOSHE->SharesDocumentWith(aLoadState->SHEntry(), &historyNavBetweenSameDoc); + mOSHE->SharesDocumentWith(aLoadState->SHEntry(), + &aState.mHistoryNavBetweenSameDoc); #ifdef DEBUG - if (historyNavBetweenSameDoc) { + if (aState.mHistoryNavBetweenSameDoc) { nsCOMPtr<nsIInputStream> currentPostData = mOSHE->GetPostData(); NS_ASSERTION(currentPostData == aLoadState->PostDataStream(), "Different POST data for entries for the same page?"); } #endif } // A same document navigation happens when we navigate between two SHEntries @@ -9014,23 +9010,31 @@ nsresult nsDocShell::MaybeHandleSameDocu // b) we're navigating to a new shentry whose URI differs from the // current URI only in its hash, the new hash is non-empty, and // we're not doing a POST. // // The restriction that the SHEntries in (a) must be different ensures // that history.go(0) and the like trigger full refreshes, rather than // same document navigations. bool doSameDocumentNavigation = - (historyNavBetweenSameDoc && mOSHE != aLoadState->SHEntry()) || + (aState.mHistoryNavBetweenSameDoc && mOSHE != aLoadState->SHEntry()) || (!aLoadState->SHEntry() && !aLoadState->PostDataStream() && - sameExceptHashes && newURIHasRef); - - if (!doSameDocumentNavigation) { - return NS_OK; - } + aState.mSameExceptHashes && aState.mNewURIHasRef); + + return doSameDocumentNavigation; +} + +nsresult nsDocShell::HandleSameDocumentNavigation( + nsDocShellLoadState* aLoadState, SameDocumentNavigationState& aState) { +#ifdef DEBUG + SameDocumentNavigationState state; + MOZ_ASSERT(IsSameDocumentNavigation(aLoadState, state)); +#endif + + nsCOMPtr<nsIURI> currentURI = mCurrentURI; // Save the position of the scrollers. nsPoint scrollPos = GetCurScrollPos(); // Reset mLoadType to its original value once we exit this block, because this // same document navigation might have started after a normal, network load, // and we don't want to clobber its load type. See bug 737307. AutoRestore<uint32_t> loadTypeResetter(mLoadType); @@ -9179,18 +9183,18 @@ nsresult nsDocShell::MaybeHandleSameDocu RefPtr<nsGlobalWindowInner> win = scriptGlobal ? scriptGlobal->GetCurrentInnerWindowInternal() : nullptr; // ScrollToAnchor doesn't necessarily cause us to scroll the window; // the function decides whether a scroll is appropriate based on the // arguments it receives. But even if we don't end up scrolling, // ScrollToAnchor performs other important tasks, such as informing // the presShell that we have a new hash. See bug 680257. - rv = ScrollToAnchor(curURIHasRef, newURIHasRef, newHash, - aLoadState->LoadType()); + nsresult rv = ScrollToAnchor(aState.mCurrentURIHasRef, aState.mNewURIHasRef, + aState.mNewHash, aLoadState->LoadType()); NS_ENSURE_SUCCESS(rv, rv); /* restore previous position of scroller(s), if we're moving * back in history (bug 59774) */ nscoord bx = 0; nscoord by = 0; bool needsScrollPosUpdate = false; @@ -9204,35 +9208,35 @@ nsresult nsDocShell::MaybeHandleSameDocu // Dispatch the popstate and hashchange events, as appropriate. // // The event dispatch below can cause us to re-enter script and // destroy the docshell, nulling out mScriptGlobal. Hold a stack // reference to avoid null derefs. See bug 914521. if (win) { // Fire a hashchange event URIs differ, and only in their hashes. - bool doHashchange = sameExceptHashes && (curURIHasRef != newURIHasRef || - !curHash.Equals(newHash)); - - if (historyNavBetweenSameDoc || doHashchange) { + bool doHashchange = aState.mSameExceptHashes && + (aState.mCurrentURIHasRef != aState.mNewURIHasRef || + !aState.mCurrentHash.Equals(aState.mNewHash)); + + if (aState.mHistoryNavBetweenSameDoc || doHashchange) { win->DispatchSyncPopState(); } if (needsScrollPosUpdate && win->HasActiveDocument()) { SetCurScrollPosEx(bx, by); } if (doHashchange) { // Note that currentURI hasn't changed because it's on the // stack, so we can just use it directly as the old URI. win->DispatchAsyncHashchange(currentURI, aLoadState->URI()); } } - *aWasSameDocument = true; return NS_OK; } nsresult nsDocShell::InternalLoad(nsDocShellLoadState* aLoadState, nsIDocShell** aDocShell, nsIRequest** aRequest) { MOZ_ASSERT(aLoadState, "need a load state!"); MOZ_ASSERT(aLoadState->TriggeringPrincipal(), @@ -9270,24 +9274,32 @@ nsresult nsDocShell::InternalLoad(nsDocS // If we have a target to move to, do that now. if (!aLoadState->Target().IsEmpty()) { return PerformRetargeting(aLoadState, aDocShell, aRequest); } // If we don't have a target, we're loading into ourselves, and our load // delegate may want to intercept that load. - bool handled; - rv = MaybeHandleLoadDelegate( - aLoadState, nsIBrowserDOMWindow::OPEN_CURRENTWINDOW, &handled); - if (NS_FAILED(rv)) { - return rv; - } - if (handled) { - return NS_OK; + SameDocumentNavigationState sameDocumentNavigationState; + bool sameDocument = + IsSameDocumentNavigation(aLoadState, sameDocumentNavigationState); + // LoadDelegate has already had chance to delegate loads which ended up to + // session history, so no need to re-delegate here, and we don't want fragment + // navigations to go through load delegate. + if (!sameDocument && !(aLoadState->LoadType() & LOAD_CMD_HISTORY)) { + bool handled; + rv = MaybeHandleLoadDelegate( + aLoadState, nsIBrowserDOMWindow::OPEN_CURRENTWINDOW, &handled); + if (NS_FAILED(rv)) { + return rv; + } + if (handled) { + return NS_OK; + } } // If a source docshell has been passed, check to see if we are sandboxed // from it as the result of an iframe or CSP sandbox. if (aLoadState->SourceDocShell() && aLoadState->SourceDocShell()->IsSandboxedFrom(this)) { return NS_ERROR_DOM_INVALID_ACCESS_ERR; } @@ -9386,20 +9398,19 @@ nsresult nsDocShell::InternalLoad(nsDocS mAllowKeywordFixup = aLoadState->HasLoadFlags(INTERNAL_LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP); mURIResultedInDocument = false; // reset the clock... // See if this is actually a load between two history entries for the same // document. If the process fails, or if we successfully navigate within the // same document, return. - bool wasSameDocument; - rv = MaybeHandleSameDocumentNavigation(aLoadState, &wasSameDocument); - if (NS_FAILED(rv) || wasSameDocument) { - return rv; + if (sameDocument) { + return HandleSameDocumentNavigation(aLoadState, + sameDocumentNavigationState); } // mContentViewer->PermitUnload can destroy |this| docShell, which // causes the next call of CanSavePresentation to crash. // Hold onto |this| until we return, to prevent a crash from happening. // (bug#331040) nsCOMPtr<nsIDocShell> kungFuDeathGrip(this); @@ -9596,16 +9607,28 @@ nsresult nsDocShell::InternalLoad(nsDocS mHistoryID = aLoadState->SHEntry()->DocshellID(); } } mSavingOldViewer = savePresentation; // If we have a saved content viewer in history, restore and show it now. if (aLoadState->SHEntry() && (mLoadType & LOAD_CMD_HISTORY)) { + // https://html.spec.whatwg.org/#history-traversal: + // To traverse the history + // "If entry has a different Document object than the current entry, then + // run the following substeps: Remove any tasks queued by the history + // traversal task source..." + // Same document object case was handled already above with + // HandleSameDocumentNavigation call. + RefPtr<ChildSHistory> shistory = GetRootSessionHistory(); + if (shistory) { + shistory->RemovePendingHistoryNavigations(); + } + // It's possible that the previous viewer of mContentViewer is the // viewer that will end up in aLoadState->SHEntry() when it gets closed. If // that's the case, we need to go ahead and force it into its shentry so we // can restore it. if (mContentViewer) { nsCOMPtr<nsIContentViewer> prevViewer = mContentViewer->GetPreviousViewer(); if (prevViewer) { @@ -11256,16 +11279,24 @@ nsresult nsDocShell::UpdateURLAndHistory NS_ENSURE_TRUE(mOSHE || aReplace, NS_ERROR_FAILURE); nsCOMPtr<nsISHEntry> oldOSHE = mOSHE; mLoadType = LOAD_PUSHSTATE; nsCOMPtr<nsISHEntry> newSHEntry; if (!aReplace) { // Step 2. + + // Step 2.2, "Remove any tasks queued by the history traversal task + // source..." + RefPtr<ChildSHistory> shistory = GetRootSessionHistory(); + if (shistory) { + shistory->RemovePendingHistoryNavigations(); + } + // Save the current scroll position (bug 590573). Step 2.3. nsPoint scrollPos = GetCurScrollPos(); mOSHE->SetScrollPosition(scrollPos.x, scrollPos.y); bool scrollRestorationIsManual = mOSHE->GetScrollRestorationIsManual(); nsCOMPtr<nsIContentSecurityPolicy> csp = aDocument->GetCsp(); // Since we're not changing which page we have loaded, pass
--- a/docshell/base/nsDocShell.h +++ b/docshell/base/nsDocShell.h @@ -1029,22 +1029,35 @@ class nsDocShell final : public nsDocLoa // In cases where we have a LoadURIDelegate (loading external links via // GeckoView), a load may need to be handled through the delegate. aWindowType // is either nsIBrowserDOMWindow::OPEN_CURRENTWINDOW or // nsIBrowserDOMWindow::OPEN_NEWWINDOW. nsresult MaybeHandleLoadDelegate(nsDocShellLoadState* aLoadState, uint32_t aWindowType, bool* aDidHandleLoad); - // Check to see if we're loading a prior history entry in the same document. - // If so, handle the scrolling or other action required instead of continuing - // with new document navigation. + struct SameDocumentNavigationState { + nsAutoCString mCurrentHash; + nsAutoCString mNewHash; + bool mCurrentURIHasRef = false; + bool mNewURIHasRef = false; + bool mSameExceptHashes = false; + bool mHistoryNavBetweenSameDoc = false; + }; + + // Check to see if we're loading a prior history entry or doing a fragment + // navigation in the same document. + bool IsSameDocumentNavigation(nsDocShellLoadState* aLoadState, + SameDocumentNavigationState& aState); + + // ... If so, handle the scrolling or other action required instead of + // continuing with new document navigation. MOZ_CAN_RUN_SCRIPT - nsresult MaybeHandleSameDocumentNavigation(nsDocShellLoadState* aLoadState, - bool* aWasSameDocument); + nsresult HandleSameDocumentNavigation(nsDocShellLoadState* aLoadState, + SameDocumentNavigationState& aState); private: // data members static nsIURIFixup* sURIFixup; #ifdef DEBUG // We're counting the number of |nsDocShells| to help find leaks static unsigned long gNumberOfDocShells; #endif /* DEBUG */
--- a/docshell/shistory/ChildSHistory.cpp +++ b/docshell/shistory/ChildSHistory.cpp @@ -48,16 +48,31 @@ void ChildSHistory::Go(int32_t aOffset, index += aOffset; if (!index.isValid()) { aRv.Throw(NS_ERROR_FAILURE); return; } aRv = mHistory->GotoIndex(index.value()); } +void ChildSHistory::AsyncGo(int32_t aOffset) { + if (!CanGo(aOffset)) { + return; + } + + RefPtr<PendingAsyncHistoryNavigation> asyncNav = + new PendingAsyncHistoryNavigation(this, aOffset); + mPendingNavigations.insertBack(asyncNav); + NS_DispatchToCurrentThread(asyncNav.forget()); +} + +void ChildSHistory::RemovePendingHistoryNavigations() { + mPendingNavigations.clear(); +} + void ChildSHistory::EvictLocalContentViewers() { mHistory->EvictAllContentViewers(); } nsISHistory* ChildSHistory::LegacySHistory() { return mHistory; } ParentSHistory* ChildSHistory::GetParentIfSameProcess() { if (XRE_IsContentProcess()) {
--- a/docshell/shistory/ChildSHistory.h +++ b/docshell/shistory/ChildSHistory.h @@ -18,16 +18,18 @@ */ #ifndef mozilla_dom_ChildSHistory_h #define mozilla_dom_ChildSHistory_h #include "nsCOMPtr.h" #include "mozilla/ErrorResult.h" #include "nsWrapperCache.h" +#include "nsThreadUtils.h" +#include "mozilla/LinkedList.h" class nsSHistory; class nsDocShell; class nsISHistory; class nsIWebNavigation; class nsIGlobalObject; namespace mozilla { @@ -57,29 +59,55 @@ class ChildSHistory : public nsISupports /** * The CanGo and Go methods are called with an offset from the current index. * Positive numbers go forward in history, while negative numbers go * backwards. */ bool CanGo(int32_t aOffset); void Go(int32_t aOffset, ErrorResult& aRv); + void AsyncGo(int32_t aOffset); + + void RemovePendingHistoryNavigations(); /** * Evicts all content viewers within the current process. */ void EvictLocalContentViewers(); nsISHistory* LegacySHistory(); ParentSHistory* GetParentIfSameProcess(); private: virtual ~ChildSHistory(); + class PendingAsyncHistoryNavigation + : public Runnable, + public mozilla::LinkedListElement<PendingAsyncHistoryNavigation> { + public: + PendingAsyncHistoryNavigation(ChildSHistory* aHistory, int32_t aOffset) + : Runnable("PendingAsyncHistoryNavigation"), + mHistory(aHistory), + mOffset(aOffset) {} + + NS_IMETHOD Run() override { + if (isInList()) { + remove(); + mHistory->Go(mOffset, IgnoreErrors()); + } + return NS_OK; + } + + private: + RefPtr<ChildSHistory> mHistory; + int32_t mOffset; + }; + RefPtr<nsDocShell> mDocShell; RefPtr<nsSHistory> mHistory; + mozilla::LinkedList<PendingAsyncHistoryNavigation> mPendingNavigations; }; } // namespace dom } // namespace mozilla #endif /* mozilla_dom_ChildSHistory_h */
--- a/docshell/test/mochitest/historyframes.html +++ b/docshell/test/mochitest/historyframes.html @@ -62,16 +62,21 @@ function run_test() { test_basic_inner_navigation(); } function end_test() { testWin.done(); } +var gTestContinuation = null; +function continueAsync() { + setTimeout(function() { gTestContinuation.next(); }) +} + function test_basic_inner_navigation() { // Navigate the inner frame a few times loadContent(URL1, function() { is(getURL(), URL1, "URL should be correct"); is(getContent(), "Test1", "Page should be correct"); loadContent(URL2, function() { is(getURL(), URL2, "URL should be correct"); @@ -81,50 +86,59 @@ function test_basic_inner_navigation() { waitForLoad(function() { is(getURL(), URL1, "URL should be correct"); is(getContent(), "Test1", "Page should be correct"); waitForLoad(function() { is(getURL(), URL2, "URL should be correct"); is(getContent(), "Test2", "Page should be correct"); - test_state_navigation(); + gTestContinuation = test_state_navigation(); + gTestContinuation.next(); }); window.history.forward(); }); window.history.back(); }); }); } -function test_state_navigation() { +function* test_state_navigation() { window.location.hash = "STATE1"; is(getURL(), URL2, "URL should be correct"); is(getContent(), "Test2", "Page should be correct"); window.location.hash = "STATE2"; is(getURL(), URL2, "URL should be correct"); is(getContent(), "Test2", "Page should be correct"); window.history.back(); + continueAsync(); + yield; is(gState(), "STATE1", "State should be correct after going back"); is(getURL(), URL2, "URL should be correct"); is(getContent(), "Test2", "Page should be correct"); window.history.forward(); + continueAsync(); + yield; is(gState(), "STATE2", "State should be correct after going forward"); is(getURL(), URL2, "URL should be correct"); is(getContent(), "Test2", "Page should be correct"); window.history.back(); + continueAsync(); + yield; window.history.back(); + continueAsync(); + yield; is(gState(), "START", "State should be correct"); is(getURL(), URL2, "URL should be correct"); is(getContent(), "Test2", "Page should be correct"); waitForLoad(function() { is(getURL(), URL1, "URL should be correct"); is(getContent(), "Test1", "Page should be correct"); @@ -138,14 +152,16 @@ function test_state_navigation() { }); window.history.back(); is(gState(), "START", "State should be correct after going back twice"); }); window.history.back(); + continueAsync(); + yield; is(gState(), "START", "State should be correct"); } </script> </pre> </body> </html>
--- a/docshell/test/mochitest/test_bug590573.html +++ b/docshell/test/mochitest/test_bug590573.html @@ -119,35 +119,47 @@ function pageLoad() { gTestContinuation = testBody(); } var ret = gTestContinuation.next(); if (ret.done) { SimpleTest.finish(); } } +function continueAsync() { + setTimeout(function() { gTestContinuation.next(); }) +} + function* testBody() { is(popup.scrollY, 0, "test 1"); popup.scroll(0, 100); popup.history.pushState("", "", "?pushed"); is(Math.round(popup.scrollY), 100, "test 2"); popup.scroll(0, 200); // set state-2's position to 200 popup.history.back(); + continueAsync(); + yield; is(Math.round(popup.scrollY), 100, "test 3"); popup.scroll(0, 150); // set original page's position to 150 popup.history.forward(); + continueAsync(); + yield; is(Math.round(popup.scrollY), 200, "test 4"); popup.history.back(); + continueAsync(); + yield; is(Math.round(popup.scrollY), 150, "test 5"); popup.history.forward(); + continueAsync(); + yield; is(Math.round(popup.scrollY), 200, "test 6"); // At this point, the history looks like: // PATH POSITION // file_bug590573_1.html 150 <-- oldest // file_bug590573_1.html?pushed 200 <-- newest, current // Now test that the scroll position is persisted when we have real @@ -182,18 +194,22 @@ function* testBody() { yield; is(popup.location.search, "?pushed"); ok(popup.document.getElementById("div1"), "page should have div1."); is(Math.round(popup.scrollY), 200, "test 8"); popup.history.back(); + continueAsync(); + yield; is(Math.round(popup.scrollY), 150, "test 9"); popup.history.forward(); + continueAsync(); + yield; is(Math.round(popup.scrollY), 200, "test 10"); // Spin one last time... setTimeout(pageLoad, 0); yield; page2PageShowCallbackEnabled = true;
--- a/docshell/test/mochitest/test_bug680257.html +++ b/docshell/test/mochitest/test_bug680257.html @@ -12,33 +12,48 @@ https://bugzilla.mozilla.org/show_bug.cg <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=680257">Mozilla Bug 680257</a> <script type="application/javascript"> SimpleTest.waitForExplicitFinish(); var popup = window.open("file_bug680257.html"); +var gTestContinuation = null; +function continueAsync() { + setTimeout(function() { gTestContinuation.next(); }) +} + // The popup will call into popupLoaded() once it loads. function popupLoaded() { // runTests() needs to be called from outside popupLoaded's onload handler. // Otherwise, the navigations we do in runTests won't create new SHEntries. - SimpleTest.executeSoon(runTests); + SimpleTest.executeSoon(function() { + if (!gTestContinuation) { + gTestContinuation = runTests(); + } + gTestContinuation.next(); + }); } -function runTests() { +function* runTests() { checkPopupLinkStyle(false, "Initial"); popup.location.hash = "a"; checkPopupLinkStyle(true, "After setting hash"); popup.history.back(); + continueAsync(); + yield; + checkPopupLinkStyle(false, "After going back"); popup.history.forward(); + continueAsync(); + yield; checkPopupLinkStyle(true, "After going forward"); popup.close(); SimpleTest.finish(); } function checkPopupLinkStyle(isTarget, desc) { var link = popup.document.getElementById("a");
--- a/docshell/test/navigation/file_bug1300461.html +++ b/docshell/test/navigation/file_bug1300461.html @@ -35,17 +35,16 @@ opener.ok(!webNav.canGoForward, "check canGoForward"); setTimeout(() => window.location = "file_bug1300461_back.html", 0); }, function() { opener.is(shistory.count, 2, "check history length"); opener.is(shistory.index, 0, "check history index"); opener.ok(webNav.canGoForward, "check canGoForward"); window.history.forward(); - opener.is(shistory.legacySHistory.requestedIndex, 1, "check requestedIndex"); }, function() { opener.is(shistory.count, 2, "check history length"); opener.is(shistory.index, 0, "check history index"); opener.ok(webNav.canGoForward, "check canGoForward"); opener.info("file_bug1300461.html tests finished"); opener.nextTest(); window.close();
--- a/docshell/test/navigation/file_scrollRestoration.html +++ b/docshell/test/navigation/file_scrollRestoration.html @@ -65,49 +65,65 @@ opener.is(window.scrollY, 0, "Shouldn't have kept the old scroll position."); opener.is(history.scrollRestoration, "manual", "Should have the same scrollRestoration mode as before fragment navigation."); history.scrollRestoration = "auto"; document.getElementById("bottom").scrollIntoView(); history.pushState({ state: "state1" }, "state1"); history.pushState({ state: "state2" }, "state2"); window.scrollTo(0, 0); history.back(); + setTimeout(test); + break; + } + case 7: { opener.isnot(Math.round(window.scrollY), 0, "Should have scrolled back to the state1's position"); opener.is(history.state.state, "state1", "Unexpected state."); history.scrollRestoration = "manual"; document.getElementById("bottom").scrollIntoView(); history.pushState({ state: "state3" }, "state3"); history.pushState({ state: "state4" }, "state4"); window.scrollTo(0, 0); history.back(); + setTimeout(test); + break; + } + case 8: { opener.is(Math.round(window.scrollY), 0, "Shouldn't have scrolled back to the state3's position"); opener.is(history.state.state, "state3", "Unexpected state."); history.pushState({ state: "state5" }, "state5"); history.scrollRestoration = "auto"; document.getElementById("bottom").scrollIntoView(); opener.isnot(Math.round(window.scrollY), 0, "Should have scrolled to 'bottom'."); history.back(); + setTimeout(test); + break; + } + case 9: { window.scrollTo(0, 0); history.forward(); + setTimeout(test); + break; + } + case 10: { opener.isnot(Math.round(window.scrollY), 0, "Should have scrolled back to the state5's position"); var ifr = document.createElement("iframe"); ifr.src = "data:text/html,"; document.body.appendChild(ifr); ifr.onload = test; break; } - case 7: { + case 11: { oldHistoryObject = SpecialPowers.wrap(event.target).contentWindow.history; event.target.src = "about:blank"; break; } - case 8: { + case 12: { try { oldHistoryObject.scrollRestoration; opener.ok(false, "Should have thrown an exception."); } catch (ex) { opener.isnot(ex, null, "Did get an exception"); } try { oldHistoryObject.scrollRestoration = "auto";
--- a/dom/base/nsHistory.cpp +++ b/dom/base/nsHistory.cpp @@ -13,18 +13,18 @@ #include "mozilla/dom/DocumentInlines.h" #include "nsIDocShell.h" #include "nsIWebNavigation.h" #include "nsIURI.h" #include "nsReadableUtils.h" #include "nsContentUtils.h" #include "nsISHistory.h" #include "mozilla/dom/Location.h" -#include "mozilla/Preferences.h" #include "mozilla/RefPtr.h" +#include "mozilla/StaticPrefs_dom.h" using namespace mozilla; using namespace mozilla::dom; // // History class implementation // NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_0(nsHistory) @@ -154,17 +154,21 @@ void nsHistory::Go(int32_t aDelta, Error RefPtr<ChildSHistory> session_history = GetSessionHistory(); if (!session_history) { aRv.Throw(NS_ERROR_FAILURE); return; } // Ignore the return value from Go(), since returning errors from Go() can // lead to exceptions and a possible leak of history length - session_history->Go(aDelta, IgnoreErrors()); + if (StaticPrefs::dom_window_history_async()) { + session_history->AsyncGo(aDelta); + } else { + session_history->Go(aDelta, IgnoreErrors()); + } } void nsHistory::Back(ErrorResult& aRv) { nsCOMPtr<nsPIDOMWindowInner> win(do_QueryReferent(mInnerWindow)); if (!win || !win->HasActiveDocument()) { aRv.Throw(NS_ERROR_DOM_SECURITY_ERR); return; @@ -172,17 +176,21 @@ void nsHistory::Back(ErrorResult& aRv) { RefPtr<ChildSHistory> sHistory = GetSessionHistory(); if (!sHistory) { aRv.Throw(NS_ERROR_FAILURE); return; } - sHistory->Go(-1, IgnoreErrors()); + if (StaticPrefs::dom_window_history_async()) { + sHistory->AsyncGo(-1); + } else { + sHistory->Go(-1, IgnoreErrors()); + } } void nsHistory::Forward(ErrorResult& aRv) { nsCOMPtr<nsPIDOMWindowInner> win(do_QueryReferent(mInnerWindow)); if (!win || !win->HasActiveDocument()) { aRv.Throw(NS_ERROR_DOM_SECURITY_ERR); return; @@ -190,17 +198,21 @@ void nsHistory::Forward(ErrorResult& aRv RefPtr<ChildSHistory> sHistory = GetSessionHistory(); if (!sHistory) { aRv.Throw(NS_ERROR_FAILURE); return; } - sHistory->Go(1, IgnoreErrors()); + if (StaticPrefs::dom_window_history_async()) { + sHistory->AsyncGo(1); + } else { + sHistory->Go(1, IgnoreErrors()); + } } void nsHistory::PushState(JSContext* aCx, JS::Handle<JS::Value> aData, const nsAString& aTitle, const nsAString& aUrl, ErrorResult& aRv) { PushOrReplaceState(aCx, aData, aTitle, aUrl, aRv, false); }
--- a/dom/tests/mochitest/general/historyframes.html +++ b/dom/tests/mochitest/general/historyframes.html @@ -78,16 +78,21 @@ function continue_test() { gTestContinuation = test_body(); } var ret = gTestContinuation.next(); if (ret.done) { testWin.done(); } } +var gTestContinuation = null; +function continueAsync() { + setTimeout(function() { gTestContinuation.next(); }) +} + function* test_basic_inner_navigation() { // Navigate the inner frame a few times yield loadContent(URL1); is(getURL(), URL1, "URL should be correct"); is(getContent(), "Test1", "Page should be correct"); yield loadContent(URL2); @@ -113,42 +118,54 @@ function* test_state_navigation() { is(getContent(), "Test2", "Page should be correct"); window.history.pushState("STATE2", window.location); is(getURL(), URL2, "URL should be correct"); is(getContent(), "Test2", "Page should be correct"); window.history.back(); + continueAsync(); + yield; is(gState, "STATE1", "State should be correct"); is(getURL(), URL2, "URL should be correct"); is(getContent(), "Test2", "Page should be correct"); window.history.forward(); + continueAsync(); + yield; is(gState, "STATE2", "State should be correct"); is(getURL(), URL2, "URL should be correct"); is(getContent(), "Test2", "Page should be correct"); window.history.back(); + continueAsync(); + yield; window.history.back(); + continueAsync(); + yield; is(gState, "START", "State should be correct"); is(getURL(), URL2, "URL should be correct"); is(getContent(), "Test2", "Page should be correct"); window.history.back(); + continueAsync(); + yield; is(gState, "START", "State should be correct"); yield waitForLoad(); is(getURL(), URL1, "URL should be correct"); is(getContent(), "Test1", "Page should be correct"); window.history.back(); + continueAsync(); + yield; is(gState, "START", "State should be correct after going back twice"); yield waitForLoad(); is(gState, "START", "State should be correct"); is(getURL(), START, "URL should be correct"); is(getContent(), "Start", "Page should be correct"); } </script>
--- a/dom/xslt/xslt/txEXSLTFunctions.cpp +++ b/dom/xslt/xslt/txEXSLTFunctions.cpp @@ -153,17 +153,17 @@ enum class txEXSLTType { LOWEST, // http://exslt.org/regular-expressions MATCH, REPLACE, TEST, // http://exslt.org/sets - DIFFERENCE, + DIFFERENCE_, // not DIFFERENCE to avoid a conflict with a winuser.h macro DISTINCT, HAS_SAME_NODE, INTERSECTION, LEADING, TRAILING, // http://exslt.org/strings CONCAT, @@ -279,17 +279,17 @@ nsresult txEXSLTFunctionCall::evaluate(t AppendASCIItoUTF16(MakeStringSpan(sTypes[exprResult->getResultType()]), strRes->mValue); NS_ADDREF(*aResult = strRes); return NS_OK; } - case txEXSLTType::DIFFERENCE: + case txEXSLTType::DIFFERENCE_: case txEXSLTType::INTERSECTION: { RefPtr<txNodeSet> nodes1; rv = evaluateToNodeSet(mParams[0], aContext, getter_AddRefs(nodes1)); NS_ENSURE_SUCCESS(rv, rv); RefPtr<txNodeSet> nodes2; rv = evaluateToNodeSet(mParams[1], aContext, getter_AddRefs(nodes2)); NS_ENSURE_SUCCESS(rv, rv); @@ -788,17 +788,17 @@ extern bool TX_InitEXSLTFunction() { EXSLT_FUNCS("http://exslt.org/regular-expressions", txEXSLTRegExFunctionCall, (MATCH, 2, 3, Expr::NODESET_RESULT, nsGkAtoms::match), (REPLACE, 4, 4, Expr::STRING_RESULT, nsGkAtoms::replace), (TEST, 2, 3, Expr::BOOLEAN_RESULT, nsGkAtoms::test)) EXSLT_FUNCS( "http://exslt.org/sets", txEXSLTFunctionCall, - (DIFFERENCE, 2, 2, Expr::NODESET_RESULT, nsGkAtoms::difference), + (DIFFERENCE_, 2, 2, Expr::NODESET_RESULT, nsGkAtoms::difference), (DISTINCT, 1, 1, Expr::NODESET_RESULT, nsGkAtoms::distinct), (HAS_SAME_NODE, 2, 2, Expr::BOOLEAN_RESULT, nsGkAtoms::hasSameNode), (INTERSECTION, 2, 2, Expr::NODESET_RESULT, nsGkAtoms::intersection), (LEADING, 2, 2, Expr::NODESET_RESULT, nsGkAtoms::leading), (TRAILING, 2, 2, Expr::NODESET_RESULT, nsGkAtoms::trailing)) EXSLT_FUNCS("http://exslt.org/strings", txEXSLTFunctionCall, (CONCAT, 1, 1, Expr::STRING_RESULT, nsGkAtoms::concat),
--- a/gfx/gl/GLConsts.py +++ b/gfx/gl/GLConsts.py @@ -17,17 +17,17 @@ Step 2: Step 3: Do not add the downloaded XML in the patch Step 4: Enjoy =) ''' # includes -from typing import List # mypy! +from typing import List # mypy! import pathlib import sys import xml.etree.ElementTree # - (_, XML_DIR_STR) = sys.argv @@ -57,38 +57,37 @@ HEADER = b''' FOOTER = b''' #endif // GLCONSTS_H_ // clang-format on '''[1:] # - + def format_lib_constant(lib, name, value): # lib would be 'GL', 'EGL', 'GLX' or 'WGL' # name is the name of the const (example: MAX_TEXTURE_SIZE) # value is the value of the const (example: 0xABCD) define = '#define LOCAL_' + lib + '_' + name whitespace = 60 - len(define) if whitespace < 0: whitespace = whitespace % 8 return define + ' ' * whitespace + ' ' + value -# - class GLConst: def __init__(self, lib, name, value, type): self.lib = lib self.name = name self.value = value self.type = type -# - class GLDatabase: LIBS = ['GL', 'EGL', 'GLX', 'WGL'] def __init__(self): self.consts = {} self.libs = set(GLDatabase.LIBS) self.vendors = set(['EXT', 'ATI']) @@ -128,27 +127,28 @@ class GLDatabase: type = enum.get('type') if not type: # if no type specified, we get the namespace's default type type = namespaceType self.consts[lib + '_' + name] = GLConst(lib, name, value, type) + # - db = GLDatabase() db.load_xml(XML_DIR / 'gl.xml') db.load_xml(XML_DIR / 'glx.xml') db.load_xml(XML_DIR / 'wgl.xml') db.load_xml(XML_DIR / 'egl.xml') # - -lines: List[str] = [] +lines: List[str] = [] # noqa: E999 (bug 1573737) keys = sorted(db.consts.keys()) for lib in db.LIBS: lines.append('// ' + lib) for k in keys: const = db.consts[k] @@ -164,9 +164,9 @@ for lib in db.LIBS: # - b_lines: List[bytes] = [HEADER] + [x.encode() for x in lines] + [FOOTER] b_data: bytes = b'\n'.join(b_lines) dest = pathlib.Path('GLConsts.h') dest.write_bytes(b_data) -print(f'Wrote {len(b_data)} bytes.') # Some indication that we're successful. +print(f'Wrote {len(b_data)} bytes.') # Some indication that we're successful.
--- a/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/NavigationDelegateTest.kt +++ b/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/NavigationDelegateTest.kt @@ -859,25 +859,20 @@ class NavigationDelegateTest : BaseSessi assertThat("URL should match", url, endsWith(HELLO2_HTML_PATH)) } }) sessionRule.session.goBack() sessionRule.waitForPageStop() sessionRule.forCallbacksDuringWait(object : Callbacks.NavigationDelegate { - @AssertCalled(count = 1, order = [1]) + @AssertCalled(count = 0, order = [1]) override fun onLoadRequest(session: GeckoSession, request: LoadRequest): GeckoResult<AllowOrDeny>? { - assertThat("URI should match", request.uri, endsWith(HELLO_HTML_PATH)) - assertThat("Trigger URL should be null", request.triggerUri, - nullValue()) - assertThat("Target should match", request.target, - equalTo(GeckoSession.NavigationDelegate.TARGET_WINDOW_CURRENT)) return null } @AssertCalled(count = 1, order = [2]) override fun onLocationChange(session: GeckoSession, url: String?) { assertThat("URL should match", url, endsWith(HELLO_HTML_PATH)) } @@ -896,25 +891,20 @@ class NavigationDelegateTest : BaseSessi return null } }) sessionRule.session.goForward() sessionRule.waitForPageStop() sessionRule.forCallbacksDuringWait(object : Callbacks.NavigationDelegate { - @AssertCalled(count = 1, order = [1]) + @AssertCalled(count = 0, order = [1]) override fun onLoadRequest(session: GeckoSession, request: LoadRequest): GeckoResult<AllowOrDeny>? { - assertThat("URI should match", request.uri, endsWith(HELLO2_HTML_PATH)) - assertThat("Trigger URL should be null", request.triggerUri, - nullValue()) - assertThat("Target should match", request.target, - equalTo(GeckoSession.NavigationDelegate.TARGET_WINDOW_CURRENT)) return null } @AssertCalled(count = 1, order = [2]) override fun onLocationChange(session: GeckoSession, url: String?) { assertThat("URL should match", url, endsWith(HELLO2_HTML_PATH)) }
--- a/modules/libpref/init/StaticPrefList.yaml +++ b/modules/libpref/init/StaticPrefList.yaml @@ -2375,16 +2375,21 @@ mirror: always # Is support for Window.event enabled? - name: dom.window.event.enabled type: bool value: true mirror: always +- name: dom.window.history.async + type: bool + value: true + mirror: always + # Enable the "noreferrer" feature argument for window.open() - name: dom.window.open.noreferrer.enabled type: bool value: true mirror: always - name: dom.worker.canceling.timeoutMilliseconds type: RelaxedAtomicUint32
--- a/mozglue/misc/StackWalk.cpp +++ b/mozglue/misc/StackWalk.cpp @@ -110,24 +110,26 @@ CRITICAL_SECTION gDbgHelpCS; // continue unwinding that thread even if other threads request suppressions // in the meantime, because we can't deadlock with those other threads. // // XXX: This global variable is a larger-than-necessary hammer. A more scoped // solution would be to maintain a counter per thread, but then it would be // more difficult for WalkStackMain64 to read the suspended thread's counter. static Atomic<size_t> sStackWalkSuppressions; +void SuppressStackWalking() { ++sStackWalkSuppressions; } + +void DesuppressStackWalking() { --sStackWalkSuppressions; } + MFBT_API -AutoSuppressStackWalking::AutoSuppressStackWalking() { - ++sStackWalkSuppressions; -} +AutoSuppressStackWalking::AutoSuppressStackWalking() { SuppressStackWalking(); } MFBT_API AutoSuppressStackWalking::~AutoSuppressStackWalking() { - --sStackWalkSuppressions; + DesuppressStackWalking(); } static uint8_t* sJitCodeRegionStart; static size_t sJitCodeRegionSize; uint8_t* sMsMpegJitCodeRegionStart; size_t sMsMpegJitCodeRegionSize; MFBT_API void RegisterJitCodeRegion(uint8_t* aStart, size_t aSize) {
--- a/mozglue/misc/StackWalk_windows.h +++ b/mozglue/misc/StackWalk_windows.h @@ -16,14 +16,19 @@ * * See comment in StackWalk.cpp */ struct MOZ_RAII AutoSuppressStackWalking { MFBT_API AutoSuppressStackWalking(); MFBT_API ~AutoSuppressStackWalking(); }; +#if defined(IMPL_MFBT) +void SuppressStackWalking(); +void DesuppressStackWalking(); +#endif // defined(IMPL_MFBT) + MFBT_API void RegisterJitCodeRegion(uint8_t* aStart, size_t size); MFBT_API void UnregisterJitCodeRegion(uint8_t* aStart, size_t size); #endif // _M_AMD64 #endif // mozilla_StackWalk_windows_h
--- a/old-configure.in +++ b/old-configure.in @@ -795,17 +795,17 @@ case "$target" in # we use it to avoid warnings about things that are unused # in some compilation units, but used in many others. This # warning insists on complaining about the latter case, which # is annoying, and rather noisy. CXXFLAGS="$CXXFLAGS -Wno-used-but-marked-unused" fi # Silence VS2017 15.5+ TR1 deprecation warnings hit by older gtest versions CXXFLAGS="$CXXFLAGS -D_SILENCE_TR1_NAMESPACE_DEPRECATION_WARNING" - LIBS="$LIBS kernel32.lib user32.lib gdi32.lib winmm.lib wsock32.lib advapi32.lib secur32.lib" + LIBS="$LIBS user32.lib gdi32.lib winmm.lib wsock32.lib advapi32.lib secur32.lib" MOZ_DEBUG_LDFLAGS='-DEBUG' WARNINGS_AS_ERRORS='-WX' # Use a higher optimization level for clang-cl, so we can come closer # to MSVC's performance numbers (see bug 1443590). if test -n "$CLANG_CL"; then MOZ_OPTIMIZE_FLAGS='-O2' else MOZ_OPTIMIZE_FLAGS='-O1 -Oi'
deleted file mode 100644 --- a/testing/web-platform/meta/html/browsers/browsing-the-web/history-traversal/persisted-user-state-restoration/scroll-restoration-fragment-scrolling-cross-origin.html.ini +++ /dev/null @@ -1,8 +0,0 @@ -[scroll-restoration-fragment-scrolling-cross-origin.html] - expected: - if (os == "android") and e10s: ERROR - [Manual scroll restoration should take precedent over scrolling to fragment in cross origin navigation] - expected: - if (os == "android") and e10s: TIMEOUT - FAIL -
--- a/testing/web-platform/meta/html/browsers/history/the-history-interface/001.html.ini +++ b/testing/web-platform/meta/html/browsers/history/the-history-interface/001.html.ini @@ -1,16 +1,13 @@ [001.html] [pushState must not be allowed to create cross-origin URLs (about:blank)] expected: FAIL [pushState must not be allowed to create cross-origin URLs (data:URI)] expected: FAIL - [pushState must remove any tasks queued by the history traversal task source] - expected: FAIL - [history.state should be a separate clone of the object, not a reference to the object passed to the event handler] expected: FAIL [pushState must be able to use an error object as data] expected: FAIL
--- a/testing/web-platform/meta/html/browsers/history/the-history-interface/002.html.ini +++ b/testing/web-platform/meta/html/browsers/history/the-history-interface/002.html.ini @@ -1,19 +1,13 @@ [002.html] [replaceState must not be allowed to create cross-origin URLs (about:blank)] expected: FAIL [replaceState must not be allowed to create cross-origin URLs (data:URI)] expected: FAIL - [replaceState must not remove any tasks queued by the history traversal task source] - expected: FAIL - - [.go must queue a task with the history traversal task source (run asynchronously)] - expected: FAIL - [history.state should be a separate clone of the object, not a reference to the object passed to the event handler] expected: FAIL [replaceState must be able to use an error object as data] expected: FAIL
deleted file mode 100644 --- a/testing/web-platform/meta/html/browsers/history/the-history-interface/004.html.ini +++ /dev/null @@ -1,4 +0,0 @@ -[004.html] - [.go commands should be queued until the thread has ended] - expected: FAIL -
deleted file mode 100644 --- a/testing/web-platform/meta/html/browsers/history/the-history-interface/combination_history_004.html.ini +++ /dev/null @@ -1,5 +0,0 @@ -[combination_history_004.html] - expected: TIMEOUT - [After calling of back method, check length] - expected: TIMEOUT -
deleted file mode 100644 --- a/testing/web-platform/meta/html/browsers/history/the-history-interface/combination_history_005.html.ini +++ /dev/null @@ -1,5 +0,0 @@ -[combination_history_005.html] - expected: TIMEOUT - [After calling of forward method, check length] - expected: TIMEOUT -
deleted file mode 100644 --- a/testing/web-platform/meta/html/browsers/history/the-history-interface/combination_history_006.html.ini +++ /dev/null @@ -1,5 +0,0 @@ -[combination_history_006.html] - expected: TIMEOUT - [After calling of go method, check length] - expected: TIMEOUT -
deleted file mode 100644 --- a/testing/web-platform/meta/html/browsers/history/the-history-interface/combination_history_007.html.ini +++ /dev/null @@ -1,5 +0,0 @@ -[combination_history_007.html] - expected: TIMEOUT - [After calling of back and pushState method, check length] - expected: TIMEOUT -
deleted file mode 100644 --- a/testing/web-platform/meta/html/browsers/history/the-history-interface/history_back.html.ini +++ /dev/null @@ -1,5 +0,0 @@ -[history_back.html] - expected: TIMEOUT - [history back] - expected: TIMEOUT -
deleted file mode 100644 --- a/testing/web-platform/meta/html/browsers/history/the-history-interface/history_forward.html.ini +++ /dev/null @@ -1,5 +0,0 @@ -[history_forward.html] - expected: TIMEOUT - [history forward] - expected: TIMEOUT -
deleted file mode 100644 --- a/testing/web-platform/meta/html/browsers/history/the-history-interface/history_go_minus.html.ini +++ /dev/null @@ -1,5 +0,0 @@ -[history_go_minus.html] - expected: TIMEOUT - [history go minus] - expected: TIMEOUT -
deleted file mode 100644 --- a/testing/web-platform/meta/html/browsers/history/the-history-interface/history_go_plus.html.ini +++ /dev/null @@ -1,5 +0,0 @@ -[history_go_plus.html] - expected: TIMEOUT - [history go plus] - expected: TIMEOUT -
deleted file mode 100644 --- a/testing/web-platform/meta/html/browsers/history/the-history-interface/traverse_the_history_1.html.ini +++ /dev/null @@ -1,4 +0,0 @@ -[traverse_the_history_1.html] - [Multiple history traversals from the same task] - expected: FAIL -
deleted file mode 100644 --- a/testing/web-platform/meta/html/browsers/history/the-history-interface/traverse_the_history_2.html.ini +++ /dev/null @@ -1,4 +0,0 @@ -[traverse_the_history_2.html] - [Multiple history traversals, last would be aborted] - expected: FAIL -
deleted file mode 100644 --- a/testing/web-platform/meta/html/browsers/history/the-history-interface/traverse_the_history_3.html.ini +++ /dev/null @@ -1,4 +0,0 @@ -[traverse_the_history_3.html] - [Multiple history traversals, last would be aborted] - expected: FAIL -
deleted file mode 100644 --- a/testing/web-platform/meta/html/browsers/history/the-history-interface/traverse_the_history_4.html.ini +++ /dev/null @@ -1,4 +0,0 @@ -[traverse_the_history_4.html] - [Multiple history traversals, last would be aborted] - expected: FAIL -
deleted file mode 100644 --- a/testing/web-platform/meta/html/browsers/history/the-history-interface/traverse_the_history_5.html.ini +++ /dev/null @@ -1,4 +0,0 @@ -[traverse_the_history_5.html] - [Multiple history traversals, last would be aborted] - expected: FAIL -
new file mode 100644 --- /dev/null +++ b/testing/web-platform/meta/resource-timing/nested-context-navigations-iframe.html.ini @@ -0,0 +1,5 @@ +[nested-context-navigations-iframe.html] + [Test that iframe navigations are not observable by the parent, even after history navigations by the parent] + expected: FAIL + [Test that crossorigin iframe navigations are not observable by the parent, even after history navigations by the parent] + expected: FAIL
--- a/testing/web-platform/tests/html/browsers/browsing-the-web/history-traversal/persisted-user-state-restoration/scroll-restoration-fragment-scrolling-cross-origin.html +++ b/testing/web-platform/tests/html/browsers/browsing-the-web/history-traversal/persisted-user-state-restoration/scroll-restoration-fragment-scrolling-cross-origin.html @@ -6,62 +6,68 @@ <script src="/common/get-host-info.sub.js"></script> <style> iframe { height: 300px; width: 300px; } </style> <div id="log"></div> -<iframe></iframe> <script> 'use strict'; - // The test does the following navigation steps for iframe - // 1. load page-with-fragment.html#fragment - // 2. load blank1 - // 3. go back to page-with-fragment.html - async_test(function(t) { - var iframe = document.querySelector('iframe'); - var hostInfo = get_host_info(); - var basePath = location.pathname.substring(0, location.pathname.lastIndexOf('/')); - var localURL = hostInfo.HTTP_ORIGIN + basePath + '/resources/page-with-fragment.html#fragment'; - var remoteURL = hostInfo.HTTP_REMOTE_ORIGIN + basePath + "/resources/blank1.html" + var next; + function frameOnload() { + if (next) { + next(); + dump("next \n"); + } else { + dump("no next \n"); + // The test does the following navigation steps for iframe + // 1. load page-with-fragment.html#fragment + // 2. load blank1 + // 3. go back to page-with-fragment.html + async_test(function(t) { + var iframe = document.querySelector('iframe'); + var hostInfo = get_host_info(); + var basePath = location.pathname.substring(0, location.pathname.lastIndexOf('/')); + var localURL = hostInfo.HTTP_ORIGIN + basePath + '/resources/page-with-fragment.html#fragment'; + var remoteURL = hostInfo.HTTP_REMOTE_ORIGIN + basePath + "/resources/blank1.html" + + var steps = [ + function() { + assert_equals(iframe.contentWindow.location.href, localURL, 'should be on page-with-fragment page'); + // wait one animation frame to ensure layout is run and fragment scrolling is complete + iframe.contentWindow.requestAnimationFrame(function() { + assert_approx_equals(iframe.contentWindow.scrollY, 800, 5, 'should scroll to fragment'); - var steps = [ - function() { - iframe.src = 'resources/page-with-fragment.html#fragment'; - }, function() { - assert_equals(iframe.contentWindow.location.href, localURL, 'should be on page-with-fragment page'); - // wait one animation frame to ensure layout is run and fragment scrolling is complete - iframe.contentWindow.requestAnimationFrame(function() { - assert_equals(iframe.contentWindow.scrollY, 800, 'should scroll to fragment'); + iframe.contentWindow.history.scrollRestoration = 'manual'; + assert_equals(iframe.contentWindow.history.scrollRestoration, 'manual'); + setTimeout(next, 0); + }); + }, function() { + // navigate to a new page from a different origin + iframe.src = remoteURL; + }, function() { + // going back causes the iframe to traverse back + history.back(); + }, function() { + // coming back from history, scrollRestoration should be set to manual and respected + assert_equals(iframe.contentWindow.location.href, localURL, 'should be back on page-with-fragment page'); + iframe.contentWindow.requestAnimationFrame(t.step_func_done(function() { + assert_equals(iframe.contentWindow.history.scrollRestoration, 'manual', 'navigating back should retain scrollRestoration value'); + assert_equals(iframe.contentWindow.scrollX, 0, 'should not scroll to fragment'); + assert_equals(iframe.contentWindow.scrollY, 0, 'should not scroll to fragment'); + })); + } + ]; - iframe.contentWindow.history.scrollRestoration = 'manual'; - assert_equals(iframe.contentWindow.history.scrollRestoration, 'manual'); - setTimeout(next, 0); + var stepCount = 0; + next = t.step_func(function() { + steps[stepCount++](); }); - }, function() { - // navigate to a new page from a different origin - iframe.src = remoteURL; - }, function() { - // going back causes the iframe to traverse back - history.back(); - }, function() { - // coming back from history, scrollRestoration should be set to manual and respected - assert_equals(iframe.contentWindow.location.href, localURL, 'should be back on page-with-fragment page'); - iframe.contentWindow.requestAnimationFrame(t.step_func_done(function() { - assert_equals(iframe.contentWindow.history.scrollRestoration, 'manual', 'navigating back should retain scrollRestoration value'); - assert_equals(iframe.contentWindow.scrollX, 0, 'should not scroll to fragment'); - assert_equals(iframe.contentWindow.scrollY, 0, 'should not scroll to fragment'); - })); - } - ]; + next(); + }, 'Manual scroll restoration should take precedent over scrolling to fragment in cross origin navigation'); + } + } +</script> +<iframe src="resources/page-with-fragment.html#fragment" onload="frameOnload()"></iframe> - var stepCount = 0; - var next = t.step_func(function() { - steps[stepCount++](); - }); - - iframe.onload = next; - next(); - }, 'Manual scroll restoration should take precedent over scrolling to fragment in cross origin navigation'); -</script>
--- a/testing/web-platform/tests/referrer-policy/generic/referrer-policy-test-case.sub.js +++ b/testing/web-platform/tests/referrer-policy/generic/referrer-policy-test-case.sub.js @@ -124,16 +124,22 @@ function ReferrerPolicyTestCase(scenario url: urls.testUrl, policyDeliveries: [delivery] }; let currentURL = location.toString(); const expectedReferrer = referrerUrlResolver[scenario.referrer_url](currentURL); + function asyncResolve(result) { + return new Promise((resolve, reject) => { + step_timeout(() => resolve(result), 0); + }); + } + // Request in the top-level document. promise_test(_ => { return invokeRequest(subresource, []) .then(result => checkResult(expectedReferrer, result)); }, testDescription); // `Referer` headers with length over 4k are culled down to an origin, so, let's test around // that boundary for tests that would otherwise return the complete URL. @@ -141,39 +147,42 @@ function ReferrerPolicyTestCase(scenario promise_test(_ => { history.pushState(null, null, "/"); history.replaceState(null, null, "A".repeat(4096 - location.href.length - 1)); const expectedReferrer = location.href; // Ensure that we don't load the same URL as the previous test. subresource.url += "&-1"; return invokeRequest(subresource, []) .then(result => checkResult(location.href, result)) - .finally(_ => history.back()); + .then(_ => history.back()) + .then(asyncResolve); }, "`Referer` header with length < 4k is not stripped to an origin."); promise_test(_ => { history.pushState(null, null, "/"); history.replaceState(null, null, "A".repeat(4096 - location.href.length)); const expectedReferrer = location.href; // Ensure that we don't load the same URL as the previous test. subresource.url += "&0"; return invokeRequest(subresource, []) .then(result => checkResult(expectedReferrer, result)) - .finally(_ => history.back()); + .then(_ => history.back()) + .then(asyncResolve); }, "`Referer` header with length == 4k is not stripped to an origin."); promise_test(_ => { const originString = referrerUrlResolver["origin"](currentURL); history.pushState(null, null, "/"); history.replaceState(null, null, "A".repeat(4096 - location.href.length + 1)); // Ensure that we don't load the same URL as the previous test. subresource.url += "&+1"; return invokeRequest(subresource, []) .then(result => checkResult(originString, result)) - .finally(_ => history.back()); + .then(_ => history.back()) + .then(asyncResolve); }, "`Referer` header with length > 4k is stripped to an origin."); } // We test requests from inside iframes only for <img> tags. // This is just to preserve the previous test coverage. // TODO(hiroshige): Enable iframe tests for all subresource types. if (scenario.subresource !== "img-tag") { return;
--- a/toolkit/components/places/tests/sync/test_bookmark_abort_merging.js +++ b/toolkit/components/places/tests/sync/test_bookmark_abort_merging.js @@ -4,20 +4,19 @@ var { AsyncShutdown } = ChromeUtils.import( "resource://gre/modules/AsyncShutdown.jsm" ); add_task(async function test_abort_merging() { let buf = await openMirror("abort_merging"); let controller = new AbortController(); - let promiseWasMerged = buf.merge(controller.signal); controller.abort(); await Assert.rejects( - promiseWasMerged, + buf.merge(controller.signal), /Operation aborted/, "Should abort merge when signaled" ); // Even though the merger is already finalized on the Rust side, the DB // connection is still open on the JS side. Finalizing `buf` closes it. await buf.finalize(); await PlacesUtils.bookmarks.eraseEverything();
rename from toolkit/library/gtest/static/TestUCRTDepends.cpp rename to toolkit/library/gtest/TestUCRTDepends.cpp
--- a/toolkit/library/gtest/moz.build +++ b/toolkit/library/gtest/moz.build @@ -2,28 +2,27 @@ # vim: set filetype=python: # 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/. FINAL_TARGET = 'dist/bin/gtest' if CONFIG['ENABLE_TESTS']: - USE_LIBS += [ - 'gkrust-gtest', - ] + USE_LIBS += [ + 'gkrust-gtest', + ] + + if CONFIG['OS_ARCH'] == 'WINNT': + UNIFIED_SOURCES += [ + 'TestUCRTDepends.cpp', + ] USE_LIBS += [ 'static:xul', - # xul-gtest is an intermediate static library. It is used as FINAL_TARGET - # for gtest code. - # If the FINAL_TARGET were the library in this directory, then the gtest - # code would end up before static:xul, and before StaticXULComponentStart, - # which needs to stay first. - 'xul-gtest', ] if CONFIG['OS_ARCH'] == 'Linux' and CONFIG['OS_TARGET'] != 'Android': GENERATED_FILES += ['symverscript'] GENERATED_FILES['symverscript'].script = '/build/gen_symverscript.py' GENERATED_FILES['symverscript'].inputs = ['../symverscript.in'] GENERATED_FILES['symverscript'].flags = [ 'xul%s' % CONFIG['MOZILLA_SYMBOLVERSION'] @@ -34,16 +33,10 @@ if CONFIG['OS_ARCH'] == 'Linux' and CONF # too old to support Python pretty-printers; if this changes, we could # make this 'ifdef GNU_CC'. if CONFIG['OS_ARCH'] == 'Linux': # Create a GDB Python auto-load file alongside the libxul shared library # in the build directory. DEFINES['topsrcdir'] = TOPSRCDIR OBJDIR_PP_FILES.toolkit.library.gtest += ['../libxul.so-gdb.py.in'] -# This needs to come after static:xul to avoid things like libfallible coming -# before StaticXULComponentStart. -Libxul('xul-gtest-real', +Libxul('xul-gtest', output_category=None if CONFIG['LINK_GTEST_DURING_COMPILE'] else 'gtest') - -DIRS += [ - 'static', -]
deleted file mode 100644 --- a/toolkit/library/gtest/static/moz.build +++ /dev/null @@ -1,14 +0,0 @@ -# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- -# vim: set filetype=python: -# 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/. - -Library('xul-gtest') - -if CONFIG['ENABLE_TESTS'] and CONFIG['OS_ARCH'] == 'WINNT': - UNIFIED_SOURCES += [ - 'TestUCRTDepends.cpp', - ] - -Libxul_defines()