author | Razvan Maries <rmaries@mozilla.com> |
Thu, 20 Jun 2019 12:45:34 +0300 | |
changeset 479292 | a440f0629814ea638bdbee6cf2f1a0425dd04c61 |
parent 479232 | 9767f3d2f79f7dd37136b1034ab1229ce5c4354b (current diff) |
parent 479291 | ef0e2b8429328d53cefee4ce64ab6b3cd2df937c (diff) |
child 479339 | 74b3b3d1544c71eecda9de902a09573f6329cb0d |
child 479692 | 20956a783e086e85f5cd8f394020bda54cc17368 |
push id | 36177 |
push user | rmaries@mozilla.com |
push date | Thu, 20 Jun 2019 09:46:31 +0000 |
treeherder | mozilla-central@a440f0629814 [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
reviewers | merge |
milestone | 69.0a1 |
first release with | nightly linux32
a440f0629814
/
69.0a1
/
20190620094631
/
files
nightly linux64
a440f0629814
/
69.0a1
/
20190620094631
/
files
nightly mac
a440f0629814
/
69.0a1
/
20190620094631
/
files
nightly win32
a440f0629814
/
69.0a1
/
20190620094631
/
files
nightly win64
a440f0629814
/
69.0a1
/
20190620094631
/
files
|
last release without | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
releases | nightly linux32
69.0a1
/
20190620094631
/
pushlog to previous
nightly linux64
69.0a1
/
20190620094631
/
pushlog to previous
nightly mac
69.0a1
/
20190620094631
/
pushlog to previous
nightly win32
69.0a1
/
20190620094631
/
pushlog to previous
nightly win64
69.0a1
/
20190620094631
/
pushlog to previous
|
testing/web-platform/meta/css/css-backgrounds/background-image-first-line.html.ini | file | annotate | diff | comparison | revisions | |
testing/web-platform/meta/webrtc/RTCDataChannel-id.html.ini | file | annotate | diff | comparison | revisions |
--- a/browser/app/profile/firefox.js +++ b/browser/app/profile/firefox.js @@ -1207,17 +1207,16 @@ pref("services.sync.prefs.sync.browser.n pref("services.sync.prefs.sync.browser.newtabpage.pinned", true); pref("services.sync.prefs.sync.browser.offline-apps.notify", true); pref("services.sync.prefs.sync.browser.safebrowsing.phishing.enabled", true); pref("services.sync.prefs.sync.browser.safebrowsing.malware.enabled", true); pref("services.sync.prefs.sync.browser.safebrowsing.downloads.enabled", true); pref("services.sync.prefs.sync.browser.safebrowsing.passwords.enabled", true); pref("services.sync.prefs.sync.browser.search.update", true); pref("services.sync.prefs.sync.browser.search.widget.inNavBar", true); -pref("services.sync.prefs.sync.browser.sessionstore.restore_on_demand", true); pref("services.sync.prefs.sync.browser.startup.homepage", true); pref("services.sync.prefs.sync.browser.startup.page", true); pref("services.sync.prefs.sync.browser.tabs.loadInBackground", true); pref("services.sync.prefs.sync.browser.tabs.warnOnClose", true); pref("services.sync.prefs.sync.browser.tabs.warnOnOpen", true); pref("services.sync.prefs.sync.browser.taskbar.previews.enable", true); pref("services.sync.prefs.sync.browser.urlbar.matchBuckets", true); pref("services.sync.prefs.sync.browser.urlbar.maxRichResults", true);
--- a/browser/base/content/browser.js +++ b/browser/base/content/browser.js @@ -1941,16 +1941,20 @@ var gBrowserInit = { // auto-resume downloads begin (such as after crashing or quitting with // active downloads) and speeds up the first-load of the download manager UI. // If the user manually opens the download manager before the timeout, the // downloads will start right away, and initializing again won't hurt. try { DownloadsCommon.initializeAllDataLinks(); ChromeUtils.import("resource:///modules/DownloadsTaskbar.jsm", {}) .DownloadsTaskbar.registerIndicator(window); + if (AppConstants.platform == "macosx") { + ChromeUtils.import("resource:///modules/DownloadsMacFinderProgress.jsm") + .DownloadsMacFinderProgress.register(); + } } catch (ex) { Cu.reportError(ex); } }, {timeout: 10000}); if (Win7Features) { scheduleIdleTask(() => Win7Features.onOpenWindow()); }
--- a/browser/components/about/AboutProtectionsHandler.jsm +++ b/browser/components/about/AboutProtectionsHandler.jsm @@ -6,17 +6,17 @@ var EXPORTED_SYMBOLS = ["AboutProtectionsHandler"]; const {RemotePages} = ChromeUtils.import("resource://gre/modules/remotepagemanager/RemotePageManagerParent.jsm"); var AboutProtectionsHandler = { _inited: false, _topics: [ - "ArrivedOnPage", + "openContentBlockingPreferences", ], init() { this.pageListener = new RemotePages("about:protections"); for (let topic of this._topics) { this.pageListener.addMessageListener(topic, this.receiveMessage); } this._inited = true; @@ -28,16 +28,16 @@ var AboutProtectionsHandler = { } for (let topic of this._topics) { this.pageListener.removeMessageListener(topic, this.receiveMessage); } this.pageListener.destroy(); }, receiveMessage(aMessage) { + let win = aMessage.target.browser.ownerGlobal; switch (aMessage.name) { - case "ArrivedOnPage": { - // Successfully recieved a message + case "openContentBlockingPreferences": + win.openPreferences("privacy-trackingprotection", {origin: "about-protections"}); break; - } } }, };
new file mode 100644 --- /dev/null +++ b/browser/components/downloads/DownloadsMacFinderProgress.jsm @@ -0,0 +1,87 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ts=2 et sw=2 tw=80 filetype=javascript: */ +/* 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/. */ + +/** + * Handles the download progress indicator of the macOS Finder. + */ + +"use strict"; + +var EXPORTED_SYMBOLS = [ + "DownloadsMacFinderProgress", +]; + +const {XPCOMUtils} = ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm"); + +XPCOMUtils.defineLazyModuleGetters(this, { + Downloads: "resource://gre/modules/Downloads.jsm", +}); + +var DownloadsMacFinderProgress = { + /** + * Maps the path of the download, to the according progress indicator instance. + */ + _finderProgresses: null, + + /** + * This method is called after a new browser window on macOS is opened, it + * registers for receiving download events for the progressbar of the Finder. + */ + register() { + // Ensure to register only once per process and not for every window. + if (!this._finderProgresses) { + this._finderProgresses = new Map(); + Downloads.getList(Downloads.ALL).then(list => list.addView(this)); + } + }, + + onDownloadAdded(download) { + if (download.stopped) { + return; + } + + let finderProgress = Cc["@mozilla.org/widget/macfinderprogress;1"] + .createInstance(Ci.nsIMacFinderProgress); + + let path = download.target.path; + + finderProgress.init(path, () => { + download.cancel().catch(Cu.reportError); + download.removePartialData().catch(Cu.reportError); + }); + + if (download.hasProgress) { + finderProgress.updateProgress(download.currentBytes, download.totalBytes); + } else { + finderProgress.updateProgress(0, 0); + } + this._finderProgresses.set(path, finderProgress); + }, + + onDownloadChanged(download) { + let path = download.target.path; + let finderProgress = this._finderProgresses.get(path); + if (!finderProgress) { + // The download is not tracked, it may have been restarted, + // thus forward the call to onDownloadAdded to check if it should be tracked. + this.onDownloadAdded(download); + } else if (download.stopped) { + finderProgress.end(); + this._finderProgresses.delete(path); + } else { + finderProgress.updateProgress(download.currentBytes, download.totalBytes); + } + }, + + onDownloadRemoved(download) { + let path = download.target.path; + let finderProgress = this._finderProgresses.get(path); + if (finderProgress) { + finderProgress.end(); + this._finderProgresses.delete(path); + } + }, +};
--- a/browser/components/downloads/moz.build +++ b/browser/components/downloads/moz.build @@ -13,10 +13,15 @@ JAR_MANIFESTS += ['jar.mn'] EXTRA_JS_MODULES += [ 'DownloadsCommon.jsm', 'DownloadsSubview.jsm', 'DownloadsTaskbar.jsm', 'DownloadsViewUI.jsm', ] +toolkit = CONFIG['MOZ_WIDGET_TOOLKIT'] + +if toolkit == 'cocoa': + EXTRA_JS_MODULES += ['DownloadsMacFinderProgress.jsm'] + with Files('**'): BUG_COMPONENT = ('Firefox', 'Downloads Panel')
--- a/browser/components/preferences/in-content/main.js +++ b/browser/components/preferences/in-content/main.js @@ -102,17 +102,16 @@ Preferences.addAll([ browser.taskbar.previews.enable - true if tabs are to be shown in the Windows 7 taskbar */ { id: "browser.link.open_newwindow", type: "int" }, { id: "browser.tabs.loadInBackground", type: "bool", inverted: true }, { id: "browser.tabs.warnOnClose", type: "bool" }, { id: "browser.tabs.warnOnOpen", type: "bool" }, - { id: "browser.sessionstore.restore_on_demand", type: "bool" }, { id: "browser.ctrlTab.recentlyUsedOrder", type: "bool" }, // CFR {id: "browser.newtabpage.activity-stream.asrouter.userprefs.cfr.addons", type: "bool"}, {id: "browser.newtabpage.activity-stream.asrouter.userprefs.cfr.features", type: "bool"}, // Fonts { id: "font.language.group", type: "wstring" },
--- a/browser/components/protections/content/protections.css +++ b/browser/components/protections/content/protections.css @@ -1,12 +1,94 @@ /* 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/. */ -.under-construction { - background-image: url("chrome://browser/content/illustrations/under-construction.svg"); - background-repeat: no-repeat; - background-position: center; - min-height: 300px; - min-width: 300px; - /* Move the image down a bit - should be slightly higher than halfway down the page */ - margin-top: -10%; + :root { + --card-box-shadow: 0 1px 4px 0 rgba(12,12,13,0.1), 0 1px 0 0 rgba(0,0,0,0.05); + --card-background: #FFF; + --clickable-text-hover: hsla(0,0%,70%,.2); + --clickable-text-active: hsla(0,0%,70%,.3); + --card-divider: rgba(12,12,13,0.1) 1px solid; + --report-background: #FAFAFC; + --card-padding: 22px; + } + +body { + background-color: var(--report-background); + font: message-box; + margin-top: 82px; +} + +#report-title { + font-size: 20px; + font-weight: 300; + margin-bottom: 22px; +} + +#report-content { + width: 763px; + margin: 0 auto; +} + +.etp-card .icon { + width: 60px; + height: 60px; + background: url("chrome://browser/skin/controlcenter/tracking-protection.svg") no-repeat center/cover; + grid-column: 1; + margin: 0 auto; +} + +.report-card { + display: grid; + grid-template-columns: 100%; + grid-template-rows: 20% auto; + border-radius: 3px; + background-color: var(--card-background); + box-shadow: var(--card-box-shadow); } + +.report-card .card-header { + display: grid; + grid-template-columns: 1fr 7fr; + padding: var(--card-padding); + grid-gap: var(--card-padding); +} + +.report-card .card-title { + font-size: 16px; + line-height: 22px; + margin: 0; + cursor: default; +} + +.report-card .content { + margin-bottom: 24px; + margin-top: 5px; + font-size: 14px; + cursor: default; +} + +#protection-details { + background: url("chrome://browser/skin/settings.svg") no-repeat 3px 3px; + padding: 4px 4px 4px 24px; + font-size: 12px; + display: inline; + cursor: default; +} + +#protection-details:hover { + background-color: var(--clickable-text-hover); +} + +#protection-details:hover:active { + background-color: var(--clickable-text-active); +} + +#protection-details span { + font-weight: bold; +} + +.card-body { + border-top: var(--card-divider); + grid-column: span 2; + grid-row: 2; + margin-bottom: 24px; +}
--- a/browser/components/protections/content/protections.html +++ b/browser/components/protections/content/protections.html @@ -2,21 +2,39 @@ - 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/. --> <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta http-equiv="Content-Security-Policy" content="default-src chrome: blob:"> - <link rel="stylesheet" media="screen, projection" type="text/css" - href="chrome://global/skin/in-content/info-pages.css" title="infop"> <link rel="stylesheet" type="text/css" href="chrome://browser/content/protections.css"> <link rel="icon" href="chrome://global/skin/icons/warning.svg"> <script type="text/javascript" src="chrome://browser/content/protections.js"></script> <title>Protection Report</title> </head> <body> - <div class="under-construction"><div/> + <div id="report-content"> + <h2 id="report-title">Privacy Protections</h2> + <div class="report-card etp-card"> + <div class="card-header"> + <div class="icon"></div> + <div class="wrapper"> + <h3 class="card-title"> + Enhanced Tracking Protection + </h3> + <p class="content"> + Trackers follow you around online to collect information about your browsing habits and interests. Firefox blocks many of these trackers and other malicious scripts. + </p> + <p id="protection-details"> + Protection Level is set to <span>Standard</span> + </p> + </div> + </div> + <div class="card-body"> + </div> + </div> + </div> </body> </html>
--- a/browser/components/protections/content/protections.js +++ b/browser/components/protections/content/protections.js @@ -1,9 +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/. */ /* eslint-env mozilla/frame-script */ document.addEventListener("DOMContentLoaded", (e) => { - RPMSendAsyncMessage("ArrivedOnPage"); + let protectionDetails = document.getElementById("protection-details"); + protectionDetails.addEventListener("click", () => { + RPMSendAsyncMessage("openContentBlockingPreferences"); + }); });
--- a/browser/modules/RemotePrompt.jsm +++ b/browser/modules/RemotePrompt.jsm @@ -12,20 +12,24 @@ ChromeUtils.defineModuleGetter(this, "Pr ChromeUtils.defineModuleGetter(this, "Services", "resource://gre/modules/Services.jsm"); var RemotePrompt = { // Listeners are added in BrowserGlue.jsm receiveMessage(message) { switch (message.name) { case "Prompt:Open": - if (message.data.uri) { - this.openModalWindow(message.data, message.target); + const COMMON_DIALOG = "chrome://global/content/commonDialog.xul"; + const SELECT_DIALOG = "chrome://global/content/selectDialog.xul"; + + if (message.data.tabPrompt) { + this.openTabPrompt(message.data, message.target); } else { - this.openTabPrompt(message.data, message.target); + let uri = (message.data.promptType == "select") ? SELECT_DIALOG : COMMON_DIALOG; + this.openModalWindow(uri, message.data, message.target); } break; } }, openTabPrompt(args, browser) { let window = browser.ownerGlobal; let tabPrompt = window.gBrowser.getTabModalPromptBox(browser); @@ -80,23 +84,23 @@ var RemotePrompt = { // there's other stuff in nsWindowWatcher::OpenWindowInternal // that we might need to do here as well. } catch (ex) { Cu.reportError(ex); onPromptClose(true); } }, - openModalWindow(args, browser) { + openModalWindow(uri, args, browser) { let window = browser.ownerGlobal; try { PromptUtils.fireDialogEvent(window, "DOMWillOpenModalDialog", browser); let bag = PromptUtils.objectToPropBag(args); - Services.ww.openWindow(window, args.uri, "_blank", + Services.ww.openWindow(window, uri, "_blank", "centerscreen,chrome,modal,titlebar", bag); PromptUtils.propBagToObject(bag, args); } finally { PromptUtils.fireDialogEvent(window, "DOMModalDialogClosed", browser); browser.messageManager.sendAsyncMessage("Prompt:Close", args); } },
--- a/browser/themes/shared/customizableui/panelUI.inc.css +++ b/browser/themes/shared/customizableui/panelUI.inc.css @@ -1117,17 +1117,17 @@ panelview .toolbarbutton-1, -moz-context-properties: fill, fill-opacity; content: url(chrome://browser/skin/back-12.svg); fill: currentColor; fill-opacity: 0.6; float: right; transform: translateY(1px); } -#main-window:not([customizing]) .subviewbutton-nav[disabled=true]::after { +:root:not([customizing]) .subviewbutton-nav[disabled=true]::after { opacity: 0.4; } .PanelUI-subView .subviewbutton-nav:-moz-locale-dir(ltr)::after { transform: scaleX(-1) translateY(1px); } .subviewbutton[shortcut]::after,
--- a/devtools/client/debugger/src/actions/event-listeners.js +++ b/devtools/client/debugger/src/actions/event-listeners.js @@ -19,17 +19,17 @@ async function updateBreakpoints(dispatc dispatch({ type: "UPDATE_EVENT_LISTENERS", active: newEvents }); const current = await asyncStore.eventListenerBreakpoints; asyncStore.eventListenerBreakpoints = { ...current, active: newEvents, }; - client.setEventListenerBreakpoints(newEvents); + await client.setEventListenerBreakpoints(newEvents); } async function updateExpanded(dispatch, newExpanded: string[]) { dispatch({ type: "UPDATE_EVENT_LISTENER_EXPANDED", expanded: newExpanded, }); @@ -41,30 +41,30 @@ async function updateExpanded(dispatch, } export function addEventListenerBreakpoints(eventsToAdd: string[]) { return async ({ dispatch, client, getState }: ThunkArgs) => { const activeListenerBreakpoints = await getActiveEventListeners(getState()); const newEvents = uniq([...eventsToAdd, ...activeListenerBreakpoints]); - updateBreakpoints(dispatch, client, newEvents); + await updateBreakpoints(dispatch, client, newEvents); }; } export function removeEventListenerBreakpoints(eventsToRemove: string[]) { return async ({ dispatch, client, getState }: ThunkArgs) => { const activeListenerBreakpoints = await getActiveEventListeners(getState()); const newEvents = remove( activeListenerBreakpoints, event => !eventsToRemove.includes(event) ); - updateBreakpoints(dispatch, client, newEvents); + await updateBreakpoints(dispatch, client, newEvents); }; } export function addEventListenerExpanded(category: string) { return async ({ dispatch, getState }: ThunkArgs) => { const expanded = await getEventListenerExpanded(getState()); const newExpanded = uniq([...expanded, category]);
--- a/devtools/client/debugger/src/client/firefox.js +++ b/devtools/client/debugger/src/client/firefox.js @@ -43,17 +43,21 @@ export async function onConnect(connecti await threadClient.reconfigure({ observeAsmJS: true, pauseWorkersUntilAttach: true, wasmBinarySource: supportsWasm, skipBreakpoints: prefs.skipPausing, }); // Retrieve possible event listener breakpoints - actions.getEventListenerBreakpointTypes(); + actions.getEventListenerBreakpointTypes().catch(e => console.error(e)); + + // Initialize the event breakpoints on the thread up front so that + // they are active once attached. + actions.addEventListenerBreakpoints([]).catch(e => console.error(e)); // In Firefox, we need to initially request all of the sources. This // usually fires off individual `newSource` notifications as the // debugger finds them, but there may be existing sources already in // the debugger (if it's paused already, or if loading the page from // bfcache) so explicity fire `newSource` events for all returned // sources. const traits = tabTarget.traits;
--- a/devtools/client/debugger/src/client/firefox/commands.js +++ b/devtools/client/debugger/src/client/firefox/commands.js @@ -30,24 +30,28 @@ import type { TabTarget, DebuggerClient, Grip, ThreadClient, ObjectClient, SourcesPacket, } from "./types"; -import type { EventListenerCategoryList } from "../../actions/types"; +import type { + EventListenerCategoryList, + EventListenerActiveList, +} from "../../actions/types"; let workerClients: Object; let threadClient: ThreadClient; let tabTarget: TabTarget; let debuggerClient: DebuggerClient; let sourceActors: { [ActorId]: SourceId }; let breakpoints: { [string]: Object }; +let eventBreakpoints: ?EventListenerActiveList; let supportsWasm: boolean; let shouldWaitForWorkers = false; type Dependencies = { threadClient: ThreadClient, tabTarget: TabTarget, debuggerClient: DebuggerClient, @@ -358,26 +362,27 @@ async function setSkipPausing(shouldSkip await forEachWorkerThread(thread => thread.skipBreakpoints(shouldSkip)); } function interrupt(thread: string): Promise<*> { return lookupThreadClient(thread).interrupt(); } async function setEventListenerBreakpoints(ids: string[]) { + eventBreakpoints = ids; + await threadClient.setActiveEventBreakpoints(ids); await forEachWorkerThread(thread => thread.setActiveEventBreakpoints(ids)); } // eslint-disable-next-line async function getEventListenerBreakpointTypes(): Promise< EventListenerCategoryList > { - const { value } = await threadClient.getAvailableEventBreakpoints(); - return value; + return threadClient.getAvailableEventBreakpoints(); } function pauseGrip(thread: string, func: Function): ObjectClient { return lookupThreadClient(thread).pauseGrip(func); } function registerSourceActor(sourceActorId: string, sourceId: SourceId) { sourceActors[sourceActorId] = sourceId; @@ -401,16 +406,17 @@ function getSourceForActor(actor: ActorI } return sourceActors[actor]; } async function fetchWorkers(): Promise<Worker[]> { if (features.windowlessWorkers) { const options = { breakpoints, + eventBreakpoints, observeAsmJS: true, }; const newWorkerClients = await updateWorkerClients({ tabTarget, debuggerClient, threadClient, workerClients,
--- a/devtools/client/debugger/src/client/firefox/types.js +++ b/devtools/client/debugger/src/client/firefox/types.js @@ -363,19 +363,17 @@ export type ThreadClient = { reconfigure: ({ observeAsmJS: boolean }) => Promise<*>, getLastPausePacket: () => ?PausedPacket, _parent: TabClient, actor: ActorId, actorID: ActorId, request: (payload: Object) => Promise<*>, url: string, setActiveEventBreakpoints: (string[]) => void, - getAvailableEventBreakpoints: () => Promise<{| - value: EventListenerCategoryList, - |}>, + getAvailableEventBreakpoints: () => Promise<EventListenerCategoryList>, skipBreakpoints: boolean => Promise<{| skip: boolean |}>, }; export type Panel = {| emit: (eventName: string) => void, openLink: (url: string) => void, openWorkerToolbox: (worker: Worker) => void, openElementInInspector: (grip: Object) => void,
--- a/devtools/client/debugger/src/components/SecondaryPanes/EventListeners.js +++ b/devtools/client/debugger/src/components/SecondaryPanes/EventListeners.js @@ -86,17 +86,24 @@ class EventListeners extends Component<P onClick={() => this.onCategoryToggle(category.name)} > <AccessibleImage className={classnames("arrow", { expanded })} /> </button> <label className="event-listener-label"> <input type="checkbox" value={category.name} - onChange={e => this.onCategoryClick(category, e.target.checked)} + onChange={e => { + this.onCategoryClick( + category, + // Clicking an indeterminate checkbox should always have the + // effect of disabling any selected items. + indeterminate ? false : e.target.checked + ); + }} checked={checked} ref={el => el && (el.indeterminate = indeterminate)} /> <span className="event-listener-category">{category.name}</span> </label> </div> ); }
--- a/devtools/client/debugger/src/reducers/event-listeners.js +++ b/devtools/client/debugger/src/reducers/event-listeners.js @@ -41,24 +41,24 @@ function update( return { ...state, expanded: action.expanded }; default: return state; } } export function getActiveEventListeners(state: State): EventListenerActiveList { - return state.eventListenerBreakpoints.active; + return state.eventListenerBreakpoints.active || []; } export function getEventListenerBreakpointTypes( state: State ): EventListenerCategoryList { - return state.eventListenerBreakpoints.categories; + return state.eventListenerBreakpoints.categories || []; } export function getEventListenerExpanded( state: State ): EventListenerExpandedList { - return state.eventListenerBreakpoints.expanded; + return state.eventListenerBreakpoints.expanded || []; } export default update;
--- a/devtools/client/debugger/src/utils/pause/why.js +++ b/devtools/client/debugger/src/utils/pause/why.js @@ -11,16 +11,17 @@ import type { Why } from "../../types"; // "breakpointConditionThrown", "clientEvaluated" // "interrupted", "attached" const reasons = { debuggerStatement: "whyPaused.debuggerStatement", breakpoint: "whyPaused.breakpoint", exception: "whyPaused.exception", resumeLimit: "whyPaused.resumeLimit", breakpointConditionThrown: "whyPaused.breakpointConditionThrown", + eventBreakpoint: "whyPaused.eventBreakpoint", // V8 DOM: "whyPaused.breakpoint", EventListener: "whyPaused.pauseOnDOMEvents", XHR: "whyPaused.xhr", promiseRejection: "whyPaused.promiseRejection", assert: "whyPaused.assert", debugCommand: "whyPaused.debugCommand",
--- a/devtools/client/debugger/test/mochitest/browser.ini +++ b/devtools/client/debugger/test/mochitest/browser.ini @@ -588,16 +588,18 @@ support-files = examples/wasm-sourcemaps/fib.c examples/wasm-sourcemaps/fib.wasm examples/wasm-sourcemaps/fib.wasm.map examples/wasm-sourcemaps/utils.js examples/doc-html-breakpoints.html examples/html-breakpoints-slow.js examples/sjs_slow-load.sjs examples/fetch.js + examples/doc-event-breakpoints.html + examples/event-breakpoints.js examples/doc-xhr.html examples/doc-xhr-run-to-completion.html examples/doc-scroll-run-to-completion.html examples/pretty.js examples/sum/sum.js examples/sum/sum.min.js examples/sum/sum.min.js.map examples/big-sourcemap_files/bundle.js @@ -801,14 +803,15 @@ skip-if = os == "win" skip-if = os == "win" [browser_dbg-wasm-sourcemaps.js] skip-if = true [browser_dbg-windowless-workers.js] [browser_dbg-windowless-workers-early-breakpoint.js] [browser_dbg-worker-scopes.js] skip-if = (os == 'linux' && debug) || ccov #Bug 1456013 [browser_dbg-event-handler.js] +[browser_dbg-event-breakpoints.js] [browser_dbg-eval-throw.js] [browser_dbg-sourceURL-breakpoint.js] [browser_dbg-old-breakpoint.js] [browser_dbg-idb-run-to-completion.js] [browser_dbg-scopes-xrays.js] [browser_dbg-message-run-to-completion.js]
new file mode 100644 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg-event-breakpoints.js @@ -0,0 +1,48 @@ +/* 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/>. */ + +function assertPauseLocation(dbg, line) { + const { location } = dbg.selectors.getVisibleSelectedFrame(); + + const source = findSource(dbg, "event-breakpoints.js"); + + is(location.sourceId, source.id, `correct sourceId`); + is(location.line, line, `correct line`); + + assertPausedLocation(dbg); +} + +add_task(async function() { + await pushPref("devtools.debugger.features.event-listeners-breakpoints", true); + + const dbg = await initDebugger("doc-event-breakpoints.html", "event-breakpoints"); + await selectSource(dbg, "event-breakpoints"); + await waitForSelectedSource(dbg, "event-breakpoints"); + + await dbg.actions.addEventListenerBreakpoints([ + "event.mouse.click", + "event.xhr.load", + "timer.timeout.set", + "timer.timeout.fire", + ]); + + invokeInTab("clickHandler"); + await waitForPaused(dbg); + assertPauseLocation(dbg, 12); + await resume(dbg); + + invokeInTab("xhrHandler"); + await waitForPaused(dbg); + assertPauseLocation(dbg, 20); + await resume(dbg); + + invokeInTab("timerHandler"); + await waitForPaused(dbg); + assertPauseLocation(dbg, 27); + await resume(dbg); + + await waitForPaused(dbg); + assertPauseLocation(dbg, 29); + await resume(dbg); +}); \ No newline at end of file
new file mode 100644 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/examples/doc-event-breakpoints.html @@ -0,0 +1,20 @@ +<!-- 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/. --> +<!DOCTYPE html> +<html> + <head> + <meta charset="utf-8"/> + <title>Debugger event bp test page</title> + </head> + + <body> + <button id="click-button">Run Click Handler</button> + <button id="xhr-button">Run XHR Handler</button> + <button id="timer-button">Run Timer Handler</button> + <div id="click-target" style="margin: 50px; background-color: green;"> + Click Target + </div> + <script src="event-breakpoints.js"></script> + </body> +</html>
new file mode 100644 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/examples/event-breakpoints.js @@ -0,0 +1,34 @@ +/* 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/>. */ + +document.getElementById("click-button").onmousedown = clickHandler; +function clickHandler() { + document.getElementById("click-target").click(); +} + +document.getElementById("click-target").onclick = clickTargetClicked; +function clickTargetClicked() { + console.log("clicked"); +} + +document.getElementById("xhr-button").onmousedown = xhrHandler; +function xhrHandler() { + var xhr = new XMLHttpRequest(); + xhr.open("GET", "doc-event-breakpoints.html", true); + xhr.onload = function(){ + console.log("xhr load"); + }; + xhr.send(); +} + +document.getElementById("timer-button").onmousedown = timerHandler; +function timerHandler() { + setTimeout( + () => { + console.log("timer callback"); + }, + 50 + ); + console.log("timer set"); +} \ No newline at end of file
--- a/devtools/client/locales/en-US/debugger.properties +++ b/devtools/client/locales/en-US/debugger.properties @@ -978,16 +978,21 @@ experimental=This is an experimental fea # in a info block explaining how the debugger is currently paused due to a `debugger` # statement in the code whyPaused.debuggerStatement=Paused on debugger statement # LOCALIZATION NOTE (whyPaused.breakpoint): The text that is displayed # in a info block explaining how the debugger is currently paused on a breakpoint whyPaused.breakpoint=Paused on breakpoint +# LOCALIZATION NOTE (whyPaused.eventBreakpoint): The text that is displayed +# in a info block explaining how the debugger is currently paused on an event +# breakpoint. +whyPaused.eventBreakpoint=Paused on event breakpoint + # LOCALIZATION NOTE (whyPaused.exception): The text that is displayed # in a info block explaining how the debugger is currently paused on an exception whyPaused.exception=Paused on exception # LOCALIZATION NOTE (whyPaused.resumeLimit): The text that is displayed # in a info block explaining how the debugger is currently paused while stepping # in or out of the stack whyPaused.resumeLimit=Paused while stepping
--- a/devtools/client/locales/en-US/netmonitor.properties +++ b/devtools/client/locales/en-US/netmonitor.properties @@ -61,16 +61,20 @@ collapseDetailsPane=Hide request details # LOCALIZATION NOTE (headersEmptyText): This is the text displayed in the # headers tab of the network details pane when there are no headers available. headersEmptyText=No headers for this request # LOCALIZATION NOTE (headersFilterText): This is the text displayed in the # headers tab of the network details pane for the filtering input. headersFilterText=Filter headers +# LOCALIZATION NOTE (webSocketsEmptyText): This is the text displayed in the +# WebSockets tab of the network details pane when there are no frames available. +webSocketsEmptyText=No WebSocket frames for this request + # LOCALIZATION NOTE (cookiesEmptyText): This is the text displayed in the # cookies tab of the network details pane when there are no cookies available. cookiesEmptyText=No cookies for this request # LOCALIZATION NOTE (cookiesFilterText): This is the text displayed in the # cookies tab of the network details pane for the filtering input. cookiesFilterText=Filter cookies @@ -632,16 +636,20 @@ netmonitor.toolbar.contentSize=Size # LOCALIZATION NOTE (netmonitor.toolbar.waterfall): This is the label displayed # in the network table toolbar, above the "waterfall" column. netmonitor.toolbar.waterfall=Timeline # LOCALIZATION NOTE (netmonitor.tab.headers): This is the label displayed # in the network details pane identifying the headers tab. netmonitor.tab.headers=Headers +# LOCALIZATION NOTE (netmonitor.tab.webSockets): This is the label displayed +# in the network details pane identifying the webSockets tab. +netmonitor.tab.webSockets=WebSockets + # LOCALIZATION NOTE (netmonitor.tab.cookies): This is the label displayed # in the network details pane identifying the cookies tab. netmonitor.tab.cookies=Cookies # LOCALIZATION NOTE (netmonitor.tab.cache): This is the label displayed # in the network details pane identifying the cache tab. netmonitor.tab.cache=Cache
--- a/devtools/client/netmonitor/src/actions/index.js +++ b/devtools/client/netmonitor/src/actions/index.js @@ -6,18 +6,20 @@ const batching = require("./batching"); const filters = require("./filters"); const requests = require("./requests"); const selection = require("./selection"); const sort = require("./sort"); const timingMarkers = require("./timing-markers"); const ui = require("./ui"); +const webSockets = require("./web-sockets"); Object.assign(exports, batching, filters, requests, selection, sort, timingMarkers, - ui + ui, + webSockets, );
--- a/devtools/client/netmonitor/src/actions/moz.build +++ b/devtools/client/netmonitor/src/actions/moz.build @@ -6,9 +6,10 @@ DevToolsModules( 'batching.js', 'filters.js', 'index.js', 'requests.js', 'selection.js', 'sort.js', 'timing-markers.js', 'ui.js', + 'web-sockets.js', )
new file mode 100644 --- /dev/null +++ b/devtools/client/netmonitor/src/actions/web-sockets.js @@ -0,0 +1,19 @@ +/* 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 { WS_ADD_FRAME } = require("../constants"); + +function addFrame(httpChannelId, data) { + return { + type: WS_ADD_FRAME, + httpChannelId, + data, + }; +} + +module.exports = { + addFrame, +};
--- a/devtools/client/netmonitor/src/components/TabboxPanel.js +++ b/devtools/client/netmonitor/src/components/TabboxPanel.js @@ -1,38 +1,41 @@ /* 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 { Component, createFactory, } = require("devtools/client/shared/vendor/react"); const PropTypes = require("devtools/client/shared/vendor/react-prop-types"); const { L10N } = require("../utils/l10n"); const { PANELS } = require("../constants"); // Components const Tabbar = createFactory(require("devtools/client/shared/components/tabs/TabBar")); const TabPanel = createFactory(require("devtools/client/shared/components/tabs/Tabs").TabPanel); const CookiesPanel = createFactory(require("./CookiesPanel")); const HeadersPanel = createFactory(require("./HeadersPanel")); +const WebSocketsPanel = createFactory(require("./WebSocketsPanel")); const ParamsPanel = createFactory(require("./ParamsPanel")); const CachePanel = createFactory(require("./CachePanel")); const ResponsePanel = createFactory(require("./ResponsePanel")); const SecurityPanel = createFactory(require("./SecurityPanel")); const StackTracePanel = createFactory(require("./StackTracePanel")); const TimingsPanel = createFactory(require("./TimingsPanel")); const COLLAPSE_DETAILS_PANE = L10N.getStr("collapseDetailsPane"); const CACHE_TITLE = L10N.getStr("netmonitor.tab.cache"); const COOKIES_TITLE = L10N.getStr("netmonitor.tab.cookies"); const HEADERS_TITLE = L10N.getStr("netmonitor.tab.headers"); +const WEBSOCKETS_TITLE = L10N.getStr("netmonitor.tab.webSockets"); const PARAMS_TITLE = L10N.getStr("netmonitor.tab.params"); const RESPONSE_TITLE = L10N.getStr("netmonitor.tab.response"); const SECURITY_TITLE = L10N.getStr("netmonitor.tab.security"); const STACK_TRACE_TITLE = L10N.getStr("netmonitor.tab.stackTrace"); const TIMINGS_TITLE = L10N.getStr("netmonitor.tab.timings"); /** * Tabbox panel component @@ -82,16 +85,21 @@ class TabboxPanel extends Component { sourceMapService, toggleNetworkDetails, } = this.props; if (!request) { return null; } + const channelId = request.channelId; + const showWebSocketsPanel = + request.cause.type === "websocket" && + Services.prefs.getBoolPref( + "devtools.netmonitor.features.webSockets"); return ( Tabbar({ activeTabId, menuDocument: window.parent.document, onSelect: selectTab, renderOnlySelected: true, showAllTabsMenu: true, sidebarToggleButton: hideToggleButton ? null : @@ -108,16 +116,24 @@ class TabboxPanel extends Component { }, HeadersPanel({ cloneSelectedRequest, connector, openLink, request, }), ), + showWebSocketsPanel && TabPanel({ + id: PANELS.WEBSOCKETS, + title: WEBSOCKETS_TITLE, + }, + WebSocketsPanel({ + channelId, + }), + ), TabPanel({ id: PANELS.COOKIES, title: COOKIES_TITLE, }, CookiesPanel({ connector, openLink, request,
new file mode 100644 --- /dev/null +++ b/devtools/client/netmonitor/src/components/WebSocketsPanel.js @@ -0,0 +1,85 @@ +/* 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 { Component } = require("devtools/client/shared/vendor/react"); +const PropTypes = require("devtools/client/shared/vendor/react-prop-types"); +const { + connect, +} = require("devtools/client/shared/redux/visibility-handler-connect"); +const { getFramesByChannelId } = require("../selectors/index"); + +const dom = require("devtools/client/shared/vendor/react-dom-factories"); +const { table, tbody, thead, tr, td, th, div } = dom; + +const { L10N } = require("../utils/l10n"); +const FRAMES_EMPTY_TEXT = L10N.getStr("webSocketsEmptyText"); + +class WebSocketsPanel extends Component { + static get propTypes() { + return { + channelId: PropTypes.number, + frames: PropTypes.array, + }; + } + + constructor(props) { + super(props); + } + + render() { + const { frames } = this.props; + + if (!frames) { + return div({ className: "empty-notice" }, + FRAMES_EMPTY_TEXT + ); + } + + const rows = []; + frames.forEach((frame, index) => { + rows.push( + tr( + { key: index, + className: "frames-row" }, + td({ className: "frames-cell" }, frame.type), + td({ className: "frames-cell" }, frame.httpChannelId), + td({ className: "frames-cell" }, frame.payload), + td({ className: "frames-cell" }, frame.opCode), + td({ className: "frames-cell" }, frame.maskBit.toString()), + td({ className: "frames-cell" }, frame.finBit.toString()), + td({ className: "frames-cell" }, frame.timeStamp) + ) + ); + }); + + return table( + { className: "frames-list-table" }, + thead( + { className: "frames-head" }, + tr( + { className: "frames-row" }, + th({ className: "frames-headerCell" }, "Type"), + th({ className: "frames-headerCell" }, "Channel ID"), + th({ className: "frames-headerCell" }, "Payload"), + th({ className: "frames-headerCell" }, "OpCode"), + th({ className: "frames-headerCell" }, "MaskBit"), + th({ className: "frames-headerCell" }, "FinBit"), + th({ className: "frames-headerCell" }, "Time") + ) + ), + tbody( + { + className: "frames-list-tableBody", + }, + rows + ) + ); + } +} + +module.exports = connect((state, props) => ({ + frames: getFramesByChannelId(state, props.channelId), +}))(WebSocketsPanel);
--- a/devtools/client/netmonitor/src/components/moz.build +++ b/devtools/client/netmonitor/src/components/moz.build @@ -42,9 +42,10 @@ DevToolsModules( 'SourceEditor.js', 'StackTracePanel.js', 'StatisticsPanel.js', 'StatusBar.js', 'StatusCode.js', 'TabboxPanel.js', 'TimingsPanel.js', 'Toolbar.js', + 'WebSocketsPanel.js', )
--- a/devtools/client/netmonitor/src/connector/firefox-data-provider.js +++ b/devtools/client/netmonitor/src/connector/firefox-data-provider.js @@ -74,16 +74,17 @@ class FirefoxDataProvider { isXHR, cause, startedDateTime, fromCache, fromServiceWorker, isThirdPartyTrackingResource, referrerPolicy, blockedReason, + channelId, } = data; // Insert blocked reason in the payload queue as well, as we'll need it later // when deciding if the request is complete. this.pushRequestToQueue(id, { blockedReason, }); @@ -101,16 +102,17 @@ class FirefoxDataProvider { // FF59+ supports fetching the traces lazily via requestData. stacktrace: cause.stacktrace, fromCache, fromServiceWorker, isThirdPartyTrackingResource, referrerPolicy, blockedReason, + channelId, }, true); } this.emit(EVENTS.REQUEST_ADDED, id); } /** * Update a network request if it already exists in application state. @@ -338,29 +340,31 @@ class FirefoxDataProvider { request: { method, url, }, startedDateTime, isThirdPartyTrackingResource, referrerPolicy, blockedReason, + channelId, } = networkInfo; await this.addRequest(actor, { cause, fromCache, fromServiceWorker, isXHR, method, startedDateTime, url, isThirdPartyTrackingResource, referrerPolicy, blockedReason, + channelId, }); this.emit(EVENTS.NETWORK_EVENT, actor); } /** * The "networkEventUpdate" message type handler. * @@ -421,63 +425,59 @@ class FirefoxDataProvider { * The "webSocketOpened" message type handler. * * @param {number} httpChannelId the channel ID * @param {string} effectiveURI the effective URI of the page * @param {string} protocols webSocket protocols * @param {string} extensions */ async onWebSocketOpened(httpChannelId, effectiveURI, protocols, extensions) { - console.log("FirefoxDataProvider onWebSocketOpened: " + - " httpChannelId: " + httpChannelId + - " effectiveURI: " + effectiveURI + - " protocols: " + protocols + - " extensions: " + extensions); } /** * The "webSocketClosed" message type handler. * * @param {boolean} wasClean * @param {number} code * @param {string} reason */ async onWebSocketClosed(wasClean, code, reason) { - console.log("FirefoxDataProvider onWebSocketClosed: " + - " wasClean: " + wasClean + - " code: " + code + - " reason: " + reason); } /** * The "frameSent" message type handler. * * @param {number} httpChannelId the channel ID * @param {object} data websocket frame information */ async onFrameSent(httpChannelId, data) { - await this.getLongString(data.payload).then(payload => { - console.log("FirefoxDataProvider onFrameSent: " + - " httpChannelId: " + httpChannelId + - " onFrameSent: " + payload); - }); + this.addFrame(httpChannelId, data); } /** * The "frameReceived" message type handler. * * @param {number} httpChannelId the channel ID * @param {object} data websocket frame information */ async onFrameReceived(httpChannelId, data) { - await this.getLongString(data.payload).then(payload => { - console.log("FirefoxDataProvider onFrameReceived: " + - " httpChannelId: " + httpChannelId + - " onFrameSent: " + payload); - }); + this.addFrame(httpChannelId, data); + } + + /** + * Add a new WebSocket frame to application state. + * + * @param {number} httpChannelId the channel ID + * @param {object} data websocket frame information + */ + async addFrame(httpChannelId, data) { + if (this.actionsEnabled && this.actions.addFrame) { + await this.actions.addFrame(httpChannelId, data); + } + // TODO: Emit an event for test here } /** * Notify actions when messages from onNetworkEventUpdate are done, networkEventUpdate * messages contain initial network info for each updateType and then we can invoke * requestData to fetch its corresponded data lazily. * Once all updateTypes of networkEventUpdate message are arrived, we flush merged * request payload from pending queue and then update component.
--- a/devtools/client/netmonitor/src/constants.js +++ b/devtools/client/netmonitor/src/constants.js @@ -29,16 +29,17 @@ const actionTypes = { SET_REQUEST_FILTER_TEXT: "SET_REQUEST_FILTER_TEXT", SORT_BY: "SORT_BY", TOGGLE_COLUMN: "TOGGLE_COLUMN", TOGGLE_RECORDING: "TOGGLE_RECORDING", TOGGLE_REQUEST_FILTER_TYPE: "TOGGLE_REQUEST_FILTER_TYPE", UPDATE_REQUEST: "UPDATE_REQUEST", WATERFALL_RESIZE: "WATERFALL_RESIZE", SET_COLUMNS_WIDTH: "SET_COLUMNS_WIDTH", + WS_ADD_FRAME: "WS_ADD_FRAME", }; // Descriptions for what this frontend is currently doing. const ACTIVITY_TYPE = { // Standing by and handling requests normally. NONE: 0, // Forcing the target to reload with cache enabled or disabled. @@ -149,21 +150,23 @@ const UPDATE_PROPS = [ "responseContentAvailable", "responseCache", "responseCacheAvailable", "formDataSections", "stacktrace", "isThirdPartyTrackingResource", "referrerPolicy", "blockedReason", + "channelId", ]; const PANELS = { COOKIES: "cookies", HEADERS: "headers", + WEBSOCKETS: "webSockets", PARAMS: "params", RESPONSE: "response", CACHE: "cache", SECURITY: "security", STACK_TRACE: "stack-trace", TIMINGS: "timings", };
--- a/devtools/client/netmonitor/src/create-store.js +++ b/devtools/client/netmonitor/src/create-store.js @@ -22,16 +22,17 @@ const eventTelemetry = require("./middle // Reducers const rootReducer = require("./reducers/index"); const { FilterTypes, Filters } = require("./reducers/filters"); const { Requests } = require("./reducers/requests"); const { Sort } = require("./reducers/sort"); const { TimingMarkers } = require("./reducers/timing-markers"); const { UI, Columns, ColumnsData } = require("./reducers/ui"); +const { WebSockets } = require("./reducers/web-sockets"); /** * Configure state and middleware for the Network monitor tool. */ function configureStore(connector, telemetry) { // Prepare initial state. const initialState = { filters: new Filters({ @@ -39,16 +40,17 @@ function configureStore(connector, telem }), requests: new Requests(), sort: new Sort(), timingMarkers: new TimingMarkers(), ui: UI({ columns: getColumnState(), columnsData: getColumnsData(), }), + webSockets: new WebSockets(), }; // Prepare middleware. const middleware = applyMiddleware( thunk, prefs, batching, recording(connector),
--- a/devtools/client/netmonitor/src/reducers/index.js +++ b/devtools/client/netmonitor/src/reducers/index.js @@ -6,20 +6,22 @@ const { combineReducers } = require("devtools/client/shared/vendor/redux"); const batchingReducer = require("./batching"); const { requestsReducer } = require("./requests"); const { sortReducer } = require("./sort"); const { filters } = require("./filters"); const { timingMarkers } = require("./timing-markers"); const { ui } = require("./ui"); +const { webSocketsReducer } = require("./web-sockets"); const networkThrottling = require("devtools/client/shared/components/throttling/reducer"); module.exports = batchingReducer( combineReducers({ requests: requestsReducer, sort: sortReducer, + webSockets: webSocketsReducer, filters, timingMarkers, ui, networkThrottling, }) );
--- a/devtools/client/netmonitor/src/reducers/moz.build +++ b/devtools/client/netmonitor/src/reducers/moz.build @@ -5,9 +5,10 @@ DevToolsModules( 'batching.js', 'filters.js', 'index.js', 'requests.js', 'sort.js', 'timing-markers.js', 'ui.js', + 'web-sockets.js', )
new file mode 100644 --- /dev/null +++ b/devtools/client/netmonitor/src/reducers/web-sockets.js @@ -0,0 +1,64 @@ +/* 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 { + WS_ADD_FRAME, +} = require("../constants"); + +/** + * This structure stores list of all WebSocket frames received + * from the backend. + */ +function WebSockets() { + return { + // Map with all requests (key = channelId, value = array of frame objects) + frames: new Map(), + }; +} + +/** + * This reducer is responsible for maintaining list of + * WebSocket frames within the Network panel. + */ +function webSocketsReducer(state = WebSockets(), action) { + switch (action.type) { + // Appending new frame into the map. + case WS_ADD_FRAME: { + const nextState = { ...state }; + + const newFrame = { + httpChannelId: action.httpChannelId, + ...action.data, + }; + + nextState.frames = mapSet(state.frames, newFrame.httpChannelId, newFrame); + + return nextState; + } + + default: + return state; + } +} + +/** + * Append new item into existing map and return new map. + */ +function mapSet(map, key, value) { + const newMap = new Map(map); + if (newMap.has(key)) { + const framesArray = [...newMap.get(key)]; + framesArray.push(value); + newMap.set(key, framesArray); + return newMap; + } + return newMap.set(key, [value]); +} + +module.exports = { + WebSockets, + webSocketsReducer, +};
--- a/devtools/client/netmonitor/src/selectors/index.js +++ b/devtools/client/netmonitor/src/selectors/index.js @@ -2,14 +2,16 @@ * 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 requests = require("./requests"); const timingMarkers = require("./timing-markers"); const ui = require("./ui"); +const webSockets = require("./web-sockets"); Object.assign(exports, requests, timingMarkers, - ui + ui, + webSockets, );
--- a/devtools/client/netmonitor/src/selectors/moz.build +++ b/devtools/client/netmonitor/src/selectors/moz.build @@ -2,9 +2,10 @@ # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at http://mozilla.org/MPL/2.0/. DevToolsModules( 'index.js', 'requests.js', 'timing-markers.js', 'ui.js', + 'web-sockets.js', )
new file mode 100644 --- /dev/null +++ b/devtools/client/netmonitor/src/selectors/web-sockets.js @@ -0,0 +1,13 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +function getFramesByChannelId(state, channelId) { + return state.webSockets.frames.get(channelId); +} + +module.exports = { + getFramesByChannelId, +};
--- a/devtools/client/themes/dark-theme.css +++ b/devtools/client/themes/dark-theme.css @@ -66,17 +66,17 @@ body { .cm-s-mozilla .cm-executed-line { background-color: #133c26; } .cm-s-mozilla .cm-number, .variable-or-property .token-number, .variable-or-property[return] > .title > .name, .variable-or-property[scope] > .title > .name { - color: #6B89FF; + color: #709AFF; } .CodeMirror-Tern-completion-number:before { background-color: #5c9966; } .theme-fg-color1, .cm-s-mozilla .cm-attribute, @@ -119,17 +119,17 @@ body { .theme-fg-color2 { color: var(--theme-highlight-purple); } .cm-s-mozilla .cm-string, .cm-s-mozilla .cm-string-2, .variable-or-property .token-string, .CodeMirror-Tern-farg { - color: #6B89FF; + color: #709AFF; } .CodeMirror-Tern-completion-string:before, .CodeMirror-Tern-completion-fn:before { background-color: #b26b47; } .cm-s-mozilla .cm-atom,
--- a/devtools/client/webconsole/test/fixtures/stub-generators/head.js +++ b/devtools/client/webconsole/test/fixtures/stub-generators/head.js @@ -70,16 +70,20 @@ function getCleanedPacket(key, packet) { if (res.startedDateTime) { res.startedDateTime = existingPacket.startedDateTime; } if (res.actor) { res.actor = existingPacket.actor; } + if (res.channelId) { + res.channelId = existingPacket.channelId; + } + if (res.message) { // Clean timeStamp on the message prop. res.message.timeStamp = existingPacket.message.timeStamp; if (res.message.timer) { // Clean timer properties on the message. // Those properties are found on console.time, timeLog and timeEnd calls, // and those time can vary, which is why we need to clean them. if ("duration" in res.message.timer) {
--- a/devtools/client/webconsole/test/fixtures/stubs/networkEvent.js +++ b/devtools/client/webconsole/test/fixtures/stubs/networkEvent.js @@ -260,16 +260,17 @@ stubPackets.set(`GET request`, { "stacktraceAvailable": true }, "response": {}, "timings": {}, "updates": [], "private": false, "isThirdPartyTrackingResource": false, "referrerPolicy": "no-referrer-when-downgrade", + "channelId": 22673132355586, "from": "server1.conn0.child1/consoleActor2" }); stubPackets.set(`GET request update`, { "networkInfo": { "_type": "NetworkEvent", "actor": "server1.conn0.child1/netEvent30", "request": { @@ -310,16 +311,17 @@ stubPackets.set(`XHR GET request`, { "stacktraceAvailable": true }, "response": {}, "timings": {}, "updates": [], "private": false, "isThirdPartyTrackingResource": false, "referrerPolicy": "no-referrer-when-downgrade", + "channelId": 22673132355587, "from": "server1.conn1.child1/consoleActor2" }); stubPackets.set(`XHR GET request update`, { "networkInfo": { "_type": "NetworkEvent", "actor": "server1.conn0.child1/netEvent31", "request": { @@ -360,16 +362,17 @@ stubPackets.set(`XHR POST request`, { "stacktraceAvailable": true }, "response": {}, "timings": {}, "updates": [], "private": false, "isThirdPartyTrackingResource": false, "referrerPolicy": "no-referrer-when-downgrade", + "channelId": 22673132355588, "from": "server1.conn2.child1/consoleActor2" }); stubPackets.set(`XHR POST request update`, { "networkInfo": { "_type": "NetworkEvent", "actor": "server1.conn0.child1/netEvent32", "request": {
--- a/devtools/server/actors/network-event.js +++ b/devtools/server/actors/network-event.js @@ -63,16 +63,17 @@ const NetworkEventActor = protocol.Actor isXHR: this._isXHR, cause: this._cause, fromCache: this._fromCache, fromServiceWorker: this._fromServiceWorker, private: this._private, isThirdPartyTrackingResource: this._isThirdPartyTrackingResource, referrerPolicy: this._referrerPolicy, blockedReason: this._blockedReason, + channelId: this._channelId, }; }, /** * Releases this actor from the pool. */ destroy(conn) { if (!this.netMonitorActor) {
--- a/devtools/server/actors/thread.js +++ b/devtools/server/actors/thread.js @@ -1,26 +1,29 @@ /* -*- indent-tabs-mode: nil; js-indent-level: 2; 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 DebuggerNotificationObserver = require("DebuggerNotificationObserver"); const Services = require("Services"); const { Cr, Ci } = require("chrome"); const { ActorPool } = require("devtools/server/actors/common"); const { createValueGrip } = require("devtools/server/actors/object/utils"); const { ActorClassWithSpec, Actor } = require("devtools/shared/protocol"); const DevToolsUtils = require("devtools/shared/DevToolsUtils"); const { assert, dumpn } = DevToolsUtils; const { threadSpec } = require("devtools/shared/specs/thread"); const { getAvailableEventBreakpoints, + eventBreakpointForNotification, + makeEventBreakpointMessage, } = require("devtools/server/actors/utils/event-breakpoints"); loader.lazyRequireGetter(this, "EnvironmentActor", "devtools/server/actors/environment", true); loader.lazyRequireGetter(this, "BreakpointActorMap", "devtools/server/actors/utils/breakpoint-actor-map", true); loader.lazyRequireGetter(this, "PauseScopedObjectActor", "devtools/server/actors/pause-scoped", true); loader.lazyRequireGetter(this, "EventLoopStack", "devtools/server/actors/utils/event-loop", true); loader.lazyRequireGetter(this, "FrameActor", "devtools/server/actors/frame", true); loader.lazyRequireGetter(this, "throttle", "devtools/shared/throttle", true); @@ -56,17 +59,18 @@ const ThreadActor = ActorClassWithSpec(t this._parent = parent; this._dbg = null; this._gripDepth = 0; this._threadLifetimePool = null; this._parentClosed = false; this._scripts = null; this._xhrBreakpoints = []; this._observingNetwork = false; - this._eventBreakpoints = []; + this._activeEventBreakpoints = new Set(); + this._activeEventPause = null; this._priorPause = null; this._options = { autoBlackBox: false, }; this.breakpointActorMap = new BreakpointActorMap(this); @@ -89,16 +93,20 @@ const ThreadActor = ActorClassWithSpec(t this.uncaughtExceptionHook = this.uncaughtExceptionHook.bind(this); this.createCompletionGrip = this.createCompletionGrip.bind(this); this.onDebuggerStatement = this.onDebuggerStatement.bind(this); this.onNewScript = this.onNewScript.bind(this); this.objectGrip = this.objectGrip.bind(this); this.pauseObjectGrip = this.pauseObjectGrip.bind(this); this._onOpeningRequest = this._onOpeningRequest.bind(this); + this._onNewDebuggee = this._onNewDebuggee.bind(this); + this._eventBreakpointListener = this._eventBreakpointListener.bind(this); + + this._debuggerNotificationObserver = new DebuggerNotificationObserver(); if (Services.obs) { // Set a wrappedJSObject property so |this| can be sent via the observer svc // for the xpcshell harness. this.wrappedJSObject = this; Services.obs.notifyObservers(this, "devtools-thread-instantiated"); } }, @@ -107,16 +115,17 @@ const ThreadActor = ActorClassWithSpec(t _gripDepth: null, get dbg() { if (!this._dbg) { this._dbg = this._parent.makeDebugger(); this._dbg.uncaughtExceptionHook = this.uncaughtExceptionHook; this._dbg.onDebuggerStatement = this.onDebuggerStatement; this._dbg.onNewScript = this.onNewScript; + this._dbg.onNewDebuggee = this._onNewDebuggee; if (this._dbg.replaying) { this._dbg.replayingOnForcedPause = this.replayingOnForcedPause.bind(this); const sendProgress = throttle((recording, executionPoint) => { if (this.attached) { this.conn.send({ type: "progress", from: this.actorID, recording, executionPoint }); } }, 100); @@ -218,16 +227,26 @@ const ThreadActor = ActorClassWithSpec(t dumpn("in ThreadActor.prototype.destroy"); if (this._state == "paused") { this.doResume(); } this._xhrBreakpoints = []; this._updateNetworkObserver(); + this._activeEventBreakpoints = new Set(); + this._debuggerNotificationObserver.removeListener( + this._eventBreakpointListener); + + for (const global of this.dbg.getDebuggees()) { + try { + this._debuggerNotificationObserver.disconnect(global); + } catch (e) { } + } + this.sources.off("newSource", this.onNewSourceEvent); this.clearDebuggees(); this.conn.removeActorPool(this._threadLifetimePool); this._threadLifetimePool = null; if (!this._dbg) { return; } @@ -280,16 +299,19 @@ const ThreadActor = ActorClassWithSpec(t hooks: this._parent, connection: this.conn, thread: this, }); if (options.breakpoints) { this._setBreakpointsOnAttach(options.breakpoints); } + if (options.eventBreakpoints) { + this.setActiveEventBreakpoints(options.eventBreakpoints); + } this.dbg.addDebuggees(); this.dbg.enabled = true; if ("observeAsmJS" in this._options) { this.dbg.allowUnobservedAsmJS = !this._options.observeAsmJS; } @@ -395,20 +417,34 @@ const ThreadActor = ActorClassWithSpec(t } return this._updateNetworkObserver(); }, getAvailableEventBreakpoints: function() { return getAvailableEventBreakpoints(); }, getActiveEventBreakpoints: function() { - return this._eventBreakpoints; + return Array.from(this._activeEventBreakpoints); }, setActiveEventBreakpoints: function(ids) { - this._eventBreakpoints = ids; + this._activeEventBreakpoints = new Set(ids); + + if (this._activeEventBreakpoints.size === 0) { + this._debuggerNotificationObserver.removeListener( + this._eventBreakpointListener); + } else { + this._debuggerNotificationObserver.addListener( + this._eventBreakpointListener); + } + }, + + _onNewDebuggee(global) { + try { + this._debuggerNotificationObserver.connect(global); + } catch (e) { } }, _updateNetworkObserver() { // Workers don't have access to `Services` and even if they did, network // requests are all dispatched to the main thread, so there would be // nothing here to listen for. We'll need to revisit implementing // XHR breakpoints for workers. if (isWorker) { @@ -508,16 +544,91 @@ const ThreadActor = ActorClassWithSpec(t Object.assign(this._options, options); // Update the global source store this.sources.setOptions(options); return {}; }, + _eventBreakpointListener(notification) { + if (this._state === "paused" || this._state === "detached") { + return; + } + + const eventBreakpoint = + eventBreakpointForNotification(this.dbg, notification); + + if (!this._activeEventBreakpoints.has(eventBreakpoint)) { + return; + } + + if (notification.phase === "pre" && !this._activeEventPause) { + this._activeEventPause = this._captureDebuggerHooks(); + + this.dbg.onEnterFrame = + this._makeEventBreakpointEnterFrame(eventBreakpoint); + } else if (notification.phase === "post" && this._activeEventPause) { + this._restoreDebuggerHooks(this._activeEventPause); + this._activeEventPause = null; + } else if (!notification.phase && !this._activeEventPause) { + const frame = this.dbg.getNewestFrame(); + if (frame) { + const { sourceActor } = this.sources.getFrameLocation(frame); + const url = sourceActor.url; + if (this.sources.isBlackBoxed(url)) { + return; + } + + this._pauseAndRespondEventBreakpoint(frame, eventBreakpoint); + } + } + }, + + _makeEventBreakpointEnterFrame(eventBreakpoint) { + return frame => { + const { sourceActor } = this.sources.getFrameLocation(frame); + const url = sourceActor.url; + if (this.sources.isBlackBoxed(url)) { + return undefined; + } + + this._restoreDebuggerHooks(this._activeEventPause); + this._activeEventPause = null; + + return this._pauseAndRespondEventBreakpoint(frame, eventBreakpoint); + }; + }, + + _pauseAndRespondEventBreakpoint(frame, eventBreakpoint) { + if (this.skipBreakpoints) { + return undefined; + } + + return this._pauseAndRespond(frame, { + type: "eventBreakpoint", + breakpoint: eventBreakpoint, + message: makeEventBreakpointMessage(eventBreakpoint), + }); + }, + + _captureDebuggerHooks() { + return { + onEnterFrame: this.dbg.onEnterFrame, + onStep: this.dbg.onStep, + onPop: this.dbg.onPop, + }; + }, + + _restoreDebuggerHooks(hooks) { + this.dbg.onEnterFrame = hooks.onEnterFrame; + this.dbg.onStep = hooks.onStep; + this.dbg.onPop = hooks.onPop; + }, + /** * Pause the debuggee, by entering a nested event loop, and return a 'paused' * packet to the client. * * @param Debugger.Frame frame * The newest debuggee frame in the stack. * @param object reason * An object with a 'type' property containing the reason for the pause. @@ -570,50 +681,51 @@ const ThreadActor = ActorClassWithSpec(t } // If the parent actor has been closed, terminate the debuggee script // instead of continuing. Executing JS after the content window is gone is // a bad idea. return this._parentClosed ? null : undefined; }, - _makeOnEnterFrame: function({ thread, pauseAndRespond }) { + _makeOnEnterFrame: function({ pauseAndRespond }) { return frame => { const { sourceActor } = this.sources.getFrameLocation(frame); const url = sourceActor.url; if (this.sources.isBlackBoxed(url)) { return undefined; } // If the initial frame offset is a step target, we are done. if (frame.script.getOffsetMetadata(frame.offset).isStepStart) { return pauseAndRespond(frame); } // Continue forward until we get to a valid step target. - const { onStep, onPop } = thread._makeSteppingHooks( + const { onStep, onPop } = this._makeSteppingHooks( null, "next", false, null ); - if (thread.dbg.replaying) { + if (this.dbg.replaying) { const offsets = - thread._findReplayingStepOffsets(null, frame, + this._findReplayingStepOffsets(null, frame, /* rewinding = */ false); frame.setReplayingOnStep(onStep, offsets); } else { frame.onStep = onStep; } frame.onPop = onPop; return undefined; }; }, - _makeOnPop: function({ thread, pauseAndRespond, startLocation, steppingType }) { + _makeOnPop: function({ pauseAndRespond, startLocation, steppingType }) { + const thread = this; const result = function(completion) { // onPop is called with 'this' set to the current frame. const location = thread.sources.getFrameLocation(this); const { sourceActor } = location; const url = sourceActor.url; if (thread.sources.isBlackBoxed(url)) { @@ -723,18 +835,19 @@ const ThreadActor = ActorClassWithSpec(t if (pausePoint) { return pausePoint.step; } return script.getOffsetMetadata(offset).isStepStart; }, - _makeOnStep: function({ thread, pauseAndRespond, startFrame, + _makeOnStep: function({ pauseAndRespond, startFrame, startLocation, steppingType, completion, rewinding }) { + const thread = this; return function() { // onStep is called with 'this' set to the current frame. const location = thread.sources.getFrameLocation(this); // Always continue execution if either: // // 1. We are in a source mapped region, but inside a null mapping @@ -823,17 +936,16 @@ const ThreadActor = ActorClassWithSpec(t // binding in each _makeOnX method, just do it once here and pass it // in to each function. const steppingHookState = { pauseAndRespond: (frame, onPacket = k=>k) => this._pauseAndRespond( frame, { type: "resumeLimit" }, onPacket ), - thread: this, startFrame: this.youngestFrame, startLocation: startLocation, steppingType: steppingType, rewinding: rewinding, completion, }; return {
--- a/devtools/server/actors/utils/event-breakpoints.js +++ b/devtools/server/actors/utils/event-breakpoints.js @@ -1,209 +1,417 @@ /* -*- indent-tabs-mode: nil; js-indent-level: 2; 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"; +function generalEvent(groupID, eventType) { + return { + id: `event.${groupID}.${eventType}`, + type: "event", + name: eventType, + message: `DOM '${eventType}' event`, + eventType, + filter: "general", + }; +} +function nodeEvent(groupID, eventType) { + return { + ...generalEvent(groupID, eventType), + filter: "node", + }; +} +function mediaNodeEvent(groupID, eventType) { + return { + ...generalEvent(groupID, eventType), + filter: "media", + }; +} +function globalEvent(groupID, eventType) { + return { + ...generalEvent(groupID, eventType), + message: `Global '${eventType}' event`, + filter: "global", + }; +} +function xhrEvent(groupID, eventType) { + return { + ...generalEvent(groupID, eventType), + message: `XHR '${eventType}' event`, + filter: "xhr", + }; +} +function workerEvent(eventType) { + return { + ...generalEvent("worker", eventType), + message: `Worker '${eventType}' event`, + filter: "worker", + }; +} + +function timerEvent(type, operation, name, notificationType) { + return { + id: `timer.${type}.${operation}`, + type: "simple", + name, + message: name, + notificationType, + }; +} + +function animationEvent(operation, name, notificationType) { + return { + id: `animationframe.${operation}`, + type: "simple", + name, + message: name, + notificationType, + }; +} + +const AVAILABLE_BREAKPOINTS = [ + { + name: "Animation", + items: [ + animationEvent( + "request", + "Request Animation Frame", + "requestAnimationFrame" + ), + animationEvent( + "cancel", + "Cancel Animation Frame", + "cancelAnimationFrame" + ), + animationEvent( + "fire", + "Animation Frame fired", + "requestAnimationFrameCallback" + ), + ], + }, + { + name: "Clipboard", + items: [ + generalEvent("clipboard", "copy"), + generalEvent("clipboard", "cut"), + generalEvent("clipboard", "paste"), + generalEvent("clipboard", "beforecopy"), + generalEvent("clipboard", "beforecut"), + generalEvent("clipboard", "beforepaste"), + ], + }, + { + name: "Control", + items: [ + generalEvent("control", "resize"), + generalEvent("control", "scroll"), + generalEvent("control", "zoom"), + generalEvent("control", "focus"), + generalEvent("control", "blur"), + generalEvent("control", "select"), + generalEvent("control", "change"), + generalEvent("control", "submit"), + generalEvent("control", "reset"), + ], + }, + { + name: "DOM Mutation", + items: [ + // Deprecated DOM events. + nodeEvent("dom-mutation", "DOMActivate"), + nodeEvent("dom-mutation", "DOMFocusIn"), + nodeEvent("dom-mutation", "DOMFocusOut"), + + // Standard DOM mutation events. + nodeEvent("dom-mutation", "DOMAttrModified"), + nodeEvent("dom-mutation", "DOMCharacterDataModified"), + nodeEvent("dom-mutation", "DOMNodeInserted"), + nodeEvent("dom-mutation", "DOMNodeInsertedIntoDocument"), + nodeEvent("dom-mutation", "DOMNodeRemoved"), + nodeEvent("dom-mutation", "DOMNodeRemovedIntoDocument"), + nodeEvent("dom-mutation", "DOMSubtreeModified"), + + // DOM load events. + nodeEvent("dom-mutation", "DOMContentLoaded"), + ], + }, + { + name: "Device", + items: [ + globalEvent("device", "deviceorientation"), + globalEvent("device", "devicemotion"), + ], + }, + { + name: "Drag and Drop", + items: [ + generalEvent("drag-and-drop", "drag"), + generalEvent("drag-and-drop", "dragstart"), + generalEvent("drag-and-drop", "dragend"), + generalEvent("drag-and-drop", "dragenter"), + generalEvent("drag-and-drop", "dragover"), + generalEvent("drag-and-drop", "dragleave"), + generalEvent("drag-and-drop", "drop"), + ], + }, + { + name: "Keyboard", + items: [ + generalEvent("keyboard", "keydown"), + generalEvent("keyboard", "keyup"), + generalEvent("keyboard", "keypress"), + generalEvent("keyboard", "input"), + ], + }, + { + name: "Load", + items: [ + globalEvent("load", "load"), + globalEvent("load", "beforeunload"), + globalEvent("load", "unload"), + globalEvent("load", "abort"), + globalEvent("load", "error"), + globalEvent("load", "hashchange"), + globalEvent("load", "popstate"), + ], + }, + { + name: "Media", + items: [ + mediaNodeEvent("media", "play"), + mediaNodeEvent("media", "pause"), + mediaNodeEvent("media", "playing"), + mediaNodeEvent("media", "canplay"), + mediaNodeEvent("media", "canplaythrough"), + mediaNodeEvent("media", "seeking"), + mediaNodeEvent("media", "seeked"), + mediaNodeEvent("media", "timeupdate"), + mediaNodeEvent("media", "ended"), + mediaNodeEvent("media", "ratechange"), + mediaNodeEvent("media", "durationchange"), + mediaNodeEvent("media", "volumechange"), + mediaNodeEvent("media", "loadstart"), + mediaNodeEvent("media", "progress"), + mediaNodeEvent("media", "suspend"), + mediaNodeEvent("media", "abort"), + mediaNodeEvent("media", "error"), + mediaNodeEvent("media", "emptied"), + mediaNodeEvent("media", "stalled"), + mediaNodeEvent("media", "loadedmetadata"), + mediaNodeEvent("media", "loadeddata"), + mediaNodeEvent("media", "waiting"), + ], + }, + { + name: "Mouse", + items: [ + generalEvent("mouse", "auxclick"), + generalEvent("mouse", "click"), + generalEvent("mouse", "dblclick"), + generalEvent("mouse", "mousedown"), + generalEvent("mouse", "mouseup"), + generalEvent("mouse", "mouseover"), + generalEvent("mouse", "mousemove"), + generalEvent("mouse", "mouseout"), + generalEvent("mouse", "mouseenter"), + generalEvent("mouse", "mouseleave"), + generalEvent("mouse", "mousewheel"), + generalEvent("mouse", "wheel"), + generalEvent("mouse", "contextmenu"), + ], + }, + { + name: "Pointer", + items: [ + generalEvent("pointer", "pointerover"), + generalEvent("pointer", "pointerout"), + generalEvent("pointer", "pointerenter"), + generalEvent("pointer", "pointerleave"), + generalEvent("pointer", "pointerdown"), + generalEvent("pointer", "pointerup"), + generalEvent("pointer", "pointermove"), + generalEvent("pointer", "pointercancel"), + generalEvent("pointer", "gotpointercapture"), + generalEvent("pointer", "lostpointercapture"), + ], + }, + { + name: "Timer", + items: [ + timerEvent("timeout", "set", "setTimeout", "setTimeout"), + timerEvent("timeout", "clear", "clearTimeout", "clearTimeout"), + timerEvent("timeout", "fire", "setTimeout fired", "setTimeoutCallback"), + timerEvent("interval", "set", "setInterval", "setInterval"), + timerEvent("interval", "clear", "clearInterval", "clearInterval"), + timerEvent( + "interval", + "fire", + "setInterval fired", + "setIntervalCallback" + ), + ], + }, + { + name: "Touch", + items: [ + generalEvent("touch", "touchstart"), + generalEvent("touch", "touchmove"), + generalEvent("touch", "touchend"), + generalEvent("touch", "touchcancel"), + ], + }, + { + name: "Worker", + items: [ + workerEvent("message"), + workerEvent("messageerror"), + ], + }, + { + name: "XHR", + items: [ + xhrEvent("xhr", "readystatechange"), + xhrEvent("xhr", "load"), + xhrEvent("xhr", "loadstart"), + xhrEvent("xhr", "loadend"), + xhrEvent("xhr", "abort"), + xhrEvent("xhr", "error"), + xhrEvent("xhr", "progress"), + xhrEvent("xhr", "timeout"), + ], + }, +]; + +const FLAT_EVENTS = []; +for (const category of AVAILABLE_BREAKPOINTS) { + for (const event of category.items) { + FLAT_EVENTS.push(event); + } +} +const EVENTS_BY_ID = {}; +for (const event of FLAT_EVENTS) { + if (EVENTS_BY_ID[event.id]) { + throw new Error("Duplicate event ID detected: " + event.id); + } + EVENTS_BY_ID[event.id] = event; +} + +const SIMPLE_EVENTS = {}; +const DOM_EVENTS = {}; +for (const eventBP of FLAT_EVENTS) { + if (eventBP.type === "simple") { + const { notificationType } = eventBP; + if (SIMPLE_EVENTS[notificationType]) { + throw new Error("Duplicate simple event"); + } + SIMPLE_EVENTS[notificationType] = eventBP.id; + } else if (eventBP.type === "event") { + const { eventType, filter } = eventBP; + + let targetTypes; + if (filter === "global") { + targetTypes = ["global"]; + } else if (filter === "xhr") { + targetTypes = ["xhr"]; + } else if (filter === "worker") { + targetTypes = ["worker"]; + } else if (filter === "general") { + targetTypes = ["global", "node"]; + } else if (filter === "node" || filter === "media") { + targetTypes = ["node"]; + } else { + throw new Error("Unexpected filter type"); + } + + for (const targetType of targetTypes) { + let byEventType = DOM_EVENTS[targetType]; + if (!byEventType) { + byEventType = {}; + DOM_EVENTS[targetType] = byEventType; + } + + if (byEventType[eventType]) { + throw new Error("Duplicate dom event: " + eventType); + } + byEventType[eventType] = eventBP.id; + } + } else { + throw new Error("Unknown type: " + eventBP.type); + } +} + +exports.eventBreakpointForNotification = eventBreakpointForNotification; +function eventBreakpointForNotification(dbg, notification) { + const notificationType = notification.type; + + if (notification.type === "domEvent") { + const domEventNotification = DOM_EVENTS[notification.targetType]; + if (!domEventNotification) { + return null; + } + + // The 'event' value is a cross-compartment wrapper for the DOM Event object. + // While we could use that directly in the main thread as an Xray wrapper, + // when debugging workers we can't, because it is an opaque wrapper. + // To make things work, we have to always interact with the Event object via + // the Debugger.Object interface. + const evt = dbg + .makeGlobalObjectReference(notification.global) + .makeDebuggeeValue(notification.event); + + const eventType = evt.getProperty("type").return; + const id = domEventNotification[eventType]; + if (!id) { + return null; + } + const eventBreakpoint = EVENTS_BY_ID[id]; + + if (eventBreakpoint.filter === "media") { + const currentTarget = evt.getProperty("currentTarget").return; + if (!currentTarget) { + return null; + } + + const nodeType = currentTarget.getProperty("nodeType").return; + const namespaceURI = currentTarget.getProperty("namespaceURI").return; + if ( + nodeType !== 1 /* ELEMENT_NODE */ || + namespaceURI !== "http://www.w3.org/1999/xhtml" + ) { + return null; + } + + const nodeName = + currentTarget.getProperty("nodeName").return.toLowerCase(); + if (nodeName !== "audio" && nodeName !== "video") { + return null; + } + } + + return id; + } + + return SIMPLE_EVENTS[notificationType] || null; +} + +exports.makeEventBreakpointMessage = makeEventBreakpointMessage; +function makeEventBreakpointMessage(id) { + return EVENTS_BY_ID[id].message; +} + exports.getAvailableEventBreakpoints = getAvailableEventBreakpoints; function getAvailableEventBreakpoints() { const available = []; for (const { name, items } of AVAILABLE_BREAKPOINTS) { available.push({ name, events: items.map(item => ({ id: item.id, - name: item.eventType, + name: item.name, })), }); } return available; } - -function event(groupID, name, filter = "content") { - return { - id: `${groupID}.event.${name}`, - type: "event", - eventType: name, - filter, - }; -} - -const AVAILABLE_BREAKPOINTS = [ - { - name: "Clipboard", - items: [ - event("clipboard", "copy"), - event("clipboard", "cut"), - event("clipboard", "paste"), - event("clipboard", "beforecopy"), - event("clipboard", "beforecut"), - event("clipboard", "beforepaste"), - ], - }, - { - name: "Control", - items: [ - event("control", "resize"), - event("control", "scroll"), - event("control", "zoom"), - event("control", "focus"), - event("control", "blur"), - event("control", "select"), - event("control", "change"), - event("control", "submit"), - event("control", "reset"), - ], - }, - { - name: "DOM Mutation", - items: [ - // Deprecated DOM events. - event("dom-mutation", "DOMActivate"), - event("dom-mutation", "DOMFocusIn"), - event("dom-mutation", "DOMFocusOut"), - - // Standard DOM mutation events. - event("dom-mutation", "DOMAttrModified"), - event("dom-mutation", "DOMCharacterDataModified"), - event("dom-mutation", "DOMNodeInserted"), - event("dom-mutation", "DOMNodeInsertedIntoDocument"), - event("dom-mutation", "DOMNodeRemoved"), - event("dom-mutation", "DOMNodeRemovedIntoDocument"), - event("dom-mutation", "DOMSubtreeModified"), - - // DOM load events. - event("dom-mutation", "DOMContentLoaded"), - ], - }, - { - name: "Device", - items: [ - event("device", "deviceorientation"), - event("device", "devicemotion"), - ], - }, - { - name: "Drag and Drop", - items: [ - event("drag-and-drop", "drag"), - event("drag-and-drop", "dragstart"), - event("drag-and-drop", "dragend"), - event("drag-and-drop", "dragenter"), - event("drag-and-drop", "dragover"), - event("drag-and-drop", "dragleave"), - event("drag-and-drop", "drop"), - ], - }, - { - name: "Keyboard", - items: [ - event("keyboard", "keydown"), - event("keyboard", "keyup"), - event("keyboard", "keypress"), - event("keyboard", "input"), - ], - }, - { - name: "Load", - items: [ - event("load", "load", "global"), - event("load", "beforeunload", "global"), - event("load", "unload", "global"), - event("load", "abort", "global"), - event("load", "error", "global"), - event("load", "hashchange", "global"), - event("load", "popstate", "global"), - ], - }, - { - name: "Media", - items: [ - event("media", "play", "media"), - event("media", "pause", "media"), - event("media", "playing", "media"), - event("media", "canplay", "media"), - event("media", "canplaythrough", "media"), - event("media", "seeking", "media"), - event("media", "seeked", "media"), - event("media", "timeupdate", "media"), - event("media", "ended", "media"), - event("media", "ratechange", "media"), - event("media", "durationchange", "media"), - event("media", "volumechange", "media"), - event("media", "loadstart", "media"), - event("media", "progress", "media"), - event("media", "suspend", "media"), - event("media", "abort", "media"), - event("media", "error", "media"), - event("media", "emptied", "media"), - event("media", "stalled", "media"), - event("media", "loadedmetadata", "media"), - event("media", "loadeddata", "media"), - event("media", "waiting", "media"), - ], - }, - { - name: "Mouse", - items: [ - event("mouse", "auxclick"), - event("mouse", "click"), - event("mouse", "dblclick"), - event("mouse", "mousedown"), - event("mouse", "mouseup"), - event("mouse", "mouseover"), - event("mouse", "mousemove"), - event("mouse", "mouseout"), - event("mouse", "mouseenter"), - event("mouse", "mouseleave"), - event("mouse", "mousewheel"), - event("mouse", "wheel"), - event("mouse", "contextmenu"), - ], - }, - { - name: "Pointer", - items: [ - event("pointer", "pointerover"), - event("pointer", "pointerout"), - event("pointer", "pointerenter"), - event("pointer", "pointerleave"), - event("pointer", "pointerdown"), - event("pointer", "pointerup"), - event("pointer", "pointermove"), - event("pointer", "pointercancel"), - event("pointer", "gotpointercapture"), - event("pointer", "lostpointercapture"), - ], - }, - { - name: "Touch", - items: [ - event("touch", "touchstart"), - event("touch", "touchmove"), - event("touch", "touchend"), - event("touch", "touchcancel"), - ], - }, - { - name: "Worker", - items: [ - event("worker", "message", "global"), - event("worker", "messageerror", "global"), - ], - }, - { - name: "XHR", - items: [ - event("xhr", "readystatechange", "xhr"), - event("xhr", "load", "xhr"), - event("xhr", "loadstart", "xhr"), - event("xhr", "loadend", "xhr"), - event("xhr", "abort", "xhr"), - event("xhr", "error", "xhr"), - event("xhr", "progress", "xhr"), - event("xhr", "timeout", "xhr"), - ], - }, -];
--- a/devtools/server/actors/utils/make-debugger.js +++ b/devtools/server/actors/utils/make-debugger.js @@ -59,25 +59,33 @@ const { reportException } = require("dev */ module.exports = function makeDebugger({ findDebuggees, shouldAddNewGlobalAsDebuggee }) { const dbg = isReplaying ? new ReplayDebugger() : new Debugger(); EventEmitter.decorate(dbg); dbg.allowUnobservedAsmJS = true; dbg.uncaughtExceptionHook = reportDebuggerHookException; + function onNewDebuggee(global) { + if (dbg.onNewDebuggee) { + dbg.onNewDebuggee(global); + } + } + dbg.onNewGlobalObject = function(global) { if (shouldAddNewGlobalAsDebuggee(global)) { safeAddDebuggee(this, global); + onNewDebuggee(global); } }; dbg.addDebuggees = function() { for (const global of findDebuggees(this)) { safeAddDebuggee(this, global); + onNewDebuggee(global); } }; return dbg; }; const reportDebuggerHookException = e => reportException("Debugger Hook", e);
--- a/devtools/shared/builtin-modules.js +++ b/devtools/shared/builtin-modules.js @@ -18,16 +18,17 @@ const promise = require("resource://gre/ const jsmScope = require("resource://devtools/shared/Loader.jsm"); const { Services } = require("resource://gre/modules/Services.jsm"); const systemPrincipal = Services.scriptSecurityManager.getSystemPrincipal(); // Steal various globals only available in JSM scope (and not Sandbox one) const { console, + DebuggerNotificationObserver, DOMPoint, DOMQuad, DOMRect, HeapSnapshot, NamedNodeMap, NodeFilter, StructuredCloneHolder, TelemetryStopwatch, @@ -224,16 +225,17 @@ function lazyRequireGetter(obj, property configurable: true, enumerable: true, }); } // List of pseudo modules exposed to all devtools modules. exports.modules = { ChromeUtils, + DebuggerNotificationObserver, HeapSnapshot, promise, // Expose "chrome" Promise, which aren't related to any document // and so are never frozen, even if the browser loader module which // pull it is destroyed. See bug 1402779. Promise, Services: Object.create(Services), TelemetryStopwatch,
--- a/devtools/shared/client/constants.js +++ b/devtools/shared/client/constants.js @@ -39,16 +39,17 @@ const UnsolicitedNotifications = { * response to a client request. */ const UnsolicitedPauses = { resumeLimit: "resumeLimit", debuggerStatement: "debuggerStatement", breakpoint: "breakpoint", DOMEvent: "DOMEvent", watchpoint: "watchpoint", + eventBreakpoint: "eventBreakpoint", exception: "exception", replayForcedPause: "replayForcedPause", }; module.exports = { ThreadStateTypes, UnsolicitedNotifications, UnsolicitedPauses,
--- a/devtools/shared/fronts/webconsole.js +++ b/devtools/shared/fronts/webconsole.js @@ -92,16 +92,17 @@ class WebConsoleFront extends FrontClass // track the list of network event updates updates: [], private: actor.private, fromCache: actor.fromCache, fromServiceWorker: actor.fromServiceWorker, isThirdPartyTrackingResource: actor.isThirdPartyTrackingResource, referrerPolicy: actor.referrerPolicy, blockedReason: actor.blockedReason, + channelId: actor.channelId, }; this._networkRequests.set(actor.actor, networkInfo); this.emit("networkEvent", networkInfo); } /** * The "networkEventUpdate" message type handler. We redirect any message to
--- a/devtools/shared/worker/loader.js +++ b/devtools/shared/worker/loader.js @@ -1,15 +1,15 @@ /* 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"; -/* global worker */ +/* global worker, DebuggerNotificationObserver */ // A CommonJS module loader that is designed to run inside a worker debugger. // We can't simply use the SDK module loader, because it relies heavily on // Components, which isn't available in workers. // // In principle, the standard instance of the worker loader should provide the // same built-in modules as its devtools counterpart, so that both loaders are // interchangable on the main thread, making them easier to test. @@ -571,16 +571,17 @@ this.worker = new WorkerDebuggerLoader({ }, loadSubScript: loadSubScript, modules: { "Debugger": Debugger, "Services": Object.create(null), "chrome": chrome, "xpcInspector": xpcInspector, "ChromeUtils": ChromeUtils, + "DebuggerNotificationObserver": DebuggerNotificationObserver, }, paths: { // âš DISCUSSION ON DEV-DEVELOPER-TOOLS REQUIRED BEFORE MODIFYING âš "devtools": "resource://devtools", // âš DISCUSSION ON DEV-DEVELOPER-TOOLS REQUIRED BEFORE MODIFYING âš "promise": "resource://gre/modules/Promise-backend.js", // âš DISCUSSION ON DEV-DEVELOPER-TOOLS REQUIRED BEFORE MODIFYING âš "xpcshell-test": "resource://test",
--- a/dom/base/DOMMatrix.cpp +++ b/dom/base/DOMMatrix.cpp @@ -37,36 +37,42 @@ JSObject* DOMMatrixReadOnly::WrapObject( JS::Handle<JSObject*> aGivenProto) { return DOMMatrixReadOnly_Binding::Wrap(aCx, this, aGivenProto); } already_AddRefed<DOMMatrixReadOnly> DOMMatrixReadOnly::Constructor( const GlobalObject& aGlobal, const Optional<StringOrUnrestrictedDoubleSequence>& aArg, ErrorResult& aRv) { - RefPtr<DOMMatrixReadOnly> rval = - new DOMMatrixReadOnly(aGlobal.GetAsSupports()); if (!aArg.WasPassed()) { + RefPtr<DOMMatrixReadOnly> rval = + new DOMMatrixReadOnly(aGlobal.GetAsSupports()); return rval.forget(); } const auto& arg = aArg.Value(); if (arg.IsString()) { nsCOMPtr<nsPIDOMWindowInner> win = do_QueryInterface(aGlobal.GetAsSupports()); if (!win) { aRv.ThrowTypeError<MSG_ILLEGAL_CONSTRUCTOR>(); return nullptr; } + RefPtr<DOMMatrixReadOnly> rval = + new DOMMatrixReadOnly(aGlobal.GetAsSupports()); rval->SetMatrixValue(arg.GetAsString(), aRv); - } else { - const auto& sequence = arg.GetAsUnrestrictedDoubleSequence(); - SetDataInMatrix(rval, sequence.Elements(), sequence.Length(), aRv); + return rval.forget(); } + const auto& sequence = arg.GetAsUnrestrictedDoubleSequence(); + const int length = sequence.Length(); + const bool is2D = length == 6; + RefPtr<DOMMatrixReadOnly> rval = + new DOMMatrixReadOnly(aGlobal.GetAsSupports(), is2D); + SetDataInMatrix(rval, sequence.Elements(), length, aRv); return rval.forget(); } already_AddRefed<DOMMatrixReadOnly> DOMMatrixReadOnly::ReadStructuredClone( nsISupports* aParent, JSStructuredCloneReader* aReader) { uint8_t is2D; if (!JS_ReadBytes(aReader, &is2D, 1)) { @@ -504,39 +510,46 @@ static void SetDataInMatrix(DOMMatrixRea lengthStr.AppendInt(aLength); aRv.ThrowTypeError<MSG_MATRIX_INIT_LENGTH_WRONG>(lengthStr); } } already_AddRefed<DOMMatrix> DOMMatrix::Constructor(const GlobalObject& aGlobal, const Float32Array& aArray32, ErrorResult& aRv) { - RefPtr<DOMMatrix> obj = new DOMMatrix(aGlobal.GetAsSupports()); aArray32.ComputeLengthAndData(); - SetDataInMatrix(obj, aArray32.Data(), aArray32.Length(), aRv); + + const int length = aArray32.Length(); + const bool is2D = length == 6; + RefPtr<DOMMatrix> obj = new DOMMatrix(aGlobal.GetAsSupports(), is2D); + SetDataInMatrix(obj, aArray32.Data(), length, aRv); return obj.forget(); } already_AddRefed<DOMMatrix> DOMMatrix::Constructor(const GlobalObject& aGlobal, const Float64Array& aArray64, ErrorResult& aRv) { - RefPtr<DOMMatrix> obj = new DOMMatrix(aGlobal.GetAsSupports()); aArray64.ComputeLengthAndData(); - SetDataInMatrix(obj, aArray64.Data(), aArray64.Length(), aRv); + + const int length = aArray64.Length(); + const bool is2D = length == 6; + RefPtr<DOMMatrix> obj = new DOMMatrix(aGlobal.GetAsSupports(), is2D); + SetDataInMatrix(obj, aArray64.Data(), length, aRv); return obj.forget(); } already_AddRefed<DOMMatrix> DOMMatrix::Constructor( const GlobalObject& aGlobal, const Sequence<double>& aNumberSequence, ErrorResult& aRv) { - RefPtr<DOMMatrix> obj = new DOMMatrix(aGlobal.GetAsSupports()); - SetDataInMatrix(obj, aNumberSequence.Elements(), aNumberSequence.Length(), - aRv); + const int length = aNumberSequence.Length(); + const bool is2D = length == 6; + RefPtr<DOMMatrix> obj = new DOMMatrix(aGlobal.GetAsSupports(), is2D); + SetDataInMatrix(obj, aNumberSequence.Elements(), length, aRv); return obj.forget(); } already_AddRefed<DOMMatrix> DOMMatrix::ReadStructuredClone( nsISupports* aParent, JSStructuredCloneReader* aReader) { uint8_t is2D; @@ -765,17 +778,19 @@ DOMMatrixReadOnly* DOMMatrixReadOnly::Se if (!ServoCSSParser::ParseTransformIntoMatrix( aTransformList, contains3dTransform, transform)) { aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR); return nullptr; } if (!contains3dTransform) { mMatrix3D = nullptr; - mMatrix2D = new gfx::MatrixDouble(); + if (!mMatrix2D) { + mMatrix2D = new gfx::MatrixDouble(); + } SetA(transform._11); SetB(transform._12); SetC(transform._21); SetD(transform._22); SetE(transform._41); SetF(transform._42); } else {
--- a/dom/bindings/Codegen.py +++ b/dom/bindings/Codegen.py @@ -2426,17 +2426,17 @@ class MethodDefiner(PropertyDefiner): raise TypeError("Cannot have indexed getter/attr on " "interface %s with other members " "that generate @@iterator, such as " "maplike/setlike or aliased functions." % self.descriptor.interface.identifier.name) self.regular.append({ "name": "@@iterator", "methodInfo": False, - "selfHostedName": "ArrayValues", + "selfHostedName": "$ArrayValues", "length": 0, "flags": "0", # Not enumerable, per spec. "condition": MemberCondition() }) # Generate the keys/values/entries aliases for value iterables. maplikeOrSetlikeOrIterable = descriptor.interface.maplikeOrSetlikeOrIterable if (not static and @@ -2452,17 +2452,17 @@ class MethodDefiner(PropertyDefiner): "length": 0, "flags": "JSPROP_ENUMERATE", "condition": PropertyDefiner.getControllingCondition(m, descriptor) }) self.regular.append({ "name": "values", "methodInfo": False, - "selfHostedName": "ArrayValues", + "selfHostedName": "$ArrayValues", "length": 0, "flags": "JSPROP_ENUMERATE", "condition": PropertyDefiner.getControllingCondition(m, descriptor) }) self.regular.append({ "name": "entries", "methodInfo": False,
--- a/dom/gamepad/Gamepad.cpp +++ b/dom/gamepad/Gamepad.cpp @@ -65,16 +65,19 @@ Gamepad::Gamepad(nsISupports* aParent, c for (uint32_t i = 0; i < aNumLightIndicator; ++i) { mLightIndicators.AppendElement( new GamepadLightIndicator(mParent, mHashKey, i)); } for (uint32_t i = 0; i < aNumTouchEvents; ++i) { mTouchEvents.AppendElement(new GamepadTouch(mParent)); } + // Mapping touchId(0) to touchIdHash(0) by default. + mTouchIdHash.Put(0, mTouchIdHashValue); + ++mTouchIdHashValue; UpdateTimestamp(); } void Gamepad::SetIndex(uint32_t aIndex) { mIndex = aIndex; } void Gamepad::SetConnected(bool aConnected) { mConnected = aConnected; } void Gamepad::SetButton(uint32_t aButton, bool aPressed, bool aTouched,
--- a/dom/gamepad/GamepadRemapping.cpp +++ b/dom/gamepad/GamepadRemapping.cpp @@ -356,16 +356,21 @@ class StadiaControllerRemapper final : p STADIA_BUTTON_EXTRA1 = BUTTON_INDEX_COUNT, STADIA_BUTTON_EXTRA2, STADIA_BUTTON_COUNT }; }; class Dualshock4Remapper final : public GamepadRemapper { public: + Dualshock4Remapper() { + mLastTouches.SetLength(TOUCH_EVENT_COUNT); + mLastTouchId.SetLength(TOUCH_EVENT_COUNT); + } + virtual uint32_t GetAxisCount() const override { return AXIS_INDEX_COUNT; } virtual uint32_t GetButtonCount() const override { return DUALSHOCK_BUTTON_COUNT; } virtual uint32_t GetLightIndicatorCount() const override { return LIGHT_INDICATOR_COUNT; @@ -398,64 +403,69 @@ class Dualshock4Remapper final : public aReport[8] = aBlue; } virtual void GetTouchData(uint32_t aIndex, void* aInput) override { nsTArray<GamepadTouchState> touches(TOUCH_EVENT_COUNT); touches.SetLength(TOUCH_EVENT_COUNT); uint8_t* rawData = (uint8_t*)aInput; + const uint32_t kTouchDimensionX = 1920; + const uint32_t kTouchDimensionY = 942; bool touch0Pressed = (((rawData[35] & 0xff) >> 7) == 0); bool touch1Pressed = (((rawData[39] & 0xff) >> 7) == 0); - if (!touch0Pressed && !touch1Pressed) { - return; - } - - if ((touch0Pressed && (rawData[35] & 0xff) < mLastTouch0Id) || - (touch1Pressed && (rawData[39] & 0xff) < mLastTouch1Id)) { + if ((touch0Pressed && (rawData[35] & 0xff) < mLastTouchId[0]) || + (touch1Pressed && (rawData[39] & 0xff) < mLastTouchId[1])) { mTouchIdBase += 128; } - const uint32_t kTouchDimensionX = 1920; - const uint32_t kTouchDimensionY = 942; - - touches[0].touchId = mTouchIdBase + (rawData[35] & 0x7f); - touches[0].surfaceId = 0; - touches[0].position[0] = NormalizeTouch( - ((rawData[37] & 0xf) << 8) | rawData[36], 0, (kTouchDimensionX - 1)); - touches[0].position[1] = - NormalizeTouch(rawData[38] << 4 | ((rawData[37] & 0xf0) >> 4), 0, - (kTouchDimensionY - 1)); - touches[0].surfaceDimensions[0] = kTouchDimensionX; - touches[0].surfaceDimensions[1] = kTouchDimensionY; - touches[0].isSurfaceDimensionsValid = true; - mLastTouch0Id = rawData[35] & 0x7f; - - touches[1].touchId = mTouchIdBase + (rawData[39] & 0x7f); - touches[1].surfaceId = 0; - touches[1].position[0] = - NormalizeTouch((((rawData[41] & 0xf) << 8) | rawData[40]) + 1, 0, - (kTouchDimensionX - 1)); - touches[1].position[1] = - NormalizeTouch(rawData[42] << 4 | ((rawData[41] & 0xf0) >> 4), 0, - (kTouchDimensionY - 1)); - touches[1].surfaceDimensions[0] = kTouchDimensionX; - touches[1].surfaceDimensions[1] = kTouchDimensionY; - touches[1].isSurfaceDimensionsValid = true; - mLastTouch1Id = rawData[39] & 0x7f; + if (touch0Pressed) { + touches[0].touchId = mTouchIdBase + (rawData[35] & 0x7f); + touches[0].surfaceId = 0; + touches[0].position[0] = NormalizeTouch( + ((rawData[37] & 0xf) << 8) | rawData[36], 0, (kTouchDimensionX - 1)); + touches[0].position[1] = + NormalizeTouch(rawData[38] << 4 | ((rawData[37] & 0xf0) >> 4), 0, + (kTouchDimensionY - 1)); + touches[0].surfaceDimensions[0] = kTouchDimensionX; + touches[0].surfaceDimensions[1] = kTouchDimensionY; + touches[0].isSurfaceDimensionsValid = true; + mLastTouchId[0] = rawData[35] & 0x7f; + } + if (touch1Pressed) { + touches[1].touchId = mTouchIdBase + (rawData[39] & 0x7f); + touches[1].surfaceId = 0; + touches[1].position[0] = + NormalizeTouch((((rawData[41] & 0xf) << 8) | rawData[40]) + 1, 0, + (kTouchDimensionX - 1)); + touches[1].position[1] = + NormalizeTouch(rawData[42] << 4 | ((rawData[41] & 0xf0) >> 4), 0, + (kTouchDimensionY - 1)); + touches[1].surfaceDimensions[0] = kTouchDimensionX; + touches[1].surfaceDimensions[1] = kTouchDimensionY; + touches[1].isSurfaceDimensionsValid = true; + mLastTouchId[1] = rawData[39] & 0x7f; + } RefPtr<GamepadPlatformService> service = GamepadPlatformService::GetParentService(); if (!service) { return; } - service->NewMultiTouchEvent(aIndex, 0, touches[0]); - service->NewMultiTouchEvent(aIndex, 1, touches[1]); + // Avoid to send duplicate untouched events to the gamepad service. + if ((mLastTouches[0] != touch0Pressed) || touch0Pressed) { + service->NewMultiTouchEvent(aIndex, 0, touches[0]); + } + if ((mLastTouches[1] != touch1Pressed) || touch1Pressed) { + service->NewMultiTouchEvent(aIndex, 1, touches[1]); + } + mLastTouches[0] = touch0Pressed; + mLastTouches[1] = touch1Pressed; } virtual void RemapAxisMoveEvent(uint32_t aIndex, uint32_t aAxis, double aValue) const override { RefPtr<GamepadPlatformService> service = GamepadPlatformService::GetParentService(); if (!service) { return; @@ -532,18 +542,18 @@ class Dualshock4Remapper final : public enum Dualshock4Buttons { DUALSHOCK_BUTTON_TOUCHPAD = BUTTON_INDEX_COUNT, DUALSHOCK_BUTTON_COUNT }; static const uint32_t LIGHT_INDICATOR_COUNT = 1; static const uint32_t TOUCH_EVENT_COUNT = 2; - unsigned long mLastTouch0Id = 0; - unsigned long mLastTouch1Id = 0; + nsTArray<unsigned long> mLastTouchId; + nsTArray<bool> mLastTouches; unsigned long mTouchIdBase = 0; }; class LogitechDInputRemapper final : public GamepadRemapper { public: virtual uint32_t GetAxisCount() const override { return AXIS_INDEX_COUNT; } virtual uint32_t GetButtonCount() const override {
--- a/dom/html/HTMLMediaElement.cpp +++ b/dom/html/HTMLMediaElement.cpp @@ -4056,20 +4056,16 @@ nsresult HTMLMediaElement::BindToTree(Bi nsresult rv = nsGenericHTMLElement::BindToTree(aContext, aParent); if (IsInComposedDoc()) { // Construct Shadow Root so web content can be hidden in the DOM. AttachAndSetUAShadowRoot(); NotifyUAWidgetSetupOrChange(); } - // FIXME(emilio, bug 1555946): mUnboundFromTree doesn't make any sense, should - // just use IsInComposedDoc() in the relevant places or something. - mUnboundFromTree = false; - if (IsInUncomposedDoc()) { // The preload action depends on the value of the autoplay attribute. // It's value may have changed, so update it. UpdatePreloadAction(); } NotifyDecoderActivityChanges(); @@ -4273,36 +4269,42 @@ void HTMLMediaElement::ReportTelemetry() this, key.get())); } } } } } void HTMLMediaElement::UnbindFromTree(bool aNullParent) { - mUnboundFromTree = true; mVisibilityState = Visibility::Untracked; if (IsInComposedDoc()) { NotifyUAWidgetTeardown(); } nsGenericHTMLElement::UnbindFromTree(aNullParent); MOZ_ASSERT(IsHidden()); NotifyDecoderActivityChanges(); - RefPtr<HTMLMediaElement> self(this); - nsCOMPtr<nsIRunnable> task = - NS_NewRunnableFunction("dom::HTMLMediaElement::UnbindFromTree", [self]() { - if (self->mUnboundFromTree) { - self->Pause(); - } - }); - RunInStableState(task); + // Dispatch a task to run once we're in a stable state which ensures we're + // paused if we're no longer in a document. Note we set a flag here to + // ensure we don't dispatch redundant tasks. + if (!mDispatchedTaskToPauseIfNotInDocument) { + mDispatchedTaskToPauseIfNotInDocument = true; + nsCOMPtr<nsIRunnable> task = NS_NewRunnableFunction( + "dom::HTMLMediaElement::UnbindFromTree", + [self = RefPtr<HTMLMediaElement>(this)]() { + self->mDispatchedTaskToPauseIfNotInDocument = false; + if (!self->IsInComposedDoc()) { + self->Pause(); + } + }); + RunInStableState(task); + } } /* static */ CanPlayStatus HTMLMediaElement::GetCanPlay( const nsAString& aType, DecoderDoctorDiagnostics* aDiagnostics) { Maybe<MediaContainerType> containerType = MakeMediaContainerType(aType); if (!containerType) { return CANPLAY_NO; @@ -5691,17 +5693,17 @@ void HTMLMediaElement::CheckAutoplayData } bool HTMLMediaElement::IsActive() const { Document* ownerDoc = OwnerDoc(); return ownerDoc && ownerDoc->IsActive() && ownerDoc->IsVisible(); } bool HTMLMediaElement::IsHidden() const { - return mUnboundFromTree || OwnerDoc()->Hidden(); + return !IsInComposedDoc() || OwnerDoc()->Hidden(); } VideoFrameContainer* HTMLMediaElement::GetVideoFrameContainer() { if (mShuttingDown) { return nullptr; } if (mVideoFrameContainer) return mVideoFrameContainer;
--- a/dom/html/HTMLMediaElement.h +++ b/dom/html/HTMLMediaElement.h @@ -1666,20 +1666,21 @@ class HTMLMediaElement : public nsGeneri RefPtr<VideoTrackList> mVideoTrackList; UniquePtr<MediaStreamTrackListener> mMediaStreamTrackListener; // The principal guarding mVideoFrameContainer access when playing a // MediaStream. nsCOMPtr<nsIPrincipal> mSrcStreamVideoPrincipal; - // True if UnbindFromTree() is called on the element. - // Note this flag is false when the element is in a phase after creation and - // before attaching to the DOM tree. - bool mUnboundFromTree = false; + // True if we've dispatched a task in UnbindFromTree() which runs in a + // stable state and attempts to pause playback if we're not in a composed + // document. The flag stops us dispatching multiple tasks if the element + // is involved in a series of append/remove cycles. + bool mDispatchedTaskToPauseIfNotInDocument = false; // True if the autoplay media was blocked because it hadn't loaded metadata // yet. bool mBlockedAsWithoutMetadata = false; // This promise is used to notify MediaElementAudioSourceNode that media // element is allowed to play when MediaElement is used as a source for web // audio.
--- a/dom/html/HTMLVideoElement.cpp +++ b/dom/html/HTMLVideoElement.cpp @@ -381,40 +381,40 @@ void HTMLVideoElement::ReleaseVideoWakeL mScreenWakeLock = nullptr; return; } } bool HTMLVideoElement::SetVisualCloneTarget( HTMLVideoElement* aVisualCloneTarget) { MOZ_DIAGNOSTIC_ASSERT( - !aVisualCloneTarget || !aVisualCloneTarget->mUnboundFromTree, + !aVisualCloneTarget || aVisualCloneTarget->IsInComposedDoc(), "Can't set the clone target to a disconnected video " "element."); MOZ_DIAGNOSTIC_ASSERT(!mVisualCloneSource, "Can't clone a video element that is already a clone."); if (!aVisualCloneTarget || - (!aVisualCloneTarget->mUnboundFromTree && !mVisualCloneSource)) { + (aVisualCloneTarget->IsInComposedDoc() && !mVisualCloneSource)) { mVisualCloneTarget = aVisualCloneTarget; return true; } return false; } bool HTMLVideoElement::SetVisualCloneSource( HTMLVideoElement* aVisualCloneSource) { MOZ_DIAGNOSTIC_ASSERT( - !aVisualCloneSource || !aVisualCloneSource->mUnboundFromTree, + !aVisualCloneSource || aVisualCloneSource->IsInComposedDoc(), "Can't set the clone source to a disconnected video " "element."); MOZ_DIAGNOSTIC_ASSERT(!mVisualCloneTarget, "Can't clone a video element that is already a " "clone."); if (!aVisualCloneSource || - (!aVisualCloneSource->mUnboundFromTree && !mVisualCloneTarget)) { + (aVisualCloneSource->IsInComposedDoc() && !mVisualCloneTarget)) { mVisualCloneSource = aVisualCloneSource; return true; } return false; } /* static */ void HTMLVideoElement::InitStatics() { @@ -447,21 +447,21 @@ double HTMLVideoElement::TotalPlayTime() } } return total; } void HTMLVideoElement::CloneElementVisually(HTMLVideoElement& aTargetVideo, ErrorResult& rv) { - MOZ_ASSERT(!mUnboundFromTree, + MOZ_ASSERT(IsInComposedDoc(), "Can't clone a video that's not bound to a DOM tree."); - MOZ_ASSERT(!aTargetVideo.mUnboundFromTree, + MOZ_ASSERT(aTargetVideo.IsInComposedDoc(), "Can't clone to a video that's not bound to a DOM tree."); - if (mUnboundFromTree || aTargetVideo.mUnboundFromTree) { + if (!IsInComposedDoc() || !aTargetVideo.IsInComposedDoc()) { rv.Throw(NS_ERROR_UNEXPECTED); return; } // Do we already have a visual clone target? If so, shut it down. if (mVisualCloneTarget) { EndCloningVisually(); }
--- a/dom/ipc/BrowserChild.cpp +++ b/dom/ipc/BrowserChild.cpp @@ -3327,24 +3327,26 @@ bool BrowserChild::DeallocPBrowserBridge ScreenIntSize BrowserChild::GetInnerSize() { LayoutDeviceIntSize innerSize = RoundedToInt(mUnscaledInnerSize * mPuppetWidget->GetDefaultScale()); return ViewAs<ScreenPixel>( innerSize, PixelCastJustification::LayoutDeviceIsScreenForTabDims); }; -nsRect BrowserChild::GetVisibleRect() { +LayoutDeviceIntRect BrowserChild::GetVisibleRect() { + CSSRect visibleRect; if (mIsTopLevel) { // We are conservative about visible rects for top-level browsers to avoid // artifacts when resizing - return nsRect(nsPoint(), CSSPixel::ToAppUnits(mUnscaledInnerSize)); + visibleRect = CSSRect(CSSPoint(), mUnscaledInnerSize); } else { - return mEffectsInfo.mVisibleRect; + visibleRect = CSSPixel::FromAppUnits(mEffectsInfo.mVisibleRect); } + return RoundedToInt(visibleRect * mPuppetWidget->GetDefaultScale()); } ScreenIntRect BrowserChild::GetOuterRect() { LayoutDeviceIntRect outerRect = RoundedToInt(mUnscaledOuterRect * mPuppetWidget->GetDefaultScale()); return ViewAs<ScreenPixel>( outerRect, PixelCastJustification::LayoutDeviceIsScreenForTabDims); }
--- a/dom/ipc/BrowserChild.h +++ b/dom/ipc/BrowserChild.h @@ -544,17 +544,17 @@ class BrowserChild final : public nsMess bool ParentIsActive() const { return mParentIsActive; } const mozilla::layers::CompositorOptions& GetCompositorOptions() const; bool AsyncPanZoomEnabled() const; ScreenIntSize GetInnerSize(); - nsRect GetVisibleRect(); + LayoutDeviceIntRect GetVisibleRect(); // Call RecvShow(nsIntSize(0, 0)) and block future calls to RecvShow(). void DoFakeShow(const ShowInfo& aShowInfo); void ContentReceivedInputBlock(uint64_t aInputBlockId, bool aPreventDefault) const; void SetTargetAPZC( uint64_t aInputBlockId,
--- a/dom/media/webaudio/MediaElementAudioSourceNode.cpp +++ b/dom/media/webaudio/MediaElementAudioSourceNode.cpp @@ -9,17 +9,18 @@ #include "AudioDestinationNode.h" #include "nsIScriptError.h" #include "AudioNodeStream.h" namespace mozilla { namespace dom { MediaElementAudioSourceNode::MediaElementAudioSourceNode(AudioContext* aContext) - : MediaStreamAudioSourceNode(aContext) {} + : MediaStreamAudioSourceNode(aContext, TrackChangeBehavior::FollowChanges) { +} /* static */ already_AddRefed<MediaElementAudioSourceNode> MediaElementAudioSourceNode::Create( AudioContext& aAudioContext, const MediaElementAudioSourceOptions& aOptions, ErrorResult& aRv) { if (aAudioContext.IsOffline()) { aRv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
--- a/dom/media/webaudio/MediaStreamAudioSourceNode.cpp +++ b/dom/media/webaudio/MediaStreamAudioSourceNode.cpp @@ -8,16 +8,17 @@ #include "mozilla/dom/MediaStreamAudioSourceNodeBinding.h" #include "AudioNodeEngine.h" #include "AudioNodeExternalInputStream.h" #include "AudioStreamTrack.h" #include "mozilla/dom/Document.h" #include "mozilla/CORSMode.h" #include "nsContentUtils.h" #include "nsIScriptError.h" +#include "nsID.h" namespace mozilla { namespace dom { NS_IMPL_CYCLE_COLLECTION_CLASS(MediaStreamAudioSourceNode) NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(MediaStreamAudioSourceNode) tmp->Destroy(); @@ -32,19 +33,21 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(MediaStreamAudioSourceNode) NS_INTERFACE_MAP_END_INHERITING(AudioNode) NS_IMPL_ADDREF_INHERITED(MediaStreamAudioSourceNode, AudioNode) NS_IMPL_RELEASE_INHERITED(MediaStreamAudioSourceNode, AudioNode) -MediaStreamAudioSourceNode::MediaStreamAudioSourceNode(AudioContext* aContext) +MediaStreamAudioSourceNode::MediaStreamAudioSourceNode( + AudioContext* aContext, TrackChangeBehavior aBehavior) : AudioNode(aContext, 2, ChannelCountMode::Max, - ChannelInterpretation::Speakers) {} + ChannelInterpretation::Speakers), + mBehavior(aBehavior) {} /* static */ already_AddRefed<MediaStreamAudioSourceNode> MediaStreamAudioSourceNode::Create( AudioContext& aAudioContext, const MediaStreamAudioSourceOptions& aOptions, ErrorResult& aRv) { if (aAudioContext.IsOffline()) { aRv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR); return nullptr; @@ -58,17 +61,17 @@ already_AddRefed<MediaStreamAudioSourceN NS_LITERAL_CSTRING("Web Audio"), document, nsContentUtils::eDOM_PROPERTIES, "MediaStreamAudioSourceNodeDifferentRate"); aRv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR); return nullptr; } RefPtr<MediaStreamAudioSourceNode> node = - new MediaStreamAudioSourceNode(&aAudioContext); + new MediaStreamAudioSourceNode(&aAudioContext, LockOnTrackPicked); node->Init(aOptions.mMediaStream, aRv); if (aRv.Failed()) { return nullptr; } return node.forget(); } @@ -91,17 +94,17 @@ void MediaStreamAudioSourceNode::Init(DO AudioNodeEngine* engine = new MediaStreamAudioSourceNodeEngine(this); mStream = AudioNodeExternalInputStream::Create(graph, engine); mInputStream->AddConsumerToKeepAlive(ToSupports(this)); mInputStream->RegisterTrackListener(this); if (mInputStream->Active()) { NotifyActive(); } - AttachToFirstTrack(mInputStream); + AttachToRightTrack(mInputStream, aRv); } void MediaStreamAudioSourceNode::Destroy() { if (mInputStream) { mInputStream->UnregisterTrackListener(this); mInputStream = nullptr; } DetachFromTrack(); @@ -132,56 +135,82 @@ void MediaStreamAudioSourceNode::DetachF mInputTrack = nullptr; } if (mInputPort) { mInputPort->Destroy(); mInputPort = nullptr; } } -void MediaStreamAudioSourceNode::AttachToFirstTrack( - const RefPtr<DOMMediaStream>& aMediaStream) { +static int AudioTrackCompare(const RefPtr<AudioStreamTrack>& aLhs, + const RefPtr<AudioStreamTrack>& aRhs) { + nsAutoStringN<NSID_LENGTH> IDLhs; + nsAutoStringN<NSID_LENGTH> IDRhs; + aLhs->GetId(IDLhs); + aRhs->GetId(IDRhs); + return NS_ConvertUTF16toUTF8(IDLhs).Compare( + NS_ConvertUTF16toUTF8(IDRhs).get()); +} + +void MediaStreamAudioSourceNode::AttachToRightTrack( + const RefPtr<DOMMediaStream>& aMediaStream, ErrorResult& aRv) { nsTArray<RefPtr<AudioStreamTrack>> tracks; aMediaStream->GetAudioTracks(tracks); + if (tracks.IsEmpty() && mBehavior == LockOnTrackPicked) { + aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); + return; + } + + // Sort the track to have a stable order, on their ID by lexicographic + // ordering on sequences of code unit values. + tracks.Sort(AudioTrackCompare); + for (const RefPtr<AudioStreamTrack>& track : tracks) { - if (track->Ended()) { - continue; + if (mBehavior == FollowChanges) { + if (track->Ended()) { + continue; + } } AttachToTrack(track); MarkActive(); return; } // There was no track available. We'll allow the node to be garbage collected. MarkInactive(); } void MediaStreamAudioSourceNode::NotifyTrackAdded( const RefPtr<MediaStreamTrack>& aTrack) { + if (mBehavior != FollowChanges) { + return; + } if (mInputTrack) { return; } if (!aTrack->AsAudioStreamTrack()) { return; } AttachToTrack(aTrack); } void MediaStreamAudioSourceNode::NotifyTrackRemoved( const RefPtr<MediaStreamTrack>& aTrack) { - if (aTrack != mInputTrack) { - return; + if (mBehavior == FollowChanges) { + if (aTrack != mInputTrack) { + return; + } + + DetachFromTrack(); + AttachToRightTrack(mInputStream, IgnoreErrors()); } - - DetachFromTrack(); - AttachToFirstTrack(mInputStream); } void MediaStreamAudioSourceNode::NotifyActive() { MOZ_ASSERT(mInputStream); Context()->StartBlockedAudioContextIfAllowed(); } /**
--- a/dom/media/webaudio/MediaStreamAudioSourceNode.h +++ b/dom/media/webaudio/MediaStreamAudioSourceNode.h @@ -75,34 +75,50 @@ class MediaStreamAudioSourceNode size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const override; // Attaches to aTrack so that its audio content will be used as input. void AttachToTrack(const RefPtr<MediaStreamTrack>& aTrack); // Detaches from the currently attached track if there is one. void DetachFromTrack(); - // Attaches to the first available audio track in aMediaStream. - void AttachToFirstTrack(const RefPtr<DOMMediaStream>& aMediaStream); + // Attaches to the first audio track in the MediaStream, when the tracks are + // ordered by id. + void AttachToRightTrack(const RefPtr<DOMMediaStream>& aMediaStream, + ErrorResult& aRv); // From DOMMediaStream::TrackListener. void NotifyTrackAdded(const RefPtr<MediaStreamTrack>& aTrack) override; void NotifyTrackRemoved(const RefPtr<MediaStreamTrack>& aTrack) override; void NotifyActive() override; // From PrincipalChangeObserver<MediaStreamTrack>. void PrincipalChanged(MediaStreamTrack* aMediaStreamTrack) override; + // This allows implementing the correct behaviour for both + // MediaElementAudioSourceNode and MediaStreamAudioSourceNode, that have most + // of their behaviour shared. + enum TrackChangeBehavior { + // MediaStreamAudioSourceNode locks on the track it picked, and never + // changes. + LockOnTrackPicked, + // MediaElementAudioSourceNode can change track, depending on what the + // HTMLMediaElement does. + FollowChanges + }; + protected: - explicit MediaStreamAudioSourceNode(AudioContext* aContext); + MediaStreamAudioSourceNode(AudioContext* aContext, + TrackChangeBehavior aBehavior); void Init(DOMMediaStream* aMediaStream, ErrorResult& aRv); virtual void Destroy(); virtual ~MediaStreamAudioSourceNode(); private: + const TrackChangeBehavior mBehavior; RefPtr<MediaInputPort> mInputPort; RefPtr<DOMMediaStream> mInputStream; // On construction we set this to the first audio track of mInputStream. RefPtr<MediaStreamTrack> mInputTrack; }; } // namespace dom
--- a/dom/media/webaudio/test/mochitest.ini +++ b/dom/media/webaudio/test/mochitest.ini @@ -99,17 +99,16 @@ skip-if = (os == "win" && processor == " [test_bug964376.html] [test_bug966247.html] tags=capturestream [test_bug972678.html] [test_bug1113634.html] [test_bug1118372.html] [test_bug1027864.html] [test_bug1056032.html] -skip-if = toolkit == 'android' # bug 1056706 [test_bug1255618.html] skip-if = (os == "win" && processor == "aarch64") # aarch64 due to 1538360 [test_bug1267579.html] [test_bug1355798.html] [test_bug1447273.html] skip-if = toolkit == 'android' && debug # bug 1485407 [test_channelMergerNode.html] [test_channelMergerNodeWithVolume.html] @@ -135,17 +134,16 @@ skip-if = !asan && toolkit != android [test_currentTime.html] [test_decodeAudioDataOnDetachedBuffer.html] [test_decodeAudioDataPromise.html] [test_decodeMultichannel.html] [test_decodeOpusTail.html] [test_delayNode.html] [test_delayNodeAtMax.html] [test_delayNodeChannelChanges.html] -skip-if = toolkit == 'android' # bug 1056706 [test_delayNodeCycles.html] [test_delayNodePassThrough.html] [test_delayNodeSmallMaxDelay.html] [test_delayNodeTailIncrease.html] [test_delayNodeTailWithDisconnect.html] [test_delayNodeTailWithGain.html] [test_delayNodeTailWithReconnect.html] [test_delayNodeWithGain.html]
--- a/dom/media/webaudio/test/test_audioContextSuspendResumeClose.html +++ b/dom/media/webaudio/test/test_audioContextSuspendResumeClose.html @@ -28,17 +28,17 @@ function tryToCreateNodeOnClosedContext( { name: "createChannelSplitter" }, { name: "createChannelMerger" }, { name: "createDynamicsCompressor" }, { name: "createOscillator" }, { name: "createMediaElementSource", args: [new Audio()], onOfflineAudioContext: false }, { name: "createMediaStreamSource", - args: [new Audio().mozCaptureStream()], + args: [(new AudioContext()).createMediaStreamDestination().stream], onOfflineAudioContext: false } ].forEach(function(e) { if (e.onOfflineAudioContext == false && ctx instanceof OfflineAudioContext) { return; } expectNoException(function() { @@ -69,17 +69,16 @@ function tryLegalOpeerationsOnClosedCont ].forEach(function(e) { expectNoException(function() { ctx[e.name].apply(ctx, e.args); }); }); loadFile("ting-44.1k-1ch.ogg", function(buf) { ctx.decodeAudioData(buf).then(function(decodedBuf) { ok(true, "decodeAudioData on a closed context should work, it did.") - todo(false, "0 " + (ctx instanceof OfflineAudioContext ? "Offline" : "Realtime")); finish(); }).catch(function(e){ ok(false, "decodeAudioData on a closed context should work, it did not"); finish(); }); }); } @@ -116,27 +115,24 @@ function testMultiContextOutput() { var input = e.inputBuffer.getChannelData(0); var silent = true; for (var i = 0; i < input.length; i++) { if (input[i] != 0.0) { silent = false; } } - todo(false, "input buffer is " + (silent ? "silent" : "noisy")); - if (silent) { silentBuffersInARow++; if (silentBuffersInARow == 10) { ok(true, "MediaStreams produce silence when their input is blocked."); sp2.onaudioprocess = null; ac1.close(); ac2.close(); - todo(false,"1"); finish(); } } else { is(silentBuffersInARow, 0, "No non silent buffer inbetween silent buffers."); } } @@ -177,17 +173,16 @@ function testMultiContextInput() { var delta = Math.abs(inputBuffer[1] - sp2.value), theoreticalIncrement = 2048 * 3 * Math.PI * 2 * osc1.frequency.value / ac1.sampleRate; ok(delta >= theoreticalIncrement, "Buffering did not occur when the context was suspended (delta:" + delta + " increment: " + theoreticalIncrement+")"); ac1.close(); ac2.close(); sp1.onaudioprocess = null; sp2.onaudioprocess = null; - todo(false, "2"); finish(); } } sp2.onaudioprocess = function(e) { var inputBuffer = e.inputBuffer.getChannelData(0); sp2.value = inputBuffer[inputBuffer.length - 1]; ac2.suspend().then(function() { @@ -226,17 +221,16 @@ function testScriptProcessNodeSuspended( ac.resume().then(function() { remainingIterations = 30; afterResume = true; }); }); } } else { sp.onaudioprocess = null; - todo(false,"3"); finish(); } } } sp.connect(ac.destination); } // Take an AudioContext, make sure it switches to running when the audio starts
--- a/dom/media/webaudio/test/test_mediaStreamAudioSourceNodeCrossOrigin.html +++ b/dom/media/webaudio/test/test_mediaStreamAudioSourceNodeCrossOrigin.html @@ -7,51 +7,54 @@ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> </head> <body> <pre id="test"> <script class="testbody" type="text/javascript"> SimpleTest.waitForExplicitFinish(); var audio = new Audio("http://example.org:80/tests/dom/media/webaudio/test/small-shot.ogg"); +audio.load(); var context = new AudioContext(); -var node = context.createMediaStreamSource(audio.mozCaptureStreamUntilEnded()); -var sp = context.createScriptProcessor(2048, 1); -node.connect(sp); -var nonzeroSampleCount = 0; -var complete = false; -var iterationCount = 0; +audio.onloadedmetadata = function() { + var node = context.createMediaStreamSource(audio.mozCaptureStreamUntilEnded()); + var sp = context.createScriptProcessor(2048, 1); + node.connect(sp); + var nonzeroSampleCount = 0; + var complete = false; + var iterationCount = 0; + + // This test ensures we receive at least expectedSampleCount nonzero samples + function processSamples(e) { + if (complete) { + return; + } -// This test ensures we receive at least expectedSampleCount nonzero samples -function processSamples(e) { - if (complete) { - return; + if (iterationCount == 0) { + // Don't start playing the audio until the AudioContext stuff is connected + // and running. + audio.play(); + } + ++iterationCount; + + var buf = e.inputBuffer.getChannelData(0); + var nonzeroSamplesThisBuffer = 0; + for (var i = 0; i < buf.length; ++i) { + if (buf[i] != 0) { + ++nonzeroSamplesThisBuffer; + } + } + is(nonzeroSamplesThisBuffer, 0, + "Checking all samples are zero"); + if (iterationCount >= 20) { + SimpleTest.finish(); + complete = true; + } } - if (iterationCount == 0) { - // Don't start playing the audio until the AudioContext stuff is connected - // and running. - audio.play(); - } - ++iterationCount; - - var buf = e.inputBuffer.getChannelData(0); - var nonzeroSamplesThisBuffer = 0; - for (var i = 0; i < buf.length; ++i) { - if (buf[i] != 0) { - ++nonzeroSamplesThisBuffer; - } - } - is(nonzeroSamplesThisBuffer, 0, - "Checking all samples are zero"); - if (iterationCount >= 20) { - SimpleTest.finish(); - complete = true; - } + audio.oncanplaythrough = function() { + sp.onaudioprocess = processSamples; + }; } - -audio.oncanplaythrough = function() { - sp.onaudioprocess = processSamples; -}; </script> </pre> </body> </html>
--- a/dom/svg/SVGGeometryElement.cpp +++ b/dom/svg/SVGGeometryElement.cpp @@ -10,16 +10,17 @@ #include "gfxPlatform.h" #include "nsCOMPtr.h" #include "nsSVGUtils.h" #include "SVGAnimatedLength.h" #include "SVGCircleElement.h" #include "SVGEllipseElement.h" #include "SVGGeometryProperty.h" #include "SVGRectElement.h" +#include "mozilla/dom/DOMPointBinding.h" #include "mozilla/dom/SVGLengthBinding.h" #include "mozilla/gfx/2D.h" #include "mozilla/RefPtr.h" #include "mozilla/SVGContentUtils.h" using namespace mozilla; using namespace mozilla::gfx; using namespace mozilla::dom; @@ -109,16 +110,28 @@ already_AddRefed<Path> SVGGeometryElemen already_AddRefed<Path> SVGGeometryElement::GetOrBuildPathForMeasuring() { RefPtr<DrawTarget> drawTarget = gfxPlatform::GetPlatform()->ScreenReferenceDrawTarget(); FillRule fillRule = mCachedPath ? mCachedPath->GetFillRule() : GetFillRule(); return GetOrBuildPath(drawTarget, fillRule); } +// This helper is currently identical to GetOrBuildPathForMeasuring. +// We keep it a separate method because for length measuring purpose, +// fillRule isn't really needed. Derived class (e.g. SVGPathElement) +// may override GetOrBuildPathForMeasuring() to ignore fillRule. And +// GetOrBuildPathForMeasuring() itself may be modified in the future. +already_AddRefed<Path> SVGGeometryElement::GetOrBuildPathForHitTest() { + RefPtr<DrawTarget> drawTarget = + gfxPlatform::GetPlatform()->ScreenReferenceDrawTarget(); + FillRule fillRule = mCachedPath ? mCachedPath->GetFillRule() : GetFillRule(); + return GetOrBuildPath(drawTarget, fillRule); +} + bool SVGGeometryElement::IsGeometryChangedViaCSS( ComputedStyle const& aNewStyle, ComputedStyle const& aOldStyle) const { if (IsSVGElement(nsGkAtoms::rect)) { return SVGRectElement::IsLengthChangedViaCSS(aNewStyle, aOldStyle); } if (IsSVGElement(nsGkAtoms::circle)) { return SVGCircleElement::IsLengthChangedViaCSS(aNewStyle, aOldStyle); @@ -148,16 +161,64 @@ FillRule SVGGeometryElement::GetFillRule if (!res) { NS_WARNING("Couldn't get ComputedStyle for content in GetFillRule"); } return fillRule; } +static Point GetPointFrom(const DOMPointInit& aPoint) { + return Point(aPoint.mX, aPoint.mY); +} + +bool SVGGeometryElement::IsPointInFill(const DOMPointInit& aPoint) { + auto point = GetPointFrom(aPoint); + + RefPtr<Path> path = GetOrBuildPathForHitTest(); + if (!path) { + return false; + } + + return path->ContainsPoint(point, {}); +} + +bool SVGGeometryElement::IsPointInStroke(const DOMPointInit& aPoint) { + auto point = GetPointFrom(aPoint); + + RefPtr<Path> path = GetOrBuildPathForHitTest(); + if (!path) { + return false; + } + + bool res = false; + SVGGeometryProperty::DoForComputedStyle(this, [&](const ComputedStyle* s) { + // Per spec, we should take vector-effect into account. + if (s->StyleSVGReset()->HasNonScalingStroke()) { + auto mat = SVGContentUtils::GetCTM(this, true); + if (mat.HasNonTranslation()) { + // We have non-scaling-stroke as well as a non-translation transform. + // We should transform the path first then apply the stroke on the + // transformed path to preserve the stroke-width. + RefPtr<PathBuilder> builder = path->TransformedCopyToBuilder(mat); + + path = builder->Finish(); + point = mat.TransformPoint(point); + } + } + + SVGContentUtils::AutoStrokeOptions strokeOptions; + SVGContentUtils::GetStrokeOptions(&strokeOptions, this, s, nullptr); + + res = path->StrokeContainsPoint(strokeOptions, point, {}); + }); + + return res; +} + float SVGGeometryElement::GetTotalLength() { RefPtr<Path> flat = GetOrBuildPathForMeasuring(); return flat ? flat->ComputeLength() : 0.f; } already_AddRefed<nsISVGPoint> SVGGeometryElement::GetPointAtLength( float distance, ErrorResult& rv) { RefPtr<Path> path = GetOrBuildPathForMeasuring();
--- a/dom/svg/SVGGeometryElement.h +++ b/dom/svg/SVGGeometryElement.h @@ -211,25 +211,30 @@ class SVGGeometryElement : public SVGGeo * estimated length (as provided by the element's 'pathLength' attribute). * This is used to scale stroke dashing, and to scale offsets along a * textPath. */ float GetPathLengthScale(PathLengthScaleForType aFor); // WebIDL already_AddRefed<DOMSVGAnimatedNumber> PathLength(); + bool IsPointInFill(const DOMPointInit& aPoint); + bool IsPointInStroke(const DOMPointInit& aPoint); float GetTotalLength(); already_AddRefed<nsISVGPoint> GetPointAtLength(float distance, ErrorResult& rv); protected: // SVGElement method virtual NumberAttributesInfo GetNumberInfo() override; SVGAnimatedNumber mPathLength; static NumberInfo sNumberInfo; mutable RefPtr<Path> mCachedPath; + + private: + already_AddRefed<Path> GetOrBuildPathForHitTest(); }; } // namespace dom } // namespace mozilla #endif // mozilla_dom_SVGGeometryElement_h
--- a/dom/tests/mochitest/general/test_DOMMatrix.html +++ b/dom/tests/mochitest/general/test_DOMMatrix.html @@ -457,17 +457,17 @@ function testSkewYInPlace() function testCreateMatrix3D() { var m = new DOMMatrix([1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]); // Should be initialised to identity cmpMatrix(m, [1, 0, 0, 1, 0, 0], "DOMMatrix should produce identity matrix"); - is(m.is2D, true, "should not produce 3d matrix"); + is(m.is2D, false, "should produce 3d matrix"); m = new DOMMatrix([1, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0]); // Should be initialised to identity cmpMatrix(m, [1, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0], "DOMMatrix should produce identity matrix"); is(m.is2D, false, "should produce 3d matrix"); } @@ -702,26 +702,26 @@ function testParsing() ok(CompareDOMMatrix(m2, m), "string parsing didn't match"); m = new DOMMatrix(); m.setMatrixValue("translate(10px, 20px) scale(.5, 2) rotate(45deg)"); ok(CompareDOMMatrix(m2, m), "string parsing didn't match"); } -function testStringify() { - var m = new DOMMatrix(); - var s = "" + m; - ok(s == "matrix" + formatMatrix(m), "stringifier 1 produced wrong result: " + s); - m.a = 100; - s = "" + m; - ok(s == "matrix" + formatMatrix(m), "stringifier 2 produced wrong result: " + s); - m.m43 = 200; - s = "" + m; - ok(s == "matrix3d" + formatMatrix(m), "stringifier 3 produced wrong result:" + s); +function testStringify() { + var m = new DOMMatrix(); + var s = "" + m; + ok(s == "matrix" + formatMatrix(m), "stringifier 1 produced wrong result: " + s); + m.a = 100; + s = "" + m; + ok(s == "matrix" + formatMatrix(m), "stringifier 2 produced wrong result: " + s); + m.m43 = 200; + s = "" + m; + ok(s == "matrix3d" + formatMatrix(m), "stringifier 3 produced wrong result:" + s); } window.addEventListener("load", main); </script> </pre> </body> </html>
--- a/dom/webidl/SVGGeometryElement.webidl +++ b/dom/webidl/SVGGeometryElement.webidl @@ -9,12 +9,15 @@ * Copyright © 2012 W3C® (MIT, ERCIM, Keio), All Rights Reserved. W3C * liability, trademark and document use rules apply. */ interface SVGGeometryElement : SVGGraphicsElement { [SameObject] readonly attribute SVGAnimatedNumber pathLength; + boolean isPointInFill(optional DOMPointInit point); + boolean isPointInStroke(optional DOMPointInit point); + float getTotalLength(); [NewObject, Throws] SVGPoint getPointAtLength(float distance); };
--- a/gfx/ipc/CompositorSession.cpp +++ b/gfx/ipc/CompositorSession.cpp @@ -6,29 +6,45 @@ #include "CompositorSession.h" #include "base/process_util.h" #include "GPUChild.h" #include "mozilla/gfx/Logging.h" #include "mozilla/gfx/GPUProcessHost.h" #include "mozilla/layers/CompositorBridgeChild.h" #include "mozilla/layers/CompositorBridgeParent.h" +#if defined(MOZ_WIDGET_ANDROID) +# include "mozilla/widget/nsWindow.h" +#endif // defined(MOZ_WIDGET_ANDROID) + namespace mozilla { namespace layers { using namespace gfx; using namespace widget; -CompositorSession::CompositorSession(CompositorWidgetDelegate* aDelegate, +CompositorSession::CompositorSession(nsBaseWidget* aWidget, + CompositorWidgetDelegate* aDelegate, CompositorBridgeChild* aChild, const LayersId& aRootLayerTreeId) - : mCompositorWidgetDelegate(aDelegate), + : mWidget(aWidget), + mCompositorWidgetDelegate(aDelegate), mCompositorBridgeChild(aChild), mRootLayerTreeId(aRootLayerTreeId) {} CompositorSession::~CompositorSession() {} CompositorBridgeChild* CompositorSession::GetCompositorBridgeChild() { return mCompositorBridgeChild; } +#if defined(MOZ_WIDGET_ANDROID) +void CompositorSession::NotifyDisablingWebRender() { + if (!mWidget) { + return; + } + nsWindow* window = static_cast<nsWindow*>(mWidget); + window->NotifyDisablingWebRender(); +} +#endif // defined(MOZ_WIDGET_ANDROID) + } // namespace layers } // namespace mozilla
--- a/gfx/ipc/CompositorSession.h +++ b/gfx/ipc/CompositorSession.h @@ -9,17 +9,17 @@ #include "base/basictypes.h" #include "mozilla/layers/LayersTypes.h" #include "mozilla/layers/CompositorTypes.h" #include "nsISupportsImpl.h" #if defined(MOZ_WIDGET_ANDROID) # include "mozilla/layers/UiCompositorControllerChild.h" #endif // defined(MOZ_WIDGET_ANDROID) -class nsIWidget; +class nsBaseWidget; namespace mozilla { namespace widget { class CompositorWidget; class CompositorWidgetDelegate; } // namespace widget namespace gfx { class GPUProcessHost; @@ -75,24 +75,27 @@ class CompositorSession { void SetUiCompositorControllerChild( RefPtr<UiCompositorControllerChild> aUiController) { mUiCompositorControllerChild = aUiController; } RefPtr<UiCompositorControllerChild> GetUiCompositorControllerChild() { return mUiCompositorControllerChild; } + + void NotifyDisablingWebRender(); #endif // defined(MOZ_WIDGET_ANDROID) protected: - CompositorSession(CompositorWidgetDelegate* aDelegate, + CompositorSession(nsBaseWidget* aWidget, CompositorWidgetDelegate* aDelegate, CompositorBridgeChild* aChild, const LayersId& aRootLayerTreeId); virtual ~CompositorSession(); protected: + nsBaseWidget* mWidget; CompositorWidgetDelegate* mCompositorWidgetDelegate; RefPtr<CompositorBridgeChild> mCompositorBridgeChild; LayersId mRootLayerTreeId; #if defined(MOZ_WIDGET_ANDROID) RefPtr<UiCompositorControllerChild> mUiCompositorControllerChild; #endif // defined(MOZ_WIDGET_ANDROID) private: DISALLOW_COPY_AND_ASSIGN(CompositorSession);
--- a/gfx/ipc/GPUProcessManager.cpp +++ b/gfx/ipc/GPUProcessManager.cpp @@ -450,16 +450,25 @@ void GPUProcessManager::DisableWebRender .ForceDisable(gfx::FeatureStatus::Unavailable, "Failed to render WebRender", NS_LITERAL_CSTRING("FEATURE_FAILURE_WEBRENDER_RENDER")); } else { MOZ_ASSERT_UNREACHABLE("Invalid value"); } gfx::gfxVars::SetUseWebRender(false); +#if defined(MOZ_WIDGET_ANDROID) + // If aError is not wr::WebRenderError::INITIALIZE, nsWindow does not + // re-create LayerManager. Needs to trigger re-creating LayerManager on + // android + if (aError != wr::WebRenderError::INITIALIZE) { + NotifyDisablingWebRender(); + } +#endif + if (mProcess) { OnRemoteProcessDeviceReset(mProcess); } else { OnInProcessDeviceReset(); } } void GPUProcessManager::NotifyWebRenderError(wr::WebRenderError aError) { @@ -636,16 +645,28 @@ void GPUProcessManager::RebuildInProcess // Notify each widget that we have lost the GPU process. This will ensure // that each widget destroys its layer manager and CompositorBridgeChild. for (const auto& session : sessions) { session->NotifySessionLost(); } } +void GPUProcessManager::NotifyDisablingWebRender() { +#if defined(MOZ_WIDGET_ANDROID) + for (const auto& session : mRemoteSessions) { + session->NotifyDisablingWebRender(); + } + + for (const auto& session : mInProcessSessions) { + session->NotifyDisablingWebRender(); + } +#endif +} + void GPUProcessManager::NotifyRemoteActorDestroyed( const uint64_t& aProcessToken) { if (!NS_IsMainThread()) { RefPtr<Runnable> task = mTaskFactory.NewRunnableMethod( &GPUProcessManager::NotifyRemoteActorDestroyed, aProcessToken); NS_DispatchToMainThread(task.forget()); return; }
--- a/gfx/ipc/GPUProcessManager.h +++ b/gfx/ipc/GPUProcessManager.h @@ -205,16 +205,18 @@ class GPUProcessManager final : public G // Called from InProcessCompositorSession. We track in process sessino so we // can notify their owning widgets that the session must be restarted void RegisterInProcessSession(InProcessCompositorSession* aSession); void UnregisterInProcessSession(InProcessCompositorSession* aSession); void RebuildRemoteSessions(); void RebuildInProcessSessions(); + void NotifyDisablingWebRender(); + void FallbackToSoftware(const char* aMessage); private: GPUProcessManager(); // Permanently disable the GPU process and record a message why. void DisableGPUProcess(const char* aMessage);
--- a/gfx/ipc/InProcessCompositorSession.cpp +++ b/gfx/ipc/InProcessCompositorSession.cpp @@ -8,23 +8,22 @@ #include "mozilla/layers/IAPZCTreeManager.h" #include "nsBaseWidget.h" namespace mozilla { namespace layers { InProcessCompositorSession::InProcessCompositorSession( - widget::CompositorWidget* aWidget, nsBaseWidget* baseWidget, + nsBaseWidget* aWidget, widget::CompositorWidget* aCompositorWidget, CompositorBridgeChild* aChild, CompositorBridgeParent* aParent) - : CompositorSession(aWidget->AsDelegate(), aChild, + : CompositorSession(aWidget, aCompositorWidget->AsDelegate(), aChild, aParent->RootLayerTreeId()), - mWidget(baseWidget), mCompositorBridgeParent(aParent), - mCompositorWidget(aWidget) { + mCompositorWidget(aCompositorWidget) { GPUProcessManager::Get()->RegisterInProcessSession(this); } /* static */ RefPtr<InProcessCompositorSession> InProcessCompositorSession::Create( nsBaseWidget* aWidget, LayerManager* aLayerManager, const LayersId& aRootLayerTreeId, CSSToLayoutDeviceScale aScale, const CompositorOptions& aOptions, bool aUseExternalSurfaceSize, @@ -40,17 +39,17 @@ RefPtr<InProcessCompositorSession> InPro MOZ_ASSERT(parent); parent->InitSameProcess(widget, aRootLayerTreeId); RefPtr<CompositorBridgeChild> child = CompositorManagerChild::CreateSameProcessWidgetCompositorBridge( aLayerManager, aNamespace); MOZ_ASSERT(child); - return new InProcessCompositorSession(widget, aWidget, child, parent); + return new InProcessCompositorSession(aWidget, widget, child, parent); } void InProcessCompositorSession::NotifySessionLost() { mWidget->NotifyCompositorSessionLost(this); } CompositorBridgeParent* InProcessCompositorSession::GetInProcessBridge() const { return mCompositorBridgeParent;
--- a/gfx/ipc/InProcessCompositorSession.h +++ b/gfx/ipc/InProcessCompositorSession.h @@ -29,23 +29,22 @@ class InProcessCompositorSession final : void SetContentController(GeckoContentController* aController) override; nsIWidget* GetWidget() const; RefPtr<IAPZCTreeManager> GetAPZCTreeManager() const override; void Shutdown() override; void NotifySessionLost(); private: - InProcessCompositorSession(widget::CompositorWidget* aWidget, - nsBaseWidget* baseWidget, + InProcessCompositorSession(nsBaseWidget* aWidget, + widget::CompositorWidget* aCompositorWidget, CompositorBridgeChild* aChild, CompositorBridgeParent* aParent); private: - nsBaseWidget* mWidget; RefPtr<CompositorBridgeParent> mCompositorBridgeParent; RefPtr<CompositorWidget> mCompositorWidget; }; } // namespace layers } // namespace mozilla #endif // _include_mozilla_gfx_ipc_InProcessCompositorSession_h_
--- a/gfx/ipc/RemoteCompositorSession.cpp +++ b/gfx/ipc/RemoteCompositorSession.cpp @@ -19,18 +19,17 @@ namespace layers { using namespace gfx; using namespace widget; RemoteCompositorSession::RemoteCompositorSession( nsBaseWidget* aWidget, CompositorBridgeChild* aChild, CompositorWidgetDelegate* aWidgetDelegate, APZCTreeManagerChild* aAPZ, const LayersId& aRootLayerTreeId) - : CompositorSession(aWidgetDelegate, aChild, aRootLayerTreeId), - mWidget(aWidget), + : CompositorSession(aWidget, aWidgetDelegate, aChild, aRootLayerTreeId), mAPZ(aAPZ) { MOZ_ASSERT(!gfxPlatform::IsHeadless()); GPUProcessManager::Get()->RegisterRemoteProcessSession(this); if (mAPZ) { mAPZ->SetCompositorSession(this); } }
--- a/gfx/ipc/RemoteCompositorSession.h +++ b/gfx/ipc/RemoteCompositorSession.h @@ -26,17 +26,16 @@ class RemoteCompositorSession final : pu GeckoContentController* GetContentController(); nsIWidget* GetWidget() const; RefPtr<IAPZCTreeManager> GetAPZCTreeManager() const override; void Shutdown() override; void NotifySessionLost(); private: - nsBaseWidget* mWidget; RefPtr<APZCTreeManagerChild> mAPZ; RefPtr<GeckoContentController> mContentController; }; } // namespace layers } // namespace mozilla #endif // include_mozilla_gfx_ipc_RemoteCompositorSession_h
--- a/gfx/webrender_bindings/RenderAndroidSurfaceTextureHostOGL.cpp +++ b/gfx/webrender_bindings/RenderAndroidSurfaceTextureHostOGL.cpp @@ -98,16 +98,22 @@ wr::WrExternalImage RenderAndroidSurface void RenderAndroidSurfaceTextureHostOGL::Unlock() {} void RenderAndroidSurfaceTextureHostOGL::DeleteTextureHandle() { NotifyNotUsed(); } bool RenderAndroidSurfaceTextureHostOGL::EnsureAttachedToGLContext() { + // During handling WebRenderError, GeckoSurfaceTexture should not be attached + // to GLContext. + if (RenderThread::Get()->IsHandlingWebRenderError()) { + return false; + } + if (mAttachedToGLContext) { return true; } if (!mGL) { mGL = RenderThread::Get()->SharedGL(); } @@ -192,35 +198,41 @@ void RenderAndroidSurfaceTextureHostOGL: if (mPrepareStatus == STATUS_MIGHT_BE_USED) { // This happens when SurfaceTexture of video is rendered on WebRender and // SurfaceTexture is not bounded to gl context yet. // It is necessary to handle a case that SurfaceTexture is not rendered on // WebRender, instead rendered to WebGL. // It is not a good way. But it is same to Compositor rendering. MOZ_ASSERT(!mSurfTex || !mSurfTex->IsSingleBuffer()); - EnsureAttachedToGLContext(); + if (!EnsureAttachedToGLContext()) { + return; + } mPrepareStatus = STATUS_PREPARE_NEEDED; } } void RenderAndroidSurfaceTextureHostOGL::NotifyNotUsed() { MOZ_ASSERT(RenderThread::IsInRenderThread()); if (mSurfTex && mSurfTex->IsSingleBuffer() && mPrepareStatus == STATUS_PREPARED) { - EnsureAttachedToGLContext(); + if (!EnsureAttachedToGLContext()) { + return; + } // Release SurfaceTexture's buffer to client side. mGL->MakeCurrent(); mSurfTex->ReleaseTexImage(); } else if (mSurfTex && mPrepareStatus == STATUS_PREPARE_NEEDED) { // This could happen when video frame was skipped. UpdateTexImage() neeeds // to be called for adjusting SurfaceTexture's buffer status. MOZ_ASSERT(!mSurfTex->IsSingleBuffer()); - EnsureAttachedToGLContext(); + if (!EnsureAttachedToGLContext()) { + return; + } mSurfTex->UpdateTexImage(); } mPrepareStatus = STATUS_NONE; } } // namespace wr } // namespace mozilla
--- a/gfx/webrender_bindings/RenderCompositorEGL.cpp +++ b/gfx/webrender_bindings/RenderCompositorEGL.cpp @@ -50,17 +50,23 @@ EGLSurface RenderCompositorEGL::CreateEG } return surface; } RenderCompositorEGL::RenderCompositorEGL( RefPtr<widget::CompositorWidget> aWidget) : RenderCompositor(std::move(aWidget)), mEGLSurface(EGL_NO_SURFACE) {} -RenderCompositorEGL::~RenderCompositorEGL() { DestroyEGLSurface(); } +RenderCompositorEGL::~RenderCompositorEGL() { +#ifdef MOZ_WIDGET_ANDROID + java::GeckoSurfaceTexture::DestroyUnused((int64_t)gl()); + java::GeckoSurfaceTexture::DetachAllFromGLContext((int64_t)gl()); +#endif + DestroyEGLSurface(); +} bool RenderCompositorEGL::BeginFrame() { #ifdef MOZ_WAYLAND bool newSurface = mWidget->AsX11() && mWidget->AsX11()->WaylandRequestsUpdatingEGLSurface(); if (newSurface) { // Destroy EGLSurface if it exists and create a new one. We will set the // swap interval after MakeCurrent() has been called.
--- a/gfx/webrender_bindings/RenderThread.cpp +++ b/gfx/webrender_bindings/RenderThread.cpp @@ -8,31 +8,33 @@ #include "GeckoProfiler.h" #include "RenderThread.h" #include "nsThreadUtils.h" #include "mtransport/runnable_utils.h" #include "mozilla/layers/AsyncImagePipelineManager.h" #include "mozilla/gfx/GPUParent.h" #include "mozilla/layers/CompositorThread.h" #include "mozilla/layers/CompositorBridgeParent.h" +#include "mozilla/layers/CompositorManagerParent.h" #include "mozilla/layers/WebRenderBridgeParent.h" #include "mozilla/layers/SharedSurfacesParent.h" #include "mozilla/StaticPtr.h" #include "mozilla/Telemetry.h" #include "mozilla/webrender/RendererOGL.h" #include "mozilla/webrender/RenderTextureHost.h" #include "mozilla/widget/CompositorWidget.h" #ifdef XP_WIN # include "GLLibraryEGL.h" # include "mozilla/widget/WinCompositorWindowThread.h" #endif #ifdef MOZ_WIDGET_ANDROID # include "GLLibraryEGL.h" +# include "GeneratedJNIWrappers.h" #endif using namespace mozilla; static already_AddRefed<gl::GLContext> CreateGLContext(); MOZ_DEFINE_MALLOC_SIZE_OF(WebRenderRendererMallocSizeOf) @@ -41,17 +43,18 @@ namespace wr { static StaticRefPtr<RenderThread> sRenderThread; RenderThread::RenderThread(base::Thread* aThread) : mThread(aThread), mWindowInfos("RenderThread.mWindowInfos"), mRenderTextureMapLock("RenderThread.mRenderTextureMapLock"), mHasShutdown(false), - mHandlingDeviceReset(false) {} + mHandlingDeviceReset(false), + mHandlingWebRenderError(false) {} RenderThread::~RenderThread() { MOZ_ASSERT(mRenderTexturesDeferred.empty()); delete mThread; } // static RenderThread* RenderThread::Get() { return sRenderThread; } @@ -803,16 +806,44 @@ void RenderThread::SimulateDeviceReset() } else { // When this function is called GPUProcessManager::SimulateDeviceReset() // already triggers destroying all CompositorSessions before re-creating // them. HandleDeviceReset("SimulateDeviceReset", /* aNotify */ false); } } +static void DoNotifyWebRenderError(WebRenderError aError) { + layers::CompositorManagerParent::NotifyWebRenderError(aError); +} + +void RenderThread::HandleWebRenderError(WebRenderError aError) { + if (mHandlingWebRenderError) { + return; + } + + layers::CompositorThreadHolder::Loop()->PostTask(NewRunnableFunction( + "DoNotifyWebRenderErrorRunnable", &DoNotifyWebRenderError, aError)); + { + MutexAutoLock lock(mRenderTextureMapLock); + mRenderTexturesDeferred.clear(); + for (const auto& entry : mRenderTextures) { + entry.second->ClearCachedResources(); + } + } + mHandlingWebRenderError = true; + // WebRender is going to be disabled by + // GPUProcessManager::NotifyWebRenderError() +} + +bool RenderThread::IsHandlingWebRenderError() { + MOZ_ASSERT(IsInRenderThread()); + return mHandlingWebRenderError; +} + gl::GLContext* RenderThread::SharedGL() { MOZ_ASSERT(IsInRenderThread()); if (!mSharedGL) { mSharedGL = CreateGLContext(); mShaders = nullptr; } if (mSharedGL && !mShaders) { mShaders = MakeUnique<WebRenderShaders>(mSharedGL, mProgramCache.get());
--- a/gfx/webrender_bindings/RenderThread.h +++ b/gfx/webrender_bindings/RenderThread.h @@ -250,16 +250,21 @@ class RenderThread final { /// Can only be called from the render thread. void HandleDeviceReset(const char* aWhere, bool aNotify); /// Can only be called from the render thread. bool IsHandlingDeviceReset(); /// Can be called from any thread. void SimulateDeviceReset(); + /// Can only be called from the render thread. + void HandleWebRenderError(WebRenderError aError); + /// Can only be called from the render thread. + bool IsHandlingWebRenderError(); + size_t RendererCount(); void SetCompositionRecorderForWindow( wr::WindowId aWindowId, RefPtr<layers::WebRenderCompositionRecorder>&& aCompositionRecorder); private: explicit RenderThread(base::Thread* aThread); @@ -309,14 +314,15 @@ class RenderThread final { // Used to remove all RenderTextureHost that are going to be removed by // a deferred callback and remove them right away without waiting for the // callback. On device reset we have to remove all GL related resources right // away. std::list<RefPtr<RenderTextureHost>> mRenderTexturesDeferred; bool mHasShutdown; bool mHandlingDeviceReset; + bool mHandlingWebRenderError; }; } // namespace wr } // namespace mozilla #endif
--- a/gfx/webrender_bindings/RendererOGL.cpp +++ b/gfx/webrender_bindings/RendererOGL.cpp @@ -5,17 +5,16 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "RendererOGL.h" #include "GLContext.h" #include "mozilla/gfx/Logging.h" #include "mozilla/gfx/gfxVars.h" #include "mozilla/gfx/Types.h" #include "mozilla/layers/CompositorBridgeParent.h" -#include "mozilla/layers/CompositorManagerParent.h" #include "mozilla/layers/CompositorThread.h" #include "mozilla/layers/LayersTypes.h" #include "mozilla/webrender/RenderCompositor.h" #include "mozilla/webrender/RenderTextureHost.h" #include "mozilla/widget/CompositorWidget.h" namespace mozilla { namespace wr { @@ -115,17 +114,18 @@ bool RendererOGL::UpdateAndRender(const } wr_renderer_update(mRenderer); auto size = mCompositor->GetBufferSize(); if (!wr_renderer_render(mRenderer, size.width, size.height, aHadSlowFrame, aOutStats)) { - NotifyWebRenderError(WebRenderError::RENDER); + RenderThread::Get()->HandleWebRenderError(WebRenderError::RENDER); + return false; } if (aReadbackBuffer.isSome()) { MOZ_ASSERT(aReadbackSize.isSome()); MOZ_ASSERT(aReadbackFormat.isSome()); wr_renderer_readback(mRenderer, aReadbackSize.ref().width, aReadbackSize.ref().height, aReadbackFormat.ref(), &aReadbackBuffer.ref()[0], @@ -211,19 +211,10 @@ void RendererOGL::AccumulateMemoryReport // Assume BGRA8 for the format since it's not exposed anywhere, // and all compositor backends should be using that. uintptr_t swapChainSize = size.width * size.height * BytesPerPixel(SurfaceFormat::B8G8R8A8) * (mCompositor->UseTripleBuffering() ? 3 : 2); aReport->swap_chain += swapChainSize; } -static void DoNotifyWebRenderError(WebRenderError aError) { - layers::CompositorManagerParent::NotifyWebRenderError(aError); -} - -void RendererOGL::NotifyWebRenderError(WebRenderError aError) { - layers::CompositorThreadHolder::Loop()->PostTask(NewRunnableFunction( - "DoNotifyWebRenderErrorRunnable", &DoNotifyWebRenderError, aError)); -} - } // namespace wr } // namespace mozilla
--- a/gfx/webrender_bindings/RendererOGL.h +++ b/gfx/webrender_bindings/RendererOGL.h @@ -99,18 +99,16 @@ class RendererOGL { void AccumulateMemoryReport(MemoryReport* aReport); wr::Renderer* GetRenderer() { return mRenderer; } gl::GLContext* gl() const; protected: - void NotifyWebRenderError(WebRenderError aError); - RefPtr<RenderThread> mThread; UniquePtr<RenderCompositor> mCompositor; wr::Renderer* mRenderer; layers::CompositorBridgeParent* mBridge; wr::WindowId mWindowId; TimeStamp mFrameStartTime; RendererScreenshotGrabber mScreenshotGrabber;
--- a/js/public/HeapAPI.h +++ b/js/public/HeapAPI.h @@ -242,16 +242,18 @@ struct Symbol { * A GC pointer, tagged with the trace kind. * * In general, a GC pointer should be stored with an exact type. This class * is for use when that is not possible because a single pointer must point * to several kinds of GC thing. */ class JS_FRIEND_API GCCellPtr { public: + GCCellPtr() : GCCellPtr(nullptr) {} + // Construction from a void* and trace kind. GCCellPtr(void* gcthing, JS::TraceKind traceKind) : ptr(checkedCast(gcthing, traceKind)) {} // Automatically construct a null GCCellPtr from nullptr. MOZ_IMPLICIT GCCellPtr(decltype(nullptr)) : ptr(checkedCast(nullptr, JS::TraceKind::Null)) {}
--- a/js/src/builtin/Array.cpp +++ b/js/src/builtin/Array.cpp @@ -3760,20 +3760,20 @@ static const JSFunctionSpec array_method /* ES6 additions */ JS_SELF_HOSTED_FN("find", "ArrayFind", 1, 0), JS_SELF_HOSTED_FN("findIndex", "ArrayFindIndex", 1, 0), JS_SELF_HOSTED_FN("copyWithin", "ArrayCopyWithin", 3, 0), JS_SELF_HOSTED_FN("fill", "ArrayFill", 3, 0), - JS_SELF_HOSTED_SYM_FN(iterator, "ArrayValues", 0, 0), + JS_SELF_HOSTED_SYM_FN(iterator, "$ArrayValues", 0, 0), JS_SELF_HOSTED_FN("entries", "ArrayEntries", 0, 0), JS_SELF_HOSTED_FN("keys", "ArrayKeys", 0, 0), - JS_SELF_HOSTED_FN("values", "ArrayValues", 0, 0), + JS_SELF_HOSTED_FN("values", "$ArrayValues", 0, 0), /* ES7 additions */ JS_SELF_HOSTED_FN("includes", "ArrayIncludes", 2, 0), /* Future additions */ JS_SELF_HOSTED_FN("flatMap", "ArrayFlatMap", 1, 0), JS_SELF_HOSTED_FN("flat", "ArrayFlat", 0, 0), @@ -3801,17 +3801,17 @@ static const JSFunctionSpec array_static JS_SELF_HOSTED_FN("splice", "ArrayStaticSplice", 3, 0), JS_SELF_HOSTED_FN("slice", "ArrayStaticSlice", 3, 0), JS_SELF_HOSTED_FN("from", "ArrayFrom", 3, 0), JS_FN("of", array_of, 0, 0), JS_FS_END}; const JSPropertySpec array_static_props[] = { - JS_SELF_HOSTED_SYM_GET(species, "ArraySpecies", 0), JS_PS_END}; + JS_SELF_HOSTED_SYM_GET(species, "$ArraySpecies", 0), JS_PS_END}; static inline bool ArrayConstructorImpl(JSContext* cx, CallArgs& args, bool isConstructor) { RootedObject proto(cx); if (isConstructor) { if (!GetPrototypeFromBuiltinConstructor(cx, args, JSProto_Array, &proto)) { return false; }
--- a/js/src/builtin/Array.js +++ b/js/src/builtin/Array.js @@ -784,20 +784,22 @@ function ArrayIteratorNext() { } // Step 12. assert(itemKind === ITEM_KIND_KEY, itemKind); result.value = index; return result; } -function ArrayValues() { +// Uncloned functions with `$` prefix are allocated as extended function +// to store the original name in `_SetCanonicalName`. +function $ArrayValues() { return CreateArrayIterator(this, ITEM_KIND_VALUE); } -_SetCanonicalName(ArrayValues, "values"); +_SetCanonicalName($ArrayValues, "values"); function ArrayEntries() { return CreateArrayIterator(this, ITEM_KIND_KEY_AND_VALUE); } function ArrayKeys() { return CreateArrayIterator(this, ITEM_KIND_KEY); } @@ -968,21 +970,21 @@ function ArrayToLocaleString(locales, op } } // Step 10. return R; } // ES 2016 draft Mar 25, 2016 22.1.2.5. -function ArraySpecies() { +function $ArraySpecies() { // Step 1. return this; } -_SetCanonicalName(ArraySpecies, "get [Symbol.species]"); +_SetCanonicalName($ArraySpecies, "get [Symbol.species]"); // ES 2016 draft Mar 25, 2016 9.4.2.3. function ArraySpeciesCreate(originalArray, length) { // Step 1. assert(typeof length == "number", "length should be a number"); assert(length >= 0, "length should be a non-negative number"); // Step 2.
--- a/js/src/builtin/Eval.cpp +++ b/js/src/builtin/Eval.cpp @@ -38,20 +38,33 @@ static void AssertInnerizedEnvironmentCh RootedObject obj(cx); for (obj = &env; obj; obj = obj->enclosingEnvironment()) { MOZ_ASSERT(!IsWindowProxy(obj)); } #endif } static bool IsEvalCacheCandidate(JSScript* script) { + if (!script->isDirectEvalInFunction()) { + return false; + } + // Make sure there are no inner objects which might use the wrong parent // and/or call scope by reusing the previous eval's script. - return script->isDirectEvalInFunction() && !script->hasSingletons() && - !script->hasObjects(); + if (script->hasSingletons()) { + return false; + } + + for (JS::GCCellPtr gcThing : script->gcthings()) { + if (gcThing.is<JSObject>()) { + return false; + } + } + + return true; } /* static */ HashNumber EvalCacheHashPolicy::hash(const EvalCacheLookup& l) { AutoCheckCannotGC nogc; uint32_t hash = l.str->hasLatin1Chars() ? HashString(l.str->latin1Chars(nogc), l.str->length()) : HashString(l.str->twoByteChars(nogc), l.str->length());
--- a/js/src/builtin/Map.js +++ b/js/src/builtin/Map.js @@ -58,20 +58,22 @@ function MapForEach(callbackfn, thisArg var value = mapIterationResultPair[1]; mapIterationResultPair[0] = null; mapIterationResultPair[1] = null; callContentFunction(callbackfn, thisArg, value, key, M); } } -function MapEntries() { +// Uncloned functions with `$` prefix are allocated as extended function +// to store the original name in `_SetCanonicalName`. +function $MapEntries() { return callFunction(std_Map_iterator, this); } -_SetCanonicalName(MapEntries, "entries"); +_SetCanonicalName($MapEntries, "entries"); var iteratorTemp = { mapIterationResultPair: null }; function MapIteratorNext() { // Step 1. var O = this; // Steps 2-3. @@ -116,13 +118,13 @@ function MapIteratorNext() { retVal.done = false; } // Steps 7, 12. return retVal; } // ES6 final draft 23.1.2.2. -function MapSpecies() { +function $MapSpecies() { // Step 1. return this; } -_SetCanonicalName(MapSpecies, "get [Symbol.species]"); +_SetCanonicalName($MapSpecies, "get [Symbol.species]");
--- a/js/src/builtin/MapObject.cpp +++ b/js/src/builtin/MapObject.cpp @@ -406,21 +406,21 @@ const JSPropertySpec MapObject::properti const JSFunctionSpec MapObject::methods[] = { JS_FN("get", get, 1, 0), JS_FN("has", has, 1, 0), JS_FN("set", set, 2, 0), JS_FN("delete", delete_, 1, 0), JS_FN("keys", keys, 0, 0), JS_FN("values", values, 0, 0), JS_FN("clear", clear, 0, 0), JS_SELF_HOSTED_FN("forEach", "MapForEach", 2, 0), // MapEntries only exists to preseve the equal identity of // entries and @@iterator. - JS_SELF_HOSTED_FN("entries", "MapEntries", 0, 0), - JS_SELF_HOSTED_SYM_FN(iterator, "MapEntries", 0, 0), JS_FS_END}; + JS_SELF_HOSTED_FN("entries", "$MapEntries", 0, 0), + JS_SELF_HOSTED_SYM_FN(iterator, "$MapEntries", 0, 0), JS_FS_END}; const JSPropertySpec MapObject::staticProperties[] = { - JS_SELF_HOSTED_SYM_GET(species, "MapSpecies", 0), JS_PS_END}; + JS_SELF_HOSTED_SYM_GET(species, "$MapSpecies", 0), JS_PS_END}; template <class Range> static void TraceKey(Range& r, const HashableValue& key, JSTracer* trc) { HashableValue newKey = key.trace(trc); if (newKey.get() != key.get()) { // The hash function must take account of the fact that the thing being // hashed may have been moved by GC. This is only an issue for BigInt as for @@ -1155,22 +1155,22 @@ const JSPropertySpec SetObject::properti const JSFunctionSpec SetObject::methods[] = { JS_FN("has", has, 1, 0), JS_FN("add", add, 1, 0), JS_FN("delete", delete_, 1, 0), JS_FN("entries", entries, 0, 0), JS_FN("clear", clear, 0, 0), JS_SELF_HOSTED_FN("forEach", "SetForEach", 2, 0), // SetValues only exists to preseve the equal identity of // values, keys and @@iterator. - JS_SELF_HOSTED_FN("values", "SetValues", 0, 0), - JS_SELF_HOSTED_FN("keys", "SetValues", 0, 0), - JS_SELF_HOSTED_SYM_FN(iterator, "SetValues", 0, 0), JS_FS_END}; + JS_SELF_HOSTED_FN("values", "$SetValues", 0, 0), + JS_SELF_HOSTED_FN("keys", "$SetValues", 0, 0), + JS_SELF_HOSTED_SYM_FN(iterator, "$SetValues", 0, 0), JS_FS_END}; const JSPropertySpec SetObject::staticProperties[] = { - JS_SELF_HOSTED_SYM_GET(species, "SetSpecies", 0), JS_PS_END}; + JS_SELF_HOSTED_SYM_GET(species, "$SetSpecies", 0), JS_PS_END}; bool SetObject::keys(JSContext* cx, HandleObject obj, JS::MutableHandle<GCVector<JS::Value>> keys) { ValueSet* set = obj->as<SetObject>().getData(); if (!set) { return false; }
--- a/js/src/builtin/Module.js +++ b/js/src/builtin/Module.js @@ -323,17 +323,16 @@ function ModuleInstantiate() // Step 7 assert(stack.length === 0, "Stack should be empty after successful instantiation"); // Step 8 return undefined; } -_SetCanonicalName(ModuleInstantiate, "ModuleInstantiate"); // 15.2.1.16.4.1 InnerModuleInstantiation(module, stack, index) function InnerModuleInstantiation(module, stack, index) { // Step 1 // TODO: Support module records other than source text module records. // Step 2 @@ -547,17 +546,16 @@ function ModuleEvaluate() assert(module.status === MODULE_STATUS_EVALUATED, "Bad module status after successful evaluation"); assert(stack.length === 0, "Stack should be empty after successful evaluation"); return undefined; } -_SetCanonicalName(ModuleEvaluate, "ModuleEvaluate"); // 15.2.1.16.5.1 InnerModuleEvaluation(module, stack, index) function InnerModuleEvaluation(module, stack, index) { // Step 1 // TODO: Support module records other than source text module records. // Step 2
--- a/js/src/builtin/RegExp.cpp +++ b/js/src/builtin/RegExp.cpp @@ -766,28 +766,28 @@ bool js::regexp_unicode(JSContext* cx, u return true; } // Steps 1-3. return CallNonGenericMethod<IsRegExpObject, regexp_unicode_impl>(cx, args); } const JSPropertySpec js::regexp_properties[] = { - JS_SELF_HOSTED_GET("flags", "RegExpFlagsGetter", 0), + JS_SELF_HOSTED_GET("flags", "$RegExpFlagsGetter", 0), JS_PSG("global", regexp_global, 0), JS_PSG("ignoreCase", regexp_ignoreCase, 0), JS_PSG("multiline", regexp_multiline, 0), JS_PSG("source", regexp_source, 0), JS_PSG("sticky", regexp_sticky, 0), JS_PSG("unicode", regexp_unicode, 0), JS_PS_END}; const JSFunctionSpec js::regexp_methods[] = { - JS_SELF_HOSTED_FN(js_toSource_str, "RegExpToString", 0, 0), - JS_SELF_HOSTED_FN(js_toString_str, "RegExpToString", 0, 0), + JS_SELF_HOSTED_FN(js_toSource_str, "$RegExpToString", 0, 0), + JS_SELF_HOSTED_FN(js_toString_str, "$RegExpToString", 0, 0), JS_FN("compile", regexp_compile, 2, 0), JS_SELF_HOSTED_FN("exec", "RegExp_prototype_Exec", 1, 0), JS_SELF_HOSTED_FN("test", "RegExpTest", 1, 0), JS_SELF_HOSTED_SYM_FN(match, "RegExpMatch", 1, 0), JS_SELF_HOSTED_SYM_FN(matchAll, "RegExpMatchAll", 1, 0), JS_SELF_HOSTED_SYM_FN(replace, "RegExpReplace", 2, 0), JS_SELF_HOSTED_SYM_FN(search, "RegExpSearch", 1, 0), JS_SELF_HOSTED_SYM_FN(split, "RegExpSplit", 2, 0), @@ -885,17 +885,17 @@ const JSPropertySpec js::regexp_static_p JS_PSG("$7", static_paren7_getter, JSPROP_PERMANENT | JSPROP_ENUMERATE), JS_PSG("$8", static_paren8_getter, JSPROP_PERMANENT | JSPROP_ENUMERATE), JS_PSG("$9", static_paren9_getter, JSPROP_PERMANENT | JSPROP_ENUMERATE), JS_PSGS("$_", static_input_getter, static_input_setter, JSPROP_PERMANENT), JS_PSG("$&", static_lastMatch_getter, JSPROP_PERMANENT), JS_PSG("$+", static_lastParen_getter, JSPROP_PERMANENT), JS_PSG("$`", static_leftContext_getter, JSPROP_PERMANENT), JS_PSG("$'", static_rightContext_getter, JSPROP_PERMANENT), - JS_SELF_HOSTED_SYM_GET(species, "RegExpSpecies", 0), + JS_SELF_HOSTED_SYM_GET(species, "$RegExpSpecies", 0), JS_PS_END}; template <typename CharT> static bool IsTrailSurrogateWithLeadSurrogateImpl(HandleLinearString input, size_t index) { JS::AutoCheckCannotGC nogc; MOZ_ASSERT(index > 0 && index < input->length()); const CharT* inputChars = input->chars<CharT>(nogc);
--- a/js/src/builtin/RegExp.js +++ b/js/src/builtin/RegExp.js @@ -1,14 +1,16 @@ /* 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/. */ // ES6 draft rev34 (2015/02/20) 21.2.5.3 get RegExp.prototype.flags -function RegExpFlagsGetter() { +// Uncloned functions with `$` prefix are allocated as extended function +// to store the original name in `_SetCanonicalName`. +function $RegExpFlagsGetter() { // Steps 1-2. var R = this; if (!IsObject(R)) ThrowTypeError(JSMSG_OBJECT_REQUIRED, R === null ? "null" : typeof R); // Step 3. var result = ""; @@ -30,20 +32,20 @@ function RegExpFlagsGetter() { // Steps 16-18. if (R.sticky) result += "y"; // Step 19. return result; } -_SetCanonicalName(RegExpFlagsGetter, "get flags"); +_SetCanonicalName($RegExpFlagsGetter, "get flags"); // ES 2017 draft 40edb3a95a475c1b251141ac681b8793129d9a6d 21.2.5.14. -function RegExpToString() +function $RegExpToString() { // Step 1. var R = this; // Step 2. if (!IsObject(R)) ThrowTypeError(JSMSG_OBJECT_REQUIRED, R === null ? "null" : typeof R); @@ -51,17 +53,17 @@ function RegExpToString() var pattern = ToString(R.source); // Step 4. var flags = ToString(R.flags); // Steps 5-6. return "/" + pattern + "/" + flags; } -_SetCanonicalName(RegExpToString, "toString"); +_SetCanonicalName($RegExpToString, "toString"); // ES 2016 draft Mar 25, 2016 21.2.5.2.3. function AdvanceStringIndex(S, index) { // Step 1. assert(typeof S === "string", "Expected string as 1st argument"); // Step 2. assert(index >= 0 && index <= MAX_NUMERIC_INDEX, "Expected integer as 2nd argument"); @@ -1065,21 +1067,21 @@ function RegExpTest(string) { // Steps 3-4. var S = ToString(string); // Steps 5-6. return RegExpExec(R, S, true); } // ES 2016 draft Mar 25, 2016 21.2.4.2. -function RegExpSpecies() { +function $RegExpSpecies() { // Step 1. return this; } -_SetCanonicalName(RegExpSpecies, "get [Symbol.species]"); +_SetCanonicalName($RegExpSpecies, "get [Symbol.species]"); function IsRegExpMatchAllOptimizable(rx, C) { if (!IsRegExpObject(rx)) return false; var RegExpCtor = GetBuiltinConstructor("RegExp"); if (C !== RegExpCtor) return false;
--- a/js/src/builtin/SelfHostingDefines.h +++ b/js/src/builtin/SelfHostingDefines.h @@ -58,18 +58,22 @@ #define ACCESSOR_DESCRIPTOR_KIND 0x200 // Property descriptor array indices. #define PROP_DESC_ATTRS_AND_KIND_INDEX 0 #define PROP_DESC_VALUE_INDEX 1 #define PROP_DESC_GETTER_INDEX 1 #define PROP_DESC_SETTER_INDEX 2 -// The extended slot in which the self-hosted name for self-hosted builtins is -// stored. +// The extended slot of uncloned self-hosted function, in which the original +// name for self-hosted builtins is stored by `_SetCanonicalName`. +#define ORIGINAL_FUNCTION_NAME_SLOT 0 + +// The extended slot of cloned self-hosted function, in which the self-hosted +// name for self-hosted builtins is stored. #define LAZY_FUNCTION_NAME_SLOT 0 // Stores the length for bound functions, so the .length property doesn't need // to be resolved eagerly. #define BOUND_FUN_LENGTH_SLOT 1 #define ITERATOR_SLOT_TARGET 0 // Used for collection iterators.
--- a/js/src/builtin/Set.js +++ b/js/src/builtin/Set.js @@ -48,27 +48,29 @@ function SetForEach(callbackfn, thisArg var value = setIterationResult[0]; setIterationResult[0] = null; callContentFunction(callbackfn, thisArg, value, value, S); } } -function SetValues() { +// Uncloned functions with `$` prefix are allocated as extended function +// to store the original name in `_SetCanonicalName`. +function $SetValues() { return callFunction(std_Set_iterator, this); } -_SetCanonicalName(SetValues, "values"); +_SetCanonicalName($SetValues, "values"); // ES6 final draft 23.2.2.2. -function SetSpecies() { +function $SetSpecies() { // Step 1. return this; } -_SetCanonicalName(SetSpecies, "get [Symbol.species]"); +_SetCanonicalName($SetSpecies, "get [Symbol.species]"); var setIteratorTemp = { setIterationResult: null }; function SetIteratorNext() { // Step 1. var O = this;
--- a/js/src/builtin/TypedArray.js +++ b/js/src/builtin/TypedArray.js @@ -1440,27 +1440,30 @@ function TypedArraySubarray(begin, end) // Step 15. var beginByteOffset = srcByteOffset + (beginIndex << elementShift); // Steps 16-17. return TypedArraySpeciesCreateWithBuffer(obj, buffer, beginByteOffset, newLength); } // ES6 draft rev30 (2014/12/24) 22.2.3.30 %TypedArray%.prototype.values() -function TypedArrayValues() { +// +// Uncloned functions with `$` prefix are allocated as extended function +// to store the original name in `_SetCanonicalName`. +function $TypedArrayValues() { // Step 1. var O = this; // See the big comment in TypedArrayEntries for what we're doing here. IsTypedArrayEnsuringArrayBuffer(O); // Step 7. return CreateArrayIterator(O, ITEM_KIND_VALUE); } -_SetCanonicalName(TypedArrayValues, "values"); +_SetCanonicalName($TypedArrayValues, "values"); // Proposed for ES7: // https://github.com/tc39/Array.prototype.includes/blob/7c023c19a0/spec.md function TypedArrayIncludes(searchElement, fromIndex = 0) { // This function is not generic. if (!IsObject(this) || !IsTypedArray(this)) { return callFunction(CallTypedArrayMethodIfWrapped, this, searchElement, fromIndex, "TypedArrayIncludes"); @@ -1546,17 +1549,17 @@ function TypedArrayStaticFrom(source, ma // Inlined: GetMethod, step 4. if (!IsCallable(usingIterator)) ThrowTypeError(JSMSG_NOT_ITERABLE, DecompileArg(0, source)); // Try to take a fast path when there's no mapper function and the // constructor is a built-in TypedArray constructor. if (!mapping && IsTypedArrayConstructor(C) && IsObject(source)) { // The source is a TypedArray using the default iterator. - if (usingIterator === TypedArrayValues && IsTypedArray(source) && + if (usingIterator === $TypedArrayValues && IsTypedArray(source) && ArrayIteratorPrototypeOptimizable()) { // Step 7.a. // Omitted but we still need to throw if |source| was detached. GetAttachedArrayBuffer(source); // Step 7.b. var len = TypedArrayLength(source); @@ -1569,17 +1572,17 @@ function TypedArrayStaticFrom(source, ma targetObj[k] = source[k]; } // Step 7.g. return targetObj; } // The source is a packed array using the default iterator. - if (usingIterator === ArrayValues && IsPackedArray(source) && + if (usingIterator === $ArrayValues && IsPackedArray(source) && ArrayIteratorPrototypeOptimizable()) { // Steps 7.b-c. var targetObj = new C(source.length); // Steps 7.a, 7.d-f. TypedArrayInitFromPackedArray(targetObj, source); @@ -1669,21 +1672,21 @@ function TypedArrayStaticOf(/*...items*/ for (var k = 0; k < len; k++) newObj[k] = items[k]; // Step 8. return newObj; } // ES 2016 draft Mar 25, 2016 22.2.2.4. -function TypedArraySpecies() { +function $TypedArraySpecies() { // Step 1. return this; } -_SetCanonicalName(TypedArraySpecies, "get [Symbol.species]"); +_SetCanonicalName($TypedArraySpecies, "get [Symbol.species]"); // ES2018 draft rev 0525bb33861c7f4e9850f8a222c89642947c4b9c // 22.2.2.1.1 Runtime Semantics: IterableToList( items, method ) function IterableToList(items, method) { // Step 1 (Inlined GetIterator). // 7.4.1 GetIterator, step 1. assert(IsCallable(method), "method argument is a function"); @@ -1801,28 +1804,28 @@ function ArrayBufferSlice(start, end) { return new_; } function IsDetachedBufferThis() { return IsDetachedBuffer(this); } // ES 2016 draft Mar 25, 2016 24.1.3.3. -function ArrayBufferSpecies() { +function $ArrayBufferSpecies() { // Step 1. return this; } -_SetCanonicalName(ArrayBufferSpecies, "get [Symbol.species]"); +_SetCanonicalName($ArrayBufferSpecies, "get [Symbol.species]"); // Shared memory and atomics proposal (30 Oct 2016) -function SharedArrayBufferSpecies() { +function $SharedArrayBufferSpecies() { // Step 1. return this; } -_SetCanonicalName(SharedArrayBufferSpecies, "get [Symbol.species]"); +_SetCanonicalName($SharedArrayBufferSpecies, "get [Symbol.species]"); // Shared memory and atomics proposal 6.2.1.5.3 (30 Oct 2016) // http://tc39.github.io/ecmascript_sharedmem/shmem.html function SharedArrayBufferSlice(start, end) { // Step 1. var O = this; // Steps 2-4,
--- a/js/src/builtin/intl/Collator.cpp +++ b/js/src/builtin/intl/Collator.cpp @@ -63,17 +63,17 @@ static const JSFunctionSpec collator_sta 1, 0), JS_FS_END}; static const JSFunctionSpec collator_methods[] = { JS_SELF_HOSTED_FN("resolvedOptions", "Intl_Collator_resolvedOptions", 0, 0), JS_FN(js_toSource_str, collator_toSource, 0, 0), JS_FS_END}; static const JSPropertySpec collator_properties[] = { - JS_SELF_HOSTED_GET("compare", "Intl_Collator_compare_get", 0), + JS_SELF_HOSTED_GET("compare", "$Intl_Collator_compare_get", 0), JS_STRING_SYM_PS(toStringTag, "Object", JSPROP_READONLY), JS_PS_END}; /** * 10.1.2 Intl.Collator([ locales [, options]]) * * ES2017 Intl draft rev 94045d234762ad107a3d09bb6f7381a65f1a2f9b */ static bool Collator(JSContext* cx, const CallArgs& args) {
--- a/js/src/builtin/intl/Collator.js +++ b/js/src/builtin/intl/Collator.js @@ -343,39 +343,41 @@ function collatorCompareToBind(x, y) { * Returns a function bound to this Collator that compares x (converted to a * String value) and y (converted to a String value), * and returns a number less than 0 if x < y, 0 if x = y, or a number greater * than 0 if x > y according to the sort order for the locale and collation * options of this Collator object. * * Spec: ECMAScript Internationalization API Specification, 10.3.3. */ -function Intl_Collator_compare_get() { +// Uncloned functions with `$` prefix are allocated as extended function +// to store the original name in `_SetCanonicalName`. +function $Intl_Collator_compare_get() { // Step 1. var collator = this; // Steps 2-3. if (!IsObject(collator) || (collator = GuardToCollator(collator)) === null) - return callFunction(CallCollatorMethodIfWrapped, this, "Intl_Collator_compare_get"); + return callFunction(CallCollatorMethodIfWrapped, this, "$Intl_Collator_compare_get"); var internals = getCollatorInternals(collator); // Step 4. if (internals.boundCompare === undefined) { // Steps 4.a-b. var F = callFunction(FunctionBind, collatorCompareToBind, collator); // Step 4.c. internals.boundCompare = F; } // Step 5. return internals.boundCompare; } -_SetCanonicalName(Intl_Collator_compare_get, "get compare"); +_SetCanonicalName($Intl_Collator_compare_get, "get compare"); /** * Returns the resolved options for a Collator object. * * Spec: ECMAScript Internationalization API Specification, 10.3.4. */ function Intl_Collator_resolvedOptions() { // Step 1.
--- a/js/src/builtin/intl/DateTimeFormat.cpp +++ b/js/src/builtin/intl/DateTimeFormat.cpp @@ -74,17 +74,17 @@ static const JSFunctionSpec dateTimeForm static const JSFunctionSpec dateTimeFormat_methods[] = { JS_SELF_HOSTED_FN("resolvedOptions", "Intl_DateTimeFormat_resolvedOptions", 0, 0), JS_SELF_HOSTED_FN("formatToParts", "Intl_DateTimeFormat_formatToParts", 1, 0), JS_FN(js_toSource_str, dateTimeFormat_toSource, 0, 0), JS_FS_END}; static const JSPropertySpec dateTimeFormat_properties[] = { - JS_SELF_HOSTED_GET("format", "Intl_DateTimeFormat_format_get", 0), + JS_SELF_HOSTED_GET("format", "$Intl_DateTimeFormat_format_get", 0), JS_STRING_SYM_PS(toStringTag, "Object", JSPROP_READONLY), JS_PS_END}; /** * 12.2.1 Intl.DateTimeFormat([ locales [, options]]) * * ES2017 Intl draft rev 94045d234762ad107a3d09bb6f7381a65f1a2f9b */ static bool DateTimeFormat(JSContext* cx, const CallArgs& args, bool construct,
--- a/js/src/builtin/intl/DateTimeFormat.js +++ b/js/src/builtin/intl/DateTimeFormat.js @@ -786,40 +786,42 @@ function dateTimeFormatFormatToBind(date /** * Returns a function bound to this DateTimeFormat that returns a String value * representing the result of calling ToNumber(date) according to the * effective locale and the formatting options of this DateTimeFormat. * * Spec: ECMAScript Internationalization API Specification, 12.4.3. */ -function Intl_DateTimeFormat_format_get() { +// Uncloned functions with `$` prefix are allocated as extended function +// to store the original name in `_SetCanonicalName`. +function $Intl_DateTimeFormat_format_get() { // Steps 1-3. var thisArg = UnwrapDateTimeFormat(this); var dtf = thisArg; if (!IsObject(dtf) || (dtf = GuardToDateTimeFormat(dtf)) === null) { return callFunction(CallDateTimeFormatMethodIfWrapped, thisArg, - "Intl_DateTimeFormat_format_get"); + "$Intl_DateTimeFormat_format_get"); } var internals = getDateTimeFormatInternals(dtf); // Step 4. if (internals.boundFormat === undefined) { // Steps 4.a-b. var F = callFunction(FunctionBind, dateTimeFormatFormatToBind, dtf); // Step 4.c. internals.boundFormat = F; } // Step 5. return internals.boundFormat; } -_SetCanonicalName(Intl_DateTimeFormat_format_get, "get format"); +_SetCanonicalName($Intl_DateTimeFormat_format_get, "get format"); /** * Intl.DateTimeFormat.prototype.formatToParts ( date ) * * Spec: ECMAScript Internationalization API Specification, 12.4.4. */ function Intl_DateTimeFormat_formatToParts(date) { // Step 1.
--- a/js/src/builtin/intl/NumberFormat.cpp +++ b/js/src/builtin/intl/NumberFormat.cpp @@ -73,17 +73,17 @@ static const JSFunctionSpec numberFormat static const JSFunctionSpec numberFormat_methods[] = { JS_SELF_HOSTED_FN("resolvedOptions", "Intl_NumberFormat_resolvedOptions", 0, 0), JS_SELF_HOSTED_FN("formatToParts", "Intl_NumberFormat_formatToParts", 1, 0), JS_FN(js_toSource_str, numberFormat_toSource, 0, 0), JS_FS_END}; static const JSPropertySpec numberFormat_properties[] = { - JS_SELF_HOSTED_GET("format", "Intl_NumberFormat_format_get", 0), + JS_SELF_HOSTED_GET("format", "$Intl_NumberFormat_format_get", 0), JS_STRING_SYM_PS(toStringTag, "Object", JSPROP_READONLY), JS_PS_END}; /** * 11.2.1 Intl.NumberFormat([ locales [, options]]) * * ES2017 Intl draft rev 94045d234762ad107a3d09bb6f7381a65f1a2f9b */ static bool NumberFormat(JSContext* cx, const CallArgs& args, bool construct) {
--- a/js/src/builtin/intl/NumberFormat.js +++ b/js/src/builtin/intl/NumberFormat.js @@ -413,40 +413,42 @@ function numberFormatFormatToBind(value) /** * Returns a function bound to this NumberFormat that returns a String value * representing the result of calling ToNumber(value) according to the * effective locale and the formatting options of this NumberFormat. * * Spec: ECMAScript Internationalization API Specification, 11.4.3. */ -function Intl_NumberFormat_format_get() { +// Uncloned functions with `$` prefix are allocated as extended function +// to store the original name in `_SetCanonicalName`. +function $Intl_NumberFormat_format_get() { // Steps 1-3. var thisArg = UnwrapNumberFormat(this); var nf = thisArg; if (!IsObject(nf) || (nf = GuardToNumberFormat(nf)) === null) { return callFunction(CallNumberFormatMethodIfWrapped, thisArg, - "Intl_NumberFormat_format_get"); + "$Intl_NumberFormat_format_get"); } var internals = getNumberFormatInternals(nf); // Step 4. if (internals.boundFormat === undefined) { // Steps 4.a-b. var F = callFunction(FunctionBind, numberFormatFormatToBind, nf); // Step 4.c. internals.boundFormat = F; } // Step 5. return internals.boundFormat; } -_SetCanonicalName(Intl_NumberFormat_format_get, "get format"); +_SetCanonicalName($Intl_NumberFormat_format_get, "get format"); /** * 11.4.4 Intl.NumberFormat.prototype.formatToParts ( value ) */ function Intl_NumberFormat_formatToParts(value) { // Step 1. var nf = this;
--- a/js/src/frontend/BytecodeEmitter.cpp +++ b/js/src/frontend/BytecodeEmitter.cpp @@ -918,35 +918,44 @@ bool BytecodeEmitter::emitAtomOp(JSAtom* bool BytecodeEmitter::emitAtomOp(uint32_t atomIndex, JSOp op) { MOZ_ASSERT(JOF_OPTYPE(op) == JOF_ATOM); return emitIndexOp(op, atomIndex); } bool BytecodeEmitter::emitInternedScopeOp(uint32_t index, JSOp op) { MOZ_ASSERT(JOF_OPTYPE(op) == JOF_SCOPE); - MOZ_ASSERT(index < perScriptData().scopeList().length()); + MOZ_ASSERT(index < perScriptData().gcThingList().length()); return emitIndex32(op, index); } bool BytecodeEmitter::emitInternedObjectOp(uint32_t index, JSOp op) { MOZ_ASSERT(JOF_OPTYPE(op) == JOF_OBJECT); - MOZ_ASSERT(index < perScriptData().objectList().length); + MOZ_ASSERT(index < perScriptData().gcThingList().length()); return emitIndex32(op, index); } bool BytecodeEmitter::emitObjectOp(ObjectBox* objbox, JSOp op) { - return emitInternedObjectOp(perScriptData().objectList().add(objbox), op); + uint32_t index; + if (!perScriptData().gcThingList().append(objbox, &index)) { + return false; + } + + return emitInternedObjectOp(index, op); } bool BytecodeEmitter::emitObjectPairOp(ObjectBox* objbox1, ObjectBox* objbox2, JSOp op) { - uint32_t index = perScriptData().objectList().add(objbox1); - perScriptData().objectList().add(objbox2); - return emitInternedObjectOp(index, op); + uint32_t index1, index2; + if (!perScriptData().gcThingList().append(objbox1, &index1) || + !perScriptData().gcThingList().append(objbox2, &index2)) { + return false; + } + + return emitInternedObjectOp(index1, op); } bool BytecodeEmitter::emitRegExp(uint32_t index) { return emitIndex32(JSOP_REGEXP, index); } bool BytecodeEmitter::emitLocalOp(JSOp op, uint32_t slot) { MOZ_ASSERT(JOF_OPTYPE(op) != JOF_ENVCOORD); @@ -1673,17 +1682,17 @@ bool BytecodeEmitter::emitNewInit() { code[1] = 0; code[2] = 0; code[3] = 0; code[4] = 0; bytecodeSection().updateDepth(offset); return true; } -bool BytecodeEmitter::iteratorResultShape(unsigned* shape) { +bool BytecodeEmitter::iteratorResultShape(uint32_t* shape) { // No need to do any guessing for the object kind, since we know exactly how // many properties we plan to have. gc::AllocKind kind = gc::GetGCObjectKind(2); RootedPlainObject obj( cx, NewBuiltinClassInstance<PlainObject>(cx, kind, TenuredObject)); if (!obj) { return false; } @@ -1699,23 +1708,21 @@ bool BytecodeEmitter::iteratorResultShap return false; } ObjectBox* objbox = parser->newObjectBox(obj); if (!objbox) { return false; } - *shape = perScriptData().objectList().add(objbox); - - return true; + return perScriptData().gcThingList().append(objbox, shape); } bool BytecodeEmitter::emitPrepareIteratorResult() { - unsigned shape; + uint32_t shape; if (!iteratorResultShape(&shape)) { return false; } return emitIndex32(JSOP_NEWOBJECT, shape); } bool BytecodeEmitter::emitFinishIteratorResult(bool done) { uint32_t value_id; @@ -5001,20 +5008,21 @@ bool BytecodeEmitter::emitCopyDataProper return false; } MOZ_ASSERT(depth - int(argc) == bytecodeSection().stackDepth()); return true; } bool BytecodeEmitter::emitBigIntOp(BigInt* bigint) { - if (!perScriptData().bigIntList().append(bigint)) { - return false; - } - return emitIndex32(JSOP_BIGINT, perScriptData().bigIntList().length() - 1); + uint32_t index; + if (!perScriptData().gcThingList().append(bigint, &index)) { + return false; + } + return emitIndex32(JSOP_BIGINT, index); } bool BytecodeEmitter::emitIterator() { // Convert iterable to iterator. if (!emit1(JSOP_DUP)) { // [stack] OBJ OBJ return false; } @@ -8241,17 +8249,21 @@ bool BytecodeEmitter::replaceNewInitWith if (!objbox) { return false; } static_assert( JSOP_NEWINIT_LENGTH == JSOP_NEWOBJECT_LENGTH, "newinit and newobject must have equal length to edit in-place"); - uint32_t index = perScriptData().objectList().add(objbox); + uint32_t index; + if (!perScriptData().gcThingList().append(objbox, &index)) { + return false; + } + jsbytecode* code = bytecodeSection().code(offset); MOZ_ASSERT(code[0] == JSOP_NEWINIT); code[0] = JSOP_NEWOBJECT; SET_UINT32(code, index); return true; } @@ -9334,22 +9346,27 @@ bool BytecodeEmitter::emitTree( break; case ParseNodeKind::BigIntExpr: if (!emitBigIntOp(pn->as<BigIntLiteral>().box()->value())) { return false; } break; - case ParseNodeKind::RegExpExpr: - if (!emitRegExp(perScriptData().objectList().add( - pn->as<RegExpLiteral>().objbox()))) { + case ParseNodeKind::RegExpExpr: { + ObjectBox* obj = pn->as<RegExpLiteral>().objbox(); + uint32_t index; + if (!perScriptData().gcThingList().append(obj, &index)) { + return false; + } + if (!emitRegExp(index)) { return false; } break; + } case ParseNodeKind::TrueExpr: if (!emit1(JSOP_TRUE)) { return false; } break; case ParseNodeKind::FalseExpr: if (!emit1(JSOP_FALSE)) {
--- a/js/src/frontend/BytecodeEmitter.h +++ b/js/src/frontend/BytecodeEmitter.h @@ -273,22 +273,21 @@ struct MOZ_STACK_CLASS BytecodeEmitter { void setVarEmitterScope(EmitterScope* emitterScope) { MOZ_ASSERT(emitterScope); MOZ_ASSERT(!varEmitterScope); varEmitterScope = emitterScope; } Scope* outermostScope() const { - return perScriptData().scopeList().vector[0]; + return perScriptData().gcThingList().firstScope(); } Scope* innermostScope() const; Scope* bodyScope() const { - MOZ_ASSERT(bodyScopeIndex < perScriptData().scopeList().length()); - return perScriptData().scopeList().vector[bodyScopeIndex]; + return perScriptData().gcThingList().getScope(bodyScopeIndex); } MOZ_ALWAYS_INLINE MOZ_MUST_USE bool makeAtomIndex(JSAtom* atom, uint32_t* indexp) { MOZ_ASSERT(perScriptData().atomIndices()); AtomIndexMap::AddPtr p = perScriptData().atomIndices()->lookupForAdd(atom); if (p) { *indexp = p->value(); @@ -541,17 +540,17 @@ struct MOZ_STACK_CLASS BytecodeEmitter { bool* emitSetFunName); MOZ_MUST_USE bool emitAssignmentRhs(uint8_t offset); MOZ_MUST_USE bool emitNewInit(); MOZ_MUST_USE bool emitSingletonInitialiser(ListNode* objOrArray); MOZ_MUST_USE bool emitPrepareIteratorResult(); MOZ_MUST_USE bool emitFinishIteratorResult(bool done); - MOZ_MUST_USE bool iteratorResultShape(unsigned* shape); + MOZ_MUST_USE bool iteratorResultShape(uint32_t* shape); MOZ_MUST_USE bool emitGetDotGeneratorInInnermostScope() { return emitGetDotGeneratorInScope(*innermostEmitterScope()); } MOZ_MUST_USE bool emitGetDotGeneratorInScope(EmitterScope& currentScope); MOZ_MUST_USE bool allocateResumeIndex(BytecodeOffset offset, uint32_t* resumeIndex);
--- a/js/src/frontend/BytecodeSection.cpp +++ b/js/src/frontend/BytecodeSection.cpp @@ -13,69 +13,45 @@ #include "frontend/ParseNode.h" // ObjectBox #include "frontend/SharedContext.h" // FunctionBox #include "vm/BytecodeUtil.h" // INDEX_LIMIT, StackUses, StackDefs #include "vm/JSContext.h" // JSContext using namespace js; using namespace js::frontend; -void CGBigIntList::finish(mozilla::Span<GCPtrBigInt> array) { - MOZ_ASSERT(length() == array.size()); - - for (unsigned i = 0; i < length(); i++) { - array[i].init(vector[i]); - } -} +bool GCThingList::append(ObjectBox* objbox, uint32_t* index) { + // Append the object to the vector and return the index in *index. Also add + // the ObjectBox to the |lastbox| linked list for finishInnerFunctions below. -/* - * Find the index of the given object for code generator. - * - * Since the emitter refers to each parsed object only once, for the index we - * use the number of already indexed objects. We also add the object to a list - * to convert the list to a fixed-size array when we complete code generation, - * see js::CGObjectList::finish below. - */ -unsigned CGObjectList::add(ObjectBox* objbox) { MOZ_ASSERT(objbox->isObjectBox()); MOZ_ASSERT(!objbox->emitLink); objbox->emitLink = lastbox; lastbox = objbox; - return length++; + + *index = vector.length(); + return vector.append(JS::GCCellPtr(objbox->object())); } -void CGObjectList::finish(mozilla::Span<GCPtrObject> array) { - MOZ_ASSERT(length <= INDEX_LIMIT); - MOZ_ASSERT(length == array.size()); - - ObjectBox* objbox = lastbox; - for (GCPtrObject& obj : mozilla::Reversed(array)) { - MOZ_ASSERT(obj == nullptr); - MOZ_ASSERT(objbox->object()->isTenured()); - obj.init(objbox->object()); - objbox = objbox->emitLink; - } -} - -void CGObjectList::finishInnerFunctions() { +void GCThingList::finishInnerFunctions() { ObjectBox* objbox = lastbox; while (objbox) { if (objbox->isFunctionBox()) { objbox->asFunctionBox()->finish(); } objbox = objbox->emitLink; } } -void CGScopeList::finish(mozilla::Span<GCPtrScope> array) { +void GCThingList::finish(mozilla::Span<JS::GCCellPtr> array) { MOZ_ASSERT(length() <= INDEX_LIMIT); MOZ_ASSERT(length() == array.size()); for (uint32_t i = 0; i < length(); i++) { - array[i].init(vector[i]); + array[i] = vector[i].get().get(); } } bool CGTryNoteList::append(JSTryNoteKind kind, uint32_t stackDepth, BytecodeOffset start, BytecodeOffset end) { MOZ_ASSERT(start <= end); // Offsets are given relative to sections, but we only expect main-section @@ -165,13 +141,11 @@ void BytecodeSection::updateDepth(Byteco stackDepth_ += ndefs; if (uint32_t(stackDepth_) > maxStackDepth_) { maxStackDepth_ = stackDepth_; } } PerScriptData::PerScriptData(JSContext* cx) - : scopeList_(cx), - bigIntList_(cx), - atomIndices_(cx->frontendCollectionPool()) {} + : gcThingList_(cx), atomIndices_(cx->frontendCollectionPool()) {} bool PerScriptData::init(JSContext* cx) { return atomIndices_.acquire(cx); }
--- a/js/src/frontend/BytecodeSection.h +++ b/js/src/frontend/BytecodeSection.h @@ -3,16 +3,17 @@ * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #ifndef frontend_BytecodeSection_h #define frontend_BytecodeSection_h #include "mozilla/Attributes.h" // MOZ_MUST_USE, MOZ_STACK_CLASS +#include "mozilla/Maybe.h" // mozilla::Maybe #include "mozilla/Span.h" // mozilla::Span #include <stddef.h> // ptrdiff_t, size_t #include <stdint.h> // uint16_t, int32_t, uint32_t #include "NamespaceImports.h" // ValueVector #include "frontend/BytecodeOffset.h" // BytecodeOffset #include "frontend/JumpList.h" // JumpTarget @@ -32,47 +33,55 @@ namespace js { class Scope; using BigIntVector = JS::GCVector<js::BigInt*>; namespace frontend { class ObjectBox; -class CGBigIntList { - JS::Rooted<BigIntVector> vector; +struct MOZ_STACK_CLASS GCThingList { + JS::RootedVector<StackGCCellPtr> vector; - public: - explicit CGBigIntList(JSContext* cx) : vector(cx, BigIntVector(cx)) {} - MOZ_MUST_USE bool append(js::BigInt* bi) { return vector.append(bi); } - size_t length() const { return vector.length(); } - void finish(mozilla::Span<GCPtrBigInt> array); -}; + // Last emitted object. + ObjectBox* lastbox = nullptr; -struct CGObjectList { - // Number of emitted so far objects. - uint32_t length; - // Last emitted object. - ObjectBox* lastbox; + // Index of the first scope in the vector. + mozilla::Maybe<uint32_t> firstScopeIndex; + + explicit GCThingList(JSContext* cx) : vector(cx) {} - CGObjectList() : length(0), lastbox(nullptr) {} - - unsigned add(ObjectBox* objbox); - void finish(mozilla::Span<GCPtrObject> array); - void finishInnerFunctions(); -}; + MOZ_MUST_USE bool append(Scope* scope, uint32_t* index) { + *index = vector.length(); + if (!vector.append(JS::GCCellPtr(scope))) { + return false; + } + if (!firstScopeIndex) { + firstScopeIndex.emplace(*index); + } + return true; + } + MOZ_MUST_USE bool append(BigInt* bi, uint32_t* index) { + *index = vector.length(); + return vector.append(JS::GCCellPtr(bi)); + } + MOZ_MUST_USE bool append(ObjectBox* obj, uint32_t* index); -struct MOZ_STACK_CLASS CGScopeList { - JS::Rooted<GCVector<Scope*>> vector; + uint32_t length() const { return vector.length(); } + void finish(mozilla::Span<JS::GCCellPtr> array); + void finishInnerFunctions(); - explicit CGScopeList(JSContext* cx) : vector(cx, GCVector<Scope*>(cx)) {} + Scope* getScope(size_t index) const { + return &vector[index].get().get().as<Scope>(); + } - bool append(Scope* scope) { return vector.append(scope); } - uint32_t length() const { return vector.length(); } - void finish(mozilla::Span<GCPtrScope> array); + Scope* firstScope() const { + MOZ_ASSERT(firstScopeIndex.isSome()); + return getScope(*firstScopeIndex); + } }; struct CGTryNoteList { Vector<JSTryNote> list; explicit CGTryNoteList(JSContext* cx) : list(cx) {} MOZ_MUST_USE bool append(JSTryNoteKind kind, uint32_t stackDepth, BytecodeOffset start, BytecodeOffset end); @@ -320,45 +329,25 @@ class BytecodeSection { // Data that is not directly associated with specific opcode/index inside // bytecode, but referred from bytecode is stored in this class. class PerScriptData { public: explicit PerScriptData(JSContext* cx); MOZ_MUST_USE bool init(JSContext* cx); - // ---- Scope ---- - - CGScopeList& scopeList() { return scopeList_; } - const CGScopeList& scopeList() const { return scopeList_; } - - // ---- Literals ---- - - CGBigIntList& bigIntList() { return bigIntList_; } - const CGBigIntList& bigIntList() const { return bigIntList_; } - - CGObjectList& objectList() { return objectList_; } - const CGObjectList& objectList() const { return objectList_; } + GCThingList& gcThingList() { return gcThingList_; } + const GCThingList& gcThingList() const { return gcThingList_; } PooledMapPtr<AtomIndexMap>& atomIndices() { return atomIndices_; } const PooledMapPtr<AtomIndexMap>& atomIndices() const { return atomIndices_; } private: - // ---- Scope ---- - - // List of emitted scopes. - CGScopeList scopeList_; - - // ---- Literals ---- - - // List of bigints used by script. - CGBigIntList bigIntList_; - - // List of emitted objects. - CGObjectList objectList_; + // List of emitted scopes/objects/bigints. + GCThingList gcThingList_; // Map from atom to index. PooledMapPtr<AtomIndexMap> atomIndices_; }; } /* namespace frontend */ } /* namespace js */
--- a/js/src/frontend/EmitterScope.cpp +++ b/js/src/frontend/EmitterScope.cpp @@ -337,26 +337,25 @@ NameLocation EmitterScope::searchAndCach template <typename ScopeCreator> bool EmitterScope::internScope(BytecodeEmitter* bce, ScopeCreator createScope) { RootedScope enclosing(bce->cx, enclosingScope(bce)); Scope* scope = createScope(bce->cx, enclosing); if (!scope) { return false; } hasEnvironment_ = scope->hasEnvironment(); - scopeIndex_ = bce->perScriptData().scopeList().length(); - return bce->perScriptData().scopeList().append(scope); + return bce->perScriptData().gcThingList().append(scope, &scopeIndex_); } template <typename ScopeCreator> bool EmitterScope::internBodyScope(BytecodeEmitter* bce, ScopeCreator createScope) { MOZ_ASSERT(bce->bodyScopeIndex == UINT32_MAX, "There can be only one body scope"); - bce->bodyScopeIndex = bce->perScriptData().scopeList().length(); + bce->bodyScopeIndex = bce->perScriptData().gcThingList().length(); return internScope(bce, createScope); } bool EmitterScope::appendScopeNote(BytecodeEmitter* bce) { MOZ_ASSERT(ScopeKindIsInBody(scope(bce)->kind()) && enclosingInFrame(), "Scope notes are not needed for body-level scopes."); noteIndex_ = bce->bytecodeSection().scopeNoteList().length(); return bce->bytecodeSection().scopeNoteList().append( @@ -1066,17 +1065,17 @@ bool EmitterScope::leave(BytecodeEmitter } } } return true; } Scope* EmitterScope::scope(const BytecodeEmitter* bce) const { - return bce->perScriptData().scopeList().vector[index()]; + return bce->perScriptData().gcThingList().getScope(index()); } NameLocation EmitterScope::lookup(BytecodeEmitter* bce, JSAtom* name) { if (Maybe<NameLocation> loc = lookupInCache(bce, name)) { return *loc; } return searchAndCache(bce, name); }
--- a/js/src/frontend/FunctionEmitter.cpp +++ b/js/src/frontend/FunctionEmitter.cpp @@ -215,17 +215,20 @@ bool FunctionEmitter::emitAsmJSModule() #ifdef DEBUG state_ = State::End; #endif return true; } bool FunctionEmitter::emitFunction() { // Make the function object a literal in the outer script's pool. - unsigned index = bce_->perScriptData().objectList().add(funbox_); + uint32_t index; + if (!bce_->perScriptData().gcThingList().append(funbox_, &index)) { + return false; + } // [stack] if (isHoisted_ == IsHoisted::No) { return emitNonHoisted(index); // [stack] FUN? }
--- a/js/src/frontend/ParseNode.h +++ b/js/src/frontend/ParseNode.h @@ -2183,17 +2183,17 @@ class TraceListNode { class BigIntBox : public TraceListNode { public: BigIntBox(BigInt* bi, TraceListNode* link); BigInt* value() const { return gcThing->as<BigInt>(); } }; class ObjectBox : public TraceListNode { protected: - friend struct CGObjectList; + friend struct GCThingList; ObjectBox* emitLink; ObjectBox(JSFunction* function, TraceListNode* link); public: ObjectBox(JSObject* obj, TraceListNode* link); JSObject* object() const { return gcThing->as<JSObject>(); }
--- a/js/src/frontend/Parser.cpp +++ b/js/src/frontend/Parser.cpp @@ -46,16 +46,17 @@ #include "js/RegExpFlags.h" // JS::RegExpFlags #include "vm/BytecodeUtil.h" #include "vm/JSAtom.h" #include "vm/JSContext.h" #include "vm/JSFunction.h" #include "vm/JSScript.h" #include "vm/ModuleBuilder.h" // js::ModuleBuilder #include "vm/RegExpObject.h" +#include "vm/SelfHosting.h" #include "vm/StringType.h" #include "wasm/AsmJS.h" #include "frontend/ParseContext-inl.h" #include "frontend/SharedContext-inl.h" #include "vm/EnvironmentObject-inl.h" using namespace js; @@ -1968,16 +1969,20 @@ JSFunction* AllocNewFunction(JSContext* bool isSelfHosting /* = false */, bool inFunctionBox /* = false */) { MOZ_ASSERT_IF(kind == FunctionSyntaxKind::Statement, atom != nullptr); RootedFunction fun(cx); gc::AllocKind allocKind = gc::AllocKind::FUNCTION; JSFunction::Flags flags; + bool isExtendedUnclonedSelfHostedFunctionName = + isSelfHosting && atom && IsExtendedUnclonedSelfHostedFunctionName(atom); + MOZ_ASSERT_IF(isExtendedUnclonedSelfHostedFunctionName, !inFunctionBox); + switch (kind) { case FunctionSyntaxKind::Expression: flags = (generatorKind == GeneratorKind::NotGenerator && asyncKind == FunctionAsyncKind::SyncFunction ? JSFunction::INTERPRETED_LAMBDA : JSFunction::INTERPRETED_LAMBDA_GENERATOR_OR_ASYNC); break; case FunctionSyntaxKind::Arrow: @@ -1998,17 +2003,17 @@ JSFunction* AllocNewFunction(JSContext* allocKind = gc::AllocKind::FUNCTION_EXTENDED; break; case FunctionSyntaxKind::Setter: flags = JSFunction::INTERPRETED_SETTER; allocKind = gc::AllocKind::FUNCTION_EXTENDED; break; default: MOZ_ASSERT(kind == FunctionSyntaxKind::Statement); - if (isSelfHosting && !inFunctionBox) { + if (isExtendedUnclonedSelfHostedFunctionName) { allocKind = gc::AllocKind::FUNCTION_EXTENDED; } flags = (generatorKind == GeneratorKind::NotGenerator && asyncKind == FunctionAsyncKind::SyncFunction ? JSFunction::INTERPRETED_NORMAL : JSFunction::INTERPRETED_GENERATOR_OR_ASYNC); }
--- a/js/src/gc/Barrier.h +++ b/js/src/gc/Barrier.h @@ -952,16 +952,31 @@ struct WeakHeapPtrHasher { static HashNumber hash(Lookup obj) { return DefaultHasher<T>::hash(obj); } static bool match(const Key& k, Lookup l) { return k.unbarrieredGet() == l; } static void rekey(Key& k, const Key& newKey) { k.set(newKey.unbarrieredGet()); } }; +// Wrapper around GCCellPtr for use with RootedVector<StackGCCellPtr>. +class MOZ_STACK_CLASS StackGCCellPtr { + JS::GCCellPtr ptr_; + + public: + MOZ_IMPLICIT StackGCCellPtr(JS::GCCellPtr ptr) : ptr_(ptr) {} + StackGCCellPtr() = default; + + void operator=(const StackGCCellPtr& other) { ptr_ = other.ptr_; } + + void trace(JSTracer* trc); + + JS::GCCellPtr get() const { return ptr_; } +}; + } // namespace js namespace mozilla { /* Specialized hashing policy for GCPtrs. */ template <class T> struct DefaultHasher<js::GCPtr<T>> : js::GCPtrHasher<T> {};
--- a/js/src/gc/Marking.cpp +++ b/js/src/gc/Marking.cpp @@ -547,16 +547,24 @@ void js::TraceManuallyBarrieredGenericPo TraceManuallyBarrieredEdge(trc, &t, name); return t; }); if (traced != thing) { *thingp = traced; } } +void StackGCCellPtr::trace(JSTracer* trc) { + Cell* thing = ptr_.asCell(); + TraceGenericPointerRoot(trc, &thing, "stack-gc-cell-ptr"); + if (thing != ptr_.asCell()) { + ptr_ = JS::GCCellPtr(thing, ptr_.kind()); + } +} + // This method is responsible for dynamic dispatch to the real tracer // implementation. Consider replacing this choke point with virtual dispatch: // a sufficiently smart C++ compiler may be able to devirtualize some paths. template <typename T> void js::gc::TraceEdgeInternal(JSTracer* trc, T* thingp, const char* name) { #define IS_SAME_TYPE_OR(name, type, _, _1) mozilla::IsSame<type*, T>::value || static_assert(JS_FOR_EACH_TRACEKIND(IS_SAME_TYPE_OR) mozilla::IsSame<T, JS::Value>::value ||
--- a/js/src/jit/IonAnalysis.cpp +++ b/js/src/jit/IonAnalysis.cpp @@ -2297,17 +2297,22 @@ static bool IsRegExpHoistableCall(Compil return false; } JSAtom* name; if (WrappedFunction* fun = call->getSingleTarget()) { if (!fun->isSelfHostedBuiltin()) { return false; } - name = GetSelfHostedFunctionName(fun->rawJSFunction()); + + // Avoid accessing `JSFunction.flags_` via `JSFunction::isExtended`. + if (!fun->isExtended()) { + return false; + } + name = GetClonedSelfHostedFunctionNameOffMainThread(fun->rawJSFunction()); } else { MDefinition* funDef = call->getFunction(); if (funDef->isDebugCheckSelfHosted()) { funDef = funDef->toDebugCheckSelfHosted()->input(); } if (funDef->isTypeBarrier()) { funDef = funDef->toTypeBarrier()->input(); }
--- a/js/src/jit/MIR.cpp +++ b/js/src/jit/MIR.cpp @@ -1518,17 +1518,18 @@ bool MParameter::congruentTo(const MDefi WrappedFunction::WrappedFunction(JSFunction* fun) : fun_(fun), nargs_(fun->nargs()), isNative_(fun->isNative()), isNativeWithJitEntry_(fun->isNativeWithJitEntry()), isConstructor_(fun->isConstructor()), isClassConstructor_(fun->isClassConstructor()), - isSelfHostedBuiltin_(fun->isSelfHostedBuiltin()) {} + isSelfHostedBuiltin_(fun->isSelfHostedBuiltin()), + isExtended_(fun->isExtended()) {} MCall* MCall::New(TempAllocator& alloc, JSFunction* target, size_t maxArgc, size_t numActualArgs, bool construct, bool ignoresReturnValue, bool isDOMCall, DOMObjectKind objectKind) { WrappedFunction* wrappedTarget = target ? new (alloc) WrappedFunction(target) : nullptr; MOZ_ASSERT(maxArgc >= numActualArgs); MCall* ins;
--- a/js/src/jit/MIR.h +++ b/js/src/jit/MIR.h @@ -2664,30 +2664,32 @@ class MInitElemGetterSetter class WrappedFunction : public TempObject { CompilerFunction fun_; uint16_t nargs_; bool isNative_ : 1; bool isNativeWithJitEntry_ : 1; bool isConstructor_ : 1; bool isClassConstructor_ : 1; bool isSelfHostedBuiltin_ : 1; + bool isExtended_ : 1; public: explicit WrappedFunction(JSFunction* fun); size_t nargs() const { return nargs_; } bool isNative() const { return isNative_; } bool isNativeWithJitEntry() const { return isNativeWithJitEntry_; } bool isNativeWithCppEntry() const { return isNative() && !isNativeWithJitEntry(); } bool isConstructor() const { return isConstructor_; } bool isClassConstructor() const { return isClassConstructor_; } bool isSelfHostedBuiltin() const { return isSelfHostedBuiltin_; } + bool isExtended() const { return isExtended_; } // fun->native() and fun->jitInfo() can safely be called off-thread: these // fields never change. JSNative native() const { return fun_->native(); } bool hasJitInfo() const { return fun_->hasJitInfo(); } const JSJitInfo* jitInfo() const { return fun_->jitInfo(); } JSFunction* rawJSFunction() const { return fun_; }
--- a/js/src/shell/js.cpp +++ b/js/src/shell/js.cpp @@ -3274,18 +3274,23 @@ static MOZ_MUST_USE bool DisassembleScri } if (!TryNotes(cx, script, sp)) { return false; } if (!ScopeNotes(cx, script, sp)) { return false; } - if (recursive && script->hasObjects()) { - for (JSObject* obj : script->objects()) { + if (recursive) { + for (JS::GCCellPtr gcThing : script->gcthings()) { + if (!gcThing.is<JSObject>()) { + continue; + } + + JSObject* obj = &gcThing.as<JSObject>(); if (obj->is<JSFunction>()) { if (!sp->put("\n")) { return false; } RootedFunction fun(cx, &obj->as<JSFunction>()); if (fun->isInterpreted()) { RootedScript script(cx, JSFunction::getOrCreateScript(cx, fun));
--- a/js/src/vm/ArrayBufferObject.cpp +++ b/js/src/vm/ArrayBufferObject.cpp @@ -300,17 +300,17 @@ static const ClassOps ArrayBufferObjectC nullptr, /* construct */ nullptr, /* trace */ }; static const JSFunctionSpec arraybuffer_functions[] = { JS_FN("isView", ArrayBufferObject::fun_isView, 1, 0), JS_FS_END}; static const JSPropertySpec arraybuffer_properties[] = { - JS_SELF_HOSTED_SYM_GET(species, "ArrayBufferSpecies", 0), JS_PS_END}; + JS_SELF_HOSTED_SYM_GET(species, "$ArrayBufferSpecies", 0), JS_PS_END}; static const JSFunctionSpec arraybuffer_proto_functions[] = { JS_SELF_HOSTED_FN("slice", "ArrayBufferSlice", 2, 0), JS_FS_END}; static const JSPropertySpec arraybuffer_proto_properties[] = { JS_PSG("byteLength", ArrayBufferObject::byteLengthGetter, 0), JS_STRING_SYM_PS(toStringTag, "ArrayBuffer", JSPROP_READONLY), JS_PS_END};
--- a/js/src/vm/BytecodeUtil.cpp +++ b/js/src/vm/BytecodeUtil.cpp @@ -2908,22 +2908,24 @@ static bool GenerateLcovInfo(JSContext* if (!script->isTopLevel()) { continue; } // Iterate from the last to the first object in order to have // the functions them visited in the opposite order when popping // elements from the stack of remaining scripts, such that the // functions are more-less listed with increasing line numbers. - if (!script->hasObjects()) { - continue; - } - auto objects = script->objects(); - for (JSObject* obj : mozilla::Reversed(objects)) { + auto gcthings = script->gcthings(); + for (JS::GCCellPtr gcThing : mozilla::Reversed(gcthings)) { + if (!gcThing.is<JSObject>()) { + continue; + } + // Only continue on JSFunction objects. + JSObject* obj = &gcThing.as<JSObject>(); if (!obj->is<JSFunction>()) { continue; } fun = &obj->as<JSFunction>(); // Let's skip wasm for now. if (!fun->isInterpreted()) { continue;
--- a/js/src/vm/CommonPropertyNames.h +++ b/js/src/vm/CommonPropertyNames.h @@ -14,25 +14,25 @@ #define FOR_EACH_COMMON_PROPERTYNAME(MACRO) \ MACRO(add, add, "add") \ MACRO(allowContentIter, allowContentIter, "allowContentIter") \ MACRO(anonymous, anonymous, "anonymous") \ MACRO(Any, Any, "Any") \ MACRO(apply, apply, "apply") \ MACRO(args, args, "args") \ MACRO(arguments, arguments, "arguments") \ - MACRO(ArrayBufferSpecies, ArrayBufferSpecies, "ArrayBufferSpecies") \ + MACRO(ArrayBufferSpecies, ArrayBufferSpecies, "$ArrayBufferSpecies") \ MACRO(ArrayIterator, ArrayIterator, "Array Iterator") \ MACRO(ArrayIteratorNext, ArrayIteratorNext, "ArrayIteratorNext") \ MACRO(ArraySort, ArraySort, "ArraySort") \ - MACRO(ArraySpecies, ArraySpecies, "ArraySpecies") \ + MACRO(ArraySpecies, ArraySpecies, "$ArraySpecies") \ MACRO(ArraySpeciesCreate, ArraySpeciesCreate, "ArraySpeciesCreate") \ MACRO(ArrayToLocaleString, ArrayToLocaleString, "ArrayToLocaleString") \ MACRO(ArrayType, ArrayType, "ArrayType") \ - MACRO(ArrayValues, ArrayValues, "ArrayValues") \ + MACRO(ArrayValues, ArrayValues, "$ArrayValues") \ MACRO(as, as, "as") \ MACRO(Async, Async, "Async") \ MACRO(AsyncFromSyncIterator, AsyncFromSyncIterator, \ "Async-from-Sync Iterator") \ MACRO(AsyncFunction, AsyncFunction, "AsyncFunction") \ MACRO(AsyncFunctionNext, AsyncFunctionNext, "AsyncFunctionNext") \ MACRO(AsyncFunctionThrow, AsyncFunctionThrow, "AsyncFunctionThrow") \ MACRO(AsyncGenerator, AsyncGenerator, "AsyncGenerator") \ @@ -338,17 +338,17 @@ MACRO(propertyIsEnumerable, propertyIsEnumerable, "propertyIsEnumerable") \ MACRO(protected, protected_, "protected") \ MACRO(proto, proto, "__proto__") \ MACRO(prototype, prototype, "prototype") \ MACRO(proxy, proxy, "proxy") \ MACRO(raw, raw, "raw") \ MACRO(reason, reason, "reason") \ MACRO(RegExpBuiltinExec, RegExpBuiltinExec, "RegExpBuiltinExec") \ - MACRO(RegExpFlagsGetter, RegExpFlagsGetter, "RegExpFlagsGetter") \ + MACRO(RegExpFlagsGetter, RegExpFlagsGetter, "$RegExpFlagsGetter") \ MACRO(RegExpMatcher, RegExpMatcher, "RegExpMatcher") \ MACRO(RegExpSearcher, RegExpSearcher, "RegExpSearcher") \ MACRO(RegExpStringIterator, RegExpStringIterator, "RegExp String Iterator") \ MACRO(RegExpTester, RegExpTester, "RegExpTester") \ MACRO(RegExp_prototype_Exec, RegExp_prototype_Exec, "RegExp_prototype_Exec") \ MACRO(Reify, Reify, "Reify") \ MACRO(reject, reject, "reject") \ MACRO(rejected, rejected, "rejected") \
--- a/js/src/vm/Debugger.cpp +++ b/js/src/vm/Debugger.cpp @@ -6454,39 +6454,40 @@ static bool DebuggerScript_getChildScrip THIS_DEBUGSCRIPT_SCRIPT_DELAZIFY(cx, argc, vp, "getChildScripts", args, obj, script); Debugger* dbg = Debugger::fromChildJSObject(obj); RootedObject result(cx, NewDenseEmptyArray(cx)); if (!result) { return false; } - if (script->hasObjects()) { - // script->savedCallerFun indicates that this is a direct eval script - // and the calling function is stored as script->objects()->vector[0]. - // It is not really a child script of this script, so skip it using - // innerObjectsStart(). - RootedFunction fun(cx); - RootedScript funScript(cx); - RootedObject s(cx); - for (const GCPtrObject& obj : script->objects()) { - if (obj->is<JSFunction>()) { - fun = &obj->as<JSFunction>(); - // The inner function could be an asm.js native. - if (!IsInterpretedNonSelfHostedFunction(fun)) { - continue; - } - funScript = GetOrCreateFunctionScript(cx, fun); - if (!funScript) { - return false; - } - s = dbg->wrapScript(cx, funScript); - if (!s || !NewbornArrayPush(cx, result, ObjectValue(*s))) { - return false; - } + + // Wrap and append scripts for the inner functions in script->gcthings(). + RootedFunction fun(cx); + RootedScript funScript(cx); + RootedObject s(cx); + for (JS::GCCellPtr gcThing : script->gcthings()) { + if (!gcThing.is<JSObject>()) { + continue; + } + + JSObject* obj = &gcThing.as<JSObject>(); + if (obj->is<JSFunction>()) { + fun = &obj->as<JSFunction>(); + // The inner function could be an asm.js native. + if (!IsInterpretedNonSelfHostedFunction(fun)) { + continue; + } + funScript = GetOrCreateFunctionScript(cx, fun); + if (!funScript) { + return false; + } + s = dbg->wrapScript(cx, funScript); + if (!s || !NewbornArrayPush(cx, result, ObjectValue(*s))) { + return false; } } } args.rval().setObject(*result); return true; } static bool ScriptOffset(JSContext* cx, const Value& v, size_t* offsetp) {
--- a/js/src/vm/EnvironmentObject.cpp +++ b/js/src/vm/EnvironmentObject.cpp @@ -3796,30 +3796,33 @@ static bool RemoveReferencedNames(JSCont break; } if (name) { remainingNames.remove(name); } } - if (script->hasObjects()) { - RootedFunction fun(cx); - RootedScript innerScript(cx); - for (JSObject* obj : script->objects()) { - if (obj->is<JSFunction>() && obj->as<JSFunction>().isInterpreted()) { - fun = &obj->as<JSFunction>(); - innerScript = JSFunction::getOrCreateScript(cx, fun); - if (!innerScript) { - return false; - } - - if (!RemoveReferencedNames(cx, innerScript, remainingNames)) { - return false; - } + RootedFunction fun(cx); + RootedScript innerScript(cx); + for (JS::GCCellPtr gcThing : script->gcthings()) { + if (!gcThing.is<JSObject>()) { + continue; + } + + JSObject* obj = &gcThing.as<JSObject>(); + if (obj->is<JSFunction>() && obj->as<JSFunction>().isInterpreted()) { + fun = &obj->as<JSFunction>(); + innerScript = JSFunction::getOrCreateScript(cx, fun); + if (!innerScript) { + return false; + } + + if (!RemoveReferencedNames(cx, innerScript, remainingNames)) { + return false; } } } return true; } static bool AnalyzeEntrainedVariablesInScript(JSContext* cx, @@ -3868,27 +3871,30 @@ static bool AnalyzeEntrainedVariablesInS r.popFront()) { buf.printf(" "); buf.putString(r.front()); } printf("%s\n", buf.string()); } - if (innerScript->hasObjects()) { - RootedFunction fun(cx); - RootedScript innerInnerScript(cx); - for (JSObject* obj : script->objects()) { - if (obj->is<JSFunction>() && obj->as<JSFunction>().isInterpreted()) { - fun = &obj->as<JSFunction>(); - innerInnerScript = JSFunction::getOrCreateScript(cx, fun); - if (!innerInnerScript || - !AnalyzeEntrainedVariablesInScript(cx, script, innerInnerScript)) { - return false; - } + RootedFunction fun(cx); + RootedScript innerInnerScript(cx); + for (JS::GCCellPtr gcThing : script->gcthings()) { + if (!gcThing.is<JSObject>()) { + continue; + } + + JSObject* obj = &gcThing.as<JSObject>(); + if (obj->is<JSFunction>() && obj->as<JSFunction>().isInterpreted()) { + fun = &obj->as<JSFunction>(); + innerInnerScript = JSFunction::getOrCreateScript(cx, fun); + if (!innerInnerScript || + !AnalyzeEntrainedVariablesInScript(cx, script, innerInnerScript)) { + return false; } } } return true; } // Look for local variables in script or any other script inner to it, which are @@ -3898,23 +3904,24 @@ static bool AnalyzeEntrainedVariablesInS // function foo() { // var a, b; // function bar() { return a; } // function baz() { return b; } // } // // |bar| unnecessarily entrains |b|, and |baz| unnecessarily entrains |a|. bool js::AnalyzeEntrainedVariables(JSContext* cx, HandleScript script) { - if (!script->hasObjects()) { - return true; - } - RootedFunction fun(cx); RootedScript innerScript(cx); - for (JSObject* obj : script->objects()) { + for (JS::GCCellPtr gcThing : script->gcthings()) { + if (!gcThing.is<JSObject>()) { + continue; + } + + JSObject* obj = &gcThing.as<JSObject>(); if (obj->is<JSFunction>() && obj->as<JSFunction>().isInterpreted()) { fun = &obj->as<JSFunction>(); innerScript = JSFunction::getOrCreateScript(cx, fun); if (!innerScript) { return false; } if (script->functionDelazifying() &&
--- a/js/src/vm/JSFunction.cpp +++ b/js/src/vm/JSFunction.cpp @@ -1215,17 +1215,17 @@ const Class JSFunction::class_ = {js_Fun const Class* const js::FunctionClassPtr = &JSFunction::class_; bool JSFunction::isDerivedClassConstructor() { bool derived; if (isInterpretedLazy()) { // There is only one plausible lazy self-hosted derived // constructor. if (isSelfHostedBuiltin()) { - JSAtom* name = GetSelfHostedFunctionName(this); + JSAtom* name = GetClonedSelfHostedFunctionName(this); // This function is called from places without access to a // JSContext. Trace some plumbing to get what we want. derived = name == compartment() ->runtimeFromAnyThread() ->commonNames->DefaultDerivedClassConstructor; } else { derived = lazyScript()->isDerivedClassConstructor(); @@ -1680,17 +1680,17 @@ bool JSFunction::createScriptForLazilyIn } } return true; } /* Lazily cloned self-hosted script. */ MOZ_ASSERT(fun->isSelfHostedBuiltin()); - RootedAtom funAtom(cx, GetSelfHostedFunctionName(fun)); + RootedAtom funAtom(cx, GetClonedSelfHostedFunctionName(fun)); if (!funAtom) { return false; } Rooted<PropertyName*> funName(cx, funAtom->asPropertyName()); return cx->runtime()->cloneSelfHostedFunctionScript(cx, funName, fun); } void JSFunction::maybeRelazify(JSRuntime* rt) { @@ -1744,17 +1744,17 @@ void JSFunction::maybeRelazify(JSRuntime flags_ |= INTERPRETED_LAZY; LazyScript* lazy = script->maybeLazyScript(); u.scripted.s.lazy_ = lazy; if (lazy) { MOZ_ASSERT(!isSelfHostedBuiltin()); } else { MOZ_ASSERT(isSelfHostedBuiltin()); MOZ_ASSERT(isExtended()); - MOZ_ASSERT(GetSelfHostedFunctionName(this)); + MOZ_ASSERT(GetClonedSelfHostedFunctionName(this)); } realm->scheduleDelazificationForDebugger(); } // ES2018 draft rev 2aea8f3e617b49df06414eb062ab44fad87661d3 // 19.2.1.1.1 CreateDynamicFunction( constructor, newTarget, kind, args ) static bool CreateDynamicFunction(JSContext* cx, const CallArgs& args,
--- a/js/src/vm/JSFunction.h +++ b/js/src/vm/JSFunction.h @@ -124,21 +124,29 @@ class JSFunction : public js::NativeObje static_assert((INTERPRETED | INTERPRETED_LAZY) == js::JS_FUNCTION_INTERPRETED_BITS, "jsfriendapi.h's JSFunction::INTERPRETED-alike is wrong"); static_assert(((FunctionKindLimit - 1) << FUNCTION_KIND_SHIFT) <= FUNCTION_KIND_MASK, "FunctionKind doesn't fit into flags_"); private: - uint16_t - nargs_; /* number of formal arguments - (including defaults and the rest parameter unlike f.length) */ - uint16_t flags_; /* bitfield composed of the above Flags enum, as well as the - kind */ + /* + * number of formal arguments + * (including defaults and the rest parameter unlike f.length) + */ + uint16_t nargs_; + + /* + * Bitfield composed of the above Flags enum, as well as the kind. + * + * If any of these flags needs to be accessed in off-thread JIT + * compilation, copy it to js::jit::WrappedFunction. + */ + uint16_t flags_; union U { class { friend class JSFunction; js::Native func_; /* native method pointer or null */ union { // Information about this function to be used by the JIT, only // used if isBuiltinNative(); use the accessor! const JSJitInfo* jitInfo_; @@ -815,16 +823,30 @@ class JSFunction : public js::NativeObje * the function has already been initialized. Otherwise use * initExtendedSlot. */ inline void initializeExtended(); inline void initExtendedSlot(size_t which, const js::Value& val); inline void setExtendedSlot(size_t which, const js::Value& val); inline const js::Value& getExtendedSlot(size_t which) const; + /* + * Same as `toExtended` and `getExtendedSlot`, but `this` is guaranteed to be + * an extended function. + * + * This function is supposed to be used off-thread, especially the JIT + * compilation thread, that cannot access JSFunction.flags_, because of + * a race condition. + * + * See Also: WrappedFunction.isExtended_ + */ + inline js::FunctionExtended* toExtendedOffMainThread(); + inline const js::FunctionExtended* toExtendedOffMainThread() const; + inline const js::Value& getExtendedSlotOffMainThread(size_t which) const; + /* Constructs a new type for the function if necessary. */ static bool setTypeForScriptedFunction(JSContext* cx, js::HandleFunction fun, bool singleton = false); /* GC support. */ js::gc::AllocKind getAllocKind() const { static_assert( js::gc::AllocKind::FUNCTION != js::gc::AllocKind::FUNCTION_EXTENDED, @@ -1003,16 +1025,24 @@ inline js::FunctionExtended* JSFunction: return static_cast<js::FunctionExtended*>(this); } inline const js::FunctionExtended* JSFunction::toExtended() const { MOZ_ASSERT(isExtended()); return static_cast<const js::FunctionExtended*>(this); } +inline js::FunctionExtended* JSFunction::toExtendedOffMainThread() { + return static_cast<js::FunctionExtended*>(this); +} + +inline const js::FunctionExtended* JSFunction::toExtendedOffMainThread() const { + return static_cast<const js::FunctionExtended*>(this); +} + inline void JSFunction::initializeExtended() { MOZ_ASSERT(isExtended()); MOZ_ASSERT(mozilla::ArrayLength(toExtended()->extendedSlots) == 2); toExtended()->extendedSlots[0].init(js::UndefinedValue()); toExtended()->extendedSlots[1].init(js::UndefinedValue()); } @@ -1028,16 +1058,23 @@ inline void JSFunction::setExtendedSlot( toExtended()->extendedSlots[which] = val; } inline const js::Value& JSFunction::getExtendedSlot(size_t which) const { MOZ_ASSERT(which < mozilla::ArrayLength(toExtended()->extendedSlots)); return toExtended()->extendedSlots[which]; } +inline const js::Value& JSFunction::getExtendedSlotOffMainThread( + size_t which) const { + MOZ_ASSERT(which < + mozilla::ArrayLength(toExtendedOffMainThread()->extendedSlots)); + return toExtendedOffMainThread()->extendedSlots[which]; +} + namespace js { JSString* FunctionToString(JSContext* cx, HandleFunction fun, bool isToSource); template <XDRMode mode> XDRResult XDRInterpretedFunction(XDRState<mode>* xdr, HandleScope enclosingScope, HandleScriptSourceObject sourceObject,
--- a/js/src/vm/JSScript.cpp +++ b/js/src/vm/JSScript.cpp @@ -349,21 +349,21 @@ XDRResult ScopeNote::XDR(XDRState<mode>* MOZ_TRY(xdr->codeUint32(&index)); MOZ_TRY(xdr->codeUint32(&start)); MOZ_TRY(xdr->codeUint32(&length)); MOZ_TRY(xdr->codeUint32(&parent)); return Ok(); } -static inline uint32_t FindScopeIndex(mozilla::Span<const GCPtrScope> scopes, +static inline uint32_t FindScopeIndex(mozilla::Span<const JS::GCCellPtr> scopes, Scope& scope) { unsigned length = scopes.size(); for (uint32_t i = 0; i < length; ++i) { - if (scopes[i] == &scope) { + if (scopes[i].asCell() == &scope) { return i; } } MOZ_CRASH("Scope not found"); } template <XDRMode mode> @@ -418,23 +418,24 @@ static XDRResult XDRInnerObject(XDRState } else if (function->isInterpreted()) { funEnclosingScope = function->nonLazyScript()->enclosingScope(); } else { MOZ_ASSERT(function->isAsmJSNative()); return xdr->fail(JS::TranscodeResult_Failure_AsmJSNotSupported); } funEnclosingScopeIndex = - FindScopeIndex(data->scopes(), *funEnclosingScope); + FindScopeIndex(data->gcthings(), *funEnclosingScope); } MOZ_TRY(xdr->codeUint32(&funEnclosingScopeIndex)); if (mode == XDR_DECODE) { - funEnclosingScope = data->scopes()[funEnclosingScopeIndex]; + funEnclosingScope = + &data->gcthings()[funEnclosingScopeIndex].as<Scope>(); } // Code nested function and script. RootedFunction tmp(cx); if (mode == XDR_ENCODE) { tmp = &inner->as<JSFunction>(); } MOZ_TRY( @@ -467,46 +468,45 @@ static XDRResult XDRInnerObject(XDRState } return Ok(); } template <XDRMode mode> static XDRResult XDRScope(XDRState<mode>* xdr, js::PrivateScriptData* data, HandleScope scriptEnclosingScope, HandleFunction fun, - uint32_t scopeIndex, MutableHandleScope scope) { + bool isFirstScope, MutableHandleScope scope) { JSContext* cx = xdr->cx(); ScopeKind scopeKind; RootedScope enclosing(cx); uint32_t enclosingIndex = 0; // The enclosingScope is encoded using an integer index into the scope array. // This means that scopes must be topologically sorted. if (mode == XDR_ENCODE) { scopeKind = scope->kind(); - if (scopeIndex == 0) { + if (isFirstScope) { enclosingIndex = UINT32_MAX; } else { MOZ_ASSERT(scope->enclosing()); - enclosingIndex = FindScopeIndex(data->scopes(), *scope->enclosing()); + enclosingIndex = FindScopeIndex(data->gcthings(), *scope->enclosing()); } } MOZ_TRY(xdr->codeEnum32(&scopeKind)); MOZ_TRY(xdr->codeUint32(&enclosingIndex)); if (mode == XDR_DECODE) { - if (scopeIndex == 0) { + if (isFirstScope) { MOZ_ASSERT(enclosingIndex == UINT32_MAX); enclosing = scriptEnclosingScope; } else { - MOZ_ASSERT(enclosingIndex < scopeIndex); - enclosing = data->scopes()[enclosingIndex]; + enclosing = &data->gcthings()[enclosingIndex].as<Scope>(); } } switch (scopeKind) { case ScopeKind::Function: MOZ_TRY(FunctionScope::XDR(xdr, fun, enclosing, scope)); break; case ScopeKind::FunctionBodyVar: @@ -543,120 +543,136 @@ static XDRResult XDRScope(XDRState<mode> MOZ_ASSERT(false, "Bad XDR scope kind"); return xdr->fail(JS::TranscodeResult_Failure_BadDecode); } return Ok(); } template <XDRMode mode> +static XDRResult XDRScriptGCThing(XDRState<mode>* xdr, PrivateScriptData* data, + HandleScriptSourceObject sourceObject, + HandleScope scriptEnclosingScope, + HandleFunction fun, bool* isFirstScope, + JS::GCCellPtr* thingp) { + JSContext* cx = xdr->cx(); + + enum class GCThingTag { Object, Scope, BigInt }; + + JS::GCCellPtr thing; + + GCThingTag tag; + if (mode == XDR_ENCODE) { + thing = *thingp; + if (thing.is<JSObject>()) { + tag = GCThingTag::Object; + } else if (thing.is<Scope>()) { + tag = GCThingTag::Scope; + } else { + MOZ_ASSERT(thing.is<BigInt>()); + tag = GCThingTag::BigInt; + } + } + + MOZ_TRY(xdr->codeEnum32(&tag)); + + switch (tag) { + case GCThingTag::Object: { + RootedObject obj(cx); + if (mode == XDR_ENCODE) { + obj = &thing.as<JSObject>(); + } + MOZ_TRY(XDRInnerObject(xdr, data, sourceObject, &obj)); + if (mode == XDR_DECODE) { + *thingp = JS::GCCellPtr(obj.get()); + } + break; + } + case GCThingTag::Scope: { + RootedScope scope(cx); + if (mode == XDR_ENCODE) { + scope = &thing.as<Scope>(); + } + MOZ_TRY(XDRScope(xdr, data, scriptEnclosingScope, fun, *isFirstScope, + &scope)); + if (mode == XDR_DECODE) { + *thingp = JS::GCCellPtr(scope.get()); + } + *isFirstScope = false; + break; + } + case GCThingTag::BigInt: { + RootedBigInt bi(cx); + if (mode == XDR_ENCODE) { + bi = &thing.as<BigInt>(); + } + MOZ_TRY(XDRBigInt(xdr, &bi)); + if (mode == XDR_DECODE) { + *thingp = JS::GCCellPtr(bi.get()); + } + break; + } + default: + // Fail in debug, but only soft-fail in release. + MOZ_ASSERT(false, "Bad XDR GCThingTag"); + return xdr->fail(JS::TranscodeResult_Failure_BadDecode); + } + return Ok(); +} + +template <XDRMode mode> /* static */ XDRResult js::PrivateScriptData::XDR(XDRState<mode>* xdr, HandleScript script, HandleScriptSourceObject sourceObject, HandleScope scriptEnclosingScope, HandleFunction fun) { - uint32_t nscopes = 0; - uint32_t nbigints = 0; - uint32_t nobjects = 0; + uint32_t ngcthings = 0; uint32_t ntrynotes = 0; uint32_t nscopenotes = 0; uint32_t nresumeoffsets = 0; JSContext* cx = xdr->cx(); PrivateScriptData* data = nullptr; if (mode == XDR_ENCODE) { data = script->data_; - nscopes = data->scopes().size(); - if (data->hasBigInts()) { - nbigints = data->bigints().size(); - } - if (data->hasObjects()) { - nobjects = data->objects().size(); - } + ngcthings = data->gcthings().size(); if (data->hasTryNotes()) { ntrynotes = data->tryNotes().size(); } if (data->hasScopeNotes()) { nscopenotes = data->scopeNotes().size(); } if (data->hasResumeOffsets()) { nresumeoffsets = data->resumeOffsets().size(); } } - MOZ_TRY(xdr->codeUint32(&nscopes)); - MOZ_TRY(xdr->codeUint32(&nbigints)); - MOZ_TRY(xdr->codeUint32(&nobjects)); + MOZ_TRY(xdr->codeUint32(&ngcthings)); MOZ_TRY(xdr->codeUint32(&ntrynotes)); MOZ_TRY(xdr->codeUint32(&nscopenotes)); MOZ_TRY(xdr->codeUint32(&nresumeoffsets)); if (mode == XDR_DECODE) { - if (!JSScript::createPrivateScriptData(cx, script, nscopes, nbigints, - nobjects, ntrynotes, nscopenotes, - nresumeoffsets)) { + if (!JSScript::createPrivateScriptData(cx, script, ngcthings, ntrynotes, + nscopenotes, nresumeoffsets)) { return xdr->fail(JS::TranscodeResult_Throw); } data = script->data_; } - if (nbigints > 0) { - RootedBigInt bi(cx); - for (GCPtrBigInt& elem : data->bigints()) { - if (mode == XDR_ENCODE) { - bi = elem.get(); - } - MOZ_TRY(XDRBigInt(xdr, &bi)); - if (mode == XDR_DECODE) { - elem.init(bi); - } - } - } - - { - MOZ_ASSERT(nscopes > 0); - GCPtrScope* vector = data->scopes().data(); - for (uint32_t i = 0; i < nscopes; ++i) { - RootedScope scope(cx); - if (mode == XDR_ENCODE) { - scope = vector[i]; - } - MOZ_TRY(XDRScope(xdr, data, scriptEnclosingScope, fun, i, &scope)); - if (mode == XDR_DECODE) { - vector[i].init(scope); - } - } - - // Verify marker to detect data corruption after decoding scope data. A - // mismatch here indicates we will almost certainly crash in release. - MOZ_TRY(xdr->codeMarker(0x48922BAB)); - } - - /* - * Here looping from 0-to-length to xdr objects is essential to ensure that - * all references to enclosing blocks (via FindScopeIndex below) happen - * after the enclosing block has been XDR'd. - */ - if (nobjects) { - for (GCPtrObject& elem : data->objects()) { - RootedObject inner(cx); - if (mode == XDR_ENCODE) { - inner = elem; - } - MOZ_TRY(XDRInnerObject(xdr, data, sourceObject, &inner)); - if (mode == XDR_DECODE) { - elem.init(inner); - } - } - } - - // Verify marker to detect data corruption after decoding object data. A + bool isFirstScope = true; + for (JS::GCCellPtr& gcThing : data->gcthings()) { + MOZ_TRY(XDRScriptGCThing(xdr, data, sourceObject, scriptEnclosingScope, fun, + &isFirstScope, &gcThing)); + } + + // Verify marker to detect data corruption after decoding GC things. A // mismatch here indicates we will almost certainly crash in release. MOZ_TRY(xdr->codeMarker(0xF83B989A)); if (ntrynotes) { for (JSTryNote& elem : data->tryNotes()) { MOZ_TRY(elem.XDR(xdr)); } } @@ -3411,46 +3427,33 @@ void js::FreeScriptData(JSRuntime* rt) { numLive); } #endif table.clear(); } /* static */ -size_t PrivateScriptData::AllocationSize(uint32_t nscopes, uint32_t nbigints, - uint32_t nobjects, uint32_t ntrynotes, +size_t PrivateScriptData::AllocationSize(uint32_t ngcthings, uint32_t ntrynotes, uint32_t nscopenotes, uint32_t nresumeoffsets) { size_t size = sizeof(PrivateScriptData); - if (nbigints) { - size += sizeof(PackedSpan); - } - if (nobjects) { - size += sizeof(PackedSpan); - } if (ntrynotes) { size += sizeof(PackedSpan); } if (nscopenotes) { size += sizeof(PackedSpan); } if (nresumeoffsets) { size += sizeof(PackedSpan); } - size += nscopes * sizeof(GCPtrScope); - - if (nbigints) { - size += nbigints * sizeof(GCPtrBigInt); - } - if (nobjects) { - size += nobjects * sizeof(GCPtrObject); - } + size += ngcthings * sizeof(JS::GCCellPtr); + if (ntrynotes) { size += ntrynotes * sizeof(JSTryNote); } if (nscopenotes) { size += nscopenotes * sizeof(ScopeNote); } if (nresumeoffsets) { size += nresumeoffsets * sizeof(uint32_t); @@ -3483,21 +3486,20 @@ void PrivateScriptData::initSpan(size_t* // Placement-new the elements initElements<T>(*cursor, length); // Advance cursor (*cursor) += length * sizeof(T); } // Initialize PackedSpans and placement-new the trailing arrays. -PrivateScriptData::PrivateScriptData(uint32_t nscopes_, uint32_t nbigints, - uint32_t nobjects, uint32_t ntrynotes, +PrivateScriptData::PrivateScriptData(uint32_t ngcthings, uint32_t ntrynotes, uint32_t nscopenotes, uint32_t nresumeoffsets) - : nscopes(nscopes_) { + : ngcthings(ngcthings) { // Convert cursor possition to a packed offset. auto ToPackedOffset = [](size_t cursor) { MOZ_ASSERT(cursor % PackedOffsets::SCALE == 0); return cursor / PackedOffsets::SCALE; }; // Helper to allocate a PackedSpan from the variable length data. auto TakeSpan = [=](size_t* cursor) { @@ -3511,150 +3513,119 @@ PrivateScriptData::PrivateScriptData(uin // Variable-length data begins immediately after PrivateScriptData itself. // NOTE: Alignment is computed using cursor/offset so the alignment of // PrivateScriptData must be stricter than any trailing array type. size_t cursor = sizeof(*this); // Layout PackedSpan structures and initialize packedOffsets fields. static_assert(alignof(PrivateScriptData) >= alignof(PackedSpan), "Incompatible alignment"); - if (nbigints) { - packedOffsets.bigintsSpanOffset = TakeSpan(&cursor); - } - if (nobjects) { - packedOffsets.objectsSpanOffset = TakeSpan(&cursor); - } if (ntrynotes) { packedOffsets.tryNotesSpanOffset = TakeSpan(&cursor); } if (nscopenotes) { packedOffsets.scopeNotesSpanOffset = TakeSpan(&cursor); } if (nresumeoffsets) { packedOffsets.resumeOffsetsSpanOffset = TakeSpan(&cursor); } - // Layout and initialize the scopes array. + // Layout and initialize the gcthings array. { - MOZ_ASSERT(nscopes > 0); - - static_assert(alignof(PackedSpan) >= alignof(GCPtrScope), + MOZ_ASSERT(ngcthings > 0); + + static_assert(alignof(PackedSpan) >= alignof(JS::GCCellPtr), "Incompatible alignment"); - initElements<GCPtrScope>(cursor, nscopes); - packedOffsets.scopesOffset = ToPackedOffset(cursor); - - cursor += nscopes * sizeof(GCPtrScope); + initElements<JS::GCCellPtr>(cursor, ngcthings); + packedOffsets.gcthingsOffset = ToPackedOffset(cursor); + + cursor += ngcthings * sizeof(JS::GCCellPtr); } // Layout arrays, initialize PackedSpans and placement-new the elements. - static_assert(alignof(PrivateScriptData) >= alignof(GCPtrBigInt), - "Incompatible alignment"); - static_assert(alignof(GCPtrScope) >= alignof(GCPtrBigInt), - "Incompatible alignment"); - initSpan<GCPtrBigInt>(&cursor, packedOffsets.bigintsSpanOffset, nbigints); - static_assert(alignof(GCPtrBigInt) >= alignof(GCPtrObject), - "Incompatible alignment"); - static_assert(alignof(GCPtrScope) >= alignof(GCPtrObject), - "Incompatible alignment"); - initSpan<GCPtrObject>(&cursor, packedOffsets.objectsSpanOffset, nobjects); - static_assert(alignof(GCPtrObject) >= alignof(JSTryNote), + static_assert(alignof(JS::GCCellPtr) >= alignof(JSTryNote), "Incompatible alignment"); initSpan<JSTryNote>(&cursor, packedOffsets.tryNotesSpanOffset, ntrynotes); static_assert(alignof(JSTryNote) >= alignof(ScopeNote), "Incompatible alignment"); initSpan<ScopeNote>(&cursor, packedOffsets.scopeNotesSpanOffset, nscopenotes); static_assert(alignof(ScopeNote) >= alignof(uint32_t), "Incompatible alignment"); initSpan<uint32_t>(&cursor, packedOffsets.resumeOffsetsSpanOffset, nresumeoffsets); // Sanity check - MOZ_ASSERT(AllocationSize(nscopes_, nbigints, nobjects, ntrynotes, - nscopenotes, nresumeoffsets) == cursor); + MOZ_ASSERT(AllocationSize(ngcthings, ntrynotes, nscopenotes, + nresumeoffsets) == cursor); } /* static */ -PrivateScriptData* PrivateScriptData::new_(JSContext* cx, uint32_t nscopes, - uint32_t nbigints, uint32_t nobjects, +PrivateScriptData* PrivateScriptData::new_(JSContext* cx, uint32_t ngcthings, uint32_t ntrynotes, uint32_t nscopenotes, uint32_t nresumeoffsets, uint32_t* dataSize) { // Compute size including trailing arrays - size_t size = AllocationSize(nscopes, nbigints, nobjects, ntrynotes, - nscopenotes, nresumeoffsets); + size_t size = + AllocationSize(ngcthings, ntrynotes, nscopenotes, nresumeoffsets); // Allocate contiguous raw buffer void* raw = cx->pod_malloc<uint8_t>(size); MOZ_ASSERT(uintptr_t(raw) % alignof(PrivateScriptData) == 0); if (!raw) { return nullptr; } if (dataSize) { *dataSize = size; } // Constuct the PrivateScriptData. Trailing arrays are uninitialized but // GCPtrs are put into a safe state. - return new (raw) PrivateScriptData(nscopes, nbigints, nobjects, ntrynotes, - nscopenotes, nresumeoffsets); + return new (raw) + PrivateScriptData(ngcthings, ntrynotes, nscopenotes, nresumeoffsets); } /* static */ bool PrivateScriptData::InitFromEmitter( JSContext* cx, js::HandleScript script, frontend::BytecodeEmitter* bce) { - uint32_t nscopes = bce->perScriptData().scopeList().length(); - uint32_t nbigints = bce->perScriptData().bigIntList().length(); - uint32_t nobjects = bce->perScriptData().objectList().length; + uint32_t ngcthings = bce->perScriptData().gcThingList().length(); uint32_t ntrynotes = bce->bytecodeSection().tryNoteList().length(); uint32_t nscopenotes = bce->bytecodeSection().scopeNoteList().length(); uint32_t nresumeoffsets = bce->bytecodeSection().resumeOffsetList().length(); // Create and initialize PrivateScriptData - if (!JSScript::createPrivateScriptData(cx, script, nscopes, nbigints, - nobjects, ntrynotes, nscopenotes, - nresumeoffsets)) { + if (!JSScript::createPrivateScriptData(cx, script, ngcthings, ntrynotes, + nscopenotes, nresumeoffsets)) { return false; } js::PrivateScriptData* data = script->data_; - if (nscopes) { - bce->perScriptData().scopeList().finish(data->scopes()); - } - if (nbigints) { - bce->perScriptData().bigIntList().finish(data->bigints()); - } - if (nobjects) { - bce->perScriptData().objectList().finish(data->objects()); + if (ngcthings) { + bce->perScriptData().gcThingList().finish(data->gcthings()); } if (ntrynotes) { bce->bytecodeSection().tryNoteList().finish(data->tryNotes()); } if (nscopenotes) { bce->bytecodeSection().scopeNoteList().finish(data->scopeNotes()); } if (nresumeoffsets) { bce->bytecodeSection().resumeOffsetList().finish(data->resumeOffsets()); } return true; } void PrivateScriptData::trace(JSTracer* trc) { - auto scopearray = scopes(); - TraceRange(trc, scopearray.size(), scopearray.data(), "scopes"); - - if (hasBigInts()) { - auto bigintarray = bigints(); - TraceRange(trc, bigintarray.size(), bigintarray.data(), "bigints"); - } - - if (hasObjects()) { - auto objarray = objects(); - TraceRange(trc, objarray.size(), objarray.data(), "objects"); + for (JS::GCCellPtr& elem : gcthings()) { + gc::Cell* thing = elem.asCell(); + TraceManuallyBarrieredGenericPointerEdge(trc, &thing, "script-gcthing"); + if (thing != elem.asCell()) { + elem = JS::GCCellPtr(thing, elem.kind()); + } } } JSScript::JSScript(JS::Realm* realm, uint8_t* stubEntry, HandleScriptSourceObject sourceObject, uint32_t sourceStart, uint32_t sourceEnd, uint32_t toStringStart, uint32_t toStringEnd) : @@ -3789,62 +3760,58 @@ bool JSScript::initScriptName(JSContext* return false; } return true; } /* static */ bool JSScript::createPrivateScriptData(JSContext* cx, HandleScript script, - uint32_t nscopes, uint32_t nbigints, - uint32_t nobjects, uint32_t ntrynotes, + uint32_t ngcthings, uint32_t ntrynotes, uint32_t nscopenotes, uint32_t nresumeoffsets) { cx->check(script); MOZ_ASSERT(!script->data_); uint32_t dataSize; - PrivateScriptData* data = - PrivateScriptData::new_(cx, nscopes, nbigints, nobjects, ntrynotes, - nscopenotes, nresumeoffsets, &dataSize); + PrivateScriptData* data = PrivateScriptData::new_( + cx, ngcthings, ntrynotes, nscopenotes, nresumeoffsets, &dataSize); if (!data) { return false; } script->data_ = data; script->dataSize_ = dataSize; AddCellMemory(script, dataSize, MemoryUse::ScriptPrivateData); return true; } /* static */ bool JSScript::initFunctionPrototype(JSContext* cx, HandleScript script, HandleFunction functionProto) { - uint32_t numScopes = 1; - uint32_t numBigInts = 0; - uint32_t numObjects = 0; + uint32_t numGCThings = 1; uint32_t numTryNotes = 0; uint32_t numScopeNotes = 0; uint32_t nresumeoffsets = 0; - if (!createPrivateScriptData(cx, script, numScopes, numBigInts, numObjects, - numTryNotes, numScopeNotes, nresumeoffsets)) { + if (!createPrivateScriptData(cx, script, numGCThings, numTryNotes, + numScopeNotes, nresumeoffsets)) { return false; } RootedScope enclosing(cx, &cx->global()->emptyGlobalScope()); Scope* functionProtoScope = FunctionScope::create(cx, nullptr, false, false, functionProto, enclosing); if (!functionProtoScope) { return false; } - mozilla::Span<GCPtrScope> scopes = script->data_->scopes(); - scopes[0].init(functionProtoScope); + mozilla::Span<JS::GCCellPtr> gcthings = script->data_->gcthings(); + gcthings[0] = JS::GCCellPtr(functionProtoScope); uint32_t codeLength = 1; uint32_t noteLength = 1; uint32_t numAtoms = 0; if (!script->createSharedScriptData(cx, codeLength, noteLength, numAtoms)) { return false; } @@ -3922,17 +3889,17 @@ bool JSScript::fullyInitFromEmitter(JSCo // If initialization fails, we must call JSScript::freeScriptData in order to // neuter the script. Various things that iterate raw scripts in a GC arena // use the presense of this data to detect if initialization is complete. auto scriptDataGuard = mozilla::MakeScopeExit([&] { script->freeScriptData(); }); /* The counts of indexed things must be checked during code generation. */ MOZ_ASSERT(bce->perScriptData().atomIndices()->count() <= INDEX_LIMIT); - MOZ_ASSERT(bce->perScriptData().objectList().length <= INDEX_LIMIT); + MOZ_ASSERT(bce->perScriptData().gcThingList().length() <= INDEX_LIMIT); uint64_t nslots = bce->maxFixedSlots + static_cast<uint64_t>(bce->bytecodeSection().maxStackDepth()); if (nslots > UINT32_MAX) { bce->reportError(nullptr, JSMSG_NEED_DIET, js_script_str); return false; } @@ -3981,17 +3948,17 @@ bool JSScript::fullyInitFromEmitter(JSCo } else { fun->setScript(script); } } // Part of the parse result – the scope containing each inner function – must // be stored in the inner function itself. Do this now that compilation is // complete and can no longer fail. - bce->perScriptData().objectList().finishInnerFunctions(); + bce->perScriptData().gcThingList().finishInnerFunctions(); #ifdef JS_STRUCTURED_SPEW // We want this to happen after line number initialization to allow filtering // to work. script->setSpewEnabled(cx->spewer().enabled(script)); #endif #ifdef DEBUG @@ -4400,142 +4367,125 @@ static JSObject* CloneInnerInterpretedFu if (!JSFunction::setTypeForScriptedFunction(cx, clone)) { return nullptr; } return clone; } +static JSObject* CloneScriptObject(JSContext* cx, PrivateScriptData* srcData, + HandleObject obj, + Handle<ScriptSourceObject*> sourceObject, + JS::HandleVector<StackGCCellPtr> gcThings) { + if (obj->is<RegExpObject>()) { + return CloneScriptRegExpObject(cx, obj->as<RegExpObject>()); + } + + if (obj->is<JSFunction>()) { + HandleFunction innerFun = obj.as<JSFunction>(); + if (innerFun->isNative()) { + if (cx->realm() != innerFun->realm()) { + MOZ_ASSERT(innerFun->isAsmJSNative()); + JS_ReportErrorASCII(cx, "AsmJS modules do not yet support cloning."); + return nullptr; + } + return innerFun; + } + + if (innerFun->isInterpretedLazy()) { + AutoRealm ar(cx, innerFun); + if (!JSFunction::getOrCreateScript(cx, innerFun)) { + return nullptr; + } + } + + Scope* enclosing = innerFun->nonLazyScript()->enclosingScope(); + uint32_t scopeIndex = FindScopeIndex(srcData->gcthings(), *enclosing); + RootedScope enclosingClone(cx, + &gcThings[scopeIndex].get().get().as<Scope>()); + return CloneInnerInterpretedFunction(cx, enclosingClone, innerFun, + sourceObject); + } + + return DeepCloneObjectLiteral(cx, obj, TenuredObject); +} + /* static */ bool PrivateScriptData::Clone(JSContext* cx, HandleScript src, HandleScript dst, MutableHandle<GCVector<Scope*>> scopes) { PrivateScriptData* srcData = src->data_; - uint32_t nscopes = srcData->scopes().size(); - uint32_t nbigints = srcData->hasBigInts() ? srcData->bigints().size() : 0; - uint32_t nobjects = srcData->hasObjects() ? srcData->objects().size() : 0; + uint32_t ngcthings = srcData->gcthings().size(); uint32_t ntrynotes = srcData->hasTryNotes() ? srcData->tryNotes().size() : 0; uint32_t nscopenotes = srcData->hasScopeNotes() ? srcData->scopeNotes().size() : 0; uint32_t nresumeoffsets = srcData->hasResumeOffsets() ? srcData->resumeOffsets().size() : 0; - /* Scopes */ - - // The passed in scopes vector contains body scopes that needed to be - // cloned especially, depending on whether the script is a function or - // global scope. Starting at scopes.length() means we only deal with - // intra-body scopes. - { - MOZ_ASSERT(nscopes != 0); - MOZ_ASSERT(src->bodyScopeIndex() + 1 == scopes.length()); - RootedScope original(cx); - RootedScope clone(cx); - for (const GCPtrScope& elem : srcData->scopes().From(scopes.length())) { - original = elem.get(); - uint32_t scopeIndex = - FindScopeIndex(srcData->scopes(), *original->enclosing()); - clone = Scope::clone(cx, original, scopes[scopeIndex]); - if (!clone || !scopes.append(clone)) { + // Clone GC things. + JS::RootedVector<StackGCCellPtr> gcThings(cx); + size_t scopeIndex = 0; + Rooted<ScriptSourceObject*> sourceObject(cx, dst->sourceObject()); + RootedObject obj(cx); + RootedScope scope(cx); + RootedScope enclosingScope(cx); + RootedBigInt bigint(cx); + for (JS::GCCellPtr gcThing : srcData->gcthings()) { + if (gcThing.is<JSObject>()) { + obj = &gcThing.as<JSObject>(); + JSObject* clone = + CloneScriptObject(cx, srcData, obj, sourceObject, gcThings); + if (!clone || !gcThings.append(JS::GCCellPtr(clone))) { return false; } - } - } - - /* BigInts */ - - Rooted<BigIntVector> bigints(cx); - if (nbigints != 0) { - RootedBigInt clone(cx); - for (const GCPtrBigInt& elem : srcData->bigints()) { - if (cx->zone() == elem->zone()) { - clone = elem; + } else if (gcThing.is<Scope>()) { + // The passed in scopes vector contains body scopes that needed to be + // cloned especially, depending on whether the script is a function or + // global scope. Clone all other scopes. + if (scopeIndex < scopes.length()) { + if (!gcThings.append(JS::GCCellPtr(scopes[scopeIndex].get()))) { + return false; + } } else { - RootedBigInt b(cx, elem); - clone = BigInt::copy(cx, b); + scope = &gcThing.as<Scope>(); + uint32_t enclosingScopeIndex = + FindScopeIndex(srcData->gcthings(), *scope->enclosing()); + enclosingScope = &gcThings[enclosingScopeIndex].get().get().as<Scope>(); + Scope* clone = Scope::clone(cx, scope, enclosingScope); + if (!clone || !gcThings.append(JS::GCCellPtr(clone))) { + return false; + } + } + scopeIndex++; + } else { + bigint = &gcThing.as<BigInt>(); + BigInt* clone = bigint; + if (cx->zone() != bigint->zone()) { + clone = BigInt::copy(cx, bigint); if (!clone) { return false; } } - if (!bigints.append(clone)) { - return false; - } - } - } - - /* Objects */ - - RootedObjectVector objects(cx); - if (nobjects != 0) { - RootedObject obj(cx); - RootedObject clone(cx); - Rooted<ScriptSourceObject*> sourceObject(cx, dst->sourceObject()); - for (const GCPtrObject& elem : srcData->objects()) { - obj = elem.get(); - clone = nullptr; - if (obj->is<RegExpObject>()) { - clone = CloneScriptRegExpObject(cx, obj->as<RegExpObject>()); - } else if (obj->is<JSFunction>()) { - RootedFunction innerFun(cx, &obj->as<JSFunction>()); - if (innerFun->isNative()) { - if (cx->realm() != innerFun->realm()) { - MOZ_ASSERT(innerFun->isAsmJSNative()); - JS_ReportErrorASCII(cx, - "AsmJS modules do not yet support cloning."); - return false; - } - clone = innerFun; - } else { - if (innerFun->isInterpretedLazy()) { - AutoRealm ar(cx, innerFun); - if (!JSFunction::getOrCreateScript(cx, innerFun)) { - return false; - } - } - - Scope* enclosing = innerFun->nonLazyScript()->enclosingScope(); - uint32_t scopeIndex = FindScopeIndex(srcData->scopes(), *enclosing); - RootedScope enclosingClone(cx, scopes[scopeIndex]); - clone = CloneInnerInterpretedFunction(cx, enclosingClone, innerFun, - sourceObject); - } - } else { - clone = DeepCloneObjectLiteral(cx, obj, TenuredObject); - } - - if (!clone || !objects.append(clone)) { + if (!gcThings.append(JS::GCCellPtr(clone))) { return false; } } } // Create the new PrivateScriptData on |dst| and fill it in. - if (!JSScript::createPrivateScriptData(cx, dst, nscopes, nbigints, nobjects, - ntrynotes, nscopenotes, - nresumeoffsets)) { + if (!JSScript::createPrivateScriptData(cx, dst, ngcthings, ntrynotes, + nscopenotes, nresumeoffsets)) { return false; } PrivateScriptData* dstData = dst->data_; { - auto array = dstData->scopes(); - for (uint32_t i = 0; i < nscopes; ++i) { - array[i].init(scopes[i]); - } - } - if (nbigints) { - auto array = dstData->bigints(); - for (unsigned i = 0; i < nbigints; ++i) { - array[i].init(bigints[i]); - } - } - if (nobjects) { - auto array = dstData->objects(); - for (unsigned i = 0; i < nobjects; ++i) { - array[i].init(objects[i]); + auto array = dstData->gcthings(); + for (uint32_t i = 0; i < ngcthings; ++i) { + array[i] = gcThings[i].get().get(); } } if (ntrynotes) { std::copy_n(srcData->tryNotes().begin(), ntrynotes, dstData->tryNotes().begin()); } if (nscopenotes) { std::copy_n(srcData->scopeNotes().begin(), nscopenotes,
--- a/js/src/vm/JSScript.h +++ b/js/src/vm/JSScript.h @@ -30,16 +30,17 @@ #include "gc/Barrier.h" #include "gc/Rooting.h" #include "jit/IonCode.h" #include "js/CompileOptions.h" #include "js/UbiNode.h" #include "js/UniquePtr.h" #include "js/Utility.h" #include "util/StructuredSpewer.h" +#include "vm/BigIntType.h" #include "vm/BytecodeIterator.h" #include "vm/BytecodeLocation.h" #include "vm/BytecodeUtil.h" #include "vm/JSAtom.h" #include "vm/NativeObject.h" #include "vm/Scope.h" #include "vm/Shape.h" #include "vm/SharedImmutableStringsCache.h" @@ -1428,76 +1429,65 @@ XDRResult XDRLazyScript(XDRState<mode>* template <XDRMode mode> XDRResult XDRScriptConst(XDRState<mode>* xdr, MutableHandleValue vp); // [SMDOC] - JSScript data layout (unshared) // // PrivateScriptData stores variable-length data associated with a script. // Abstractly a PrivateScriptData consists of all these arrays: // -// * A non-empty array of GCPtrScope in scopes() -// * A possibly-empty array of GCPtrBigInt in bigints() -// * A possibly-empty array of JSObject* in objects() +// * A non-empty array of GCCellPtr in gcthings() // * A possibly-empty array of JSTryNote in tryNotes() // * A possibly-empty array of ScopeNote in scopeNotes() // * A possibly-empty array of uint32_t in resumeOffsets() // // Accessing any of these arrays just requires calling the appropriate public // Span-computing function. // // Under the hood, PrivateScriptData is a small class followed by a memory // layout that compactly encodes all these arrays, in this manner (only // explicit padding, "--" separators for readability only): // // <PrivateScriptData itself> // -- -// (OPTIONAL) PackedSpan for bigints() -// (OPTIONAL) PackedSpan for objects() -// (OPTIONAL) PackedSpan for tryNotes() -// (OPTIONAL) PackedSpan for scopeNotes() -// (OPTIONAL) PackedSpan for resumeOffsets() +// (OPTIONAL) PackedSpan for gcthings() // -- -// (REQUIRED) All the GCPtrScopes that constitute scopes() -// -- -// (OPTIONAL) All the GCPtrBigInts that constitute bigints() -// -- -// (OPTIONAL) All the GCPtrObjects that constitute objects() +// (REQUIRED) All the GCCellPtrs that constitute gcthings() // -- // (OPTIONAL) All the JSTryNotes that constitute tryNotes() // -- // (OPTIONAL) All the ScopeNotes that constitute scopeNotes() // -- // (OPTIONAL) All the uint32_t's that constitute resumeOffsets() // // The contents of PrivateScriptData indicate which optional items are present. // PrivateScriptData::packedOffsets contains bit-fields, one per array. // Multiply each packed offset by sizeof(uint32_t) to compute a *real* offset. // -// PrivateScriptData::scopesOffset indicates where scopes() begins. The bound -// of five PackedSpans ensures we can encode this offset compactly. -// PrivateScriptData::nscopes indicates the number of GCPtrScopes in scopes(). +// PrivateScriptData::gcthingsOffset indicates where gcthings() begins. The +// bound of five PackedSpans ensures we can encode this offset compactly. +// PrivateScriptData::ngcthings indicates the number of GCCellPtrs in +// gcthings(). // // The other PackedScriptData::*Offset fields indicate where a potential // corresponding PackedSpan resides. If the packed offset is 0, there is no // PackedSpan, and the array is empty. Otherwise the PackedSpan's uint32_t // offset and length fields store: 1) a *non-packed* offset (a literal count of // bytes offset from the *start* of PrivateScriptData struct) to the // corresponding array, and 2) the number of elements in the array, // respectively. class alignas(uintptr_t) PrivateScriptData final { struct PackedOffsets { static constexpr size_t SCALE = sizeof(uint32_t); static constexpr size_t MAX_OFFSET = 0b1111; - // (Scaled) offset to Scopes - uint32_t scopesOffset : 8; + // (Scaled) offset to GC things. + uint32_t gcthingsOffset : 8; // (Scaled) offset to Spans. These are set to 0 if they don't exist. - uint32_t bigintsSpanOffset : 4; - uint32_t objectsSpanOffset : 4; uint32_t tryNotesSpanOffset : 4; uint32_t scopeNotesSpanOffset : 4; uint32_t resumeOffsetsSpanOffset : 4; }; // Detect accidental size regressions. static_assert(sizeof(PackedOffsets) == sizeof(uint32_t), "unexpected bit-field packing"); @@ -1506,17 +1496,17 @@ class alignas(uintptr_t) PrivateScriptDa // the private data. struct alignas(uintptr_t) PackedSpan { uint32_t offset; uint32_t length; }; // Concrete Fields PackedOffsets packedOffsets = {}; // zeroes - uint32_t nscopes = 0; + uint32_t ngcthings = 0; js::FieldInitializers fieldInitializers_ = js::FieldInitializers::Invalid(); // Translate an offset into a concrete pointer. template <typename T> T* offsetToPointer(size_t offset) { uintptr_t base = reinterpret_cast<uintptr_t>(this); uintptr_t elem = base + offset; @@ -1541,65 +1531,54 @@ class alignas(uintptr_t) PrivateScriptDa // Helpers for creating initializing trailing data template <typename T> void initSpan(size_t* cursor, uint32_t scaledSpanOffset, size_t length); template <typename T> void initElements(size_t offset, size_t length); // Size to allocate - static size_t AllocationSize(uint32_t nscopes, uint32_t nbigints, - uint32_t nobjects, uint32_t ntrynotes, + static size_t AllocationSize(uint32_t ngcthings, uint32_t ntrynotes, uint32_t nscopenotes, uint32_t nresumeoffsets); // Initialize header and PackedSpans - PrivateScriptData(uint32_t nscopes_, uint32_t nbigints, uint32_t nobjects, - uint32_t ntrynotes, uint32_t nscopenotes, - uint32_t nresumeoffsets); + PrivateScriptData(uint32_t ngcthings, uint32_t ntrynotes, + uint32_t nscopenotes, uint32_t nresumeoffsets); public: // Accessors for typed array spans. - mozilla::Span<GCPtrScope> scopes() { - GCPtrScope* base = - packedOffsetToPointer<GCPtrScope>(packedOffsets.scopesOffset); - return mozilla::MakeSpan(base, nscopes); - } - mozilla::Span<GCPtrBigInt> bigints() { - return packedOffsetToSpan<GCPtrBigInt>(packedOffsets.bigintsSpanOffset); - } - mozilla::Span<GCPtrObject> objects() { - return packedOffsetToSpan<GCPtrObject>(packedOffsets.objectsSpanOffset); + mozilla::Span<JS::GCCellPtr> gcthings() { + JS::GCCellPtr* base = + packedOffsetToPointer<JS::GCCellPtr>(packedOffsets.gcthingsOffset); + return mozilla::MakeSpan(base, ngcthings); } mozilla::Span<JSTryNote> tryNotes() { return packedOffsetToSpan<JSTryNote>(packedOffsets.tryNotesSpanOffset); } mozilla::Span<ScopeNote> scopeNotes() { return packedOffsetToSpan<ScopeNote>(packedOffsets.scopeNotesSpanOffset); } mozilla::Span<uint32_t> resumeOffsets() { return packedOffsetToSpan<uint32_t>(packedOffsets.resumeOffsetsSpanOffset); } // Fast tests for if array exists - bool hasBigInts() const { return packedOffsets.bigintsSpanOffset != 0; } - bool hasObjects() const { return packedOffsets.objectsSpanOffset != 0; } bool hasTryNotes() const { return packedOffsets.tryNotesSpanOffset != 0; } bool hasScopeNotes() const { return packedOffsets.scopeNotesSpanOffset != 0; } bool hasResumeOffsets() const { return packedOffsets.resumeOffsetsSpanOffset != 0; } void setFieldInitializers(FieldInitializers fieldInitializers) { fieldInitializers_ = fieldInitializers; } const FieldInitializers& getFieldInitializers() { return fieldInitializers_; } // Allocate a new PrivateScriptData. Headers and GCPtrs are initialized. // The size of allocation is returned as an out parameter. - static PrivateScriptData* new_(JSContext* cx, uint32_t nscopes, - uint32_t nbigints, uint32_t nobjects, + static PrivateScriptData* new_(JSContext* cx, uint32_t ngcthings, uint32_t ntrynotes, uint32_t nscopenotes, uint32_t nresumeoffsets, uint32_t* dataSize); template <XDRMode mode> static MOZ_MUST_USE XDRResult XDR(js::XDRState<mode>* xdr, js::HandleScript script, js::HandleScriptSourceObject sourceObject, js::HandleScope scriptEnclosingScope, @@ -1638,17 +1617,17 @@ class alignas(uintptr_t) SharedScriptDat uint32_t mainOffset = 0; // Fixed frame slots. uint32_t nfixed = 0; // Slots plus maximum stack depth. uint32_t nslots = 0; - // Index into the scopes array of the body scope. + // Index into the gcthings array of the body scope. uint32_t bodyScopeIndex = 0; // Number of IC entries to allocate in JitScript for Baseline ICs. uint32_t numICEntries = 0; // ES6 function length. uint16_t funLength = 0; @@ -2104,18 +2083,17 @@ class JSScript : public js::gc::TenuredC uint32_t sourceStart, uint32_t sourceEnd, uint32_t toStringStart, uint32_t toStringEnd); // NOTE: If you use createPrivateScriptData directly instead of via // fullyInitFromEmitter, you are responsible for notifying the debugger // after successfully creating the script. static bool createPrivateScriptData(JSContext* cx, JS::Handle<JSScript*> script, - uint32_t nscopes, uint32_t nbigints, - uint32_t nobjects, uint32_t ntrynotes, + uint32_t ngcthings, uint32_t ntrynotes, uint32_t nscopenotes, uint32_t nresumeoffsets); private: void initFromFunctionBox(js::frontend::FunctionBox* funbox); public: static bool fullyInitFromEmitter(JSContext* cx, js::HandleScript script, @@ -2706,26 +2684,34 @@ class JSScript : public js::gc::TenuredC bool functionHasExtraBodyVarScope() const { bool res = hasFlag(ImmutableFlags::FunctionHasExtraBodyVarScope); MOZ_ASSERT_IF(res, functionHasParameterExprs()); return res; } js::VarScope* functionExtraBodyVarScope() const { MOZ_ASSERT(functionHasExtraBodyVarScope()); - for (js::Scope* scope : scopes()) { + for (JS::GCCellPtr gcThing : gcthings()) { + if (!gcThing.is<js::Scope>()) { + continue; + } + js::Scope* scope = &gcThing.as<js::Scope>(); if (scope->kind() == js::ScopeKind::FunctionBodyVar) { return &scope->as<js::VarScope>(); } } MOZ_CRASH("Function extra body var scope not found"); } bool needsBodyEnvironment() const { - for (js::Scope* scope : scopes()) { + for (JS::GCCellPtr gcThing : gcthings()) { + if (!gcThing.is<js::Scope>()) { + continue; + } + js::Scope* scope = &gcThing.as<js::Scope>(); if (ScopeKindIsInBody(scope->kind()) && scope->hasEnvironment()) { return true; } } return false; } inline js::LexicalScope* maybeNamedLambdaScope() const; @@ -2812,32 +2798,22 @@ class JSScript : public js::gc::TenuredC size_t sizeOfData(mozilla::MallocSizeOf mallocSizeOf) const; void addSizeOfJitScript(mozilla::MallocSizeOf mallocSizeOf, size_t* sizeOfJitScript, size_t* sizeOfBaselineFallbackStubs) const; size_t dataSize() const { return dataSize_; } - bool hasBigInts() const { return data_->hasBigInts(); } - bool hasObjects() const { return data_->hasObjects(); } bool hasTrynotes() const { return data_->hasTryNotes(); } bool hasScopeNotes() const { return data_->hasScopeNotes(); } bool hasResumeOffsets() const { return data_->hasResumeOffsets(); } - mozilla::Span<const js::GCPtrScope> scopes() const { return data_->scopes(); } - - mozilla::Span<const js::GCPtrBigInt> bigints() const { - MOZ_ASSERT(hasBigInts()); - return data_->bigints(); - } - - mozilla::Span<const js::GCPtrObject> objects() const { - MOZ_ASSERT(hasObjects()); - return data_->objects(); + mozilla::Span<const JS::GCCellPtr> gcthings() const { + return data_->gcthings(); } mozilla::Span<const JSTryNote> trynotes() const { MOZ_ASSERT(hasTrynotes()); return data_->tryNotes(); } mozilla::Span<const js::ScopeNote> scopeNotes() const { @@ -2895,26 +2871,28 @@ class JSScript : public js::gc::TenuredC return getAtom(index)->asPropertyName(); } js::PropertyName* getName(jsbytecode* pc) const { return getAtom(pc)->asPropertyName(); } JSObject* getObject(size_t index) { - MOZ_ASSERT(objects()[index]->isTenured()); - return objects()[index]; + MOZ_ASSERT(gcthings()[index].asCell()->isTenured()); + return &gcthings()[index].as<JSObject>(); } JSObject* getObject(jsbytecode* pc) { MOZ_ASSERT(containsPC(pc) && containsPC(pc + sizeof(uint32_t))); return getObject(GET_UINT32_INDEX(pc)); } - js::Scope* getScope(size_t index) const { return scopes()[index]; } + js::Scope* getScope(size_t index) const { + return &gcthings()[index].as<js::Scope>(); + } js::Scope* getScope(jsbytecode* pc) const { // This method is used to get a scope directly using a JSOp with an // index. To search through ScopeNotes to look for a Scope using pc, // use lookupScope. MOZ_ASSERT(containsPC(pc) && containsPC(pc + sizeof(uint32_t))); MOZ_ASSERT(js::JOF_OPTYPE(JSOp(*pc)) == JOF_SCOPE, "Did you mean to use lookupScope(pc)?"); @@ -2929,17 +2907,19 @@ class JSScript : public js::gc::TenuredC return functionNonDelazifying(); } return nullptr; } inline js::RegExpObject* getRegExp(size_t index); inline js::RegExpObject* getRegExp(jsbytecode* pc); - js::BigInt* getBigInt(size_t index) { return bigints()[index]; } + js::BigInt* getBigInt(size_t index) { + return &gcthings()[index].as<js::BigInt>(); + } js::BigInt* getBigInt(jsbytecode* pc) { MOZ_ASSERT(containsPC(pc)); MOZ_ASSERT(js::JOF_OPTYPE(JSOp(*pc)) == JOF_BIGINT); return getBigInt(GET_UINT32_INDEX(pc)); } // The following 3 functions find the static scope just before the
--- a/js/src/vm/Realm.cpp +++ b/js/src/vm/Realm.cpp @@ -655,20 +655,22 @@ void Realm::setNewObjectMetadata(JSConte if (!objects_.objectMetadataTable->add(cx, obj, metadata)) { oomUnsafe.crash("setNewObjectMetadata"); } } } static bool AddInnerLazyFunctionsFromScript( JSScript* script, MutableHandleObjectVector lazyFunctions) { - if (!script->hasObjects()) { - return true; - } - for (JSObject* obj : script->objects()) { + for (JS::GCCellPtr gcThing : script->gcthings()) { + if (!gcThing.is<JSObject>()) { + continue; + } + + JSObject* obj = &gcThing.as<JSObject>(); if (obj->is<JSFunction>() && obj->as<JSFunction>().isInterpretedLazy()) { if (!lazyFunctions.append(obj)) { return false; } } } return true; }
--- a/js/src/vm/SelfHosting.cpp +++ b/js/src/vm/SelfHosting.cpp @@ -910,50 +910,85 @@ bool js::intrinsic_NewRegExpStringIterat if (!obj) { return false; } args.rval().setObject(*obj); return true; } -JSAtom* js::GetSelfHostedFunctionName(JSFunction* fun) { +static JSAtom* GetUnclonedSelfHostedFunctionName(JSFunction* fun) { + if (!fun->isExtended()) { + return nullptr; + } + Value name = fun->getExtendedSlot(ORIGINAL_FUNCTION_NAME_SLOT); + if (!name.isString()) { + return nullptr; + } + return &name.toString()->asAtom(); +} + +JSAtom* js::GetClonedSelfHostedFunctionName(JSFunction* fun) { + if (!fun->isExtended()) { + return nullptr; + } Value name = fun->getExtendedSlot(LAZY_FUNCTION_NAME_SLOT); if (!name.isString()) { return nullptr; } return &name.toString()->asAtom(); } -static void SetSelfHostedFunctionName(JSFunction* fun, JSAtom* name) { +JSAtom* js::GetClonedSelfHostedFunctionNameOffMainThread(JSFunction* fun) { + Value name = fun->getExtendedSlotOffMainThread(LAZY_FUNCTION_NAME_SLOT); + if (!name.isString()) { + return nullptr; + } + return &name.toString()->asAtom(); +} + +bool js::IsExtendedUnclonedSelfHostedFunctionName(JSAtom* name) { + if (name->length() < 2) { + return false; + } + return name->latin1OrTwoByteChar(0) == '$'; +} + +static void SetUnclonedSelfHostedFunctionName(JSFunction* fun, JSAtom* name) { + fun->setExtendedSlot(ORIGINAL_FUNCTION_NAME_SLOT, StringValue(name)); +} + +static void SetClonedSelfHostedFunctionName(JSFunction* fun, JSAtom* name) { fun->setExtendedSlot(LAZY_FUNCTION_NAME_SLOT, StringValue(name)); } static bool intrinsic_SetCanonicalName(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); MOZ_ASSERT(args.length() == 2); RootedFunction fun(cx, &args[0].toObject().as<JSFunction>()); MOZ_ASSERT(fun->isSelfHostedBuiltin()); + MOZ_ASSERT(fun->isExtended()); + MOZ_ASSERT(IsExtendedUnclonedSelfHostedFunctionName(fun->explicitName())); JSAtom* atom = AtomizeString(cx, args[1].toString()); if (!atom) { return false; } // _SetCanonicalName can only be called on top-level function declarations. MOZ_ASSERT(fun->kind() == JSFunction::NormalFunction); MOZ_ASSERT(!fun->isLambda()); // It's an error to call _SetCanonicalName multiple times. - MOZ_ASSERT(!GetSelfHostedFunctionName(fun)); + MOZ_ASSERT(!GetUnclonedSelfHostedFunctionName(fun)); // Set the lazy function name so we can later retrieve the script from the // self-hosting global. - SetSelfHostedFunctionName(fun, fun->explicitName()); + SetUnclonedSelfHostedFunctionName(fun, fun->explicitName()); fun->setAtom(atom); args.rval().setUndefined(); return true; } static bool intrinsic_GeneratorObjectIsClosed(JSContext* cx, unsigned argc, Value* vp) { @@ -3289,21 +3324,24 @@ static JSObject* CloneObject(JSContext* } } #endif RootedObject clone(cx); if (selfHostedObject->is<JSFunction>()) { RootedFunction selfHostedFunction(cx, &selfHostedObject->as<JSFunction>()); if (selfHostedFunction->isInterpreted()) { + bool hasName = selfHostedFunction->explicitName() != nullptr; + // Arrow functions use the first extended slot for their lexical |this| // value. And methods use the first extended slot for their home-object. // We only expect to see normal functions here. MOZ_ASSERT(selfHostedFunction->kind() == JSFunction::NormalFunction); - js::gc::AllocKind kind = selfHostedFunction->getAllocKind(); + js::gc::AllocKind kind = hasName ? gc::AllocKind::FUNCTION_EXTENDED + : selfHostedFunction->getAllocKind(); Handle<GlobalObject*> global = cx->global(); Rooted<LexicalEnvironmentObject*> globalLexical( cx, &global->lexicalEnvironment()); RootedScope emptyGlobalScope(cx, &global->emptyGlobalScope()); Rooted<ScriptSourceObject*> sourceObject( cx, SelfHostingScriptSourceObject(cx)); if (!sourceObject) { @@ -3314,21 +3352,21 @@ static JSObject* CloneObject(JSContext* clone = CloneFunctionAndScript(cx, selfHostedFunction, globalLexical, emptyGlobalScope, sourceObject, kind); // To be able to re-lazify the cloned function, its name in the // self-hosting compartment has to be stored on the clone. Re-lazification // is only possible if this isn't a function expression. if (clone && !selfHostedFunction->isLambda()) { // If |_SetCanonicalName| was called on the function, the self-hosted // name is stored in the extended slot. - JSAtom* name = GetSelfHostedFunctionName(selfHostedFunction); + JSAtom* name = GetUnclonedSelfHostedFunctionName(selfHostedFunction); if (!name) { name = selfHostedFunction->explicitName(); } - SetSelfHostedFunctionName(&clone->as<JSFunction>(), name); + SetClonedSelfHostedFunctionName(&clone->as<JSFunction>(), name); } } else { clone = CloneSelfHostingIntrinsic(cx, selfHostedFunction); } } else if (selfHostedObject->is<RegExpObject>()) { RegExpObject& reobj = selfHostedObject->as<RegExpObject>(); RootedAtom source(cx, reobj.getSource()); MOZ_ASSERT(source->isPermanentAtom()); @@ -3416,28 +3454,29 @@ bool JSRuntime::createLazySelfHostedFunc JSFunction* selfHostedFun = getUnclonedSelfHostedFunction(cx, selfHostedName); if (!selfHostedFun) { return false; } if (!selfHostedFun->isClassConstructor() && !selfHostedFun->hasGuessedAtom() && selfHostedFun->explicitName() != selfHostedName) { - MOZ_ASSERT(GetSelfHostedFunctionName(selfHostedFun) == selfHostedName); + MOZ_ASSERT(GetUnclonedSelfHostedFunctionName(selfHostedFun) == + selfHostedName); funName = selfHostedFun->explicitName(); } fun.set(NewScriptedFunction(cx, nargs, JSFunction::INTERPRETED_LAZY, funName, proto, gc::AllocKind::FUNCTION_EXTENDED, newKind)); if (!fun) { return false; } fun->setIsSelfHostedBuiltin(); - SetSelfHostedFunctionName(fun, selfHostedName); + SetClonedSelfHostedFunctionName(fun, selfHostedName); return true; } bool JSRuntime::cloneSelfHostedFunctionScript(JSContext* cx, HandlePropertyName name, HandleFunction targetFun) { RootedFunction sourceFun(cx, getUnclonedSelfHostedFunction(cx, name)); if (!sourceFun) { @@ -3523,23 +3562,23 @@ bool JSRuntime::cloneSelfHostedValue(JSC return CloneValue(cx, selfHostedValue, vp); } void JSRuntime::assertSelfHostedFunctionHasCanonicalName( JSContext* cx, HandlePropertyName name) { #ifdef DEBUG JSFunction* selfHostedFun = getUnclonedSelfHostedFunction(cx, name); MOZ_ASSERT(selfHostedFun); - MOZ_ASSERT(GetSelfHostedFunctionName(selfHostedFun) == name); + MOZ_ASSERT(GetUnclonedSelfHostedFunctionName(selfHostedFun) == name); #endif } bool js::IsSelfHostedFunctionWithName(JSFunction* fun, JSAtom* name) { return fun->isSelfHostedBuiltin() && fun->isExtended() && - GetSelfHostedFunctionName(fun) == name; + GetClonedSelfHostedFunctionName(fun) == name; } static_assert( JSString::MAX_LENGTH <= INT32_MAX, "StringIteratorNext in builtin/String.js assumes the stored index " "into the string is an Int32Value"); static_assert(JSString::MAX_LENGTH == MAX_STRING_LENGTH,
--- a/js/src/vm/SelfHosting.h +++ b/js/src/vm/SelfHosting.h @@ -16,29 +16,40 @@ namespace js { /* * Check whether the given JSFunction is a self-hosted function whose * self-hosted name is the given name. */ bool IsSelfHostedFunctionWithName(JSFunction* fun, JSAtom* name); /* - * Returns the name of the function's binding in the self-hosted global. + * Returns the name of the cloned function's binding in the self-hosted global. + * + * This returns a non-null value only when this is a top level function + * declaration in the self-hosted global. + */ +JSAtom* GetClonedSelfHostedFunctionName(JSFunction* fun); + +/* + * Same as GetClonedSelfHostedFunctionName, but `fun` is guaranteed to be an + * extended function. * - * This returns a non-null value only when: - * * This is a top level function declaration in the self-hosted global. - * * And either: - * * This function is not cloned and `_SetCanonicalName` has been called to - * set a different function name. - * * This function is cloned. + * This function is supposed to be used off-thread, especially the JIT + * compilation thread, that cannot access JSFunction.flags_, because of + * a race condition. * - * For functions not cloned and not `_SetCanonicalName`ed, use - * `fun->explicitName()` instead. + * See Also: WrappedFunction.isExtended_ */ -JSAtom* GetSelfHostedFunctionName(JSFunction* fun); +JSAtom* GetClonedSelfHostedFunctionNameOffMainThread(JSFunction* fun); + +/* + * Uncloned self-hosted functions with `$` prefix are allocated as + * extended function, to store the original name in `_SetCanonicalName`. + */ +bool IsExtendedUnclonedSelfHostedFunctionName(JSAtom* name); bool IsCallSelfHostedNonGenericMethod(NativeImpl impl); bool ReportIncompatibleSelfHostedMethod(JSContext* cx, const CallArgs& args); /* Get the compile options used when compiling self hosted code. */ void FillSelfHostingCompileOptions(JS::CompileOptions& options);
--- a/js/src/vm/SharedArrayObject.cpp +++ b/js/src/vm/SharedArrayObject.cpp @@ -355,17 +355,17 @@ static const ClassOps SharedArrayBufferO nullptr, /* hasInstance */ nullptr, /* construct */ nullptr, /* trace */ }; static const JSFunctionSpec sharedarrray_functions[] = {JS_FS_END}; static const JSPropertySpec sharedarrray_properties[] = { - JS_SELF_HOSTED_SYM_GET(species, "SharedArrayBufferSpecies", 0), JS_PS_END}; + JS_SELF_HOSTED_SYM_GET(species, "$SharedArrayBufferSpecies", 0), JS_PS_END}; static const JSFunctionSpec sharedarray_proto_functions[] = { JS_SELF_HOSTED_FN("slice", "SharedArrayBufferSlice", 2, 0), JS_FS_END}; static const JSPropertySpec sharedarray_proto_properties[] = { JS_PSG("byteLength", SharedArrayBufferObject::byteLengthGetter, 0), JS_STRING_SYM_PS(toStringTag, "SharedArrayBuffer", JSPROP_READONLY), JS_PS_END};
--- a/js/src/vm/TypedArrayObject.cpp +++ b/js/src/vm/TypedArrayObject.cpp @@ -1893,29 +1893,29 @@ bool TypedArrayObject::set(JSContext* cx JS_SELF_HOSTED_FN("reduce", "TypedArrayReduce", 1, 0), JS_SELF_HOSTED_FN("reduceRight", "TypedArrayReduceRight", 1, 0), JS_SELF_HOSTED_FN("reverse", "TypedArrayReverse", 0, 0), JS_SELF_HOSTED_FN("slice", "TypedArraySlice", 2, 0), JS_SELF_HOSTED_FN("some", "TypedArraySome", 1, 0), JS_SELF_HOSTED_FN("sort", "TypedArraySort", 1, 0), JS_SELF_HOSTED_FN("entries", "TypedArrayEntries", 0, 0), JS_SELF_HOSTED_FN("keys", "TypedArrayKeys", 0, 0), - JS_SELF_HOSTED_FN("values", "TypedArrayValues", 0, 0), - JS_SELF_HOSTED_SYM_FN(iterator, "TypedArrayValues", 0, 0), + JS_SELF_HOSTED_FN("values", "$TypedArrayValues", 0, 0), + JS_SELF_HOSTED_SYM_FN(iterator, "$TypedArrayValues", 0, 0), JS_SELF_HOSTED_FN("includes", "TypedArrayIncludes", 2, 0), JS_SELF_HOSTED_FN("toString", "ArrayToString", 0, 0), JS_SELF_HOSTED_FN("toLocaleString", "TypedArrayToLocaleString", 2, 0), JS_FS_END}; /* static */ const JSFunctionSpec TypedArrayObject::staticFunctions[] = { JS_SELF_HOSTED_FN("from", "TypedArrayStaticFrom", 3, 0), JS_SELF_HOSTED_FN("of", "TypedArrayStaticOf", 0, 0), JS_FS_END}; /* static */ const JSPropertySpec TypedArrayObject::staticProperties[] = { - JS_SELF_HOSTED_SYM_GET(species, "TypedArraySpecies", 0), JS_PS_END}; + JS_SELF_HOSTED_SYM_GET(species, "$TypedArraySpecies", 0), JS_PS_END}; static JSObject* CreateSharedTypedArrayPrototype(JSContext* cx, JSProtoKey key) { return GlobalObject::createBlankPrototype( cx, cx->global(), &TypedArrayObject::sharedTypedArrayPrototypeClass); } static const ClassSpec TypedArrayObjectSharedTypedArrayPrototypeClassSpec = {
--- a/layout/base/nsCSSFrameConstructor.cpp +++ b/layout/base/nsCSSFrameConstructor.cpp @@ -10693,16 +10693,20 @@ nsContainerFrame* nsCSSFrameConstructor: // // ColumnSetWrapper (original style) // ColumnSet (-moz-column-set) // Block (-moz-column-content) // nsBlockFrame* columnSetWrapper = NS_NewColumnSetWrapperFrame( mPresShell, aComputedStyle, nsFrameState(NS_FRAME_OWNS_ANON_BOXES)); InitAndRestoreFrame(aState, aContent, aParentFrame, columnSetWrapper); + if (aParentFrame->HasAnyStateBits(NS_FRAME_HAS_MULTI_COLUMN_ANCESTOR) && + !ShouldSuppressColumnSpanDescendants(aParentFrame)) { + columnSetWrapper->AddStateBits(NS_FRAME_HAS_MULTI_COLUMN_ANCESTOR); + } RefPtr<ComputedStyle> columnSetStyle = mPresShell->StyleSet()->ResolveInheritingAnonymousBoxStyle( PseudoStyleType::columnSet, aComputedStyle); nsContainerFrame* columnSet = NS_NewColumnSetFrame( mPresShell, columnSetStyle, nsFrameState(NS_FRAME_OWNS_ANON_BOXES)); InitAndRestoreFrame(aState, aContent, columnSetWrapper, columnSet); columnSet->AddStateBits(NS_FRAME_HAS_MULTI_COLUMN_ANCESTOR);
--- a/layout/base/nsLayoutUtils.cpp +++ b/layout/base/nsLayoutUtils.cpp @@ -3787,18 +3787,21 @@ nsresult nsLayoutUtils::PaintFrame(gfxCo doc->GetIsActive(&isActive); builder->SetInActiveDocShell(isActive); } nsRect rootVisualOverflow = aFrame->GetVisualOverflowRectRelativeToSelf(); // If we are in a remote browser, then apply clipping from ancestor browsers if (BrowserChild* browserChild = BrowserChild::GetFrom(presShell)) { + LayoutDeviceIntRect unscaledVisibleRect = browserChild->GetVisibleRect(); + CSSRect visibleRect = + unscaledVisibleRect / presContext->CSSToDevPixelScale(); rootVisualOverflow.IntersectRect(rootVisualOverflow, - browserChild->GetVisibleRect()); + CSSPixel::ToAppUnits(visibleRect)); } nsIFrame* rootScrollFrame = presShell->GetRootScrollFrame(); if (rootScrollFrame && !aFrame->GetParent()) { nsIScrollableFrame* rootScrollableFrame = presShell->GetRootScrollFrameAsScrollable(); MOZ_ASSERT(rootScrollableFrame); nsRect displayPortBase = rootVisualOverflow;
--- a/layout/generic/nsBlockFrame.cpp +++ b/layout/generic/nsBlockFrame.cpp @@ -3599,16 +3599,17 @@ void nsBlockFrame::ReflowBlockFrame(Bloc availSpace.BStart(wm) -= bStartMargin; if (NS_UNCONSTRAINEDSIZE != availSpace.BSize(wm)) { availSpace.BSize(wm) += bStartMargin; } // Construct the reflow input for the block. Maybe<ReflowInput> blockReflowInput; Maybe<LogicalSize> cbSize; + LogicalSize availSize = availSpace.Size(wm); if (Style()->GetPseudoType() == PseudoStyleType::columnContent) { // Calculate the multicol containing block's block size so that the // children with percentage block size get correct percentage basis. const ReflowInput* cbReflowInput = StaticPrefs::layout_css_column_span_enabled() ? aState.mReflowInput.mParentReflowInput->mCBReflowInput : aState.mReflowInput.mCBReflowInput; MOZ_ASSERT(cbReflowInput->mFrame->StyleColumn()->IsColumnContainerStyle(), @@ -3623,21 +3624,49 @@ void nsBlockFrame::ReflowBlockFrame(Bloc // If a ColumnSetWrapper is in a balancing column content, it may be // pushed or pulled back and forth between column contents. Always add // NS_FRAME_HAS_DIRTY_CHILDREN bit to it so that its ColumnSet children // can have a chance to reflow under current block size constraint. if (aState.mReflowInput.mFlags.mIsColumnBalancing && frame->IsColumnSetWrapperFrame()) { frame->AddStateBits(NS_FRAME_HAS_DIRTY_CHILDREN); } - } - - blockReflowInput.emplace( - aState.mPresContext, aState.mReflowInput, frame, - availSpace.Size(wm).ConvertTo(frame->GetWritingMode(), wm), cbSize); + } else if (IsColumnSetWrapperFrame()) { + // If we are reflowing our ColumnSet children, we want to apply our block + // size constraint to the available block size when constructing reflow + // input for ColumnSet so that ColumnSet can use it to compute its max + // column block size. + if (frame->IsColumnSetFrame()) { + if (availSize.BSize(wm) != NS_UNCONSTRAINEDSIZE) { + // If the available size is constrained, we need to subtract + // ColumnSetWrapper's block-end border and padding. + availSize.BSize(wm) -= aState.BorderPadding().BEnd(wm); + } + + nscoord contentBSize = aState.mReflowInput.ComputedBSize(); + if (aState.mReflowInput.ComputedMaxBSize() != NS_UNCONSTRAINEDSIZE) { + contentBSize = + std::min(contentBSize, aState.mReflowInput.ComputedMaxBSize()); + } + if (contentBSize != NS_UNCONSTRAINEDSIZE) { + // ColumnSet is not the outermost frame in the column container, so it + // cannot have any margin. We don't need to consider any margin that + // can be generated by "box-decoration-break: clone" as we do in + // BlockReflowInput::ComputeBlockAvailSpace(). + const nscoord availContentBSize = std::max( + 0, contentBSize - (aState.mBCoord - aState.ContentBStart())); + availSize.BSize(wm) = + std::min(availSize.BSize(wm), availContentBSize); + } + } + } + + blockReflowInput.emplace(aState.mPresContext, aState.mReflowInput, frame, + availSize.ConvertTo(frame->GetWritingMode(), wm), + cbSize); nsFloatManager::SavedState floatManagerState; nsReflowStatus frameReflowStatus; do { if (floatAvailableSpace.HasFloats()) { // Set if floatAvailableSpace.HasFloats() is true for any // iteration of the loop. aLine->SetLineIsImpactedByFloat(true);
--- a/layout/generic/nsColumnSetFrame.cpp +++ b/layout/generic/nsColumnSetFrame.cpp @@ -306,21 +306,16 @@ nsColumnSetFrame::ReflowConfig nsColumnS nscoord computedBSize = GetEffectiveComputedBSize(aReflowInput, consumedBSize); nscoord colBSize = GetAvailableContentBSize(aReflowInput); if (aReflowInput.ComputedBSize() != NS_UNCONSTRAINEDSIZE) { colBSize = aReflowInput.ComputedBSize(); } else if (aReflowInput.ComputedMaxBSize() != NS_UNCONSTRAINEDSIZE) { colBSize = std::min(colBSize, aReflowInput.ComputedMaxBSize()); - } else if (StaticPrefs::layout_css_column_span_enabled() && - aReflowInput.mCBReflowInput->ComputedMaxBSize() != - NS_UNCONSTRAINEDSIZE) { - colBSize = - std::min(colBSize, aReflowInput.mCBReflowInput->ComputedMaxBSize()); } nscoord colGap = ColumnUtils::GetColumnGap(this, aReflowInput.ComputedISize()); int32_t numColumns = colStyle->mColumnCount; // If column-fill is set to 'balance', then we want to balance the columns. const bool isBalancing = @@ -1059,16 +1054,18 @@ void nsColumnSetFrame::FindBestBalanceBS aConfig.mKnownFeasibleBSize); if (aConfig.mKnownInfeasibleBSize >= aConfig.mKnownFeasibleBSize - 1) { // aConfig.mKnownFeasibleBSize is where we want to be break; } if (aConfig.mKnownInfeasibleBSize >= availableContentBSize) { + // There's no feasible block-size to fit our contents. We may need to + // reflow one more time after this loop. break; } if (lastKnownFeasibleBSize - aConfig.mKnownFeasibleBSize == 1) { // We decreased the feasible block-size by one twip only. This could // indicate that there is a continuously breakable child frame // that we are crawling through. maybeContinuousBreakingDetected = true; @@ -1119,17 +1116,39 @@ void nsColumnSetFrame::FindBestBalanceBS if (aConfig.mIsBalancing && !aColData.mFeasible && !aPresContext->HasPendingInterrupt()) { // We may need to reflow one more time at the feasible block-size to // get a valid layout. bool skip = false; if (aConfig.mKnownInfeasibleBSize >= availableContentBSize) { aConfig.mColMaxBSize = availableContentBSize; if (mLastBalanceBSize == availableContentBSize) { - skip = true; + if (StaticPrefs::layout_css_column_span_enabled() && + aUnboundedLastColumn && + !aReflowInput.mCBReflowInput->mFrame->HasAnyStateBits( + NS_FRAME_HAS_MULTI_COLUMN_ANCESTOR)) { + // If we are end up here, we have a constrained available content + // block size and cannot fit all the content during the very first + // balancing iteration. We may need to give up balancing and reflow + // again. + // + // Note that we do this only for the top-level column container + // because we don't want a nested column container to create overflow + // columns immediately if its content doesn't fit, and change its + // completeness from incomplete to complete. That is because 1) the + // top-level one might do column balancing, and it can enlarge the + // available block-size so that the nested one could fit its content + // in next balancing iteration; or 2) the top-level container is + // filling columns sequentially, and may have more inline-size to + // create more column boxes for the nested column containers' + // next-in-flow frames. + aConfig = ChooseColumnStrategy(aReflowInput, true); + } else { + skip = true; + } } } else { aConfig.mColMaxBSize = aConfig.mKnownFeasibleBSize; } if (!skip) { // If our block-size is unconstrained, make sure that the last column is // allowed to have arbitrary block-size here, even though we were // balancing. Otherwise we'd have to split, and it's not clear what we'd
--- a/layout/reftests/columns/column-balancing-nested-000-ref.html +++ b/layout/reftests/columns/column-balancing-nested-000-ref.html @@ -12,17 +12,16 @@ color:black; background-color:white; font-size:16px; padding:0; margin:0; } .colset { -moz-column-count: 2; -moz-column-gap: 1px; -moz-column-rule: 1px solid black; border: solid silver; - margin-bottom:1em; width:30ch; } p { margin: 0; } /* balancing column sets deeper than level 2 should only use 1 column */ .colset .colset .colset, .colset .colset .colset .colset { -moz-column-count: 1; @@ -31,25 +30,27 @@ </head> <body> <div class="colset"> <p>one one one one one</p> <div class="colset"> <p>two two two two two</p> </div> </div> +<br> <div class="colset"> <p>one one one one one</p> <div class="colset"> <p>two two two two two</p> <div class="colset"> <p>three three three three three</p> </div> </div> </div> +<br> <div class="colset"> <p>one one one one one</p> <div class="colset"> <p>two two two two two</p> <div class="colset"> <p>three three</p> <div class="colset"> <p>four four four four four</p>
--- a/layout/reftests/columns/column-balancing-nested-000.html +++ b/layout/reftests/columns/column-balancing-nested-000.html @@ -12,38 +12,39 @@ color:black; background-color:white; font-size:16px; padding:0; margin:0; } .colset { -moz-column-count: 2; -moz-column-gap: 1px; -moz-column-rule: 1px solid black; border: solid silver; - margin-bottom:1em; width:30ch; } p { margin: 0; } </style> </head> <body> <div class="colset"> <p>one one one one one</p> <div class="colset"> <p>two two two two two</p> </div> </div> +<br> <div class="colset"> <p>one one one one one</p> <div class="colset"> <p>two two two two two</p> <div class="colset"> <p>three three three three three</p> </div> </div> </div> +<br> <div class="colset"> <p>one one one one one</p> <div class="colset"> <p>two two two two two</p> <div class="colset"> <p>three three</p> <div class="colset"> <p>four four four four four</p>
--- a/modules/libmar/moz.build +++ b/modules/libmar/moz.build @@ -5,16 +5,16 @@ # file, You can obtain one at http://mozilla.org/MPL/2.0/. with Files('**'): BUG_COMPONENT = ('Toolkit', 'Application Update') DIRS += ['src'] if CONFIG['MOZ_ENABLE_SIGNMAR']: - DIRS += ['sign', 'verify'] + DIRS += ['sign'] TEST_DIRS += ['tests'] -elif CONFIG['MOZ_VERIFY_MAR_SIGNATURE']: - DIRS += ['verify'] + if CONFIG['MOZ_VERIFY_MAR_SIGNATURE']: + DIRS += ['verify'] # If we are building ./sign and ./verify then ./tool must come after it DIRS += ['tool']
--- a/modules/libmar/tool/moz.build +++ b/modules/libmar/tool/moz.build @@ -18,19 +18,23 @@ HOST_USE_LIBS += [ if CONFIG['MOZ_ENABLE_SIGNMAR']: Program('signmar') SOURCES += HOST_SOURCES USE_LIBS += [ 'mar', 'signmar', - 'verifymar', ] + if CONFIG['MOZ_VERIFY_MAR_SIGNATURE']: + USE_LIBS += [ + 'verifymar', + ] + for var in ('MAR_CHANNEL_ID', 'MOZ_APP_VERSION'): DEFINES[var] = '"%s"' % CONFIG[var] HOST_DEFINES[var] = DEFINES[var] if CONFIG['MOZ_ENABLE_SIGNMAR']: USE_LIBS += [ 'nspr', 'nss',
--- a/modules/libmar/verify/moz.build +++ b/modules/libmar/verify/moz.build @@ -20,12 +20,24 @@ elif CONFIG['OS_ARCH'] == 'Darwin': 'MacVerifyCrypto.cpp', ] OS_LIBS += [ '-framework Security', ] else: DEFINES['MAR_NSS'] = True LOCAL_INCLUDES += ['../sign'] + USE_LIBS += [ + 'nspr', + 'nss', + 'signmar', + ] + # Ideally, this would be '-Wl,-rpath=$ORIGIN', but the build system + # doesn't do the right escaping yet. Even more ideally, this would + # be LDFLAGS, but the build system doesn't propagate those like USE_LIBS + # and OS_LIBS. Bug #1041943. + OS_LIBS += [ + '-Wl,-rpath=\\$$ORIGIN', + ] LOCAL_INCLUDES += [ '../src', ]
--- a/modules/libpref/init/all.js +++ b/modules/libpref/init/all.js @@ -2510,17 +2510,17 @@ pref("font.blacklist.underline_offset", pref("security.directory", ""); // security-sensitive dialogs should delay button enabling. In milliseconds. pref("security.dialog_enable_delay", 1000); pref("security.notification_enable_delay", 500); #if defined(DEBUG) && !defined(ANDROID) -pref("csp.about_uris_without_csp", "blank,printpreview,srcdoc,addons,cache-entry,config,debugging,devtools,downloads,home,newtab,performance,plugins,profiles,preferences,restartrequired,serviceworkers,sessionrestore,support,sync-log,telemetry,url-classifier,welcomeback"); +pref("csp.about_uris_without_csp", "blank,printpreview,srcdoc,addons,cache-entry,config,debugging,devtools,downloads,home,newtab,plugins,profiles,preferences,restartrequired,serviceworkers,sessionrestore,support,sync-log,telemetry,url-classifier,welcomeback"); // the following prefs are for testing purposes only. pref("csp.overrule_about_uris_without_csp_whitelist", false); pref("csp.skip_about_page_has_csp_assert", false); // assertion flag will be set to false after fixing Bug 1473549 pref("security.allow_eval_with_system_principal", false); pref("security.uris_using_eval_with_system_principal", "autocomplete.xml,redux.js,react-redux.js,content-task.js,preferencesbindings.js,lodash.js,jszip.js,sinon-7.2.7.js,ajv-4.1.1.js,jsol.js"); #endif
--- a/modules/libpref/parser/src/lib.rs +++ b/modules/libpref/parser/src/lib.rs @@ -624,19 +624,19 @@ impl<'t> Parser<'t> { } fn match_hex_digits(&mut self, ndigits: i32) -> Option<u16> { debug_assert!(ndigits == 2 || ndigits == 4); let mut value: u16 = 0; for _ in 0..ndigits { value = value << 4; match self.get_char() { - c @ b'0'...b'9' => value += (c - b'0') as u16, - c @ b'A'...b'F' => value += (c - b'A') as u16 + 10, - c @ b'a'...b'f' => value += (c - b'a') as u16 + 10, + c @ b'0' ..= b'9' => value += (c - b'0') as u16, + c @ b'A' ..= b'F' => value += (c - b'A') as u16 + 10, + c @ b'a' ..= b'f' => value += (c - b'a') as u16 + 10, _ => { self.unget_char(); return None; } } } Some(value) }
--- a/mozglue/baseprofiler/core/platform-linux-android.cpp +++ b/mozglue/baseprofiler/core/platform-linux-android.cpp @@ -71,17 +71,17 @@ using namespace mozilla; namespace mozilla { namespace baseprofiler { int profiler_current_process_id() { return getpid(); } int profiler_current_thread_id() { // glibc doesn't provide a wrapper for gettid(). -#if defined(__GLIBC__) +#if defined(__linux__) || !defined(__BIONIC__) return static_cast<int>(static_cast<pid_t>(syscall(SYS_gettid))); #else return static_cast<int>(gettid()); #endif } static int64_t MicrosecondsSince1970() { struct timeval tv;
--- a/netwerk/protocol/http/Http2Push.cpp +++ b/netwerk/protocol/http/Http2Push.cpp @@ -24,39 +24,116 @@ namespace mozilla { namespace net { class CallChannelOnPush final : public Runnable { public: CallChannelOnPush(nsIHttpChannelInternal* associatedChannel, const nsACString& pushedURI, Http2PushedStream* pushStream) : Runnable("net::CallChannelOnPush"), mAssociatedChannel(associatedChannel), - mPushedURI(pushedURI), - mPushedStream(pushStream) {} + mPushedURI(pushedURI) { + mPushedStreamWrapper = new Http2PushedStreamWrapper(pushStream); + } NS_IMETHOD Run() override { MOZ_ASSERT(NS_IsMainThread()); RefPtr<nsHttpChannel> channel; CallQueryInterface(mAssociatedChannel, channel.StartAssignment()); MOZ_ASSERT(channel); - if (channel && NS_SUCCEEDED(channel->OnPush(mPushedURI, mPushedStream))) { + if (channel && + NS_SUCCEEDED(channel->OnPush(mPushedURI, mPushedStreamWrapper))) { return NS_OK; } LOG3(("Http2PushedStream Orphan %p failed OnPush\n", this)); - mPushedStream->OnPushFailed(); + mPushedStreamWrapper->OnPushFailed(); return NS_OK; } private: nsCOMPtr<nsIHttpChannelInternal> mAssociatedChannel; const nsCString mPushedURI; - Http2PushedStream* mPushedStream; + RefPtr<Http2PushedStreamWrapper> mPushedStreamWrapper; }; +// Because WeakPtr isn't thread-safe we must ensure that the object is destroyed +// on the socket thread, so any Release() called on a different thread is +// dispatched to the socket thread. +bool Http2PushedStreamWrapper::DispatchRelease() { + if (OnSocketThread()) { + return false; + } + + gSocketTransportService->Dispatch( + NewNonOwningRunnableMethod("net::Http2PushedStreamWrapper::Release", this, + &Http2PushedStreamWrapper::Release), + NS_DISPATCH_NORMAL); + + return true; +} + +NS_IMPL_ADDREF(Http2PushedStreamWrapper) +NS_IMETHODIMP_(MozExternalRefCountType) +Http2PushedStreamWrapper::Release() { + nsrefcnt count = mRefCnt - 1; + if (DispatchRelease()) { + // Redispatched to the socket thread. + return count; + } + + MOZ_ASSERT(0 != mRefCnt, "dup release"); + count = --mRefCnt; + NS_LOG_RELEASE(this, count, "Http2PushedStreamWrapper"); + + if (0 == count) { + mRefCnt = 1; + delete (this); + return 0; + } + + return count; +} + +NS_INTERFACE_MAP_BEGIN(Http2PushedStreamWrapper) +NS_INTERFACE_MAP_END + +Http2PushedStreamWrapper::Http2PushedStreamWrapper( + Http2PushedStream* aPushStream) { + MOZ_ASSERT(OnSocketThread(), "not on socket thread"); + mStream = aPushStream; + mRequestString = aPushStream->GetRequestString(); +} + +Http2PushedStreamWrapper::~Http2PushedStreamWrapper() { + MOZ_ASSERT(OnSocketThread(), "not on socket thread"); +} + +Http2PushedStream* Http2PushedStreamWrapper::GetStream() { + MOZ_ASSERT(OnSocketThread(), "not on socket thread"); + if (mStream) { + Http2Stream* stream = mStream; + return static_cast<Http2PushedStream*>(stream); + } + return nullptr; +} + +void Http2PushedStreamWrapper::OnPushFailed() { + if (OnSocketThread()) { + if (mStream) { + Http2Stream* stream = mStream; + static_cast<Http2PushedStream*>(stream)->OnPushFailed(); + } + } else { + gSocketTransportService->Dispatch( + NewRunnableMethod("net::Http2PushedStreamWrapper::OnPushFailed", this, + &Http2PushedStreamWrapper::OnPushFailed), + NS_DISPATCH_NORMAL); + } +} + ////////////////////////////////////////// // Http2PushedStream ////////////////////////////////////////// Http2PushedStream::Http2PushedStream( Http2PushTransactionBuffer* aTransaction, Http2Session* aSession, Http2Stream* aAssociatedStream, uint32_t aID, uint64_t aCurrentForegroundTabOuterContentWindowId)
--- a/netwerk/protocol/http/Http2Push.h +++ b/netwerk/protocol/http/Http2Push.h @@ -127,12 +127,30 @@ class Http2PushTransactionBuffer final : bool mIsDone; UniquePtr<char[]> mBufferedHTTP1; uint32_t mBufferedHTTP1Size; uint32_t mBufferedHTTP1Used; uint32_t mBufferedHTTP1Consumed; }; +class Http2PushedStreamWrapper : public nsISupports { + public: + NS_DECL_THREADSAFE_ISUPPORTS + bool DispatchRelease(); + + explicit Http2PushedStreamWrapper(Http2PushedStream* aPushStream); + + nsCString& GetRequestString() { return mRequestString; } + Http2PushedStream* GetStream(); + void OnPushFailed(); + + private: + virtual ~Http2PushedStreamWrapper(); + + nsCString mRequestString; + WeakPtr<Http2Stream> mStream; +}; + } // namespace net } // namespace mozilla #endif // mozilla_net_Http2Push_Internal_h
--- a/netwerk/protocol/http/Http2Session.cpp +++ b/netwerk/protocol/http/Http2Session.cpp @@ -443,30 +443,36 @@ bool Http2Session::AddStream(nsAHttpTran if (!mFirstHttpTransaction && !mTlsHandshakeFinished) { mFirstHttpTransaction = aHttpTransaction->QueryHttpTransaction(); LOG3(("Http2Session::AddStream first session=%p trans=%p ", this, mFirstHttpTransaction.get())); } if (mClosed || mShouldGoAway) { nsHttpTransaction* trans = aHttpTransaction->QueryHttpTransaction(); - if (trans && !trans->GetPushedStream()) { - LOG3( - ("Http2Session::AddStream %p atrans=%p trans=%p session unusable - " - "resched.\n", - this, aHttpTransaction, trans)); - aHttpTransaction->SetConnection(nullptr); - nsresult rv = gHttpHandler->InitiateTransaction(trans, trans->Priority()); - if (NS_FAILED(rv)) { + if (trans) { + RefPtr<Http2PushedStreamWrapper> pushedStreamWrapper; + pushedStreamWrapper = trans->GetPushedStream(); + if (!pushedStreamWrapper || !pushedStreamWrapper->GetStream()) { LOG3( - ("Http2Session::AddStream %p atrans=%p trans=%p failed to initiate " - "transaction (%08x).\n", - this, aHttpTransaction, trans, static_cast<uint32_t>(rv))); + ("Http2Session::AddStream %p atrans=%p trans=%p session unusable - " + "resched.\n", + this, aHttpTransaction, trans)); + aHttpTransaction->SetConnection(nullptr); + nsresult rv = + gHttpHandler->InitiateTransaction(trans, trans->Priority()); + if (NS_FAILED(rv)) { + LOG3( + ("Http2Session::AddStream %p atrans=%p trans=%p failed to " + "initiate " + "transaction (%08x).\n", + this, aHttpTransaction, trans, static_cast<uint32_t>(rv))); + } + return true; } - return true; } } aHttpTransaction->SetConnection(this); aHttpTransaction->OnActivated(); if (aIsWebsocket) { MOZ_ASSERT(!aUseTunnel, "Websocket on tunnel?!");
--- a/netwerk/protocol/http/Http2Stream.cpp +++ b/netwerk/protocol/http/Http2Stream.cpp @@ -444,23 +444,25 @@ nsresult Http2Stream::ParseHttpRequestHe if (head->IsGet()) { // from :scheme, :authority, :path nsIRequestContext* requestContext = mTransaction->RequestContext(); SpdyPushCache* cache = nullptr; if (requestContext) { cache = requestContext->GetSpdyPushCache(); } + RefPtr<Http2PushedStreamWrapper> pushedStreamWrapper; Http2PushedStream* pushedStream = nullptr; // If a push stream is attached to the transaction via onPush, match only // with that one. This occurs when a push was made with in conjunction with // a nsIHttpPushListener nsHttpTransaction* trans = mTransaction->QueryHttpTransaction(); - if (trans && (pushedStream = trans->TakePushedStream())) { + if (trans && (pushedStreamWrapper = trans->TakePushedStream()) && + (pushedStream = pushedStreamWrapper->GetStream())) { if (pushedStream->mSession == mSession) { LOG3(("Pushed Stream match based on OnPush correlation %p", pushedStream)); } else { LOG3(("Pushed Stream match failed due to stream mismatch %p %" PRId64 " %" PRId64 "\n", pushedStream, pushedStream->mSession->Serial(), mSession->Serial()));
--- a/netwerk/protocol/http/nsHttpChannel.cpp +++ b/netwerk/protocol/http/nsHttpChannel.cpp @@ -9367,24 +9367,24 @@ nsHttpChannel::SetNotificationCallbacks( } return rv; } bool nsHttpChannel::AwaitingCacheCallbacks() { return mCacheEntriesToWaitFor != 0; } -void nsHttpChannel::SetPushedStream(Http2PushedStream* stream) { +void nsHttpChannel::SetPushedStream(Http2PushedStreamWrapper* stream) { MOZ_ASSERT(stream); MOZ_ASSERT(!mPushedStream); mPushedStream = stream; } nsresult nsHttpChannel::OnPush(const nsACString& url, - Http2PushedStream* pushedStream) { + Http2PushedStreamWrapper* pushedStream) { MOZ_ASSERT(NS_IsMainThread()); LOG(("nsHttpChannel::OnPush [this=%p]\n", this)); MOZ_ASSERT(mCaps & NS_HTTP_ONPUSH_LISTENER); nsCOMPtr<nsIHttpPushListener> pushListener; NS_QueryNotificationCallbacks(mCallbacks, mLoadGroup, NS_GET_IID(nsIHttpPushListener), getter_AddRefs(pushListener));
--- a/netwerk/protocol/http/nsHttpChannel.h +++ b/netwerk/protocol/http/nsHttpChannel.h @@ -139,17 +139,17 @@ class nsHttpChannel final : public HttpB nsHttpChannel(); virtual MOZ_MUST_USE nsresult Init(nsIURI* aURI, uint32_t aCaps, nsProxyInfo* aProxyInfo, uint32_t aProxyResolveFlags, nsIURI* aProxyURI, uint64_t aChannelId, nsContentPolicyType aContentPolicyType) override; MOZ_MUST_USE nsresult OnPush(const nsACString& uri, - Http2PushedStream* pushedStream); + Http2PushedStreamWrapper* pushedStream); static bool IsRedirectStatus(uint32_t status); static bool WillRedirect(nsHttpResponseHead* response); // Methods HttpBaseChannel didn't implement for us or that we override. // // nsIRequest NS_IMETHOD Cancel(nsresult status) override; @@ -536,17 +536,17 @@ class nsHttpChannel final : public HttpB bool ignoreMissingPartialLen = false); MOZ_MUST_USE nsresult SetupByteRangeRequest(int64_t partialLen); void UntieByteRangeRequest(); void UntieValidationRequest(); MOZ_MUST_USE nsresult OpenCacheInputStream(nsICacheEntry* cacheEntry, bool startBuffering, bool checkingAppCacheEntry); - void SetPushedStream(Http2PushedStream* stream); + void SetPushedStream(Http2PushedStreamWrapper* stream); void MaybeWarnAboutAppCache(); void SetLoadGroupUserAgentOverride(); void SetOriginHeader(); void SetDoNotTrack(); @@ -745,17 +745,17 @@ class nsHttpChannel final : public HttpB // true. nsCString mTopWindowOrigin; nsTArray<nsContinueRedirectionFunc> mRedirectFuncStack; // Needed for accurate DNS timing RefPtr<nsDNSPrefetch> mDNSPrefetch; - Http2PushedStream* mPushedStream; + RefPtr<Http2PushedStreamWrapper> mPushedStream; // True if the channel's principal was found on a phishing, malware, or // tracking (if tracking protection is enabled) blocklist bool mLocalBlocklist; MOZ_MUST_USE nsresult WaitForRedirectCallback(); void PushRedirectAsyncFunc(nsContinueRedirectionFunc func); void PopRedirectAsyncFunc(nsContinueRedirectionFunc func);
--- a/netwerk/protocol/http/nsHttpConnectionMgr.cpp +++ b/netwerk/protocol/http/nsHttpConnectionMgr.cpp @@ -1887,24 +1887,28 @@ nsresult nsHttpConnectionMgr::ProcessNew // been canceled (see bug 190001). if (NS_FAILED(trans->Status())) { LOG((" transaction was canceled... dropping event!\n")); return NS_OK; } trans->SetPendingTime(); - Http2PushedStream* pushedStream = trans->GetPushedStream(); - if (pushedStream) { - LOG((" ProcessNewTransaction %p tied to h2 session push %p\n", trans, - pushedStream->Session())); - return pushedStream->Session()->AddStream(trans, trans->Priority(), false, - false, nullptr) - ? NS_OK - : NS_ERROR_UNEXPECTED; + RefPtr<Http2PushedStreamWrapper> pushedStreamWrapper = + trans->GetPushedStream(); + if (pushedStreamWrapper) { + Http2PushedStream* pushedStream = pushedStreamWrapper->GetStream(); + if (pushedStream) { + LOG((" ProcessNewTransaction %p tied to h2 session push %p\n", trans, + pushedStream->Session())); + return pushedStream->Session()->AddStream(trans, trans->Priority(), false, + false, nullptr) + ? NS_OK + : NS_ERROR_UNEXPECTED; + } } nsresult rv = NS_OK; nsHttpConnectionInfo* ci = trans->ConnectionInfo(); MOZ_ASSERT(ci); nsConnectionEntry* ent = GetOrCreateConnectionEntry(ci, !!trans->TunnelProvider());
--- a/netwerk/protocol/http/nsHttpHandler.cpp +++ b/netwerk/protocol/http/nsHttpHandler.cpp @@ -935,18 +935,18 @@ void nsHttpHandler::BuildUserAgent() { mUserAgent += ' '; mUserAgent += mAppName; mUserAgent += '/'; mUserAgent += mAppVersion; } } #ifdef XP_WIN -# define WNT_BASE "Windows NT %ld.%ld" -# define W64_PREFIX "; Win64" +# define OSCPU_WINDOWS "Windows NT %ld.%ld" +# define OSCPU_WIN64 OSCPU_WINDOWS "; Win64; x64" #endif void nsHttpHandler::InitUserAgentComponents() { #ifndef MOZ_UA_OS_AGNOSTIC // Gather platform. mPlatform.AssignLiteral( # if defined(ANDROID) "Android" @@ -1012,28 +1012,28 @@ void nsHttpHandler::InitUserAgentCompone #ifndef MOZ_UA_OS_AGNOSTIC // Gather OS/CPU. # if defined(XP_WIN) OSVERSIONINFO info = {sizeof(OSVERSIONINFO)}; # pragma warning(push) # pragma warning(disable : 4996) if (GetVersionEx(&info)) { # pragma warning(pop) + const char* format; -# if defined _M_IA64 - format = WNT_BASE W64_PREFIX "; IA64"; -# elif defined _M_X64 || defined _M_AMD64 - format = WNT_BASE W64_PREFIX "; x64"; +# if defined _M_X64 || defined _M_AMD64 + format = OSCPU_WIN64; # else BOOL isWow64 = FALSE; if (!IsWow64Process(GetCurrentProcess(), &isWow64)) { isWow64 = FALSE; } - format = isWow64 ? WNT_BASE "; WOW64" : WNT_BASE; + format = isWow64 ? OSCPU_WIN64 : OSCPU_WINDOWS; # endif + SmprintfPointer buf = mozilla::Smprintf(format, info.dwMajorVersion, info.dwMinorVersion); if (buf) { mOscpu = buf.get(); } } # elif defined(XP_MACOSX) # if defined(__ppc__) @@ -1042,45 +1042,32 @@ void nsHttpHandler::InitUserAgentCompone mOscpu.AssignLiteral("Intel Mac OS X"); # endif SInt32 majorVersion = nsCocoaFeatures::OSXVersionMajor(); SInt32 minorVersion = nsCocoaFeatures::OSXVersionMinor(); mOscpu += nsPrintfCString(" %d.%d", static_cast<int>(majorVersion), static_cast<int>(minorVersion)); # elif defined(XP_UNIX) struct utsname name; - int ret = uname(&name); if (ret >= 0) { nsAutoCString buf; buf = (char*)name.sysname; - - if (strcmp(name.machine, "x86_64") == 0 && - sizeof(void*) == sizeof(int32_t)) { - // We're running 32-bit code on x86_64. Make this browser - // look like it's running on i686 hardware, but append " - // (x86_64)" to the end of the oscpu identifier to be able - // to differentiate this from someone running 64-bit code - // on x86_64.. - - buf += " i686 on x86_64"; - } else { - buf += ' '; + buf += ' '; # ifdef AIX - // AIX uname returns machine specific info in the uname.machine - // field and does not return the cpu type like other platforms. - // We use the AIX version and release numbers instead. - buf += (char*)name.version; - buf += '.'; - buf += (char*)name.release; + // AIX uname returns machine specific info in the uname.machine + // field and does not return the cpu type like other platforms. + // We use the AIX version and release numbers instead. + buf += (char*)name.version; + buf += '.'; + buf += (char*)name.release; # else - buf += (char*)name.machine; + buf += (char*)name.machine; # endif - } mOscpu.Assign(buf); } # endif #endif mUserAgentIsDirty = true; }
--- a/netwerk/protocol/http/nsHttpTransaction.h +++ b/netwerk/protocol/http/nsHttpTransaction.h @@ -138,23 +138,24 @@ class nsHttpTransaction final : public n void SetRequestContext(nsIRequestContext* aRequestContext); void DispatchedAsBlocking(); void RemoveDispatchedAsBlocking(); void DisableSpdy() override; nsHttpTransaction* QueryHttpTransaction() override { return this; } - Http2PushedStream* GetPushedStream() { return mPushedStream; } - Http2PushedStream* TakePushedStream() { - Http2PushedStream* r = mPushedStream; - mPushedStream = nullptr; - return r; + already_AddRefed<Http2PushedStreamWrapper> GetPushedStream() { + return do_AddRef(mPushedStream); } - void SetPushedStream(Http2PushedStream* push) { mPushedStream = push; } + already_AddRefed<Http2PushedStreamWrapper> TakePushedStream() { + return mPushedStream.forget(); + } + + void SetPushedStream(Http2PushedStreamWrapper* push) { mPushedStream = push; } uint32_t InitialRwin() const { return mInitialRwin; }; bool ChannelPipeFull() { return mWaitingOnPipeOut; } // Locked methods to get and set timing info const TimingStruct Timings(); void BootstrapTimings(TimingStruct times); void SetDomainLookupStart(mozilla::TimeStamp timeStamp, bool onlyIfNull = false); @@ -298,17 +299,17 @@ class nsHttpTransaction final : public n // After a 304/204 or other "no-content" style response we will skip over // up to MAX_INVALID_RESPONSE_BODY_SZ bytes when looking for the next // response header to deal with servers that actually sent a response // body where they should not have. This member tracks how many bytes have // so far been skipped. uint32_t mInvalidResponseBytesRead; - Http2PushedStream* mPushedStream; + RefPtr<Http2PushedStreamWrapper> mPushedStream; uint32_t mInitialRwin; nsHttpChunkedDecoder* mChunkedDecoder; TimingStruct mTimings; nsresult mStatus;
--- a/netwerk/sctp/datachannel/DataChannel.cpp +++ b/netwerk/sctp/datachannel/DataChannel.cpp @@ -562,21 +562,16 @@ bool DataChannelConnection::Init(const u event.se_type = event_type; if (usrsctp_setsockopt(mMasterSocket, IPPROTO_SCTP, SCTP_EVENT, &event, sizeof(event)) < 0) { LOG(("*** failed setsockopt SCTP_EVENT errno %d", errno)); goto error_cleanup; } } - // Update number of streams - mStreams.AppendElements(aNumStreams); - for (uint32_t i = 0; i < aNumStreams; ++i) { - mStreams[i] = nullptr; - } memset(&initmsg, 0, sizeof(initmsg)); len = sizeof(initmsg); if (usrsctp_getsockopt(mMasterSocket, IPPROTO_SCTP, SCTP_INITMSG, &initmsg, &len) < 0) { LOG(("*** failed getsockopt SCTP_INITMSG")); goto error_cleanup; } LOG(("Setting number of SCTP streams to %u, was %u/%u", aNumStreams, @@ -662,30 +657,37 @@ bool DataChannelConnection::ConnectToTra "SCTP wasn't initialized before ConnectToTransport!"); if (NS_WARN_IF(aTransportId.empty())) { return false; } mLocalPort = localport; mRemotePort = remoteport; mState = CONNECTING; - - RUN_ON_THREAD( - mSTS, - WrapRunnable(RefPtr<DataChannelConnection>(this), - &DataChannelConnection::SetSignals, aTransportId, aClient), - NS_DISPATCH_NORMAL); + mAllocateEven = Some(aClient); + + // Could be faster. Probably doesn't matter. + while (auto channel = mChannels.Get(INVALID_STREAM)) { + mChannels.Remove(channel); + channel->mStream = FindFreeStream(); + if (channel->mStream != INVALID_STREAM) { + mChannels.Insert(channel); + } + } + + RUN_ON_THREAD(mSTS, + WrapRunnable(RefPtr<DataChannelConnection>(this), + &DataChannelConnection::SetSignals, aTransportId), + NS_DISPATCH_NORMAL); return true; } -void DataChannelConnection::SetSignals(const std::string& aTransportId, - bool aClient) { +void DataChannelConnection::SetSignals(const std::string& aTransportId) { ASSERT_WEBRTC(IsSTSThread()); mTransportId = aTransportId; - mAllocateEven = aClient; mTransportHandler->SignalPacketReceived.connect( this, &DataChannelConnection::SctpDtlsInput); // SignalStateChange() doesn't call you with the initial state if (mTransportHandler->GetState(mTransportId, false) == TransportLayer::TS_OPEN) { LOG(("Setting transport signals, dtls already open")); CompleteConnect(); } else { @@ -1020,69 +1022,68 @@ bool DataChannelConnection::Connect(cons Dispatch(do_AddRef(new DataChannelOnMessageAvailable( DataChannelOnMessageAvailable::ON_CONNECTION, this, (DataChannel*)nullptr))); return true; } #endif DataChannel* DataChannelConnection::FindChannelByStream(uint16_t stream) { - return mStreams.SafeElementAt(stream); + return mChannels.Get(stream).get(); } uint16_t DataChannelConnection::FindFreeStream() { - uint32_t i, j, limit; - - limit = mStreams.Length(); - if (limit > MAX_NUM_STREAMS) limit = MAX_NUM_STREAMS; - - for (i = (mAllocateEven ? 0 : 1); i < limit; i += 2) { - if (!mStreams[i]) { - // Verify it's not still in the process of closing - for (j = 0; j < mStreamsResetting.Length(); ++j) { - if (mStreamsResetting[j] == i) { - break; - } + ASSERT_WEBRTC(NS_IsMainThread()); + uint16_t i, limit; + + limit = MAX_NUM_STREAMS; + + MOZ_ASSERT(mAllocateEven.isSome()); + for (i = (*mAllocateEven ? 0 : 1); i < limit; i += 2) { + if (mChannels.Get(i)) { + continue; + } + + // Verify it's not still in the process of closing + size_t j; + for (j = 0; j < mStreamsResetting.Length(); ++j) { + if (mStreamsResetting[j] == i) { + break; } - if (j == mStreamsResetting.Length()) break; + } + + if (j == mStreamsResetting.Length()) { + return i; } } - if (i >= limit) { - return INVALID_STREAM; - } - return i; + return INVALID_STREAM; } uint32_t DataChannelConnection::UpdateCurrentStreamIndex() { - if (mCurrentStream == mStreams.Length() - 1) { + RefPtr<DataChannel> channel = mChannels.GetNextChannel(mCurrentStream); + if (!channel) { mCurrentStream = 0; } else { - ++mCurrentStream; + mCurrentStream = channel->mStream; } - return mCurrentStream; } uint32_t DataChannelConnection::GetCurrentStreamIndex() { - // Fix current stream index (in case #streams decreased) - if (mCurrentStream >= mStreams.Length()) { - mCurrentStream = 0; - } - return mCurrentStream; } bool DataChannelConnection::RequestMoreStreams(int32_t aNeeded) { struct sctp_status status; struct sctp_add_streams sas; uint32_t outStreamsNeeded; socklen_t len; - if (aNeeded + mStreams.Length() > MAX_NUM_STREAMS) { - aNeeded = MAX_NUM_STREAMS - mStreams.Length(); + if (aNeeded + mNegotiatedIdLimit > MAX_NUM_STREAMS) { + aNeeded = MAX_NUM_STREAMS - mNegotiatedIdLimit; } if (aNeeded <= 0) { return false; } len = (socklen_t)sizeof(struct sctp_status); if (usrsctp_getsockopt(mMasterSocket, IPPROTO_SCTP, SCTP_STATUS, &status, &len) < 0) { @@ -1103,18 +1104,18 @@ bool DataChannelConnection::RequestMoreS LOG(("Already have %u output streams", outStreamsNeeded)); return true; } LOG(("***failed: setsockopt ADD errno=%d", errno)); return false; } LOG(("Requested %u more streams", outStreamsNeeded)); - // We add to mStreams when we get a SCTP_STREAM_CHANGE_EVENT and the - // values are larger than mStreams.Length() + // We add to mNegotiatedIdLimit when we get a SCTP_STREAM_CHANGE_EVENT and the + // values are larger than mNegotiatedIdLimit return true; } // Returns a POSIX error code. int DataChannelConnection::SendControlMessage(const uint8_t* data, uint32_t len, uint16_t stream) { struct sctp_sendv_spa info = {0}; @@ -1212,16 +1213,17 @@ int DataChannelConnection::SendOpenReque // filling the SCTP's buffers. // returns if we're still blocked (true) bool DataChannelConnection::SendDeferredMessages() { RefPtr<DataChannel> channel; // we may null out the refs to this // This may block while something is modifying channels, but should not block // for IO + ASSERT_WEBRTC(!NS_IsMainThread()); mLock.AssertCurrentThreadOwns(); LOG(("SendDeferredMessages called, pending type: %d", mPendingType)); if (!mPendingType) { return false; } // Send pending control messages @@ -1237,17 +1239,17 @@ bool DataChannelConnection::SendDeferred // Note: There may or may not be pending data messages mPendingType = PENDING_DATA; } bool blocked = false; uint32_t i = GetCurrentStreamIndex(); uint32_t end = i; do { - channel = mStreams[i]; + channel = mChannels.Get(i); // Should already be cleared if closing/closed if (!channel || channel->mBufferedData.IsEmpty()) { i = UpdateCurrentStreamIndex(); continue; } // Send buffered data messages // Warning: This will fail in case ndata is inactive and a previously @@ -1314,16 +1316,17 @@ bool DataChannelConnection::SendBuffered // Caller must ensure that length <= SIZE_MAX void DataChannelConnection::HandleOpenRequestMessage( const struct rtcweb_datachannel_open_request* req, uint32_t length, uint16_t stream) { RefPtr<DataChannel> channel; uint32_t prValue; uint16_t prPolicy; + ASSERT_WEBRTC(!NS_IsMainThread()); mLock.AssertCurrentThreadOwns(); const size_t requiredLength = (sizeof(*req) - 1) + ntohs(req->label_length) + ntohs(req->protocol_length); if (((size_t)length) != requiredLength) { LOG(("%s: Inconsistent length: %u, should be %zu", __FUNCTION__, length, requiredLength)); if (((size_t)length) < requiredLength) return; @@ -1349,17 +1352,17 @@ void DataChannelConnection::HandleOpenRe LOG(("Unknown channel type %d", req->channel_type)); /* XXX error handling */ return; } prValue = ntohl(req->reliability_param); bool ordered = !(req->channel_type & 0x80); if ((channel = FindChannelByStream(stream))) { - if (!(channel->mFlags & DATA_CHANNEL_FLAGS_EXTERNAL_NEGOTIATED)) { + if (!channel->mNegotiated) { LOG( ("ERROR: HandleOpenRequestMessage: channel for pre-existing stream " "%u that was not externally negotiated. JS is lying to us, or " "there's an id collision.", stream)); /* XXX: some error handling */ } else { LOG(("Open for externally negotiated channel %u", stream)); @@ -1370,31 +1373,31 @@ void DataChannelConnection::HandleOpenRe ("WARNING: external negotiation mismatch with OpenRequest:" "channel %u, policy %u/%u, value %u/%u, ordered %d/%d", stream, prPolicy, channel->mPrPolicy, prValue, channel->mPrValue, static_cast<int>(ordered), static_cast<int>(channel->mOrdered))); } } return; } - if (stream >= mStreams.Length()) { + if (stream >= mNegotiatedIdLimit) { LOG(("%s: stream %u out of bounds (%zu)", __FUNCTION__, stream, - mStreams.Length())); + mNegotiatedIdLimit)); return; } nsCString label( nsDependentCSubstring(&req->label[0], ntohs(req->label_length))); nsCString protocol(nsDependentCSubstring( &req->label[ntohs(req->label_length)], ntohs(req->protocol_length))); channel = new DataChannel(this, stream, DataChannel::OPEN, label, protocol, prPolicy, prValue, ordered, false, nullptr, nullptr); - mStreams[stream] = channel; + mChannels.Insert(channel); LOG(("%s: sending ON_CHANNEL_CREATED for %s/%s: %u", __FUNCTION__, channel->mLabel.get(), channel->mProtocol.get(), stream)); Dispatch(do_AddRef(new DataChannelOnMessageAvailable( DataChannelOnMessageAvailable::ON_CHANNEL_CREATED, this, channel))); LOG(("%s: deferring sending ON_CHANNEL_OPEN for %p", __FUNCTION__, channel.get())); @@ -1556,18 +1559,17 @@ void DataChannelConnection::HandleDataMe // Remaining chunks of previously truncated message (due to the buffer being // full)? if (channel->mFlags & DATA_CHANNEL_FLAGS_CLOSING_TOO_LARGE) { LOG( ("DataChannel: Ignoring partial message of length %u, buffer full and " "closing", data_length)); // Only unblock if unordered - if ((channel->mFlags & DATA_CHANNEL_FLAGS_OUT_OF_ORDER_ALLOWED) && - (flags & MSG_EOR)) { + if (!channel->mOrdered && (flags & MSG_EOR)) { channel->mFlags &= ~DATA_CHANNEL_FLAGS_CLOSING_TOO_LARGE; } } // Buffer message until complete bufferFlags = BufferMessage(channel->mRecvBuffer, buffer, data_length, ppid, flags); if (bufferFlags & DATA_CHANNEL_BUFFER_MESSAGE_FLAGS_TOO_LARGE) { @@ -1984,17 +1986,19 @@ void DataChannelConnection::ClearResets( } for (uint32_t i = 0; i < mStreamsResetting.Length(); ++i) { RefPtr<DataChannel> channel; channel = FindChannelByStream(mStreamsResetting[i]); if (channel) { LOG(("Forgetting channel %u (%p) with pending reset", channel->mStream, channel.get())); - mStreams[channel->mStream] = nullptr; + // TODO: Do we _really_ want to remove this? Are we allowed to reuse the + // id? + mChannels.Remove(channel); } } mStreamsResetting.Clear(); } void DataChannelConnection::ResetOutgoingStream(uint16_t stream) { uint32_t i; @@ -2066,21 +2070,20 @@ void DataChannelConnection::HandleStream // 2. We sent our own reset (CLOSING); either they crossed on the // wire, or this is a response to our Reset. // Go to CLOSED // 3. We've sent a open but haven't gotten a response yet (CONNECTING) // I believe this is impossible, as we don't have an input stream // yet. LOG(("Incoming: Channel %u closed", channel->mStream)); - if (mStreams[channel->mStream]) { + if (mChannels.Remove(channel)) { // Mark the stream for reset (the reset is sent below) ResetOutgoingStream(channel->mStream); } - mStreams[channel->mStream] = nullptr; LOG(("Disconnected DataChannel %p from connection %p", (void*)channel.get(), (void*)channel->mConnection.get())); channel->StreamClosedLocked(); } else { LOG(("Can't find incoming channel %d", i)); } } @@ -2091,104 +2094,72 @@ void DataChannelConnection::HandleStream if (!mStreamsResetting.IsEmpty()) { LOG(("Sending %zu pending resets", mStreamsResetting.Length())); SendOutgoingStreamReset(); } } void DataChannelConnection::HandleStreamChangeEvent( const struct sctp_stream_change_event* strchg) { - uint16_t stream; - RefPtr<DataChannel> channel; - + ASSERT_WEBRTC(!NS_IsMainThread()); if (strchg->strchange_flags == SCTP_STREAM_CHANGE_DENIED) { LOG(("*** Failed increasing number of streams from %zu (%u/%u)", - mStreams.Length(), strchg->strchange_instrms, + mNegotiatedIdLimit, strchg->strchange_instrms, strchg->strchange_outstrms)); // XXX FIX! notify pending opens of failure return; } - if (strchg->strchange_instrms > mStreams.Length()) { - LOG(("Other side increased streams from %zu to %u", mStreams.Length(), + if (strchg->strchange_instrms > mNegotiatedIdLimit) { + LOG(("Other side increased streams from %zu to %u", mNegotiatedIdLimit, strchg->strchange_instrms)); } - if (strchg->strchange_outstrms > mStreams.Length() || - strchg->strchange_instrms > mStreams.Length()) { - uint16_t old_len = mStreams.Length(); - uint16_t new_len = - std::max(strchg->strchange_outstrms, strchg->strchange_instrms); + uint16_t old_limit = mNegotiatedIdLimit; + uint16_t new_limit = + std::max(strchg->strchange_outstrms, strchg->strchange_instrms); + if (new_limit > mNegotiatedIdLimit) { LOG(("Increasing number of streams from %u to %u - adding %u (in: %u)", - old_len, new_len, new_len - old_len, strchg->strchange_instrms)); + old_limit, new_limit, new_limit - old_limit, + strchg->strchange_instrms)); // make sure both are the same length - mStreams.AppendElements(new_len - old_len); - LOG(("New length = %zu (was %d)", mStreams.Length(), old_len)); - for (size_t i = old_len; i < mStreams.Length(); ++i) { - mStreams[i] = nullptr; - } + mNegotiatedIdLimit = new_limit; + LOG(("New length = %zu (was %d)", mNegotiatedIdLimit, old_limit)); // Re-process any channels waiting for streams. // Linear search, but we don't increase channels often and // the array would only get long in case of an app error normally // Make sure we request enough streams if there's a big jump in streams // Could make a more complex API for OpenXxxFinish() and avoid this loop - size_t num_needed = mPending.GetSize(); - LOG(("%zu of %d new streams already needed", num_needed, - new_len - old_len)); - num_needed -= (new_len - old_len); // number we added - if (num_needed > 0) { - if (num_needed < 16) num_needed = 16; - LOG(("Not enough new streams, asking for %zu more", num_needed)); + auto channels = mChannels.GetAll(); + size_t num_needed = + channels.Length() ? (channels.LastElement()->mStream + 1) : 0; + MOZ_ASSERT(num_needed != INVALID_STREAM); + if (num_needed > new_limit) { + int32_t more_needed = num_needed - ((int32_t)mNegotiatedIdLimit) + 16; + LOG(("Not enough new streams, asking for %d more", more_needed)); // TODO: parameter is an int32_t but we pass size_t - RequestMoreStreams(num_needed); + RequestMoreStreams(more_needed); } else if (strchg->strchange_outstrms < strchg->strchange_instrms) { LOG(("Requesting %d output streams to match partner", strchg->strchange_instrms - strchg->strchange_outstrms)); RequestMoreStreams(strchg->strchange_instrms - strchg->strchange_outstrms); } ProcessQueuedOpens(); } // else probably not a change in # of streams - for (uint32_t i = 0; i < mStreams.Length(); ++i) { - channel = mStreams[i]; - if (!channel) continue; - - if (channel->mStream == INVALID_STREAM) { - if ((strchg->strchange_flags & SCTP_STREAM_CHANGE_DENIED) || - (strchg->strchange_flags & SCTP_STREAM_CHANGE_FAILED)) { + if ((strchg->strchange_flags & SCTP_STREAM_CHANGE_DENIED) || + (strchg->strchange_flags & SCTP_STREAM_CHANGE_FAILED)) { + // Other side denied our request. Need to AnnounceClosed some stuff. + for (auto& channel : mChannels.GetAll()) { + if (channel->mStream >= mNegotiatedIdLimit) { /* XXX: Signal to the other end. */ channel->AnnounceClosed(); // maybe fire onError (bug 843625) - } else { - stream = FindFreeStream(); - if (stream != INVALID_STREAM) { - channel->mStream = stream; - mStreams[stream] = channel; - - // Send open request - int error = SendOpenRequestMessage( - channel->mLabel, channel->mProtocol, channel->mStream, - !!(channel->mFlags & DATA_CHANNEL_FLAGS_OUT_OF_ORDER_ALLOWED), - channel->mPrPolicy, channel->mPrValue); - if (error) { - LOG(("SendOpenRequest failed, error = %d", error)); - // Close the channel, inform the user - mStreams[channel->mStream] = nullptr; - channel->AnnounceClosed(); - // Don't need to reset; we didn't open it - } else { - channel->mFlags |= DATA_CHANNEL_FLAGS_READY; - channel->AnnounceOpen(); - } - } else { - /* We will not find more ... */ - break; - } } } } } // Called with mLock locked! void DataChannelConnection::HandleNotification( const union sctp_notification* notif, size_t n) { @@ -2274,19 +2245,28 @@ int DataChannelConnection::ReceiveCallba // usrsctp defines the callback as returning an int, but doesn't use it return 1; } already_AddRefed<DataChannel> DataChannelConnection::Open( const nsACString& label, const nsACString& protocol, Type type, bool inOrder, uint32_t prValue, DataChannelListener* aListener, nsISupports* aContext, bool aExternalNegotiated, uint16_t aStream) { + ASSERT_WEBRTC(NS_IsMainThread()); if (!aExternalNegotiated) { - // aStream == INVALID_STREAM to have the protocol allocate - aStream = INVALID_STREAM; + if (mAllocateEven.isSome()) { + aStream = FindFreeStream(); + if (aStream == INVALID_STREAM) { + return nullptr; + } + } else { + // We do not yet know whether we are client or server, and an id has not + // been chosen for us. We will need to choose later. + aStream = INVALID_STREAM; + } } uint16_t prPolicy = SCTP_PR_SCTP_NONE; LOG( ("DC Open: label %s/%s, type %u, inorder %d, prValue %u, listener %p, " "context %p, external: %s, stream %u", PromiseFlatCString(label).get(), PromiseFlatCString(protocol).get(), type, inOrder, prValue, aListener, aContext, @@ -2305,40 +2285,39 @@ already_AddRefed<DataChannel> DataChanne LOG(("ERROR: unsupported channel type: %u", type)); MOZ_ASSERT(false); return nullptr; } if ((prPolicy == SCTP_PR_SCTP_NONE) && (prValue != 0)) { return nullptr; } - // Don't look past currently-negotiated streams - if (aStream != INVALID_STREAM && aStream < mStreams.Length() && - mStreams[aStream]) { + if (aStream != INVALID_STREAM && mChannels.Get(aStream)) { LOG(("ERROR: external negotiation of already-open channel %u", aStream)); // XXX How do we indicate this up to the application? Probably the // caller's job, but we may need to return an error code. return nullptr; } RefPtr<DataChannel> channel(new DataChannel( this, aStream, DataChannel::CONNECTING, label, protocol, prPolicy, prValue, inOrder, aExternalNegotiated, aListener, aContext)); + mChannels.Insert(channel); MutexAutoLock lock(mLock); // OpenFinish assumes this return OpenFinish(channel.forget()); } // Separate routine so we can also call it to finish up from pending opens already_AddRefed<DataChannel> DataChannelConnection::OpenFinish( already_AddRefed<DataChannel>&& aChannel) { RefPtr<DataChannel> channel(aChannel); // takes the reference passed in // Normally 1 reference if called from ::Open(), or 2 if called from // ProcessQueuedOpens() unless the DOMDataChannel was gc'd - uint16_t stream = channel->mStream; + const uint16_t stream = channel->mStream; bool queue = false; mLock.AssertCurrentThreadOwns(); // Cases we care about: // Pre-negotiated: // Not Open: // Doesn't fit: @@ -2358,62 +2337,53 @@ already_AddRefed<DataChannel> DataChanne // -> RequestMoreStreams && queue // Does fit: // -> open // So the Open cases are basically the same // Not Open cases are simply queue for non-negotiated, and // either change the initial ask or possibly renegotiate after open. if (mState == OPEN) { - if (stream == INVALID_STREAM) { - stream = FindFreeStream(); // may be INVALID_STREAM if we need more - } - if (stream == INVALID_STREAM || stream >= mStreams.Length()) { + MOZ_ASSERT(stream != INVALID_STREAM); + if (stream >= mNegotiatedIdLimit) { // RequestMoreStreams() limits to MAX_NUM_STREAMS -- allocate extra // streams to avoid going back immediately for more if the ask to N, N+1, // etc - int32_t more_needed = (stream == INVALID_STREAM) - ? 16 - : (stream - ((int32_t)mStreams.Length())) + 16; + int32_t more_needed = stream - ((int32_t)mNegotiatedIdLimit) + 16; if (!RequestMoreStreams(more_needed)) { // Something bad happened... we're done goto request_error_cleanup; } queue = true; } } else { // not OPEN - if (stream != INVALID_STREAM && stream >= mStreams.Length() && + if (stream != INVALID_STREAM && stream >= mNegotiatedIdLimit && mState == CLOSED) { // Update number of streams for init message struct sctp_initmsg initmsg; socklen_t len = sizeof(initmsg); - int32_t total_needed = stream + 16; + uint16_t total_needed = + (stream < UINT16_MAX - 16) ? stream + 16 : UINT16_MAX; memset(&initmsg, 0, sizeof(initmsg)); if (usrsctp_getsockopt(mMasterSocket, IPPROTO_SCTP, SCTP_INITMSG, &initmsg, &len) < 0) { LOG(("*** failed getsockopt SCTP_INITMSG")); goto request_error_cleanup; } LOG(("Setting number of SCTP streams to %u, was %u/%u", total_needed, initmsg.sinit_num_ostreams, initmsg.sinit_max_instreams)); initmsg.sinit_num_ostreams = total_needed; initmsg.sinit_max_instreams = MAX_NUM_STREAMS; if (usrsctp_setsockopt(mMasterSocket, IPPROTO_SCTP, SCTP_INITMSG, &initmsg, (socklen_t)sizeof(initmsg)) < 0) { LOG(("*** failed setsockopt SCTP_INITMSG, errno %d", errno)); goto request_error_cleanup; } - - int32_t old_len = mStreams.Length(); - mStreams.AppendElements(total_needed - old_len); - for (int32_t i = old_len; i < total_needed; ++i) { - mStreams[i] = nullptr; - } } // else if state is CONNECTING, we'll just re-negotiate when OpenFinish // is called, if needed queue = true; } if (queue) { LOG(("Queuing channel %p (%u) to finish open", channel.get(), stream)); // Also serves to mark we told the app @@ -2421,57 +2391,51 @@ already_AddRefed<DataChannel> DataChanne // we need a ref for the nsDeQue and one to return DataChannel* rawChannel = channel; rawChannel->AddRef(); mPending.Push(rawChannel); return channel.forget(); } MOZ_ASSERT(stream != INVALID_STREAM); - // just allocated (& OPEN), or externally negotiated - mStreams[stream] = channel; // holds a reference - channel->mStream = stream; + MOZ_ASSERT(stream < mNegotiatedIdLimit); #ifdef TEST_QUEUED_DATA // It's painful to write a test for this... channel->AnnounceOpen(); - channel->mFlags |= DATA_CHANNEL_FLAGS_READY; SendDataMsgInternalOrBuffer(channel, "Help me!", 8, DATA_CHANNEL_PPID_DOMSTRING); #endif - if (channel->mFlags & DATA_CHANNEL_FLAGS_OUT_OF_ORDER_ALLOWED) { + if (!channel->mOrdered) { // Don't send unordered until this gets cleared channel->mFlags |= DATA_CHANNEL_FLAGS_WAITING_ACK; } - if (!(channel->mFlags & DATA_CHANNEL_FLAGS_EXTERNAL_NEGOTIATED)) { - int error = SendOpenRequestMessage( - channel->mLabel, channel->mProtocol, stream, - !!(channel->mFlags & DATA_CHANNEL_FLAGS_OUT_OF_ORDER_ALLOWED), - channel->mPrPolicy, channel->mPrValue); + if (!channel->mNegotiated) { + int error = SendOpenRequestMessage(channel->mLabel, channel->mProtocol, + stream, !channel->mOrdered, + channel->mPrPolicy, channel->mPrValue); if (error) { LOG(("SendOpenRequest failed, error = %d", error)); if (channel->mFlags & DATA_CHANNEL_FLAGS_FINISH_OPEN) { // We already returned the channel to the app. NS_ERROR("Failed to send open request"); channel->AnnounceClosed(); } // If we haven't returned the channel yet, it will get destroyed when we // exit this function. - mStreams[stream] = nullptr; - channel->mStream = INVALID_STREAM; + mChannels.Remove(channel); // we'll be destroying the channel return nullptr; /* NOTREACHED */ } } // Either externally negotiated or we sent Open - channel->mFlags |= DATA_CHANNEL_FLAGS_READY; // FIX? Move into DOMDataChannel? I don't think we can send it yet here channel->AnnounceOpen(); return channel.forget(); request_error_cleanup: if (channel->mFlags & DATA_CHANNEL_FLAGS_FINISH_OPEN) { // We already returned the channel to the app. @@ -2645,35 +2609,34 @@ int DataChannelConnection::SendDataMsgIn // General flags info.sendv_flags = SCTP_SEND_SNDINFO_VALID; // Set stream identifier, protocol identifier and flags info.sendv_sndinfo.snd_sid = channel.mStream; info.sendv_sndinfo.snd_flags = SCTP_EOR; info.sendv_sndinfo.snd_ppid = htonl(ppid); + MutexAutoLock lock(mLock); // Need to protect mFlags... :( // Unordered? // To avoid problems where an in-order OPEN is lost and an // out-of-order data message "beats" it, require data to be in-order // until we get an ACK. - if ((channel.mFlags & DATA_CHANNEL_FLAGS_OUT_OF_ORDER_ALLOWED) && - !(channel.mFlags & DATA_CHANNEL_FLAGS_WAITING_ACK)) { + if (!channel.mOrdered && !(channel.mFlags & DATA_CHANNEL_FLAGS_WAITING_ACK)) { info.sendv_sndinfo.snd_flags |= SCTP_UNORDERED; } // Partial reliability policy if (channel.mPrPolicy != SCTP_PR_SCTP_NONE) { info.sendv_prinfo.pr_policy = channel.mPrPolicy; info.sendv_prinfo.pr_value = channel.mPrValue; info.sendv_flags |= SCTP_SEND_PRINFO_VALID; } // Create message instance and send OutgoingMsg msg(info, data, len); - MutexAutoLock lock(mLock); bool buffered; size_t written = 0; mDeferSend = true; int error = SendMsgInternalOrBuffer(channel.mBufferedData, msg, buffered, &written); mDeferSend = false; if (written) { channel.DecrementBufferedAmount(written); @@ -2738,17 +2701,17 @@ class ReadBlobRunnable : public Runnable RefPtr<DataChannelConnection> mConnection; uint16_t mStream; // Use RefCount for preventing the object is deleted when SendBlob returns. RefPtr<nsIInputStream> mBlob; }; // Returns a POSIX error code. int DataChannelConnection::SendBlob(uint16_t stream, nsIInputStream* aBlob) { - DataChannel* channel = mStreams[stream]; + RefPtr<DataChannel> channel = mChannels.Get(stream); if (NS_WARN_IF(!channel)) { return EINVAL; // TODO: Find a better error code } // Spawn a thread to send the data if (!mInternalIOThread) { nsresult rv = NS_NewNamedThread("DataChannel IO", getter_AddRefs(mInternalIOThread)); @@ -2824,47 +2787,37 @@ void DataChannelConnection::ReadBlob( NS_ReleaseOnMainThreadSystemGroup("DataChannelBlobSendRunnable", runnable.forget()); return; } aBlob->Close(); Dispatch(runnable.forget()); } -void DataChannelConnection::GetStreamIds(std::vector<uint16_t>* aStreamList) { - ASSERT_WEBRTC(NS_IsMainThread()); - for (uint32_t i = 0; i < mStreams.Length(); ++i) { - if (mStreams[i]) { - aStreamList->push_back(mStreams[i]->mStream); - } - } -} - // Returns a POSIX error code. int DataChannelConnection::SendDataMsgCommon(uint16_t stream, const nsACString& aMsg, bool isBinary) { ASSERT_WEBRTC(NS_IsMainThread()); // We really could allow this from other threads, so long as we deal with // asynchronosity issues with channels closing, in particular access to - // mStreams, and issues with the association closing (access to mSocket). + // mChannels, and issues with the association closing (access to mSocket). const uint8_t* data = (const uint8_t*)aMsg.BeginReading(); uint32_t len = aMsg.Length(); #if (UINT32_MAX > SIZE_MAX) if (len > SIZE_MAX) { return EMSGSIZE; } #endif - DataChannel* channelPtr; LOG(("Sending %sto stream %u: %u bytes", isBinary ? "binary " : "", stream, len)); // XXX if we want more efficiency, translate flags once at open time - channelPtr = mStreams[stream]; + RefPtr<DataChannel> channelPtr = mChannels.Get(stream); if (NS_WARN_IF(!channelPtr)) { return EINVAL; // TODO: Find a better error code } auto& channel = *channelPtr; if (isBinary) { return SendDataMsg(channel, data, len, DATA_CHANNEL_PPID_BINARY_PARTIAL, @@ -2889,34 +2842,34 @@ void DataChannelConnection::Close(DataCh // Called from someone who holds a ref via ::Close(), or from ~DataChannel void DataChannelConnection::CloseInt(DataChannel* aChannel) { MOZ_ASSERT(aChannel); RefPtr<DataChannel> channel(aChannel); // make sure it doesn't go away on us mLock.AssertCurrentThreadOwns(); LOG(("Connection %p/Channel %p: Closing stream %u", channel->mConnection.get(), channel.get(), channel->mStream)); + + aChannel->mBufferedData.Clear(); + if (mState == CLOSED) { + // If we're CLOSING, we might leave this in place until we can send a + // reset. + mChannels.Remove(channel); + } + // re-test since it may have closed before the lock was grabbed if (aChannel->mReadyState == CLOSED || aChannel->mReadyState == CLOSING) { LOG(("Channel already closing/closed (%u)", aChannel->mReadyState)); - if (mState == CLOSED && channel->mStream != INVALID_STREAM) { - // called from CloseAll() - // we're not going to hang around waiting any more - mStreams[channel->mStream] = nullptr; - } return; } - aChannel->mBufferedData.Clear(); + if (channel->mStream != INVALID_STREAM) { ResetOutgoingStream(channel->mStream); - if (mState == CLOSED) { // called from CloseAll() - // Let resets accumulate then send all at once in CloseAll() - // we're not going to hang around waiting - mStreams[channel->mStream] = nullptr; - } else { + if (mState != CLOSED) { + // Individual channel is being closed, send reset now. SendOutgoingStreamReset(); } } aChannel->mReadyState = CLOSING; if (mState == CLOSED) { // we're not going to hang around waiting channel->StreamClosedLocked(); } @@ -2932,39 +2885,99 @@ void DataChannelConnection::CloseAll() { { MutexAutoLock lock(mLock); mState = CLOSED; } // Close current channels // If there are runnables, they hold a strong ref and keep the channel // and/or connection alive (even if in a CLOSED state) - bool closed_some = false; - for (uint32_t i = 0; i < mStreams.Length(); ++i) { - if (mStreams[i]) { - mStreams[i]->Close(); - closed_some = true; - } + for (auto& channel : mChannels.GetAll()) { + channel->Close(); } // Clean up any pending opens for channels RefPtr<DataChannel> channel; while (nullptr != (channel = dont_AddRef( static_cast<DataChannel*>(mPending.PopFront())))) { LOG(("closing pending channel %p, stream %u", channel.get(), channel->mStream)); channel->Close(); // also releases the ref on each iteration - closed_some = true; } // It's more efficient to let the Resets queue in shutdown and then // SendOutgoingStreamReset() here. - if (closed_some) { - MutexAutoLock lock(mLock); - SendOutgoingStreamReset(); + MutexAutoLock lock(mLock); + SendOutgoingStreamReset(); +} + +bool DataChannelConnection::Channels::IdComparator::Equals( + const RefPtr<DataChannel>& aChannel, uint16_t aId) const { + return aChannel->mStream == aId; +} + +bool DataChannelConnection::Channels::IdComparator::LessThan( + const RefPtr<DataChannel>& aChannel, uint16_t aId) const { + return aChannel->mStream < aId; +} + +bool DataChannelConnection::Channels::IdComparator::Equals( + const RefPtr<DataChannel>& a1, const RefPtr<DataChannel>& a2) const { + return Equals(a1, a2->mStream); +} + +bool DataChannelConnection::Channels::IdComparator::LessThan( + const RefPtr<DataChannel>& a1, const RefPtr<DataChannel>& a2) const { + return LessThan(a1, a2->mStream); +} + +void DataChannelConnection::Channels::Insert( + const RefPtr<DataChannel>& aChannel) { + LOG(("Inserting channel %u : %p", aChannel->mStream, aChannel.get())); + MutexAutoLock lock(mMutex); + if (aChannel->mStream != INVALID_STREAM) { + MOZ_ASSERT(!mChannels.ContainsSorted(aChannel, IdComparator())); } + + MOZ_ASSERT(!mChannels.Contains(aChannel)); + + mChannels.InsertElementSorted(aChannel, IdComparator()); +} + +bool DataChannelConnection::Channels::Remove( + const RefPtr<DataChannel>& aChannel) { + LOG(("Removing channel %u : %p", aChannel->mStream, aChannel.get())); + MutexAutoLock lock(mMutex); + if (aChannel->mStream == INVALID_STREAM) { + return mChannels.RemoveElement(aChannel); + } + + return mChannels.RemoveElementSorted(aChannel, IdComparator()); +} + +RefPtr<DataChannel> DataChannelConnection::Channels::Get(uint16_t aId) const { + MutexAutoLock lock(mMutex); + auto index = mChannels.BinaryIndexOf(aId, IdComparator()); + if (index == ChannelArray::NoIndex) { + return nullptr; + } + return mChannels[index]; +} + +RefPtr<DataChannel> DataChannelConnection::Channels::GetNextChannel( + uint16_t aCurrentId) const { + MutexAutoLock lock(mMutex); + if (mChannels.IsEmpty()) { + return nullptr; + } + + auto index = mChannels.IndexOfFirstElementGt(aCurrentId, IdComparator()); + if (index == mChannels.Length()) { + index = 0; + } + return mChannels[index]; } DataChannel::~DataChannel() { // NS_ASSERTION since this is more "I think I caught all the cases that // can cause this" than a true kill-the-program assertion. If this is // wrong, nothing bad happens. A worst it's a leak. NS_ASSERTION(mReadyState == CLOSED || mReadyState == CLOSING, "unexpected state in ~DataChannel"); @@ -2981,18 +2994,16 @@ void DataChannel::Close() { // Used when disconnecting from the DataChannelConnection void DataChannel::StreamClosedLocked() { mConnection->mLock.AssertCurrentThreadOwns(); ENSURE_DATACONNECTION; LOG(("Destroying Data channel %u", mStream)); MOZ_ASSERT_IF(mStream != INVALID_STREAM, !mConnection->FindChannelByStream(mStream)); - // Spec doesn't say to mess with the stream id... - mStream = INVALID_STREAM; AnnounceClosed(); // We leave mConnection live until the DOM releases us, to avoid races } void DataChannel::ReleaseConnection() { ASSERT_WEBRTC(NS_IsMainThread()); mConnection = nullptr; }
--- a/netwerk/sctp/datachannel/DataChannel.h +++ b/netwerk/sctp/datachannel/DataChannel.h @@ -159,17 +159,17 @@ class DataChannelConnection final : publ #endif #ifdef SCTP_DTLS_SUPPORTED bool ConnectToTransport(const std::string& aTransportId, bool aClient, uint16_t localport, uint16_t remoteport); void TransportStateChange(const std::string& aTransportId, TransportLayer::State aState); void CompleteConnect(); - void SetSignals(const std::string& aTransportId, bool aClient); + void SetSignals(const std::string& aTransportId); #endif typedef enum { RELIABLE = 0, PARTIAL_RELIABLE_REXMIT = 1, PARTIAL_RELIABLE_TIMED = 2 } Type; @@ -211,18 +211,16 @@ class DataChannelConnection final : publ } friend class DataChannel; Mutex mLock; void ReadBlob(already_AddRefed<DataChannelConnection> aThis, uint16_t aStream, nsIInputStream* aBlob); - void GetStreamIds(std::vector<uint16_t>* aStreamList); - bool SendDeferredMessages(); protected: friend class DataChannelOnMessageAvailable; // Avoid cycles with PeerConnectionImpl // Use from main thread only as WeakPtr is not threadsafe WeakPtr<DataConnectionListener> mListener; @@ -305,35 +303,66 @@ class DataChannelConnection final : publ bool on = false; if (mSTS) { mSTS->IsOnCurrentThread(&on); } return on; } #endif + class Channels { + public: + Channels() : mMutex("DataChannelConnection::Channels::mMutex") {} + void Insert(const RefPtr<DataChannel>& aChannel); + bool Remove(const RefPtr<DataChannel>& aChannel); + RefPtr<DataChannel> Get(uint16_t aId) const; + typedef AutoTArray<RefPtr<DataChannel>, 16> ChannelArray; + ChannelArray GetAll() const { + MutexAutoLock lock(mMutex); + return mChannels; + } + RefPtr<DataChannel> GetNextChannel(uint16_t aCurrentId) const; + + private: + struct IdComparator { + bool Equals(const RefPtr<DataChannel>& aChannel, uint16_t aId) const; + bool LessThan(const RefPtr<DataChannel>& aChannel, uint16_t aId) const; + bool Equals(const RefPtr<DataChannel>& a1, + const RefPtr<DataChannel>& a2) const; + bool LessThan(const RefPtr<DataChannel>& a1, + const RefPtr<DataChannel>& a2) const; + }; + mutable Mutex mMutex; + ChannelArray mChannels; + }; + bool mSendInterleaved = false; bool mMaxMessageSizeSet = false; uint64_t mMaxMessageSize = 0; - bool mAllocateEven = false; + // Main thread only + Maybe<bool> mAllocateEven; // Data: - // NOTE: while this array will auto-expand, increases in the number of + // NOTE: while this container will auto-expand, increases in the number of // channels available from the stack must be negotiated! - AutoTArray<RefPtr<DataChannel>, 16> mStreams; + // Accessed from both main and sts, API is threadsafe + Channels mChannels; + // STS only uint32_t mCurrentStream = 0; nsDeque mPending; // Holds addref'ed DataChannel's -- careful! + // STS and main + size_t mNegotiatedIdLimit = 0; // GUARDED_BY(mConnection->mLock) uint8_t mPendingType = PENDING_NONE; // holds data that's come in before a channel is open nsTArray<nsAutoPtr<QueuedDataMessage>> mQueuedData; // holds outgoing control messages nsTArray<nsAutoPtr<BufferedOutgoingMsg>> mBufferedControl; // GUARDED_BY(mConnection->mLock) - // Streams pending reset - AutoTArray<uint16_t, 4> mStreamsResetting; + // Streams pending reset. Accessed from main and STS. + AutoTArray<uint16_t, 4> mStreamsResetting; // GUARDED_BY(mConnection->mLock) // accessed from STS thread struct socket* mMasterSocket = nullptr; // cloned from mMasterSocket on successful Connect on STS thread struct socket* mSocket = nullptr; uint16_t mState = CLOSED; // Protected with mLock #ifdef SCTP_DTLS_SUPPORTED std::string mTransportId; @@ -380,27 +409,20 @@ class DataChannel { mProtocol(protocol), mReadyState(state), mStream(stream), mPrPolicy(policy), mPrValue(value), mNegotiated(negotiated), mOrdered(ordered), mFlags(0), - mId(0), mIsRecvBinary(false), mBufferedThreshold(0), // default from spec mBufferedAmount(0), mMainThreadEventTarget(connection->GetNeckoTarget()) { - if (!ordered) { - mFlags |= DATA_CHANNEL_FLAGS_OUT_OF_ORDER_ALLOWED; - } - if (negotiated) { - mFlags |= DATA_CHANNEL_FLAGS_EXTERNAL_NEGOTIATED; - } NS_ASSERTION(mConnection, "NULL connection"); } private: ~DataChannel(); public: NS_INLINE_DECL_THREADSAFE_REFCOUNTING(DataChannel) @@ -488,20 +510,20 @@ class DataChannel { RefPtr<DataChannelConnection> mConnection; nsCString mLabel; nsCString mProtocol; // This is mainthread only uint16_t mReadyState; uint16_t mStream; uint16_t mPrPolicy; uint32_t mPrValue; + // Accessed on main and STS const bool mNegotiated; const bool mOrdered; uint32_t mFlags; - uint32_t mId; bool mIsRecvBinary; size_t mBufferedThreshold; // Read/written on main only. Decremented via message-passing, because the // spec requires us to queue a task for this. size_t mBufferedAmount; nsCString mRecvBuffer; nsTArray<nsAutoPtr<BufferedOutgoingMsg>> mBufferedData; // GUARDED_BY(mConnection->mLock)
--- a/netwerk/sctp/datachannel/DataChannelProtocol.h +++ b/netwerk/sctp/datachannel/DataChannelProtocol.h @@ -31,20 +31,17 @@ #define DATA_CHANNEL_PPID_CONTROL 50 #define DATA_CHANNEL_PPID_BINARY_PARTIAL 52 #define DATA_CHANNEL_PPID_BINARY 53 #define DATA_CHANNEL_PPID_DOMSTRING_PARTIAL 54 #define DATA_CHANNEL_PPID_DOMSTRING 51 #define DATA_CHANNEL_MAX_BINARY_FRAGMENT 0x4000 -#define DATA_CHANNEL_FLAGS_READY 0x00000001 -#define DATA_CHANNEL_FLAGS_OUT_OF_ORDER_ALLOWED 0x00000002 #define DATA_CHANNEL_FLAGS_FINISH_OPEN 0x00000004 -#define DATA_CHANNEL_FLAGS_EXTERNAL_NEGOTIATED 0x00000008 #define DATA_CHANNEL_FLAGS_WAITING_ACK 0x00000010 #define DATA_CHANNEL_FLAGS_CLOSING_TOO_LARGE 0x00000020 #define DATA_CHANNEL_BUFFER_MESSAGE_FLAGS_TOO_LARGE 0x01 #define DATA_CHANNEL_BUFFER_MESSAGE_FLAGS_BUFFERED 0x02 #define DATA_CHANNEL_BUFFER_MESSAGE_FLAGS_COMPLETE 0x04 #define INVALID_STREAM (0xFFFF)
--- a/taskcluster/ci/test/web-platform.yml +++ b/taskcluster/ci/test/web-platform.yml @@ -53,17 +53,16 @@ web-platform-tests: default: 12 max-run-time: by-test-platform: .*-ccov/debug: 10800 default: 7200 e10s: true run-on-projects: by-test-platform: - android.*: ['mozilla-central', 'try'] windows10-aarch64/opt: ['try', 'mozilla-central'] .*-qr/.*: ['release', 'try'] # skip on integration branches due to high load default: built-projects tier: by-test-platform: android.*: 2 linux64-asan/opt: 2 windows10-aarch64.*: 2 @@ -111,24 +110,21 @@ web-platform-tests-reftests: linux64(-shippable|-devedition)?/opt: 3 macosx10(10|14)-64/debug: 6 windows.*-(32|64)(-qr)?/debug: 5 android.*: 6 default: 4 e10s: true run-on-projects: by-test-platform: - android.*: ['mozilla-central', 'try'] windows10-aarch64/opt: ['try', 'mozilla-central'] default: built-projects tier: by-test-platform: - android.*-x86_64/opt: 3 - android.*-x86_64/debug: 3 - android.*x86(?!x86_64)/opt: 2 + android.*: 2 linux64-asan/opt: 2 windows10-aarch64.*: 2 default: default mozharness: extra-options: - --test-type=reftest web-platform-tests-reftests-headless: @@ -162,17 +158,16 @@ web-platform-tests-wdspec: .*-ccov/debug: 4 default: 2 mozharness: extra-options: - --test-type=wdspec run-on-projects: by-test-platform: windows10-aarch64/opt: ['try', 'mozilla-central'] - android.*: ['mozilla-central', 'try'] .*-qr/.*: ['release', 'try'] default: built-projects tier: by-test-platform: android.*: 3 linux64-asan/opt: 2 .*-qr/.*: 2 # can't be tier-1 if it's not running on integration branches default: default
--- a/taskcluster/taskgraph/target_tasks.py +++ b/taskcluster/taskgraph/target_tasks.py @@ -302,16 +302,22 @@ def target_tasks_mozilla_esr68(full_task def filter(task): if not filter_release_tasks(task, parameters): return False if not standard_filter(task, parameters): return False + platform = task.attributes.get('test_platform') + + # Don't run QuantumRender tests on esr68. + if platform and '-qr/' in platform: + return False + # Unlike esr60, we do want all kinds of fennec builds on esr68. return True return [l for l, t in full_task_graph.tasks.iteritems() if filter(t)] @_target_task('promote_desktop')
deleted file mode 100644 --- a/testing/web-platform/meta/css/css-backgrounds/background-image-first-line.html.ini +++ /dev/null @@ -1,2 +0,0 @@ -[background-image-first-line.html] - expected: FAIL
new file mode 100644 --- /dev/null +++ b/testing/web-platform/meta/css/css-multicol/multicol-breaking-004.html.ini @@ -0,0 +1,2 @@ +[multicol-breaking-004.html] + prefs: [layout.css.column-span.enabled:true]
--- a/testing/web-platform/meta/css/css-multicol/multicol-fill-auto-block-children-001.xht.ini +++ b/testing/web-platform/meta/css/css-multicol/multicol-fill-auto-block-children-001.xht.ini @@ -1,2 +1,2 @@ [multicol-fill-auto-block-children-001.xht] - expected: FAIL + prefs: [layout.css.column-span.enabled:true]
--- a/testing/web-platform/meta/css/css-multicol/multicol-fill-auto-block-children-002.xht.ini +++ b/testing/web-platform/meta/css/css-multicol/multicol-fill-auto-block-children-002.xht.ini @@ -1,2 +1,2 @@ [multicol-fill-auto-block-children-002.xht] - expected: FAIL + prefs: [layout.css.column-span.enabled:true]
new file mode 100644 --- /dev/null +++ b/testing/web-platform/meta/css/css-multicol/multicol-span-all-children-height-002.html.ini @@ -0,0 +1,2 @@ +[multicol-span-all-children-height-002.html] + prefs: [layout.css.column-span.enabled:true]
new file mode 100644 --- /dev/null +++ b/testing/web-platform/meta/css/css-multicol/multicol-span-all-children-height-003.html.ini @@ -0,0 +1,2 @@ +[multicol-span-all-children-height-003.html] + prefs: [layout.css.column-span.enabled:true]
--- a/testing/web-platform/meta/css/geometry/DOMMatrix-001.html.ini +++ b/testing/web-platform/meta/css/geometry/DOMMatrix-001.html.ini @@ -1,79 +1,10 @@ [DOMMatrix-001.html] - [testConstructor0] - expected: FAIL - - [testConstructor1] - expected: FAIL - - [testConstructor2] - expected: FAIL - - [testConstructor3] - expected: FAIL - - [testConstructor4] - expected: FAIL - - [testConstructor5] - expected: FAIL - - [testConstructor6] - expected: FAIL - - [testConstructor7] - expected: FAIL - - [testConstructor8] - expected: FAIL - - [testConstructor9] - expected: FAIL - - [testConstructor10] - expected: FAIL - - [testConstructor12] - expected: FAIL - - [testConstructor13] - expected: FAIL - [new DOMMatrix(undefined)] expected: FAIL - [new DOMMatrix(float32Array) 16 elements] - expected: FAIL - - [new DOMMatrix(float64Array) 16 elements] - expected: FAIL - - [new DOMMatrix(sequence) 16 elements] - expected: FAIL - - [new DOMMatrix(sequence)] - expected: FAIL - - [new DOMMatrix(matrix)] - expected: FAIL - - [new DOMMatrixReadOnly(float32Array) 16 elements] - expected: FAIL - - [new DOMMatrixReadOnly(float64Array) 16 elements] - expected: FAIL - - [new DOMMatrixReadOnly(sequence) 16 elements] - expected: FAIL - - [new DOMMatrixReadOnly(sequence)] - expected: FAIL - - [new DOMMatrixReadOnly(matrix)] - expected: FAIL - [new DOMMatrix("scale(2) translateX(5px) translateY(5px) rotate(5deg) rotate(-5deg)")] expected: FAIL <