author | Gurzau Raul <rgurzau@mozilla.com> |
Sat, 20 Apr 2019 12:54:53 +0300 | |
changeset 470310 | a092972b53f0e566a36770e7b03363036ff820ec |
parent 470295 | 6e082b6757630d3c57382feb106a674056881f86 (current diff) |
parent 470309 | 6ffe4f5a3c4744adce3f62255cf91351c39a33a0 (diff) |
child 470311 | bbdaf05c9d16b95a9bea1bf1125d69fb58aacd81 |
child 470313 | 9bb46f41277cf0beec92dd551c86c500dad5c3cf |
push id | 83651 |
push user | rgurzau@mozilla.com |
push date | Sat, 20 Apr 2019 10:01:30 +0000 |
treeherder | autoland@a092972b53f0 [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
reviewers | merge |
milestone | 68.0a1 |
first release with | nightly linux32
a092972b53f0
/
68.0a1
/
20190420095532
/
files
nightly linux64
a092972b53f0
/
68.0a1
/
20190420095532
/
files
nightly mac
a092972b53f0
/
68.0a1
/
20190420095532
/
files
nightly win32
a092972b53f0
/
68.0a1
/
20190420095532
/
files
nightly win64
a092972b53f0
/
68.0a1
/
20190420095532
/
files
|
last release without | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
releases | nightly linux32
68.0a1
/
20190420095532
/
pushlog to previous
nightly linux64
68.0a1
/
20190420095532
/
pushlog to previous
nightly mac
68.0a1
/
20190420095532
/
pushlog to previous
nightly win32
68.0a1
/
20190420095532
/
pushlog to previous
nightly win64
68.0a1
/
20190420095532
/
pushlog to previous
|
--- a/browser/installer/windows/nsis/shared.nsh +++ b/browser/installer/windows/nsis/shared.nsh @@ -1187,19 +1187,32 @@ ; assume that unpinning unpinned a side by side installation from ; the Start Menu and pin this installation to the Start Menu. ${Unless} $R8 == $R9 ; Pin the shortcut to the Start Menu. 5381 is the shell32.dll ; resource id for the "Pin to Start Menu" string. InvokeShellVerb::DoIt "$SMPROGRAMS" "$1" "5381" ${EndUnless} - ; Pin the shortcut to the TaskBar. 5386 is the shell32.dll resource - ; id for the "Pin to Taskbar" string. - InvokeShellVerb::DoIt "$SMPROGRAMS" "$1" "5386" + ${If} ${AtMostWin2012R2} + ; Pin the shortcut to the TaskBar. 5386 is the shell32.dll + ; resource id for the "Pin to Taskbar" string. + InvokeShellVerb::DoIt "$SMPROGRAMS" "$1" "5386" + ${Else} + ; In Windows 10 the "Pin to Taskbar" resource was removed, so we + ; can't access the verb that way anymore. We have a create a + ; command key using the GUID that's assigned to this action and + ; then invoke that as a verb. + ReadRegStr $R9 HKLM \ + "Software\Microsoft\Windows\CurrentVersion\Explorer\CommandStore\shell\Windows.taskbarpin" \ + "ExplorerCommandHandler" + WriteRegStr HKCU "Software\Classes\*\shell\${AppRegName}-$AppUserModelID" "ExplorerCommandHandler" $R9 + InvokeShellVerb::DoIt "$SMPROGRAMS" "$1" "${AppRegName}-$AppUserModelID" + DeleteRegKey HKCU "Software\Classes\*\shell\${AppRegName}-$AppUserModelID" + ${EndIf} ; Delete the shortcut if it was created ${If} "$8" == "true" Delete "$SMPROGRAMS\$1" ${EndIf} ${EndIf} ${If} $TmpVal == "HKCU"
--- a/devtools/client/debugger/panel.js +++ b/devtools/client/debugger/panel.js @@ -134,16 +134,20 @@ DebuggerPanel.prototype = { frames.forEach(frame => { frame.actor = frame.id; }); return { frames, selected }; }, + lookupConsoleClient: function(thread) { + return this._client.lookupConsoleClient(thread); + }, + getMappedExpression(expression) { return this._actions.getMappedExpression(expression); }, isPaused() { const thread = this._selectors.getCurrentThread(this._getState()); return this._selectors.getIsPaused(this._getState(), thread); },
--- a/devtools/client/debugger/src/client/firefox/commands.js +++ b/devtools/client/debugger/src/client/firefox/commands.js @@ -506,12 +506,13 @@ const clientCommands = { registerSourceActor, fetchWorkers, getMainThread, sendPacket, setSkipPausing, setEventListenerBreakpoints, waitForWorkers, detachWorkers, - hasWasmSupport + hasWasmSupport, + lookupConsoleClient }; export { setupCommands, clientCommands };
--- a/devtools/client/locales/en-US/netmonitor.properties +++ b/devtools/client/locales/en-US/netmonitor.properties @@ -140,16 +140,21 @@ jsonpScopeName=JSONP → callback %S() # in the response tab of the network details pane when the response is over # the truncation limit and thus was truncated. responseTruncated=Response has been truncated # LOCALIZATION NOTE (responsePreview): This is the text displayed # in the response tab of the network details pane for an HTML preview. responsePreview=Preview +# LOCALIZATION NOTE (networkMenu.raced): This is the label displayed +# in the network menu specifying the transfer or a request is +# raced. %S refers to the current transfer size. +networkMenu.raced=%S (raced) + # LOCALIZATION NOTE (networkMenu.sortedAsc): This is the tooltip displayed # in the network table toolbar, for any column that is sorted ascending. networkMenu.sortedAsc=Sorted ascending # LOCALIZATION NOTE (networkMenu.sortedDesc): This is the tooltip displayed # in the network table toolbar, for any column that is sorted descending. networkMenu.sortedDesc=Sorted descending @@ -216,17 +221,17 @@ networkMenu.sizeGB=%S GB networkMenu.sizeUnavailable=— # LOCALIZATION NOTE (networkMenu.sizeUnavailable.title): This is the tooltip # displayed in the network menu specifying that the transferred size of a # request is unavailable. networkMenu.sizeUnavailable.title=Transferred size is not available # LOCALIZATION NOTE (networkMenu.sizeCached): This is the label displayed -# in the network menu specifying the transferred of a request is +# in the network menu specifying the transfer or a request is # cached. networkMenu.sizeCached=cached # LOCALIZATION NOTE (networkMenu.sizeServiceWorker): This is the label displayed # in the network menu specifying the transferred of a request computed # by a service worker. networkMenu.sizeServiceWorker=service worker
--- a/devtools/client/netmonitor/src/components/RequestListColumnTransferredSize.js +++ b/devtools/client/netmonitor/src/components/RequestListColumnTransferredSize.js @@ -13,16 +13,17 @@ const { propertiesEqual } = require("../ const SIZE_CACHED = L10N.getStr("networkMenu.sizeCached"); const SIZE_SERVICE_WORKER = L10N.getStr("networkMenu.sizeServiceWorker"); const SIZE_UNAVAILABLE = L10N.getStr("networkMenu.sizeUnavailable"); const SIZE_UNAVAILABLE_TITLE = L10N.getStr("networkMenu.sizeUnavailable.title"); const UPDATED_TRANSFERRED_PROPS = [ "transferredSize", "fromCache", + "isRacing", "fromServiceWorker", ]; class RequestListColumnTransferredSize extends Component { static get propTypes() { return { item: PropTypes.object.isRequired, }; @@ -34,27 +35,31 @@ class RequestListColumnTransferredSize e render() { const { blockedReason, fromCache, fromServiceWorker, status, transferredSize, + isRacing, } = this.props.item; let text; if (blockedReason) { text = L10N.getFormatStr("networkMenu.blockedBy", blockedReason); } else if (fromCache || status === "304") { text = SIZE_CACHED; } else if (fromServiceWorker) { text = SIZE_SERVICE_WORKER; } else if (typeof transferredSize == "number") { text = getFormattedSize(transferredSize); + if (isRacing && typeof isRacing == "boolean") { + text = L10N.getFormatStr("networkMenu.raced", text); + } } else if (transferredSize === null) { text = SIZE_UNAVAILABLE; } const title = text == SIZE_UNAVAILABLE ? SIZE_UNAVAILABLE_TITLE : text; return ( dom.td({
--- a/devtools/client/netmonitor/src/components/RequestListItem.js +++ b/devtools/client/netmonitor/src/components/RequestListItem.js @@ -90,16 +90,17 @@ loader.lazyGetter(this, "RequestListColu */ const UPDATED_REQ_ITEM_PROPS = [ "mimeType", "eventTimings", "securityState", "status", "statusText", "fromCache", + "isRacing", "fromServiceWorker", "method", "url", "remoteAddress", "cause", "contentSize", "transferredSize", "startedMillis",
--- a/devtools/client/netmonitor/src/connector/firefox-data-provider.js +++ b/devtools/client/netmonitor/src/connector/firefox-data-provider.js @@ -366,16 +366,17 @@ class FirefoxDataProvider { const { packet, networkInfo } = data; const { actor } = networkInfo; const { updateType } = packet; switch (updateType) { case "securityInfo": this.pushRequestToQueue(actor, { securityState: networkInfo.securityState, + isRacing: packet.isRacing, }); break; case "responseStart": this.pushRequestToQueue(actor, { httpVersion: networkInfo.response.httpVersion, remoteAddress: networkInfo.response.remoteAddress, remotePort: networkInfo.response.remotePort, status: networkInfo.response.status,
--- a/devtools/client/netmonitor/src/constants.js +++ b/devtools/client/netmonitor/src/constants.js @@ -114,16 +114,17 @@ const EVENTS = { const UPDATE_PROPS = [ "method", "url", "remotePort", "remoteAddress", "status", "statusText", "httpVersion", + "isRacing", "securityState", "securityInfo", "securityInfoAvailable", "mimeType", "contentSize", "transferredSize", "totalTime", "eventTimings",
--- a/devtools/client/webconsole/actions/autocomplete.js +++ b/devtools/client/webconsole/actions/autocomplete.js @@ -21,17 +21,17 @@ const { */ function autocompleteUpdate(force, getterPath) { return ({dispatch, getState, services}) => { if (services.inputHasSelection()) { return dispatch(autocompleteClear()); } const inputValue = services.getInputValue(); - const frameActorId = services.getFrameActor(); + const { frameActor: frameActorId, client } = services.getFrameActor(); const cursor = services.getInputCursor(); const state = getState().autocomplete; const { cache } = state; if (!force && ( !inputValue || /^[a-zA-Z0-9_$]/.test(inputValue.substring(cursor)) )) { @@ -68,17 +68,17 @@ function autocompleteUpdate(force, gette } else { authorizedEvaluations = [getterPath]; } } return dispatch(autocompleteDataFetch({ input, frameActorId, - client: services.getWebConsoleClient(), + client, authorizedEvaluations, force, })); }; } /** * Called when the autocompletion data should be cleared.
--- a/devtools/client/webconsole/components/JSTerm.js +++ b/devtools/client/webconsole/components/JSTerm.js @@ -669,18 +669,21 @@ class JSTerm extends Component { */ requestEvaluation(str, options = {}) { // Send telemetry event. If we are in the browser toolbox we send -1 as the // toolbox session id. this.props.serviceContainer.recordTelemetryEvent("execute_js", { "lines": str.split(/\n/).length, }); - return this.webConsoleClient.evaluateJSAsync(str, { - frameActor: this.props.serviceContainer.getFrameActor(options.frame), + const { frameActor, client } = + this.props.serviceContainer.getFrameActor(options.frame); + + return client.evaluateJSAsync(str, { + frameActor, ...options, }); } /** * Copy the object/variable by invoking the server * which invokes the `copy(variable)` command and makes it * available in the clipboard
--- a/devtools/client/webconsole/test/mochitest/browser.ini +++ b/devtools/client/webconsole/test/mochitest/browser.ini @@ -45,16 +45,18 @@ support-files = test-dynamic-import.html test-dynamic-import.js test-error.html test-error-worker.html test-error-worker.js test-error-worker2.js test-eval-in-stackframe.html test-eval-sources.html + test-evaluate-worker.html + test-evaluate-worker.js test-external-script-errors.html test-external-script-errors.js test-iframe-insecure-form-action.html test-iframe1.html test-iframe2.html test-iframe3.html test-iframe-wrong-hud-iframe.html test-iframe-wrong-hud.html @@ -407,8 +409,9 @@ tags = trackingprotection [browser_webconsole_view_source.js] [browser_webconsole_visibility_messages.js] [browser_webconsole_warn_about_replaced_api.js] [browser_webconsole_warning_group_content_blocking.js] [browser_webconsole_warning_groups_outside_console_group.js] [browser_webconsole_warning_groups.js] [browser_webconsole_websocket.js] [browser_webconsole_worker_error.js] +[browser_webconsole_worker_evaluate.js]
new file mode 100644 --- /dev/null +++ b/devtools/client/webconsole/test/mochitest/browser_webconsole_worker_evaluate.js @@ -0,0 +1,32 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +// When the debugger is paused in a worker thread, console evaluations should +// be performed in that worker's selected frame. + +"use strict"; + +const TEST_URI = "http://example.com/browser/devtools/client/webconsole/" + + "test/mochitest/test-evaluate-worker.html"; + +add_task(async function() { + const hud = await openNewTabAndConsole(TEST_URI); + const {jsterm} = hud; + + await openDebugger(); + const toolbox = gDevTools.getToolbox(hud.target); + const dbg = createDebuggerContext(toolbox); + + jsterm.execute("pauseInWorker(42)"); + + await waitForPaused(dbg); + await openConsole(); + + const onMessage = waitForMessage(hud, "42"); + jsterm.execute("data"); + await onMessage; + + ok(true, "Evaluated console message in worker thread"); +});
new file mode 100644 --- /dev/null +++ b/devtools/client/webconsole/test/mochitest/test-evaluate-worker.html @@ -0,0 +1,9 @@ +<script> +"use strict"; +var w = new Worker("test-evaluate-worker.js"); + +// eslint-disable-next-line no-unused-vars +function pauseInWorker(value) { + w.postMessage(value); +} +</script>
new file mode 100644 --- /dev/null +++ b/devtools/client/webconsole/test/mochitest/test-evaluate-worker.js @@ -0,0 +1,8 @@ +"use strict"; + +self.addEventListener("message", ({ data }) => foo(data)); + +function foo(data) { + // eslint-disable-next-line no-debugger + debugger; +}
--- a/devtools/client/webconsole/webconsole-wrapper.js +++ b/devtools/client/webconsole/webconsole-wrapper.js @@ -126,29 +126,40 @@ class WebConsoleWrapper { return webConsoleUI.webConsoleClient; }, /** * Retrieve the FrameActor ID given a frame depth, or the selected one if no * frame depth given. * * @param {Number} frame: optional frame depth. - * @return {String|null}: The FrameActor ID for the given frame depth (or the - * selected frame if it exists). + * @return { frameActor: String|null, client: Object }: + * frameActor is the FrameActor ID for the given frame depth + * (or the selected frame if it exists), null if no frame was found. + * client is the WebConsole client for the thread the frame is + * associated with. */ getFrameActor: (frame = null) => { const state = this.hud.getDebuggerFrames(); if (!state) { - return null; + return { frameActor: null, client: webConsoleUI.webConsoleClient }; } const grip = Number.isInteger(frame) ? state.frames[frame] : state.frames[state.selected]; - return grip ? grip.actor : null; + + if (!grip) { + return { frameActor: null, client: webConsoleUI.webConsoleClient }; + } + + return { + frameActor: grip.actor, + client: this.hud.lookupConsoleClient(grip.thread), + }; }, inputHasSelection: () => { const {editor, inputNode} = webConsoleUI.jsterm || {}; return editor ? !!editor.getSelection() : (inputNode && inputNode.selectionStart !== inputNode.selectionEnd); },
--- a/devtools/client/webconsole/webconsole.js +++ b/devtools/client/webconsole/webconsole.js @@ -248,16 +248,28 @@ class WebConsole { if (!panel) { return null; } return panel.getFrames(); } /** + * Return the console client to use when interacting with a thread. + * + * @param {String} thread: The ID of the target thread. + * @returns {Object} The console client associated with the thread. + */ + lookupConsoleClient(thread) { + const toolbox = gDevTools.getToolbox(this.target); + const panel = toolbox.getPanel("jsdebugger"); + return panel.lookupConsoleClient(thread); + } + + /** * Given an expression, returns an object containing a new expression, mapped by the * parser worker to provide additional feature for the user (top-level await, * original languages mapping, …). * * @param {String} expression: The input to maybe map. * @returns {Object|null} * Returns null if the input can't be mapped. * If it can, returns an object containing the following:
--- a/devtools/client/webreplay/mochitest/browser.ini +++ b/devtools/client/webreplay/mochitest/browser.ini @@ -14,16 +14,18 @@ support-files = !/devtools/client/debugger/test/mochitest/helpers/context.js !/devtools/client/inspector/test/shared-head.js examples/doc_rr_basic.html examples/doc_rr_continuous.html examples/doc_rr_logs.html examples/doc_rr_recovery.html examples/doc_rr_error.html examples/doc_inspector_basic.html + examples/doc_inspector_styles.html + examples/styles.css [browser_dbg_rr_breakpoints-01.js] [browser_dbg_rr_breakpoints-02.js] [browser_dbg_rr_breakpoints-03.js] [browser_dbg_rr_breakpoints-04.js] [browser_dbg_rr_breakpoints-05.js] [browser_dbg_rr_record.js] [browser_dbg_rr_stepping-01.js] @@ -36,8 +38,9 @@ skip-if = true # See bug 1481009 [browser_dbg_rr_replay-02.js] [browser_dbg_rr_replay-03.js] [browser_dbg_rr_console_warp-01.js] [browser_dbg_rr_console_warp-02.js] [browser_dbg_rr_logpoint-01.js] [browser_dbg_rr_logpoint-02.js] [browser_rr_inspector-01.js] [browser_rr_inspector-02.js] +[browser_rr_inspector-03.js]
new file mode 100644 --- /dev/null +++ b/devtools/client/webreplay/mochitest/browser_rr_inspector-03.js @@ -0,0 +1,61 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ +/* eslint-disable no-undef */ + +"use strict"; + +Services.scriptloader.loadSubScript( + "chrome://mochitests/content/browser/devtools/client/inspector/test/shared-head.js", + this +); + +function getComputedViewProperty(view, name) { + let prop; + for (const property of view.styleDocument.querySelectorAll( + "#computed-container .computed-property-view")) { + const nameSpan = property.querySelector(".computed-property-name"); + const valueSpan = property.querySelector(".computed-property-value"); + + if (nameSpan.firstChild.textContent === name) { + prop = {nameSpan: nameSpan, valueSpan: valueSpan}; + break; + } + } + return prop; +} + +// Test that styles for elements can be viewed when using web replay. +add_task(async function() { + const dbg = await attachRecordingDebugger( + "doc_inspector_styles.html", + { waitForRecording: true } + ); + const {threadClient, tab, toolbox} = dbg; + await threadClient.resume(); + + await threadClient.interrupt(); + + const {inspector, view} = await openComputedView(); + await checkBackgroundColor("body", "rgb(0, 128, 0)"); + await checkBackgroundColor("#maindiv", "rgb(0, 0, 255)"); + + const bp = await setBreakpoint(threadClient, "doc_inspector_styles.html", 11); + + await rewindToLine(threadClient, 11); + await checkBackgroundColor("#maindiv", "rgb(255, 0, 0)"); + + await threadClient.removeBreakpoint(bp); + await toolbox.closeToolbox(); + await gBrowser.removeTab(tab); + + async function checkBackgroundColor(node, color) { + await selectNode(node, inspector); + + const value = getComputedViewProperty(view, "background-color").valueSpan; + const nodeInfo = view.getNodeInfo(value); + is(nodeInfo.value.property, "background-color"); + is(nodeInfo.value.value, color); + } +});
new file mode 100644 --- /dev/null +++ b/devtools/client/webreplay/mochitest/examples/doc_inspector_styles.html @@ -0,0 +1,16 @@ +<body> +<link rel="stylesheet" href="styles.css"> +<div id="maindiv">HELLO</div> +<script> +const cpmm = SpecialPowers.Services.cpmm; +function recordingFinished() { + cpmm.sendAsyncMessage("RecordingFinished"); +} +function foo() { + document.getElementById("maindiv").innerHTML = "GOODBYE"; + document.getElementById("maindiv").style.backgroundColor = "blue"; + window.setTimeout(recordingFinished); +} +window.setTimeout(foo); +</script> +</body>
new file mode 100644 --- /dev/null +++ b/devtools/client/webreplay/mochitest/examples/styles.css @@ -0,0 +1,7 @@ +body { + background-color: green; +} + +div { + background-color: red; +}
--- a/devtools/server/actors/network-event.js +++ b/devtools/server/actors/network-event.js @@ -398,26 +398,26 @@ const NetworkEventActor = protocol.Actor }, /** * Add connection security information. * * @param object info * The object containing security information. */ - addSecurityInfo(info) { + addSecurityInfo(info, isRacing) { // Ignore calls when this actor is already destroyed if (!this.actorID) { return; } this._securityInfo = info; - this.emit("network-event-update:security-info", "securityInfo", { state: info.state, + isRacing: isRacing, }); }, /** * Add network response headers. * * @param array headers * The response headers array.
--- a/devtools/server/actors/network-monitor/network-response-listener.js +++ b/devtools/server/actors/network-monitor/network-response-listener.js @@ -290,17 +290,23 @@ NetworkResponseListener.prototype = { // Take the security information from the original nsIHTTPChannel instead of // the nsIRequest received in onStartRequest. If response to this request // was a redirect from http to https, the request object seems to contain // security info for the https request after redirect. const secinfo = this.httpActivity.channel.securityInfo; const info = NetworkHelper.parseSecurityInfo(secinfo, this.httpActivity); - this.httpActivity.owner.addSecurityInfo(info); + let isRacing = false; + const channel = this.httpActivity.channel; + if (channel instanceof Ci.nsICacheInfoChannel) { + isRacing = channel.isRacing(); + } + + this.httpActivity.owner.addSecurityInfo(info, isRacing); }), /** * Fetches cache information from CacheEntry * @private */ _fetchCacheInformation: function() { const httpActivity = this.httpActivity;
--- a/devtools/server/actors/replay/debugger.js +++ b/devtools/server/actors/replay/debugger.js @@ -934,19 +934,16 @@ ReplayDebuggerObject.prototype = { get isGeneratorFunction() { return this._data.isGeneratorFunction; }, get isAsyncFunction() { return this._data.isAsyncFunction; }, get class() { return this._data.class; }, get name() { return this._data.name; }, get displayName() { return this._data.displayName; }, get parameterNames() { return this._data.parameterNames; }, get script() { return this._dbg._getScript(this._data.script); }, get environment() { return this._dbg._getObject(this._data.environment); }, - get boundTargetFunction() { return this.isBoundFunction ? NYI() : undefined; }, - get boundThis() { return this.isBoundFunction ? NYI() : undefined; }, - get boundArguments() { return this.isBoundFunction ? NYI() : undefined; }, get isProxy() { return this._data.isProxy; }, get proto() { return this._dbg._getObject(this._data.proto); }, isExtensible() { return this._data.isExtensible; }, isSealed() { return this._data.isSealed; }, isFrozen() { return this._data.isFrozen; }, unsafeDereference() { @@ -1020,16 +1017,37 @@ ReplayDebuggerObject.prototype = { return this._dbg._convertValue(this._proxyData.target); }, get proxyHandler() { this._ensureProxyData(); return this._dbg._convertValue(this._proxyData.handler); }, + get boundTargetFunction() { + if (this.isBoundFunction) { + return this._dbg._getObject(this._data.boundTargetFunction); + } + return undefined; + }, + + get boundThis() { + if (this.isBoundFunction) { + return this._dbg._convertValue(this._data.boundThis); + } + return undefined; + }, + + get boundArguments() { + if (this.isBoundFunction) { + return this._dbg._getObject(this._data.boundArguments); + } + return undefined; + }, + call(thisv, ...args) { return this.apply(thisv, args); }, apply(thisv, args) { thisv = this._dbg._convertValueForChild(thisv); args = (args || []).map(v => this._dbg._convertValueForChild(v));
--- a/devtools/server/actors/replay/inspector.js +++ b/devtools/server/actors/replay/inspector.js @@ -34,28 +34,54 @@ function dbg() { /////////////////////////////////////////////////////////////////////////////// // Public Interface /////////////////////////////////////////////////////////////////////////////// const ReplayInspector = { // Return a proxy for the window in the replaying process. get window() { - return gWindow; + if (!gFixedProxy.window) { + updateFixedProxies(); + } + return gFixedProxy.window; }, // Create the InspectorUtils object to bind for other server users. createInspectorUtils(utils) { - // Overwrite some APIs that will fail if called on proxies from the - // replaying process. + return new Proxy({}, { + get(_, name) { + switch (name) { + case "getAllStyleSheets": + case "getCSSStyleRules": + case "getRuleLine": + case "getRuleColumn": + case "getRelativeRuleLine": + case "getSelectorCount": + case "getSelectorText": + case "selectorMatchesElement": + case "hasRulesModifiedByCSSOM": + case "getSpecificity": + return gFixedProxy.InspectorUtils[name]; + case "hasPseudoClassLock": + return () => false; + default: + return utils[name]; + } + }, + }); + }, + + // Create the CSSRule object to bind for other server users. + createCSSRule(rule) { return { - ...utils, - hasPseudoClassLock() { return false; }, - getAllStyleSheets() { return []; }, - getCSSStyleRules() { return []; }, + ...rule, + isInstance(node) { + return gFixedProxy.CSSRule.isInstance(node); + }, }; }, wrapRequireHook(requireHook) { return (id, require) => { const rv = requireHook(id, require); return substituteRequire(id, rv); }; @@ -133,23 +159,17 @@ function createSubstituteChrome(chrome) }), }; } function createSubstituteServices(Services) { return newSubstituteProxy(Services, { els: { getListenerInfoFor(node) { - const id = unwrapValue(node)._data.id; - const rv = dbg()._sendRequestAllowDiverge({ - type: "getListenerInfoFor", - id, - }); - const obj = dbg()._getObject(rv.id); - return wrapValue(obj); + return gFixedProxy.Services.els.getListenerInfoFor(node); }, }, }); } function createSubstitute(id, rv) { switch (id) { case "chrome": return createSubstituteChrome(rv); @@ -360,17 +380,17 @@ const ReplayInspectorProxyHandler = { ThrowError(rv.throw); }, construct(target, args) { target = getTargetObject(target); const proxy = wrapObject(target); // Create fake MutationObservers to satisfy callers in the inspector. - if (proxy == gWindow.MutationObserver) { + if (proxy == gFixedProxy.window.MutationObserver) { return { observe: () => {}, disconnect: () => {}, }; } NotAllowed(); }, @@ -412,36 +432,36 @@ const ReplayInspectorProxyHandler = { }; /////////////////////////////////////////////////////////////////////////////// // Fixed Proxies /////////////////////////////////////////////////////////////////////////////// // Proxies for the window and root document are reused to ensure consistent // actors are used for these objects. -const gWindowTarget = { object: {} }, gDocumentTarget = { object: {} }; -const gWindow = new Proxy(gWindowTarget, ReplayInspectorProxyHandler); -const gDocument = new Proxy(gDocumentTarget, ReplayInspectorProxyHandler); +const gFixedProxyTargets = {}; +const gFixedProxy = {}; function initFixedProxy(proxy, target, obj) { target.object = obj; proxyMap.set(proxy, obj); obj._inspectorObject = proxy; } function updateFixedProxies() { dbg()._ensurePaused(); - const data = dbg()._sendRequestAllowDiverge({ type: "getWindow" }); - const dbgWindow = dbg()._getObject(data.id); - initFixedProxy(gWindow, gWindowTarget, dbgWindow); - - const rv = getObjectProperty(dbgWindow, "document"); - assert(rv.return instanceof ReplayDebugger.Object); - initFixedProxy(gDocument, gDocumentTarget, rv.return); + const data = dbg()._sendRequestAllowDiverge({ type: "getFixedObjects" }); + for (const [key, value] of Object.entries(data)) { + if (!gFixedProxyTargets[key]) { + gFixedProxyTargets[key] = { object: {} }; + gFixedProxy[key] = new Proxy(gFixedProxyTargets[key], ReplayInspectorProxyHandler); + } + initFixedProxy(gFixedProxy[key], gFixedProxyTargets[key], dbg()._getObject(value)); + } } /////////////////////////////////////////////////////////////////////////////// // Utilities /////////////////////////////////////////////////////////////////////////////// function NYI() { ThrowError("Not yet implemented");
--- a/devtools/server/actors/replay/replay.js +++ b/devtools/server/actors/replay/replay.js @@ -21,28 +21,38 @@ // any point where such interactions might occur. // eslint-disable spaced-comment "use strict"; const CC = Components.Constructor; // Create a sandbox with the resources we need. require() doesn't work here. -const sandbox = Cu.Sandbox(CC("@mozilla.org/systemprincipal;1", "nsIPrincipal")()); +const sandbox = Cu.Sandbox(CC("@mozilla.org/systemprincipal;1", "nsIPrincipal")(), { + wantGlobalProperties: [ + "InspectorUtils", + "CSSRule", + ], +}); Cu.evalInSandbox( "Components.utils.import('resource://gre/modules/jsdebugger.jsm');" + "Components.utils.import('resource://gre/modules/Services.jsm');" + "addDebuggerToGlobal(this);", sandbox ); -const Debugger = sandbox.Debugger; -const RecordReplayControl = sandbox.RecordReplayControl; -const Services = sandbox.Services; +const { + Debugger, + RecordReplayControl, + Services, + InspectorUtils, + CSSRule, +} = sandbox; const dbg = new Debugger(); +const firstGlobal = dbg.makeGlobalObjectReference(sandbox); // We are interested in debugging all globals in the process. dbg.onNewGlobalObject = function(global) { try { dbg.addDebuggee(global); } catch (e) { // Ignore errors related to adding a same-compartment debuggee. // See bug 1523755. @@ -542,19 +552,26 @@ function convertValueFromParent(value) { } } return value; } function makeDebuggeeValue(value) { if (isNonNullObject(value)) { assert(!(value instanceof Debugger.Object)); - const global = Cu.getGlobalForObject(value); - const dbgGlobal = dbg.makeGlobalObjectReference(global); - return dbgGlobal.makeDebuggeeValue(value); + try { + const global = Cu.getGlobalForObject(value); + const dbgGlobal = dbg.makeGlobalObjectReference(global); + return dbgGlobal.makeDebuggeeValue(value); + } catch (e) { + // Sometimes the global which Cu.getGlobalForObject finds has + // isInvisibleToDebugger set. Wrap the object into the first global we + // found in this case. + return firstGlobal.makeDebuggeeValue(value); + } } return value; } function getDebuggeeValue(value) { if (value && typeof value == "object") { assert(value instanceof Debugger.Object); return value.unsafeDereference(); @@ -713,17 +730,17 @@ const gRequestHandlers = { getSource(request) { return getSourceData(request.id); }, getObject(request) { const object = gPausedObjects.getObject(request.id); if (object instanceof Debugger.Object) { - return { + const rv = { id: request.id, kind: "Object", callable: object.callable, isBoundFunction: object.isBoundFunction, isArrowFunction: object.isArrowFunction, isGeneratorFunction: object.isGeneratorFunction, isAsyncFunction: object.isAsyncFunction, proto: getObjectId(object.proto), @@ -733,16 +750,22 @@ const gRequestHandlers = { parameterNames: object.parameterNames, script: gScripts.getId(object.script), environment: getObjectId(object.environment), isProxy: object.isProxy, isExtensible: object.isExtensible(), isSealed: object.isSealed(), isFrozen: object.isFrozen(), }; + if (rv.isBoundFunction) { + rv.boundTargetFunction = getObjectId(object.boundTargetFunction); + rv.boundThis = convertValue(object.boundThis); + rv.boundArguments = getObjectId(makeDebuggeeValue(object.boundArguments)); + } + return rv; } if (object instanceof Debugger.Environment) { return { id: request.id, kind: "Environment", type: object.type, parent: getObjectId(object.parent), object: object.type == "declarative" ? 0 : getObjectId(object.object), @@ -876,23 +899,29 @@ const gRequestHandlers = { recordingEndpoint(request) { return RecordReplayControl.recordingEndpoint(); }, ///////////////////////////////////////////////////////// // Inspector Requests ///////////////////////////////////////////////////////// - getWindow(request) { + getFixedObjects(request) { if (!RecordReplayControl.maybeDivergeFromRecording()) { return { throw: "Recording divergence in getWindow" }; } - // Hopefully there is exactly one window in this enumerator. - return { id: getObjectId(makeDebuggeeValue(getWindow())) }; + const window = getWindow(); + return { + window: getObjectId(makeDebuggeeValue(window)), + document: getObjectId(makeDebuggeeValue(window.document)), + Services: getObjectId(makeDebuggeeValue(Services)), + InspectorUtils: getObjectId(makeDebuggeeValue(InspectorUtils)), + CSSRule: getObjectId(makeDebuggeeValue(CSSRule)), + }; }, newDeepTreeWalker(request) { if (!RecordReplayControl.maybeDivergeFromRecording()) { return { throw: "Recording divergence in newDeepTreeWalker" }; } const walker = Cc["@mozilla.org/inspector/deep-tree-walker;1"] @@ -941,26 +970,16 @@ const gRequestHandlers = { const element = getWindow().document.elementFromPoint(request.clientX, request.clientY); if (!element) { return { id: 0 }; } const obj = makeDebuggeeValue(element); return { id: getObjectId(obj) }; }, - - getListenerInfoFor(request) { - if (!RecordReplayControl.maybeDivergeFromRecording()) { - return { throw: "Recording divergence in getListenerInfoFor" }; - } - - const node = gPausedObjects.getObject(request.id).unsafeDereference(); - const obj = makeDebuggeeValue(Services.els.getListenerInfoFor(node) || []); - return { id: getObjectId(obj) }; - }, }; // eslint-disable-next-line no-unused-vars function ProcessRequest(request) { try { if (gRequestHandlers[request.type]) { return gRequestHandlers[request.type](request); }
--- a/devtools/server/actors/stylesheets.js +++ b/devtools/server/actors/stylesheets.js @@ -14,16 +14,20 @@ const {mediaRuleSpec, styleSheetSpec, const InspectorUtils = require("InspectorUtils"); loader.lazyRequireGetter(this, "CssLogic", "devtools/shared/inspector/css-logic"); loader.lazyRequireGetter(this, "addPseudoClassLock", "devtools/server/actors/highlighters/utils/markup", true); loader.lazyRequireGetter(this, "removePseudoClassLock", "devtools/server/actors/highlighters/utils/markup", true); loader.lazyRequireGetter(this, "loadSheet", "devtools/shared/layout/utils", true); +loader.lazyRequireGetter(this, "ReplayDebugger", + "devtools/server/actors/replay/debugger"); +loader.lazyRequireGetter(this, "ReplayInspector", + "devtools/server/actors/replay/inspector"); var TRANSITION_PSEUDO_CLASS = ":-moz-styleeditor-transitioning"; var TRANSITION_DURATION_MS = 500; var TRANSITION_BUFFER_MS = 1000; var TRANSITION_RULE_SELECTOR = `:root${TRANSITION_PSEUDO_CLASS}, :root${TRANSITION_PSEUDO_CLASS} *`; var TRANSITION_SHEET = "data:text/css;charset=utf-8," + encodeURIComponent(` @@ -173,16 +177,23 @@ async function fetchStylesheet(sheet, co let result; if (consoleActor) { result = await consoleActor.getRequestContentForURL(href); if (result) { return result; } } + // When replaying, fetch the stylesheets from the replaying process, so that + // we get the same sheets which were used when recording. + if (isReplaying) { + const dbg = new ReplayDebugger(); + return dbg.replayingContent(href); + } + const options = { loadFromCache: true, policy: Ci.nsIContentPolicy.TYPE_INTERNAL_STYLESHEET, charset: getCSSCharset(sheet), }; // Bug 1282660 - We use the system principal to load the default internal // stylesheets instead of the content principal since such stylesheets @@ -222,17 +233,17 @@ var StyleSheetActor = protocol.ActorClas toString: function() { return "[StyleSheetActor " + this.actorID + "]"; }, /** * Window of target */ get window() { - return this.parentActor.window; + return isReplaying ? ReplayInspector.window : this.parentActor.window; }, /** * Document of target. */ get document() { return this.window.document; }, @@ -664,17 +675,18 @@ var StyleSheetsActor = protocol.ActorCla /** * Protocol method for getting a list of StyleSheetActors representing * all the style sheets in this document. */ async getStyleSheets() { let actors = []; - for (const win of this.parentActor.windows) { + const windows = isReplaying ? [ReplayInspector.window] : this.parentActor.windows; + for (const win of windows) { const sheets = await this._addStyleSheets(win); actors = actors.concat(sheets); } return actors; }, /** * Check if we should be showing this stylesheet.
--- a/devtools/shared/DevToolsUtils.js +++ b/devtools/shared/DevToolsUtils.js @@ -637,17 +637,17 @@ function mainThreadFetch(urlIn, aOptions /** * Opens a channel for given URL. Tries a bit harder than NetUtil.newChannel. * * @param {String} url - The URL to open a channel for. * @param {Object} options - The options object passed to @method fetch. * @return {nsIChannel} - The newly created channel. Throws on failure. */ -function newChannelForURL(url, { policy, window, principal }) { +function newChannelForURL(url, { policy, window, principal }, recursing = false) { const securityFlags = Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL; let uri; try { uri = Services.io.newURI(url); } catch (e) { // In the xpcshell tests, the script url is the absolute path of the test // file, which will make a malformed URI error be thrown. Add the file @@ -684,21 +684,27 @@ function newChannelForURL(url, { policy, } channelOptions.loadingPrincipal = prin; } try { return NetUtil.newChannel(channelOptions); } catch (e) { + // Don't infinitely recurse if newChannel keeps throwing. + if (recursing) { + throw e; + } + // In xpcshell tests on Windows, nsExternalProtocolHandler::NewChannel() // can throw NS_ERROR_UNKNOWN_PROTOCOL if the external protocol isn't // supported by Windows, so we also need to handle the exception here if // parsing the URL above doesn't throw. - return newChannelForURL("file://" + url, { policy, window, principal }); + return newChannelForURL("file://" + url, { policy, window, principal }, + /* recursing */ true); } } // Fetch is defined differently depending on whether we are on the main thread // or a worker thread. if (this.isWorker) { // Services is not available in worker threads, nor is there any other way // to fetch a URL. We need to enlist the help from the main thread here, by
--- a/devtools/shared/builtin-modules.js +++ b/devtools/shared/builtin-modules.js @@ -292,17 +292,16 @@ defineLazyGetter(exports.modules, "xpcIn // List of all custom globals exposed to devtools modules. // Changes here should be mirrored to devtools/.eslintrc. exports.globals = { atob, Blob, btoa, console, CSS, - CSSRule, // Make sure `define` function exists. This allows defining some modules // in AMD format while retaining CommonJS compatibility through this hook. // JSON Viewer needs modules in AMD format, as it currently uses RequireJS // from a content document and can't access our usual loaders. So, any // modules shared with the JSON Viewer should include a define wrapper: // // // Make this available to both AMD and CJS environments // define(function(require, exports, module) { @@ -376,8 +375,15 @@ lazyGlobal("WebSocket", () => { return Services.appShell.hiddenDOMWindow.WebSocket; }); lazyGlobal("indexedDB", () => { return require("devtools/shared/indexed-db").createDevToolsIndexedDB(indexedDB); }); lazyGlobal("isReplaying", () => { return exports.modules.Debugger.recordReplayProcessKind() == "Middleman"; }); +lazyGlobal("CSSRule", () => { + if (exports.modules.Debugger.recordReplayProcessKind() == "Middleman") { + const ReplayInspector = require("devtools/server/actors/replay/inspector"); + return ReplayInspector.createCSSRule(CSSRule); + } + return CSSRule; +});
--- a/devtools/shared/specs/network-event.js +++ b/devtools/shared/specs/network-event.js @@ -125,16 +125,17 @@ const networkEventSpec = generateActorSp response: Option(1, "json"), }, "network-event-update:security-info": { type: "networkEventUpdate", updateType: Arg(0, "string"), state: Option(1, "string"), + isRacing: Option(1, "boolean"), }, "network-event-update:response-content": { type: "networkEventUpdate", updateType: Arg(0, "string"), mimeType: Option(1, "string"), contentSize: Option(1, "number"),
--- a/layout/style/Loader.cpp +++ b/layout/style/Loader.cpp @@ -1571,16 +1571,25 @@ nsresult Loader::LoadSheet(SheetLoadData Loader::Completed Loader::ParseSheet(const nsACString& aBytes, SheetLoadData* aLoadData, AllowAsyncParse aAllowAsync) { LOG(("css::Loader::ParseSheet")); AUTO_PROFILER_LABEL("css::Loader::ParseSheet", LAYOUT_CSSParsing); MOZ_ASSERT(aLoadData); aLoadData->mIsBeingParsed = true; + // Tell the record/replay system about any sheets that are being parsed, + // so that devtools code can find them later. + if (recordreplay::IsRecordingOrReplaying() && aLoadData->mURI) { + recordreplay::NoteContentParse( + aLoadData, aLoadData->mURI->GetSpecOrDefault().get(), "text/css", + reinterpret_cast<const Utf8Unit*>(aBytes.BeginReading()), + aBytes.Length()); + } + StyleSheet* sheet = aLoadData->mSheet; MOZ_ASSERT(sheet); // Some cases, like inline style and UA stylesheets, need to be parsed // synchronously. The former may trigger child loads, the latter must not. if (aLoadData->mSyncLoad || aAllowAsync == AllowAsyncParse::No) { sheet->ParseSheetSync(this, aBytes, aLoadData, aLoadData->mLineNumber); aLoadData->mIsBeingParsed = false;
--- a/mfbt/RecordReplay.h +++ b/mfbt/RecordReplay.h @@ -311,17 +311,17 @@ MFBT_API ProgressCounter NewTimeWarpTarg // Return whether a script should update the progress counter when it runs. MFBT_API bool ShouldUpdateProgressCounter(const char* aURL); // Define a RecordReplayControl object on the specified global object, with // methods specialized to the current recording/replaying or middleman process // kind. MFBT_API bool DefineRecordReplayControlObject(JSContext* aCx, JSObject* aObj); -// Notify the infrastructure that some URL which contains JavaScript is +// Notify the infrastructure that some URL which contains JavaScript or CSS is // being parsed. This is used to provide the complete contents of the URL to // devtools code when it is inspecting the state of this process; that devtools // code can't simply fetch the URL itself since it may have been changed since // the recording was made or may no longer exist. The token for a parse may not // be used in other parses until after EndContentParse() is called. MFBT_API void BeginContentParse(const void* aToken, const char* aURL, const char* aContentType);
--- a/netwerk/base/nsICacheInfoChannel.idl +++ b/netwerk/base/nsICacheInfoChannel.idl @@ -51,16 +51,25 @@ interface nsICacheInfoChannel : nsISuppo /** * TRUE if this channel's data is being loaded from the cache. This value * is undefined before the channel fires its OnStartRequest notification * and after the channel fires its OnStopRequest notification. */ boolean isFromCache(); /** + * Returns true if the channel raced the cache and network requests. + * In order to determine if the response is coming from the cache or the + * network, the consumer can check isFromCache(). + * The method can only be called after the channel fires its OnStartRequest + * notification. + */ + boolean isRacing(); + + /** * The unique ID of the corresponding nsICacheEntry from which the response is * retrieved. By comparing the returned value, we can judge whether the data * of two distinct nsICacheInfoChannels is from the same nsICacheEntry. This * scenario could be useful when verifying whether the alternative data from * one nsICacheInfochannel matches the main data from another one. * * Note: NS_ERROR_NOT_AVAILABLE is thrown when a nsICacheInfoChannel has no * valid corresponding nsICacheEntry.
--- a/netwerk/protocol/http/HttpChannelChild.cpp +++ b/netwerk/protocol/http/HttpChannelChild.cpp @@ -169,16 +169,17 @@ HttpChannelChild::HttpChannelChild() mCacheFetchCount(0), mCacheExpirationTime(nsICacheEntry::NO_EXPIRATION_TIME), mDeletingChannelSent(false), mIPCOpen(false), mUnknownDecoderInvolved(false), mDivertingToParent(false), mFlushedForDiversion(false), mIsFromCache(false), + mIsRacing(false), mCacheNeedToReportBytesReadInitialized(false), mNeedToReportBytesRead(true), mCacheEntryAvailable(false), mAltDataCacheEntryAvailable(false), mSendResumeAt(false), mKeptAlive(false), mIPCActorDeleted(false), mSuspendSent(false), @@ -394,31 +395,33 @@ void HttpChannelChild::AssociateApplicat class StartRequestEvent : public NeckoTargetChannelEvent<HttpChannelChild> { public: StartRequestEvent( HttpChannelChild* aChild, const nsresult& aChannelStatus, const nsHttpResponseHead& aResponseHead, const bool& aUseResponseHead, const nsHttpHeaderArray& aRequestHeaders, const ParentLoadInfoForwarderArgs& loadInfoForwarder, - const bool& aIsFromCache, const bool& aCacheEntryAvailable, - const uint64_t& aCacheEntryId, const int32_t& aCacheFetchCount, - const uint32_t& aCacheExpirationTime, const nsCString& aCachedCharset, + const bool& aIsFromCache, const bool& aIsRacing, + const bool& aCacheEntryAvailable, const uint64_t& aCacheEntryId, + const int32_t& aCacheFetchCount, const uint32_t& aCacheExpirationTime, + const nsCString& aCachedCharset, const nsCString& aSecurityInfoSerialization, const NetAddr& aSelfAddr, const NetAddr& aPeerAddr, const uint32_t& aCacheKey, const nsCString& altDataType, const int64_t& altDataLen, const bool& deliveringAltData, const bool& aApplyConversion, const ResourceTimingStruct& aTiming) : NeckoTargetChannelEvent<HttpChannelChild>(aChild), mChannelStatus(aChannelStatus), mResponseHead(aResponseHead), mRequestHeaders(aRequestHeaders), mUseResponseHead(aUseResponseHead), mApplyConversion(aApplyConversion), mIsFromCache(aIsFromCache), + mIsRacing(aIsRacing), mCacheEntryAvailable(aCacheEntryAvailable), mCacheEntryId(aCacheEntryId), mCacheFetchCount(aCacheFetchCount), mCacheExpirationTime(aCacheExpirationTime), mCachedCharset(aCachedCharset), mSecurityInfoSerialization(aSecurityInfoSerialization), mSelfAddr(aSelfAddr), mPeerAddr(aPeerAddr), @@ -428,30 +431,31 @@ class StartRequestEvent : public NeckoTa mDeliveringAltData(deliveringAltData), mLoadInfoForwarder(loadInfoForwarder), mTiming(aTiming) {} void Run() override { LOG(("StartRequestEvent [this=%p]\n", mChild)); mChild->OnStartRequest( mChannelStatus, mResponseHead, mUseResponseHead, mRequestHeaders, - mLoadInfoForwarder, mIsFromCache, mCacheEntryAvailable, mCacheEntryId, - mCacheFetchCount, mCacheExpirationTime, mCachedCharset, + mLoadInfoForwarder, mIsFromCache, mIsRacing, mCacheEntryAvailable, + mCacheEntryId, mCacheFetchCount, mCacheExpirationTime, mCachedCharset, mSecurityInfoSerialization, mSelfAddr, mPeerAddr, mCacheKey, mAltDataType, mAltDataLen, mDeliveringAltData, mApplyConversion, mTiming); } private: nsresult mChannelStatus; nsHttpResponseHead mResponseHead; nsHttpHeaderArray mRequestHeaders; bool mUseResponseHead; bool mApplyConversion; bool mIsFromCache; + bool mIsRacing; bool mCacheEntryAvailable; uint64_t mCacheEntryId; int32_t mCacheFetchCount; uint32_t mCacheExpirationTime; nsCString mCachedCharset; nsCString mSecurityInfoSerialization; NetAddr mSelfAddr; NetAddr mPeerAddr; @@ -462,41 +466,42 @@ class StartRequestEvent : public NeckoTa ParentLoadInfoForwarderArgs mLoadInfoForwarder; ResourceTimingStruct mTiming; }; mozilla::ipc::IPCResult HttpChannelChild::RecvOnStartRequest( const nsresult& channelStatus, const nsHttpResponseHead& responseHead, const bool& useResponseHead, const nsHttpHeaderArray& requestHeaders, const ParentLoadInfoForwarderArgs& loadInfoForwarder, - const bool& isFromCache, const bool& cacheEntryAvailable, - const uint64_t& cacheEntryId, const int32_t& cacheFetchCount, - const uint32_t& cacheExpirationTime, const nsCString& cachedCharset, - const nsCString& securityInfoSerialization, const NetAddr& selfAddr, - const NetAddr& peerAddr, const int16_t& redirectCount, - const uint32_t& cacheKey, const nsCString& altDataType, - const int64_t& altDataLen, const bool& deliveringAltData, - const bool& aApplyConversion, const ResourceTimingStruct& aTiming) { + const bool& isFromCache, const bool& isRacing, + const bool& cacheEntryAvailable, const uint64_t& cacheEntryId, + const int32_t& cacheFetchCount, const uint32_t& cacheExpirationTime, + const nsCString& cachedCharset, const nsCString& securityInfoSerialization, + const NetAddr& selfAddr, const NetAddr& peerAddr, + const int16_t& redirectCount, const uint32_t& cacheKey, + const nsCString& altDataType, const int64_t& altDataLen, + const bool& deliveringAltData, const bool& aApplyConversion, + const ResourceTimingStruct& aTiming) { AUTO_PROFILER_LABEL("HttpChannelChild::RecvOnStartRequest", NETWORK); LOG(("HttpChannelChild::RecvOnStartRequest [this=%p]\n", this)); // mFlushedForDiversion and mDivertingToParent should NEVER be set at this // stage, as they are set in the listener's OnStartRequest. MOZ_RELEASE_ASSERT( !mFlushedForDiversion, "mFlushedForDiversion should be unset before OnStartRequest!"); MOZ_RELEASE_ASSERT( !mDivertingToParent, "mDivertingToParent should be unset before OnStartRequest!"); mRedirectCount = redirectCount; mEventQ->RunOrEnqueue(new StartRequestEvent( this, channelStatus, responseHead, useResponseHead, requestHeaders, - loadInfoForwarder, isFromCache, cacheEntryAvailable, cacheEntryId, - cacheFetchCount, cacheExpirationTime, cachedCharset, + loadInfoForwarder, isFromCache, isRacing, cacheEntryAvailable, + cacheEntryId, cacheFetchCount, cacheExpirationTime, cachedCharset, securityInfoSerialization, selfAddr, peerAddr, cacheKey, altDataType, altDataLen, deliveringAltData, aApplyConversion, aTiming)); { // Child's mEventQ is to control the execution order of the IPC messages // from both main thread IPDL and PBackground IPDL. // To guarantee the ordering, PBackground IPC messages that are sent after // OnStartRequest will be throttled until OnStartRequest hits the Child's @@ -515,21 +520,21 @@ mozilla::ipc::IPCResult HttpChannelChild return IPC_OK(); } void HttpChannelChild::OnStartRequest( const nsresult& channelStatus, const nsHttpResponseHead& responseHead, const bool& useResponseHead, const nsHttpHeaderArray& requestHeaders, const ParentLoadInfoForwarderArgs& loadInfoForwarder, - const bool& isFromCache, const bool& cacheEntryAvailable, - const uint64_t& cacheEntryId, const int32_t& cacheFetchCount, - const uint32_t& cacheExpirationTime, const nsCString& cachedCharset, - const nsCString& securityInfoSerialization, const NetAddr& selfAddr, - const NetAddr& peerAddr, const uint32_t& cacheKey, + const bool& isFromCache, const bool& isRacing, + const bool& cacheEntryAvailable, const uint64_t& cacheEntryId, + const int32_t& cacheFetchCount, const uint32_t& cacheExpirationTime, + const nsCString& cachedCharset, const nsCString& securityInfoSerialization, + const NetAddr& selfAddr, const NetAddr& peerAddr, const uint32_t& cacheKey, const nsCString& altDataType, const int64_t& altDataLen, const bool& deliveringAltData, const bool& aApplyConversion, const ResourceTimingStruct& aTiming) { LOG(("HttpChannelChild::OnStartRequest [this=%p]\n", this)); // mFlushedForDiversion and mDivertingToParent should NEVER be set at this // stage, as they are set in the listener's OnStartRequest. MOZ_RELEASE_ASSERT( @@ -564,16 +569,17 @@ void HttpChannelChild::OnStartRequest( MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv), "Deserializing security info should not fail"); Unused << rv; // So we don't get an unused error in release builds. } ipc::MergeParentLoadInfoForwarder(loadInfoForwarder, mLoadInfo); mIsFromCache = isFromCache; + mIsRacing = isRacing; mCacheEntryAvailable = cacheEntryAvailable; mCacheEntryId = cacheEntryId; mCacheFetchCount = cacheFetchCount; mCacheExpirationTime = cacheExpirationTime; mCachedCharset = cachedCharset; mSelfAddr = selfAddr; mPeerAddr = peerAddr; @@ -2996,16 +3002,25 @@ HttpChannelChild::GetCacheEntryId(uint64 return NS_ERROR_NOT_AVAILABLE; } *aCacheEntryId = mCacheEntryId; return NS_OK; } NS_IMETHODIMP +HttpChannelChild::IsRacing(bool* aIsRacing) { + if (!mAfterOnStartRequestBegun) { + return NS_ERROR_NOT_AVAILABLE; + } + *aIsRacing = mIsRacing; + return NS_OK; +} + +NS_IMETHODIMP HttpChannelChild::GetCacheKey(uint32_t* cacheKey) { MOZ_ASSERT(NS_IsMainThread()); if (mSynthesizedCacheInfo) { return mSynthesizedCacheInfo->GetCacheKey(cacheKey); } *cacheKey = mCacheKey; return NS_OK;
--- a/netwerk/protocol/http/HttpChannelChild.h +++ b/netwerk/protocol/http/HttpChannelChild.h @@ -133,19 +133,20 @@ class HttpChannelChild final : public PH nsresult CrossProcessRedirectFinished(nsresult aStatus); protected: mozilla::ipc::IPCResult RecvOnStartRequest( const nsresult& channelStatus, const nsHttpResponseHead& responseHead, const bool& useResponseHead, const nsHttpHeaderArray& requestHeaders, const ParentLoadInfoForwarderArgs& loadInfoForwarder, - const bool& isFromCache, const bool& cacheEntryAvailable, - const uint64_t& cacheEntryId, const int32_t& cacheFetchCount, - const uint32_t& cacheExpirationTime, const nsCString& cachedCharset, + const bool& isFromCache, const bool& isRacing, + const bool& cacheEntryAvailable, const uint64_t& cacheEntryId, + const int32_t& cacheFetchCount, const uint32_t& cacheExpirationTime, + const nsCString& cachedCharset, const nsCString& securityInfoSerialization, const NetAddr& selfAddr, const NetAddr& peerAddr, const int16_t& redirectCount, const uint32_t& cacheKey, const nsCString& altDataType, const int64_t& altDataLen, const bool& deliveringAltData, const bool& aApplyConversion, const ResourceTimingStruct& aTiming) override; mozilla::ipc::IPCResult RecvFailedAsyncOpen(const nsresult& status) override; mozilla::ipc::IPCResult RecvRedirect1Begin( @@ -397,16 +398,17 @@ class HttpChannelChild final : public PH // Once set, OnData and possibly OnStop will be diverted to the parent. Atomic<bool, ReleaseAcquire> mDivertingToParent; // Once set, no OnStart/OnData/OnStop callbacks should be received from the // parent channel, nor dequeued from the ChannelEventQueue. Atomic<bool, ReleaseAcquire> mFlushedForDiversion; Atomic<bool, SequentiallyConsistent> mIsFromCache; + Atomic<bool, SequentiallyConsistent> mIsRacing; // Set if we get the result and cache |mNeedToReportBytesRead| Atomic<bool, SequentiallyConsistent> mCacheNeedToReportBytesReadInitialized; // True if we need to tell the parent the size of unreported received data Atomic<bool, SequentiallyConsistent> mNeedToReportBytesRead; uint8_t mCacheEntryAvailable : 1; uint8_t mAltDataCacheEntryAvailable : 1; @@ -458,19 +460,20 @@ class HttpChannelChild final : public PH bool RemoteChannelExists() { return mIPCOpen && !mKeptAlive; } void AssociateApplicationCache(const nsCString& groupID, const nsCString& clientID); void OnStartRequest( const nsresult& channelStatus, const nsHttpResponseHead& responseHead, const bool& useResponseHead, const nsHttpHeaderArray& requestHeaders, const ParentLoadInfoForwarderArgs& loadInfoForwarder, - const bool& isFromCache, const bool& cacheEntryAvailable, - const uint64_t& cacheEntryId, const int32_t& cacheFetchCount, - const uint32_t& cacheExpirationTime, const nsCString& cachedCharset, + const bool& isFromCache, const bool& isRacing, + const bool& cacheEntryAvailable, const uint64_t& cacheEntryId, + const int32_t& cacheFetchCount, const uint32_t& cacheExpirationTime, + const nsCString& cachedCharset, const nsCString& securityInfoSerialization, const NetAddr& selfAddr, const NetAddr& peerAddr, const uint32_t& cacheKey, const nsCString& altDataType, const int64_t& altDataLen, const bool& deliveringAltData, const bool& aApplyConversion, const ResourceTimingStruct& aTiming); void MaybeDivertOnData(const nsCString& data, const uint64_t& offset, const uint32_t& count); void OnTransportAndData(const nsresult& channelStatus, const nsresult& status,
--- a/netwerk/protocol/http/HttpChannelParent.cpp +++ b/netwerk/protocol/http/HttpChannelParent.cpp @@ -1359,25 +1359,27 @@ HttpChannelParent::OnStartRequest(nsIReq MOZ_ASSERT(pcp, "We should have a manager if our IPC isn't closed"); DebugOnly<nsresult> rv = static_cast<ContentParent*>(pcp)->AboutToLoadHttpFtpDocumentForChild( chan); MOZ_ASSERT(NS_SUCCEEDED(rv)); } bool isFromCache = false; + bool isRacing = false; uint64_t cacheEntryId = 0; int32_t fetchCount = 0; uint32_t expirationTime = nsICacheEntry::NO_EXPIRATION_TIME; nsCString cachedCharset; RefPtr<nsHttpChannel> httpChannelImpl = do_QueryObject(chan); if (httpChannelImpl) { httpChannelImpl->IsFromCache(&isFromCache); + httpChannelImpl->IsRacing(&isRacing); httpChannelImpl->GetCacheEntryId(&cacheEntryId); httpChannelImpl->GetCacheTokenFetchCount(&fetchCount); httpChannelImpl->GetCacheTokenExpirationTime(&expirationTime); httpChannelImpl->GetCacheTokenCachedCharset(cachedCharset); } bool loadedFromApplicationCache = false; @@ -1468,21 +1470,21 @@ HttpChannelParent::OnStartRequest(nsIReq ResourceTimingStruct timing; GetTimingAttributes(mChannel, timing); rv = NS_OK; if (mIPCClosed || !SendOnStartRequest( channelStatus, *responseHead, useResponseHead, cleanedUpRequest ? cleanedUpRequestHeaders : requestHead->Headers(), - loadInfoForwarderArg, isFromCache, mCacheEntry ? true : false, - cacheEntryId, fetchCount, expirationTime, cachedCharset, - secInfoSerialization, chan->GetSelfAddr(), chan->GetPeerAddr(), - redirectCount, cacheKey, altDataType, altDataLen, deliveringAltData, - applyConversion, timing)) { + loadInfoForwarderArg, isFromCache, isRacing, + mCacheEntry ? true : false, cacheEntryId, fetchCount, expirationTime, + cachedCharset, secInfoSerialization, chan->GetSelfAddr(), + chan->GetPeerAddr(), redirectCount, cacheKey, altDataType, altDataLen, + deliveringAltData, applyConversion, timing)) { rv = NS_ERROR_UNEXPECTED; } requestHead->Exit(); // OnStartRequest is sent to content process successfully. // Notify PHttpBackgroundChannelChild that all following IPC mesasges // should be run after OnStartRequest is handled. if (NS_SUCCEEDED(rv)) {
--- a/netwerk/protocol/http/InterceptedHttpChannel.cpp +++ b/netwerk/protocol/http/InterceptedHttpChannel.cpp @@ -1171,16 +1171,25 @@ InterceptedHttpChannel::IsFromCache(bool if (mSynthesizedCacheInfo) { return mSynthesizedCacheInfo->IsFromCache(value); } *value = false; return NS_OK; } NS_IMETHODIMP +InterceptedHttpChannel::IsRacing(bool* value) { + if (mSynthesizedCacheInfo) { + return mSynthesizedCacheInfo->IsRacing(value); + } + *value = false; + return NS_OK; +} + +NS_IMETHODIMP InterceptedHttpChannel::GetCacheEntryId(uint64_t* aCacheEntryId) { if (mSynthesizedCacheInfo) { return mSynthesizedCacheInfo->GetCacheEntryId(aCacheEntryId); } return NS_ERROR_NOT_AVAILABLE; } NS_IMETHODIMP
--- a/netwerk/protocol/http/PHttpChannel.ipdl +++ b/netwerk/protocol/http/PHttpChannel.ipdl @@ -107,16 +107,17 @@ parent: child: async OnStartRequest(nsresult channelStatus, nsHttpResponseHead responseHead, bool useResponseHead, nsHttpHeaderArray requestHeaders, ParentLoadInfoForwarderArgs loadInfoForwarder, bool isFromCache, + bool isRacing, bool cacheEntryAvailable, uint64_t cacheEntryId, int32_t cacheFetchCount, uint32_t cacheExpirationTime, nsCString cachedCharset, nsCString securityInfoSerialization, NetAddr selfAddr, NetAddr peerAddr,
--- a/netwerk/protocol/http/nsHttpChannel.cpp +++ b/netwerk/protocol/http/nsHttpChannel.cpp @@ -8658,16 +8658,25 @@ nsHttpChannel::GetAltDataInputStream(con return NS_OK; } //----------------------------------------------------------------------------- // nsHttpChannel::nsICachingChannel //----------------------------------------------------------------------------- NS_IMETHODIMP +nsHttpChannel::IsRacing(bool *aIsRacing) { + if (!mAfterOnStartRequestBegun) { + return NS_ERROR_NOT_AVAILABLE; + } + *aIsRacing = mRaceCacheWithNetwork; + return NS_OK; +} + +NS_IMETHODIMP nsHttpChannel::GetCacheToken(nsISupports **token) { NS_ENSURE_ARG_POINTER(token); if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE; return CallQueryInterface(mCacheEntry, token); } NS_IMETHODIMP nsHttpChannel::SetCacheToken(nsISupports *token) {
--- a/netwerk/test/unit/test_race_cache_with_network.js +++ b/netwerk/test/unit/test_race_cache_with_network.js @@ -53,16 +53,17 @@ function cached_handler(metadata, respon g200Counter++; } let gResponseCounter = 0; let gIsFromCache = 0; function checkContent(request, buffer, context, isFromCache) { Assert.equal(buffer, gResponseBody); + info("isRacing: " + request.QueryInterface(Ci.nsICacheInfoChannel).isRacing() + "\n"); gResponseCounter++; if (isFromCache) { gIsFromCache++; } executeSoon(() => { testGenerator.next(); }); } function run_test() {
index 36aded09f5f181fcc38f679a68060d512e6a1842..e1eab1bbca9ca7dd6213e323fd4778039d52455c GIT binary patch literal 4096 zc%1E5e{2(V6o1|7C{VT<TEia%Z)6%wu-<z32Mm+JcEEJVtzEkz6z$rBcHVl+^@<yT zWHPGM(#1cJe=sC47mb?vBcdQNX+Q-MF>0onKSCtVrE`mxWHAe?p6~m;>(&SXVhH}T z*L?52@8^BK@B7}n`|fU=pL!We0l<ty(*RC^F12uL8OBk5|HtL<!Td{=r%a8PDx1TR z7}Kf9yOcl&6AVP7vdXlHj1rGBktkEY#lv*SA<?;L(Sj-?#`v`}C*L{x?Py_4pZaxl z5VdspB|{$^eVzJ-M$e$$c;GU{Hy-$U^exoxNH9#+{W~{-2WT{zA^q9NwgUDulvge^ zmFdM=bZpsTyx4FsbZVn^iB18S)ga7HN(?IH&S+f;%K@H1-G%YGZq__(aO-XK_&Q;3 zzRszNT`KAiOO1rgP*lWBh-aKiD4+%aj+W>^aZid;taa)%7$N`*dhyolcp0Z6N^%h3 z8v~%c^6Pl(Z(ZP=+)eSOCZ3<L&I3sCR!{#07Hlcr-v0&Fw*IeB`_hU0HBD;~vg8j% zB2$sLg7-F^n4HoyAv;l`W0xpq{)=G426mcY{7Cx(LHBS!3Q7ND(m#^)Ta*4=(r-`t zr;>hK_Baxgd_KjG^nPlsIj64Mxq3-_ZTMZ>U31tN%q5pjST#*+4+&a4@6#M>5%i^| zG=~+9n(25&_z;q_$1u>?dc1fvhk%|mZxzB}G}=v`a4SQ?)#DYDJ5*LeYpyw0abVhn zd3tl{G^x{_jt})t*mj=hhb)lFCpZ5zkg^B^0|Q!96<K%MMAqfVx<O)E(q6ke{R-)Z zThN+V!s(+NJK^-Ax!RwPx25?ZL36AkNzrEL8z96Ygd5_vxEqhV@pgSjt!aBAvu|El z!pg?)32!F_enL{EkybMFRv}YsvWz`!q%uWvXeBvJ1Ib~E<d6g1nI+Dp#CaJ=R#}om zDJ1>*ioQ1}J)id4j0RvTDZC?7Gu5w1WcIe>LxJ}vNTN-UXxNf{3lS7j7EELOas4*o zO`6DljuE#eGF#)z(0`tv#0-JbFwnjQ;aXD8?}#B+bF84%7-3fXC*yXFJP-LAK3j1p zgdq`A=g}TuD@aGO@9PEXORy6KBR7`)ThsLTeaIR6j!dicl|axgy3%UoY@`w-sW>*# zXQUCYPw&`v@m#y9^{f@N2N(iBuoSPy$<gp7GU_8KHu0zWQ19L<3#g0J8_a^HR#`QN zy+BHp`;r#TVa7R^8878BwL8w5q4zQ319i>u3-oN&gGFes)f_-}c$A*M90^1NIxI86 zAX!F@Buk>)Ia!16D?1J+j&LQwZXC;(>pnNyn<{l1-<m!1ap0-x!Pi6&!44a~VbmEM zgE%G(j5*S|xclsv_r7+lp7%7P-;o>cDw<m>XLHz&Hue^B%q;c-@#B1D5$^=rc<^CW z5tc_Ak2EYP!pboJ6-BY`M!i=`o(sl;9i3POu~b<C*zEDuxtpCK3GEzgP(-mY(xwEI zZm2J5lmj7S)`DLQy!%PMk#p(Xizedrs7fTds|CCs-d*s7p-%QjBhiQ&2}qGWBK8dt z64@J#$3!DyM2z7hRwvgfVn7ueqA@iP4I+09Vhu%WWl1ywZ)xP$d7BHnTs3W!<hFoB z;$oGhQcP8Xog~lq3U;&welu9>B>LhnlEOFFe(Wnb^@Y)e^SwBFNWbyh<OuD1Lv*+N ztmp}glH?PWHkv}c+@Rk0{>{mql?X&(Hv~XJD})CwgjxX+f}nzoR`(4!J5fu>A<}_b z#phBsJORTOLSGEx7$=Cc82<3Ap`}0@@}kIBDPKlTl(MhiN1-bTL!nWC#tfq+;g16s zIFU!}Ir-0G)Bm3+XYXT|v9Gcx*pJu@dy)Nt{e>N8ud;IW{_6hfH>*!qce`G64Z2Rc uzISC^f4IzC1?S)%<W_RkTodQx0-VC_;SO?r+%c}7dz<^e%bJthlYap1#Gj)8
--- a/toolkit/components/extensions/ExtensionCommon.jsm +++ b/toolkit/components/extensions/ExtensionCommon.jsm @@ -2110,26 +2110,30 @@ class EventManager { // about all primed listeners in the extension's persistentListeners Map. static primeListeners(extension) { EventManager._initPersistentListeners(extension); for (let [module, moduleEntry] of extension.persistentListeners) { let api = extension.apiManager.getAPI(module, extension, "addon_parent"); for (let [event, eventEntry] of moduleEntry) { for (let listener of eventEntry.values()) { - let primed = {pendingEvents: []}; + let primed = {pendingEvents: [], cleared: false}; listener.primed = primed; let bgStartupPromise = new Promise(r => extension.once("startup", r)); let wakeup = () => { extension.emit("background-page-event"); return bgStartupPromise; }; let fireEvent = (...args) => new Promise((resolve, reject) => { + if (primed.cleared) { + reject(new Error("listener not re-registered")); + return; + } primed.pendingEvents.push({args, resolve, reject}); extension.emit("background-page-event"); }); let fire = { wakeup, sync: fireEvent, async: fireEvent, @@ -2172,16 +2176,17 @@ class EventManager { for (let evt of primed.pendingEvents) { evt.reject(new Error("listener not re-registered")); } if (clearPersistent) { EventManager.clearPersistentListener(extension, module, event, key); } primed.unregister(); + primed.cleared = true; } } } } // Record the fact that there is a listener for the given event in // the given extension. `args` is an Array containing any extra // arguments that were passed to addListener().
--- a/toolkit/components/extensions/parent/ext-backgroundPage.js +++ b/toolkit/components/extensions/parent/ext-backgroundPage.js @@ -61,16 +61,23 @@ class BackgroundPage extends HiddenExten if (context) { // Wait until all event listeners registered by the script so far // to be handled. await Promise.all(context.listenerPromises); context.listenerPromises = null; } + if (extension.persistentListeners) { + // |this.extension| may be null if the extension was shut down. + // In that case, we still want to clear the primed listeners, + // but not update the persistent listeners in the startupData. + EventManager.clearPrimedListeners(extension, !!this.extension); + } + extension.emit("startup"); } shutdown() { this.extension._backgroundPageFrameLoader = null; super.shutdown(); } } @@ -108,20 +115,16 @@ this.backgroundPage = class extends Exte extension.once("start-background-page", async () => { if (!this.extension) { // Extension was shut down. Don't build the background page. // Primed listeners have been cleared in onShutdown. return; } await this.build(); - // |this.extension| may be null if the extension was shut down. - // In that case, we still want to clear the primed listeners, - // but not update the persistent listeners in the startupData. - EventManager.clearPrimedListeners(extension, !!this.extension); }); // There are two ways to start the background page: // 1. If a primed event fires, then start the background page as // soon as we have painted a browser window. Note that we have // to touch browserPaintedPromise here to initialize the listener // or else we can miss it if the event occurs after the first // window is painted but before #2
--- a/toolkit/components/extensions/parent/ext-webRequest.js +++ b/toolkit/components/extensions/parent/ext-webRequest.js @@ -21,16 +21,20 @@ function registerEvent(extension, eventN if (filter.windowId != null && browserData.windowId != filter.windowId) { return; } let event = data.serialize(eventName); event.tabId = browserData.tabId; if (data.registerTraceableChannel) { + // If this is a primed listener, no tabParent was passed in here, + // but the convert() callback later in this function will be called + // when the background page is started. Force that to happen here + // after which we'll have a valid tabParent. if (fire.wakeup) { await fire.wakeup(); } data.registerTraceableChannel(extension.policy, tabParent); } return fire.sync(event); };
--- a/toolkit/components/extensions/test/xpcshell/test_ext_persistent_events.js +++ b/toolkit/components/extensions/test/xpcshell/test_ext_persistent_events.js @@ -27,70 +27,72 @@ const SCHEMA = [ // serialized into a string which is later loaded by the WebExtensions // framework in the same context as other extension APIs. By writing it // this way rather than as a big string constant we get lint coverage. // But eslint doesn't understand that this code runs in a different context // where the EventManager class is available so just tell it here: /* global EventManager */ const API = class extends ExtensionAPI { primeListener(extension, event, fire, params) { - let data = {wrappedJSObject: {event, params}}; - Services.obs.notifyObservers(data, "prime-event-listener"); + Services.obs.notifyObservers({event, params}, "prime-event-listener"); const FIRE_TOPIC = `fire-${event}`; - async function listener(subject, topic, _data) { + async function listener(subject, topic, data) { try { - await fire.async(subject.wrappedJSObject); + if (subject.wrappedJSObject.waitForBackground) { + await fire.wakeup(); + } + await fire.async(subject.wrappedJSObject.listenerArgs); } catch (err) { - Services.obs.notifyObservers(data, "listener-callback-exception"); + Services.obs.notifyObservers({event}, "listener-callback-exception"); } } Services.obs.addObserver(listener, FIRE_TOPIC); return { unregister() { - Services.obs.notifyObservers(data, "unregister-primed-listener"); + Services.obs.notifyObservers({event, params}, "unregister-primed-listener"); Services.obs.removeObserver(listener, FIRE_TOPIC); }, convert(_fire) { - Services.obs.notifyObservers(data, "convert-event-listener"); + Services.obs.notifyObservers({event, params}, "convert-event-listener"); fire = _fire; }, }; } getAPI(context) { return { eventtest: { onEvent1: new EventManager({ context, name: "test.event1", persistent: { module: "eventtest", event: "onEvent1", }, register: (fire, ...params) => { - let data = {wrappedJSObject: {event: "onEvent1", params}}; + let data = {event: "onEvent1", params}; Services.obs.notifyObservers(data, "register-event-listener"); return () => { Services.obs.notifyObservers(data, "unregister-event-listener"); }; }, }).api(), onEvent2: new EventManager({ context, name: "test.event1", persistent: { module: "eventtest", event: "onEvent2", }, register: (fire, ...params) => { - let data = {wrappedJSObject: {event: "onEvent2", params}}; + let data = {event: "onEvent2", params}; Services.obs.notifyObservers(data, "register-event-listener"); return () => { Services.obs.notifyObservers(data, "unregister-event-listener"); }; }, }).api(), }, }; @@ -247,24 +249,23 @@ add_task(async function() { info = await p; check(info, "convert"); await extension.awaitMessage("ready"); // Check that when the event is triggered, all the plumbing worked // correctly for the primed-then-converted listener. - let eventDetails = {test: "kaboom"}; - let eventSubject = {wrappedJSObject: eventDetails}; - Services.obs.notifyObservers(eventSubject, "fire-onEvent1"); + let listenerArgs = {test: "kaboom"}; + Services.obs.notifyObservers({listenerArgs}, "fire-onEvent1"); let details = await extension.awaitMessage("listener1"); - deepEqual(details, eventDetails, "Listener 1 fired"); + deepEqual(details, listenerArgs, "Listener 1 fired"); details = await extension.awaitMessage("listener2"); - deepEqual(details, eventDetails, "Listener 2 fired"); + deepEqual(details, listenerArgs, "Listener 2 fired"); // Check that the converted listener is properly unregistered at // browser shutdown. [info] = await Promise.all([ promiseObservable("unregister-primed-listener", 3), AddonTestUtils.promiseShutdownManager(), ]); check(info, "unregister"); @@ -275,24 +276,24 @@ add_task(async function() { AddonTestUtils.promiseStartupManager(), ]); check(info, "prime"); // Check that triggering the event before the listener has been converted // causes the background page to be loaded and the listener to be converted, // and the listener is invoked. p = promiseObservable("convert-event-listener", 3); - eventDetails.test = "startup event"; - Services.obs.notifyObservers(eventSubject, "fire-onEvent2"); + listenerArgs.test = "startup event"; + Services.obs.notifyObservers({listenerArgs}, "fire-onEvent2"); info = await p; check(info, "convert"); details = await extension.awaitMessage("listener3"); - deepEqual(details, eventDetails, "Listener 3 fired for event during startup"); + deepEqual(details, listenerArgs, "Listener 3 fired for event during startup"); await extension.awaitMessage("ready"); // Check that the unregister process works when we manually remove // a listener. p = promiseObservable("unregister-primed-listener", 1); extension.sendMessage("unregister2"); info = await p; @@ -315,20 +316,20 @@ add_task(async function() { // starts up. p = promiseObservable("unregister-primed-listener", 1, () => extension.awaitMessage("ready")); Services.obs.notifyObservers(null, "sessionstore-windows-restored"); info = await p; check(info, "unregister", {listener1: false, listener3: false}); // Just listener1 should be registered now, fire event1 to confirm. - eventDetails.test = "third time"; - Services.obs.notifyObservers(eventSubject, "fire-onEvent1"); + listenerArgs.test = "third time"; + Services.obs.notifyObservers({listenerArgs}, "fire-onEvent1"); details = await extension.awaitMessage("listener1"); - deepEqual(details, eventDetails, "Listener 1 fired"); + deepEqual(details, listenerArgs, "Listener 1 fired"); // Tell the extension not to re-register listener1 on the next startup extension.sendMessage("unregister1"); await extension.awaitMessage("unregistered"); // Shut down, start up info = await promiseObservable("unregister-primed-listener", 1, () => AddonTestUtils.promiseShutdownManager()); @@ -336,17 +337,17 @@ add_task(async function() { info = await promiseObservable("prime-event-listener", 1, () => AddonTestUtils.promiseStartupManager()); check(info, "register", {listener2: false, listener3: false}); // Check that firing event1 causes the listener fire callback to // reject. p = promiseObservable("listener-callback-exception", 1); - Services.obs.notifyObservers(eventSubject, "fire-onEvent1"); + Services.obs.notifyObservers({listenerArgs, waitForBackground: true}, "fire-onEvent1"); await p; ok(true, "Primed listener that was not re-registered received an error when event was triggered during startup"); await extension.awaitMessage("ready"); await extension.unload(); await AddonTestUtils.promiseShutdownManager();
--- a/toolkit/components/extensions/test/xpcshell/test_ext_webRequest_startup.js +++ b/toolkit/components/extensions/test/xpcshell/test_ext_webRequest_startup.js @@ -118,17 +118,17 @@ add_task(async function test_2() { Services.obs.notifyObservers(null, "sessionstore-windows-restored"); await extension.awaitMessage("ready"); await extension.unload(); await promiseShutdownManager(); }); -// Test that a block listener that uses filterResponseData() works +// Test that a blocking listener that uses filterResponseData() works // properly (i.e., that the delayed call to registerTraceableChannel // works properly). add_task(async function test_3() { const DATA = `<!DOCTYPE html> <html> <body> <h1>This is a modified page</h1> </body>
--- a/toolkit/content/aboutProfiles.js +++ b/toolkit/content/aboutProfiles.js @@ -12,17 +12,17 @@ XPCOMUtils.defineLazyServiceGetter( "ProfileService", "@mozilla.org/toolkit/profile-service;1", "nsIToolkitProfileService" ); async function flush() { try { ProfileService.flush(); - refreshUI(); + rebuildProfileList(); } catch (e) { let [title, msg, button] = await document.l10n.formatValues([ { id: "profiles-flush-fail-title" }, { id: e.result == Cr.NS_ERROR_DATABASE_CHANGED ? "profiles-flush-conflict" : "profiles-flush-failed" }, { id: "profiles-flush-restart-button" }, ]); @@ -33,17 +33,17 @@ async function flush() { (PS.BUTTON_POS_1 * PS.BUTTON_TITLE_IS_STRING), null, button, null, null, {}); if (result == 1) { restart(false); } } } -function refreshUI() { +function rebuildProfileList() { let parent = document.getElementById("profiles"); while (parent.firstChild) { parent.firstChild.remove(); } let defaultProfile; try { defaultProfile = ProfileService.defaultProfile; @@ -67,29 +67,16 @@ function refreshUI() { } display({ profile, isDefault: profile == defaultProfile, isCurrentProfile, isInUse, }); } - - let createButton = document.getElementById("create-button"); - createButton.onclick = createProfileWizard; - - let restartSafeModeButton = document.getElementById("restart-in-safe-mode-button"); - if (!Services.policies || Services.policies.isAllowed("safeMode")) { - restartSafeModeButton.onclick = function() { restart(true); }; - } else { - restartSafeModeButton.setAttribute("disabled", "true"); - } - - let restartNormalModeButton = document.getElementById("restart-button"); - restartNormalModeButton.onclick = function() { restart(false); }; } function display(profileData) { let parent = document.getElementById("profiles"); let div = document.createElement("div"); parent.appendChild(div); @@ -347,15 +334,28 @@ function restart(safeMode) { if (safeMode) { Services.startup.restartInSafeMode(flags); } else { Services.startup.quit(flags); } } window.addEventListener("DOMContentLoaded", function() { + let createButton = document.getElementById("create-button"); + createButton.addEventListener("click", createProfileWizard); + + let restartSafeModeButton = document.getElementById("restart-in-safe-mode-button"); + if (!Services.policies || Services.policies.isAllowed("safeMode")) { + restartSafeModeButton.addEventListener("click", () => { restart(true); }); + } else { + restartSafeModeButton.setAttribute("disabled", "true"); + } + + let restartNormalModeButton = document.getElementById("restart-button"); + restartNormalModeButton.addEventListener("click", () => { restart(false); }); + if (ProfileService.isListOutdated) { document.getElementById("owned").hidden = true; } else { document.getElementById("conflict").hidden = true; - refreshUI(); + rebuildProfileList(); } }, {once: true});