diff --git a/.arcconfig b/.arcconfig --- a/.arcconfig +++ b/.arcconfig @@ -1,5 +1,5 @@ { - "phabricator.uri" : "https://phabricator.services.mozilla.com/", + "phabricator.uri" : "https://phabricator-dev.allizom.org/", "repository.callsign": "MOZILLACENTRAL", "history.immutable": false } diff --git a/.eslintignore b/.eslintignore --- a/.eslintignore +++ b/.eslintignore @@ -261,10 +261,11 @@ dom/u2f/** dom/url/** dom/vr/** dom/webauthn/** dom/webbrowserpersist/** +dom/webgpu/** dom/webidl/** dom/websocket/** dom/workers/** dom/worklet/** dom/xbl/** diff --git a/accessible/ipc/win/HandlerProvider.cpp b/accessible/ipc/win/HandlerProvider.cpp --- a/accessible/ipc/win/HandlerProvider.cpp +++ b/accessible/ipc/win/HandlerProvider.cpp @@ -162,37 +162,10 @@ *aOutPayloadSize = mSerializer->GetSize(); return S_OK; } -template -class MOZ_RAII ExecuteWhen final -{ -public: - ExecuteWhen(CondFnT& aCondFn, ExeFnT& aExeFn) - : mCondFn(aCondFn) - , mExeFn(aExeFn) - { - } - - ~ExecuteWhen() - { - if (mCondFn()) { - mExeFn(); - } - } - - ExecuteWhen(const ExecuteWhen&) = delete; - ExecuteWhen(ExecuteWhen&&) = delete; - ExecuteWhen& operator=(const ExecuteWhen&) = delete; - ExecuteWhen& operator=(ExecuteWhen&&) = delete; - -private: - CondFnT& mCondFn; - ExeFnT& mExeFn; -}; - void HandlerProvider::BuildStaticIA2Data( NotNull aInterceptor, StaticIA2Data* aOutData) { @@ -276,11 +249,12 @@ auto cleanup = [this, aOutIA2Data]() -> void { CleanupDynamicIA2Data(*aOutIA2Data); }; - ExecuteWhen onFail(hasFailed, cleanup); + mscom::ExecuteWhen + onFail(hasFailed, cleanup); const VARIANT kChildIdSelf = {VT_I4}; VARIANT varVal; hr = target->accLocation(&aOutIA2Data->mLeft, &aOutIA2Data->mTop, diff --git a/accessible/windows/msaa/Compatibility.cpp b/accessible/windows/msaa/Compatibility.cpp --- a/accessible/windows/msaa/Compatibility.cpp +++ b/accessible/windows/msaa/Compatibility.cpp @@ -93,11 +93,12 @@ //////////////////////////////////////////////////////////////////////////////// // Compatibility //////////////////////////////////////////////////////////////////////////////// static WindowsDllInterceptor sUser32Interceptor; -static decltype(&InSendMessageEx) sInSendMessageExStub = nullptr; +static WindowsDllInterceptor::FuncHookType + sInSendMessageExStub; static bool sInSendMessageExHackEnabled = false; static PVOID sVectoredExceptionHandler = nullptr; #if defined(_MSC_VER) #include @@ -265,15 +266,13 @@ // InSendMessageEx compatibility hack. NVDA does not require this. // We also skip UIA, as we see crashes there. if ((sConsumers & (~(UIAUTOMATION | NVDA))) && BrowserTabsRemoteAutostart()) { sUser32Interceptor.Init("user32.dll"); - if (!sInSendMessageExStub) { - sUser32Interceptor.AddHook("InSendMessageEx", - reinterpret_cast(&InSendMessageExHook), - (void**)&sInSendMessageExStub); - } + sInSendMessageExStub.Set(sUser32Interceptor, "InSendMessageEx", + &InSendMessageExHook); + // The vectored exception handler allows us to catch exceptions ahead of any // SEH handlers. if (!sVectoredExceptionHandler) { // We need to let ASan's ShadowExceptionHandler remain in the firstHandler // position, otherwise we'll get infinite recursion when our handler diff --git a/browser/app/profile/firefox.js b/browser/app/profile/firefox.js --- a/browser/app/profile/firefox.js +++ b/browser/app/profile/firefox.js @@ -1412,12 +1412,12 @@ pref("identity.fxaccounts.migrateToDevEdition", true); #else pref("identity.fxaccounts.migrateToDevEdition", false); #endif -// If activated, send tab will use the new FxA messages backend. -pref("identity.fxaccounts.messages.enabled", false); +// If activated, send tab will use the new FxA commands backend. +pref("identity.fxaccounts.commands.enabled", false); // On GTK, we now default to showing the menubar only when alt is pressed: #ifdef MOZ_WIDGET_GTK pref("ui.key.menuAccessKeyFocuses", true); #endif diff --git a/browser/app/winlauncher/DllBlocklistWin.cpp b/browser/app/winlauncher/DllBlocklistWin.cpp --- a/browser/app/winlauncher/DllBlocklistWin.cpp +++ b/browser/app/winlauncher/DllBlocklistWin.cpp @@ -244,11 +244,12 @@ return true; } typedef decltype(&NtMapViewOfSection) NtMapViewOfSection_func; -static NtMapViewOfSection_func stub_NtMapViewOfSection; +static mozilla::CrossProcessDllInterceptor::FuncHookType + stub_NtMapViewOfSection; static NTSTATUS NTAPI patched_NtMapViewOfSection(HANDLE aSection, HANDLE aProcess, PVOID* aBaseAddress, ULONG_PTR aZeroBits, SIZE_T aCommitSize, PLARGE_INTEGER aSectionOffset, PSIZE_T aViewSize, @@ -354,13 +355,12 @@ bool InitializeDllBlocklistOOP(HANDLE aChildProcess) { mozilla::CrossProcessDllInterceptor intcpt(aChildProcess); intcpt.Init(L"ntdll.dll"); - bool ok = intcpt.AddDetour("NtMapViewOfSection", - reinterpret_cast(&patched_NtMapViewOfSection), - (void**) &stub_NtMapViewOfSection); + bool ok = stub_NtMapViewOfSection.SetDetour(intcpt, "NtMapViewOfSection", + &patched_NtMapViewOfSection); if (!ok) { return false; } // Set the child process's copy of stub_NtMapViewOfSection diff --git a/browser/base/content/aboutNetError-new.xhtml b/browser/base/content/aboutNetError-new.xhtml --- a/browser/base/content/aboutNetError-new.xhtml +++ b/browser/base/content/aboutNetError-new.xhtml @@ -19,11 +19,11 @@ &loadError.label; - + diff --git a/browser/base/content/aboutNetError.js b/browser/base/content/aboutNetError.js --- a/browser/base/content/aboutNetError.js +++ b/browser/base/content/aboutNetError.js @@ -176,14 +176,16 @@ sd.textContent = getDescription(); } } if (showCaptivePortalUI) { initPageCaptivePortal(); + updateContainerPosition(); return; } if (gIsCertError) { initPageCertError(); + updateContainerPosition(); return; } addAutofocus("errorTryAgain"); document.body.classList.add("neterror"); @@ -291,10 +293,16 @@ var container = document.getElementById("errorLongDesc"); for (var span of container.querySelectorAll("span.hostname")) { span.textContent = document.location.hostname; } } + updateContainerPosition(); +} + +function updateContainerPosition() { + let textContainer = document.getElementById("text-container"); + textContainer.style.marginTop = `calc(50vh - ${textContainer.clientHeight / 2}px)`; } function initPageCaptivePortal() { document.body.className = "captiveportal"; document.getElementById("openPortalLoginPageButton") diff --git a/browser/base/content/browser-sync.js b/browser/base/content/browser-sync.js --- a/browser/base/content/browser-sync.js +++ b/browser/base/content/browser-sync.js @@ -325,28 +325,43 @@ devices = await fxAccounts.getDeviceList(); } catch (e) { console.error("Could not get the FxA device list", e); devices = []; // We can still run in degraded mode. } - const toSendMessages = []; + const fxaCommandsDevices = []; + const oldSendTabClients = []; for (const client of clients) { const device = devices.find(d => d.id == client.fxaDeviceId); - if (device && fxAccounts.messages.canReceiveSendTabMessages(device)) { - toSendMessages.push(device); + if (!device) { + console.error(`Could not find associated FxA device for ${client.name}`); + continue; + } else if ((await fxAccounts.commands.sendTab.isDeviceCompatible(device))) { + fxaCommandsDevices.push(device); } else { - try { - await Weave.Service.clientsEngine.sendURIToClientForDisplay(url, client.id, title); - } catch (e) { - console.error("Could not send tab to device", e); + oldSendTabClients.push(client); + } + } + if (fxaCommandsDevices.length) { + console.log(`Sending a tab to ${fxaCommandsDevices.map(d => d.name).join(", ")} using FxA commands.`); + const report = await fxAccounts.commands.sendTab.send(fxaCommandsDevices, {url, title}); + for (let {device, error} of report.failed) { + console.error(`Failed to send a tab with FxA commands for ${device.name}. + Falling back on the Sync back-end`, error); + const client = clients.find(c => c.fxaDeviceId == device.id); + if (!client) { + console.error(`Could not find associated Sync device for ${device.name}`); + continue; } + oldSendTabClients.push(client); } } - if (toSendMessages.length) { + for (let client of oldSendTabClients) { try { - await fxAccounts.messages.sendTab(toSendMessages, {url, title}); + console.log(`Sending a tab to ${client.name} using Sync.`); + await Weave.Service.clientsEngine.sendURIToClientForDisplay(url, client.id, title); } catch (e) { - console.error("Could not send tab to device", e); + console.error("Could not send tab to device.", e); } } }, populateSendTabToDevicesMenu(devicesPopup, url, title, createDeviceNodeFn) { @@ -589,11 +604,17 @@ return; } const state = UIState.get(); if (state.status == UIState.STATUS_SIGNED_IN) { this.updateSyncStatus({ syncing: true }); - Services.tm.dispatchToMainThread(() => Weave.Service.sync()); + Services.tm.dispatchToMainThread(() => { + // We are pretty confident that push helps us pick up all FxA commands, + // but some users might have issues with push, so let's unblock them + // by fetching the missed FxA commands on manual sync. + fxAccounts.commands.fetchMissedRemoteCommands(); + Weave.Service.sync(); + }); } }, openPrefs(entryPoint = "syncbutton", origin = undefined) { window.openPreferences("paneSync", { origin, urlParams: { entrypoint: entryPoint } }); diff --git a/browser/base/content/hiddenWindow.xul b/browser/base/content/hiddenWindow.xul --- a/browser/base/content/hiddenWindow.xul +++ b/browser/base/content/hiddenWindow.xul @@ -3,11 +3,10 @@ # # 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/. -#ifdef XP_MACOSX @@ -29,7 +28,5 @@ id="macDockMenuNewPrivateWindow" /> - -#endif diff --git a/browser/base/content/nsContextMenu.js b/browser/base/content/nsContextMenu.js --- a/browser/base/content/nsContextMenu.js +++ b/browser/base/content/nsContextMenu.js @@ -993,16 +993,26 @@ mm.sendAsyncMessage("ContextMenu:SaveVideoFrameAsImage", {}, { target: this.target, }); + // Cache this because we fetch the data async + let {documentURIObject} = gContextMenuContentData; + let onMessage = (message) => { mm.removeMessageListener("ContextMenu:SaveVideoFrameAsImage:Result", onMessage); + // FIXME can we switch this to a blob URL? let dataURL = message.data.dataURL; - saveImageURL(dataURL, name, "SaveImageTitle", true, false, - document.documentURIObject, null, null, null, - isPrivate); + saveImageURL(dataURL, name, "SaveImageTitle", + true, // bypass cache + false, // don't skip prompt for where to save + documentURIObject, // referrer + null, // document + null, // content type + null, // content disposition + isPrivate, + this.principal); }; mm.addMessageListener("ContextMenu:SaveVideoFrameAsImage:Result", onMessage); }, leaveDOMFullScreen() { @@ -1236,17 +1246,19 @@ if (this.onCanvas) { // Bypass cache, since it's a data: URL. this._canvasToBlobURL(this.target).then(function(blobURL) { saveImageURL(blobURL, "canvas.png", "SaveImageTitle", true, false, referrerURI, null, null, null, - isPrivate); + isPrivate, + document.nodePrincipal /* system, because blob: */); }, Cu.reportError); } else if (this.onImage) { urlSecurityCheck(this.mediaURL, this.principal); saveImageURL(this.mediaURL, null, "SaveImageTitle", false, false, referrerURI, null, gContextMenuContentData.contentType, - gContextMenuContentData.contentDisposition, isPrivate); + gContextMenuContentData.contentDisposition, isPrivate, + this.principal); } else if (this.onVideo || this.onAudio) { var dialogTitle = this.onVideo ? "SaveVideoTitle" : "SaveAudioTitle"; this.saveHelper(this.mediaURL, null, dialogTitle, false, doc, referrerURI, this.frameOuterWindowID, "", isContentWindowPrivate); } diff --git a/browser/base/content/pageinfo/pageInfo.js b/browser/base/content/pageinfo/pageInfo.js --- a/browser/base/content/pageinfo/pageInfo.js +++ b/browser/base/content/pageinfo/pageInfo.js @@ -687,11 +687,12 @@ selectSaveFolder(function(aDirectory) { if (aDirectory) { var saveAnImage = function(aURIString, aChosenData, aBaseURI) { uniqueFile(aChosenData.file); internalSave(aURIString, null, null, null, null, false, "SaveImageTitle", - aChosenData, aBaseURI, null, false, null, gDocInfo.isContentWindowPrivate); + aChosenData, aBaseURI, null, false, null, + gDocInfo.isContentWindowPrivate, gDocInfo.principal); }; for (var i = 0; i < rowArray.length; i++) { let v = rowArray[i]; let dir = aDirectory.clone(); diff --git a/browser/base/content/tab-content.js b/browser/base/content/tab-content.js --- a/browser/base/content/tab-content.js +++ b/browser/base/content/tab-content.js @@ -227,10 +227,11 @@ var ContentSearchMediator = { whitelist: new Set([ "about:home", "about:newtab", + "about:welcome", ]), init(chromeGlobal) { chromeGlobal.addEventListener("ContentSearchClient", this, true, true); addMessageListener("ContentSearch", this); diff --git a/browser/base/content/utilityOverlay.js b/browser/base/content/utilityOverlay.js --- a/browser/base/content/utilityOverlay.js +++ b/browser/base/content/utilityOverlay.js @@ -303,11 +303,12 @@ if (where == "save") { // TODO(1073187): propagate referrerPolicy. // ContentClick.jsm passes isContentWindowPrivate for saveURL instead of passing a CPOW initiatingDoc if ("isContentWindowPrivate" in params) { - saveURL(url, null, null, true, true, aNoReferrer ? null : aReferrerURI, null, params.isContentWindowPrivate); + saveURL(url, null, null, true, true, aNoReferrer ? null : aReferrerURI, + null, params.isContentWindowPrivate, aPrincipal); } else { if (!aInitiatingDoc) { Cu.reportError("openUILink/openLinkIn was called with " + "where == 'save' but without initiatingDoc. See bug 814264."); return; diff --git a/browser/base/jar.mn b/browser/base/jar.mn --- a/browser/base/jar.mn +++ b/browser/base/jar.mn @@ -98,12 +98,14 @@ * content/browser/urlbarBindings.xml (content/urlbarBindings.xml) content/browser/utilityOverlay.js (content/utilityOverlay.js) content/browser/webext-panels.js (content/webext-panels.js) * content/browser/webext-panels.xul (content/webext-panels.xul) content/browser/nsContextMenu.js (content/nsContextMenu.js) +#ifdef XP_MACOSX # XXX: We should exclude this one as well (bug 71895) * content/browser/hiddenWindow.xul (content/hiddenWindow.xul) +#endif #ifndef XP_MACOSX * content/browser/webrtcIndicator.xul (content/webrtcIndicator.xul) content/browser/webrtcIndicator.js (content/webrtcIndicator.js) #endif # the following files are browser-specific overrides diff --git a/browser/components/downloads/DownloadsCommon.jsm b/browser/components/downloads/DownloadsCommon.jsm --- a/browser/components/downloads/DownloadsCommon.jsm +++ b/browser/components/downloads/DownloadsCommon.jsm @@ -61,11 +61,10 @@ const kDownloadsStringsRequiringFormatting = { sizeWithUnits: true, statusSeparator: true, statusSeparatorBeforeNumber: true, - fileExecutableSecurityWarning: true }; const kDownloadsStringsRequiringPluralForm = { otherDownloads3: true }; diff --git a/browser/components/nsBrowserGlue.js b/browser/components/nsBrowserGlue.js --- a/browser/components/nsBrowserGlue.js +++ b/browser/components/nsBrowserGlue.js @@ -484,11 +484,11 @@ data = JSON.parse(data); if (data.isLocalDevice) { this._onDeviceDisconnected(); } break; - case "fxaccounts:messages:display-tabs": + case "fxaccounts:commands:open-uri": case "weave:engine:clients:display-uris": this._onDisplaySyncURIs(subject); break; case "session-save": this._setPrefToSaveSession(true); @@ -635,11 +635,11 @@ os.addObserver(this, "weave:service:ready"); os.addObserver(this, "fxaccounts:onverified"); os.addObserver(this, "fxaccounts:device_connected"); os.addObserver(this, "fxaccounts:verify_login"); os.addObserver(this, "fxaccounts:device_disconnected"); - os.addObserver(this, "fxaccounts:messages:display-tabs"); + os.addObserver(this, "fxaccounts:commands:open-uri"); os.addObserver(this, "weave:engine:clients:display-uris"); os.addObserver(this, "session-save"); os.addObserver(this, "places-init-complete"); os.addObserver(this, "distribution-customization-complete"); os.addObserver(this, "handle-xul-text-link"); @@ -678,11 +678,11 @@ os.removeObserver(this, "weave:service:ready"); os.removeObserver(this, "fxaccounts:onverified"); os.removeObserver(this, "fxaccounts:device_connected"); os.removeObserver(this, "fxaccounts:verify_login"); os.removeObserver(this, "fxaccounts:device_disconnected"); - os.removeObserver(this, "fxaccounts:messages:display-tabs"); + os.removeObserver(this, "fxaccounts:commands:open-uri"); os.removeObserver(this, "weave:engine:clients:display-uris"); os.removeObserver(this, "session-save"); if (this._bookmarksBackupIdleTime) { this._idleService.removeIdleObserver(this, this._bookmarksBackupIdleTime); delete this._bookmarksBackupIdleTime; diff --git a/browser/components/shell/nsMacShellService.cpp b/browser/components/shell/nsMacShellService.cpp --- a/browser/components/shell/nsMacShellService.cpp +++ b/browser/components/shell/nsMacShellService.cpp @@ -151,11 +151,11 @@ nsCOMPtr docShell = do_QueryInterface(container); if (docShell) { loadContext = do_QueryInterface(docShell); } - return wbp->SaveURI(imageURI, 0, + return wbp->SaveURI(imageURI, aElement->NodePrincipal(), 0, docURI, aElement->OwnerDoc()->GetReferrerPolicy(), nullptr, nullptr, mBackgroundFile, loadContext); } diff --git a/browser/extensions/pdfjs/README.mozilla b/browser/extensions/pdfjs/README.mozilla --- a/browser/extensions/pdfjs/README.mozilla +++ b/browser/extensions/pdfjs/README.mozilla @@ -1,5 +1,5 @@ This is the PDF.js project output, https://github.com/mozilla/pdf.js -Current extension version is: 2.0.641 +Current extension version is: 2.0.661 -Taken from upstream commit: 6fa2c779 +Taken from upstream commit: 42922c9a diff --git a/browser/extensions/pdfjs/content/build/pdf.js b/browser/extensions/pdfjs/content/build/pdf.js --- a/browser/extensions/pdfjs/content/build/pdf.js +++ b/browser/extensions/pdfjs/content/build/pdf.js @@ -121,12 +121,12 @@ /***/ (function(module, exports, __w_pdfjs_require__) { "use strict"; -var pdfjsVersion = '2.0.641'; -var pdfjsBuild = '6fa2c779'; +var pdfjsVersion = '2.0.661'; +var pdfjsBuild = '42922c9a'; var pdfjsSharedUtil = __w_pdfjs_require__(1); var pdfjsDisplayAPI = __w_pdfjs_require__(6); var pdfjsDisplayTextLayer = __w_pdfjs_require__(18); var pdfjsDisplayAnnotationLayer = __w_pdfjs_require__(19); var pdfjsDisplayDOMUtils = __w_pdfjs_require__(7); @@ -4221,11 +4221,11 @@ source.length = pdfDataRangeTransport.length; source.initialData = pdfDataRangeTransport.initialData; } return worker.messageHandler.sendWithPromise('GetDocRequest', { docId, - apiVersion: '2.0.641', + apiVersion: '2.0.661', source: { data: source.data, url: source.url, password: source.password, disableAutoFetch: source.disableAutoFetch, @@ -5466,20 +5466,20 @@ this._scheduleNextBound = this._scheduleNext.bind(this); this._nextBound = this._next.bind(this); this._canvas = params.canvasContext.canvas; } InternalRenderTask.prototype = { - initializeGraphics: function InternalRenderTask_initializeGraphics(transparency) { + initializeGraphics(transparency) { + if (this.cancelled) { + return; + } if (this._canvas) { if (canvasInRendering.has(this._canvas)) { throw new Error('Cannot use the same canvas during multiple render() operations. ' + 'Use different canvas or ensure previous operations were ' + 'cancelled or completed.'); } canvasInRendering.set(this._canvas, this); } - if (this.cancelled) { - return; - } if (this._pdfBug && _global_scope2.default.StepperManager && _global_scope2.default.StepperManager.enabled) { this.stepper = _global_scope2.default.StepperManager.create(this.pageNumber - 1); this.stepper.init(this.operatorList); this.stepper.nextBreakPoint = this.stepper.getNextBreakPoint(); } @@ -5561,12 +5561,12 @@ }; return InternalRenderTask; }(); var version, build; { - exports.version = version = '2.0.641'; - exports.build = build = '6fa2c779'; + exports.version = version = '2.0.661'; + exports.build = build = '42922c9a'; } exports.getDocument = getDocument; exports.LoopbackPort = LoopbackPort; exports.PDFDataRangeTransport = PDFDataRangeTransport; exports.PDFWorker = PDFWorker; diff --git a/browser/extensions/pdfjs/content/build/pdf.worker.js b/browser/extensions/pdfjs/content/build/pdf.worker.js --- a/browser/extensions/pdfjs/content/build/pdf.worker.js +++ b/browser/extensions/pdfjs/content/build/pdf.worker.js @@ -121,12 +121,12 @@ /***/ (function(module, exports, __w_pdfjs_require__) { "use strict"; -var pdfjsVersion = '2.0.641'; -var pdfjsBuild = '6fa2c779'; +var pdfjsVersion = '2.0.661'; +var pdfjsBuild = '42922c9a'; var pdfjsCoreWorker = __w_pdfjs_require__(1); exports.WorkerMessageHandler = pdfjsCoreWorker.WorkerMessageHandler; /***/ }), /* 1 */ @@ -325,11 +325,11 @@ var pdfManager; var terminated = false; var cancelXHRs = null; var WorkerTasks = []; let apiVersion = docParams.apiVersion; - let workerVersion = '2.0.641'; + let workerVersion = '2.0.661'; if (apiVersion !== workerVersion) { throw new Error(`The API version "${apiVersion}" does not match ` + `the Worker version "${workerVersion}".`); } var docId = docParams.docId; var docBaseUrl = docParams.docBaseUrl; diff --git a/browser/extensions/pdfjs/content/web/viewer.js b/browser/extensions/pdfjs/content/web/viewer.js --- a/browser/extensions/pdfjs/content/web/viewer.js +++ b/browser/extensions/pdfjs/content/web/viewer.js @@ -568,11 +568,14 @@ this.pdfHistory = new _pdf_history.PDFHistory({ linkService: pdfLinkService, eventBus }); pdfLinkService.setHistory(this.pdfHistory); - this.findController = new _pdf_find_controller.PDFFindController({ pdfViewer: this.pdfViewer }); + this.findController = new _pdf_find_controller.PDFFindController({ + pdfViewer: this.pdfViewer, + eventBus + }); this.findController.onUpdateResultsCount = matchCount => { if (this.supportsIntegratedFind) { return; } this.findBar.updateResultsCount(matchCount); @@ -930,12 +933,11 @@ }); }); let pageModePromise = pdfDocument.getPageMode().catch(function () {}); this.toolbar.setPagesCount(pdfDocument.numPages, false); this.secondaryToolbar.setPagesCount(pdfDocument.numPages); - let id = this.documentFingerprint = pdfDocument.fingerprint; - let store = this.store = new _view_history.ViewHistory(id); + const store = this.store = new _view_history.ViewHistory(pdfDocument.fingerprint); let baseDocumentUrl; baseDocumentUrl = this.baseUrl; this.pdfLinkService.setDocument(pdfDocument, baseDocumentUrl); this.pdfDocumentProperties.setDocument(pdfDocument, this.url); let pdfViewer = this.pdfViewer; @@ -947,11 +949,11 @@ pdfThumbnailViewer.setDocument(pdfDocument); firstPagePromise.then(pdfPage => { this.loadingBar.setWidth(this.appConfig.viewerContainer); if (!_app_options.AppOptions.get('disableHistory') && !this.isViewerEmbedded) { let resetHistory = !_app_options.AppOptions.get('showPreviousViewOnLoad'); - this.pdfHistory.initialize(id, resetHistory); + this.pdfHistory.initialize(pdfDocument.fingerprint, resetHistory); if (this.pdfHistory.initialBookmark) { this.initialBookmark = this.pdfHistory.initialBookmark; this.initialRotation = this.pdfHistory.initialRotation; } } @@ -978,16 +980,12 @@ let spreadMode = _app_options.AppOptions.get('spreadModeOnLoad'); if (values.page && _app_options.AppOptions.get('showPreviousViewOnLoad')) { hash = 'page=' + values.page + '&zoom=' + (zoom || values.zoom) + ',' + values.scrollLeft + ',' + values.scrollTop; rotation = parseInt(values.rotation, 10); sidebarView = sidebarView || values.sidebarView | 0; - if (values.scrollMode !== null) { - scrollMode = values.scrollMode; - } - if (values.spreadMode !== null) { - spreadMode = values.spreadMode; - } + scrollMode = scrollMode || values.scrollMode | 0; + spreadMode = spreadMode || values.spreadMode | 0; } if (pageMode && !_app_options.AppOptions.get('disablePageMode')) { sidebarView = sidebarView || apiPageModeToSidebarView(pageMode); } return { @@ -1126,23 +1124,25 @@ generator: generatorId, formType }); }); }, - setInitialView(storedHash, values = {}) { - let { rotation, sidebarView, scrollMode, spreadMode } = values; + setInitialView(storedHash, { rotation, sidebarView, scrollMode, spreadMode } = {}) { let setRotation = angle => { if ((0, _ui_utils.isValidRotation)(angle)) { this.pdfViewer.pagesRotation = angle; } }; - if (Number.isInteger(scrollMode)) { - this.pdfViewer.setScrollMode(scrollMode); - } - if (Number.isInteger(spreadMode)) { - this.pdfViewer.setSpreadMode(spreadMode); - } + let setViewerModes = (scroll, spread) => { + if (Number.isInteger(scroll)) { + this.pdfViewer.scrollMode = scroll; + } + if (Number.isInteger(spread)) { + this.pdfViewer.spreadMode = spread; + } + }; + setViewerModes(scrollMode, spreadMode); this.isInitialViewSet = true; this.pdfSidebar.setInitialView(sidebarView); if (this.initialBookmark) { setRotation(this.initialRotation); delete this.initialRotation; @@ -1582,14 +1582,14 @@ } function webViewerRotateCcw() { PDFViewerApplication.rotatePages(-90); } function webViewerSwitchScrollMode(evt) { - PDFViewerApplication.pdfViewer.setScrollMode(evt.mode); + PDFViewerApplication.pdfViewer.scrollMode = evt.mode; } function webViewerSwitchSpreadMode(evt) { - PDFViewerApplication.pdfViewer.setSpreadMode(evt.mode); + PDFViewerApplication.pdfViewer.spreadMode = evt.mode; } function webViewerDocumentProperties() { PDFViewerApplication.pdfDocumentProperties.open(); } function webViewerFind(evt) { @@ -3220,10 +3220,18 @@ }, sidebarViewOnLoad: { value: 0, kind: OptionKind.VIEWER }, + scrollModeOnLoad: { + value: 0, + kind: OptionKind.VIEWER + }, + spreadModeOnLoad: { + value: 0, + kind: OptionKind.VIEWER + }, textLayerMode: { value: 1, kind: OptionKind.VIEWER }, useOnlyCssZoom: { @@ -4177,10 +4185,12 @@ }); exports.PDFFindController = exports.FindState = undefined; var _pdfjsLib = __webpack_require__(3); +var _dom_events = __webpack_require__(10); + var _ui_utils = __webpack_require__(2); const FindState = { FOUND: 0, NOT_FOUND: 1, @@ -4202,12 +4212,13 @@ '\u00BC': '1/4', '\u00BD': '1/2', '\u00BE': '3/4' }; class PDFFindController { - constructor({ pdfViewer }) { + constructor({ pdfViewer, eventBus = (0, _dom_events.getGlobalEventBus)() }) { this.pdfViewer = pdfViewer; + this.eventBus = eventBus; this.onUpdateResultsCount = null; this.onUpdateState = null; this.reset(); let replace = Object.keys(CHARACTERS_TO_NORMALIZE).join(''); this.normalizationRegex = new RegExp('[' + replace + ']', 'g'); @@ -4233,11 +4244,15 @@ this.resumePageIdx = null; this.state = null; this.dirtyMatch = false; this.findTimeout = null; this._firstPagePromise = new Promise(resolve => { - this.resolveFirstPage = resolve; + const eventBus = this.eventBus; + eventBus.on('pagesinit', function onPagesInit() { + eventBus.off('pagesinit', onPagesInit); + resolve(); + }); }); } executeCommand(cmd, state) { if (this.state === null || cmd !== 'findagain') { this.dirtyMatch = true; @@ -6340,22 +6355,22 @@ _scrollIntoView({ pageDiv, pageSpot = null }) { if (!pageSpot && !this.isInPresentationMode) { const left = pageDiv.offsetLeft + pageDiv.clientLeft; const right = left + pageDiv.clientWidth; const { scrollLeft, clientWidth } = this.container; - if (this.scrollMode === _base_viewer.ScrollMode.HORIZONTAL || left < scrollLeft || right > scrollLeft + clientWidth) { + if (this._scrollMode === _base_viewer.ScrollMode.HORIZONTAL || left < scrollLeft || right > scrollLeft + clientWidth) { pageSpot = { left: 0, top: 0 }; } } (0, _ui_utils.scrollIntoView)(pageDiv, pageSpot); } _getVisiblePages() { if (!this.isInPresentationMode) { - return (0, _ui_utils.getVisibleElements)(this.container, this._pages, true, this.scrollMode === _base_viewer.ScrollMode.HORIZONTAL); + return (0, _ui_utils.getVisibleElements)(this.container, this._pages, true, this._scrollMode === _base_viewer.ScrollMode.HORIZONTAL); } let currentPage = this._pages[this._currentPageNumber - 1]; let visible = [{ id: currentPage.id, view: currentPage @@ -6398,78 +6413,11 @@ source: this, location: this._location }); } get _isScrollModeHorizontal() { - return this.isInPresentationMode ? false : this.scrollMode === _base_viewer.ScrollMode.HORIZONTAL; - } - setScrollMode(mode) { - if (mode === this.scrollMode) { - return; - } - super.setScrollMode(mode); - this.eventBus.dispatch('scrollmodechanged', { mode }); - this._updateScrollModeClasses(); - if (!this.pdfDocument) { - return; - } - const pageNumber = this._currentPageNumber; - if (isNaN(this._currentScaleValue)) { - this._setScale(this._currentScaleValue, true); - } - this.scrollPageIntoView({ pageNumber }); - this.update(); - } - _updateScrollModeClasses() { - const { scrollMode, viewer } = this; - if (scrollMode === _base_viewer.ScrollMode.HORIZONTAL) { - viewer.classList.add('scrollHorizontal'); - } else { - viewer.classList.remove('scrollHorizontal'); - } - if (scrollMode === _base_viewer.ScrollMode.WRAPPED) { - viewer.classList.add('scrollWrapped'); - } else { - viewer.classList.remove('scrollWrapped'); - } - } - setSpreadMode(mode) { - if (mode === this.spreadMode) { - return; - } - super.setSpreadMode(mode); - this.eventBus.dispatch('spreadmodechanged', { mode }); - this._regroupSpreads(); - } - _regroupSpreads() { - if (!this.pdfDocument) { - return; - } - const viewer = this.viewer, - pages = this._pages; - viewer.textContent = ''; - if (this.spreadMode === _base_viewer.SpreadMode.NONE) { - for (let i = 0, iMax = pages.length; i < iMax; ++i) { - viewer.appendChild(pages[i].div); - } - } else { - const parity = this.spreadMode - 1; - let spread = null; - for (let i = 0, iMax = pages.length; i < iMax; ++i) { - if (spread === null) { - spread = document.createElement('div'); - spread.className = 'spread'; - viewer.appendChild(spread); - } else if (i % 2 === parity) { - spread = spread.cloneNode(false); - viewer.appendChild(spread); - } - spread.appendChild(pages[i].div); - } - } - this.scrollPageIntoView({ pageNumber: this._currentPageNumber }); - this.update(); + return this.isInPresentationMode ? false : this._scrollMode === _base_viewer.ScrollMode.HORIZONTAL; } } exports.PDFViewer = PDFViewer; /***/ }), @@ -6568,12 +6516,10 @@ this.renderer = options.renderer || _ui_utils.RendererType.CANVAS; this.enableWebGL = options.enableWebGL || false; this.useOnlyCssZoom = options.useOnlyCssZoom || false; this.maxCanvasPixels = options.maxCanvasPixels; this.l10n = options.l10n || _ui_utils.NullL10n; - this.scrollMode = options.scrollMode || ScrollMode.VERTICAL; - this.spreadMode = options.spreadMode || SpreadMode.NONE; this.defaultRenderingQueue = !options.renderingQueue; if (this.defaultRenderingQueue) { this.renderingQueue = new _pdf_rendering_queue.PDFRenderingQueue(); this.renderingQueue.setViewer(this); } else { @@ -6583,13 +6529,10 @@ this.presentationModeState = _ui_utils.PresentationModeState.UNKNOWN; this._resetView(); if (this.removePageBorders) { this.viewer.classList.add('removePageBorders'); } - if (this.scrollMode !== ScrollMode.VERTICAL) { - this._updateScrollModeClasses(); - } } get pagesCount() { return this._pages.length; } getPageView(index) { @@ -6763,12 +6706,12 @@ l10n: this.l10n }); bindOnAfterAndBeforeDraw(pageView); this._pages.push(pageView); } - if (this.spreadMode !== SpreadMode.NONE) { - this._regroupSpreads(); + if (this._spreadMode !== SpreadMode.NONE) { + this._updateSpreadMode(); } onePageRenderedCapability.promise.then(() => { if (pdfDocument.loadingParams['disableAutoFetch']) { pagesCapability.resolve(); return; @@ -6794,13 +6737,10 @@ }); this.eventBus.dispatch('pagesinit', { source: this }); if (this.defaultRenderingQueue) { this.update(); } - if (this.findController) { - this.findController.resolveFirstPage(); - } }).catch(reason => { console.error('Unable to initialize viewer', reason); }); } setPageLabels(labels) { @@ -6830,11 +6770,14 @@ this._buffer = new PDFPageViewBuffer(DEFAULT_CACHE_SIZE); this._location = null; this._pagesRotation = 0; this._pagesRequests = []; this._pageViewsReady = false; + this._scrollMode = ScrollMode.VERTICAL; + this._spreadMode = SpreadMode.NONE; this.viewer.textContent = ''; + this._updateScrollMode(); } _scrollUpdate() { if (this.pagesCount === 0) { return; } @@ -7190,24 +7133,100 @@ height: size.width, rotation: (size.rotation + 90) % 360 }; }); } - setScrollMode(mode) { + get scrollMode() { + return this._scrollMode; + } + set scrollMode(mode) { + if (this._scrollMode === mode) { + return; + } if (!Number.isInteger(mode) || !Object.values(ScrollMode).includes(mode)) { throw new Error(`Invalid scroll mode: ${mode}`); } - this.scrollMode = mode; + this._scrollMode = mode; + this.eventBus.dispatch('scrollmodechanged', { + source: this, + mode + }); + this._updateScrollMode(this._currentPageNumber); + } + _updateScrollMode(pageNumber = null) { + const scrollMode = this._scrollMode, + viewer = this.viewer; + if (scrollMode === ScrollMode.HORIZONTAL) { + viewer.classList.add('scrollHorizontal'); + } else { + viewer.classList.remove('scrollHorizontal'); + } + if (scrollMode === ScrollMode.WRAPPED) { + viewer.classList.add('scrollWrapped'); + } else { + viewer.classList.remove('scrollWrapped'); + } + if (!this.pdfDocument || !pageNumber) { + return; + } + if (this._currentScaleValue && isNaN(this._currentScaleValue)) { + this._setScale(this._currentScaleValue, true); + } + this.scrollPageIntoView({ pageNumber }); + this.update(); } - _updateScrollModeClasses() {} - setSpreadMode(mode) { + setScrollMode(mode) {} + get spreadMode() { + return this._spreadMode; + } + set spreadMode(mode) { + if (this._spreadMode === mode) { + return; + } if (!Number.isInteger(mode) || !Object.values(SpreadMode).includes(mode)) { throw new Error(`Invalid spread mode: ${mode}`); } - this.spreadMode = mode; + this._spreadMode = mode; + this.eventBus.dispatch('spreadmodechanged', { + source: this, + mode + }); + this._updateSpreadMode(this._currentPageNumber); } - _regroupSpreads() {} + _updateSpreadMode(pageNumber = null) { + if (!this.pdfDocument) { + return; + } + const viewer = this.viewer, + pages = this._pages; + viewer.textContent = ''; + if (this._spreadMode === SpreadMode.NONE) { + for (let i = 0, iMax = pages.length; i < iMax; ++i) { + viewer.appendChild(pages[i].div); + } + } else { + const parity = this._spreadMode - 1; + let spread = null; + for (let i = 0, iMax = pages.length; i < iMax; ++i) { + if (spread === null) { + spread = document.createElement('div'); + spread.className = 'spread'; + viewer.appendChild(spread); + } else if (i % 2 === parity) { + spread = spread.cloneNode(false); + viewer.appendChild(spread); + } + spread.appendChild(pages[i].div); + } + } + if (!pageNumber) { + return; + } + this.scrollPageIntoView({ pageNumber }); + this.update(); + } + setSpreadMode(mode) {} } exports.BaseViewer = BaseViewer; exports.ScrollMode = ScrollMode; exports.SpreadMode = SpreadMode; @@ -8157,10 +8176,12 @@ } reset() { this.pageNumber = 0; this.pagesCount = 0; this._updateUIState(); + this.eventBus.dispatch('resetscrollmode', { source: this }); + this.eventBus.dispatch('resetspreadmode', { source: this }); } _updateUIState() { this.items.firstPage.disabled = this.pageNumber <= 1; this.items.lastPage.disabled = this.pageNumber >= this.pagesCount; this.items.pageRotateCw.disabled = this.pagesCount === 0; @@ -8197,11 +8218,11 @@ break; } }); } _bindScrollModeListener(buttons) { - this.eventBus.on('scrollmodechanged', function (evt) { + function scrollModeChanged(evt) { buttons.scrollVerticalButton.classList.remove('toggled'); buttons.scrollHorizontalButton.classList.remove('toggled'); buttons.scrollWrappedButton.classList.remove('toggled'); switch (evt.mode) { case _base_viewer.ScrollMode.VERTICAL: @@ -8212,14 +8233,20 @@ break; case _base_viewer.ScrollMode.WRAPPED: buttons.scrollWrappedButton.classList.add('toggled'); break; } + } + this.eventBus.on('scrollmodechanged', scrollModeChanged); + this.eventBus.on('resetscrollmode', evt => { + if (evt.source === this) { + scrollModeChanged({ mode: _base_viewer.ScrollMode.VERTICAL }); + } }); } _bindSpreadModeListener(buttons) { - this.eventBus.on('spreadmodechanged', function (evt) { + function spreadModeChanged(evt) { buttons.spreadNoneButton.classList.remove('toggled'); buttons.spreadOddButton.classList.remove('toggled'); buttons.spreadEvenButton.classList.remove('toggled'); switch (evt.mode) { case _base_viewer.SpreadMode.NONE: @@ -8230,10 +8257,16 @@ break; case _base_viewer.SpreadMode.EVEN: buttons.spreadEvenButton.classList.add('toggled'); break; } + } + this.eventBus.on('spreadmodechanged', spreadModeChanged); + this.eventBus.on('resetspreadmode', evt => { + if (evt.source === this) { + spreadModeChanged({ mode: _base_viewer.SpreadMode.NONE }); + } }); } open() { if (this.opened) { return; diff --git a/browser/locales/en-US/chrome/browser/downloads/downloads.properties b/browser/locales/en-US/chrome/browser/downloads/downloads.properties --- a/browser/locales/en-US/chrome/browser/downloads/downloads.properties +++ b/browser/locales/en-US/chrome/browser/downloads/downloads.properties @@ -90,14 +90,10 @@ # is usually no need to change the separator or the order of the substitutions, # even for right-to-left languages, unless the defaults are not suitable. statusSeparator=%1$S \u2014 %2$S statusSeparatorBeforeNumber=%1$S \u2014 %2$S -fileExecutableSecurityWarning=“%S” is an executable file. Executable files may contain viruses or other malicious code that could harm your computer. Use caution when opening this file. Are you sure you want to launch “%S”? -fileExecutableSecurityWarningTitle=Open Executable File? -fileExecutableSecurityWarningDontAsk=Don’t ask me this again - # LOCALIZATION NOTE (otherDownloads3): # This is displayed in an item at the bottom of the Downloads Panel when # there are more downloads than can fit in the list in the panel. Use a # semi-colon list of plural forms. # See: http://developer.mozilla.org/en/Localization_and_Plurals diff --git a/browser/themes/shared/aboutNetError-new.css b/browser/themes/shared/aboutNetError-new.css new file mode 100644 --- /dev/null +++ b/browser/themes/shared/aboutNetError-new.css @@ -0,0 +1,226 @@ +/* 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/. */ + +@import url("chrome://browser/skin/error-pages.css"); + +:root { + --exception-button-container-background: #F5F5F7; +} + +body.certerror { + border-style: solid; + border-color: #ffe900; + border-width: 16px; + width: 100%; +} + +body.captiveportal .title { + background-image: url("wifi.svg"); +} + +body.certerror .title { + background-image: url("cert-error-new.svg"); +} + +body.blocked .title { + background-image: url("chrome://global/skin/icons/blocked.svg"); +} + +#errorContainer { + display: none; +} + +/* Pressing the retry button will cause the cursor to flicker from a pointer to + * not-allowed. Override the disabled cursor behaviour since we will never show + * the button disabled as the initial state. */ +button:disabled { + cursor: pointer; +} + +#prefChangeContainer { + display: none; +} + +#learnMoreContainer { + display: none; +} + +#certErrorAndCaptivePortalButtonContainer { + display: none; +} + +body:not(.neterror) #certErrorAndCaptivePortalButtonContainer { + display: flex; +} + +body:not(.neterror) #netErrorButtonContainer { + display: none; +} + +#errorTryAgain { + margin-top: 1.2em; +} + +#advancedButton { + display: none; +} + +body.captiveportal #returnButton { + display: none; +} + +body:not(.captiveportal) #openPortalLoginPageButton { + display: none; +} + +#openPortalLoginPageButton { + margin-inline-start: 0; +} + +body:not(.neterror) #advancedButton { + display: block; +} + +#certificateErrorReporting { + display: none; +} + +#advancedPanelContainer { + width: 100%; + left: 0; +} + +.advanced-panel { + /* Hidden until the link is clicked */ + display: none; + background-color: white; + border: 1px lightgray solid; + margin: 48px auto; + min-width: var(--in-content-container-min-width); + max-width: var(--in-content-container-max-width); +} + +#overrideWeakCryptoPanel { + display: none; + flex-direction: row; + flex-wrap: wrap; + justify-content: space-between; + align-content: space-between; + align-items: flex-start; + margin-top: 1em; +} + +span#hostname { + font-weight: bold; +} + +#automaticallyReportInFuture { + cursor: pointer; +} + +#errorCode:not([href]) { + color: var(--in-content-page-color); + cursor: text; + text-decoration: none; +} + +#errorCode[href] { + white-space: nowrap; +} + +#badCertTechnicalInfo { + margin: 3em; + overflow: auto; + white-space: pre-wrap; +} + +#certificateErrorReporting { + display: none; +} + +#certificateErrorDebugInformation { + display: none; + background-color: var(--in-content-box-background-hover) !important; + border-top: 1px solid var(--in-content-border-color); + width: 100%; + padding: 1em 17.5%; + box-sizing: border-box; +} + +#certificateErrorText { + font-family: monospace; + white-space: pre-wrap; + padding: 1em 0; +} + +#cert_domain_link:not([href]) { + color: var(--in-content-page-color); + text-decoration: none; +} + +.exceptionDialogButtonContainer { + background-color: var(--exception-button-container-background); + display: flex; + justify-content: end; + padding: 10px; +} + +.exceptionDialogButtonContainer[hidden] { + display: none; +} + +.illustrated #errorPageContainer { + min-height: 300px; + display: flex; + flex-direction: column; + background-position: left center; +} + +.illustrated[dir="rtl"] #errorPageContainer { + background-position: right center; +} + +.illustrated .title { + background: none; + padding-inline-start: 0; + margin-inline-start: 0; +} + +.illustrated #text-container { + margin: auto; + padding-inline-start: 38%; +} + +.illustrated #errorPageContainer { + background-repeat: no-repeat; + background-size: 38%; +} + +.connectionFailure #errorPageContainer, +.netInterrupt #errorPageContainer, +.netTimeout #errorPageContainer, +.netReset #errorPageContainer, +.netOffline #errorPageContainer { + background-image: url("chrome://browser/content/illustrations/error-connection-failure.svg"); +} + +.dnsNotFound #errorPageContainer { + background-image: url("chrome://browser/content/illustrations/error-server-not-found.svg"); +} + +.malformedURI #errorPageContainer { + background-image: url("chrome://browser/content/illustrations/error-malformed-url.svg"); +} + +.malformedURI #errorTryAgain { + display: none; +} + +#wrongSystemTimePanel { + display: none; +} + +#wrongSystemTimeWithoutReferencePanel { + display: none; +} diff --git a/browser/themes/shared/error-pages.css b/browser/themes/shared/error-pages.css --- a/browser/themes/shared/error-pages.css +++ b/browser/themes/shared/error-pages.css @@ -5,13 +5,11 @@ @import url("chrome://global/skin/in-content/info-pages.css"); body { background-size: 64px 32px; background-repeat: repeat-x; - /* Top padding for when the window height is small. - Bottom padding to keep everything centered. */ - padding: 75px 0; + padding: 0; /* info-pages.css sets a minimum width of 13em to the content * container. If we don't set a min-width here, the content * gets clipped in iframes with small width. We don't accomodate * any padding to prioritize real estate in the small viewport. */ min-width: 13em; @@ -26,14 +24,10 @@ .button-spacer { flex: 1; } @media only screen and (max-width: 959px) { - body { - padding: 75px 48px; - } - .title { background-image: none !important; padding-inline-start: 0; margin-inline-start: 0; } @@ -43,15 +37,11 @@ } } @media only screen and (max-width: 640px) { body { - justify-content: unset; - /* Now that everything is top-aligned, we don't need the - * bottom padding for centering - though it's added back - * when the viewport height is < 480px (see below). */ - padding: 75px 20px 0; + padding: 0 75px; } .title-text { padding-bottom: 0; border-bottom: none; diff --git a/browser/themes/shared/incontent-icons/cert-error-new.svg b/browser/themes/shared/incontent-icons/cert-error-new.svg new file mode 100644 --- /dev/null +++ b/browser/themes/shared/incontent-icons/cert-error-new.svg @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/browser/themes/shared/incontent-icons/cert-error.svg b/browser/themes/shared/incontent-icons/cert-error.svg --- a/browser/themes/shared/incontent-icons/cert-error.svg +++ b/browser/themes/shared/incontent-icons/cert-error.svg @@ -37,6 +37,6 @@ - + \ No newline at end of file diff --git a/browser/themes/shared/jar.inc.mn b/browser/themes/shared/jar.inc.mn --- a/browser/themes/shared/jar.inc.mn +++ b/browser/themes/shared/jar.inc.mn @@ -6,10 +6,11 @@ # actual theme-specific manifests, so that shared resources need only # be specified once. As a result, the source file paths are relative # to the location of the actual manifest. skin/classic/browser/aboutNetError.css (../shared/aboutNetError.css) + skin/classic/browser/aboutNetError-new.css (../shared/aboutNetError-new.css) skin/classic/browser/blockedSite.css (../shared/blockedSite.css) skin/classic/browser/error-pages.css (../shared/error-pages.css) skin/classic/browser/aboutRestartRequired.css (../shared/aboutRestartRequired.css) * skin/classic/browser/aboutSessionRestore.css (../shared/aboutSessionRestore.css) skin/classic/browser/aboutLibrary.css (../shared/aboutLibrary.css) @@ -214,10 +215,11 @@ skin/classic/browser/translation-16.png (../shared/translation/translation-16.png) skin/classic/browser/translation-16@2x.png (../shared/translation/translation-16@2x.png) skin/classic/browser/update-badge.svg (../shared/update-badge.svg) skin/classic/browser/warning.svg (../shared/warning.svg) skin/classic/browser/cert-error.svg (../shared/incontent-icons/cert-error.svg) + skin/classic/browser/cert-error-new.svg (../shared/incontent-icons/cert-error-new.svg) skin/classic/browser/wifi.svg (../shared/incontent-icons/wifi.svg) skin/classic/browser/tab-crashed.svg (../shared/incontent-icons/tab-crashed.svg) skin/classic/browser/favicon-search-16.svg (../shared/favicon-search-16.svg) skin/classic/browser/icon-search-64.svg (../shared/incontent-icons/icon-search-64.svg) skin/classic/browser/welcome-back.svg (../shared/incontent-icons/welcome-back.svg) diff --git a/build/moz.configure/node.configure b/build/moz.configure/node.configure new file mode 100644 --- /dev/null +++ b/build/moz.configure/node.configure @@ -0,0 +1,73 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +option('--enable-nodejs', + help='Require Node.js to build') + + +# "nodejs" is first in the tuple on the assumption that it's only likely to +# exist on systems (probably linux distros) where there is a program in the path +# called "node" that does something else. +nodejs = check_prog('NODEJS', ('nodejs', 'node',), + allow_missing=True) + + +@depends_if(nodejs) +@checking('for node.js version') +@imports('os') +@imports('re') +@imports('subprocess') +def nodejs_version(node): + env = dict(os.environ) + env[b'NODE_DISABLE_COLORS'] = b'1' + + try: + out = subprocess.check_output([node, '--version'], env=env, + stderr=subprocess.STDOUT) + except subprocess.CalledProcessError as e: + return None + + match = re.search(r'v(.+)$', out) + + if not match: + return None + + return Version(match.group(1)) + + +@depends('--enable-nodejs', nodejs, nodejs_version) +def nodejs_suitability(require, node, version): + MIN_NODE_VERSION = Version('8.11') + + if not node: + msg = ('could not find Node.js executable; ensure `node` or `nodejs` ' + 'is in PATH or set NODEJS in environment to point to an ' + 'executable') + + if require: + raise FatalCheckError(msg) + else: + log.warning(msg) + log.warning('(This will become a fatal error in the near future.)') + return + + if not version: + msg = 'could not resolve Node.js version' + if require: + raise FatalCheckError(msg) + else: + log.warning(msg) + log.warning('(This will become a fatal error in the near future.)') + return + + if version < MIN_NODE_VERSION: + msg = 'NODEJS must point to node %s or newer; %s found' % ( + MIN_NODE_VERSION, version) + + if require: + raise FatalCheckError(msg) + else: + log.warning(msg) diff --git a/caps/OriginAttributes.cpp b/caps/OriginAttributes.cpp --- a/caps/OriginAttributes.cpp +++ b/caps/OriginAttributes.cpp @@ -126,12 +126,14 @@ value.AppendInt(mPrivateBrowsingId); params.Set(NS_LITERAL_STRING("privateBrowsingId"), value); } if (!mFirstPartyDomain.IsEmpty()) { - MOZ_RELEASE_ASSERT(mFirstPartyDomain.FindCharInSet(dom::quota::QuotaManager::kReplaceChars) == kNotFound); - params.Set(NS_LITERAL_STRING("firstPartyDomain"), mFirstPartyDomain); + nsAutoString sanitizedFirstPartyDomain(mFirstPartyDomain); + sanitizedFirstPartyDomain.ReplaceChar(dom::quota::QuotaManager::kReplaceChars, '+'); + + params.Set(NS_LITERAL_STRING("firstPartyDomain"), sanitizedFirstPartyDomain); } aStr.Truncate(); params.Serialize(value); diff --git a/devtools/client/debugger/new/README.mozilla b/devtools/client/debugger/new/README.mozilla --- a/devtools/client/debugger/new/README.mozilla +++ b/devtools/client/debugger/new/README.mozilla @@ -1,11 +1,11 @@ This is the debugger.html project output. See https://github.com/devtools-html/debugger.html -Version 66 +Version 68 -Comparison: https://github.com/devtools-html/debugger.html/compare/release-65...release-66 +Comparison: https://github.com/devtools-html/debugger.html/compare/release-67...release-68 Packages: - babel-plugin-transform-es2015-modules-commonjs @6.26.2 - babel-preset-react @6.24.1 - react @16.2.0 diff --git a/devtools/client/debugger/new/dist/debugger.css b/devtools/client/debugger/new/dist/debugger.css --- a/devtools/client/debugger/new/dist/debugger.css +++ b/devtools/client/debugger/new/dist/debugger.css @@ -245,11 +245,10 @@ ::-webkit-scrollbar-thumb { border-radius: 8px; background: rgba(113, 113, 113, 0.5); } - /* 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 . */ menupopup { diff --git a/devtools/client/debugger/new/dist/vendors.js b/devtools/client/debugger/new/dist/vendors.js --- a/devtools/client/debugger/new/dist/vendors.js +++ b/devtools/client/debugger/new/dist/vendors.js @@ -1,15 +1,15 @@ (function webpackUniversalModuleDefinition(root, factory) { if(typeof exports === 'object' && typeof module === 'object') - module.exports = factory(require("devtools/client/shared/vendor/react"), require("devtools/client/shared/vendor/react-dom"), require("Services"), require("devtools/shared/flags"), require("devtools/client/shared/vendor/react-prop-types"), require("devtools/client/shared/vendor/react-dom-factories")); + module.exports = factory(require("devtools/client/shared/vendor/react"), require("devtools/client/shared/vendor/lodash"), require("devtools/client/shared/vendor/react-dom"), require("Services"), require("devtools/shared/flags"), require("devtools/client/shared/vendor/react-prop-types"), require("devtools/client/shared/vendor/react-dom-factories")); else if(typeof define === 'function' && define.amd) - define(["devtools/client/shared/vendor/react", "devtools/client/shared/vendor/react-dom", "Services", "devtools/shared/flags", "devtools/client/shared/vendor/react-prop-types", "devtools/client/shared/vendor/react-dom-factories"], factory); + define(["devtools/client/shared/vendor/react", "devtools/client/shared/vendor/lodash", "devtools/client/shared/vendor/react-dom", "Services", "devtools/shared/flags", "devtools/client/shared/vendor/react-prop-types", "devtools/client/shared/vendor/react-dom-factories"], factory); else { - var a = typeof exports === 'object' ? factory(require("devtools/client/shared/vendor/react"), require("devtools/client/shared/vendor/react-dom"), require("Services"), require("devtools/shared/flags"), require("devtools/client/shared/vendor/react-prop-types"), require("devtools/client/shared/vendor/react-dom-factories")) : factory(root["devtools/client/shared/vendor/react"], root["devtools/client/shared/vendor/react-dom"], root["Services"], root["devtools/shared/flags"], root["devtools/client/shared/vendor/react-prop-types"], root["devtools/client/shared/vendor/react-dom-factories"]); + var a = typeof exports === 'object' ? factory(require("devtools/client/shared/vendor/react"), require("devtools/client/shared/vendor/lodash"), require("devtools/client/shared/vendor/react-dom"), require("Services"), require("devtools/shared/flags"), require("devtools/client/shared/vendor/react-prop-types"), require("devtools/client/shared/vendor/react-dom-factories")) : factory(root["devtools/client/shared/vendor/react"], root["devtools/client/shared/vendor/lodash"], root["devtools/client/shared/vendor/react-dom"], root["Services"], root["devtools/shared/flags"], root["devtools/client/shared/vendor/react-prop-types"], root["devtools/client/shared/vendor/react-dom-factories"]); for(var i in a) (typeof exports === 'object' ? exports : root)[i] = a[i]; } -})(typeof self !== 'undefined' ? self : this, function(__WEBPACK_EXTERNAL_MODULE_0__, __WEBPACK_EXTERNAL_MODULE_4__, __WEBPACK_EXTERNAL_MODULE_22__, __WEBPACK_EXTERNAL_MODULE_52__, __WEBPACK_EXTERNAL_MODULE_3642__, __WEBPACK_EXTERNAL_MODULE_3643__) { +})(typeof self !== 'undefined' ? self : this, function(__WEBPACK_EXTERNAL_MODULE_0__, __WEBPACK_EXTERNAL_MODULE_2__, __WEBPACK_EXTERNAL_MODULE_4__, __WEBPACK_EXTERNAL_MODULE_22__, __WEBPACK_EXTERNAL_MODULE_52__, __WEBPACK_EXTERNAL_MODULE_3642__, __WEBPACK_EXTERNAL_MODULE_3643__) { return /******/ (function(modules) { // webpackBootstrap /******/ // The module cache /******/ var installedModules = {}; /******/ /******/ // The require function @@ -78,96 +78,10 @@ /***/ 0: /***/ (function(module, exports) { module.exports = __WEBPACK_EXTERNAL_MODULE_0__; -/***/ }), - -/***/ 10: -/***/ (function(module, exports, __webpack_require__) { - -var Symbol = __webpack_require__(7); - -/** Used for built-in method references. */ -var objectProto = Object.prototype; - -/** Used to check objects for own properties. */ -var hasOwnProperty = objectProto.hasOwnProperty; - -/** - * Used to resolve the - * [`toStringTag`](http://ecma-international.org/ecma-262/7.0/#sec-object.prototype.tostring) - * of values. - */ -var nativeObjectToString = objectProto.toString; - -/** Built-in value references. */ -var symToStringTag = Symbol ? Symbol.toStringTag : undefined; - -/** - * A specialized version of `baseGetTag` which ignores `Symbol.toStringTag` values. - * - * @private - * @param {*} value The value to query. - * @returns {string} Returns the raw `toStringTag`. - */ -function getRawTag(value) { - var isOwn = hasOwnProperty.call(value, symToStringTag), - tag = value[symToStringTag]; - - try { - value[symToStringTag] = undefined; - var unmasked = true; - } catch (e) {} - - var result = nativeObjectToString.call(value); - if (unmasked) { - if (isOwn) { - value[symToStringTag] = tag; - } else { - delete value[symToStringTag]; - } - } - return result; -} - -module.exports = getRawTag; - - -/***/ }), - -/***/ 100: -/***/ (function(module, exports, __webpack_require__) { - -var assocIndexOf = __webpack_require__(96); - -/** - * Sets the list cache `key` to `value`. - * - * @private - * @name set - * @memberOf ListCache - * @param {string} key The key of the value to set. - * @param {*} value The value to set. - * @returns {Object} Returns the list cache instance. - */ -function listCacheSet(key, value) { - var data = this.__data__, - index = assocIndexOf(data, key); - - if (index < 0) { - ++this.size; - data.push([key, value]); - } else { - data[index][1] = value; - } - return this; -} - -module.exports = listCacheSet; - - /***/ }), /***/ 1000: /***/ (function(module, exports) { @@ -201,608 +115,586 @@ module.exports = "" /***/ }), -/***/ 101: -/***/ (function(module, exports, __webpack_require__) { - -var getNative = __webpack_require__(81), - root = __webpack_require__(8); - -/* Built-in method references that are verified to be native. */ -var Map = getNative(root, 'Map'); - -module.exports = Map; +/***/ 1043: +/***/ (function(module, exports) { +module.exports = "" /***/ }), -/***/ 102: -/***/ (function(module, exports, __webpack_require__) { - -var getMapData = __webpack_require__(103); - -/** - * Removes `key` and its value from the map. - * - * @private - * @name delete - * @memberOf MapCache - * @param {string} key The key of the value to remove. - * @returns {boolean} Returns `true` if the entry was removed, else `false`. - */ -function mapCacheDelete(key) { - var result = getMapData(this, key)['delete'](key); - this.size -= result ? 1 : 0; - return result; -} - -module.exports = mapCacheDelete; +/***/ 1044: +/***/ (function(module, exports) { +module.exports = "Created with Sketch." /***/ }), -/***/ 103: -/***/ (function(module, exports, __webpack_require__) { - -var isKeyable = __webpack_require__(104); - -/** - * Gets the data for `map`. - * - * @private - * @param {Object} map The map to query. - * @param {string} key The reference key. - * @returns {*} Returns the map data. - */ -function getMapData(map, key) { - var data = map.__data__; - return isKeyable(key) - ? data[typeof key == 'string' ? 'string' : 'hash'] - : data.map; -} - -module.exports = getMapData; +/***/ 1045: +/***/ (function(module, exports) { +module.exports = "Created with Sketch." /***/ }), -/***/ 104: +/***/ 1117: /***/ (function(module, exports) { -/** - * Checks if `value` is suitable for use as unique object key. - * - * @private - * @param {*} value The value to check. - * @returns {boolean} Returns `true` if `value` is suitable, else `false`. - */ -function isKeyable(value) { - var type = typeof value; - return (type == 'string' || type == 'number' || type == 'symbol' || type == 'boolean') - ? (value !== '__proto__') - : (value === null); -} - -module.exports = isKeyable; - +module.exports = "" /***/ }), -/***/ 1043: +/***/ 1118: /***/ (function(module, exports) { -module.exports = "" +module.exports = "" /***/ }), -/***/ 1044: +/***/ 1119: /***/ (function(module, exports) { -module.exports = "Created with Sketch." +module.exports = "" /***/ }), -/***/ 1045: +/***/ 1174: /***/ (function(module, exports) { -module.exports = "Created with Sketch." +module.exports = "image/svg+xml" /***/ }), -/***/ 105: -/***/ (function(module, exports, __webpack_require__) { - -var getMapData = __webpack_require__(103); - -/** - * Gets the map value for `key`. - * - * @private - * @name get - * @memberOf MapCache - * @param {string} key The key of the value to get. - * @returns {*} Returns the entry value. - */ -function mapCacheGet(key) { - return getMapData(this, key).get(key); -} +/***/ 118: +/***/ (function(module, exports) { -module.exports = mapCacheGet; /***/ }), -/***/ 106: +/***/ 119: /***/ (function(module, exports, __webpack_require__) { -var getMapData = __webpack_require__(103); +/* WEBPACK VAR INJECTION */(function(process) {// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. -/** - * Checks if a map value for `key` exists. - * - * @private - * @name has - * @memberOf MapCache - * @param {string} key The key of the entry to check. - * @returns {boolean} Returns `true` if an entry for `key` exists, else `false`. - */ -function mapCacheHas(key) { - return getMapData(this, key).has(key); -} +// resolves . and .. elements in a path array with directory names there +// must be no slashes, empty elements, or device names (c:\) in the array +// (so also no leading and trailing slashes - it does not distinguish +// relative and absolute paths) +function normalizeArray(parts, allowAboveRoot) { + // if the path tries to go above the root, `up` ends up > 0 + var up = 0; + for (var i = parts.length - 1; i >= 0; i--) { + var last = parts[i]; + if (last === '.') { + parts.splice(i, 1); + } else if (last === '..') { + parts.splice(i, 1); + up++; + } else if (up) { + parts.splice(i, 1); + up--; + } + } -module.exports = mapCacheHas; + // if the path is allowed to go above the root, restore leading ..s + if (allowAboveRoot) { + for (; up--; up) { + parts.unshift('..'); + } + } + return parts; +} -/***/ }), +// Split a filename into [root, dir, basename, ext], unix version +// 'root' is just a slash, or nothing. +var splitPathRe = + /^(\/?|)([\s\S]*?)((?:\.{1,2}|[^\/]+?|)(\.[^.\/]*|))(?:[\/]*)$/; +var splitPath = function(filename) { + return splitPathRe.exec(filename).slice(1); +}; -/***/ 107: -/***/ (function(module, exports, __webpack_require__) { +// path.resolve([from ...], to) +// posix version +exports.resolve = function() { + var resolvedPath = '', + resolvedAbsolute = false; -var getMapData = __webpack_require__(103); + for (var i = arguments.length - 1; i >= -1 && !resolvedAbsolute; i--) { + var path = (i >= 0) ? arguments[i] : process.cwd(); -/** - * Sets the map `key` to `value`. - * - * @private - * @name set - * @memberOf MapCache - * @param {string} key The key of the value to set. - * @param {*} value The value to set. - * @returns {Object} Returns the map cache instance. - */ -function mapCacheSet(key, value) { - var data = getMapData(this, key), - size = data.size; + // Skip empty and invalid entries + if (typeof path !== 'string') { + throw new TypeError('Arguments to path.resolve must be strings'); + } else if (!path) { + continue; + } - data.set(key, value); - this.size += data.size == size ? 0 : 1; - return this; -} + resolvedPath = path + '/' + resolvedPath; + resolvedAbsolute = path.charAt(0) === '/'; + } -module.exports = mapCacheSet; + // At this point the path should be resolved to a full absolute path, but + // handle relative paths to be safe (might happen when process.cwd() fails) + // Normalize the path + resolvedPath = normalizeArray(filter(resolvedPath.split('/'), function(p) { + return !!p; + }), !resolvedAbsolute).join('/'); -/***/ }), + return ((resolvedAbsolute ? '/' : '') + resolvedPath) || '.'; +}; -/***/ 108: -/***/ (function(module, exports, __webpack_require__) { +// path.normalize(path) +// posix version +exports.normalize = function(path) { + var isAbsolute = exports.isAbsolute(path), + trailingSlash = substr(path, -1) === '/'; -var baseToString = __webpack_require__(109); + // Normalize the path + path = normalizeArray(filter(path.split('/'), function(p) { + return !!p; + }), !isAbsolute).join('/'); -/** - * Converts `value` to a string. An empty string is returned for `null` - * and `undefined` values. The sign of `-0` is preserved. - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category Lang - * @param {*} value The value to convert. - * @returns {string} Returns the converted string. - * @example - * - * _.toString(null); - * // => '' - * - * _.toString(-0); - * // => '-0' - * - * _.toString([1, 2, 3]); - * // => '1,2,3' - */ -function toString(value) { - return value == null ? '' : baseToString(value); -} + if (!path && !isAbsolute) { + path = '.'; + } + if (path && trailingSlash) { + path += '/'; + } -module.exports = toString; + return (isAbsolute ? '/' : '') + path; +}; +// posix version +exports.isAbsolute = function(path) { + return path.charAt(0) === '/'; +}; -/***/ }), +// posix version +exports.join = function() { + var paths = Array.prototype.slice.call(arguments, 0); + return exports.normalize(filter(paths, function(p, index) { + if (typeof p !== 'string') { + throw new TypeError('Arguments to path.join must be strings'); + } + return p; + }).join('/')); +}; -/***/ 109: -/***/ (function(module, exports, __webpack_require__) { -var Symbol = __webpack_require__(7), - arrayMap = __webpack_require__(110), - isArray = __webpack_require__(70), - isSymbol = __webpack_require__(72); +// path.relative(from, to) +// posix version +exports.relative = function(from, to) { + from = exports.resolve(from).substr(1); + to = exports.resolve(to).substr(1); -/** Used as references for various `Number` constants. */ -var INFINITY = 1 / 0; + function trim(arr) { + var start = 0; + for (; start < arr.length; start++) { + if (arr[start] !== '') break; + } -/** Used to convert symbols to primitives and strings. */ -var symbolProto = Symbol ? Symbol.prototype : undefined, - symbolToString = symbolProto ? symbolProto.toString : undefined; + var end = arr.length - 1; + for (; end >= 0; end--) { + if (arr[end] !== '') break; + } -/** - * The base implementation of `_.toString` which doesn't convert nullish - * values to empty strings. - * - * @private - * @param {*} value The value to process. - * @returns {string} Returns the string. - */ -function baseToString(value) { - // Exit early for strings to avoid a performance hit in some environments. - if (typeof value == 'string') { - return value; - } - if (isArray(value)) { - // Recursively convert values (susceptible to call stack limits). - return arrayMap(value, baseToString) + ''; - } - if (isSymbol(value)) { - return symbolToString ? symbolToString.call(value) : ''; + if (start > end) return []; + return arr.slice(start, end - start + 1); } - var result = (value + ''); - return (result == '0' && (1 / value) == -INFINITY) ? '-0' : result; -} - -module.exports = baseToString; + var fromParts = trim(from.split('/')); + var toParts = trim(to.split('/')); -/***/ }), - -/***/ 11: -/***/ (function(module, exports) { - -/** Used for built-in method references. */ -var objectProto = Object.prototype; - -/** - * Used to resolve the - * [`toStringTag`](http://ecma-international.org/ecma-262/7.0/#sec-object.prototype.tostring) - * of values. - */ -var nativeObjectToString = objectProto.toString; - -/** - * Converts `value` to a string using `Object.prototype.toString`. - * - * @private - * @param {*} value The value to convert. - * @returns {string} Returns the converted string. - */ -function objectToString(value) { - return nativeObjectToString.call(value); -} - -module.exports = objectToString; - - -/***/ }), - -/***/ 110: -/***/ (function(module, exports) { - -/** - * A specialized version of `_.map` for arrays without support for iteratee - * shorthands. - * - * @private - * @param {Array} [array] The array to iterate over. - * @param {Function} iteratee The function invoked per iteration. - * @returns {Array} Returns the new mapped array. - */ -function arrayMap(array, iteratee) { - var index = -1, - length = array == null ? 0 : array.length, - result = Array(length); - - while (++index < length) { - result[index] = iteratee(array[index], index, array); + var length = Math.min(fromParts.length, toParts.length); + var samePartsLength = length; + for (var i = 0; i < length; i++) { + if (fromParts[i] !== toParts[i]) { + samePartsLength = i; + break; + } } - return result; -} -module.exports = arrayMap; + var outputParts = []; + for (var i = samePartsLength; i < fromParts.length; i++) { + outputParts.push('..'); + } + outputParts = outputParts.concat(toParts.slice(samePartsLength)); -/***/ }), + return outputParts.join('/'); +}; -/***/ 111: -/***/ (function(module, exports, __webpack_require__) { +exports.sep = '/'; +exports.delimiter = ':'; -var isSymbol = __webpack_require__(72); +exports.dirname = function(path) { + var result = splitPath(path), + root = result[0], + dir = result[1]; -/** Used as references for various `Number` constants. */ -var INFINITY = 1 / 0; + if (!root && !dir) { + // No dirname whatsoever + return '.'; + } -/** - * Converts `value` to a string key if it's not a string or symbol. - * - * @private - * @param {*} value The value to inspect. - * @returns {string|symbol} Returns the key. - */ -function toKey(value) { - if (typeof value == 'string' || isSymbol(value)) { - return value; + if (dir) { + // It has a dirname, strip trailing slash + dir = dir.substr(0, dir.length - 1); } - var result = (value + ''); - return (result == '0' && (1 / value) == -INFINITY) ? '-0' : result; -} -module.exports = toKey; + return root + dir; +}; -/***/ }), +exports.basename = function(path, ext) { + var f = splitPath(path)[2]; + // TODO: make this comparison case-insensitive on windows? + if (ext && f.substr(-1 * ext.length) === ext) { + f = f.substr(0, f.length - ext.length); + } + return f; +}; -/***/ 1117: -/***/ (function(module, exports) { -module.exports = "" +exports.extname = function(path) { + return splitPath(path)[3]; +}; -/***/ }), +function filter (xs, f) { + if (xs.filter) return xs.filter(f); + var res = []; + for (var i = 0; i < xs.length; i++) { + if (f(xs[i], i, xs)) res.push(xs[i]); + } + return res; +} -/***/ 1118: -/***/ (function(module, exports) { +// String.prototype.substr - negative index don't work in IE8 +var substr = 'ab'.substr(-1) === 'b' + ? function (str, start, len) { return str.substr(start, len) } + : function (str, start, len) { + if (start < 0) start = str.length + start; + return str.substr(start, len); + } +; -module.exports = "" +/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(120))) /***/ }), -/***/ 1119: +/***/ 120: /***/ (function(module, exports) { -module.exports = "" - -/***/ }), +// shim for using process in browser +var process = module.exports = {}; -/***/ 112: -/***/ (function(module, exports, __webpack_require__) { +// cached from whatever global is present so that test runners that stub it +// don't break things. But we need to wrap it in a try catch in case it is +// wrapped in strict mode code which doesn't define any globals. It's inside a +// function because try/catches deoptimize in certain engines. -var baseSet = __webpack_require__(113); +var cachedSetTimeout; +var cachedClearTimeout; -/** - * Sets the value at `path` of `object`. If a portion of `path` doesn't exist, - * it's created. Arrays are created for missing index properties while objects - * are created for all other missing properties. Use `_.setWith` to customize - * `path` creation. - * - * **Note:** This method mutates `object`. - * - * @static - * @memberOf _ - * @since 3.7.0 - * @category Object - * @param {Object} object The object to modify. - * @param {Array|string} path The path of the property to set. - * @param {*} value The value to set. - * @returns {Object} Returns `object`. - * @example - * - * var object = { 'a': [{ 'b': { 'c': 3 } }] }; - * - * _.set(object, 'a[0].b.c', 4); - * console.log(object.a[0].b.c); - * // => 4 - * - * _.set(object, ['x', '0', 'y', 'z'], 5); - * console.log(object.x[0].y.z); - * // => 5 - */ -function set(object, path, value) { - return object == null ? object : baseSet(object, path, value); +function defaultSetTimout() { + throw new Error('setTimeout has not been defined'); +} +function defaultClearTimeout () { + throw new Error('clearTimeout has not been defined'); } +(function () { + try { + if (typeof setTimeout === 'function') { + cachedSetTimeout = setTimeout; + } else { + cachedSetTimeout = defaultSetTimout; + } + } catch (e) { + cachedSetTimeout = defaultSetTimout; + } + try { + if (typeof clearTimeout === 'function') { + cachedClearTimeout = clearTimeout; + } else { + cachedClearTimeout = defaultClearTimeout; + } + } catch (e) { + cachedClearTimeout = defaultClearTimeout; + } +} ()) +function runTimeout(fun) { + if (cachedSetTimeout === setTimeout) { + //normal enviroments in sane situations + return setTimeout(fun, 0); + } + // if setTimeout wasn't available but was latter defined + if ((cachedSetTimeout === defaultSetTimout || !cachedSetTimeout) && setTimeout) { + cachedSetTimeout = setTimeout; + return setTimeout(fun, 0); + } + try { + // when when somebody has screwed with setTimeout but no I.E. maddness + return cachedSetTimeout(fun, 0); + } catch(e){ + try { + // When we are in I.E. but the script has been evaled so I.E. doesn't trust the global object when called normally + return cachedSetTimeout.call(null, fun, 0); + } catch(e){ + // same as above but when it's a version of I.E. that must have the global object for 'this', hopfully our context correct otherwise it will throw a global error + return cachedSetTimeout.call(this, fun, 0); + } + } -module.exports = set; +} +function runClearTimeout(marker) { + if (cachedClearTimeout === clearTimeout) { + //normal enviroments in sane situations + return clearTimeout(marker); + } + // if clearTimeout wasn't available but was latter defined + if ((cachedClearTimeout === defaultClearTimeout || !cachedClearTimeout) && clearTimeout) { + cachedClearTimeout = clearTimeout; + return clearTimeout(marker); + } + try { + // when when somebody has screwed with setTimeout but no I.E. maddness + return cachedClearTimeout(marker); + } catch (e){ + try { + // When we are in I.E. but the script has been evaled so I.E. doesn't trust the global object when called normally + return cachedClearTimeout.call(null, marker); + } catch (e){ + // same as above but when it's a version of I.E. that must have the global object for 'this', hopfully our context correct otherwise it will throw a global error. + // Some versions of I.E. have different rules for clearTimeout vs setTimeout + return cachedClearTimeout.call(this, marker); + } + } -/***/ }), -/***/ 113: -/***/ (function(module, exports, __webpack_require__) { -var assignValue = __webpack_require__(114), - castPath = __webpack_require__(69), - isIndex = __webpack_require__(117), - isObject = __webpack_require__(84), - toKey = __webpack_require__(111); +} +var queue = []; +var draining = false; +var currentQueue; +var queueIndex = -1; -/** - * The base implementation of `_.set`. - * - * @private - * @param {Object} object The object to modify. - * @param {Array|string} path The path of the property to set. - * @param {*} value The value to set. - * @param {Function} [customizer] The function to customize path creation. - * @returns {Object} Returns `object`. - */ -function baseSet(object, path, value, customizer) { - if (!isObject(object)) { - return object; - } - path = castPath(path, object); - - var index = -1, - length = path.length, - lastIndex = length - 1, - nested = object; - - while (nested != null && ++index < length) { - var key = toKey(path[index]), - newValue = value; - - if (index != lastIndex) { - var objValue = nested[key]; - newValue = customizer ? customizer(objValue, key, nested) : undefined; - if (newValue === undefined) { - newValue = isObject(objValue) - ? objValue - : (isIndex(path[index + 1]) ? [] : {}); - } +function cleanUpNextTick() { + if (!draining || !currentQueue) { + return; + } + draining = false; + if (currentQueue.length) { + queue = currentQueue.concat(queue); + } else { + queueIndex = -1; + } + if (queue.length) { + drainQueue(); } - assignValue(nested, key, newValue); - nested = nested[key]; - } - return object; } -module.exports = baseSet; - - -/***/ }), - -/***/ 114: -/***/ (function(module, exports, __webpack_require__) { - -var baseAssignValue = __webpack_require__(115), - eq = __webpack_require__(97); - -/** Used for built-in method references. */ -var objectProto = Object.prototype; - -/** Used to check objects for own properties. */ -var hasOwnProperty = objectProto.hasOwnProperty; +function drainQueue() { + if (draining) { + return; + } + var timeout = runTimeout(cleanUpNextTick); + draining = true; -/** - * Assigns `value` to `key` of `object` if the existing value is not equivalent - * using [`SameValueZero`](http://ecma-international.org/ecma-262/7.0/#sec-samevaluezero) - * for equality comparisons. - * - * @private - * @param {Object} object The object to modify. - * @param {string} key The key of the property to assign. - * @param {*} value The value to assign. - */ -function assignValue(object, key, value) { - var objValue = object[key]; - if (!(hasOwnProperty.call(object, key) && eq(objValue, value)) || - (value === undefined && !(key in object))) { - baseAssignValue(object, key, value); - } + var len = queue.length; + while(len) { + currentQueue = queue; + queue = []; + while (++queueIndex < len) { + if (currentQueue) { + currentQueue[queueIndex].run(); + } + } + queueIndex = -1; + len = queue.length; + } + currentQueue = null; + draining = false; + runClearTimeout(timeout); } -module.exports = assignValue; - - -/***/ }), - -/***/ 115: -/***/ (function(module, exports, __webpack_require__) { - -var defineProperty = __webpack_require__(116); - -/** - * The base implementation of `assignValue` and `assignMergeValue` without - * value checks. - * - * @private - * @param {Object} object The object to modify. - * @param {string} key The key of the property to assign. - * @param {*} value The value to assign. - */ -function baseAssignValue(object, key, value) { - if (key == '__proto__' && defineProperty) { - defineProperty(object, key, { - 'configurable': true, - 'enumerable': true, - 'value': value, - 'writable': true - }); - } else { - object[key] = value; - } -} - -module.exports = baseAssignValue; +process.nextTick = function (fun) { + var args = new Array(arguments.length - 1); + if (arguments.length > 1) { + for (var i = 1; i < arguments.length; i++) { + args[i - 1] = arguments[i]; + } + } + queue.push(new Item(fun, args)); + if (queue.length === 1 && !draining) { + runTimeout(drainQueue); + } +}; +// v8 likes predictible objects +function Item(fun, array) { + this.fun = fun; + this.array = array; +} +Item.prototype.run = function () { + this.fun.apply(null, this.array); +}; +process.title = 'browser'; +process.browser = true; +process.env = {}; +process.argv = []; +process.version = ''; // empty string to avoid regexp issues +process.versions = {}; -/***/ }), +function noop() {} -/***/ 116: -/***/ (function(module, exports, __webpack_require__) { +process.on = noop; +process.addListener = noop; +process.once = noop; +process.off = noop; +process.removeListener = noop; +process.removeAllListeners = noop; +process.emit = noop; +process.prependListener = noop; +process.prependOnceListener = noop; -var getNative = __webpack_require__(81); +process.listeners = function (name) { return [] } -var defineProperty = (function() { - try { - var func = getNative(Object, 'defineProperty'); - func({}, '', {}); - return func; - } catch (e) {} -}()); +process.binding = function (name) { + throw new Error('process.binding is not supported'); +}; -module.exports = defineProperty; +process.cwd = function () { return '/' }; +process.chdir = function (dir) { + throw new Error('process.chdir is not supported'); +}; +process.umask = function() { return 0; }; /***/ }), -/***/ 117: -/***/ (function(module, exports) { +/***/ 121: +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. -/** Used as references for various `Number` constants. */ -var MAX_SAFE_INTEGER = 9007199254740991; -/** Used to detect unsigned integer values. */ -var reIsUint = /^(?:0|[1-9]\d*)$/; -/** - * Checks if `value` is a valid array-like index. - * - * @private - * @param {*} value The value to check. - * @param {number} [length=MAX_SAFE_INTEGER] The upper bounds of a valid index. - * @returns {boolean} Returns `true` if `value` is a valid index, else `false`. - */ -function isIndex(value, length) { - var type = typeof value; - length = length == null ? MAX_SAFE_INTEGER : length; - - return !!length && - (type == 'number' || - (type != 'symbol' && reIsUint.test(value))) && - (value > -1 && value % 1 == 0 && value < length); +// If obj.hasOwnProperty has been overridden, then calling +// obj.hasOwnProperty(prop) will break. +// See: https://github.com/joyent/node/issues/1707 +function hasOwnProperty(obj, prop) { + return Object.prototype.hasOwnProperty.call(obj, prop); } -module.exports = isIndex; +module.exports = function(qs, sep, eq, options) { + sep = sep || '&'; + eq = eq || '='; + var obj = {}; + if (typeof qs !== 'string' || qs.length === 0) { + return obj; + } -/***/ }), + var regexp = /\+/g; + qs = qs.split(sep); -/***/ 1174: -/***/ (function(module, exports) { + var maxKeys = 1000; + if (options && typeof options.maxKeys === 'number') { + maxKeys = options.maxKeys; + } -module.exports = "image/svg+xml" + var len = qs.length; + // maxKeys <= 0 means that we should not limit keys count + if (maxKeys > 0 && len > maxKeys) { + len = maxKeys; + } -/***/ }), + for (var i = 0; i < len; ++i) { + var x = qs[i].replace(regexp, '%20'), + idx = x.indexOf(eq), + kstr, vstr, k, v; -/***/ 118: -/***/ (function(module, exports) { + if (idx >= 0) { + kstr = x.substr(0, idx); + vstr = x.substr(idx + 1); + } else { + kstr = x; + vstr = ''; + } + + k = decodeURIComponent(kstr); + v = decodeURIComponent(vstr); + + if (!hasOwnProperty(obj, k)) { + obj[k] = v; + } else if (isArray(obj[k])) { + obj[k].push(v); + } else { + obj[k] = [obj[k], v]; + } + } + + return obj; +}; +var isArray = Array.isArray || function (xs) { + return Object.prototype.toString.call(xs) === '[object Array]'; +}; /***/ }), -/***/ 119: +/***/ 122: /***/ (function(module, exports, __webpack_require__) { -/* WEBPACK VAR INJECTION */(function(process) {// Copyright Joyent, Inc. and other Node contributors. +"use strict"; +// Copyright Joyent, Inc. and other Node contributors. // // Permission is hereby granted, free of charge, to any person obtaining a // copy of this software and associated documentation files (the // "Software"), to deal in the Software without restriction, including // without limitation the rights to use, copy, modify, merge, publish, @@ -819,943 +711,431 @@ // NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, // DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE // USE OR OTHER DEALINGS IN THE SOFTWARE. -// resolves . and .. elements in a path array with directory names there -// must be no slashes, empty elements, or device names (c:\) in the array -// (so also no leading and trailing slashes - it does not distinguish -// relative and absolute paths) -function normalizeArray(parts, allowAboveRoot) { - // if the path tries to go above the root, `up` ends up > 0 - var up = 0; - for (var i = parts.length - 1; i >= 0; i--) { - var last = parts[i]; - if (last === '.') { - parts.splice(i, 1); - } else if (last === '..') { - parts.splice(i, 1); - up++; - } else if (up) { - parts.splice(i, 1); - up--; - } - } - - // if the path is allowed to go above the root, restore leading ..s - if (allowAboveRoot) { - for (; up--; up) { - parts.unshift('..'); - } - } - - return parts; -} -// Split a filename into [root, dir, basename, ext], unix version -// 'root' is just a slash, or nothing. -var splitPathRe = - /^(\/?|)([\s\S]*?)((?:\.{1,2}|[^\/]+?|)(\.[^.\/]*|))(?:[\/]*)$/; -var splitPath = function(filename) { - return splitPathRe.exec(filename).slice(1); -}; -// path.resolve([from ...], to) -// posix version -exports.resolve = function() { - var resolvedPath = '', - resolvedAbsolute = false; +var stringifyPrimitive = function(v) { + switch (typeof v) { + case 'string': + return v; - for (var i = arguments.length - 1; i >= -1 && !resolvedAbsolute; i--) { - var path = (i >= 0) ? arguments[i] : process.cwd(); + case 'boolean': + return v ? 'true' : 'false'; - // Skip empty and invalid entries - if (typeof path !== 'string') { - throw new TypeError('Arguments to path.resolve must be strings'); - } else if (!path) { - continue; - } + case 'number': + return isFinite(v) ? v : ''; - resolvedPath = path + '/' + resolvedPath; - resolvedAbsolute = path.charAt(0) === '/'; + default: + return ''; } - - // At this point the path should be resolved to a full absolute path, but - // handle relative paths to be safe (might happen when process.cwd() fails) - - // Normalize the path - resolvedPath = normalizeArray(filter(resolvedPath.split('/'), function(p) { - return !!p; - }), !resolvedAbsolute).join('/'); - - return ((resolvedAbsolute ? '/' : '') + resolvedPath) || '.'; }; -// path.normalize(path) -// posix version -exports.normalize = function(path) { - var isAbsolute = exports.isAbsolute(path), - trailingSlash = substr(path, -1) === '/'; +module.exports = function(obj, sep, eq, name) { + sep = sep || '&'; + eq = eq || '='; + if (obj === null) { + obj = undefined; + } - // Normalize the path - path = normalizeArray(filter(path.split('/'), function(p) { - return !!p; - }), !isAbsolute).join('/'); + if (typeof obj === 'object') { + return map(objectKeys(obj), function(k) { + var ks = encodeURIComponent(stringifyPrimitive(k)) + eq; + if (isArray(obj[k])) { + return map(obj[k], function(v) { + return ks + encodeURIComponent(stringifyPrimitive(v)); + }).join(sep); + } else { + return ks + encodeURIComponent(stringifyPrimitive(obj[k])); + } + }).join(sep); - if (!path && !isAbsolute) { - path = '.'; - } - if (path && trailingSlash) { - path += '/'; } - return (isAbsolute ? '/' : '') + path; + if (!name) return ''; + return encodeURIComponent(stringifyPrimitive(name)) + eq + + encodeURIComponent(stringifyPrimitive(obj)); }; -// posix version -exports.isAbsolute = function(path) { - return path.charAt(0) === '/'; +var isArray = Array.isArray || function (xs) { + return Object.prototype.toString.call(xs) === '[object Array]'; }; -// posix version -exports.join = function() { - var paths = Array.prototype.slice.call(arguments, 0); - return exports.normalize(filter(paths, function(p, index) { - if (typeof p !== 'string') { - throw new TypeError('Arguments to path.join must be strings'); - } - return p; - }).join('/')); +function map (xs, f) { + if (xs.map) return xs.map(f); + var res = []; + for (var i = 0; i < xs.length; i++) { + res.push(f(xs[i], i)); + } + return res; +} + +var objectKeys = Object.keys || function (obj) { + var res = []; + for (var key in obj) { + if (Object.prototype.hasOwnProperty.call(obj, key)) res.push(key); + } + return res; }; -// path.relative(from, to) -// posix version -exports.relative = function(from, to) { - from = exports.resolve(from).substr(1); - to = exports.resolve(to).substr(1); +/***/ }), - function trim(arr) { - var start = 0; - for (; start < arr.length; start++) { - if (arr[start] !== '') break; - } +/***/ 1233: +/***/ (function(module, exports) { - var end = arr.length - 1; - for (; end >= 0; end--) { - if (arr[end] !== '') break; - } +module.exports = "" - if (start > end) return []; - return arr.slice(start, end - start + 1); - } +/***/ }), - var fromParts = trim(from.split('/')); - var toParts = trim(to.split('/')); +/***/ 1290: +/***/ (function(module, exports) { - var length = Math.min(fromParts.length, toParts.length); - var samePartsLength = length; - for (var i = 0; i < length; i++) { - if (fromParts[i] !== toParts[i]) { - samePartsLength = i; - break; - } - } +module.exports = "" - var outputParts = []; - for (var i = samePartsLength; i < fromParts.length; i++) { - outputParts.push('..'); - } +/***/ }), - outputParts = outputParts.concat(toParts.slice(samePartsLength)); +/***/ 1301: +/***/ (function(module, exports) { - return outputParts.join('/'); -}; +// removed by extract-text-webpack-plugin -exports.sep = '/'; -exports.delimiter = ':'; +/***/ }), -exports.dirname = function(path) { - var result = splitPath(path), - root = result[0], - dir = result[1]; +/***/ 1309: +/***/ (function(module, exports) { - if (!root && !dir) { - // No dirname whatsoever - return '.'; - } +// removed by extract-text-webpack-plugin - if (dir) { - // It has a dirname, strip trailing slash - dir = dir.substr(0, dir.length - 1); - } +/***/ }), - return root + dir; -}; +/***/ 1310: +/***/ (function(module, exports) { +// removed by extract-text-webpack-plugin -exports.basename = function(path, ext) { - var f = splitPath(path)[2]; - // TODO: make this comparison case-insensitive on windows? - if (ext && f.substr(-1 * ext.length) === ext) { - f = f.substr(0, f.length - ext.length); - } - return f; -}; +/***/ }), +/***/ 1347: +/***/ (function(module, exports) { -exports.extname = function(path) { - return splitPath(path)[3]; -}; +module.exports = "" -function filter (xs, f) { - if (xs.filter) return xs.filter(f); - var res = []; - for (var i = 0; i < xs.length; i++) { - if (f(xs[i], i, xs)) res.push(xs[i]); - } - return res; -} +/***/ }), -// String.prototype.substr - negative index don't work in IE8 -var substr = 'ab'.substr(-1) === 'b' - ? function (str, start, len) { return str.substr(start, len) } - : function (str, start, len) { - if (start < 0) start = str.length + start; - return str.substr(start, len); - } -; +/***/ 1348: +/***/ (function(module, exports) { -/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(120))) +module.exports = "" /***/ }), -/***/ 120: -/***/ (function(module, exports) { +/***/ 1355: +/***/ (function(module, exports, __webpack_require__) { -// shim for using process in browser -var process = module.exports = {}; +"use strict"; -// cached from whatever global is present so that test runners that stub it -// don't break things. But we need to wrap it in a try catch in case it is -// wrapped in strict mode code which doesn't define any globals. It's inside a -// function because try/catches deoptimize in certain engines. -var cachedSetTimeout; -var cachedClearTimeout; +/* 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 defaultSetTimout() { - throw new Error('setTimeout has not been defined'); -} -function defaultClearTimeout () { - throw new Error('clearTimeout has not been defined'); -} -(function () { - try { - if (typeof setTimeout === 'function') { - cachedSetTimeout = setTimeout; - } else { - cachedSetTimeout = defaultSetTimout; - } - } catch (e) { - cachedSetTimeout = defaultSetTimout; - } - try { - if (typeof clearTimeout === 'function') { - cachedClearTimeout = clearTimeout; - } else { - cachedClearTimeout = defaultClearTimeout; - } - } catch (e) { - cachedClearTimeout = defaultClearTimeout; - } -} ()) -function runTimeout(fun) { - if (cachedSetTimeout === setTimeout) { - //normal enviroments in sane situations - return setTimeout(fun, 0); - } - // if setTimeout wasn't available but was latter defined - if ((cachedSetTimeout === defaultSetTimout || !cachedSetTimeout) && setTimeout) { - cachedSetTimeout = setTimeout; - return setTimeout(fun, 0); - } - try { - // when when somebody has screwed with setTimeout but no I.E. maddness - return cachedSetTimeout(fun, 0); - } catch(e){ - try { - // When we are in I.E. but the script has been evaled so I.E. doesn't trust the global object when called normally - return cachedSetTimeout.call(null, fun, 0); - } catch(e){ - // same as above but when it's a version of I.E. that must have the global object for 'this', hopfully our context correct otherwise it will throw a global error - return cachedSetTimeout.call(this, fun, 0); - } - } +const feature = __webpack_require__(1461); +module.exports = feature; -} -function runClearTimeout(marker) { - if (cachedClearTimeout === clearTimeout) { - //normal enviroments in sane situations - return clearTimeout(marker); - } - // if clearTimeout wasn't available but was latter defined - if ((cachedClearTimeout === defaultClearTimeout || !cachedClearTimeout) && clearTimeout) { - cachedClearTimeout = clearTimeout; - return clearTimeout(marker); - } - try { - // when when somebody has screwed with setTimeout but no I.E. maddness - return cachedClearTimeout(marker); - } catch (e){ - try { - // When we are in I.E. but the script has been evaled so I.E. doesn't trust the global object when called normally - return cachedClearTimeout.call(null, marker); - } catch (e){ - // same as above but when it's a version of I.E. that must have the global object for 'this', hopfully our context correct otherwise it will throw a global error. - // Some versions of I.E. have different rules for clearTimeout vs setTimeout - return cachedClearTimeout.call(this, marker); - } - } +/***/ }), +/***/ 1359: +/***/ (function(module, exports, __webpack_require__) { +"use strict"; -} -var queue = []; -var draining = false; -var currentQueue; -var queueIndex = -1; -function cleanUpNextTick() { - if (!draining || !currentQueue) { - return; - } - draining = false; - if (currentQueue.length) { - queue = currentQueue.concat(queue); - } else { - queueIndex = -1; - } - if (queue.length) { - drainQueue(); - } -} +Object.defineProperty(exports, "__esModule", { + value: true +}); -function drainQueue() { - if (draining) { - return; - } - var timeout = runTimeout(cleanUpNextTick); - draining = true; +var _Svg = __webpack_require__(1540); - var len = queue.length; - while(len) { - currentQueue = queue; - queue = []; - while (++queueIndex < len) { - if (currentQueue) { - currentQueue[queueIndex].run(); - } - } - queueIndex = -1; - len = queue.length; - } - currentQueue = null; - draining = false; - runClearTimeout(timeout); -} +var _Svg2 = _interopRequireDefault(_Svg); -process.nextTick = function (fun) { - var args = new Array(arguments.length - 1); - if (arguments.length > 1) { - for (var i = 1; i < arguments.length; i++) { - args[i - 1] = arguments[i]; - } - } - queue.push(new Item(fun, args)); - if (queue.length === 1 && !draining) { - runTimeout(drainQueue); - } -}; +__webpack_require__(1310); -// v8 likes predictible objects -function Item(fun, array) { - this.fun = fun; - this.array = array; -} -Item.prototype.run = function () { - this.fun.apply(null, this.array); -}; -process.title = 'browser'; -process.browser = true; -process.env = {}; -process.argv = []; -process.version = ''; // empty string to avoid regexp issues -process.versions = {}; +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } -function noop() {} +/* 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 . */ -process.on = noop; -process.addListener = noop; -process.once = noop; -process.off = noop; -process.removeListener = noop; -process.removeAllListeners = noop; -process.emit = noop; -process.prependListener = noop; -process.prependOnceListener = noop; +/** + * This file maps the SVG React Components in the assets/images directory. + */ -process.listeners = function (name) { return [] } +exports.default = _Svg2.default; -process.binding = function (name) { - throw new Error('process.binding is not supported'); -}; +/***/ }), -process.cwd = function () { return '/' }; -process.chdir = function (dir) { - throw new Error('process.chdir is not supported'); -}; -process.umask = function() { return 0; }; +/***/ 1363: +/***/ (function(module, exports, __webpack_require__) { +/* 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/. */ -/***/ }), +const networkRequest = __webpack_require__(1367); +const workerUtils = __webpack_require__(1368); -/***/ 121: -/***/ (function(module, exports, __webpack_require__) { +module.exports = { + networkRequest, + workerUtils +}; -"use strict"; -// Copyright Joyent, Inc. and other Node contributors. -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the -// "Software"), to deal in the Software without restriction, including -// without limitation the rights to use, copy, modify, merge, publish, -// distribute, sublicense, and/or sell copies of the Software, and to permit -// persons to whom the Software is furnished to do so, subject to the -// following conditions: -// -// The above copyright notice and this permission notice shall be included -// in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN -// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, -// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR -// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE -// USE OR OTHER DEALINGS IN THE SOFTWARE. +/***/ }), +/***/ 1367: +/***/ (function(module, exports) { +/* 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/. */ -// If obj.hasOwnProperty has been overridden, then calling -// obj.hasOwnProperty(prop) will break. -// See: https://github.com/joyent/node/issues/1707 -function hasOwnProperty(obj, prop) { - return Object.prototype.hasOwnProperty.call(obj, prop); +function networkRequest(url, opts) { + return fetch(url, { + cache: opts.loadFromCache ? "default" : "no-cache" + }).then(res => { + if (res.status >= 200 && res.status < 300) { + return res.text().then(text => ({ content: text })); + } + return Promise.reject(`request failed with status ${res.status}`); + }); } -module.exports = function(qs, sep, eq, options) { - sep = sep || '&'; - eq = eq || '='; - var obj = {}; +module.exports = networkRequest; - if (typeof qs !== 'string' || qs.length === 0) { - return obj; - } +/***/ }), - var regexp = /\+/g; - qs = qs.split(sep); +/***/ 1368: +/***/ (function(module, exports) { - var maxKeys = 1000; - if (options && typeof options.maxKeys === 'number') { - maxKeys = options.maxKeys; - } +function _asyncToGenerator(fn) { return function () { var gen = fn.apply(this, arguments); return new Promise(function (resolve, reject) { function step(key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { return Promise.resolve(value).then(function (value) { step("next", value); }, function (err) { step("throw", err); }); } } return step("next"); }); }; } - var len = qs.length; - // maxKeys <= 0 means that we should not limit keys count - if (maxKeys > 0 && len > maxKeys) { - len = maxKeys; - } +function WorkerDispatcher() { + this.msgId = 1; + this.worker = null; +} /* 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/. */ - for (var i = 0; i < len; ++i) { - var x = qs[i].replace(regexp, '%20'), - idx = x.indexOf(eq), - kstr, vstr, k, v; +WorkerDispatcher.prototype = { + start(url) { + this.worker = new Worker(url); + this.worker.onerror = () => { + console.error(`Error in worker ${url}`); + }; + }, - if (idx >= 0) { - kstr = x.substr(0, idx); - vstr = x.substr(idx + 1); - } else { - kstr = x; - vstr = ''; + stop() { + if (!this.worker) { + return; } - k = decodeURIComponent(kstr); - v = decodeURIComponent(vstr); + this.worker.terminate(); + this.worker = null; + }, - if (!hasOwnProperty(obj, k)) { - obj[k] = v; - } else if (isArray(obj[k])) { - obj[k].push(v); - } else { - obj[k] = [obj[k], v]; - } - } + task(method, { queue = false } = {}) { + const calls = []; + const push = args => { + return new Promise((resolve, reject) => { + if (queue && calls.length === 0) { + Promise.resolve().then(flush); + } - return obj; -}; + calls.push([args, resolve, reject]); -var isArray = Array.isArray || function (xs) { - return Object.prototype.toString.call(xs) === '[object Array]'; -}; + if (!queue) { + flush(); + } + }); + }; + const flush = () => { + const items = calls.slice(); + calls.length = 0; -/***/ }), + const id = this.msgId++; + this.worker.postMessage({ id, method, calls: items.map(item => item[0]) }); -/***/ 122: -/***/ (function(module, exports, __webpack_require__) { + const listener = ({ data: result }) => { + if (result.id !== id) { + return; + } -"use strict"; -// Copyright Joyent, Inc. and other Node contributors. -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the -// "Software"), to deal in the Software without restriction, including -// without limitation the rights to use, copy, modify, merge, publish, -// distribute, sublicense, and/or sell copies of the Software, and to permit -// persons to whom the Software is furnished to do so, subject to the -// following conditions: -// -// The above copyright notice and this permission notice shall be included -// in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN -// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, -// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR -// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE -// USE OR OTHER DEALINGS IN THE SOFTWARE. + if (!this.worker) { + return; + } + this.worker.removeEventListener("message", listener); + result.results.forEach((resultData, i) => { + const [, resolve, reject] = items[i]; -var stringifyPrimitive = function(v) { - switch (typeof v) { - case 'string': - return v; + if (resultData.error) { + reject(resultData.error); + } else { + resolve(resultData.response); + } + }); + }; - case 'boolean': - return v ? 'true' : 'false'; - - case 'number': - return isFinite(v) ? v : ''; + this.worker.addEventListener("message", listener); + }; - default: - return ''; + return (...args) => push(args); } }; -module.exports = function(obj, sep, eq, name) { - sep = sep || '&'; - eq = eq || '='; - if (obj === null) { - obj = undefined; - } +function workerHandler(publicInterface) { + return function (msg) { + const { id, method, calls } = msg.data; - if (typeof obj === 'object') { - return map(objectKeys(obj), function(k) { - var ks = encodeURIComponent(stringifyPrimitive(k)) + eq; - if (isArray(obj[k])) { - return map(obj[k], function(v) { - return ks + encodeURIComponent(stringifyPrimitive(v)); - }).join(sep); - } else { - return ks + encodeURIComponent(stringifyPrimitive(obj[k])); + Promise.all(calls.map(args => { + try { + const response = publicInterface[method].apply(undefined, args); + if (response instanceof Promise) { + return response.then(val => ({ response: val }), + // Error can't be sent via postMessage, so be sure to + // convert to string. + err => ({ error: err.toString() })); + } else { + return { response }; + } + } catch (error) { + // Error can't be sent via postMessage, so be sure to convert to + // string. + return { error: error.toString() }; } - }).join(sep); - - } - - if (!name) return ''; - return encodeURIComponent(stringifyPrimitive(name)) + eq + - encodeURIComponent(stringifyPrimitive(obj)); -}; - -var isArray = Array.isArray || function (xs) { - return Object.prototype.toString.call(xs) === '[object Array]'; -}; - -function map (xs, f) { - if (xs.map) return xs.map(f); - var res = []; - for (var i = 0; i < xs.length; i++) { - res.push(f(xs[i], i)); - } - return res; + })).then(results => { + self.postMessage({ id, results }); + }); + }; } -var objectKeys = Object.keys || function (obj) { - var res = []; - for (var key in obj) { - if (Object.prototype.hasOwnProperty.call(obj, key)) res.push(key); - } - return res; -}; - - -/***/ }), - -/***/ 1233: -/***/ (function(module, exports) { - -module.exports = "" - -/***/ }), - -/***/ 1290: -/***/ (function(module, exports) { - -module.exports = "" - -/***/ }), - -/***/ 1301: -/***/ (function(module, exports) { - -// removed by extract-text-webpack-plugin - -/***/ }), - -/***/ 1309: -/***/ (function(module, exports) { - -// removed by extract-text-webpack-plugin - -/***/ }), - -/***/ 1310: -/***/ (function(module, exports) { - -// removed by extract-text-webpack-plugin - -/***/ }), - -/***/ 1347: -/***/ (function(module, exports) { - -module.exports = "" - -/***/ }), +function streamingWorkerHandler(publicInterface, { timeout = 100 } = {}, worker = self) { + let streamingWorker = (() => { + var _ref = _asyncToGenerator(function* (id, tasks) { + let isWorking = true; -/***/ 1348: -/***/ (function(module, exports) { + const intervalId = setTimeout(function () { + isWorking = false; + }, timeout); -module.exports = "" + const results = []; + while (tasks.length !== 0 && isWorking) { + const { callback, context, args } = tasks.shift(); + const result = yield callback.call(context, args); + results.push(result); + } + worker.postMessage({ id, status: "pending", data: results }); + clearInterval(intervalId); -/***/ }), + if (tasks.length !== 0) { + yield streamingWorker(id, tasks); + } + }); -/***/ 1355: -/***/ (function(module, exports, __webpack_require__) { + return function streamingWorker(_x, _x2) { + return _ref.apply(this, arguments); + }; + })(); -"use strict"; + return (() => { + var _ref2 = _asyncToGenerator(function* (msg) { + const { id, method, args } = msg.data; + const workerMethod = publicInterface[method]; + if (!workerMethod) { + console.error(`Could not find ${method} defined in worker.`); + } + worker.postMessage({ id, status: "start" }); + try { + const tasks = workerMethod(args); + yield streamingWorker(id, tasks); + worker.postMessage({ id, status: "done" }); + } catch (error) { + worker.postMessage({ id, status: "error", error }); + } + }); -const feature = __webpack_require__(1461); + return function (_x3) { + return _ref2.apply(this, arguments); + }; + })(); +} -module.exports = feature; +module.exports = { + WorkerDispatcher, + workerHandler, + streamingWorkerHandler +}; /***/ }), -/***/ 1359: +/***/ 1376: /***/ (function(module, exports, __webpack_require__) { "use strict"; -Object.defineProperty(exports, "__esModule", { - value: true -}); - -var _Svg = __webpack_require__(1540); - -var _Svg2 = _interopRequireDefault(_Svg); - -__webpack_require__(1310); - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } - -/* 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 . */ - -/** - * This file maps the SVG React Components in the assets/images directory. - */ - -exports.default = _Svg2.default; - -/***/ }), - -/***/ 1363: -/***/ (function(module, exports, __webpack_require__) { - /* 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/. */ -const networkRequest = __webpack_require__(1367); -const workerUtils = __webpack_require__(1368); +const Menu = __webpack_require__(1464); +const MenuItem = __webpack_require__(1466); +const { PrefsHelper } = __webpack_require__(1467); +const Services = __webpack_require__(22); +const KeyShortcuts = __webpack_require__(1468); +const { ZoomKeys } = __webpack_require__(1469); +const EventEmitter = __webpack_require__(1382); +const SourceUtils = __webpack_require__(3637); +const Telemetry = __webpack_require__(3750); +const { getUnicodeHostname, getUnicodeUrlPath, getUnicodeUrl } = __webpack_require__(3638); module.exports = { - networkRequest, - workerUtils + KeyShortcuts, + Menu, + MenuItem, + PrefsHelper, + Services, + ZoomKeys, + EventEmitter, + SourceUtils, + Telemetry, + getUnicodeHostname, + getUnicodeUrlPath, + getUnicodeUrl }; /***/ }), -/***/ 1367: -/***/ (function(module, exports) { +/***/ 1382: +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + /* 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 networkRequest(url, opts) { - return fetch(url, { - cache: opts.loadFromCache ? "default" : "no-cache" - }).then(res => { - if (res.status >= 200 && res.status < 300) { - return res.text().then(text => ({ content: text })); - } - return Promise.reject(`request failed with status ${res.status}`); - }); -} - -module.exports = networkRequest; - -/***/ }), +var EventEmitter = function EventEmitter() {}; +module.exports = EventEmitter; -/***/ 1368: -/***/ (function(module, exports) { - -function _asyncToGenerator(fn) { return function () { var gen = fn.apply(this, arguments); return new Promise(function (resolve, reject) { function step(key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { return Promise.resolve(value).then(function (value) { step("next", value); }, function (err) { step("throw", err); }); } } return step("next"); }); }; } - -function WorkerDispatcher() { - this.msgId = 1; - this.worker = null; -} /* 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/. */ - -WorkerDispatcher.prototype = { - start(url) { - this.worker = new Worker(url); - this.worker.onerror = () => { - console.error(`Error in worker ${url}`); - }; - }, - - stop() { - if (!this.worker) { - return; - } - - this.worker.terminate(); - this.worker = null; - }, - - task(method, { queue = false } = {}) { - const calls = []; - const push = args => { - return new Promise((resolve, reject) => { - if (queue && calls.length === 0) { - Promise.resolve().then(flush); - } - - calls.push([args, resolve, reject]); - - if (!queue) { - flush(); - } - }); - }; - - const flush = () => { - const items = calls.slice(); - calls.length = 0; - - const id = this.msgId++; - this.worker.postMessage({ id, method, calls: items.map(item => item[0]) }); - - const listener = ({ data: result }) => { - if (result.id !== id) { - return; - } - - if (!this.worker) { - return; - } - - this.worker.removeEventListener("message", listener); - - result.results.forEach((resultData, i) => { - const [, resolve, reject] = items[i]; - - if (resultData.error) { - reject(resultData.error); - } else { - resolve(resultData.response); - } - }); - }; - - this.worker.addEventListener("message", listener); - }; - - return (...args) => push(args); - } -}; - -function workerHandler(publicInterface) { - return function (msg) { - const { id, method, calls } = msg.data; - - Promise.all(calls.map(args => { - try { - const response = publicInterface[method].apply(undefined, args); - if (response instanceof Promise) { - return response.then(val => ({ response: val }), - // Error can't be sent via postMessage, so be sure to - // convert to string. - err => ({ error: err.toString() })); - } else { - return { response }; - } - } catch (error) { - // Error can't be sent via postMessage, so be sure to convert to - // string. - return { error: error.toString() }; - } - })).then(results => { - self.postMessage({ id, results }); - }); - }; -} - -function streamingWorkerHandler(publicInterface, { timeout = 100 } = {}, worker = self) { - let streamingWorker = (() => { - var _ref = _asyncToGenerator(function* (id, tasks) { - let isWorking = true; - - const intervalId = setTimeout(function () { - isWorking = false; - }, timeout); - - const results = []; - while (tasks.length !== 0 && isWorking) { - const { callback, context, args } = tasks.shift(); - const result = yield callback.call(context, args); - results.push(result); - } - worker.postMessage({ id, status: "pending", data: results }); - clearInterval(intervalId); - - if (tasks.length !== 0) { - yield streamingWorker(id, tasks); - } - }); - - return function streamingWorker(_x, _x2) { - return _ref.apply(this, arguments); - }; - })(); - - return (() => { - var _ref2 = _asyncToGenerator(function* (msg) { - const { id, method, args } = msg.data; - const workerMethod = publicInterface[method]; - if (!workerMethod) { - console.error(`Could not find ${method} defined in worker.`); - } - worker.postMessage({ id, status: "start" }); - - try { - const tasks = workerMethod(args); - yield streamingWorker(id, tasks); - worker.postMessage({ id, status: "done" }); - } catch (error) { - worker.postMessage({ id, status: "error", error }); - } - }); - - return function (_x3) { - return _ref2.apply(this, arguments); - }; - })(); -} - -module.exports = { - WorkerDispatcher, - workerHandler, - streamingWorkerHandler -}; - -/***/ }), - -/***/ 1376: -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; - - -/* 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/. */ - -const Menu = __webpack_require__(1464); -const MenuItem = __webpack_require__(1466); -const { PrefsHelper } = __webpack_require__(1467); -const Services = __webpack_require__(22); -const KeyShortcuts = __webpack_require__(1468); -const { ZoomKeys } = __webpack_require__(1469); -const EventEmitter = __webpack_require__(1382); -const SourceUtils = __webpack_require__(3637); -const Telemetry = __webpack_require__(3750); -const { getUnicodeHostname, getUnicodeUrlPath, getUnicodeUrl } = __webpack_require__(3638); - -module.exports = { - KeyShortcuts, - Menu, - MenuItem, - PrefsHelper, - Services, - ZoomKeys, - EventEmitter, - SourceUtils, - Telemetry, - getUnicodeHostname, - getUnicodeUrlPath, - getUnicodeUrl -}; - -/***/ }), - -/***/ 1382: -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; - - -/* 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/. */ - -var EventEmitter = function EventEmitter() {}; -module.exports = EventEmitter; - -const promise = __webpack_require__(1465); +const promise = __webpack_require__(1465); /** * Decorate an object with event emitter functionality. * * @param Object aObjectToDecorate @@ -1868,46 +1248,10 @@ } } } }; -/***/ }), - -/***/ 14: -/***/ (function(module, exports) { - -/** - * Checks if `value` is object-like. A value is object-like if it's not `null` - * and has a `typeof` result of "object". - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category Lang - * @param {*} value The value to check. - * @returns {boolean} Returns `true` if `value` is object-like, else `false`. - * @example - * - * _.isObjectLike({}); - * // => true - * - * _.isObjectLike([1, 2, 3]); - * // => true - * - * _.isObjectLike(_.noop); - * // => false - * - * _.isObjectLike(null); - * // => false - */ -function isObjectLike(value) { - return value != null && typeof value == 'object'; -} - -module.exports = isObjectLike; - - /***/ }), /***/ 1413: /***/ (function(module, exports, __webpack_require__) { @@ -2068,23 +1412,19 @@ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const pick = __webpack_require__(67); -const put = __webpack_require__(112); +/* 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/. */ + +const { get: pick, set: put } = __webpack_require__(2); const fs = __webpack_require__(118); const path = __webpack_require__(119); let config; - -const flag = __webpack_require__(52); - -function isBrowser() { - return typeof window == "object" && typeof module == "undefined"; -} - /** * Gets a config value for a given key * e.g "chrome.webSocketPort" */ function getValue(key) { @@ -2093,71 +1433,28 @@ function setValue(key, value) { return put(config, key, value); } -function isEnabled(key) { - return config.features && typeof config.features[key] == "object" ? config.features[key].enabled : config.features[key]; +function setConfig(value) { + config = value; } -function isDevelopment() { - if (isBrowser()) { - if (true) { - return false; - } - const href = window.location ? window.location.href : ""; - return href.match(/^file:/) || href.match(/localhost:/); - } - - if (isFirefoxPanel()) { - // Default to production if compiling for the Firefox panel - return "production" === "development"; - } - return "production" !== "production"; -} - -function isTesting() { - return flag.testing; -} - -function isFirefoxPanel() { - return "firefox-panel" == "firefox-panel"; -} - -function isApplication() { - return "firefox-panel" == "application"; -} - -function isFirefox() { - return (/firefox/i.test(navigator.userAgent) - ); -} - -function setConfig(value) { - config = value; -} - -function getConfig() { - return config; +function getConfig() { + return config; } function updateLocalConfig(relativePath) { const localConfigPath = path.resolve(relativePath, "../configs/local.json"); const output = JSON.stringify(config, null, 2); fs.writeFileSync(localConfigPath, output, { flag: "w" }); return output; } module.exports = { - isEnabled, getValue, setValue, - isDevelopment, - isTesting, - isFirefoxPanel, - isApplication, - isFirefox, getConfig, setConfig, updateLocalConfig }; @@ -5449,10 +4746,17 @@ module.exports = "" /***/ }), +/***/ 2: +/***/ (function(module, exports) { + +module.exports = __WEBPACK_EXTERNAL_MODULE_2__; + +/***/ }), + /***/ 22: /***/ (function(module, exports) { module.exports = __WEBPACK_EXTERNAL_MODULE_22__; @@ -9600,1625 +8904,461 @@ // Example: https://foo.com:8888/file.js // `hostname`: "foo.com" // `host`: "foo.com:8888" let isChrome = isChromeScheme(location); - url.fileName = url.pathname ? url.pathname.slice(url.pathname.lastIndexOf("/") + 1) || "/" : "/"; - - if (isChrome) { - url.hostname = null; - url.host = null; - } - - gURLStore.set(location, url); - return url; - } catch (e) { - gURLStore.set(location, null); - return null; - } -} - -/** -* Parse a source into a short and long name as well as a host name. -* -* @param {String} source -* The source to parse. Can be a URI or names like "(eval)" or -* "self-hosted". -* @return {Object} -* An object with the following properties: -* - {String} short: A short name for the source. -* - "http://page.com/test.js#go?q=query" -> "test.js" -* - {String} long: The full, long name for the source, with - hash/query stripped. -* - "http://page.com/test.js#go?q=query" -> "http://page.com/test.js" -* - {String?} host: If available, the host name for the source. -* - "http://page.com/test.js#go?q=query" -> "page.com" -*/ -function getSourceNames(source) { - let data = gSourceNamesStore.get(source); - - if (data) { - return data; - } - - let short, long, host; - const sourceStr = source ? String(source) : ""; - - // If `data:...` uri - if (isDataScheme(sourceStr)) { - let commaIndex = sourceStr.indexOf(","); - if (commaIndex > -1) { - // The `short` name for a data URI becomes `data:` followed by the actual - // encoded content, omitting the MIME type, and charset. - short = `data:${sourceStr.substring(commaIndex + 1)}`.slice(0, 100); - let result = { short, long: sourceStr }; - gSourceNamesStore.set(source, result); - return result; - } - } - - // If Scratchpad URI, like "Scratchpad/1"; no modifications, - // and short/long are the same. - if (isScratchpadScheme(sourceStr)) { - let result = { short: sourceStr, long: sourceStr }; - gSourceNamesStore.set(source, result); - return result; - } - - const parsedUrl = parseURL(sourceStr); - - if (!parsedUrl) { - // Malformed URI. - long = sourceStr; - short = sourceStr.slice(0, 100); - } else { - host = parsedUrl.host; - - long = parsedUrl.href; - if (parsedUrl.hash) { - long = long.replace(parsedUrl.hash, ""); - } - if (parsedUrl.search) { - long = long.replace(parsedUrl.search, ""); - } - - short = parsedUrl.fileName; - // If `short` is just a slash, and we actually have a path, - // strip the slash and parse again to get a more useful short name. - // e.g. "http://foo.com/bar/" -> "bar", rather than "/" - if (short === "/" && parsedUrl.pathname !== "/") { - short = parseURL(long.replace(/\/$/, "")).fileName; - } - } - - if (!short) { - if (!long) { - long = UNKNOWN_SOURCE_STRING; - } - short = long.slice(0, 100); - } - - let result = { short, long, host }; - gSourceNamesStore.set(source, result); - return result; -} - -// For the functions below, we assume that we will never access the location -// argument out of bounds, which is indeed the vast majority of cases. -// -// They are written this way because they are hot. Each frame is checked for -// being content or chrome when processing the profile. - -function isColonSlashSlash(location, i = 0) { - return location.charCodeAt(++i) === CHAR_CODE_COLON && location.charCodeAt(++i) === CHAR_CODE_SLASH && location.charCodeAt(++i) === CHAR_CODE_SLASH; -} - -/** -* Checks for a Scratchpad URI, like "Scratchpad/1" -*/ -function isScratchpadScheme(location, i = 0) { - return location.charCodeAt(i) === CHAR_CODE_CAP_S && location.charCodeAt(++i) === CHAR_CODE_C && location.charCodeAt(++i) === CHAR_CODE_R && location.charCodeAt(++i) === CHAR_CODE_A && location.charCodeAt(++i) === CHAR_CODE_T && location.charCodeAt(++i) === CHAR_CODE_C && location.charCodeAt(++i) === CHAR_CODE_H && location.charCodeAt(++i) === CHAR_CODE_P && location.charCodeAt(++i) === CHAR_CODE_A && location.charCodeAt(++i) === CHAR_CODE_D && location.charCodeAt(++i) === CHAR_CODE_SLASH; -} - -function isDataScheme(location, i = 0) { - return location.charCodeAt(i) === CHAR_CODE_D && location.charCodeAt(++i) === CHAR_CODE_A && location.charCodeAt(++i) === CHAR_CODE_T && location.charCodeAt(++i) === CHAR_CODE_A && location.charCodeAt(++i) === CHAR_CODE_COLON; -} - -function isContentScheme(location, i = 0) { - let firstChar = location.charCodeAt(i); - - switch (firstChar) { - // "http://" or "https://" - case CHAR_CODE_H: - if (location.charCodeAt(++i) === CHAR_CODE_T && location.charCodeAt(++i) === CHAR_CODE_T && location.charCodeAt(++i) === CHAR_CODE_P) { - if (location.charCodeAt(i + 1) === CHAR_CODE_S) { - ++i; - } - return isColonSlashSlash(location, i); - } - return false; - - // "file://" - case CHAR_CODE_F: - if (location.charCodeAt(++i) === CHAR_CODE_I && location.charCodeAt(++i) === CHAR_CODE_L && location.charCodeAt(++i) === CHAR_CODE_E) { - return isColonSlashSlash(location, i); - } - return false; - - // "app://" - case CHAR_CODE_A: - if (location.charCodeAt(++i) == CHAR_CODE_P && location.charCodeAt(++i) == CHAR_CODE_P) { - return isColonSlashSlash(location, i); - } - return false; - - // "blob:" - case CHAR_CODE_B: - if (location.charCodeAt(++i) == CHAR_CODE_L && location.charCodeAt(++i) == CHAR_CODE_O && location.charCodeAt(++i) == CHAR_CODE_B && location.charCodeAt(++i) == CHAR_CODE_COLON) { - return isContentScheme(location, i + 1); - } - return false; - - default: - return false; - } -} - -function isChromeScheme(location, i = 0) { - let firstChar = location.charCodeAt(i); - - switch (firstChar) { - // "chrome://" - case CHAR_CODE_C: - if (location.charCodeAt(++i) === CHAR_CODE_H && location.charCodeAt(++i) === CHAR_CODE_R && location.charCodeAt(++i) === CHAR_CODE_O && location.charCodeAt(++i) === CHAR_CODE_M && location.charCodeAt(++i) === CHAR_CODE_E) { - return isColonSlashSlash(location, i); - } - return false; - - // "resource://" - case CHAR_CODE_R: - if (location.charCodeAt(++i) === CHAR_CODE_E && location.charCodeAt(++i) === CHAR_CODE_S && location.charCodeAt(++i) === CHAR_CODE_O && location.charCodeAt(++i) === CHAR_CODE_U && location.charCodeAt(++i) === CHAR_CODE_R && location.charCodeAt(++i) === CHAR_CODE_C && location.charCodeAt(++i) === CHAR_CODE_E) { - return isColonSlashSlash(location, i); - } - return false; - - // "jar:file://" - case CHAR_CODE_J: - if (location.charCodeAt(++i) === CHAR_CODE_A && location.charCodeAt(++i) === CHAR_CODE_R && location.charCodeAt(++i) === CHAR_CODE_COLON && location.charCodeAt(++i) === CHAR_CODE_F && location.charCodeAt(++i) === CHAR_CODE_I && location.charCodeAt(++i) === CHAR_CODE_L && location.charCodeAt(++i) === CHAR_CODE_E) { - return isColonSlashSlash(location, i); - } - return false; - - default: - return false; - } -} - -function isWASM(location, i = 0) { - return ( - // "wasm-function[" - location.charCodeAt(i) === CHAR_CODE_W && location.charCodeAt(++i) === CHAR_CODE_A && location.charCodeAt(++i) === CHAR_CODE_S && location.charCodeAt(++i) === CHAR_CODE_M && location.charCodeAt(++i) === CHAR_CODE_DASH && location.charCodeAt(++i) === CHAR_CODE_F && location.charCodeAt(++i) === CHAR_CODE_U && location.charCodeAt(++i) === CHAR_CODE_N && location.charCodeAt(++i) === CHAR_CODE_C && location.charCodeAt(++i) === CHAR_CODE_T && location.charCodeAt(++i) === CHAR_CODE_I && location.charCodeAt(++i) === CHAR_CODE_O && location.charCodeAt(++i) === CHAR_CODE_N && location.charCodeAt(++i) === CHAR_CODE_L_SQUARE_BRACKET - ); -} - -/** -* A utility method to get the file name from a sourcemapped location -* The sourcemap location can be in any form. This method returns a -* formatted file name for different cases like Windows or OSX. -* @param source -* @returns String -*/ -function getSourceMappedFile(source) { - // If sourcemapped source is a OSX path, return - // the characters after last "/". - // If sourcemapped source is a Windowss path, return - // the characters after last "\\". - if (source.lastIndexOf("/") >= 0) { - source = source.slice(source.lastIndexOf("/") + 1); - } else if (source.lastIndexOf("\\") >= 0) { - source = source.slice(source.lastIndexOf("\\") + 1); - } - return source; -} - -module.exports = { - parseURL, - getSourceNames, - isScratchpadScheme, - isChromeScheme, - isContentScheme, - isWASM, - isDataScheme, - getSourceMappedFile -}; - -/***/ }), - -/***/ 3753: -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; -/* 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/. */ - -// This file is a chrome-API-free version of the module -// devtools/client/shared/unicode-url.js in the mozilla-central repository, so -// that it can be used in Chrome-API-free applications, such as the Launchpad. -// But because of this, it cannot take advantage of utilizing chrome APIs and -// should implement the similar functionalities on its own. -// -// Please keep in mind that if the feature in this file has changed, don't -// forget to also change that accordingly in -// devtools/client/shared/unicode-url.js in the mozilla-central repository. - - - -const punycode = __webpack_require__(3641); - -/** - * Gets a readble Unicode hostname from a hostname. - * - * If the `hostname` is a readable ASCII hostname, such as example.org, then - * this function will simply return the original `hostname`. - * - * If the `hostname` is a Punycode hostname representing a Unicode domain name, - * such as xn--g6w.xn--8pv, then this function will return the readable Unicode - * domain name by decoding the Punycode hostname. - * - * @param {string} hostname - * the hostname from which the Unicode hostname will be - * parsed, such as example.org, xn--g6w.xn--8pv. - * @return {string} The Unicode hostname. It may be the same as the `hostname` - * passed to this function if the `hostname` itself is - * a readable ASCII hostname or a Unicode hostname. - */ -function getUnicodeHostname(hostname) { - try { - return punycode.toUnicode(hostname); - } catch (err) {} - return hostname; -} - -/** - * Gets a readble Unicode URL pathname from a URL pathname. - * - * If the `urlPath` is a readable ASCII URL pathname, such as /a/b/c.js, then - * this function will simply return the original `urlPath`. - * - * If the `urlPath` is a URI-encoded pathname, such as %E8%A9%A6/%E6%B8%AC.js, - * then this function will return the readable Unicode pathname. - * - * If the `urlPath` is a malformed URL pathname, then this function will simply - * return the original `urlPath`. - * - * @param {string} urlPath - * the URL path from which the Unicode URL path will be parsed, - * such as /a/b/c.js, %E8%A9%A6/%E6%B8%AC.js. - * @return {string} The Unicode URL Path. It may be the same as the `urlPath` - * passed to this function if the `urlPath` itself is a readable - * ASCII url or a Unicode url. - */ -function getUnicodeUrlPath(urlPath) { - try { - return decodeURIComponent(urlPath); - } catch (err) {} - return urlPath; -} - -/** - * Gets a readable Unicode URL from a URL. - * - * If the `url` is a readable ASCII URL, such as http://example.org/a/b/c.js, - * then this function will simply return the original `url`. - * - * If the `url` includes either an unreadable Punycode domain name or an - * unreadable URI-encoded pathname, such as - * http://xn--g6w.xn--8pv/%E8%A9%A6/%E6%B8%AC.js, then this function will return - * the readable URL by decoding all its unreadable URL components to Unicode - * characters. - * - * If the `url` is a malformed URL, then this function will return the original - * `url`. - * - * If the `url` is a data: URI, then this function will return the original - * `url`. - * - * @param {string} url - * the full URL, or a data: URI. from which the readable URL - * will be parsed, such as, http://example.org/a/b/c.js, - * http://xn--g6w.xn--8pv/%E8%A9%A6/%E6%B8%AC.js - * @return {string} The readable URL. It may be the same as the `url` passed to - * this function if the `url` itself is readable. - */ -function getUnicodeUrl(url) { - try { - const { protocol, hostname } = new URL(url); - if (protocol === "data:") { - // Never convert a data: URI. - return url; - } - const readableHostname = getUnicodeHostname(hostname); - url = decodeURIComponent(url); - return url.replace(hostname, readableHostname); - } catch (err) {} - return url; -} - -module.exports = { - getUnicodeHostname, - getUnicodeUrlPath, - getUnicodeUrl -}; - -/***/ }), - -/***/ 4: -/***/ (function(module, exports) { - -module.exports = __WEBPACK_EXTERNAL_MODULE_4__; - -/***/ }), - -/***/ 52: -/***/ (function(module, exports) { - -module.exports = __WEBPACK_EXTERNAL_MODULE_52__; - -/***/ }), - -/***/ 6: -/***/ (function(module, exports, __webpack_require__) { - -var Symbol = __webpack_require__(7), - getRawTag = __webpack_require__(10), - objectToString = __webpack_require__(11); - -/** `Object#toString` result references. */ -var nullTag = '[object Null]', - undefinedTag = '[object Undefined]'; - -/** Built-in value references. */ -var symToStringTag = Symbol ? Symbol.toStringTag : undefined; - -/** - * The base implementation of `getTag` without fallbacks for buggy environments. - * - * @private - * @param {*} value The value to query. - * @returns {string} Returns the `toStringTag`. - */ -function baseGetTag(value) { - if (value == null) { - return value === undefined ? undefinedTag : nullTag; - } - return (symToStringTag && symToStringTag in Object(value)) - ? getRawTag(value) - : objectToString(value); -} - -module.exports = baseGetTag; - - -/***/ }), - -/***/ 66: -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; - - -exports.decode = exports.parse = __webpack_require__(121); -exports.encode = exports.stringify = __webpack_require__(122); - - -/***/ }), - -/***/ 67: -/***/ (function(module, exports, __webpack_require__) { - -var baseGet = __webpack_require__(68); - -/** - * Gets the value at `path` of `object`. If the resolved value is - * `undefined`, the `defaultValue` is returned in its place. - * - * @static - * @memberOf _ - * @since 3.7.0 - * @category Object - * @param {Object} object The object to query. - * @param {Array|string} path The path of the property to get. - * @param {*} [defaultValue] The value returned for `undefined` resolved values. - * @returns {*} Returns the resolved value. - * @example - * - * var object = { 'a': [{ 'b': { 'c': 3 } }] }; - * - * _.get(object, 'a[0].b.c'); - * // => 3 - * - * _.get(object, ['a', '0', 'b', 'c']); - * // => 3 - * - * _.get(object, 'a.b.c', 'default'); - * // => 'default' - */ -function get(object, path, defaultValue) { - var result = object == null ? undefined : baseGet(object, path); - return result === undefined ? defaultValue : result; -} - -module.exports = get; - - -/***/ }), - -/***/ 68: -/***/ (function(module, exports, __webpack_require__) { - -var castPath = __webpack_require__(69), - toKey = __webpack_require__(111); - -/** - * The base implementation of `_.get` without support for default values. - * - * @private - * @param {Object} object The object to query. - * @param {Array|string} path The path of the property to get. - * @returns {*} Returns the resolved value. - */ -function baseGet(object, path) { - path = castPath(path, object); - - var index = 0, - length = path.length; - - while (object != null && index < length) { - object = object[toKey(path[index++])]; - } - return (index && index == length) ? object : undefined; -} - -module.exports = baseGet; - - -/***/ }), - -/***/ 69: -/***/ (function(module, exports, __webpack_require__) { - -var isArray = __webpack_require__(70), - isKey = __webpack_require__(71), - stringToPath = __webpack_require__(73), - toString = __webpack_require__(108); - -/** - * Casts `value` to a path array if it's not one. - * - * @private - * @param {*} value The value to inspect. - * @param {Object} [object] The object to query keys on. - * @returns {Array} Returns the cast property path array. - */ -function castPath(value, object) { - if (isArray(value)) { - return value; - } - return isKey(value, object) ? [value] : stringToPath(toString(value)); -} - -module.exports = castPath; - - -/***/ }), - -/***/ 7: -/***/ (function(module, exports, __webpack_require__) { - -var root = __webpack_require__(8); - -/** Built-in value references. */ -var Symbol = root.Symbol; - -module.exports = Symbol; - - -/***/ }), - -/***/ 70: -/***/ (function(module, exports) { - -/** - * Checks if `value` is classified as an `Array` object. - * - * @static - * @memberOf _ - * @since 0.1.0 - * @category Lang - * @param {*} value The value to check. - * @returns {boolean} Returns `true` if `value` is an array, else `false`. - * @example - * - * _.isArray([1, 2, 3]); - * // => true - * - * _.isArray(document.body.children); - * // => false - * - * _.isArray('abc'); - * // => false - * - * _.isArray(_.noop); - * // => false - */ -var isArray = Array.isArray; - -module.exports = isArray; - - -/***/ }), - -/***/ 71: -/***/ (function(module, exports, __webpack_require__) { - -var isArray = __webpack_require__(70), - isSymbol = __webpack_require__(72); - -/** Used to match property names within property paths. */ -var reIsDeepProp = /\.|\[(?:[^[\]]*|(["'])(?:(?!\1)[^\\]|\\.)*?\1)\]/, - reIsPlainProp = /^\w*$/; - -/** - * Checks if `value` is a property name and not a property path. - * - * @private - * @param {*} value The value to check. - * @param {Object} [object] The object to query keys on. - * @returns {boolean} Returns `true` if `value` is a property name, else `false`. - */ -function isKey(value, object) { - if (isArray(value)) { - return false; - } - var type = typeof value; - if (type == 'number' || type == 'symbol' || type == 'boolean' || - value == null || isSymbol(value)) { - return true; - } - return reIsPlainProp.test(value) || !reIsDeepProp.test(value) || - (object != null && value in Object(object)); -} - -module.exports = isKey; - - -/***/ }), - -/***/ 72: -/***/ (function(module, exports, __webpack_require__) { - -var baseGetTag = __webpack_require__(6), - isObjectLike = __webpack_require__(14); - -/** `Object#toString` result references. */ -var symbolTag = '[object Symbol]'; - -/** - * Checks if `value` is classified as a `Symbol` primitive or object. - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category Lang - * @param {*} value The value to check. - * @returns {boolean} Returns `true` if `value` is a symbol, else `false`. - * @example - * - * _.isSymbol(Symbol.iterator); - * // => true - * - * _.isSymbol('abc'); - * // => false - */ -function isSymbol(value) { - return typeof value == 'symbol' || - (isObjectLike(value) && baseGetTag(value) == symbolTag); -} - -module.exports = isSymbol; - - -/***/ }), - -/***/ 73: -/***/ (function(module, exports, __webpack_require__) { - -var memoizeCapped = __webpack_require__(74); - -/** Used to match property names within property paths. */ -var rePropName = /[^.[\]]+|\[(?:(-?\d+(?:\.\d+)?)|(["'])((?:(?!\2)[^\\]|\\.)*?)\2)\]|(?=(?:\.|\[\])(?:\.|\[\]|$))/g; - -/** Used to match backslashes in property paths. */ -var reEscapeChar = /\\(\\)?/g; - -/** - * Converts `string` to a property path array. - * - * @private - * @param {string} string The string to convert. - * @returns {Array} Returns the property path array. - */ -var stringToPath = memoizeCapped(function(string) { - var result = []; - if (string.charCodeAt(0) === 46 /* . */) { - result.push(''); - } - string.replace(rePropName, function(match, number, quote, subString) { - result.push(quote ? subString.replace(reEscapeChar, '$1') : (number || match)); - }); - return result; -}); - -module.exports = stringToPath; - - -/***/ }), - -/***/ 74: -/***/ (function(module, exports, __webpack_require__) { - -var memoize = __webpack_require__(75); - -/** Used as the maximum memoize cache size. */ -var MAX_MEMOIZE_SIZE = 500; - -/** - * A specialized version of `_.memoize` which clears the memoized function's - * cache when it exceeds `MAX_MEMOIZE_SIZE`. - * - * @private - * @param {Function} func The function to have its output memoized. - * @returns {Function} Returns the new memoized function. - */ -function memoizeCapped(func) { - var result = memoize(func, function(key) { - if (cache.size === MAX_MEMOIZE_SIZE) { - cache.clear(); - } - return key; - }); - - var cache = result.cache; - return result; -} - -module.exports = memoizeCapped; - - -/***/ }), - -/***/ 75: -/***/ (function(module, exports, __webpack_require__) { - -var MapCache = __webpack_require__(76); - -/** Error message constants. */ -var FUNC_ERROR_TEXT = 'Expected a function'; - -/** - * Creates a function that memoizes the result of `func`. If `resolver` is - * provided, it determines the cache key for storing the result based on the - * arguments provided to the memoized function. By default, the first argument - * provided to the memoized function is used as the map cache key. The `func` - * is invoked with the `this` binding of the memoized function. - * - * **Note:** The cache is exposed as the `cache` property on the memoized - * function. Its creation may be customized by replacing the `_.memoize.Cache` - * constructor with one whose instances implement the - * [`Map`](http://ecma-international.org/ecma-262/7.0/#sec-properties-of-the-map-prototype-object) - * method interface of `clear`, `delete`, `get`, `has`, and `set`. - * - * @static - * @memberOf _ - * @since 0.1.0 - * @category Function - * @param {Function} func The function to have its output memoized. - * @param {Function} [resolver] The function to resolve the cache key. - * @returns {Function} Returns the new memoized function. - * @example - * - * var object = { 'a': 1, 'b': 2 }; - * var other = { 'c': 3, 'd': 4 }; - * - * var values = _.memoize(_.values); - * values(object); - * // => [1, 2] - * - * values(other); - * // => [3, 4] - * - * object.a = 2; - * values(object); - * // => [1, 2] - * - * // Modify the result cache. - * values.cache.set(object, ['a', 'b']); - * values(object); - * // => ['a', 'b'] - * - * // Replace `_.memoize.Cache`. - * _.memoize.Cache = WeakMap; - */ -function memoize(func, resolver) { - if (typeof func != 'function' || (resolver != null && typeof resolver != 'function')) { - throw new TypeError(FUNC_ERROR_TEXT); - } - var memoized = function() { - var args = arguments, - key = resolver ? resolver.apply(this, args) : args[0], - cache = memoized.cache; - - if (cache.has(key)) { - return cache.get(key); - } - var result = func.apply(this, args); - memoized.cache = cache.set(key, result) || cache; - return result; - }; - memoized.cache = new (memoize.Cache || MapCache); - return memoized; -} - -// Expose `MapCache`. -memoize.Cache = MapCache; - -module.exports = memoize; - - -/***/ }), - -/***/ 76: -/***/ (function(module, exports, __webpack_require__) { - -var mapCacheClear = __webpack_require__(77), - mapCacheDelete = __webpack_require__(102), - mapCacheGet = __webpack_require__(105), - mapCacheHas = __webpack_require__(106), - mapCacheSet = __webpack_require__(107); - -/** - * Creates a map cache object to store key-value pairs. - * - * @private - * @constructor - * @param {Array} [entries] The key-value pairs to cache. - */ -function MapCache(entries) { - var index = -1, - length = entries == null ? 0 : entries.length; - - this.clear(); - while (++index < length) { - var entry = entries[index]; - this.set(entry[0], entry[1]); - } -} - -// Add methods to `MapCache`. -MapCache.prototype.clear = mapCacheClear; -MapCache.prototype['delete'] = mapCacheDelete; -MapCache.prototype.get = mapCacheGet; -MapCache.prototype.has = mapCacheHas; -MapCache.prototype.set = mapCacheSet; - -module.exports = MapCache; - - -/***/ }), - -/***/ 77: -/***/ (function(module, exports, __webpack_require__) { - -var Hash = __webpack_require__(78), - ListCache = __webpack_require__(93), - Map = __webpack_require__(101); - -/** - * Removes all key-value entries from the map. - * - * @private - * @name clear - * @memberOf MapCache - */ -function mapCacheClear() { - this.size = 0; - this.__data__ = { - 'hash': new Hash, - 'map': new (Map || ListCache), - 'string': new Hash - }; -} - -module.exports = mapCacheClear; - - -/***/ }), - -/***/ 78: -/***/ (function(module, exports, __webpack_require__) { - -var hashClear = __webpack_require__(79), - hashDelete = __webpack_require__(89), - hashGet = __webpack_require__(90), - hashHas = __webpack_require__(91), - hashSet = __webpack_require__(92); - -/** - * Creates a hash object. - * - * @private - * @constructor - * @param {Array} [entries] The key-value pairs to cache. - */ -function Hash(entries) { - var index = -1, - length = entries == null ? 0 : entries.length; - - this.clear(); - while (++index < length) { - var entry = entries[index]; - this.set(entry[0], entry[1]); - } -} - -// Add methods to `Hash`. -Hash.prototype.clear = hashClear; -Hash.prototype['delete'] = hashDelete; -Hash.prototype.get = hashGet; -Hash.prototype.has = hashHas; -Hash.prototype.set = hashSet; - -module.exports = Hash; - - -/***/ }), - -/***/ 79: -/***/ (function(module, exports, __webpack_require__) { - -var nativeCreate = __webpack_require__(80); - -/** - * Removes all key-value entries from the hash. - * - * @private - * @name clear - * @memberOf Hash - */ -function hashClear() { - this.__data__ = nativeCreate ? nativeCreate(null) : {}; - this.size = 0; -} - -module.exports = hashClear; - - -/***/ }), - -/***/ 792: -/***/ (function(module, exports) { - -var g; - -// This works in non-strict mode -g = (function() { - return this; -})(); - -try { - // This works if eval is allowed (see CSP) - g = g || Function("return this")() || (1,eval)("this"); -} catch(e) { - // This works if the window reference is available - if(typeof window === "object") - g = window; -} - -// g can still be undefined, but nothing to do about it... -// We return undefined, instead of nothing here, so it's -// easier to handle this case. if(!global) { ...} - -module.exports = g; - - -/***/ }), - -/***/ 793: -/***/ (function(module, exports) { - -module.exports = function(module) { - if(!module.webpackPolyfill) { - module.deprecate = function() {}; - module.paths = []; - // module.parent = undefined by default - if(!module.children) module.children = []; - Object.defineProperty(module, "loaded", { - enumerable: true, - get: function() { - return module.l; - } - }); - Object.defineProperty(module, "id", { - enumerable: true, - get: function() { - return module.i; - } - }); - module.webpackPolyfill = 1; - } - return module; -}; - - -/***/ }), - -/***/ 8: -/***/ (function(module, exports, __webpack_require__) { - -var freeGlobal = __webpack_require__(9); - -/** Detect free variable `self`. */ -var freeSelf = typeof self == 'object' && self && self.Object === Object && self; - -/** Used as a reference to the global object. */ -var root = freeGlobal || freeSelf || Function('return this')(); - -module.exports = root; - - -/***/ }), - -/***/ 80: -/***/ (function(module, exports, __webpack_require__) { - -var getNative = __webpack_require__(81); - -/* Built-in method references that are verified to be native. */ -var nativeCreate = getNative(Object, 'create'); - -module.exports = nativeCreate; - - -/***/ }), - -/***/ 806: -/***/ (function(module, exports) { - -module.exports = "dojo_square" - -/***/ }), - -/***/ 81: -/***/ (function(module, exports, __webpack_require__) { - -var baseIsNative = __webpack_require__(82), - getValue = __webpack_require__(88); - -/** - * Gets the native function at `key` of `object`. - * - * @private - * @param {Object} object The object to query. - * @param {string} key The key of the method to get. - * @returns {*} Returns the function if it's native, else `undefined`. - */ -function getNative(object, key) { - var value = getValue(object, key); - return baseIsNative(value) ? value : undefined; -} - -module.exports = getNative; - - -/***/ }), - -/***/ 82: -/***/ (function(module, exports, __webpack_require__) { - -var isFunction = __webpack_require__(83), - isMasked = __webpack_require__(85), - isObject = __webpack_require__(84), - toSource = __webpack_require__(87); - -/** - * Used to match `RegExp` - * [syntax characters](http://ecma-international.org/ecma-262/7.0/#sec-patterns). - */ -var reRegExpChar = /[\\^$.*+?()[\]{}|]/g; - -/** Used to detect host constructors (Safari). */ -var reIsHostCtor = /^\[object .+?Constructor\]$/; - -/** Used for built-in method references. */ -var funcProto = Function.prototype, - objectProto = Object.prototype; - -/** Used to resolve the decompiled source of functions. */ -var funcToString = funcProto.toString; - -/** Used to check objects for own properties. */ -var hasOwnProperty = objectProto.hasOwnProperty; + url.fileName = url.pathname ? url.pathname.slice(url.pathname.lastIndexOf("/") + 1) || "/" : "/"; -/** Used to detect if a method is native. */ -var reIsNative = RegExp('^' + - funcToString.call(hasOwnProperty).replace(reRegExpChar, '\\$&') - .replace(/hasOwnProperty|(function).*?(?=\\\()| for .+?(?=\\\])/g, '$1.*?') + '$' -); + if (isChrome) { + url.hostname = null; + url.host = null; + } -/** - * The base implementation of `_.isNative` without bad shim checks. - * - * @private - * @param {*} value The value to check. - * @returns {boolean} Returns `true` if `value` is a native function, - * else `false`. - */ -function baseIsNative(value) { - if (!isObject(value) || isMasked(value)) { - return false; + gURLStore.set(location, url); + return url; + } catch (e) { + gURLStore.set(location, null); + return null; } - var pattern = isFunction(value) ? reIsNative : reIsHostCtor; - return pattern.test(toSource(value)); } -module.exports = baseIsNative; - - -/***/ }), - -/***/ 83: -/***/ (function(module, exports, __webpack_require__) { - -var baseGetTag = __webpack_require__(6), - isObject = __webpack_require__(84); - -/** `Object#toString` result references. */ -var asyncTag = '[object AsyncFunction]', - funcTag = '[object Function]', - genTag = '[object GeneratorFunction]', - proxyTag = '[object Proxy]'; - /** - * Checks if `value` is classified as a `Function` object. - * - * @static - * @memberOf _ - * @since 0.1.0 - * @category Lang - * @param {*} value The value to check. - * @returns {boolean} Returns `true` if `value` is a function, else `false`. - * @example - * - * _.isFunction(_); - * // => true - * - * _.isFunction(/abc/); - * // => false - */ -function isFunction(value) { - if (!isObject(value)) { - return false; +* Parse a source into a short and long name as well as a host name. +* +* @param {String} source +* The source to parse. Can be a URI or names like "(eval)" or +* "self-hosted". +* @return {Object} +* An object with the following properties: +* - {String} short: A short name for the source. +* - "http://page.com/test.js#go?q=query" -> "test.js" +* - {String} long: The full, long name for the source, with + hash/query stripped. +* - "http://page.com/test.js#go?q=query" -> "http://page.com/test.js" +* - {String?} host: If available, the host name for the source. +* - "http://page.com/test.js#go?q=query" -> "page.com" +*/ +function getSourceNames(source) { + let data = gSourceNamesStore.get(source); + + if (data) { + return data; } - // The use of `Object#toString` avoids issues with the `typeof` operator - // in Safari 9 which returns 'object' for typed arrays and other constructors. - var tag = baseGetTag(value); - return tag == funcTag || tag == genTag || tag == asyncTag || tag == proxyTag; -} -module.exports = isFunction; + let short, long, host; + const sourceStr = source ? String(source) : ""; + // If `data:...` uri + if (isDataScheme(sourceStr)) { + let commaIndex = sourceStr.indexOf(","); + if (commaIndex > -1) { + // The `short` name for a data URI becomes `data:` followed by the actual + // encoded content, omitting the MIME type, and charset. + short = `data:${sourceStr.substring(commaIndex + 1)}`.slice(0, 100); + let result = { short, long: sourceStr }; + gSourceNamesStore.set(source, result); + return result; + } + } -/***/ }), + // If Scratchpad URI, like "Scratchpad/1"; no modifications, + // and short/long are the same. + if (isScratchpadScheme(sourceStr)) { + let result = { short: sourceStr, long: sourceStr }; + gSourceNamesStore.set(source, result); + return result; + } -/***/ 84: -/***/ (function(module, exports) { + const parsedUrl = parseURL(sourceStr); -/** - * Checks if `value` is the - * [language type](http://www.ecma-international.org/ecma-262/7.0/#sec-ecmascript-language-types) - * of `Object`. (e.g. arrays, functions, objects, regexes, `new Number(0)`, and `new String('')`) - * - * @static - * @memberOf _ - * @since 0.1.0 - * @category Lang - * @param {*} value The value to check. - * @returns {boolean} Returns `true` if `value` is an object, else `false`. - * @example - * - * _.isObject({}); - * // => true - * - * _.isObject([1, 2, 3]); - * // => true - * - * _.isObject(_.noop); - * // => true - * - * _.isObject(null); - * // => false - */ -function isObject(value) { - var type = typeof value; - return value != null && (type == 'object' || type == 'function'); -} + if (!parsedUrl) { + // Malformed URI. + long = sourceStr; + short = sourceStr.slice(0, 100); + } else { + host = parsedUrl.host; -module.exports = isObject; + long = parsedUrl.href; + if (parsedUrl.hash) { + long = long.replace(parsedUrl.hash, ""); + } + if (parsedUrl.search) { + long = long.replace(parsedUrl.search, ""); + } + short = parsedUrl.fileName; + // If `short` is just a slash, and we actually have a path, + // strip the slash and parse again to get a more useful short name. + // e.g. "http://foo.com/bar/" -> "bar", rather than "/" + if (short === "/" && parsedUrl.pathname !== "/") { + short = parseURL(long.replace(/\/$/, "")).fileName; + } + } -/***/ }), + if (!short) { + if (!long) { + long = UNKNOWN_SOURCE_STRING; + } + short = long.slice(0, 100); + } -/***/ 85: -/***/ (function(module, exports, __webpack_require__) { + let result = { short, long, host }; + gSourceNamesStore.set(source, result); + return result; +} -var coreJsData = __webpack_require__(86); +// For the functions below, we assume that we will never access the location +// argument out of bounds, which is indeed the vast majority of cases. +// +// They are written this way because they are hot. Each frame is checked for +// being content or chrome when processing the profile. -/** Used to detect methods masquerading as native. */ -var maskSrcKey = (function() { - var uid = /[^.]+$/.exec(coreJsData && coreJsData.keys && coreJsData.keys.IE_PROTO || ''); - return uid ? ('Symbol(src)_1.' + uid) : ''; -}()); +function isColonSlashSlash(location, i = 0) { + return location.charCodeAt(++i) === CHAR_CODE_COLON && location.charCodeAt(++i) === CHAR_CODE_SLASH && location.charCodeAt(++i) === CHAR_CODE_SLASH; +} /** - * Checks if `func` has its source masked. - * - * @private - * @param {Function} func The function to check. - * @returns {boolean} Returns `true` if `func` is masked, else `false`. - */ -function isMasked(func) { - return !!maskSrcKey && (maskSrcKey in func); +* Checks for a Scratchpad URI, like "Scratchpad/1" +*/ +function isScratchpadScheme(location, i = 0) { + return location.charCodeAt(i) === CHAR_CODE_CAP_S && location.charCodeAt(++i) === CHAR_CODE_C && location.charCodeAt(++i) === CHAR_CODE_R && location.charCodeAt(++i) === CHAR_CODE_A && location.charCodeAt(++i) === CHAR_CODE_T && location.charCodeAt(++i) === CHAR_CODE_C && location.charCodeAt(++i) === CHAR_CODE_H && location.charCodeAt(++i) === CHAR_CODE_P && location.charCodeAt(++i) === CHAR_CODE_A && location.charCodeAt(++i) === CHAR_CODE_D && location.charCodeAt(++i) === CHAR_CODE_SLASH; } -module.exports = isMasked; - - -/***/ }), - -/***/ 86: -/***/ (function(module, exports, __webpack_require__) { - -var root = __webpack_require__(8); - -/** Used to detect overreaching core-js shims. */ -var coreJsData = root['__core-js_shared__']; - -module.exports = coreJsData; +function isDataScheme(location, i = 0) { + return location.charCodeAt(i) === CHAR_CODE_D && location.charCodeAt(++i) === CHAR_CODE_A && location.charCodeAt(++i) === CHAR_CODE_T && location.charCodeAt(++i) === CHAR_CODE_A && location.charCodeAt(++i) === CHAR_CODE_COLON; +} +function isContentScheme(location, i = 0) { + let firstChar = location.charCodeAt(i); -/***/ }), + switch (firstChar) { + // "http://" or "https://" + case CHAR_CODE_H: + if (location.charCodeAt(++i) === CHAR_CODE_T && location.charCodeAt(++i) === CHAR_CODE_T && location.charCodeAt(++i) === CHAR_CODE_P) { + if (location.charCodeAt(i + 1) === CHAR_CODE_S) { + ++i; + } + return isColonSlashSlash(location, i); + } + return false; -/***/ 87: -/***/ (function(module, exports) { + // "file://" + case CHAR_CODE_F: + if (location.charCodeAt(++i) === CHAR_CODE_I && location.charCodeAt(++i) === CHAR_CODE_L && location.charCodeAt(++i) === CHAR_CODE_E) { + return isColonSlashSlash(location, i); + } + return false; -/** Used for built-in method references. */ -var funcProto = Function.prototype; + // "app://" + case CHAR_CODE_A: + if (location.charCodeAt(++i) == CHAR_CODE_P && location.charCodeAt(++i) == CHAR_CODE_P) { + return isColonSlashSlash(location, i); + } + return false; -/** Used to resolve the decompiled source of functions. */ -var funcToString = funcProto.toString; + // "blob:" + case CHAR_CODE_B: + if (location.charCodeAt(++i) == CHAR_CODE_L && location.charCodeAt(++i) == CHAR_CODE_O && location.charCodeAt(++i) == CHAR_CODE_B && location.charCodeAt(++i) == CHAR_CODE_COLON) { + return isContentScheme(location, i + 1); + } + return false; -/** - * Converts `func` to its source code. - * - * @private - * @param {Function} func The function to convert. - * @returns {string} Returns the source code. - */ -function toSource(func) { - if (func != null) { - try { - return funcToString.call(func); - } catch (e) {} - try { - return (func + ''); - } catch (e) {} + default: + return false; } - return ''; } -module.exports = toSource; +function isChromeScheme(location, i = 0) { + let firstChar = location.charCodeAt(i); + switch (firstChar) { + // "chrome://" + case CHAR_CODE_C: + if (location.charCodeAt(++i) === CHAR_CODE_H && location.charCodeAt(++i) === CHAR_CODE_R && location.charCodeAt(++i) === CHAR_CODE_O && location.charCodeAt(++i) === CHAR_CODE_M && location.charCodeAt(++i) === CHAR_CODE_E) { + return isColonSlashSlash(location, i); + } + return false; -/***/ }), + // "resource://" + case CHAR_CODE_R: + if (location.charCodeAt(++i) === CHAR_CODE_E && location.charCodeAt(++i) === CHAR_CODE_S && location.charCodeAt(++i) === CHAR_CODE_O && location.charCodeAt(++i) === CHAR_CODE_U && location.charCodeAt(++i) === CHAR_CODE_R && location.charCodeAt(++i) === CHAR_CODE_C && location.charCodeAt(++i) === CHAR_CODE_E) { + return isColonSlashSlash(location, i); + } + return false; -/***/ 88: -/***/ (function(module, exports) { + // "jar:file://" + case CHAR_CODE_J: + if (location.charCodeAt(++i) === CHAR_CODE_A && location.charCodeAt(++i) === CHAR_CODE_R && location.charCodeAt(++i) === CHAR_CODE_COLON && location.charCodeAt(++i) === CHAR_CODE_F && location.charCodeAt(++i) === CHAR_CODE_I && location.charCodeAt(++i) === CHAR_CODE_L && location.charCodeAt(++i) === CHAR_CODE_E) { + return isColonSlashSlash(location, i); + } + return false; -/** - * Gets the value at `key` of `object`. - * - * @private - * @param {Object} [object] The object to query. - * @param {string} key The key of the property to get. - * @returns {*} Returns the property value. - */ -function getValue(object, key) { - return object == null ? undefined : object[key]; + default: + return false; + } } -module.exports = getValue; - - -/***/ }), - -/***/ 89: -/***/ (function(module, exports) { +function isWASM(location, i = 0) { + return ( + // "wasm-function[" + location.charCodeAt(i) === CHAR_CODE_W && location.charCodeAt(++i) === CHAR_CODE_A && location.charCodeAt(++i) === CHAR_CODE_S && location.charCodeAt(++i) === CHAR_CODE_M && location.charCodeAt(++i) === CHAR_CODE_DASH && location.charCodeAt(++i) === CHAR_CODE_F && location.charCodeAt(++i) === CHAR_CODE_U && location.charCodeAt(++i) === CHAR_CODE_N && location.charCodeAt(++i) === CHAR_CODE_C && location.charCodeAt(++i) === CHAR_CODE_T && location.charCodeAt(++i) === CHAR_CODE_I && location.charCodeAt(++i) === CHAR_CODE_O && location.charCodeAt(++i) === CHAR_CODE_N && location.charCodeAt(++i) === CHAR_CODE_L_SQUARE_BRACKET + ); +} /** - * Removes `key` and its value from the hash. - * - * @private - * @name delete - * @memberOf Hash - * @param {Object} hash The hash to modify. - * @param {string} key The key of the value to remove. - * @returns {boolean} Returns `true` if the entry was removed, else `false`. - */ -function hashDelete(key) { - var result = this.has(key) && delete this.__data__[key]; - this.size -= result ? 1 : 0; - return result; +* A utility method to get the file name from a sourcemapped location +* The sourcemap location can be in any form. This method returns a +* formatted file name for different cases like Windows or OSX. +* @param source +* @returns String +*/ +function getSourceMappedFile(source) { + // If sourcemapped source is a OSX path, return + // the characters after last "/". + // If sourcemapped source is a Windowss path, return + // the characters after last "\\". + if (source.lastIndexOf("/") >= 0) { + source = source.slice(source.lastIndexOf("/") + 1); + } else if (source.lastIndexOf("\\") >= 0) { + source = source.slice(source.lastIndexOf("\\") + 1); + } + return source; } -module.exports = hashDelete; - - -/***/ }), - -/***/ 9: -/***/ (function(module, exports, __webpack_require__) { - -/* WEBPACK VAR INJECTION */(function(global) {/** Detect free variable `global` from Node.js. */ -var freeGlobal = typeof global == 'object' && global && global.Object === Object && global; - -module.exports = freeGlobal; - -/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(792))) +module.exports = { + parseURL, + getSourceNames, + isScratchpadScheme, + isChromeScheme, + isContentScheme, + isWASM, + isDataScheme, + getSourceMappedFile +}; /***/ }), -/***/ 90: +/***/ 3753: /***/ (function(module, exports, __webpack_require__) { -var nativeCreate = __webpack_require__(80); +"use strict"; +/* 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/. */ + +// This file is a chrome-API-free version of the module +// devtools/client/shared/unicode-url.js in the mozilla-central repository, so +// that it can be used in Chrome-API-free applications, such as the Launchpad. +// But because of this, it cannot take advantage of utilizing chrome APIs and +// should implement the similar functionalities on its own. +// +// Please keep in mind that if the feature in this file has changed, don't +// forget to also change that accordingly in +// devtools/client/shared/unicode-url.js in the mozilla-central repository. -/** Used to stand-in for `undefined` hash values. */ -var HASH_UNDEFINED = '__lodash_hash_undefined__'; -/** Used for built-in method references. */ -var objectProto = Object.prototype; -/** Used to check objects for own properties. */ -var hasOwnProperty = objectProto.hasOwnProperty; +const punycode = __webpack_require__(3641); /** - * Gets the hash value for `key`. + * Gets a readble Unicode hostname from a hostname. + * + * If the `hostname` is a readable ASCII hostname, such as example.org, then + * this function will simply return the original `hostname`. + * + * If the `hostname` is a Punycode hostname representing a Unicode domain name, + * such as xn--g6w.xn--8pv, then this function will return the readable Unicode + * domain name by decoding the Punycode hostname. * - * @private - * @name get - * @memberOf Hash - * @param {string} key The key of the value to get. - * @returns {*} Returns the entry value. + * @param {string} hostname + * the hostname from which the Unicode hostname will be + * parsed, such as example.org, xn--g6w.xn--8pv. + * @return {string} The Unicode hostname. It may be the same as the `hostname` + * passed to this function if the `hostname` itself is + * a readable ASCII hostname or a Unicode hostname. */ -function hashGet(key) { - var data = this.__data__; - if (nativeCreate) { - var result = data[key]; - return result === HASH_UNDEFINED ? undefined : result; - } - return hasOwnProperty.call(data, key) ? data[key] : undefined; +function getUnicodeHostname(hostname) { + try { + return punycode.toUnicode(hostname); + } catch (err) {} + return hostname; } -module.exports = hashGet; - - -/***/ }), - -/***/ 91: -/***/ (function(module, exports, __webpack_require__) { - -var nativeCreate = __webpack_require__(80); - -/** Used for built-in method references. */ -var objectProto = Object.prototype; - -/** Used to check objects for own properties. */ -var hasOwnProperty = objectProto.hasOwnProperty; - /** - * Checks if a hash value for `key` exists. + * Gets a readble Unicode URL pathname from a URL pathname. + * + * If the `urlPath` is a readable ASCII URL pathname, such as /a/b/c.js, then + * this function will simply return the original `urlPath`. + * + * If the `urlPath` is a URI-encoded pathname, such as %E8%A9%A6/%E6%B8%AC.js, + * then this function will return the readable Unicode pathname. * - * @private - * @name has - * @memberOf Hash - * @param {string} key The key of the entry to check. - * @returns {boolean} Returns `true` if an entry for `key` exists, else `false`. + * If the `urlPath` is a malformed URL pathname, then this function will simply + * return the original `urlPath`. + * + * @param {string} urlPath + * the URL path from which the Unicode URL path will be parsed, + * such as /a/b/c.js, %E8%A9%A6/%E6%B8%AC.js. + * @return {string} The Unicode URL Path. It may be the same as the `urlPath` + * passed to this function if the `urlPath` itself is a readable + * ASCII url or a Unicode url. */ -function hashHas(key) { - var data = this.__data__; - return nativeCreate ? (data[key] !== undefined) : hasOwnProperty.call(data, key); +function getUnicodeUrlPath(urlPath) { + try { + return decodeURIComponent(urlPath); + } catch (err) {} + return urlPath; } -module.exports = hashHas; - - -/***/ }), - -/***/ 919: -/***/ (function(module, exports) { - -module.exports = "" - -/***/ }), - -/***/ 92: -/***/ (function(module, exports, __webpack_require__) { - -var nativeCreate = __webpack_require__(80); - -/** Used to stand-in for `undefined` hash values. */ -var HASH_UNDEFINED = '__lodash_hash_undefined__'; - /** - * Sets the hash `key` to `value`. + * Gets a readable Unicode URL from a URL. + * + * If the `url` is a readable ASCII URL, such as http://example.org/a/b/c.js, + * then this function will simply return the original `url`. + * + * If the `url` includes either an unreadable Punycode domain name or an + * unreadable URI-encoded pathname, such as + * http://xn--g6w.xn--8pv/%E8%A9%A6/%E6%B8%AC.js, then this function will return + * the readable URL by decoding all its unreadable URL components to Unicode + * characters. + * + * If the `url` is a malformed URL, then this function will return the original + * `url`. + * + * If the `url` is a data: URI, then this function will return the original + * `url`. * - * @private - * @name set - * @memberOf Hash - * @param {string} key The key of the value to set. - * @param {*} value The value to set. - * @returns {Object} Returns the hash instance. + * @param {string} url + * the full URL, or a data: URI. from which the readable URL + * will be parsed, such as, http://example.org/a/b/c.js, + * http://xn--g6w.xn--8pv/%E8%A9%A6/%E6%B8%AC.js + * @return {string} The readable URL. It may be the same as the `url` passed to + * this function if the `url` itself is readable. */ -function hashSet(key, value) { - var data = this.__data__; - this.size += this.has(key) ? 0 : 1; - data[key] = (nativeCreate && value === undefined) ? HASH_UNDEFINED : value; - return this; +function getUnicodeUrl(url) { + try { + const { protocol, hostname } = new URL(url); + if (protocol === "data:") { + // Never convert a data: URI. + return url; + } + const readableHostname = getUnicodeHostname(hostname); + url = decodeURIComponent(url); + return url.replace(hostname, readableHostname); + } catch (err) {} + return url; } -module.exports = hashSet; - +module.exports = { + getUnicodeHostname, + getUnicodeUrlPath, + getUnicodeUrl +}; /***/ }), -/***/ 920: +/***/ 4: /***/ (function(module, exports) { -module.exports = "" - -/***/ }), - -/***/ 93: -/***/ (function(module, exports, __webpack_require__) { - -var listCacheClear = __webpack_require__(94), - listCacheDelete = __webpack_require__(95), - listCacheGet = __webpack_require__(98), - listCacheHas = __webpack_require__(99), - listCacheSet = __webpack_require__(100); - -/** - * Creates an list cache object. - * - * @private - * @constructor - * @param {Array} [entries] The key-value pairs to cache. - */ -function ListCache(entries) { - var index = -1, - length = entries == null ? 0 : entries.length; - - this.clear(); - while (++index < length) { - var entry = entries[index]; - this.set(entry[0], entry[1]); - } -} - -// Add methods to `ListCache`. -ListCache.prototype.clear = listCacheClear; -ListCache.prototype['delete'] = listCacheDelete; -ListCache.prototype.get = listCacheGet; -ListCache.prototype.has = listCacheHas; -ListCache.prototype.set = listCacheSet; - -module.exports = ListCache; - +module.exports = __WEBPACK_EXTERNAL_MODULE_4__; /***/ }), -/***/ 94: +/***/ 52: /***/ (function(module, exports) { -/** - * Removes all key-value entries from the list cache. - * - * @private - * @name clear - * @memberOf ListCache - */ -function listCacheClear() { - this.__data__ = []; - this.size = 0; -} - -module.exports = listCacheClear; - +module.exports = __WEBPACK_EXTERNAL_MODULE_52__; /***/ }), -/***/ 95: +/***/ 66: /***/ (function(module, exports, __webpack_require__) { -var assocIndexOf = __webpack_require__(96); - -/** Used for built-in method references. */ -var arrayProto = Array.prototype; - -/** Built-in value references. */ -var splice = arrayProto.splice; - -/** - * Removes `key` and its value from the list cache. - * - * @private - * @name delete - * @memberOf ListCache - * @param {string} key The key of the value to remove. - * @returns {boolean} Returns `true` if the entry was removed, else `false`. - */ -function listCacheDelete(key) { - var data = this.__data__, - index = assocIndexOf(data, key); +"use strict"; - if (index < 0) { - return false; - } - var lastIndex = data.length - 1; - if (index == lastIndex) { - data.pop(); - } else { - splice.call(data, index, 1); - } - --this.size; - return true; -} -module.exports = listCacheDelete; +exports.decode = exports.parse = __webpack_require__(121); +exports.encode = exports.stringify = __webpack_require__(122); /***/ }), -/***/ 96: -/***/ (function(module, exports, __webpack_require__) { - -var eq = __webpack_require__(97); - -/** - * Gets the index at which the `key` is found in `array` of key-value pairs. - * - * @private - * @param {Array} array The array to inspect. - * @param {*} key The key to search for. - * @returns {number} Returns the index of the matched value, else `-1`. - */ -function assocIndexOf(array, key) { - var length = array.length; - while (length--) { - if (eq(array[length][0], key)) { - return length; - } - } - return -1; -} +/***/ 792: +/***/ (function(module, exports) { -module.exports = assocIndexOf; +var g; + +// This works in non-strict mode +g = (function() { + return this; +})(); + +try { + // This works if eval is allowed (see CSP) + g = g || Function("return this")() || (1,eval)("this"); +} catch(e) { + // This works if the window reference is available + if(typeof window === "object") + g = window; +} + +// g can still be undefined, but nothing to do about it... +// We return undefined, instead of nothing here, so it's +// easier to handle this case. if(!global) { ...} + +module.exports = g; /***/ }), -/***/ 97: +/***/ 793: /***/ (function(module, exports) { -/** - * Performs a - * [`SameValueZero`](http://ecma-international.org/ecma-262/7.0/#sec-samevaluezero) - * comparison between two values to determine if they are equivalent. - * - * @static - * @memberOf _ - * @since 4.0.0 - * @category Lang - * @param {*} value The value to compare. - * @param {*} other The other value to compare. - * @returns {boolean} Returns `true` if the values are equivalent, else `false`. - * @example - * - * var object = { 'a': 1 }; - * var other = { 'a': 1 }; - * - * _.eq(object, object); - * // => true - * - * _.eq(object, other); - * // => false - * - * _.eq('a', 'a'); - * // => true - * - * _.eq('a', Object('a')); - * // => false - * - * _.eq(NaN, NaN); - * // => true - */ -function eq(value, other) { - return value === other || (value !== value && other !== other); -} - -module.exports = eq; +module.exports = function(module) { + if(!module.webpackPolyfill) { + module.deprecate = function() {}; + module.paths = []; + // module.parent = undefined by default + if(!module.children) module.children = []; + Object.defineProperty(module, "loaded", { + enumerable: true, + get: function() { + return module.l; + } + }); + Object.defineProperty(module, "id", { + enumerable: true, + get: function() { + return module.i; + } + }); + module.webpackPolyfill = 1; + } + return module; +}; /***/ }), -/***/ 98: -/***/ (function(module, exports, __webpack_require__) { - -var assocIndexOf = __webpack_require__(96); - -/** - * Gets the list cache value for `key`. - * - * @private - * @name get - * @memberOf ListCache - * @param {string} key The key of the value to get. - * @returns {*} Returns the entry value. - */ -function listCacheGet(key) { - var data = this.__data__, - index = assocIndexOf(data, key); - - return index < 0 ? undefined : data[index][1]; -} - -module.exports = listCacheGet; +/***/ 806: +/***/ (function(module, exports) { +module.exports = "dojo_square" /***/ }), -/***/ 99: -/***/ (function(module, exports, __webpack_require__) { +/***/ 919: +/***/ (function(module, exports) { -var assocIndexOf = __webpack_require__(96); +module.exports = "" -/** - * Checks if a list cache value for `key` exists. - * - * @private - * @name has - * @memberOf ListCache - * @param {string} key The key of the entry to check. - * @returns {boolean} Returns `true` if an entry for `key` exists, else `false`. - */ -function listCacheHas(key) { - return assocIndexOf(this.__data__, key) > -1; -} +/***/ }), -module.exports = listCacheHas; +/***/ 920: +/***/ (function(module, exports) { +module.exports = "" /***/ }), /***/ 993: /***/ (function(module, exports, __webpack_require__) { diff --git a/devtools/client/debugger/new/src/actions/breakpoints.js b/devtools/client/debugger/new/src/actions/breakpoints.js --- a/devtools/client/debugger/new/src/actions/breakpoints.js +++ b/devtools/client/debugger/new/src/actions/breakpoints.js @@ -57,14 +57,20 @@ dispatch, getState, client, sourceMaps }) => { + const response = await (0, _syncBreakpoint.syncClientBreakpoint)(getState, client, sourceMaps, sourceId, pendingBreakpoint); + + if (!response) { + return; + } + const { breakpoint, previousLocation - } = await (0, _syncBreakpoint.syncClientBreakpoint)(getState, client, sourceMaps, sourceId, pendingBreakpoint); + } = response; return dispatch({ type: "SYNC_BREAKPOINT", breakpoint, previousLocation }); @@ -283,17 +289,12 @@ function toggleBreakpoints(shouldDisableBreakpoints, breakpoints) { return async ({ dispatch }) => { - for (const [, breakpoint] of breakpoints) { - if (shouldDisableBreakpoints) { - await dispatch(disableBreakpoint(breakpoint.location)); - } else { - await dispatch(enableBreakpoint(breakpoint.location)); - } - } + const promises = breakpoints.valueSeq().toJS().map(([, breakpoint]) => shouldDisableBreakpoints ? dispatch(disableBreakpoint(breakpoint.location)) : dispatch(enableBreakpoint(breakpoint.location))); + await Promise.all(promises); }; } /** * Removes all breakpoints * @@ -305,15 +306,12 @@ function removeAllBreakpoints() { return async ({ dispatch, getState }) => { - const breakpoints = (0, _selectors.getBreakpoints)(getState()); - - for (const [, breakpoint] of breakpoints) { - await dispatch(removeBreakpoint(breakpoint.location)); - } + const breakpointList = (0, _selectors.getBreakpoints)(getState()).valueSeq().toJS(); + return Promise.all(breakpointList.map(bp => dispatch(removeBreakpoint(bp.location)))); }; } /** * Removes breakpoints * @@ -324,13 +322,12 @@ function removeBreakpoints(breakpoints) { return async ({ dispatch }) => { - for (const [, breakpoint] of breakpoints) { - await dispatch(removeBreakpoint(breakpoint.location)); - } + const breakpointList = breakpoints.valueSeq().toJS(); + return Promise.all(breakpointList.map(bp => dispatch(removeBreakpoint(bp.location)))); }; } function remapBreakpoints(sourceId) { return async ({ diff --git a/devtools/client/debugger/new/src/actions/breakpoints/syncBreakpoint.js b/devtools/client/debugger/new/src/actions/breakpoints/syncBreakpoint.js --- a/devtools/client/debugger/new/src/actions/breakpoints/syncBreakpoint.js +++ b/devtools/client/debugger/new/src/actions/breakpoints/syncBreakpoint.js @@ -54,13 +54,18 @@ // and adding a new breakpoint async function syncClientBreakpoint(getState, client, sourceMaps, sourceId, pendingBreakpoint) { (0, _breakpoint.assertPendingBreakpoint)(pendingBreakpoint); - const source = (0, _selectors.getSourceFromId)(getState(), sourceId); + const source = (0, _selectors.getSource)(getState(), sourceId); const generatedSourceId = sourceMaps.isOriginalId(sourceId) ? (0, _devtoolsSourceMap.originalToGeneratedId)(sourceId) : sourceId; - const generatedSource = (0, _selectors.getSourceFromId)(getState(), generatedSourceId); + const generatedSource = (0, _selectors.getSource)(getState(), generatedSourceId); + + if (!source) { + return null; + } + const { location, astLocation } = pendingBreakpoint; diff --git a/devtools/client/debugger/new/src/actions/sources/newSources.js b/devtools/client/debugger/new/src/actions/sources/newSources.js --- a/devtools/client/debugger/new/src/actions/sources/newSources.js +++ b/devtools/client/debugger/new/src/actions/sources/newSources.js @@ -135,15 +135,12 @@ return; } // load the source text if there is a pending breakpoint for it await dispatch((0, _loadSourceText.loadSourceText)(source)); - const pendingBreakpointsArray = pendingBreakpoints.valueSeq().toJS(); - - for (const pendingBreakpoint of pendingBreakpointsArray) { - await dispatch((0, _breakpoints.syncBreakpoint)(sourceId, pendingBreakpoint)); - } + const breakpoints = pendingBreakpoints.valueSeq().toJS(); + await Promise.all(breakpoints.map(bp => dispatch((0, _breakpoints.syncBreakpoint)(sourceId, bp)))); }; } function restoreBlackBoxedSources(sources) { return async ({ diff --git a/devtools/client/debugger/new/src/components/App.js b/devtools/client/debugger/new/src/components/App.js --- a/devtools/client/debugger/new/src/components/App.js +++ b/devtools/client/debugger/new/src/components/App.js @@ -292,6 +292,13 @@ activeSearch: (0, _selectors.getActiveSearch)(state), quickOpenEnabled: (0, _selectors.getQuickOpenEnabled)(state), orientation: (0, _selectors.getOrientation)(state) }); -exports.default = (0, _reactRedux.connect)(mapStateToProps, _actions2.default)(App); \ No newline at end of file +exports.default = (0, _reactRedux.connect)(mapStateToProps, { + setActiveSearch: _actions2.default.setActiveSearch, + closeActiveSearch: _actions2.default.closeActiveSearch, + closeProjectSearch: _actions2.default.closeProjectSearch, + openQuickOpen: _actions2.default.openQuickOpen, + closeQuickOpen: _actions2.default.closeQuickOpen, + setOrientation: _actions2.default.setOrientation +})(App); \ No newline at end of file diff --git a/devtools/client/debugger/new/src/components/Editor/Footer.js b/devtools/client/debugger/new/src/components/Editor/Footer.js --- a/devtools/client/debugger/new/src/components/Editor/Footer.js +++ b/devtools/client/debugger/new/src/components/Editor/Footer.js @@ -68,11 +68,11 @@ selectedSource, toggleBlackBox } = this.props; const sourceLoaded = selectedSource && (0, _source.isLoaded)(selectedSource); - if (!sourceLoaded) { + if (!sourceLoaded || selectedSource.isPrettyPrinted) { return; } const blackboxed = selectedSource.isBlackBoxed; const tooltip = L10N.getStr("sourceFooter.blackbox"); @@ -193,6 +193,12 @@ prettySource: (0, _selectors.getPrettySource)(state, selectedId), endPanelCollapsed: (0, _selectors.getPaneCollapse)(state, "end") }; }; -exports.default = (0, _reactRedux.connect)(mapStateToProps, _actions2.default)(SourceFooter); \ No newline at end of file +exports.default = (0, _reactRedux.connect)(mapStateToProps, { + togglePrettyPrint: _actions2.default.togglePrettyPrint, + toggleBlackBox: _actions2.default.toggleBlackBox, + jumpToMappedLocation: _actions2.default.jumpToMappedLocation, + recordCoverage: _actions2.default.recordCoverage, + togglePaneCollapse: _actions2.default.togglePaneCollapse +})(SourceFooter); \ No newline at end of file diff --git a/devtools/client/debugger/new/src/components/Editor/Preview/index.js b/devtools/client/debugger/new/src/components/Editor/Preview/index.js --- a/devtools/client/debugger/new/src/components/Editor/Preview/index.js +++ b/devtools/client/debugger/new/src/components/Editor/Preview/index.js @@ -188,18 +188,11 @@ preview: (0, _selectors.getPreview)(state), isPaused: (0, _selectors.getIsPaused)(state), selectedSource: (0, _selectors.getSelectedSource)(state) }); -const { - addExpression, - setPopupObjectProperties, - updatePreview, - clearPreview -} = _actions2.default; -const mapDispatchToProps = { - addExpression, - setPopupObjectProperties, - updatePreview, - clearPreview -}; -exports.default = (0, _reactRedux.connect)(mapStateToProps, mapDispatchToProps)(Preview); \ No newline at end of file +exports.default = (0, _reactRedux.connect)(mapStateToProps, { + clearPreview: _actions2.default.clearPreview, + setPopupObjectProperties: _actions2.default.setPopupObjectProperties, + addExpression: _actions2.default.addExpression, + updatePreview: _actions2.default.updatePreview +})(Preview); \ No newline at end of file diff --git a/devtools/client/debugger/new/src/components/Editor/SearchBar.js b/devtools/client/debugger/new/src/components/Editor/SearchBar.js --- a/devtools/client/debugger/new/src/components/Editor/SearchBar.js +++ b/devtools/client/debugger/new/src/components/Editor/SearchBar.js @@ -364,6 +364,14 @@ modifiers: (0, _selectors.getFileSearchModifiers)(state), highlightedLineRange: (0, _selectors.getHighlightedLineRange)(state), searchResults: (0, _selectors.getFileSearchResults)(state) }); -exports.default = (0, _reactRedux.connect)(mapStateToProps, _actions2.default)(SearchBar); \ No newline at end of file +exports.default = (0, _reactRedux.connect)(mapStateToProps, { + toggleFileSearchModifier: _actions2.default.toggleFileSearchModifier, + setFileSearchQuery: _actions2.default.setFileSearchQuery, + setActiveSearch: _actions2.default.setActiveSearch, + closeFileSearch: _actions2.default.closeFileSearch, + doSearch: _actions2.default.doSearch, + traverseResults: _actions2.default.traverseResults, + updateSearchResults: _actions2.default.updateSearchResults +})(SearchBar); \ No newline at end of file diff --git a/devtools/client/debugger/new/src/components/Editor/Tab.js b/devtools/client/debugger/new/src/components/Editor/Tab.js --- a/devtools/client/debugger/new/src/components/Editor/Tab.js +++ b/devtools/client/debugger/new/src/components/Editor/Tab.js @@ -16,20 +16,16 @@ var _SourceIcon2 = _interopRequireDefault(_SourceIcon); var _Button = require("../shared/Button/index"); -var _text = require("../../utils/text"); - var _actions = require("../../actions/index"); var _actions2 = _interopRequireDefault(_actions); var _source = require("../../utils/source"); -var _devtoolsModules = require("devtools/client/debugger/new/dist/vendors").vendored["devtools-modules"]; - var _clipboard = require("../../utils/clipboard"); var _tabs = require("../../utils/tabs"); var _selectors = require("../../selectors/index"); @@ -139,11 +135,10 @@ selectedSource, selectSpecificSource, closeTab, source } = this.props; - const filename = (0, _source.getFilename)(source); const sourceId = source.id; const active = selectedSource && sourceId == selectedSource.id && !this.isProjectSearchEnabled() && !this.isSourceSearchEnabled(); const isPrettyCode = (0, _source.isPretty)(source); function onClickClose(e) { @@ -175,11 +170,11 @@ }, _react2.default.createElement(_SourceIcon2.default, { source: source, shouldHide: icon => ["file", "javascript"].includes(icon) }), _react2.default.createElement("div", { className: "filename" - }, (0, _text.truncateMiddleText)((0, _devtoolsModules.getUnicodeUrlPath)(filename), 30)), _react2.default.createElement(_Button.CloseButton, { + }, (0, _source.getTruncatedFileName)(source)), _react2.default.createElement(_Button.CloseButton, { handleClick: onClickClose, tooltip: L10N.getStr("sourceTabs.closeTabButtonTooltip") })); } @@ -194,6 +189,12 @@ selectedSource: selectedSource, activeSearch: (0, _selectors.getActiveSearch)(state) }; }; -exports.default = (0, _reactRedux.connect)(mapStateToProps, _actions2.default)(Tab); \ No newline at end of file +exports.default = (0, _reactRedux.connect)(mapStateToProps, { + selectSpecificSource: _actions2.default.selectSpecificSource, + closeTab: _actions2.default.closeTab, + closeTabs: _actions2.default.closeTabs, + togglePrettyPrint: _actions2.default.togglePrettyPrint, + showSource: _actions2.default.showSource +})(Tab); \ No newline at end of file diff --git a/devtools/client/debugger/new/src/components/Editor/Tabs.js b/devtools/client/debugger/new/src/components/Editor/Tabs.js --- a/devtools/client/debugger/new/src/components/Editor/Tabs.js +++ b/devtools/client/debugger/new/src/components/Editor/Tabs.js @@ -54,11 +54,11 @@ moveTab } = this.props; const sourceTabEls = this.refs.sourceTabs.children; const hiddenTabs = (0, _tabs.getHiddenTabs)(tabSources, sourceTabEls); - if ((0, _ui.isVisible)() && hiddenTabs.find(tab => tab.id == selectedSource.id)) { + if (selectedSource && (0, _ui.isVisible)() && hiddenTabs.find(tab => tab.id == selectedSource.id)) { return moveTab(selectedSource.url, 0); } this.setState({ hiddenTabs @@ -203,6 +203,12 @@ const mapStateToProps = state => ({ selectedSource: (0, _selectors.getSelectedSource)(state), tabSources: (0, _selectors.getSourcesForTabs)(state) }); -exports.default = (0, _reactRedux.connect)(mapStateToProps, _actions2.default)(Tabs); \ No newline at end of file +exports.default = (0, _reactRedux.connect)(mapStateToProps, { + selectSpecificSource: _actions2.default.selectSpecificSource, + moveTab: _actions2.default.moveTab, + closeTab: _actions2.default.closeTab, + togglePaneCollapse: _actions2.default.togglePaneCollapse, + showSource: _actions2.default.showSource +})(Tabs); \ No newline at end of file diff --git a/devtools/client/debugger/new/src/components/Editor/index.js b/devtools/client/debugger/new/src/components/Editor/index.js --- a/devtools/client/debugger/new/src/components/Editor/index.js +++ b/devtools/client/debugger/new/src/components/Editor/index.js @@ -633,6 +633,15 @@ conditionalPanelLine: (0, _selectors.getConditionalPanelLine)(state), symbols: (0, _selectors.getSymbols)(state, selectedSource) }; }; -exports.default = (0, _reactRedux.connect)(mapStateToProps, _actions2.default)(Editor); \ No newline at end of file +exports.default = (0, _reactRedux.connect)(mapStateToProps, { + openConditionalPanel: _actions2.default.openConditionalPanel, + closeConditionalPanel: _actions2.default.closeConditionalPanel, + setContextMenu: _actions2.default.setContextMenu, + continueToHere: _actions2.default.continueToHere, + toggleBreakpoint: _actions2.default.toggleBreakpoint, + addOrToggleDisabledBreakpoint: _actions2.default.addOrToggleDisabledBreakpoint, + jumpToMappedLocation: _actions2.default.jumpToMappedLocation, + traverseResults: _actions2.default.traverseResults +})(Editor); \ No newline at end of file diff --git a/devtools/client/debugger/new/src/components/PrimaryPanes/Outline.js b/devtools/client/debugger/new/src/components/PrimaryPanes/Outline.js --- a/devtools/client/debugger/new/src/components/PrimaryPanes/Outline.js +++ b/devtools/client/debugger/new/src/components/PrimaryPanes/Outline.js @@ -200,6 +200,10 @@ selectedLocation: (0, _selectors.getSelectedLocation)(state), getFunctionText: line => (0, _function.findFunctionText)(line, selectedSource, symbols) }; }; -exports.default = (0, _reactRedux.connect)(mapStateToProps, _actions2.default)(Outline); \ No newline at end of file +exports.default = (0, _reactRedux.connect)(mapStateToProps, { + selectLocation: _actions2.default.selectLocation, + getFunctionText: _actions2.default.getFunctionText, + flashLineRange: _actions2.default.flashLineRange +})(Outline); \ No newline at end of file diff --git a/devtools/client/debugger/new/src/components/PrimaryPanes/SourcesTree.js b/devtools/client/debugger/new/src/components/PrimaryPanes/SourcesTree.js --- a/devtools/client/debugger/new/src/components/PrimaryPanes/SourcesTree.js +++ b/devtools/client/debugger/new/src/components/PrimaryPanes/SourcesTree.js @@ -10,25 +10,23 @@ var _classnames = require("devtools/client/debugger/new/dist/vendors").vendored["classnames"]; var _classnames2 = _interopRequireDefault(_classnames); -var _devtoolsContextmenu = require("devtools/client/debugger/new/dist/vendors").vendored["devtools-contextmenu"]; - var _reactRedux = require("devtools/client/shared/vendor/react-redux"); -var _SourceIcon = require("../shared/SourceIcon"); +var _selectors = require("../../selectors/index"); -var _SourceIcon2 = _interopRequireDefault(_SourceIcon); +var _actions = require("../../actions/index"); -var _selectors = require("../../selectors/index"); +var _actions2 = _interopRequireDefault(_actions); -var _sourceTree = require("../../actions/source-tree"); +var _source = require("../../utils/source"); -var _sources = require("../../actions/sources/index"); +var _SourcesTreeItem = require("./SourcesTreeItem"); -var _ui = require("../../actions/ui"); +var _SourcesTreeItem2 = _interopRequireDefault(_SourcesTreeItem); var _ManagedTree = require("../shared/ManagedTree"); var _ManagedTree2 = _interopRequireDefault(_ManagedTree); @@ -36,16 +34,10 @@ var _Svg2 = _interopRequireDefault(_Svg); var _sourcesTree = require("../../utils/sources-tree/index"); -var _source = require("../../utils/source"); - -var _clipboard = require("../../utils/clipboard"); - -var _prefs = require("../../utils/prefs"); - function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } /* 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 . */ @@ -94,23 +86,29 @@ projectRoot: nextProps.projectRoot })); } if (nextProps.shownSource && nextProps.shownSource != shownSource) { - const listItems = (0, _sourcesTree.getDirectories)(nextProps.shownSource, sourceTree); + const matchingSources = Object.keys(sources).filter(sourceId => { + return (0, _source.getRawSourceURL)(sources[sourceId].url) === nextProps.shownSource; + }); - if (listItems && listItems[0]) { - this.selectItem(listItems[0]); - } + if (matchingSources.length) { + const listItems = (0, _sourcesTree.getDirectories)(sources[matchingSources[0]], sourceTree); - return this.setState({ - listItems - }); + if (listItems && listItems.length) { + this.selectItem(listItems[0]); + } + + return this.setState({ + listItems + }); + } } if (nextProps.selectedSource && nextProps.selectedSource != selectedSource) { - const highlightItems = (0, _sourcesTree.getDirectories)((0, _source.getRawSourceURL)(nextProps.selectedSource.url), sourceTree); + const highlightItems = (0, _sourcesTree.getDirectories)(nextProps.selectedSource, sourceTree); this.setState({ highlightItems }); } // NOTE: do not run this every time a source is clicked, // only when a new source is added @@ -144,26 +142,10 @@ sourceTree } = this.state; return sourceTree.contents.length === 0; } - renderItemName(name) { - switch (name) { - case "ng://": - return "Angular"; - - case "webpack://": - return "Webpack"; - - case "moz-extension://": - return L10N.getStr("extensionsText"); - - default: - return name; - } - } - renderEmptyElement(message) { return _react2.default.createElement("div", { key: "empty", className: "no-sources-message" }, message); @@ -281,114 +263,15 @@ if ((0, _sourcesTree.isDirectory)(item)) { return path; } const source = this.getSource(item); - const blackBoxedPart = source && source.isBlackBoxed ? ":blackboxed" : ""; - return `${path}${blackBoxedPart}`; - }; - - this.getIcon = (sources, item, depth) => { - const { - debuggeeUrl, - projectRoot - } = this.props; - - if (item.path === "webpack://") { - return _react2.default.createElement(_Svg2.default, { - name: "webpack" - }); - } else if (item.path === "ng://") { - return _react2.default.createElement(_Svg2.default, { - name: "angular" - }); - } else if (item.path === "moz-extension://") { - return _react2.default.createElement("img", { - className: "extension" - }); - } - - if (depth === 0 && projectRoot === "") { - return _react2.default.createElement("img", { - className: (0, _classnames2.default)("domain", { - debuggee: debuggeeUrl && debuggeeUrl.includes(item.name) - }) - }); - } + const blackBoxedPart = source && source.isBlackBoxed ? ":blackboxed" : ""; // Original and generated sources can point to the same path + // therefore necessary to distinguish as path is used as keys. - if ((0, _sourcesTree.isDirectory)(item)) { - return _react2.default.createElement("img", { - className: "folder" - }); - } - - const source = this.getSource(item); - - if (source) { - return _react2.default.createElement(_SourceIcon2.default, { - source: source - }); - } - - return null; - }; - - this.onContextMenu = (event, item) => { - const copySourceUri2Label = L10N.getStr("copySourceUri2"); - const copySourceUri2Key = L10N.getStr("copySourceUri2.accesskey"); - const setDirectoryRootLabel = L10N.getStr("setDirectoryRoot.label"); - const setDirectoryRootKey = L10N.getStr("setDirectoryRoot.accesskey"); - const removeDirectoryRootLabel = L10N.getStr("removeDirectoryRoot.label"); - event.stopPropagation(); - event.preventDefault(); - const menuOptions = []; - - if (!(0, _sourcesTree.isDirectory)(item)) { - // Flow requires some extra handling to ensure the value of contents. - const { - contents - } = item; - - if (!Array.isArray(contents)) { - const copySourceUri2 = { - id: "node-menu-copy-source", - label: copySourceUri2Label, - accesskey: copySourceUri2Key, - disabled: false, - click: () => (0, _clipboard.copyToTheClipboard)(contents.url) - }; - menuOptions.push(copySourceUri2); - } - } - - if ((0, _sourcesTree.isDirectory)(item) && _prefs.features.root) { - const { - path - } = item; - const { - projectRoot - } = this.props; - - if (projectRoot.endsWith(path)) { - menuOptions.push({ - id: "node-remove-directory-root", - label: removeDirectoryRootLabel, - disabled: false, - click: () => this.props.clearProjectDirectoryRoot() - }); - } else { - menuOptions.push({ - id: "node-set-directory-root", - label: setDirectoryRootLabel, - accesskey: setDirectoryRootKey, - disabled: false, - click: () => this.props.setProjectDirectoryRoot(path) - }); - } - } - - (0, _devtoolsContextmenu.showMenu)(event, menuOptions); + const generatedPart = source && source.sourceMapURL ? ":generated" : ""; + return `${path}${blackBoxedPart}${generatedPart}`; }; this.onExpand = (item, expandedState) => { this.props.setExpandedState(expandedState); }; @@ -405,44 +288,10 @@ if (e.keyCode === 13 && focusedItem) { this.selectItem(focusedItem); } }; - this.renderItem = (item, depth, focused, _, expanded, { - setExpanded - }) => { - const arrow = (0, _sourcesTree.isDirectory)(item) ? _react2.default.createElement("img", { - className: (0, _classnames2.default)("arrow", { - expanded: expanded - }) - }) : _react2.default.createElement("i", { - className: "no-arrow" - }); - const { - sources - } = this.props; - const icon = this.getIcon(sources, item, depth); - return _react2.default.createElement("div", { - className: (0, _classnames2.default)("node", { - focused - }), - key: item.path, - onClick: e => { - this.focusItem(item); - - if ((0, _sourcesTree.isDirectory)(item)) { - setExpanded(item, !!expanded, e.altKey); - } else { - this.selectItem(item); - } - }, - onContextMenu: e => this.onContextMenu(e, item) - }, arrow, icon, _react2.default.createElement("span", { - className: "label" - }, " ", this.renderItemName(item.name), " ")); - }; - this.getRoots = () => { const { projectRoot } = this.props; const { @@ -460,10 +309,33 @@ return sourceContents.contents; } return sourceTree.contents; }; + + this.renderItem = (item, depth, focused, _, expanded, { + setExpanded + }) => { + const { + debuggeeUrl, + projectRoot + } = this.props; + return _react2.default.createElement(_SourcesTreeItem2.default, { + item: item, + depth: depth, + focused: focused, + expanded: expanded, + setExpanded: setExpanded, + focusItem: this.focusItem, + selectItem: this.selectItem, + source: this.getSource(item), + debuggeeUrl: debuggeeUrl, + projectRoot: projectRoot, + clearProjectDirectoryRoot: this.props.clearProjectDirectoryRoot, + setProjectDirectoryRoot: this.props.setProjectDirectoryRoot + }); + }; }; const mapStateToProps = state => { return { shownSource: (0, _selectors.getShownSource)(state), @@ -474,12 +346,11 @@ sources: (0, _selectors.getRelativeSources)(state), sourceCount: (0, _selectors.getSourceCount)(state) }; }; -const actionCreators = { - setExpandedState: _sourceTree.setExpandedState, - selectSource: _sources.selectSource, - setProjectDirectoryRoot: _ui.setProjectDirectoryRoot, - clearProjectDirectoryRoot: _ui.clearProjectDirectoryRoot -}; -exports.default = (0, _reactRedux.connect)(mapStateToProps, actionCreators)(SourcesTree); \ No newline at end of file +exports.default = (0, _reactRedux.connect)(mapStateToProps, { + selectSource: _actions2.default.selectSource, + setExpandedState: _actions2.default.setExpandedState, + setProjectDirectoryRoot: _actions2.default.setProjectDirectoryRoot, + clearProjectDirectoryRoot: _actions2.default.clearProjectDirectoryRoot +})(SourcesTree); \ No newline at end of file diff --git a/devtools/client/debugger/new/src/components/PrimaryPanes/SourcesTreeItem.js b/devtools/client/debugger/new/src/components/PrimaryPanes/SourcesTreeItem.js new file mode 100644 --- /dev/null +++ b/devtools/client/debugger/new/src/components/PrimaryPanes/SourcesTreeItem.js @@ -0,0 +1,207 @@ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); + +var _react = require("devtools/client/shared/vendor/react"); + +var _react2 = _interopRequireDefault(_react); + +var _classnames = require("devtools/client/debugger/new/dist/vendors").vendored["classnames"]; + +var _classnames2 = _interopRequireDefault(_classnames); + +var _devtoolsContextmenu = require("devtools/client/debugger/new/dist/vendors").vendored["devtools-contextmenu"]; + +var _SourceIcon = require("../shared/SourceIcon"); + +var _SourceIcon2 = _interopRequireDefault(_SourceIcon); + +var _Svg = require("devtools/client/debugger/new/dist/vendors").vendored["Svg"]; + +var _Svg2 = _interopRequireDefault(_Svg); + +var _sourcesTree = require("../../utils/sources-tree/index"); + +var _clipboard = require("../../utils/clipboard"); + +var _prefs = require("../../utils/prefs"); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +/* 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 . */ +class SourceTreeItem extends _react.Component { + constructor(...args) { + var _temp; + + return _temp = super(...args), this.onClick = e => { + const { + expanded, + item, + focusItem, + setExpanded, + selectItem + } = this.props; + focusItem(item); + + if ((0, _sourcesTree.isDirectory)(item)) { + setExpanded(item, !!expanded, e.altKey); + } else { + selectItem(item); + } + }, this.onContextMenu = (event, item) => { + const copySourceUri2Label = L10N.getStr("copySourceUri2"); + const copySourceUri2Key = L10N.getStr("copySourceUri2.accesskey"); + const setDirectoryRootLabel = L10N.getStr("setDirectoryRoot.label"); + const setDirectoryRootKey = L10N.getStr("setDirectoryRoot.accesskey"); + const removeDirectoryRootLabel = L10N.getStr("removeDirectoryRoot.label"); + event.stopPropagation(); + event.preventDefault(); + const menuOptions = []; + + if (!(0, _sourcesTree.isDirectory)(item)) { + // Flow requires some extra handling to ensure the value of contents. + const { + contents + } = item; + + if (!Array.isArray(contents)) { + const copySourceUri2 = { + id: "node-menu-copy-source", + label: copySourceUri2Label, + accesskey: copySourceUri2Key, + disabled: false, + click: () => (0, _clipboard.copyToTheClipboard)(contents.url) + }; + menuOptions.push(copySourceUri2); + } + } + + if ((0, _sourcesTree.isDirectory)(item) && _prefs.features.root) { + const { + path + } = item; + const { + projectRoot + } = this.props; + + if (projectRoot.endsWith(path)) { + menuOptions.push({ + id: "node-remove-directory-root", + label: removeDirectoryRootLabel, + disabled: false, + click: () => this.props.clearProjectDirectoryRoot() + }); + } else { + menuOptions.push({ + id: "node-set-directory-root", + label: setDirectoryRootLabel, + accesskey: setDirectoryRootKey, + disabled: false, + click: () => this.props.setProjectDirectoryRoot(path) + }); + } + } + + (0, _devtoolsContextmenu.showMenu)(event, menuOptions); + }, _temp; + } + + getIcon(item, depth) { + const { + debuggeeUrl, + projectRoot, + source + } = this.props; + + if (item.path === "webpack://") { + return _react2.default.createElement(_Svg2.default, { + name: "webpack" + }); + } else if (item.path === "ng://") { + return _react2.default.createElement(_Svg2.default, { + name: "angular" + }); + } else if (item.path === "moz-extension://") { + return _react2.default.createElement("img", { + className: "extension" + }); + } + + if (depth === 0 && projectRoot === "") { + return _react2.default.createElement("img", { + className: (0, _classnames2.default)("domain", { + debuggee: debuggeeUrl && debuggeeUrl.includes(item.name) + }) + }); + } + + if ((0, _sourcesTree.isDirectory)(item)) { + return _react2.default.createElement("img", { + className: "folder" + }); + } + + if (source) { + return _react2.default.createElement(_SourceIcon2.default, { + source: source + }); + } + + return null; + } + + renderItemArrow() { + const { + item, + expanded + } = this.props; + return (0, _sourcesTree.isDirectory)(item) ? _react2.default.createElement("img", { + className: (0, _classnames2.default)("arrow", { + expanded + }) + }) : _react2.default.createElement("i", { + className: "no-arrow" + }); + } + + renderItemName(name) { + switch (name) { + case "ng://": + return "Angular"; + + case "webpack://": + return "Webpack"; + + case "moz-extension://": + return L10N.getStr("extensionsText"); + + default: + return name; + } + } + + render() { + const { + item, + depth, + focused + } = this.props; + return _react2.default.createElement("div", { + className: (0, _classnames2.default)("node", { + focused + }), + key: item.path, + onClick: this.onClick, + onContextMenu: e => this.onContextMenu(e, item) + }, this.renderItemArrow(), this.getIcon(item, depth), _react2.default.createElement("span", { + className: "label" + }, " ", this.renderItemName(item.name), " ")); + } + +} + +exports.default = SourceTreeItem; \ No newline at end of file diff --git a/devtools/client/debugger/new/src/components/PrimaryPanes/index.js b/devtools/client/debugger/new/src/components/PrimaryPanes/index.js --- a/devtools/client/debugger/new/src/components/PrimaryPanes/index.js +++ b/devtools/client/debugger/new/src/components/PrimaryPanes/index.js @@ -122,6 +122,11 @@ selectedTab: (0, _selectors.getSelectedPrimaryPaneTab)(state), sources: (0, _selectors.getSources)(state), sourceSearchOn: (0, _selectors.getActiveSearch)(state) === "source" }); -exports.default = (0, _reactRedux.connect)(mapStateToProps, _actions2.default)(PrimaryPanes); \ No newline at end of file +exports.default = (0, _reactRedux.connect)(mapStateToProps, { + setPrimaryPaneTab: _actions2.default.setPrimaryPaneTab, + selectLocation: _actions2.default.selectLocation, + setActiveSearch: _actions2.default.setActiveSearch, + closeActiveSearch: _actions2.default.closeActiveSearch +})(PrimaryPanes); \ No newline at end of file diff --git a/devtools/client/debugger/new/src/components/PrimaryPanes/moz.build b/devtools/client/debugger/new/src/components/PrimaryPanes/moz.build --- a/devtools/client/debugger/new/src/components/PrimaryPanes/moz.build +++ b/devtools/client/debugger/new/src/components/PrimaryPanes/moz.build @@ -9,6 +9,7 @@ DevToolsModules( 'index.js', 'Outline.js', 'SourcesTree.js', + 'SourcesTreeItem.js', ) diff --git a/devtools/client/debugger/new/src/components/ProjectSearch.js b/devtools/client/debugger/new/src/components/ProjectSearch.js --- a/devtools/client/debugger/new/src/components/ProjectSearch.js +++ b/devtools/client/debugger/new/src/components/ProjectSearch.js @@ -319,6 +319,13 @@ results: (0, _selectors.getTextSearchResults)(state), query: (0, _selectors.getTextSearchQuery)(state), status: (0, _selectors.getTextSearchStatus)(state) }); -exports.default = (0, _reactRedux.connect)(mapStateToProps, _actions2.default)(ProjectSearch); \ No newline at end of file +exports.default = (0, _reactRedux.connect)(mapStateToProps, { + closeProjectSearch: _actions2.default.closeProjectSearch, + searchSources: _actions2.default.searchSources, + clearSearch: _actions2.default.clearSearch, + selectLocation: _actions2.default.selectLocation, + setActiveSearch: _actions2.default.setActiveSearch, + doSearchForHighlight: _actions2.default.doSearchForHighlight +})(ProjectSearch); \ No newline at end of file diff --git a/devtools/client/debugger/new/src/components/QuickOpenModal.js b/devtools/client/debugger/new/src/components/QuickOpenModal.js --- a/devtools/client/debugger/new/src/components/QuickOpenModal.js +++ b/devtools/client/debugger/new/src/components/QuickOpenModal.js @@ -482,6 +482,13 @@ }; } /* istanbul ignore next: ignoring testing of redux connection stuff */ -exports.default = (0, _reactRedux.connect)(mapStateToProps, _actions2.default)(QuickOpenModal); \ No newline at end of file +exports.default = (0, _reactRedux.connect)(mapStateToProps, { + shortcutsModalEnabled: _actions2.default.shortcutsModalEnabled, + selectLocation: _actions2.default.selectLocation, + setQuickOpenQuery: _actions2.default.setQuickOpenQuery, + highlightLineRange: _actions2.default.highlightLineRange, + closeQuickOpen: _actions2.default.closeQuickOpen, + toggleShortcutsModal: _actions2.default.toggleShortcutsModal +})(QuickOpenModal); \ No newline at end of file diff --git a/devtools/client/debugger/new/src/components/SecondaryPanes/Breakpoints/Breakpoint.js b/devtools/client/debugger/new/src/components/SecondaryPanes/Breakpoints/Breakpoint.js --- a/devtools/client/debugger/new/src/components/SecondaryPanes/Breakpoints/Breakpoint.js +++ b/devtools/client/debugger/new/src/components/SecondaryPanes/Breakpoints/Breakpoint.js @@ -194,6 +194,17 @@ breakpoints: (0, _selectors.getBreakpoints)(state), frame: (0, _selectors.getTopFrame)(state), selectedSource: (0, _selectors.getSelectedSource)(state) }); -exports.default = (0, _reactRedux.connect)(mapStateToProps, _actions2.default)(Breakpoint); \ No newline at end of file +exports.default = (0, _reactRedux.connect)(mapStateToProps, { + enableBreakpoint: _actions2.default.enableBreakpoint, + removeBreakpoint: _actions2.default.removeBreakpoint, + removeBreakpoints: _actions2.default.removeBreakpoints, + removeAllBreakpoints: _actions2.default.removeAllBreakpoints, + disableBreakpoint: _actions2.default.disableBreakpoint, + selectSpecificLocation: _actions2.default.selectSpecificLocation, + selectLocation: _actions2.default.selectLocation, + toggleAllBreakpoints: _actions2.default.toggleAllBreakpoints, + toggleBreakpoints: _actions2.default.toggleBreakpoints, + openConditionalPanel: _actions2.default.openConditionalPanel +})(Breakpoint); \ No newline at end of file diff --git a/devtools/client/debugger/new/src/components/SecondaryPanes/Breakpoints/BreakpointsContextMenu.js b/devtools/client/debugger/new/src/components/SecondaryPanes/Breakpoints/BreakpointsContextMenu.js --- a/devtools/client/debugger/new/src/components/SecondaryPanes/Breakpoints/BreakpointsContextMenu.js +++ b/devtools/client/debugger/new/src/components/SecondaryPanes/Breakpoints/BreakpointsContextMenu.js @@ -16,11 +16,11 @@ removeBreakpoints, removeAllBreakpoints, toggleBreakpoints, toggleAllBreakpoints, toggleDisabledBreakpoint, - selectLocation, + selectSpecificLocation, setBreakpointCondition, openConditionalPanel, breakpoints, breakpoint, contextMenuEvent @@ -127,20 +127,20 @@ const addConditionItem = { id: "node-menu-add-condition", label: addConditionLabel, accesskey: addConditionKey, click: () => { - selectLocation(breakpoint.location); + selectSpecificLocation(breakpoint.location); openConditionalPanel(breakpoint.location.line); } }; const editConditionItem = { id: "node-menu-edit-condition", label: editConditionLabel, accesskey: editConditionKey, click: () => { - selectLocation(breakpoint.location); + selectSpecificLocation(breakpoint.location); openConditionalPanel(breakpoint.location.line); } }; const hideEnableSelfItem = !breakpoint.disabled; const hideEnableAllItem = disabledBreakpoints.size === 0; diff --git a/devtools/client/debugger/new/src/components/SecondaryPanes/Breakpoints/index.js b/devtools/client/debugger/new/src/components/SecondaryPanes/Breakpoints/index.js --- a/devtools/client/debugger/new/src/components/SecondaryPanes/Breakpoints/index.js +++ b/devtools/client/debugger/new/src/components/SecondaryPanes/Breakpoints/index.js @@ -81,11 +81,11 @@ key: source.url, onClick: () => this.props.selectSource(source.id) }, _react2.default.createElement(_SourceIcon2.default, { source: source, shouldHide: icon => ["file", "javascript"].includes(icon) - }), (0, _source.getFilename)(source)), ...breakpoints.map(breakpoint => _react2.default.createElement(_Breakpoint2.default, { + }), (0, _source.getTruncatedFileName)(source)), ...breakpoints.map(breakpoint => _react2.default.createElement(_Breakpoint2.default, { breakpoint: breakpoint, source: source, key: (0, _breakpoint.makeLocationId)(breakpoint.location) }))])]; } @@ -101,6 +101,9 @@ const mapStateToProps = state => ({ breakpointSources: (0, _selectors.getBreakpointSources)(state), selectedSource: (0, _selectors.getSelectedSource)(state) }); -exports.default = (0, _reactRedux.connect)(mapStateToProps, _actions2.default)(Breakpoints); \ No newline at end of file +exports.default = (0, _reactRedux.connect)(mapStateToProps, { + pauseOnExceptions: _actions2.default.pauseOnExceptions, + selectSource: _actions2.default.selectSource +})(Breakpoints); \ No newline at end of file diff --git a/devtools/client/debugger/new/src/components/SecondaryPanes/CommandBar.js b/devtools/client/debugger/new/src/components/SecondaryPanes/CommandBar.js --- a/devtools/client/debugger/new/src/components/SecondaryPanes/CommandBar.js +++ b/devtools/client/debugger/new/src/components/SecondaryPanes/CommandBar.js @@ -291,6 +291,20 @@ isWaitingOnBreak: (0, _selectors.getIsWaitingOnBreak)(state), canRewind: (0, _selectors.getCanRewind)(state), skipPausing: (0, _selectors.getSkipPausing)(state) }); -exports.default = (0, _reactRedux.connect)(mapStateToProps, _actions2.default)(CommandBar); \ No newline at end of file +exports.default = (0, _reactRedux.connect)(mapStateToProps, { + timeTravelTo: _actions2.default.timeTravelTo, + clearHistory: _actions2.default.clearHistory, + resume: _actions2.default.resume, + stepIn: _actions2.default.stepIn, + stepOut: _actions2.default.stepOut, + stepOver: _actions2.default.stepOver, + breakOnNext: _actions2.default.breakOnNext, + rewind: _actions2.default.rewind, + reverseStepIn: _actions2.default.reverseStepIn, + reverseStepOut: _actions2.default.reverseStepOut, + reverseStepOver: _actions2.default.reverseStepOver, + pauseOnExceptions: _actions2.default.pauseOnExceptions, + toggleSkipPausing: _actions2.default.toggleSkipPausing +})(CommandBar); \ No newline at end of file diff --git a/devtools/client/debugger/new/src/components/SecondaryPanes/EventListeners.js b/devtools/client/debugger/new/src/components/SecondaryPanes/EventListeners.js --- a/devtools/client/debugger/new/src/components/SecondaryPanes/EventListeners.js +++ b/devtools/client/debugger/new/src/components/SecondaryPanes/EventListeners.js @@ -109,6 +109,12 @@ return { listeners }; }; -exports.default = (0, _reactRedux.connect)(mapStateToProps, _actions2.default)(EventListeners); \ No newline at end of file +exports.default = (0, _reactRedux.connect)(mapStateToProps, { + selectLocation: _actions2.default.selectLocation, + addBreakpoint: _actions2.default.addBreakpoint, + enableBreakpoint: _actions2.default.enableBreakpoint, + disableBreakpoint: _actions2.default.disableBreakpoint, + removeBreakpoint: _actions2.default.removeBreakpoint +})(EventListeners); \ No newline at end of file diff --git a/devtools/client/debugger/new/src/components/SecondaryPanes/Expressions.js b/devtools/client/debugger/new/src/components/SecondaryPanes/Expressions.js --- a/devtools/client/debugger/new/src/components/SecondaryPanes/Expressions.js +++ b/devtools/client/debugger/new/src/components/SecondaryPanes/Expressions.js @@ -371,6 +371,16 @@ expressions: (0, _selectors.getExpressions)(state), expressionError: (0, _selectors.getExpressionError)(state) }; }; -exports.default = (0, _reactRedux.connect)(mapStateToProps, _actions2.default)(Expressions); \ No newline at end of file +exports.default = (0, _reactRedux.connect)(mapStateToProps, { + autocomplete: _actions2.default.autocomplete, + clearAutocomplete: _actions2.default.clearAutocomplete, + onExpressionAdded: _actions2.default.onExpressionAdded, + addExpression: _actions2.default.addExpression, + clearExpressionError: _actions2.default.clearExpressionError, + evaluateExpressions: _actions2.default.evaluateExpressions, + updateExpression: _actions2.default.updateExpression, + deleteExpression: _actions2.default.deleteExpression, + openLink: _actions2.default.openLink +})(Expressions); \ No newline at end of file diff --git a/devtools/client/debugger/new/src/components/SecondaryPanes/Frames/index.js b/devtools/client/debugger/new/src/components/SecondaryPanes/Frames/index.js --- a/devtools/client/debugger/new/src/components/SecondaryPanes/Frames/index.js +++ b/devtools/client/debugger/new/src/components/SecondaryPanes/Frames/index.js @@ -172,6 +172,11 @@ frameworkGroupingOn: (0, _selectors.getFrameworkGroupingState)(state), selectedFrame: (0, _selectors.getSelectedFrame)(state), pause: (0, _selectors.isPaused)(state) }); -exports.default = (0, _reactRedux.connect)(mapStateToProps, _actions2.default)(Frames); \ No newline at end of file +exports.default = (0, _reactRedux.connect)(mapStateToProps, { + selectFrame: _actions2.default.selectFrame, + toggleBlackBox: _actions2.default.toggleBlackBox, + toggleFrameworkGrouping: _actions2.default.toggleFrameworkGrouping, + disableFrameTruncate: _actions2.default.disableFrameTruncate +})(Frames); \ No newline at end of file diff --git a/devtools/client/debugger/new/src/components/SecondaryPanes/FrameworkComponent.js b/devtools/client/debugger/new/src/components/SecondaryPanes/FrameworkComponent.js --- a/devtools/client/debugger/new/src/components/SecondaryPanes/FrameworkComponent.js +++ b/devtools/client/debugger/new/src/components/SecondaryPanes/FrameworkComponent.js @@ -111,6 +111,8 @@ const mapStateToProps = state => ({ selectedFrame: (0, _selectors.getSelectedFrame)(state), popupObjectProperties: (0, _selectors.getAllPopupObjectProperties)(state) }); -exports.default = (0, _reactRedux.connect)(mapStateToProps, _actions2.default)(FrameworkComponent); \ No newline at end of file +exports.default = (0, _reactRedux.connect)(mapStateToProps, { + setPopupObjectProperties: _actions2.default.setPopupObjectProperties +})(FrameworkComponent); \ No newline at end of file diff --git a/devtools/client/debugger/new/src/components/SecondaryPanes/Scopes.js b/devtools/client/debugger/new/src/components/SecondaryPanes/Scopes.js --- a/devtools/client/debugger/new/src/components/SecondaryPanes/Scopes.js +++ b/devtools/client/debugger/new/src/components/SecondaryPanes/Scopes.js @@ -145,6 +145,8 @@ originalFrameScopes, generatedFrameScopes }; }; -exports.default = (0, _reactRedux.connect)(mapStateToProps, _actions2.default)(Scopes); \ No newline at end of file +exports.default = (0, _reactRedux.connect)(mapStateToProps, { + openLink: _actions2.default.openLink +})(Scopes); \ No newline at end of file diff --git a/devtools/client/debugger/new/src/components/SecondaryPanes/index.js b/devtools/client/debugger/new/src/components/SecondaryPanes/index.js --- a/devtools/client/debugger/new/src/components/SecondaryPanes/index.js +++ b/devtools/client/debugger/new/src/components/SecondaryPanes/index.js @@ -406,6 +406,12 @@ shouldPauseOnExceptions: (0, _selectors.getShouldPauseOnExceptions)(state), shouldPauseOnCaughtExceptions: (0, _selectors.getShouldPauseOnCaughtExceptions)(state), workers: (0, _selectors.getWorkers)(state) }); -exports.default = (0, _reactRedux.connect)(mapStateToProps, _actions2.default)(SecondaryPanes); \ No newline at end of file +exports.default = (0, _reactRedux.connect)(mapStateToProps, { + toggleAllBreakpoints: _actions2.default.toggleAllBreakpoints, + toggleShortcutsModal: _actions2.default.toggleShortcutsModal, + evaluateExpressions: _actions2.default.evaluateExpressions, + pauseOnExceptions: _actions2.default.pauseOnExceptions, + breakOnNext: _actions2.default.breakOnNext +})(SecondaryPanes); \ No newline at end of file diff --git a/devtools/client/debugger/new/src/components/WelcomeBox.js b/devtools/client/debugger/new/src/components/WelcomeBox.js --- a/devtools/client/debugger/new/src/components/WelcomeBox.js +++ b/devtools/client/debugger/new/src/components/WelcomeBox.js @@ -88,6 +88,10 @@ const mapStateToProps = state => ({ endPanelCollapsed: (0, _selectors.getPaneCollapse)(state, "end") }); -exports.default = (0, _reactRedux.connect)(mapStateToProps, _actions2.default)(WelcomeBox); \ No newline at end of file +exports.default = (0, _reactRedux.connect)(mapStateToProps, { + togglePaneCollapse: _actions2.default.togglePaneCollapse, + setActiveSearch: _actions2.default.setActiveSearch, + openQuickOpen: _actions2.default.openQuickOpen +})(WelcomeBox); \ No newline at end of file diff --git a/devtools/client/debugger/new/src/selectors/breakpointSources.js b/devtools/client/debugger/new/src/selectors/breakpointSources.js --- a/devtools/client/debugger/new/src/selectors/breakpointSources.js +++ b/devtools/client/debugger/new/src/selectors/breakpointSources.js @@ -22,11 +22,11 @@ } function findBreakpointSources(sources, breakpoints) { const sourceIds = (0, _lodash.uniq)(breakpoints.valueSeq().filter(bp => !bp.hidden).map(bp => bp.location.sourceId).toJS()); const breakpointSources = sourceIds.map(id => sources[id]).filter(source => source && !source.isBlackBoxed); - return (0, _lodash.sortBy)(breakpointSources, source => (0, _source.getFilenameFromURL)(source.url)); + return (0, _lodash.sortBy)(breakpointSources, source => (0, _source.getFilename)(source)); } function _getBreakpointSources(breakpoints, sources) { const breakpointSources = findBreakpointSources(sources, breakpoints); return breakpointSources.map(source => ({ diff --git a/devtools/client/debugger/new/src/utils/editor/source-documents.js b/devtools/client/debugger/new/src/utils/editor/source-documents.js --- a/devtools/client/debugger/new/src/utils/editor/source-documents.js +++ b/devtools/client/debugger/new/src/utils/editor/source-documents.js @@ -156,12 +156,13 @@ if (hasDocument(source.id)) { const doc = getDocument(source.id); if (editor.codeMirror.doc === doc) { const mode = (0, _source.getMode)(source, symbols); + const currentMode = editor.codeMirror.getOption("mode"); - if (doc.mode.name !== mode.name) { + if (currentMode.name !== mode.name) { editor.setMode(mode); } return; } diff --git a/devtools/client/debugger/new/src/utils/source.js b/devtools/client/debugger/new/src/utils/source.js --- a/devtools/client/debugger/new/src/utils/source.js +++ b/devtools/client/debugger/new/src/utils/source.js @@ -18,13 +18,13 @@ exports.isPretty = isPretty; exports.isPrettyURL = isPrettyURL; exports.isThirdParty = isThirdParty; exports.getPrettySourceURL = getPrettySourceURL; exports.getRawSourceURL = getRawSourceURL; -exports.getFilenameFromURL = getFilenameFromURL; exports.getFormattedSourceId = getFormattedSourceId; exports.getFilename = getFilename; +exports.getTruncatedFileName = getTruncatedFileName; exports.getFileURL = getFileURL; exports.getSourcePath = getSourcePath; exports.getSourceLineCount = getSourceLineCount; exports.getMode = getMode; exports.isLoaded = isLoaded; @@ -34,11 +34,11 @@ var _devtoolsSourceMap = require("devtools/client/shared/source-map/index.js"); var _utils = require("./utils"); -var _path = require("./path"); +var _text = require("../utils/text"); var _url = require("devtools/client/debugger/new/dist/vendors").vendored["url"]; var _devtoolsModules = require("devtools/client/debugger/new/dist/vendors").vendored["devtools-modules"]; @@ -151,21 +151,10 @@ function resolveFileURL(url, transformUrl = initialUrl => initialUrl) { url = getRawSourceURL(url || ""); const name = transformUrl(url); return (0, _utils.endTruncateStr)(name, 50); } -/** - * Gets a readable filename from a URL for display purposes. - * - * @memberof utils/source - * @static - */ - - -function getFilenameFromURL(url) { - return resolveFileURL(url, initialUrl => (0, _devtoolsModules.getUnicodeUrlPath)((0, _path.basename)(initialUrl)) || "(index)"); -} function getFormattedSourceId(id) { const sourceId = id.split("/")[1]; return `SOURCE${sourceId}`; } @@ -186,18 +175,25 @@ if (!url) { return getFormattedSourceId(id); } - let filename = getFilenameFromURL(url); - const qMarkIdx = filename.indexOf("?"); + const { + filename + } = (0, _sourcesTree.getURL)(source); + return filename; +} +/** + * Provides a middle-trunated filename + * + * @memberof utils/source + * @static + */ - if (qMarkIdx > 0) { - filename = filename.slice(0, qMarkIdx); - } - return filename; +function getTruncatedFileName(source, length = 30) { + return (0, _text.truncateMiddleText)(getFilename(source), length); } /** * Gets a readable source URL for display purposes. * If the source does not have a URL, the source ID will be returned instead. * diff --git a/devtools/client/debugger/new/src/utils/sources-tree/addToTree.js b/devtools/client/debugger/new/src/utils/sources-tree/addToTree.js --- a/devtools/client/debugger/new/src/utils/sources-tree/addToTree.js +++ b/devtools/client/debugger/new/src/utils/sources-tree/addToTree.js @@ -119,16 +119,16 @@ * @static */ function addToTree(tree, source, debuggeeUrl, projectRoot) { - const url = (0, _getURL.getURL)(source.url, debuggeeUrl); + const url = (0, _getURL.getURL)(source, debuggeeUrl); const debuggeeHost = (0, _treeOrder.getDomain)(debuggeeUrl); if ((0, _utils.isInvalidUrl)(url, source)) { return; } const finalNode = traverseTree(url, tree, debuggeeHost); // $FlowIgnore finalNode.contents = addSourceToNode(finalNode, url, source); } \ No newline at end of file diff --git a/devtools/client/debugger/new/src/utils/sources-tree/getDirectories.js b/devtools/client/debugger/new/src/utils/sources-tree/getDirectories.js --- a/devtools/client/debugger/new/src/utils/sources-tree/getDirectories.js +++ b/devtools/client/debugger/new/src/utils/sources-tree/getDirectories.js @@ -35,23 +35,23 @@ } return returnTarget; } -function getDirectories(sourceUrl, sourceTree) { - const url = (0, _getURL.getURL)(sourceUrl); +function getDirectories(source, sourceTree) { + const url = (0, _getURL.getURL)(source); const fullUrl = `${url.group}${url.path}`; const parentMap = (0, _utils.createParentMap)(sourceTree); - const source = findSource(sourceTree, fullUrl); + const subtreeSource = findSource(sourceTree, fullUrl); - if (!source) { + if (!subtreeSource) { return []; } - let node = source; + let node = subtreeSource; const directories = []; - directories.push(source); + directories.push(subtreeSource); while (true) { node = parentMap.get(node); if (!node) { diff --git a/devtools/client/debugger/new/src/utils/sources-tree/getURL.js b/devtools/client/debugger/new/src/utils/sources-tree/getURL.js --- a/devtools/client/debugger/new/src/utils/sources-tree/getURL.js +++ b/devtools/client/debugger/new/src/utils/sources-tree/getURL.js @@ -6,17 +6,18 @@ exports.getFilenameFromPath = getFilenameFromPath; exports.getURL = getURL; var _url = require("devtools/client/debugger/new/dist/vendors").vendored["url"]; -var _lodash = require("devtools/client/shared/vendor/lodash"); - var _devtoolsModules = require("devtools/client/debugger/new/dist/vendors").vendored["devtools-modules"]; -/* 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 . */ +function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; var ownKeys = Object.keys(source); if (typeof Object.getOwnPropertySymbols === 'function') { ownKeys = ownKeys.concat(Object.getOwnPropertySymbols(source).filter(function (sym) { return Object.getOwnPropertyDescriptor(source, sym).enumerable; })); } ownKeys.forEach(function (key) { _defineProperty(target, key, source[key]); }); } return target; } + +function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } + +const urlMap = new WeakMap(); + function getFilenameFromPath(pathname) { let filename = ""; if (pathname) { filename = pathname.substring(pathname.lastIndexOf("/") + 1); // This file does not have a name. Default should be (index). @@ -28,18 +29,20 @@ return filename; } const NoDomain = "(no domain)"; +const def = { + path: "", + group: "", + filename: "" +}; -function getURL(sourceUrl, debuggeeUrl = "") { - const url = sourceUrl; - const def = { - path: "", - group: "", - filename: "" - }; +function _getURL(source, debuggeeUrl) { + const { + url + } = source; if (!url) { return def; } @@ -47,78 +50,89 @@ pathname, protocol, host, path } = (0, _url.parse)(url); - const defaultDomain = (0, _url.parse)(debuggeeUrl).host; - const filename = getFilenameFromPath(pathname); + const filename = (0, _devtoolsModules.getUnicodeUrlPath)(getFilenameFromPath(pathname)); switch (protocol) { case "javascript:": // Ignore `javascript:` URLs for now return def; case "moz-extension:": case "resource:": - return (0, _lodash.merge)(def, { + return _objectSpread({}, def, { path, - group: `${protocol}//${host || ""}`, - filename + filename, + group: `${protocol}//${host || ""}` }); case "webpack:": case "ng:": - return (0, _lodash.merge)(def, { + return _objectSpread({}, def, { path: path, - group: `${protocol}//`, - filename: filename + filename, + group: `${protocol}//` }); case "about:": // An about page is a special case - return (0, _lodash.merge)(def, { + return _objectSpread({}, def, { path: "/", - group: url, - filename: filename + filename, + group: url }); case "data:": - return (0, _lodash.merge)(def, { + return _objectSpread({}, def, { path: "/", group: NoDomain, filename: url }); case null: if (pathname && pathname.startsWith("/")) { // use file protocol for a URL like "/foo/bar.js" - return (0, _lodash.merge)(def, { + return _objectSpread({}, def, { path: path, - group: "file://", - filename: filename + filename, + group: "file://" }); } else if (host === null) { // use anonymous group for weird URLs - return (0, _lodash.merge)(def, { + const defaultDomain = (0, _url.parse)(debuggeeUrl).host; + return _objectSpread({}, def, { path: url, group: defaultDomain, - filename: filename + filename }); } break; case "http:": case "https:": - return (0, _lodash.merge)(def, { + return _objectSpread({}, def, { path: pathname, - group: (0, _devtoolsModules.getUnicodeHostname)(host), - filename: filename + filename, + group: (0, _devtoolsModules.getUnicodeHostname)(host) }); } - return (0, _lodash.merge)(def, { + return _objectSpread({}, def, { path: path, group: protocol ? `${protocol}//` : "", - filename: filename + filename }); +} + +function getURL(source, debuggeeUrl = "") { + if (urlMap.has(source)) { + return urlMap.get(source) || def; + } + + const url = _getURL(source, debuggeeUrl); + + urlMap.set(source, url); + return url; } \ No newline at end of file diff --git a/devtools/client/debugger/new/test/mochitest/browser.ini b/devtools/client/debugger/new/test/mochitest/browser.ini --- a/devtools/client/debugger/new/test/mochitest/browser.ini +++ b/devtools/client/debugger/new/test/mochitest/browser.ini @@ -227,11 +227,11 @@ [browser_dbg-sourcemaps-bogus.js] skip-if = os == 'linux' && !asan # bug 1447118 [browser_dbg-sources.js] [browser_dbg-sources-named-eval.js] [browser_dbg-stepping.js] -skip-if = debug || (verify && (os == 'win')) +skip-if = debug || (verify && (os == 'win')) || (os == "win" && os_version == "6.1") [browser_dbg-tabs.js] [browser_dbg-tabs-pretty-print.js] [browser_dbg-toggling-tools.js] [browser_dbg-wasm-sourcemaps.js] skip-if = true diff --git a/devtools/client/definitions.js b/devtools/client/definitions.js --- a/devtools/client/definitions.js +++ b/devtools/client/definitions.js @@ -2,11 +2,11 @@ * 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 Services=require("Services"); // space-infix-ops (eslint) const osString = Services.appinfo.OS; const { Cu } = require("chrome"); // Panels loader.lazyGetter(this, "OptionsPanel", () => require("devtools/client/framework/toolbox-options").OptionsPanel); diff --git a/devtools/client/framework/components/ToolboxTabs.js b/devtools/client/framework/components/ToolboxTabs.js --- a/devtools/client/framework/components/ToolboxTabs.js +++ b/devtools/client/framework/components/ToolboxTabs.js @@ -73,10 +73,12 @@ this.updateOverflowedTabs(); } } componentWillUnmount() { + window.removeEventListener("resize", this.resizeHandler); + window.cancelIdleCallback(this._resizeTimerId); this._tabsOrderManager.destroy(); } /** * Check if two array of ids are the same or not. diff --git a/devtools/client/inspector/animation/animation.js b/devtools/client/inspector/animation/animation.js --- a/devtools/client/inspector/animation/animation.js +++ b/devtools/client/inspector/animation/animation.js @@ -515,11 +515,12 @@ */ async setHighlightedNode(nodeFront) { await this.inspector.highlighters.hideBoxModelHighlighter(); if (nodeFront) { - await this.inspector.highlighters.showBoxModelHighlighter(nodeFront); + await this.inspector.highlighters.showBoxModelHighlighter( + nodeFront, { hideInfoBar: true, hideGuides: true }); } this.inspector.store.dispatch(updateHighlightedNode(nodeFront)); } diff --git a/devtools/client/inspector/animation/components/AnimationTarget.js b/devtools/client/inspector/animation/components/AnimationTarget.js --- a/devtools/client/inspector/animation/components/AnimationTarget.js +++ b/devtools/client/inspector/animation/components/AnimationTarget.js @@ -86,11 +86,12 @@ async highlight() { await this.ensureNodeFront(); if (this.state.nodeFront) { - this.props.onShowBoxModelHighlighterForNode(this.state.nodeFront); + this.props.onShowBoxModelHighlighterForNode( + this.state.nodeFront, { hideInfoBar: true, hideGuides: true }); } } async select() { await this.ensureNodeFront(); diff --git a/devtools/client/inspector/animation/components/keyframes-graph/ColorPath.js b/devtools/client/inspector/animation/components/keyframes-graph/ColorPath.js --- a/devtools/client/inspector/animation/components/keyframes-graph/ColorPath.js +++ b/devtools/client/inspector/animation/components/keyframes-graph/ColorPath.js @@ -31,11 +31,11 @@ getPropertyValue(keyframe) { return keyframe.value; } propToState({ keyframes }) { - const maxObject = { distance: 0 }; + const maxObject = { distance: -Number.MAX_VALUE }; for (let i = 0; i < keyframes.length - 1; i++) { const value1 = getRGBA(keyframes[i].value); for (let j = i + 1; j < keyframes.length; j++) { const value2 = getRGBA(keyframes[j].value); diff --git a/devtools/client/inspector/animation/test/doc_multi_keyframes.html b/devtools/client/inspector/animation/test/doc_multi_keyframes.html --- a/devtools/client/inspector/animation/test/doc_multi_keyframes.html +++ b/devtools/client/inspector/animation/test/doc_multi_keyframes.html @@ -198,8 +198,32 @@ opacity: 1, offset: 1, }, ] ); + + createAnimation( + "same-color", + [ + { + backgroundColor: "lime", + }, + { + backgroundColor: "lime", + }, + ] + ); + + createAnimation( + "currentcolor", + [ + { + backgroundColor: "currentColor", + }, + { + backgroundColor: "lime", + }, + ] + ); diff --git a/devtools/client/inspector/animation/test/keyframes-graph_keyframe-marker_head.js b/devtools/client/inspector/animation/test/keyframes-graph_keyframe-marker_head.js --- a/devtools/client/inspector/animation/test/keyframes-graph_keyframe-marker_head.js +++ b/devtools/client/inspector/animation/test/keyframes-graph_keyframe-marker_head.js @@ -130,11 +130,47 @@ marginInlineStart: "100%", }, ], }, ], - } + }, + { + targetClass: "same-color", + properties: [ + { + name: "background-color", + expectedValues: [ + { + title: "rgb(0, 255, 0)", + marginInlineStart: "0%", + }, + { + title: "rgb(0, 255, 0)", + marginInlineStart: "100%", + }, + ], + }, + ], + }, + { + targetClass: "currentcolor", + properties: [ + { + name: "background-color", + expectedValues: [ + { + title: "currentcolor", + marginInlineStart: "0%", + }, + { + title: "rgb(0, 255, 0)", + marginInlineStart: "100%", + }, + ], + }, + ], + }, ]; /** * Do test for keyframes-graph_keyframe-marker-ltf/rtl. * @@ -177,6 +213,5 @@ `${ expectedValue.marginInlineStart }`); } } } } - diff --git a/devtools/client/menus.js b/devtools/client/menus.js --- a/devtools/client/menus.js +++ b/devtools/client/menus.js @@ -1,10 +1,10 @@ /* 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"; +// "use strict"; // strict (eslint) /** * This module defines the sorted list of menuitems inserted into the * "Web Developer" menu. * It also defines the key shortcuts that relates to them. @@ -37,10 +37,12 @@ loader.lazyRequireGetter(this, "openDocLink", "devtools/client/shared/link", true); loader.lazyImporter(this, "BrowserToolboxProcess", "resource://devtools/client/framework/ToolboxProcess.jsm"); loader.lazyImporter(this, "ScratchpadManager", "resource://devtools/client/scratchpad/scratchpad-manager.jsm"); + // no-tabs (eslint) + exports.menuitems = [ { id: "menu_devToolbox", l10nKey: "devToolboxMenuItem", oncommand(event) { const window = event.target.ownerDocument.defaultView; diff --git a/devtools/client/shared/scroll.js b/devtools/client/shared/scroll.js --- a/devtools/client/shared/scroll.js +++ b/devtools/client/shared/scroll.js @@ -2,11 +2,11 @@ * 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"; -// Make this available to both AMD and CJS environments +# Make this available to both AMD and CJS environments // Parsing error: Unexpected character '#' (eslint) define(function(require, exports, module) { /** * Scroll the document so that the element "elem" appears in the viewport. * * @param {DOMNode} elem diff --git a/devtools/client/webconsole/browser-console.js b/devtools/client/webconsole/browser-console.js new file mode 100644 --- /dev/null +++ b/devtools/client/webconsole/browser-console.js @@ -0,0 +1,133 @@ +/* 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"; + +var Services = require("Services"); +loader.lazyRequireGetter(this, "extend", "devtools/shared/extend", true); +loader.lazyRequireGetter(this, "Telemetry", "devtools/client/shared/telemetry"); +loader.lazyRequireGetter(this, "WebConsole", "devtools/client/webconsole/webconsole"); + +// The preference prefix for all of the Browser Console filters. +const BC_FILTER_PREFS_PREFIX = "devtools.browserconsole.filter."; + +/** + * A BrowserConsole instance is an interactive console initialized *per target* + * that displays console log data as well as provides an interactive terminal to + * manipulate the target's document content. + * + * This object only wraps the iframe that holds the Browser Console UI. This is + * meant to be an integration point between the Firefox UI and the Browser Console + * UI and features. + * + * This object extends the WebConsole object located in webconsole.js + * + * @constructor + * @param object target + * The target that the browser console will connect to. + * @param nsIDOMWindow iframeWindow + * The window where the browser console UI is already loaded. + * @param nsIDOMWindow chromeWindow + * The window of the browser console owner. + * @param object hudService + * The parent HUD Service + */ +function BrowserConsole(target, iframeWindow, chromeWindow, hudService) { + WebConsole.call(this, target, iframeWindow, chromeWindow, hudService); + this._telemetry = new Telemetry(); +} + +BrowserConsole.prototype = extend(WebConsole.prototype, { + _browserConsole: true, + _bcInit: null, + _bcDestroyer: null, + + $init: WebConsole.prototype.init, + + /** + * Initialize the Browser Console instance. + * + * @return object + * A promise for the initialization. + */ + init() { + if (this._bcInit) { + return this._bcInit; + } + + // Only add the shutdown observer if we've opened a Browser Console window. + ShutdownObserver.init(this.hudService); + + this.ui._filterPrefsPrefix = BC_FILTER_PREFS_PREFIX; + + const window = this.iframeWindow; + + // Make sure that the closing of the Browser Console window destroys this + // instance. + window.addEventListener("unload", () => { + this.destroy(); + }, {once: true}); + + this._telemetry.toolOpened("browserconsole"); + + this._bcInit = this.$init(); + return this._bcInit; + }, + + $destroy: WebConsole.prototype.destroy, + + /** + * Destroy the object. + * + * @return object + * A promise object that is resolved once the Browser Console is closed. + */ + destroy() { + if (this._bcDestroyer) { + return this._bcDestroyer; + } + + this._bcDestroyer = (async () => { + this._telemetry.toolClosed("browserconsole"); + await this.$destroy(); + await this.target.client.close(); + this.hudService._browserConsoleID = null; + this.chromeWindow.close(); + })(); + + return this._bcDestroyer; + }, +}); + +/** + * The ShutdownObserver listens for app shutdown and saves the current state + * of the Browser Console for session restore. + */ +var ShutdownObserver = { + _initialized: false, + + init(hudService) { + if (this._initialized) { + return; + } + + Services.obs.addObserver(this, "quit-application-granted"); + + this._initialized = true; + this.hudService = hudService; + }, + + observe(message, topic) { + if (topic == "quit-application-granted") { + this.hudService.storeBrowserConsoleSessionState(); + this.uninit(); + } + }, + + uninit() { + Services.obs.removeObserver(this, "quit-application-granted"); + } +}; + +module.exports = BrowserConsole; diff --git a/devtools/client/webconsole/components/JSTerm.js b/devtools/client/webconsole/components/JSTerm.js --- a/devtools/client/webconsole/components/JSTerm.js +++ b/devtools/client/webconsole/components/JSTerm.js @@ -17,10 +17,11 @@ loader.lazyRequireGetter(this, "PropTypes", "devtools/client/shared/vendor/react-prop-types"); loader.lazyRequireGetter(this, "gDevTools", "devtools/client/framework/devtools", true); loader.lazyRequireGetter(this, "KeyCodes", "devtools/client/shared/keycodes", true); loader.lazyRequireGetter(this, "Editor", "devtools/client/sourceeditor/editor"); loader.lazyRequireGetter(this, "Telemetry", "devtools/client/shared/telemetry"); +loader.lazyRequireGetter(this, "processScreenshot", "devtools/shared/webconsole/screenshot-helper"); const l10n = require("devtools/client/webconsole/webconsole-l10n"); const HELP_URL = "https://developer.mozilla.org/docs/Tools/Web_Console/Helpers"; @@ -329,11 +330,11 @@ * Optional function to invoke when the evaluation result is added to * the output. * @param object response * The message received from the server. */ - _executeResultCallback(callback, response) { + async _executeResultCallback(callback, response) { if (!this.hud) { return; } if (response.error) { console.error("Evaluation error " + response.error + ": " + response.message); @@ -371,10 +372,16 @@ this.hud.owner.openLink(HELP_URL); break; case "copyValueToClipboard": clipboardHelper.copyString(helperResult.value); break; + case "screenshotOutput": + const { args, value } = helperResult; + const results = await processScreenshot(this.hud.window, args, value); + this.screenshotNotify(results); + // early return as screenshot notify has dispatched all necessary messages + return; } } // Hide undefined results coming from JSTerm helper functions. if (!errorMessage && result && typeof result == "object" && @@ -397,10 +404,15 @@ } }, true); return this.hud.consoleOutput; } + screenshotNotify(results) { + const wrappedResults = results.map(result => ({ result })); + this.hud.consoleOutput.dispatchMessagesAdd(wrappedResults); + } + /** * Execute a string. Execution happens asynchronously in the content process. * * @param string [executeString] * The string you want to execute. If this is not provided, the current diff --git a/devtools/client/webconsole/hudservice.js b/devtools/client/webconsole/hudservice.js --- a/devtools/client/webconsole/hudservice.js +++ b/devtools/client/webconsole/hudservice.js @@ -3,49 +3,42 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ "use strict"; var Services = require("Services"); -loader.lazyRequireGetter(this, "Utils", "devtools/client/webconsole/utils", true); -loader.lazyRequireGetter(this, "extend", "devtools/shared/extend", true); loader.lazyRequireGetter(this, "TargetFactory", "devtools/client/framework/target", true); loader.lazyRequireGetter(this, "Tools", "devtools/client/definitions", true); -loader.lazyRequireGetter(this, "Telemetry", "devtools/client/shared/telemetry"); -loader.lazyRequireGetter(this, "WebConsoleFrame", "devtools/client/webconsole/webconsole-frame", true); loader.lazyRequireGetter(this, "gDevTools", "devtools/client/framework/devtools", true); loader.lazyRequireGetter(this, "DebuggerServer", "devtools/server/main", true); loader.lazyRequireGetter(this, "DebuggerClient", "devtools/shared/client/debugger-client", true); -loader.lazyRequireGetter(this, "viewSource", "devtools/client/shared/view-source"); loader.lazyRequireGetter(this, "l10n", "devtools/client/webconsole/webconsole-l10n"); -loader.lazyRequireGetter(this, "openDocLink", "devtools/client/shared/link", true); -const BC_WINDOW_FEATURES = "chrome,titlebar,toolbar,centerscreen,resizable,dialog=no"; - -// The preference prefix for all of the Browser Console filters. -const BC_FILTER_PREFS_PREFIX = "devtools.browserconsole.filter."; +loader.lazyRequireGetter(this, "WebConsole", "devtools/client/webconsole/webconsole"); +loader.lazyRequireGetter(this, "BrowserConsole", "devtools/client/webconsole/browser-console"); -var gHudId = 0; +const BC_WINDOW_FEATURES = "chrome,titlebar,toolbar,centerscreen,resizable,dialog=no"; -function HUD_SERVICE() { +function HUDService() { this.consoles = new Map(); this.lastFinishedRequest = { callback: null }; } -HUD_SERVICE.prototype = -{ +HUDService.prototype = { _browserConsoleID: null, _browserConsoleInitializing: null, /** * Keeps a reference for each Web Console / Browser Console that is created. * @type Map */ consoles: null, _browerConsoleSessionState: false, + storeBrowserConsoleSessionState() { this._browerConsoleSessionState = !!this.getBrowserConsole(); }, + getBrowserConsoleSessionState() { return this._browerConsoleSessionState; }, /** @@ -81,11 +74,11 @@ * The window of the web console owner. * @return object * A promise object for the opening of the new WebConsole instance. */ openWebConsole(target, iframeWindow, chromeWindow) { - const hud = new WebConsole(target, iframeWindow, chromeWindow); + const hud = new WebConsole(target, iframeWindow, chromeWindow, this); this.consoles.set(hud.hudId, hud); return hud.init(); }, /** @@ -101,11 +94,11 @@ * The window of the browser console owner. * @return object * A promise object for the opening of the new BrowserConsole instance. */ openBrowserConsole(target, iframeWindow, chromeWindow) { - const hud = new BrowserConsole(target, iframeWindow, chromeWindow); + const hud = new BrowserConsole(target, iframeWindow, chromeWindow, this); this._browserConsoleID = hud.hudId; this.consoles.set(hud.hudId, hud); return hud.init(); }, @@ -244,416 +237,6 @@ getBrowserConsole() { return this.getHudReferenceById(this._browserConsoleID); }, }; -/** - * A WebConsole instance is an interactive console initialized *per target* - * that displays console log data as well as provides an interactive terminal to - * manipulate the target's document content. - * - * This object only wraps the iframe that holds the Web Console UI. This is - * meant to be an integration point between the Firefox UI and the Web Console - * UI and features. - * - * @constructor - * @param object target - * The target that the web console will connect to. - * @param nsIDOMWindow iframeWindow - * The window where the web console UI is already loaded. - * @param nsIDOMWindow chromeWindow - * The window of the web console owner. - */ -function WebConsole(target, iframeWindow, chromeWindow) { - this.iframeWindow = iframeWindow; - this.chromeWindow = chromeWindow; - this.hudId = "hud_" + ++gHudId; - this.target = target; - this.browserWindow = this.chromeWindow.top; - const element = this.browserWindow.document.documentElement; - if (element.getAttribute("windowtype") != gDevTools.chromeWindowType) { - this.browserWindow = HUDService.currentContext(); - } - this.ui = new WebConsoleFrame(this); -} -WebConsole.prototype = { - iframeWindow: null, - chromeWindow: null, - browserWindow: null, - hudId: null, - target: null, - ui: null, - _browserConsole: false, - _destroyer: null, - - /** - * Getter for a function to to listen for every request that completes. Used - * by unit tests. The callback takes one argument: the HTTP activity object as - * received from the remote Web Console. - * - * @type function - */ - get lastFinishedRequestCallback() { - return HUDService.lastFinishedRequest.callback; - }, - - /** - * Getter for the window that can provide various utilities that the web - * console makes use of, like opening links, managing popups, etc. In - * most cases, this will be |this.browserWindow|, but in some uses (such as - * the Browser Toolbox), there is no browser window, so an alternative window - * hosts the utilities there. - * @type nsIDOMWindow - */ - get chromeUtilsWindow() { - if (this.browserWindow) { - return this.browserWindow; - } - return this.chromeWindow.top; - }, - - /** - * Getter for the xul:popupset that holds any popups we open. - * @type Element - */ - get mainPopupSet() { - return this.chromeUtilsWindow.document.getElementById("mainPopupSet"); - }, - - /** - * Getter for the output element that holds messages we display. - * @type Element - */ - get outputNode() { - return this.ui ? this.ui.outputNode : null; - }, - - get gViewSourceUtils() { - return this.chromeUtilsWindow.gViewSourceUtils; - }, - - /** - * Initialize the Web Console instance. - * - * @return object - * A promise for the initialization. - */ - init() { - return this.ui.init().then(() => this); - }, - - /** - * The JSTerm object that manages the console's input. - * @see webconsole.js::JSTerm - * @type object - */ - get jsterm() { - return this.ui ? this.ui.jsterm : null; - }, - - /** - * Alias for the WebConsoleFrame.setFilterState() method. - * @see webconsole.js::WebConsoleFrame.setFilterState() - */ - setFilterState() { - this.ui && this.ui.setFilterState.apply(this.ui, arguments); - }, - - /** - * Open a link in a new tab. - * - * @param string link - * The URL you want to open in a new tab. - */ - openLink(link, e) { - openDocLink(link); - }, - - /** - * Open a link in Firefox's view source. - * - * @param string sourceURL - * The URL of the file. - * @param integer sourceLine - * The line number which should be highlighted. - */ - viewSource(sourceURL, sourceLine) { - this.gViewSourceUtils.viewSource({ URL: sourceURL, lineNumber: sourceLine || 0 }); - }, - - /** - * Tries to open a Stylesheet file related to the web page for the web console - * instance in the Style Editor. If the file is not found, it is opened in - * source view instead. - * - * Manually handle the case where toolbox does not exist (Browser Console). - * - * @param string sourceURL - * The URL of the file. - * @param integer sourceLine - * The line number which you want to place the caret. - */ - viewSourceInStyleEditor(sourceURL, sourceLine) { - const toolbox = gDevTools.getToolbox(this.target); - if (!toolbox) { - this.viewSource(sourceURL, sourceLine); - return; - } - toolbox.viewSourceInStyleEditor(sourceURL, sourceLine); - }, - - /** - * Tries to open a JavaScript file related to the web page for the web console - * instance in the Script Debugger. If the file is not found, it is opened in - * source view instead. - * - * Manually handle the case where toolbox does not exist (Browser Console). - * - * @param string sourceURL - * The URL of the file. - * @param integer sourceLine - * The line number which you want to place the caret. - */ - viewSourceInDebugger(sourceURL, sourceLine) { - const toolbox = gDevTools.getToolbox(this.target); - if (!toolbox) { - this.viewSource(sourceURL, sourceLine); - return; - } - toolbox.viewSourceInDebugger(sourceURL, sourceLine).then(() => { - this.ui.emit("source-in-debugger-opened"); - }); - }, - - /** - * Tries to open a JavaScript file related to the web page for the web console - * instance in the corresponding Scratchpad. - * - * @param string sourceURL - * The URL of the file which corresponds to a Scratchpad id. - */ - viewSourceInScratchpad(sourceURL, sourceLine) { - viewSource.viewSourceInScratchpad(sourceURL, sourceLine); - }, - - /** - * Retrieve information about the JavaScript debugger's stackframes list. This - * is used to allow the Web Console to evaluate code in the selected - * stackframe. - * - * @return object|null - * An object which holds: - * - frames: the active ThreadClient.cachedFrames array. - * - selected: depth/index of the selected stackframe in the debugger - * UI. - * If the debugger is not open or if it's not paused, then |null| is - * returned. - */ - getDebuggerFrames() { - const toolbox = gDevTools.getToolbox(this.target); - if (!toolbox) { - return null; - } - const panel = toolbox.getPanel("jsdebugger"); - - if (!panel) { - return null; - } - - return panel.getFrames(); - }, - - async getMappedExpression(expression) { - const toolbox = gDevTools.getToolbox(this.target); - if (!toolbox) { - return expression; - } - const panel = toolbox.getPanel("jsdebugger"); - - if (!panel) { - return expression; - } - - return panel.getMappedExpression(expression); - }, - - /** - * Retrieves the current selection from the Inspector, if such a selection - * exists. This is used to pass the ID of the selected actor to the Web - * Console server for the $0 helper. - * - * @return object|null - * A Selection referring to the currently selected node in the - * Inspector. - * If the inspector was never opened, or no node was ever selected, - * then |null| is returned. - */ - getInspectorSelection() { - const toolbox = gDevTools.getToolbox(this.target); - if (!toolbox) { - return null; - } - const panel = toolbox.getPanel("inspector"); - if (!panel || !panel.selection) { - return null; - } - return panel.selection; - }, - - /** - * Destroy the object. Call this method to avoid memory leaks when the Web - * Console is closed. - * - * @return object - * A promise object that is resolved once the Web Console is closed. - */ - async destroy() { - if (this._destroyer) { - return this._destroyer; - } - - this._destroyer = (async () => { - HUDService.consoles.delete(this.hudId); - - // The document may already be removed - if (this.chromeUtilsWindow && this.mainPopupSet) { - const popupset = this.mainPopupSet; - const panels = popupset.querySelectorAll("panel[hudId=" + this.hudId + "]"); - for (const panel of panels) { - panel.hidePopup(); - } - } - - if (this.ui) { - await this.ui.destroy(); - } - - if (!this._browserConsole) { - try { - await this.target.activeTab.focus(); - } catch (ex) { - // Tab focus can fail if the tab or target is closed. - } - } - - const id = Utils.supportsString(this.hudId); - Services.obs.notifyObservers(id, "web-console-destroyed"); - })(); - - return this._destroyer; - }, -}; - -/** - * A BrowserConsole instance is an interactive console initialized *per target* - * that displays console log data as well as provides an interactive terminal to - * manipulate the target's document content. - * - * This object only wraps the iframe that holds the Browser Console UI. This is - * meant to be an integration point between the Firefox UI and the Browser Console - * UI and features. - * - * @constructor - * @param object target - * The target that the browser console will connect to. - * @param nsIDOMWindow iframeWindow - * The window where the browser console UI is already loaded. - * @param nsIDOMWindow chromeWindow - * The window of the browser console owner. - */ -function BrowserConsole() { - WebConsole.apply(this, arguments); - this._telemetry = new Telemetry(); -} - -BrowserConsole.prototype = extend(WebConsole.prototype, { - _browserConsole: true, - _bcInit: null, - _bcDestroyer: null, - - $init: WebConsole.prototype.init, - - /** - * Initialize the Browser Console instance. - * - * @return object - * A promise for the initialization. - */ - init() { - if (this._bcInit) { - return this._bcInit; - } - - // Only add the shutdown observer if we've opened a Browser Console window. - ShutdownObserver.init(); - - this.ui._filterPrefsPrefix = BC_FILTER_PREFS_PREFIX; - - const window = this.iframeWindow; - - // Make sure that the closing of the Browser Console window destroys this - // instance. - window.addEventListener("unload", () => { - this.destroy(); - }, {once: true}); - - this._telemetry.toolOpened("browserconsole"); - - this._bcInit = this.$init(); - return this._bcInit; - }, - - $destroy: WebConsole.prototype.destroy, - - /** - * Destroy the object. - * - * @return object - * A promise object that is resolved once the Browser Console is closed. - */ - destroy() { - if (this._bcDestroyer) { - return this._bcDestroyer; - } - - this._bcDestroyer = (async () => { - this._telemetry.toolClosed("browserconsole"); - await this.$destroy(); - await this.target.client.close(); - HUDService._browserConsoleID = null; - this.chromeWindow.close(); - })(); - - return this._bcDestroyer; - }, -}); - -const HUDService = new HUD_SERVICE(); -exports.HUDService = HUDService; - -/** - * The ShutdownObserver listens for app shutdown and saves the current state - * of the Browser Console for session restore. - */ -var ShutdownObserver = { - _initialized: false, - init() { - if (this._initialized) { - return; - } - - Services.obs.addObserver(this, "quit-application-granted"); - - this._initialized = true; - }, - - observe(message, topic) { - if (topic == "quit-application-granted") { - HUDService.storeBrowserConsoleSessionState(); - this.uninit(); - } - }, - - uninit() { - Services.obs.removeObserver(this, "quit-application-granted"); - } -}; +exports.HUDService = new HUDService(); diff --git a/devtools/client/webconsole/moz.build b/devtools/client/webconsole/moz.build --- a/devtools/client/webconsole/moz.build +++ b/devtools/client/webconsole/moz.build @@ -13,10 +13,11 @@ 'selectors', 'test', 'utils', ] DevToolsModules( + 'browser-console.js', 'console-commands.js', 'constants.js', 'hudservice.js', 'main.js', 'panel.js', @@ -25,8 +26,9 @@ 'utils.js', 'webconsole-connection-proxy.js', 'webconsole-frame.js', 'webconsole-l10n.js', 'webconsole-output-wrapper.js', + 'webconsole.js', ) with Files('**'): BUG_COMPONENT = ('DevTools', 'Console') diff --git a/devtools/client/webconsole/test/mochitest/browser.ini b/devtools/client/webconsole/test/mochitest/browser.ini --- a/devtools/client/webconsole/test/mochitest/browser.ini +++ b/devtools/client/webconsole/test/mochitest/browser.ini @@ -101,10 +101,11 @@ test-insecure-passwords-about-blank-web-console-warning.html test-insecure-passwords-web-console-warning.html test-inspect-cross-domain-objects-frame.html test-inspect-cross-domain-objects-top.html test-jsterm-dollar.html + test_jsterm_screenshot_command.html test-local-session-storage.html test-location-debugger-link-console-log.js test-location-debugger-link-errors.js test-location-debugger-link.html test-location-styleeditor-link-1.css @@ -225,10 +226,14 @@ [browser_jsterm_no_input_and_tab_key_pressed.js] [browser_jsterm_no_input_change_and_tab_key_pressed.js] [browser_jsterm_null_undefined.js] [browser_jsterm_popup_close_on_tab_switch.js] [browser_jsterm_popup.js] +[browser_jsterm_screenshot_command_clipboard.js] +subsuite = clipboard +[browser_jsterm_screenshot_command_file.js] +skip-if = (os == 'win') # Bug 1464461, disabled on Win due to timeouts [browser_jsterm_selfxss.js] subsuite = clipboard [browser_webconsole_allow_mixedcontent_securityerrors.js] tags = mcb [browser_webconsole_batching.js] diff --git a/devtools/client/webconsole/test/mochitest/browser_jsterm_screenshot_command_clipboard.js b/devtools/client/webconsole/test/mochitest/browser_jsterm_screenshot_command_clipboard.js new file mode 100644 --- /dev/null +++ b/devtools/client/webconsole/test/mochitest/browser_jsterm_screenshot_command_clipboard.js @@ -0,0 +1,209 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +// Test that screenshot command works properly + +"use strict"; + +const TEST_URI = "http://example.com/browser/devtools/client/webconsole/" + + "test/mochitest/test_jsterm_screenshot_command.html"; + +// on some machines, such as macOS, dpr is set to 2. This is expected behavior, however +// to keep tests consistant across OSs we are setting the dpr to 1 +const dpr = "--dpr 1"; + +add_task(async function() { + const hud = await openNewTabAndConsole(TEST_URI); + ok(hud, "web console opened"); + + await testClipboard(hud); + await testFullpageClipboard(hud); + await testSelectorClipboard(hud); + + // overflow + await createScrollbarOverflow(); + await testFullpageClipboardScrollbar(hud); +}); + +async function testClipboard(hud) { + const command = `:screenshot --clipboard ${dpr}`; + const onMessage = waitForMessage(hud, "Screenshot copied to clipboard."); + hud.jsterm.execute(command); + await onMessage; + const contentSize = await getContentSize(); + const imgSize = await getImageSizeFromClipboard(); + + is(imgSize.width, contentSize.innerWidth, + "Clipboard: Image width matches window size"); + is(imgSize.height, contentSize.innerHeight, + "Clipboard: Image height matches window size"); +} + +async function testFullpageClipboard(hud) { + const command = `:screenshot --fullpage --clipboard ${dpr}`; + const onMessage = waitForMessage(hud, "Screenshot copied to clipboard."); + hud.jsterm.execute(command); + await onMessage; + const contentSize = await getContentSize(); + const imgSize = await getImageSizeFromClipboard(); + + is(imgSize.width, + (contentSize.innerWidth + contentSize.scrollMaxX - + contentSize.scrollMinX), + "Fullpage Clipboard: Image width matches page size"); + is(imgSize.height, + (contentSize.innerHeight + contentSize.scrollMaxY - + contentSize.scrollMinY), + "Fullpage Clipboard: Image height matches page size"); +} + +async function testSelectorClipboard(hud) { + const command = `:screenshot --selector "img#testImage" --clipboard ${dpr}`; + const messageReceived = waitForMessage(hud, "Screenshot copied to clipboard."); + hud.jsterm.execute(command); + await messageReceived; + const imgSize1 = await getImageSizeFromClipboard(); + await ContentTask.spawn( + gBrowser.selectedBrowser, + imgSize1, + function(imgSize) { + const img = content.document.querySelector("#testImage"); + is(imgSize.width, img.clientWidth, + "Selector Clipboard: Image width matches element size"); + is(imgSize.height, img.clientHeight, + "Selector Clipboard: Image height matches element size"); + } + ); +} + +async function testFullpageClipboardScrollbar(hud) { + const command = `:screenshot --fullpage --clipboard ${dpr}`; + const onMessage = waitForMessage(hud, "Screenshot copied to clipboard."); + hud.jsterm.execute(command); + await onMessage; + const contentSize = await getContentSize(); + const scrollbarSize = await getScrollbarSize(); + const imgSize = await getImageSizeFromClipboard(); + + is(imgSize.width, + (contentSize.innerWidth + contentSize.scrollMaxX - + contentSize.scrollMinX) - scrollbarSize.width, + "Scroll Fullpage Clipboard: Image width matches page size minus scrollbar size"); + is(imgSize.height, + (contentSize.innerHeight + contentSize.scrollMaxY - + contentSize.scrollMinY) - scrollbarSize.height, + "Scroll Fullpage Clipboard: Image height matches page size minus scrollbar size"); +} + +async function createScrollbarOverflow() { + // Trigger scrollbars by forcing document to overflow + // This only affects results on OSes with scrollbars that reduce document size + // (non-floating scrollbars). With default OS settings, this means Windows + // and Linux are affected, but Mac is not. For Mac to exhibit this behavior, + // change System Preferences -> General -> Show scroll bars to Always. + await ContentTask.spawn(gBrowser.selectedBrowser, {}, function() { + content.document.body.classList.add("overflow"); + }); +} + +async function getScrollbarSize() { + const scrollbarSize = await ContentTask.spawn( + gBrowser.selectedBrowser, + {}, + function() { + const winUtils = content.QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIDOMWindowUtils); + const scrollbarHeight = {}; + const scrollbarWidth = {}; + winUtils.getScrollbarSize(true, scrollbarWidth, scrollbarHeight); + return { + width: scrollbarWidth.value, + height: scrollbarHeight.value, + }; + } + ); + info(`Scrollbar size: ${scrollbarSize.width}x${scrollbarSize.height}`); + return scrollbarSize; +} + +async function getContentSize() { + const contentSize = await ContentTask.spawn( + gBrowser.selectedBrowser, + {}, + function() { + return { + scrollMaxY: content.scrollMaxY, + scrollMaxX: content.scrollMaxX, + scrollMinY: content.scrollMinY, + scrollMinX: content.scrollMinX, + innerWidth: content.innerWidth, + innerHeight: content.innerHeight + }; + } + ); + + info(`content size: ${contentSize.innerWidth}x${contentSize.innerHeight}`); + return contentSize; +} + +async function getImageSizeFromClipboard() { + const clipid = Ci.nsIClipboard; + const clip = Cc["@mozilla.org/widget/clipboard;1"].getService(clipid); + const trans = Cc["@mozilla.org/widget/transferable;1"] + .createInstance(Ci.nsITransferable); + const flavor = "image/png"; + trans.init(null); + trans.addDataFlavor(flavor); + + clip.getData(trans, clipid.kGlobalClipboard); + const data = {}; + const dataLength = {}; + trans.getTransferData(flavor, data, dataLength); + + ok(data.value, "screenshot exists"); + ok(dataLength.value > 0, "screenshot has length"); + + let image = data.value; + let dataURI = `data:${flavor};base64,`; + + // Due to the differences in how images could be stored in the clipboard the + // checks below are needed. The clipboard could already provide the image as + // byte streams, but also as pointer, or as image container. If it's not + // possible obtain a byte stream, the function returns `null`. + if (image instanceof Ci.nsISupportsInterfacePointer) { + image = image.data; + } + + if (image instanceof Ci.imgIContainer) { + image = Cc["@mozilla.org/image/tools;1"] + .getService(Ci.imgITools) + .encodeImage(image, flavor); + } + + if (image instanceof Ci.nsIInputStream) { + const binaryStream = Cc["@mozilla.org/binaryinputstream;1"] + .createInstance(Ci.nsIBinaryInputStream); + binaryStream.setInputStream(image); + const rawData = binaryStream.readBytes(binaryStream.available()); + const charCodes = Array.from(rawData, c => c.charCodeAt(0) & 0xff); + let encodedData = String.fromCharCode(...charCodes); + encodedData = btoa(encodedData); + dataURI = dataURI + encodedData; + } else { + throw new Error("Unable to read image data"); + } + + const img = document.createElementNS("http://www.w3.org/1999/xhtml", "img"); + + const loaded = once(img, "load"); + + img.src = dataURI; + document.documentElement.appendChild(img); + await loaded; + img.remove(); + + return { + width: img.width, + height: img.height, + }; +} diff --git a/devtools/client/webconsole/test/mochitest/browser_jsterm_screenshot_command_file.js b/devtools/client/webconsole/test/mochitest/browser_jsterm_screenshot_command_file.js new file mode 100644 --- /dev/null +++ b/devtools/client/webconsole/test/mochitest/browser_jsterm_screenshot_command_file.js @@ -0,0 +1,39 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +// Test that screenshot command works properly + +"use strict"; + +const TEST_URI = "http://example.com/browser/devtools/client/webconsole/" + + "test/mochitest/test_jsterm_screenshot_command.html"; + +const FileUtils = (ChromeUtils.import("resource://gre/modules/FileUtils.jsm", {})).FileUtils; +// on some machines, such as macOS, dpr is set to 2. This is expected behavior, however +// to keep tests consistant across OSs we are setting the dpr to 1 +const dpr = "--dpr 1"; + +add_task(async function() { + await addTab(TEST_URI); + + const hud = await openConsole(); + ok(hud, "web console opened"); + + await testFile(hud); +}); + +async function testFile(hud) { + // Test capture to file + const file = FileUtils.getFile("TmpD", [ "TestScreenshotFile.png" ]); + const command = `:screenshot ${file.path} ${dpr}`; + const onMessage = waitForMessage(hud, `Saved to ${file.path}`); + hud.jsterm.execute(command); + await onMessage; + + ok(file.exists(), "Screenshot file exists"); + + if (file.exists()) { + file.remove(false); + } +} + diff --git a/devtools/client/webconsole/test/mochitest/test_jsterm_screenshot_command.html b/devtools/client/webconsole/test/mochitest/test_jsterm_screenshot_command.html new file mode 100644 --- /dev/null +++ b/devtools/client/webconsole/test/mochitest/test_jsterm_screenshot_command.html @@ -0,0 +1,18 @@ + + + + + + + + diff --git a/devtools/client/webconsole/webconsole-output-wrapper.js b/devtools/client/webconsole/webconsole-output-wrapper.js --- a/devtools/client/webconsole/webconsole-output-wrapper.js +++ b/devtools/client/webconsole/webconsole-output-wrapper.js @@ -259,16 +259,16 @@ }); } else { promise = Promise.resolve(); } - this.batchedMessagesAdd(packet); + this.batchedMessageAdd(packet); return promise; }, dispatchMessagesAdd: function(messages) { - store.dispatch(actions.messagesAdd(messages)); + this.batchedMessagesAdd(messages); }, dispatchMessagesClear: function() { // We might still have pending message additions and updates when the clear action is // triggered, so we need to flush them to make sure we don't have unexpected behavior @@ -371,15 +371,20 @@ batchedRequestUpdates: function(message) { this.queuedRequestUpdates.push(message); this.setTimeoutIfNeeded(); }, - batchedMessagesAdd: function(message) { + batchedMessageAdd: function(message) { this.queuedMessageAdds.push(message); this.setTimeoutIfNeeded(); }, + batchedMessagesAdd: function(messages) { + this.queuedMessageAdds = this.queuedMessageAdds.concat(messages); + this.setTimeoutIfNeeded(); + }, + dispatchClearHistory: function() { store.dispatch(actions.clearHistory()); }, /** diff --git a/devtools/client/webconsole/webconsole.js b/devtools/client/webconsole/webconsole.js new file mode 100644 --- /dev/null +++ b/devtools/client/webconsole/webconsole.js @@ -0,0 +1,318 @@ +/* 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"; + +var Services = require("Services"); +loader.lazyRequireGetter(this, "Utils", "devtools/client/webconsole/utils", true); +loader.lazyRequireGetter(this, "WebConsoleFrame", "devtools/client/webconsole/webconsole-frame", true); +loader.lazyRequireGetter(this, "gDevTools", "devtools/client/framework/devtools", true); +loader.lazyRequireGetter(this, "viewSource", "devtools/client/shared/view-source"); +loader.lazyRequireGetter(this, "openDocLink", "devtools/client/shared/link", true); + +var gHudId = 0; + +/** + * A WebConsole instance is an interactive console initialized *per target* + * that displays console log data as well as provides an interactive terminal to + * manipulate the target's document content. + * + * This object only wraps the iframe that holds the Web Console UI. This is + * meant to be an integration point between the Firefox UI and the Web Console + * UI and features. + * + * @constructor + * @param object target + * The target that the web console will connect to. + * @param nsIDOMWindow iframeWindow + * The window where the web console UI is already loaded. + * @param nsIDOMWindow chromeWindow + * The window of the web console owner. + * @param object hudService + * The parent HUD Service + */ +function WebConsole(target, iframeWindow, chromeWindow, hudService) { + this.iframeWindow = iframeWindow; + this.chromeWindow = chromeWindow; + this.hudId = "hud_" + ++gHudId; + this.target = target; + this.browserWindow = this.chromeWindow.top; + this.hudService = hudService; + + const element = this.browserWindow.document.documentElement; + if (element.getAttribute("windowtype") != gDevTools.chromeWindowType) { + this.browserWindow = this.hudService.currentContext(); + } + this.ui = new WebConsoleFrame(this); +} + +WebConsole.prototype = { + iframeWindow: null, + chromeWindow: null, + browserWindow: null, + hudId: null, + target: null, + ui: null, + _browserConsole: false, + _destroyer: null, + + /** + * Getter for a function to to listen for every request that completes. Used + * by unit tests. The callback takes one argument: the HTTP activity object as + * received from the remote Web Console. + * + * @type function + */ + get lastFinishedRequestCallback() { + return this.hudService.lastFinishedRequest.callback; + }, + + /** + * Getter for the window that can provide various utilities that the web + * console makes use of, like opening links, managing popups, etc. In + * most cases, this will be |this.browserWindow|, but in some uses (such as + * the Browser Toolbox), there is no browser window, so an alternative window + * hosts the utilities there. + * @type nsIDOMWindow + */ + get chromeUtilsWindow() { + if (this.browserWindow) { + return this.browserWindow; + } + return this.chromeWindow.top; + }, + + /** + * Getter for the xul:popupset that holds any popups we open. + * @type Element + */ + get mainPopupSet() { + return this.chromeUtilsWindow.document.getElementById("mainPopupSet"); + }, + + /** + * Getter for the output element that holds messages we display. + * @type Element + */ + get outputNode() { + return this.ui ? this.ui.outputNode : null; + }, + + get gViewSourceUtils() { + return this.chromeUtilsWindow.gViewSourceUtils; + }, + + /** + * Initialize the Web Console instance. + * + * @return object + * A promise for the initialization. + */ + init() { + return this.ui.init().then(() => this); + }, + + /** + * The JSTerm object that manages the console's input. + * @see webconsole.js::JSTerm + * @type object + */ + get jsterm() { + return this.ui ? this.ui.jsterm : null; + }, + + /** + * Alias for the WebConsoleFrame.setFilterState() method. + * @see webconsole.js::WebConsoleFrame.setFilterState() + */ + setFilterState() { + this.ui && this.ui.setFilterState.apply(this.ui, arguments); + }, + + /** + * Open a link in a new tab. + * + * @param string link + * The URL you want to open in a new tab. + */ + openLink(link, e) { + openDocLink(link); + }, + + /** + * Open a link in Firefox's view source. + * + * @param string sourceURL + * The URL of the file. + * @param integer sourceLine + * The line number which should be highlighted. + */ + viewSource(sourceURL, sourceLine) { + this.gViewSourceUtils.viewSource({ URL: sourceURL, lineNumber: sourceLine || 0 }); + }, + + /** + * Tries to open a Stylesheet file related to the web page for the web console + * instance in the Style Editor. If the file is not found, it is opened in + * source view instead. + * + * Manually handle the case where toolbox does not exist (Browser Console). + * + * @param string sourceURL + * The URL of the file. + * @param integer sourceLine + * The line number which you want to place the caret. + */ + viewSourceInStyleEditor(sourceURL, sourceLine) { + const toolbox = gDevTools.getToolbox(this.target); + if (!toolbox) { + this.viewSource(sourceURL, sourceLine); + return; + } + toolbox.viewSourceInStyleEditor(sourceURL, sourceLine); + }, + + /** + * Tries to open a JavaScript file related to the web page for the web console + * instance in the Script Debugger. If the file is not found, it is opened in + * source view instead. + * + * Manually handle the case where toolbox does not exist (Browser Console). + * + * @param string sourceURL + * The URL of the file. + * @param integer sourceLine + * The line number which you want to place the caret. + */ + viewSourceInDebugger(sourceURL, sourceLine) { + const toolbox = gDevTools.getToolbox(this.target); + if (!toolbox) { + this.viewSource(sourceURL, sourceLine); + return; + } + toolbox.viewSourceInDebugger(sourceURL, sourceLine).then(() => { + this.ui.emit("source-in-debugger-opened"); + }); + }, + + /** + * Tries to open a JavaScript file related to the web page for the web console + * instance in the corresponding Scratchpad. + * + * @param string sourceURL + * The URL of the file which corresponds to a Scratchpad id. + */ + viewSourceInScratchpad(sourceURL, sourceLine) { + viewSource.viewSourceInScratchpad(sourceURL, sourceLine); + }, + + /** + * Retrieve information about the JavaScript debugger's stackframes list. This + * is used to allow the Web Console to evaluate code in the selected + * stackframe. + * + * @return object|null + * An object which holds: + * - frames: the active ThreadClient.cachedFrames array. + * - selected: depth/index of the selected stackframe in the debugger + * UI. + * If the debugger is not open or if it's not paused, then |null| is + * returned. + */ + getDebuggerFrames() { + const toolbox = gDevTools.getToolbox(this.target); + if (!toolbox) { + return null; + } + const panel = toolbox.getPanel("jsdebugger"); + + if (!panel) { + return null; + } + + return panel.getFrames(); + }, + + async getMappedExpression(expression) { + const toolbox = gDevTools.getToolbox(this.target); + if (!toolbox) { + return expression; + } + const panel = toolbox.getPanel("jsdebugger"); + + if (!panel) { + return expression; + } + + return panel.getMappedExpression(expression); + }, + + /** + * Retrieves the current selection from the Inspector, if such a selection + * exists. This is used to pass the ID of the selected actor to the Web + * Console server for the $0 helper. + * + * @return object|null + * A Selection referring to the currently selected node in the + * Inspector. + * If the inspector was never opened, or no node was ever selected, + * then |null| is returned. + */ + getInspectorSelection() { + const toolbox = gDevTools.getToolbox(this.target); + if (!toolbox) { + return null; + } + const panel = toolbox.getPanel("inspector"); + if (!panel || !panel.selection) { + return null; + } + return panel.selection; + }, + + /** + * Destroy the object. Call this method to avoid memory leaks when the Web + * Console is closed. + * + * @return object + * A promise object that is resolved once the Web Console is closed. + */ + async destroy() { + if (this._destroyer) { + return this._destroyer; + } + + this._destroyer = (async () => { + this.hudService.consoles.delete(this.hudId); + + // The document may already be removed + if (this.chromeUtilsWindow && this.mainPopupSet) { + const popupset = this.mainPopupSet; + const panels = popupset.querySelectorAll("panel[hudId=" + this.hudId + "]"); + for (const panel of panels) { + panel.hidePopup(); + } + } + + if (this.ui) { + await this.ui.destroy(); + } + + if (!this._browserConsole) { + try { + await this.target.activeTab.focus(); + } catch (ex) { + // Tab focus can fail if the tab or target is closed. + } + } + + const id = Utils.supportsString(this.hudId); + Services.obs.notifyObservers(id, "web-console-destroyed"); + })(); + + return this._destroyer; + }, +}; + +module.exports = WebConsole; diff --git a/devtools/server/actors/stylesheets.js b/devtools/server/actors/stylesheets.js --- a/devtools/server/actors/stylesheets.js +++ b/devtools/server/actors/stylesheets.js @@ -677,12 +677,12 @@ } this.parentActor.off("stylesheet-added", this._onNewStyleSheetActor); this.parentActor.off("window-ready", this._onWindowReady); - this.parentActor.chromeEventHandler.removeEventListener("StyleSheetAdded", - this._onSheetAdded, true); + this.parentActor.chromeEventHandler + .removeEventListener("StyleSheetApplicableStateChanged", this._onSheetAdded, true); protocol.Actor.prototype.destroy.call(this); }, /** diff --git a/devtools/server/actors/targets/browsing-context.js b/devtools/server/actors/targets/browsing-context.js --- a/devtools/server/actors/targets/browsing-context.js +++ b/devtools/server/actors/targets/browsing-context.js @@ -1449,10 +1449,11 @@ * @return StyleSheetActor actor * The actor for this style sheet. * */ createStyleSheetActor(styleSheet) { + assert(!this.exited, "Target must not be exited to create a sheet actor."); if (this._styleSheetActors.has(styleSheet)) { return this._styleSheetActors.get(styleSheet); } const actor = new StyleSheetActor(styleSheet, this); this._styleSheetActors.set(styleSheet, actor); diff --git a/devtools/server/actors/webconsole.js b/devtools/server/actors/webconsole.js --- a/devtools/server/actors/webconsole.js +++ b/devtools/server/actors/webconsole.js @@ -25,10 +25,12 @@ loader.lazyRequireGetter(this, "StackTraceCollector", "devtools/shared/webconsole/network-monitor", true); loader.lazyRequireGetter(this, "JSPropertyProvider", "devtools/shared/webconsole/js-property-provider", true); loader.lazyRequireGetter(this, "Parser", "resource://devtools/shared/Parser.jsm", true); loader.lazyRequireGetter(this, "NetUtil", "resource://gre/modules/NetUtil.jsm", true); loader.lazyRequireGetter(this, "addWebConsoleCommands", "devtools/server/actors/webconsole/utils", true); +loader.lazyRequireGetter(this, "formatCommand", "devtools/server/actors/webconsole/commands", true); +loader.lazyRequireGetter(this, "isCommand", "devtools/server/actors/webconsole/commands", true); loader.lazyRequireGetter(this, "CONSOLE_WORKER_IDS", "devtools/server/actors/webconsole/utils", true); loader.lazyRequireGetter(this, "WebConsoleUtils", "devtools/server/actors/webconsole/utils", true); loader.lazyRequireGetter(this, "EnvironmentActor", "devtools/server/actors/environment", true); loader.lazyRequireGetter(this, "EventEmitter", "devtools/shared/event-emitter"); @@ -873,10 +875,11 @@ /** * Handler for the "evaluateJSAsync" request. This method evaluates the given * JavaScript string and sends back a packet with a unique ID. * The result will be returned later as an unsolicited `evaluationResult`, * that can be associated back to this request via the `resultID` field. + * Cannot be async, see Comment two on Bug #1452920 * * @param object request * The JSON request object received from the Web Console client. * @return object * The response packet to send to with the unique id in the @@ -895,10 +898,43 @@ // Then, execute the script that may pause. const response = this.evaluateJS(request); response.resultID = resultID; + this._waitForHelperResultAndSend(response).catch(e => + DevToolsUtils.reportException( + "evaluateJSAsync", + Error(`Encountered error while waiting for Helper Result: ${e}`) + ) + ); + }, + + /** + * In order to have asynchronous commands such as screenshot, we have to be + * able to handle promises in the helper result. This method handles waiting + * for the promise, and then dispatching the result + * + * + * @private + * @param object response + * The response packet to send to with the unique id in the + * `resultID` field, and potentially a promise in the helperResult + * field. + * + * @return object + * The response packet to send to with the unique id in the + * `resultID` field, with a sanitized helperResult field. + */ + _waitForHelperResultAndSend: async function(response) { + // Wait for asynchronous command completion before sending back the response + if ( + response.helperResult && + typeof response.helperResult.then == "function" + ) { + response.helperResult = await response.helperResult; + } + // Finally, send an unsolicited evaluationResult packet with // the normal return value this.conn.sendActorEvent(this.actorID, "evaluationResult", response); }, @@ -1086,12 +1122,17 @@ }; addWebConsoleCommands(helpers); this._webConsoleCommandsCache = Object.getOwnPropertyNames(helpers.sandbox); } + matches = matches.concat(this._webConsoleCommandsCache - .filter(n => n.startsWith(result.matchProp))); + .filter(n => + // filter out `screenshot` command as it is inaccessible without + // the `:` prefix + n !== "screenshot" && n.startsWith(result.matchProp) + )); } return { from: this.actorID, matches: matches.sort(), @@ -1294,10 +1335,20 @@ // The help function needs to be easy to guess, so we make the () optional. if (trimmedString == "help" || trimmedString == "?") { string = "help()"; } + const isCmd = isCommand(string); + // we support Unix like syntax for commands if it is preceeded by `:` + if (isCmd) { + try { + string = formatCommand(string); + } catch (e) { + string = `throw "${e}"`; + } + } + // Add easter egg for console.mihai(). if (trimmedString == "console.mihai()" || trimmedString == "console.mihai();") { string = "\"http://incompleteness.me/blog/2015/02/09/console-dot-mihai/\""; } @@ -1367,31 +1418,41 @@ } // Check if the Debugger.Frame or Debugger.Object for the global include // $ or $$. We will not overwrite these functions with the Web Console // commands. - let found$ = false, found$$ = false; - if (frame) { - const env = frame.environment; - if (env) { - found$ = !!env.find("$"); - found$$ = !!env.find("$$"); + let found$ = false, found$$ = false, disableScreenshot = false; + // do not override command functions if we are using the command key `:` + // before the command string + if (!isCmd) { + // if we do not have the command key as a prefix, screenshot is disabled by default + disableScreenshot = true; + if (frame) { + const env = frame.environment; + if (env) { + found$ = !!env.find("$"); + found$$ = !!env.find("$$"); + } + } else { + found$ = !!dbgWindow.getOwnPropertyDescriptor("$"); + found$$ = !!dbgWindow.getOwnPropertyDescriptor("$$"); } - } else { - found$ = !!dbgWindow.getOwnPropertyDescriptor("$"); - found$$ = !!dbgWindow.getOwnPropertyDescriptor("$$"); } - let $ = null, $$ = null; + let $ = null, $$ = null, screenshot = null; if (found$) { $ = bindings.$; delete bindings.$; } if (found$$) { $$ = bindings.$$; delete bindings.$$; } + if (disableScreenshot) { + screenshot = bindings.screenshot; + delete bindings.screenshot; + } // Ready to evaluate the string. helpers.evalInput = string; let evalOptions; @@ -1490,10 +1551,13 @@ bindings.$ = $; } if ($$) { bindings.$$ = $$; } + if (screenshot) { + bindings.screenshot = screenshot; + } if (bindings._self) { delete bindings._self; } diff --git a/devtools/server/actors/webconsole/commands.js b/devtools/server/actors/webconsole/commands.js new file mode 100644 --- /dev/null +++ b/devtools/server/actors/webconsole/commands.js @@ -0,0 +1,227 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* 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 validCommands = ["help", "screenshot"]; + +const COMMAND = "command"; +const KEY = "key"; +const ARG = "arg"; + +const COMMAND_PREFIX = /^:/; +const KEY_PREFIX = /^--/; + +// default value for flags +const DEFAULT_VALUE = true; +const COMMAND_DEFAULT_FLAG = { + screenshot: "filename" +}; + +/** + * When given a string that begins with `:` and a unix style string, + * format a JS like object. + * This is intended to be used by the WebConsole actor only. + * + * @param String string + * A string to format that begins with `:`. + * + * @returns String formatted as `command({ ..args })` + */ +function formatCommand(string) { + if (!isCommand(string)) { + throw Error("formatCommand was called without `:`"); + } + const tokens = string.trim().split(/\s+/).map(createToken); + const { command, args } = parseCommand(tokens); + const argsString = formatArgs(args); + return `${command}(${argsString})`; +} + +/** + * collapses the array of arguments from the parsed command into + * a single string + * + * @param Object tree + * A tree object produced by parseCommand + * + * @returns String formatted as ` { key: value, ... } ` or an empty string + */ +function formatArgs(args) { + return Object.keys(args).length ? + JSON.stringify(args) : + ""; +} + +/** + * creates a token object depending on a string which as a prefix, + * either `:` for a command or `--` for a key, or nothing for an argument + * + * @param String string + * A string to use as the basis for the token + * + * @returns Object Token Object, with the following shape + * { type: String, value: String } + */ +function createToken(string) { + if (isCommand(string)) { + const value = string.replace(COMMAND_PREFIX, ""); + if (!value || !validCommands.includes(value)) { + throw Error(`'${value}' is not a valid command`); + } + return { type: COMMAND, value }; + } + if (isKey(string)) { + const value = string.replace(KEY_PREFIX, ""); + if (!value) { + throw Error("invalid flag"); + } + return { type: KEY, value }; + } + return { type: ARG, value: string }; +} + +/** + * returns a command Tree object for a set of tokens + * + * + * @param Array Tokens tokens + * An array of Token objects + * + * @returns Object Tree Object, with the following shape + * { command: String, args: Array of Strings } + */ +function parseCommand(tokens) { + let command = null; + const args = {}; + + for (let i = 0; i < tokens.length; i++) { + const token = tokens[i]; + if (token.type === COMMAND) { + if (command) { + // we are throwing here because two commands have been passed and it is unclear + // what the user's intention was + throw Error("Invalid command"); + } + command = token.value; + } + + if (token.type === KEY) { + const nextTokenIndex = i + 1; + const nextToken = tokens[nextTokenIndex]; + let values = args[token.value] || DEFAULT_VALUE; + if (nextToken && nextToken.type === ARG) { + const { value, offset } = collectString(nextToken, tokens, nextTokenIndex); + // in order for JSON.stringify to correctly output values, they must be correctly + // typed + // As per the GCLI documentation, we can only have one value associated with a + // flag but multiple flags with the same name can exist and should be combined + // into and array. Here we are associating only the value on the right hand + // side if it is of type `arg` as a single value; the second case initializes + // an array, and the final case pushes a value to an existing array + const typedValue = getTypedValue(value); + if (values === DEFAULT_VALUE) { + values = typedValue; + } else if (!Array.isArray(values)) { + values = [values, typedValue]; + } else { + values.push(typedValue); + } + // skip the next token since we have already consumed it + i = nextTokenIndex + offset; + } + args[token.value] = values; + } + + // Since this has only been implemented for screenshot, we can only have one default + // value. Eventually we may have more default values. For now, ignore multiple + // unflagged args + const defaultFlag = COMMAND_DEFAULT_FLAG[command]; + if (token.type === ARG && !args[defaultFlag]) { + const { value, offset } = collectString(token, tokens, i); + args[defaultFlag] = getTypedValue(value); + i = i + offset; + } + } + return { command, args }; +} + +const stringChars = ["\"", "'", "`"]; +function isStringChar(testChar) { + return stringChars.includes(testChar); +} + +function checkLastChar(string, testChar) { + const lastChar = string[string.length - 1]; + return lastChar === testChar; +} + +function hasUnexpectedChar(value, char, rightOffset, leftOffset) { + const lastPos = value.length - 1; + value.slice(rightOffset, lastPos - leftOffset).includes(char); +} + +function collectString(token, tokens, index) { + const firstChar = token.value[0]; + const isString = isStringChar(firstChar); + let value = token.value; + + // the test value is not a string, or it is a string but a complete one + // i.e. `"test"`, as opposed to `"foo`. In either case, this we can return early + if (!isString || checkLastChar(value, firstChar)) { + return { value, offset: 0 }; + } + + if (hasUnexpectedChar(value, firstChar, 1, 0)) { + throw Error(`String contains unexpected ${firstChar} character`); + } + + let offset = null; + for (let i = index + 1; i <= tokens.length; i++) { + if (i === tokens.length) { + throw Error("String does not terminate"); + } + + const nextToken = tokens[i]; + if (nextToken.type !== ARG) { + throw Error(`String does not terminate before flag ${nextToken.value}`); + } + + if (hasUnexpectedChar(nextToken.value, firstChar, 0, 1)) { + throw Error(`String contains unexpected ${firstChar} character`); + } + + value = `${value} ${nextToken.value}`; + if (checkLastChar(nextToken.value, firstChar)) { + offset = i - index; + break; + } + } + return { value, offset }; +} + +function isCommand(string) { + return COMMAND_PREFIX.test(string); +} + +function isKey(string) { + return KEY_PREFIX.test(string); +} + +function getTypedValue(value) { + if (!isNaN(value)) { + return Number(value); + } + if (value === "true" || value === "false") { + return Boolean(value); + } + if (isStringChar(value[0])) { + return value.slice(1, value.length - 1); + } + return value; +} + +exports.formatCommand = formatCommand; +exports.isCommand = isCommand; diff --git a/devtools/server/actors/webconsole/moz.build b/devtools/server/actors/webconsole/moz.build --- a/devtools/server/actors/webconsole/moz.build +++ b/devtools/server/actors/webconsole/moz.build @@ -3,10 +3,12 @@ # This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at http://mozilla.org/MPL/2.0/. DevToolsModules( + 'commands.js', 'content-process-forward.js', 'listeners.js', + 'screenshot.js', 'utils.js', 'worker-listeners.js', ) diff --git a/devtools/server/actors/webconsole/screenshot.js b/devtools/server/actors/webconsole/screenshot.js new file mode 100644 --- /dev/null +++ b/devtools/server/actors/webconsole/screenshot.js @@ -0,0 +1,141 @@ +/* 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 { Ci, Cu } = require("chrome"); +const { getRect } = require("devtools/shared/layout/utils"); +const { LocalizationHelper } = require("devtools/shared/l10n"); + +const CONTAINER_FLASHING_DURATION = 500; +const STRINGS_URI = "devtools/shared/locales/screenshot.properties"; +const L10N = new LocalizationHelper(STRINGS_URI); + +exports.screenshot = function takeAsyncScreenshot(owner, args = {}) { + if (args.help) { + // Early return as help will be handled on the client side. + return null; + } + return captureScreenshot(args, owner.window.document); +}; + +/** + * This function is called to simulate camera effects + */ +function simulateCameraFlash(document) { + const window = document.defaultView; + const frames = Cu.cloneInto({ opacity: [ 0, 1 ] }, window); + document.documentElement.animate(frames, CONTAINER_FLASHING_DURATION); +} + +/** + * This function simply handles the --delay argument before calling + * createScreenshotData + */ +function captureScreenshot(args, document) { + if (args.delay > 0) { + return new Promise((resolve, reject) => { + document.defaultView.setTimeout(() => { + createScreenshotData(document, args).then(resolve, reject); + }, args.delay * 1000); + }); + } + return createScreenshotData(document, args); +} + +/** + * This does the dirty work of creating a base64 string out of an + * area of the browser window + */ +function createScreenshotData(document, args) { + const window = document.defaultView; + let left = 0; + let top = 0; + let width; + let height; + const currentX = window.scrollX; + const currentY = window.scrollY; + + let filename = getFilename(args.filename); + + if (args.fullpage) { + // Bug 961832: GCLI screenshot shows fixed position element in wrong + // position if we don't scroll to top + window.scrollTo(0, 0); + width = window.innerWidth + window.scrollMaxX - window.scrollMinX; + height = window.innerHeight + window.scrollMaxY - window.scrollMinY; + filename = filename.replace(".png", "-fullpage.png"); + } else if (args.selector) { + const node = window.document.querySelector(args.selector); + ({ top, left, width, height } = getRect(window, node, window)); + } else { + left = window.scrollX; + top = window.scrollY; + width = window.innerWidth; + height = window.innerHeight; + } + + // Only adjust for scrollbars when considering the full window + if (!args.selector) { + const winUtils = window.QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIDOMWindowUtils); + const scrollbarHeight = {}; + const scrollbarWidth = {}; + winUtils.getScrollbarSize(false, scrollbarWidth, scrollbarHeight); + width -= scrollbarWidth.value; + height -= scrollbarHeight.value; + } + + const canvas = document.createElementNS("http://www.w3.org/1999/xhtml", "canvas"); + const ctx = canvas.getContext("2d"); + const ratio = args.dpr ? args.dpr : window.devicePixelRatio; + canvas.width = width * ratio; + canvas.height = height * ratio; + ctx.scale(ratio, ratio); + ctx.drawWindow(window, left, top, width, height, "#fff"); + const data = canvas.toDataURL("image/png", ""); + + // See comment above on bug 961832 + if (args.fullpage) { + window.scrollTo(currentX, currentY); + } + + simulateCameraFlash(document); + + return Promise.resolve({ + destinations: [], + data: data, + height: height, + width: width, + filename: filename, + }); +} + +/** + * We may have a filename specified in args, or we might have to generate + * one. + */ +function getFilename(defaultName) { + // Create a name for the file if not present + if (defaultName) { + return defaultName; + } + + const date = new Date(); + let dateString = date.getFullYear() + "-" + (date.getMonth() + 1) + + "-" + date.getDate(); + dateString = dateString.split("-").map(function(part) { + if (part.length == 1) { + part = "0" + part; + } + return part; + }).join("-"); + + const timeString = date.toTimeString().replace(/:/g, ".").split(" ")[0]; + return L10N.getFormatStr( + "screenshotGeneratedFilename", + dateString, + timeString + ) + ".png"; +} diff --git a/devtools/server/actors/webconsole/utils.js b/devtools/server/actors/webconsole/utils.js --- a/devtools/server/actors/webconsole/utils.js +++ b/devtools/server/actors/webconsole/utils.js @@ -1,15 +1,16 @@ /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ -/* vim: set ft= javascript ts=2 et sw=2 tw=80: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ "use strict"; const {Ci, Cu} = require("chrome"); +loader.lazyRequireGetter(this, "screenshot", "devtools/server/actors/webconsole/screenshot", true); + // Note that this is only used in WebConsoleCommands, see $0 and pprint(). if (!isWorker) { loader.lazyImporter(this, "VariablesView", "resource://devtools/client/shared/widgets/VariablesView.jsm"); } @@ -590,10 +591,32 @@ type: "copyValueToClipboard", value: payload, }; }); +/** + * Take a screenshot of a page. + * + * @param object args + * The arguments to be passed to the screenshot + * @return void + */ +WebConsoleCommands._registerOriginal("screenshot", function(owner, args) { + owner.helperResult = (async () => { + // creates data for saving the screenshot + const value = await screenshot(owner, args); + return { + type: "screenshotOutput", + value, + // pass args through to the client, so that the client can take care of copying + // and saving the screenshot data on the client machine instead of on the + // remote machine + args + }; + })(); +}); + /** * (Internal only) Add the bindings to |owner.sandbox|. * This is intended to be used by the WebConsole actor only. * * @param object owner diff --git a/devtools/server/tests/unit/test_format_command.js b/devtools/server/tests/unit/test_format_command.js new file mode 100644 --- /dev/null +++ b/devtools/server/tests/unit/test_format_command.js @@ -0,0 +1,96 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ +/* eslint-disable no-shadow, max-nested-callbacks */ + +"use strict"; + +const { formatCommand } = require("devtools/server/actors/webconsole/commands.js"); + +const testcases = [ + { input: ":help", expectedOutput: "help()" }, + { + input: ":screenshot --fullscreen", + expectedOutput: "screenshot({\"fullscreen\":true})" + }, + { + input: ":screenshot --fullscreen true", + expectedOutput: "screenshot({\"fullscreen\":true})" + }, + { input: ":screenshot ", expectedOutput: "screenshot()" }, + { + input: ":screenshot --dpr 0.5 --fullpage --chrome", + expectedOutput: "screenshot({\"dpr\":0.5,\"fullpage\":true,\"chrome\":true})" + }, + { + input: ":screenshot 'filename'", + expectedOutput: "screenshot({\"filename\":\"filename\"})" + }, + { + input: ":screenshot filename", + expectedOutput: "screenshot({\"filename\":\"filename\"})" + }, + { + input: ":screenshot --name 'filename' --name `filename` --name \"filename\"", + expectedOutput: "screenshot({\"name\":[\"filename\",\"filename\",\"filename\"]})" + }, + { + input: ":screenshot 'filename1' 'filename2' 'filename3'", + expectedOutput: "screenshot({\"filename\":\"filename1\"})" + }, + { + input: ":screenshot --chrome --chrome", + expectedOutput: "screenshot({\"chrome\":true})" + }, + { + input: ":screenshot \"file name with spaces\"", + expectedOutput: "screenshot({\"filename\":\"file name with spaces\"})" + }, + { + input: ":screenshot 'filename1' --name 'filename2'", + expectedOutput: "screenshot({\"filename\":\"filename1\",\"name\":\"filename2\"})" + }, + { + input: ":screenshot --name 'filename1' 'filename2'", + expectedOutput: "screenshot({\"name\":\"filename1\",\"filename\":\"filename2\"})" + }, + { + input: ":screenshot \"fo\\\"o bar\"", + expectedOutput: "screenshot({\"filename\":\"fo\\\\\\\"o bar\"})" + }, + { + input: ":screenshot \"foo b\\\"ar\"", + expectedOutput: "screenshot({\"filename\":\"foo b\\\\\\\"ar\"})" + } +]; + +const edgecases = [ + { input: ":", expectedError: "'' is not a valid command" }, + { input: ":invalid", expectedError: "'invalid' is not a valid command" }, + { input: ":screenshot :help", expectedError: "invalid command" }, + { input: ":screenshot --", expectedError: "invalid flag" }, + { + input: ":screenshot \"fo\"o bar", + expectedError: "String contains unexpected `\"` character" + }, + { + input: ":screenshot \"foo b\"ar", + expectedError: "String contains unexpected `\"` character" + }, + { input: ": screenshot", expectedError: "'' is not a valid command" }, + { input: ":screenshot \"file name", expectedError: "String does not terminate" }, + { + input: ":screenshot \"file name --clipboard", + expectedError: "String does not terminate before flag \"clipboard\"" + }, + { input: "::screenshot", expectedError: "':screenshot' is not a valid command" } +]; + +function run_test() { + testcases.forEach(testcase => { + Assert.equal(formatCommand(testcase.input), testcase.expectedOutput); + }); + + edgecases.forEach(testcase => { + Assert.throws(() => formatCommand(testcase.input), testcase.expectedError); + }); +} diff --git a/devtools/server/tests/unit/xpcshell.ini b/devtools/server/tests/unit/xpcshell.ini --- a/devtools/server/tests/unit/xpcshell.ini +++ b/devtools/server/tests/unit/xpcshell.ini @@ -84,10 +84,11 @@ [test_eval-01.js] [test_eval-02.js] [test_eval-03.js] [test_eval-04.js] [test_eval-05.js] +[test_format_command.js] [test_promises_actor_attach.js] [test_promises_actor_exist.js] [test_promises_actor_list_promises.js] skip-if = coverage # bug 1336670 [test_promises_actor_onnewpromise.js] diff --git a/devtools/shared/gcli/commands/screenshot.js b/devtools/shared/gcli/commands/screenshot.js --- a/devtools/shared/gcli/commands/screenshot.js +++ b/devtools/shared/gcli/commands/screenshot.js @@ -552,11 +552,10 @@ // objects as opposed to just writing with OS.File allows us to tie into the // download manager to record a download entry and to get visual feedback from // the downloads toolbar button when the save is done. const nsIWBP = Ci.nsIWebBrowserPersist; const flags = nsIWBP.PERSIST_FLAGS_REPLACE_EXISTING_FILES | - nsIWBP.PERSIST_FLAGS_FORCE_ALLOW_COOKIES | nsIWBP.PERSIST_FLAGS_BYPASS_CACHE | nsIWBP.PERSIST_FLAGS_AUTODETECT_APPLY_CONVERSION; const isPrivate = PrivateBrowsingUtils.isContentWindowPrivate(document.defaultView); const persist = Cc["@mozilla.org/embedding/browser/nsWebBrowserPersist;1"] @@ -571,11 +570,13 @@ null, persist, isPrivate); const listener = new DownloadListener(window, tr); persist.progressListener = listener; + const principal = Services.scriptSecurityManager.getSystemPrincipal(); persist.savePrivacyAwareURI(sourceURI, + principal, 0, document.documentURIObject, Ci.nsIHttpChannel.REFERRER_POLICY_UNSET, null, null, diff --git a/devtools/shared/locales/en-US/screenshot.properties b/devtools/shared/locales/en-US/screenshot.properties new file mode 100644 --- /dev/null +++ b/devtools/shared/locales/en-US/screenshot.properties @@ -0,0 +1,116 @@ +# 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/. + +# LOCALIZATION NOTE These strings are used inside Web Console commands. +# The Web Console command line is available from the Web Developer sub-menu +# -> 'Web Console'. +# +# The correct localization of this file might be to keep it in +# English, or another language commonly spoken among web developers. +# You want to make that choice consistent across the developer tools. +# A good criteria is the language in which you'd find the best +# documentation on web development on the web. + +# LOCALIZATION NOTE (screenshotDesc) A very short description of the +# 'screenshot' command. Displayed when the --help flag is passed to +# the screenshot command. +screenshotDesc=Save an image of the page + +# LOCALIZATION NOTE (screenshotFilenameDesc) A very short string to describe +# the 'filename' parameter to the 'screenshot' command. Displayed when the +# --help flag is passed to the screenshot command. +screenshotFilenameDesc=Destination filename + +# LOCALIZATION NOTE (screenshotFilenameManual) A fuller description of the +# 'filename' parameter to the 'screenshot' command. +screenshotFilenameManual=The name of the file (should have a ‘.png’ extension) to which we write the screenshot. + +# LOCALIZATION NOTE (screenshotClipboardDesc) A very short string to describe +# the 'clipboard' parameter to the 'screenshot' command. Displayed when the +# --help flag is passed to the screenshot command. +screenshotClipboardDesc=Copy screenshot to clipboard? (true/false) + +# LOCALIZATION NOTE (screenshotClipboardManual) A fuller description of the +# 'clipboard' parameter to the 'screenshot' command. Displayed when the +# --help flag is passed to the screenshot command. +screenshotClipboardManual=True if you want to copy the screenshot instead of saving it to a file. + +# LOCALIZATION NOTE (screenshotGroupOptions) A label for the optional options of +# the screenshot command. Displayed when the --help flag is passed to the +# screenshot command. +screenshotGroupOptions=Options + +# LOCALIZATION NOTE (screenshotDelayDesc) A very short string to describe +# the 'delay' parameter to the 'screenshot' command. Displayed when the +# --help flag is passed to the screenshot command. +screenshotDelayDesc=Delay (seconds) + +# LOCALIZATION NOTE (screenshotDelayManual) A fuller description of the +# 'delay' parameter to the 'screenshot' command. Displayed when the +# --help flag is passed to the screenshot command. +screenshotDelayManual=The time to wait (in seconds) before the screenshot is taken + +# LOCALIZATION NOTE (screenshotDPRDesc) A very short string to describe +# the 'dpr' parameter to the 'screenshot' command. Displayed when the +# --help flag is passed to the `screenshot command. +screenshotDPRDesc=Device pixel ratio + +# LOCALIZATION NOTE (screenshotDPRManual) A fuller description of the +# 'dpr' parameter to the 'screenshot' command. Displayed when the +# --help flag is passed to the `screenshot command. +screenshotDPRManual=The device pixel ratio to use when taking the screenshot + +# LOCALIZATION NOTE (screenshotFullPageDesc) A very short string to describe +# the 'fullpage' parameter to the 'screenshot' command. Displayed when the +# --help flag is passed to the `screenshot command. +screenshotFullPageDesc=Entire webpage? (true/false) + +# LOCALIZATION NOTE (screenshotFullPageManual) A fuller description of the +# 'fullpage' parameter to the 'screenshot' command. Displayed when the +# --help flag is passed to the `screenshot command. +screenshotFullPageManual=True if the screenshot should also include parts of the webpage which are outside the current scrolled bounds. + +# LOCALIZATION NOTE (screenshotFileDesc) A very short string to describe +# the 'file' parameter to the 'screenshot' command. Displayed when the +# --help flag is passed to the `screenshot command. +screenshotFileDesc=Save to file? (true/false) + +# LOCALIZATION NOTE (screenshotFileManual) A fuller description of the +# 'file' parameter to the 'screenshot' command. Displayed when the +# --help flag is passed to the `screenshot command. +screenshotFileManual=True if the screenshot should save the file even when other options are enabled (eg. clipboard). + +# LOCALIZATION NOTE (screenshotGeneratedFilename) The auto generated filename +# when no file name is provided. The first argument (%1$S) is the date string +# in yyyy-mm-dd format and the second argument (%2$S) is the time string +# in HH.MM.SS format. Please don't add the extension here. +screenshotGeneratedFilename=Screen Shot %1$S at %2$S + +# LOCALIZATION NOTE (screenshotErrorSavingToFile) Text displayed to user upon +# encountering error while saving the screenshot to the file specified. +# The argument (%1$S) is the filename. +screenshotErrorSavingToFile=Error saving to %1$S + +# LOCALIZATION NOTE (screenshotSavedToFile) Text displayed to user when the +# screenshot is successfully saved to the file specified. +# The argument (%1$S) is the filename. +screenshotSavedToFile=Saved to %1$S + +# LOCALIZATION NOTE (screenshotErrorCopying) Text displayed to user upon +# encountering error while copying the screenshot to clipboard. +screenshotErrorCopying=Error occurred while copying screenshot to clipboard. + +# LOCALIZATION NOTE (screenshotCopied) Text displayed to user when the +# screenshot is successfully copied to the clipboard. +screenshotCopied=Screenshot copied to clipboard. + +# LOCALIZATION NOTE (inspectNodeDesc) A very short string to describe the +# 'node' parameter to the 'inspect' command. Displayed when the +# --help flag is passed to the `screenshot command. +inspectNodeDesc=CSS selector + +# LOCALIZATION NOTE (inspectNodeManual) A fuller description of the 'node' +# parameter to the 'inspect' command. Displayed when the --help flag is +# passed to the `screenshot command. +inspectNodeManual=A CSS selector for use with document.querySelector which identifies a single element diff --git a/devtools/shared/webconsole/moz.build b/devtools/shared/webconsole/moz.build --- a/devtools/shared/webconsole/moz.build +++ b/devtools/shared/webconsole/moz.build @@ -11,7 +11,8 @@ DevToolsModules( 'client.js', 'js-property-provider.js', 'network-helper.js', 'network-monitor.js', + 'screenshot-helper.js', 'throttle.js', ) diff --git a/devtools/shared/webconsole/screenshot-helper.js b/devtools/shared/webconsole/screenshot-helper.js new file mode 100644 --- /dev/null +++ b/devtools/shared/webconsole/screenshot-helper.js @@ -0,0 +1,397 @@ +/* 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 { Cc, Ci, Cr } = require("chrome"); +const ChromeUtils = require("ChromeUtils"); +const { LocalizationHelper } = require("devtools/shared/l10n"); +const Services = require("Services"); +const { NetUtil } = require("resource://gre/modules/NetUtil.jsm"); + +loader.lazyImporter(this, "Downloads", "resource://gre/modules/Downloads.jsm"); +loader.lazyImporter(this, "OS", "resource://gre/modules/osfile.jsm"); +loader.lazyImporter(this, "FileUtils", "resource://gre/modules/FileUtils.jsm"); +loader.lazyImporter(this, "PrivateBrowsingUtils", + "resource://gre/modules/PrivateBrowsingUtils.jsm"); + +const STRINGS_URI = "devtools/shared/locales/screenshot.properties"; +const L10N = new LocalizationHelper(STRINGS_URI); + +const screenshotDescription = L10N.getStr("screenshotDesc"); +const screenshotGroupOptions = L10N.getStr("screenshotGroupOptions"); +const screenshotCommandParams = [ + { + name: "clipboard", + type: "boolean", + description: L10N.getStr("screenshotClipboardDesc"), + manual: L10N.getStr("screenshotClipboardManual") + }, + { + name: "delay", + type: "number", + description: L10N.getStr("screenshotDelayDesc"), + manual: L10N.getStr("screenshotDelayManual") + }, + { + name: "dpr", + type: "number", + description: L10N.getStr("screenshotDPRDesc"), + manual: L10N.getStr("screenshotDPRManual") + }, + { + name: "fullpage", + type: "boolean", + description: L10N.getStr("screenshotFullPageDesc"), + manual: L10N.getStr("screenshotFullPageManual") + }, + { + name: "selector", + type: "string", + description: L10N.getStr("inspectNodeDesc"), + manual: L10N.getStr("inspectNodeManual") + }, + { + name: "file", + type: "boolean", + description: L10N.getStr("screenshotFileDesc"), + manual: L10N.getStr("screenshotFileManual"), + }, + { + name: "filename", + type: "string", + description: L10N.getStr("screenshotFilenameDesc"), + manual: L10N.getStr("screenshotFilenameManual") + } +]; + +/** + * Creates a string from an object for use when screenshot is passed the `--help` argument + * + * @param object param + * The param object to be formatted. + * @return string + * The formatted information from the param object as a string + */ +function formatHelpField(param) { + const padding = " ".repeat(5); + return Object.entries(param).map(([key, value]) => { + if (key === "name") { + const name = `${padding}--${value}`; + return name; + } + return `${padding.repeat(2)}${key}: ${value}`; + }).join("\n"); +} + +/** + * Creates a string response from the screenshot options for use when + * screenshot is passed the `--help` argument + * + * @return string + * The formatted information from the param object as a string + */ +function getFormattedHelpData() { + const formattedParams = screenshotCommandParams + .map(formatHelpField) + .join("\n\n"); + + return `${screenshotDescription}\n${screenshotGroupOptions}\n\n${formattedParams}`; +} + +/** + * Main entry point in this file; Takes the original arguments that `:screenshot` was + * called with and the image value from the server, and uses the client window to save + * the screenshot to the remote debugging machine's memory or clipboard. + * + * @param object window + * The Debugger Client window. + * + * @param object args + * The original args with which the screenshot + * was called. + * @param object value + * an object with a image value and file name + * + * @return string[] + * Response messages from processing the screenshot + */ +function processScreenshot(window, args = {}, value) { + if (args.help) { + const message = getFormattedHelpData(); + // Wrap meesage in an array so that the return value is consistant with saveScreenshot + return [message]; + } + simulateCameraShutter(window.document); + return saveScreenshot(window, args, value); +} + +/** + * This function is called to simulate camera effects + * + * @param object document + * The Debugger Client document. + */ +function simulateCameraShutter(document) { + const window = document.defaultView; + if (Services.prefs.getBoolPref("devtools.screenshot.audio.enabled")) { + const audioCamera = new window.Audio("resource://devtools/client/themes/audio/shutter.wav"); + audioCamera.play(); + } +} + +/** + * Save the captured screenshot to one of several destinations. + * + * @param object window + * The Debugger Client window. + * + * @param object args + * The original args with which the screenshot was called. + * + * @param object image + * The image object that was sent from the server. + * + * @return string[] + * Response messages from processing the screenshot. + */ +async function saveScreenshot(window, args, image) { + const fileNeeded = args.filename || + !args.clipboard || args.file; + const results = []; + + if (args.clipboard) { + const result = await saveToClipboard(window, image.data); + results.push(result); + } + + if (fileNeeded) { + const result = await saveToFile(window, image); + results.push(result); + } + return results; +} + +/** + * Save the image data to the clipboard. This returns a promise, so it can + * be treated exactly like file processing. + * + * @param object window + * The Debugger Client window. + * + * @param string data + * The image data encoded in base64 that was sent from the server. + * + * @return string + * Response message from processing the screenshot. + */ +function saveToClipboard(window, data) { + return new Promise(resolve => { + try { + const channel = NetUtil.newChannel({ + uri: data, + loadUsingSystemPrincipal: true, + contentPolicyType: Ci.nsIContentPolicy.TYPE_INTERNAL_IMAGE + }); + const input = channel.open2(); + + const loadContext = window.QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIWebNavigation) + .QueryInterface(Ci.nsILoadContext); + + const callback = { + onImageReady(container, status) { + if (!container) { + console.error("imgTools.decodeImageAsync failed"); + resolve(L10N.getStr("screenshotErrorCopying")); + return; + } + + try { + const wrapped = Cc["@mozilla.org/supports-interface-pointer;1"] + .createInstance(Ci.nsISupportsInterfacePointer); + wrapped.data = container; + + const trans = Cc["@mozilla.org/widget/transferable;1"] + .createInstance(Ci.nsITransferable); + trans.init(loadContext); + trans.addDataFlavor(channel.contentType); + trans.setTransferData(channel.contentType, wrapped, -1); + + Services.clipboard.setData(trans, null, Ci.nsIClipboard.kGlobalClipboard); + + resolve(L10N.getStr("screenshotCopied")); + } catch (ex) { + console.error(ex); + resolve(L10N.getStr("screenshotErrorCopying")); + } + } + }; + + const threadManager = Cc["@mozilla.org/thread-manager;1"].getService(); + const imgTools = Cc["@mozilla.org/image/tools;1"] + .getService(Ci.imgITools); + imgTools.decodeImageAsync(input, channel.contentType, callback, + threadManager.currentThread); + } catch (ex) { + console.error(ex); + resolve(L10N.getStr("screenshotErrorCopying")); + } + }); +} + +/** + * Progress listener that forwards calls to a transfer object. + * + * This is used below in saveToFile to forward progress updates from the + * nsIWebBrowserPersist object that does the actual saving to the nsITransfer + * which just represents the operation for the Download Manager. This keeps the + * Download Manager updated on saving progress and completion, so that it gives + * visual feedback from the downloads toolbar button when the save is done. + * + * It also allows the browser window to show auth prompts if needed (should not + * be needed for saving screenshots). + * + * This code is borrowed directly from contentAreaUtils.js. + * + * @param object win + * The Debugger Client window. + * + * @param object transfer + * The transfer object. + * + */ +function DownloadListener(win, transfer) { + this.window = win; + this.transfer = transfer; + + // For most method calls, forward to the transfer object. + for (const name in transfer) { + if (name != "QueryInterface" && + name != "onStateChange") { + this[name] = (...args) => transfer[name].apply(transfer, args); + } + } + + // Allow saveToFile to await completion for error handling + this._completedDeferred = {}; + this.completed = new Promise((resolve, reject) => { + this._completedDeferred.resolve = resolve; + this._completedDeferred.reject = reject; + }); +} + +DownloadListener.prototype = { + QueryInterface: ChromeUtils.generateQI(["nsIInterfaceRequestor", + "nsIWebProgressListener", + "nsIWebProgressListener2"]), + + getInterface: function(iid) { + if (iid.equals(Ci.nsIAuthPrompt) || + iid.equals(Ci.nsIAuthPrompt2)) { + const ww = Cc["@mozilla.org/embedcomp/window-watcher;1"] + .getService(Ci.nsIPromptFactory); + return ww.getPrompt(this.window, iid); + } + + throw Cr.NS_ERROR_NO_INTERFACE; + }, + + onStateChange: function(webProgress, request, state, status) { + // Check if the download has completed + if ((state & Ci.nsIWebProgressListener.STATE_STOP) && + (state & Ci.nsIWebProgressListener.STATE_IS_NETWORK)) { + if (status == Cr.NS_OK) { + this._completedDeferred.resolve(); + } else { + this._completedDeferred.reject(); + } + } + + this.transfer.onStateChange.apply(this.transfer, arguments); + } +}; + +/** + * Save the screenshot data to disk, returning a promise which is resolved on + * completion. + * + * @param object window + * The Debugger Client window. + * + * @param object image + * The image object that was sent from the server. + * + * @return string + * Response message from processing the screenshot. + */ +async function saveToFile(window, image) { + const document = window.document; + let filename = image.filename; + + // Check there is a .png extension to filename + if (!filename.match(/.png$/i)) { + filename += ".png"; + } + + const downloadsDir = await Downloads.getPreferredDownloadsDirectory(); + const downloadsDirExists = await OS.File.exists(downloadsDir); + if (downloadsDirExists) { + // If filename is absolute, it will override the downloads directory and + // still be applied as expected. + filename = OS.Path.join(downloadsDir, filename); + } + + const sourceURI = Services.io.newURI(image.data); + const targetFile = new FileUtils.File(filename); + const targetFileURI = Services.io.newFileURI(targetFile); + + // Create download and track its progress. + // This is adapted from saveURL in contentAreaUtils.js, but simplified greatly + // and modified to allow saving to arbitrary paths on disk. Using these + // objects as opposed to just writing with OS.File allows us to tie into the + // download manager to record a download entry and to get visual feedback from + // the downloads toolbar button when the save is done. + const nsIWBP = Ci.nsIWebBrowserPersist; + const flags = nsIWBP.PERSIST_FLAGS_REPLACE_EXISTING_FILES | + nsIWBP.PERSIST_FLAGS_BYPASS_CACHE | + nsIWBP.PERSIST_FLAGS_AUTODETECT_APPLY_CONVERSION; + const isPrivate = + PrivateBrowsingUtils.isContentWindowPrivate(document.defaultView); + const persist = Cc["@mozilla.org/embedding/browser/nsWebBrowserPersist;1"] + .createInstance(Ci.nsIWebBrowserPersist); + persist.persistFlags = flags; + const tr = Cc["@mozilla.org/transfer;1"].createInstance(Ci.nsITransfer); + tr.init(sourceURI, + targetFileURI, + "", + null, + null, + null, + persist, + isPrivate); + const listener = new DownloadListener(window, tr); + persist.progressListener = listener; + const principal = Services.scriptSecurityManager.getSystemPrincipal(); + persist.savePrivacyAwareURI(sourceURI, + principal, + 0, + document.documentURIObject, + Ci.nsIHttpChannel.REFERRER_POLICY_UNSET, + null, + null, + targetFileURI, + isPrivate); + + try { + // Await successful completion of the save via the listener + await listener.completed; + return L10N.getFormatStr("screenshotSavedToFile", filename); + } catch (ex) { + console.error(ex); + return L10N.getFormatStr("screenshotErrorSavingToFile", filename); + } +} + +module.exports = processScreenshot; diff --git a/devtools/startup/devtools-startup.js b/devtools/startup/devtools-startup.js --- a/devtools/startup/devtools-startup.js +++ b/devtools/startup/devtools-startup.js @@ -38,10 +38,12 @@ "resource://gre/modules/AppConstants.jsm"); ChromeUtils.defineModuleGetter(this, "CustomizableUI", "resource:///modules/CustomizableUI.jsm"); ChromeUtils.defineModuleGetter(this, "CustomizableWidgets", "resource:///modules/CustomizableWidgets.jsm"); +ChromeUtils.defineModuleGetter(this, "PrivateBrowsingUtils", + "resource://gre/modules/PrivateBrowsingUtils.jsm"); // We don't want to spend time initializing the full loader here so we create // our own lazy require. XPCOMUtils.defineLazyGetter(this, "Telemetry", function() { const { require } = ChromeUtils.import("resource://devtools/shared/Loader.jsm", {}); @@ -946,20 +948,36 @@ const browser = chrome.gBrowser.selectedBrowser; if (message.data === null) { // Save original contents chrome.saveBrowser(browser); } else { + if (!message.data.startsWith("blob:null") || !browser.contentPrincipal.isNullPrincipal) { + Cu.reportError("Got invalid request to save JSON data"); + return; + } // The following code emulates saveBrowser, but: // - Uses the given blob URL containing the custom contents to save. // - Obtains the file name from the URL of the document, not the blob. + // - avoids passing the document and explicitly passes system principal. + // We have a blob created by a null principal to save, and the null + // principal is from the child. Null principals don't survive crossing + // over IPC, so there's no other principal that'll work. const persistable = browser.frameLoader; persistable.startPersistence(0, { onDocumentReady(doc) { const uri = chrome.makeURI(doc.documentURI, doc.characterSet); const filename = chrome.getDefaultFileName(undefined, uri, doc, null); - chrome.internalSave(message.data, doc, filename, null, doc.contentType, - false, null, null, null, doc, false, null, undefined); + chrome.internalSave(message.data, null, filename, null, doc.contentType, + false /* bypass cache */, + null, /* filepicker title key */ + null, /* file chosen */ + null, /* referrer */ + null, /* initiating document */ + false, /* don't skip prompt for a location */ + null, /* cache key */ + PrivateBrowsingUtils.isBrowserPrivate(browser), /* private browsing ? */ + Services.scriptSecurityManager.getSystemPrincipal()); }, onError(status) { throw new Error("JSON Viewer's onSave failed in startPersistence"); } }); diff --git a/dom/base/Element.h b/dom/base/Element.h --- a/dom/base/Element.h +++ b/dom/base/Element.h @@ -1099,10 +1099,12 @@ void GetAttribute(const nsAString& aName, DOMString& aReturn); void GetAttributeNS(const nsAString& aNamespaceURI, const nsAString& aLocalName, nsAString& aReturn); + bool ToggleAttribute(const nsAString& aName, const Optional& aForce, + nsIPrincipal* aTriggeringPrincipal, ErrorResult& aError); void SetAttribute(const nsAString& aName, const nsAString& aValue, nsIPrincipal* aTriggeringPrincipal, ErrorResult& aError); void SetAttributeNS(const nsAString& aNamespaceURI, const nsAString& aLocalName, const nsAString& aValue, diff --git a/dom/base/Element.cpp b/dom/base/Element.cpp --- a/dom/base/Element.cpp +++ b/dom/base/Element.cpp @@ -1319,10 +1319,47 @@ aReturn.SetNull(); } } } +bool +Element::ToggleAttribute(const nsAString& aName, + const Optional& aForce, + nsIPrincipal* aTriggeringPrincipal, + ErrorResult& aError) +{ + aError = nsContentUtils::CheckQName(aName, false); + if (aError.Failed()) { + return false; + } + + nsAutoString nameToUse; + const nsAttrName* name = InternalGetAttrNameFromQName(aName, &nameToUse); + if (!name) { + if (aForce.WasPassed() && !aForce.Value()) { + return false; + } + RefPtr nameAtom = NS_AtomizeMainThread(nameToUse); + if (!nameAtom) { + aError.Throw(NS_ERROR_OUT_OF_MEMORY); + return false; + } + aError = SetAttr(kNameSpaceID_None, nameAtom, EmptyString(), aTriggeringPrincipal, true); + return true; + } + if (aForce.WasPassed() && aForce.Value()) { + return true; + } + // Hold a strong reference here so that the atom or nodeinfo doesn't go + // away during UnsetAttr. If it did UnsetAttr would be left with a + // dangling pointer as argument without knowing it. + nsAttrName tmp(*name); + + aError = UnsetAttr(name->NamespaceID(), name->LocalName(), true); + return false; +} + void Element::SetAttribute(const nsAString& aName, const nsAString& aValue, nsIPrincipal* aTriggeringPrincipal, ErrorResult& aError) diff --git a/dom/base/Navigator.h b/dom/base/Navigator.h --- a/dom/base/Navigator.h +++ b/dom/base/Navigator.h @@ -74,10 +74,11 @@ class Presentation; class LegacyMozTCPSocket; class VRDisplay; class VRServiceTest; class StorageManager; +class MediaCapabilities; class Navigator final : public nsISupports , public nsWrapperCache { public: @@ -213,10 +214,12 @@ StorageManager* Storage(); static void GetAcceptLanguages(nsTArray& aLanguages); + dom::MediaCapabilities* MediaCapabilities(); + // WebIDL helper methods static bool HasUserMediaSupport(JSContext* /* unused */, JSObject* /* unused */); nsPIDOMWindowInner* GetParentObject() const @@ -272,10 +275,11 @@ RefPtr mGamepadServiceTest; nsTArray > mVRGetDisplaysPromises; RefPtr mVRServiceTest; nsTArray mRequestedVibrationPattern; RefPtr mStorageManager; + RefPtr mMediaCapabilities; }; } // namespace dom } // namespace mozilla diff --git a/dom/base/Navigator.cpp b/dom/base/Navigator.cpp --- a/dom/base/Navigator.cpp +++ b/dom/base/Navigator.cpp @@ -32,10 +32,11 @@ #include "mozilla/Preferences.h" #include "mozilla/Telemetry.h" #include "BatteryManager.h" #include "mozilla/dom/CredentialsContainer.h" #include "mozilla/dom/GamepadServiceTest.h" +#include "mozilla/dom/MediaCapabilities.h" #include "mozilla/dom/WakeLock.h" #include "mozilla/dom/power/PowerManagerService.h" #include "mozilla/dom/MIDIAccessManager.h" #include "mozilla/dom/MIDIOptionsBinding.h" #include "mozilla/dom/Permissions.h" @@ -150,10 +151,11 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mConnection) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mStorageManager) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCredentials) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mMediaDevices) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mServiceWorkerContainer) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mMediaCapabilities) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mWindow) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mMediaKeySystemAccessManager) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPresentation) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mGamepadServiceTest) @@ -220,10 +222,12 @@ if (mVRServiceTest) { mVRServiceTest->Shutdown(); mVRServiceTest = nullptr; } + + mMediaCapabilities = nullptr; } void Navigator::GetUserAgent(nsAString& aUserAgent, CallerType aCallerType, ErrorResult& aRv) const @@ -1793,10 +1797,20 @@ mCredentials = new CredentialsContainer(GetWindow()); } return mCredentials; } +dom::MediaCapabilities* +Navigator::MediaCapabilities() +{ + if (!mMediaCapabilities) { + mMediaCapabilities = + new dom::MediaCapabilities(GetWindow()->AsGlobal()); + } + return mMediaCapabilities; +} + /* static */ bool Navigator::Webdriver() { return Preferences::GetBool("marionette.enabled", false); diff --git a/dom/base/ScreenLuminance.h b/dom/base/ScreenLuminance.h new file mode 100644 --- /dev/null +++ b/dom/base/ScreenLuminance.h @@ -0,0 +1,58 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 et tw=78: */ +/* 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 mozilla_dom_ScreenLuminance_h +#define mozilla_dom_ScreenLuminance_h + +#include "nsCycleCollectionParticipant.h" +#include "nsISupportsImpl.h" + +class nsScreen; + +namespace mozilla { +namespace dom { + +class ScreenLuminance final : public nsWrapperCache +{ +public: + // Ref counting and cycle collection + NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(ScreenLuminance) + NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_NATIVE_CLASS(ScreenLuminance) + + // WebIDL methods + double Min() const { return mMin; } + double Max() const { return mMax; } + double MaxAverage() const { return mMaxAverage; } + // End WebIDL methods + + ScreenLuminance(nsScreen* aScreen, + double aMin, + double aMax, + double aMaxAverage) + : mScreen(aScreen) + , mMin(aMin) + , mMax(aMax) + , mMaxAverage(aMaxAverage) + { + } + + nsScreen* GetParentObject() const { return mScreen; } + JSObject* WrapObject(JSContext* aCx, + JS::Handle aGivenProto) override; + +private: + virtual ~ScreenLuminance() {} + + RefPtr mScreen; + double mMin; + double mMax; + double mMaxAverage; +}; + +} // namespace dom +} // namespace mozilla + +#endif // mozilla_dom_ScreenLuminance_h diff --git a/dom/base/ScreenLuminance.cpp b/dom/base/ScreenLuminance.cpp new file mode 100644 --- /dev/null +++ b/dom/base/ScreenLuminance.cpp @@ -0,0 +1,24 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 et tw=78: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "ScreenLuminance.h" +#include "nsScreen.h" + +namespace mozilla { +namespace dom { + +NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(ScreenLuminance, AddRef) +NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(ScreenLuminance, Release) +NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(ScreenLuminance, mScreen) + +JSObject* +ScreenLuminance::WrapObject(JSContext* aCx, JS::Handle aGivenProto) +{ + return ScreenLuminance_Binding::Wrap(aCx, this, aGivenProto); +} + +} // namespace dom +} // namespace mozilla diff --git a/dom/base/UseCounters.conf b/dom/base/UseCounters.conf --- a/dom/base/UseCounters.conf +++ b/dom/base/UseCounters.conf @@ -113,5 +113,9 @@ method console.timeEnd method console.exception method console.timeStamp method console.profile method console.profileEnd + +// document.open information +custom DocumentOpen calls document.open in a way that creates a new Window object +custom DocumentOpenReplace calls document.open in a way that creates a new Window object and replaces the old history entry. diff --git a/dom/base/moz.build b/dom/base/moz.build --- a/dom/base/moz.build +++ b/dom/base/moz.build @@ -208,10 +208,11 @@ 'Pose.h', 'ProcessGlobal.h', 'ProcessMessageManager.h', 'ResponsiveImageSelector.h', 'SameProcessMessageQueue.h', + 'ScreenLuminance.h', 'ScreenOrientation.h', 'Selection.h', 'ShadowRoot.h', 'StructuredCloneBlob.h', 'StructuredCloneHolder.h', @@ -357,10 +358,11 @@ 'PostMessageEvent.cpp', 'ProcessGlobal.cpp', 'ProcessMessageManager.cpp', 'ResponsiveImageSelector.cpp', 'SameProcessMessageQueue.cpp', + 'ScreenLuminance.cpp', 'ScreenOrientation.cpp', 'Selection.cpp', 'SelectionChangeListener.cpp', 'ShadowRoot.cpp', 'StructuredCloneBlob.cpp', diff --git a/dom/base/nsContentAreaDragDrop.h b/dom/base/nsContentAreaDragDrop.h --- a/dom/base/nsContentAreaDragDrop.h +++ b/dom/base/nsContentAreaDragDrop.h @@ -75,10 +75,11 @@ public: NS_DECL_ISUPPORTS NS_DECL_NSIFLAVORDATAPROVIDER nsresult SaveURIToFile(nsIURI* inSourceURI, + nsIPrincipal* inTriggeringPrincipal, nsIFile* inDestFile, bool isPrivate); }; #endif /* nsContentAreaDragDrop_h__ */ diff --git a/dom/base/nsContentAreaDragDrop.cpp b/dom/base/nsContentAreaDragDrop.cpp --- a/dom/base/nsContentAreaDragDrop.cpp +++ b/dom/base/nsContentAreaDragDrop.cpp @@ -139,10 +139,11 @@ // SaveURIToFile // used on platforms where it's possible to drag items (e.g. images) // into the file system nsresult nsContentAreaDragDropDataProvider::SaveURIToFile(nsIURI* inSourceURI, + nsIPrincipal* inTriggeringPrincipal, nsIFile* inDestFile, bool isPrivate) { nsCOMPtr sourceURL = do_QueryInterface(inSourceURI); if (!sourceURL) { @@ -160,11 +161,12 @@ NS_ENSURE_SUCCESS(rv, rv); persist->SetPersistFlags(nsIWebBrowserPersist::PERSIST_FLAGS_AUTODETECT_APPLY_CONVERSION); // referrer policy can be anything since the referrer is nullptr - return persist->SavePrivacyAwareURI(inSourceURI, 0, nullptr, + return persist->SavePrivacyAwareURI(inSourceURI, + inTriggeringPrincipal, 0, nullptr, mozilla::net::RP_Unset, nullptr, nullptr, inDestFile, isPrivate); } @@ -340,11 +342,13 @@ file->Append(targetFilename); bool isPrivate; aTransferable->GetIsPrivateData(&isPrivate); - rv = SaveURIToFile(sourceURI, file, isPrivate); + nsCOMPtr principal; + aTransferable->GetRequestingPrincipal(getter_AddRefs(principal)); + rv = SaveURIToFile(sourceURI, principal, file, isPrivate); // send back an nsIFile if (NS_SUCCEEDED(rv)) { CallQueryInterface(file, aData); *aDataLen = sizeof(nsIFile*); } diff --git a/dom/base/nsContentUtils.cpp b/dom/base/nsContentUtils.cpp --- a/dom/base/nsContentUtils.cpp +++ b/dom/base/nsContentUtils.cpp @@ -4795,11 +4795,11 @@ } for (auto i = sEventListenerManagersHash->Iter(); !i.Done(); i.Next()) { auto entry = static_cast(i.Get()); nsINode* n = static_cast(entry->mListenerManager->GetTarget()); - if (n && n->IsInUncomposedDoc() && + if (n && n->IsInComposedDoc() && nsCCUncollectableMarker::InGeneration(n->OwnerDoc()->GetMarkedCCGeneration())) { entry->mListenerManager->MarkForCC(); } } } @@ -7236,11 +7236,11 @@ { #ifdef XP_MACOSX // We control dispatch to all mac plugins. return false; #else - if (!aContent || !aContent->IsInUncomposedDoc()) { + if (!aContent || !aContent->IsInComposedDoc()) { return false; } nsCOMPtr olc = do_QueryInterface(aContent); if (!olc) { diff --git a/dom/base/nsGlobalWindowInner.h b/dom/base/nsGlobalWindowInner.h --- a/dom/base/nsGlobalWindowInner.h +++ b/dom/base/nsGlobalWindowInner.h @@ -43,10 +43,11 @@ #include "mozilla/ErrorResult.h" #include "mozilla/Attributes.h" #include "mozilla/GuardObjects.h" #include "mozilla/LinkedList.h" #include "mozilla/TimeStamp.h" +#include "mozilla/webgpu/InstanceProvider.h" #include "nsWrapperCacheInlines.h" #include "nsIIdleObserver.h" #include "nsIDocument.h" #include "mozilla/dom/EventTarget.h" #include "mozilla/dom/WindowBinding.h" @@ -215,10 +216,11 @@ , public nsIScriptObjectPrincipal , public nsSupportsWeakReference , public nsIInterfaceRequestor , public PRCListStr , public nsAPostRefreshObserver + , public mozilla::webgpu::InstanceProvider { public: typedef mozilla::TimeStamp TimeStamp; typedef mozilla::TimeDuration TimeDuration; diff --git a/dom/base/nsGlobalWindowInner.cpp b/dom/base/nsGlobalWindowInner.cpp --- a/dom/base/nsGlobalWindowInner.cpp +++ b/dom/base/nsGlobalWindowInner.cpp @@ -884,10 +884,11 @@ //*** nsGlobalWindowInner: Object Management //***************************************************************************** nsGlobalWindowInner::nsGlobalWindowInner(nsGlobalWindowOuter *aOuterWindow) : nsPIDOMWindowInner(aOuterWindow->AsOuter()), + mozilla::webgpu::InstanceProvider(this), mIdleFuzzFactor(0), mIdleCallbackIndex(-1), mCurrentlyIdle(false), mAddActiveEventFuzzTime(true), mWasOffline(false), @@ -1477,10 +1478,12 @@ for (size_t i = 0; i < tmp->mDocumentFlushedResolvers.Length(); i++) { NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDocumentFlushedResolvers[i]->mPromise); NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDocumentFlushedResolvers[i]->mCallback); } + static_cast(tmp)->CcTraverse(cb); + NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsGlobalWindowInner) tmp->CleanupCachedXBLHandlers(); @@ -1580,10 +1583,12 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK(mDocumentFlushedResolvers[i]->mPromise); NS_IMPL_CYCLE_COLLECTION_UNLINK(mDocumentFlushedResolvers[i]->mCallback); } tmp->mDocumentFlushedResolvers.Clear(); + static_cast(tmp)->CcUnlink(); + NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER NS_IMPL_CYCLE_COLLECTION_UNLINK_END #ifdef DEBUG void diff --git a/dom/base/nsRange.cpp b/dom/base/nsRange.cpp --- a/dom/base/nsRange.cpp +++ b/dom/base/nsRange.cpp @@ -3028,18 +3028,18 @@ // Hold strong pointers across the flush nsCOMPtr startContainer = aStartContainer; nsCOMPtr endContainer = aEndContainer; // Flush out layout so our frames are up to date. - if (!aStartContainer->IsInUncomposedDoc()) { + if (!aStartContainer->IsInComposedDoc()) { return; } if (aFlushLayout) { aStartContainer->OwnerDoc()->FlushPendingNotifications(FlushType::Layout); // Recheck whether we're still in the document - if (!aStartContainer->IsInUncomposedDoc()) { + if (!aStartContainer->IsInComposedDoc()) { return; } } RangeSubtreeIterator iter; @@ -3168,11 +3168,11 @@ nsIDocument* doc = mStart.Container()->OwnerDoc(); NS_ENSURE_TRUE(doc, NS_ERROR_UNEXPECTED); doc->FlushPendingNotifications(FlushType::Frames); // Recheck whether we're still in the document - NS_ENSURE_TRUE(mStart.Container()->IsInUncomposedDoc(), NS_ERROR_UNEXPECTED); + NS_ENSURE_TRUE(mStart.Container()->IsInComposedDoc(), NS_ERROR_UNEXPECTED); // A table to map gfxFontEntry objects to InspectorFontFace objects. // (We hold on to the InspectorFontFaces strongly due to the nsAutoPtrs // in the nsClassHashtable, until we move them out into aResult at the end // of the function.) diff --git a/dom/base/nsScreen.h b/dom/base/nsScreen.h --- a/dom/base/nsScreen.h +++ b/dom/base/nsScreen.h @@ -5,10 +5,12 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #ifndef nsScreen_h___ #define nsScreen_h___ #include "mozilla/Attributes.h" +#include "mozilla/dom/ScreenBinding.h" +#include "mozilla/dom/ScreenLuminance.h" #include "mozilla/dom/ScreenOrientation.h" #include "mozilla/DOMEventTargetHelper.h" #include "mozilla/ErrorResult.h" #include "nsCOMPtr.h" #include "nsRect.h" @@ -108,10 +110,23 @@ nsRect rect; aRv = GetAvailRect(rect); return rect.Height(); } + // Media Capabilities extension + mozilla::dom::ScreenColorGamut ColorGamut() const + { + return mozilla::dom::ScreenColorGamut::Srgb; + } + + already_AddRefed GetLuminance() const + { + return nullptr; + } + + IMPL_EVENT_HANDLER(change); + // Deprecated void GetMozOrientation(nsString& aOrientation, mozilla::dom::CallerType aCallerType) const; IMPL_EVENT_HANDLER(mozorientationchange) diff --git a/dom/base/nsScreen.cpp b/dom/base/nsScreen.cpp --- a/dom/base/nsScreen.cpp +++ b/dom/base/nsScreen.cpp @@ -2,11 +2,10 @@ /* vim: set ts=8 sts=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/. */ -#include "mozilla/dom/ScreenBinding.h" #include "nsContentUtils.h" #include "nsScreen.h" #include "nsIDocument.h" #include "nsIDocShell.h" #include "nsIDocument.h" diff --git a/dom/base/test/test_range_bounds.html b/dom/base/test/test_range_bounds.html --- a/dom/base/test/test_range_bounds.html +++ b/dom/base/test/test_range_bounds.html @@ -253,10 +253,26 @@ firstSpanRect.height], [bdoRect.right - 7 * widthPerChar, bdoRect.right, bdoRect.top, bdoRect.bottom, 7*widthPerChar, bdoRect.height]]}); } + +function testShadowDOM() { + var ifr = document.createElement("iframe"); + document.body.appendChild(ifr); + var doc = ifr.contentDocument; + var d = doc.createElement("div"); + var sr = d.attachShadow({mode: "open"}); + sr.innerHTML = "
inside shadow DOM
"; + doc.body.appendChild(d); + var r = new ifr.contentWindow.Range(); + r.selectNode(sr.firstChild); + var rect = r.getBoundingClientRect(); + isnot(rect.width, 0, "Div element inside shadow shouldn't have zero size."); + isnot(rect.height, 0, "Div element inside shadow shouldn't have zero size."); +} + function test(){ //test ltr doTest(); //test rtl @@ -272,11 +288,15 @@ //test transforms isTransformed = true; root.style.transform = "translate(30px,50px)"; doTest(); - SimpleTest.finish(); + SpecialPowers.pushPrefEnv({"set":[["dom.webcomponents.shadowdom.enabled", true]]}, + () => { + testShadowDOM(); + SimpleTest.finish(); + }); } window.onload = function() { SimpleTest.waitForExplicitFinish(); setTimeout(test, 0); diff --git a/dom/bindings/Bindings.conf b/dom/bindings/Bindings.conf --- a/dom/bindings/Bindings.conf +++ b/dom/bindings/Bindings.conf @@ -515,10 +515,14 @@ 'MatchPatternSet': { 'headerFile': 'mozilla/extensions/MatchPattern.h', 'nativeType': 'mozilla::extensions::MatchPatternSet', }, +'MediaCapabilitiesInfo' : { + 'wrapperCache': False, +}, + 'MediaKeys' : { 'implicitJSContext': [ 'createSession'] }, 'MediaStream': { @@ -1291,10 +1295,143 @@ 'WebGLVertexArrayObject': { 'nativeType': 'mozilla::WebGLVertexArray', 'headerFile': 'WebGLVertexArray.h' }, +# WebGPU + +'WebGPU': { + 'nativeType': 'mozilla::webgpu::Instance', +}, +'WebGPUAdapter': { + 'nativeType': 'mozilla::webgpu::Adapter', +}, +'WebGPUAttachmentState': { + 'nativeType': 'mozilla::webgpu::AttachmentState', +}, +'WebGPUBindGroup': { + 'nativeType': 'mozilla::webgpu::BindGroup', +}, +'WebGPUBindGroupLayout': { + 'nativeType': 'mozilla::webgpu::BindGroupLayout', +}, +'WebGPUBlendState': { + 'nativeType': 'mozilla::webgpu::BlendState', +}, +'WebGPUBuffer': { + 'nativeType': 'mozilla::webgpu::Buffer', +}, +'WebGPUCommandBuffer': { + 'nativeType': 'mozilla::webgpu::CommandBuffer', +}, +'WebGPUCommandEncoder': { + 'nativeType': 'mozilla::webgpu::CommandEncoder', +}, +'WebGPUComputePipeline': { + 'nativeType': 'mozilla::webgpu::ComputePipeline', +}, +'WebGPUDepthStencilState': { + 'nativeType': 'mozilla::webgpu::DepthStencilState', +}, +'WebGPUDevice': { + 'nativeType': 'mozilla::webgpu::Device', +}, +'WebGPUFence': { + 'nativeType': 'mozilla::webgpu::Fence', +}, +'WebGPUInputState': { + 'nativeType': 'mozilla::webgpu::InputState', +}, +'WebGPULogEntry': { + 'nativeType': 'mozilla::webgpu::LogEntry', +}, +'WebGPUPipelineLayout': { + 'nativeType': 'mozilla::webgpu::PipelineLayout', +}, +'WebGPUQueue': { + 'nativeType': 'mozilla::webgpu::Queue', +}, +'WebGPURenderPipeline': { + 'nativeType': 'mozilla::webgpu::RenderPipeline', +}, +'WebGPUSampler': { + 'nativeType': 'mozilla::webgpu::Sampler', +}, +'WebGPUShaderModule': { + 'nativeType': 'mozilla::webgpu::ShaderModule', +}, +'WebGPUSwapChain': { + 'nativeType': 'mozilla::webgpu::SwapChain', +}, +'WebGPUTexture': { + 'nativeType': 'mozilla::webgpu::Texture', +}, +'WebGPUTextureView': { + 'nativeType': 'mozilla::webgpu::TextureView', +}, + + +'WebGPUBindingType': { + 'concrete': False, +}, +'WebGPUBlendFactor': { + 'concrete': False, +}, +'WebGPUBlendOperation': { + 'concrete': False, +}, +'WebGPUBufferUsage': { + 'concrete': False, +}, +'WebGPUColorWriteBits': { + 'concrete': False, +}, +'WebGPUCompareFunction': { + 'concrete': False, +}, +'WebGPUFilterMode': { + 'concrete': False, +}, +'WebGPUIndexFormat': { + 'concrete': False, +}, +'WebGPUInputStepMode': { + 'concrete': False, +}, +'WebGPULoadOp': { + 'concrete': False, +}, +'WebGPUPrimitiveTopology': { + 'concrete': False, +}, +'WebGPUShaderStage': { + 'concrete': False, +}, +'WebGPUShaderStageBit': { + 'concrete': False, +}, +'WebGPUStencilOperation': { + 'concrete': False, +}, +'WebGPUStoreOp': { + 'concrete': False, +}, +'WebGPUTextureDimension': { + 'concrete': False, +}, +'WebGPUTextureFormat': { + 'concrete': False, +}, +'WebGPUTextureUsage': { + 'concrete': False, +}, +'WebGPUVertexFormat': { + 'concrete': False, +}, + +# WebRTC + 'WebrtcGlobalInformation': { 'nativeType': 'mozilla::dom::WebrtcGlobalInformation', 'headerFile': 'WebrtcGlobalInformation.h', 'wrapperCache': False, 'concrete': False, diff --git a/dom/bindings/Errors.msg b/dom/bindings/Errors.msg --- a/dom/bindings/Errors.msg +++ b/dom/bindings/Errors.msg @@ -102,5 +102,7 @@ MSG_DEF(MSG_ONLY_IF_CACHED_WITHOUT_SAME_ORIGIN, 1, JSEXN_TYPEERR, "Request mode '{0}' was used, but request cache mode 'only-if-cached' can only be used with request mode 'same-origin'.") MSG_DEF(MSG_THRESHOLD_RANGE_ERROR, 0, JSEXN_RANGEERR, "Threshold values must all be in the range [0, 1].") MSG_DEF(MSG_WORKER_THREAD_SHUTTING_DOWN, 0, JSEXN_TYPEERR, "The Worker thread is shutting down.") MSG_DEF(MSG_CACHE_OPEN_FAILED, 0, JSEXN_TYPEERR, "CacheStorage.open() failed to access the storage system.") MSG_DEF(MSG_MATRIX_INIT_LENGTH_WRONG, 1, JSEXN_TYPEERR, "Matrix init sequence must have a length of 6 or 16 (actual value: {0})") +MSG_DEF(MSG_INVALID_MEDIA_VIDEO_CONFIGURATION, 0, JSEXN_TYPEERR, "Invalid VideoConfiguration.") +MSG_DEF(MSG_INVALID_MEDIA_AUDIO_CONFIGURATION, 0, JSEXN_TYPEERR, "Invalid AudioConfiguration.") diff --git a/dom/bindings/moz.build b/dom/bindings/moz.build --- a/dom/bindings/moz.build +++ b/dom/bindings/moz.build @@ -5,10 +5,13 @@ # file, You can obtain one at http://mozilla.org/MPL/2.0/. with Files("**"): BUG_COMPONENT = ("Core", "DOM") +if CONFIG['CC_TYPE'] == 'msvc': + CXXFLAGS += ['-bigobj'] + TEST_DIRS += ['test'] XPIDL_SOURCES += [ 'nsIScriptError.idl' ] diff --git a/dom/browser-element/BrowserElementParent.cpp b/dom/browser-element/BrowserElementParent.cpp --- a/dom/browser-element/BrowserElementParent.cpp +++ b/dom/browser-element/BrowserElementParent.cpp @@ -158,11 +158,11 @@ NS_LITERAL_STRING("mozbrowseropenwindow"), cx, val, &status); if (dispatchSucceeded) { - if (aPopupFrameElement->IsInUncomposedDoc()) { + if (aPopupFrameElement->IsInComposedDoc()) { return BrowserElementParent::OPEN_WINDOW_ADDED; } if (status == nsEventStatus_eConsumeNoDefault) { // If the frame was not added to a document, report to callers whether // preventDefault was called on or not diff --git a/dom/events/PointerEventHandler.cpp b/dom/events/PointerEventHandler.cpp --- a/dom/events/PointerEventHandler.cpp +++ b/dom/events/PointerEventHandler.cpp @@ -630,11 +630,11 @@ nsCOMPtr shell = targetDoc->GetShell(); if (NS_WARN_IF(!shell)) { return; } - if (!aIsGotCapture && !aCaptureTarget->IsInUncomposedDoc()) { + if (!aIsGotCapture && !aCaptureTarget->IsInComposedDoc()) { // If the capturing element was removed from the DOM tree, fire // ePointerLostCapture at the document. PointerEventInit init; init.mPointerId = aPointerEvent->pointerId; init.mBubbles = true; diff --git a/dom/html/HTMLMediaElement.cpp b/dom/html/HTMLMediaElement.cpp --- a/dom/html/HTMLMediaElement.cpp +++ b/dom/html/HTMLMediaElement.cpp @@ -7690,11 +7690,11 @@ Telemetry::Accumulate(Telemetry::VIDEO_AS_CONTENT_SOURCE, 0); } else { // 1 = ALL_INVISIBLE Telemetry::Accumulate(Telemetry::VIDEO_AS_CONTENT_SOURCE, 1); - if (IsInUncomposedDoc()) { + if (IsInComposedDoc()) { // 0 = ALL_IN_TREE Telemetry::Accumulate(Telemetry::VIDEO_AS_CONTENT_SOURCE_IN_TREE_OR_NOT, 0); } else { // 1 = ALL_NOT_IN_TREE @@ -7710,11 +7710,11 @@ Telemetry::Accumulate(Telemetry::VIDEO_AS_CONTENT_SOURCE, 2); } else { // 3 = drawImage_INVISIBLE Telemetry::Accumulate(Telemetry::VIDEO_AS_CONTENT_SOURCE, 3); - if (IsInUncomposedDoc()) { + if (IsInComposedDoc()) { // 2 = drawImage_IN_TREE Telemetry::Accumulate( Telemetry::VIDEO_AS_CONTENT_SOURCE_IN_TREE_OR_NOT, 2); } else { // 3 = drawImage_NOT_IN_TREE @@ -7730,11 +7730,11 @@ Telemetry::Accumulate(Telemetry::VIDEO_AS_CONTENT_SOURCE, 4); } else { // 5 = createPattern_INVISIBLE Telemetry::Accumulate(Telemetry::VIDEO_AS_CONTENT_SOURCE, 5); - if (IsInUncomposedDoc()) { + if (IsInComposedDoc()) { // 4 = createPattern_IN_TREE Telemetry::Accumulate( Telemetry::VIDEO_AS_CONTENT_SOURCE_IN_TREE_OR_NOT, 4); } else { // 5 = createPattern_NOT_IN_TREE @@ -7750,11 +7750,11 @@ Telemetry::Accumulate(Telemetry::VIDEO_AS_CONTENT_SOURCE, 6); } else { // 7 = createImageBitmap_INVISIBLE Telemetry::Accumulate(Telemetry::VIDEO_AS_CONTENT_SOURCE, 7); - if (IsInUncomposedDoc()) { + if (IsInComposedDoc()) { // 6 = createImageBitmap_IN_TREE Telemetry::Accumulate( Telemetry::VIDEO_AS_CONTENT_SOURCE_IN_TREE_OR_NOT, 6); } else { // 7 = createImageBitmap_NOT_IN_TREE @@ -7770,11 +7770,11 @@ Telemetry::Accumulate(Telemetry::VIDEO_AS_CONTENT_SOURCE, 8); } else { // 9 = captureStream_INVISIBLE Telemetry::Accumulate(Telemetry::VIDEO_AS_CONTENT_SOURCE, 9); - if (IsInUncomposedDoc()) { + if (IsInComposedDoc()) { // 8 = captureStream_IN_TREE Telemetry::Accumulate( Telemetry::VIDEO_AS_CONTENT_SOURCE_IN_TREE_OR_NOT, 8); } else { // 9 = captureStream_NOT_IN_TREE @@ -7795,11 +7795,11 @@ if (!isVisible) { LOG(LogLevel::Debug, ("%p Log VIDEO_AS_CONTENT_SOURCE_IN_TREE_OR_NOT: inTree = %u, API: " "'%d' and 'All'", this, - IsInUncomposedDoc(), + IsInComposedDoc(), static_cast(aAPI))); } } void @@ -7922,11 +7922,11 @@ void HTMLMediaElement::NotifyDecoderActivityChanges() const { if (mDecoder) { mDecoder->NotifyOwnerActivityChanged( - !IsHidden(), mVisibilityState, IsInUncomposedDoc()); + !IsHidden(), mVisibilityState, IsInComposedDoc()); } } nsIDocument* HTMLMediaElement::GetDocument() const diff --git a/dom/html/nsHTMLDocument.cpp b/dom/html/nsHTMLDocument.cpp --- a/dom/html/nsHTMLDocument.cpp +++ b/dom/html/nsHTMLDocument.cpp @@ -1337,10 +1337,18 @@ aError.Throw(NS_ERROR_DOM_SECURITY_ERR); return nullptr; } + // At this point we know this is a valid-enough document.open() call + // and not a no-op. Increment our use counters. + SetDocumentAndPageUseCounter(eUseCounter_custom_DocumentOpen); + bool isReplace = aReplace.LowerCaseEqualsLiteral("replace"); + if (isReplace) { + SetDocumentAndPageUseCounter(eUseCounter_custom_DocumentOpenReplace); + } + // Stop current loads targeted at the window this document is in. if (mScriptGlobalObject) { nsCOMPtr cv; shell->GetContentViewer(getter_AddRefs(cv)); @@ -1565,12 +1573,11 @@ // Now check whether we were opened with a "replace" argument. If // so, we need to tell the docshell to not create a new history // entry for this load. Otherwise, make sure that we're doing a normal load, // not whatever type of load was previously done on this docshell. - shell->SetLoadType(aReplace.LowerCaseEqualsLiteral("replace") ? - LOAD_NORMAL_REPLACE : LOAD_NORMAL); + shell->SetLoadType(isReplace ? LOAD_NORMAL_REPLACE : LOAD_NORMAL); nsCOMPtr cv; shell->GetContentViewer(getter_AddRefs(cv)); if (cv) { cv->LoadStart(this); diff --git a/dom/media/ADTSDecoder.h b/dom/media/ADTSDecoder.h --- a/dom/media/ADTSDecoder.h +++ b/dom/media/ADTSDecoder.h @@ -5,21 +5,27 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #ifndef ADTS_DECODER_H_ #define ADTS_DECODER_H_ +#include "mozilla/UniquePtr.h" +#include "nsTArray.h" + namespace mozilla { class MediaContainerType; +class TrackInfo; class ADTSDecoder { public: // Returns true if the ADTS backend is pref'ed on, and we're running on a // platform that is likely to have decoders for the format. static bool IsEnabled(); static bool IsSupportedType(const MediaContainerType& aContainerType); + static nsTArray> GetTracksInfo( + const MediaContainerType& aType); }; } // namespace mozilla #endif // !ADTS_DECODER_H_ diff --git a/dom/media/ADTSDecoder.cpp b/dom/media/ADTSDecoder.cpp --- a/dom/media/ADTSDecoder.cpp +++ b/dom/media/ADTSDecoder.cpp @@ -29,6 +29,21 @@ } return false; } +/* static */ nsTArray> +ADTSDecoder::GetTracksInfo(const MediaContainerType& aType) +{ + nsTArray> tracks; + if (!IsSupportedType(aType)) { + return tracks; + } + + tracks.AppendElement( + CreateTrackInfoWithMIMETypeAndContainerTypeExtraParameters( + NS_LITERAL_CSTRING("audio/mp4a-latm"), aType)); + + return tracks; +} + } // namespace mozilla diff --git a/dom/media/Benchmark.h b/dom/media/Benchmark.h --- a/dom/media/Benchmark.h +++ b/dom/media/Benchmark.h @@ -21,24 +21,24 @@ class Benchmark; class BenchmarkPlayback : public QueueObject { friend class Benchmark; - BenchmarkPlayback(Benchmark* aMainThreadState, MediaDataDemuxer* aDemuxer); + BenchmarkPlayback(Benchmark* aGlobalState, MediaDataDemuxer* aDemuxer); void DemuxSamples(); void DemuxNextSample(); - void MainThreadShutdown(); + void GlobalShutdown(); void InitDecoder(TrackInfo&& aInfo); void Output(const MediaDataDecoder::DecodedData& aResults); void Error(const MediaResult& aError); void InputExhausted(); // Shutdown trackdemuxer and demuxer if any and shutdown the task queues. void FinalizeShutdown(); - Atomic mMainThreadState; + Atomic mGlobalState; RefPtr mDecoderTaskQueue; RefPtr mDecoder; // Object only accessed on Thread() @@ -60,39 +60,40 @@ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(Benchmark) struct Parameters { Parameters() - : mFramesToMeasure(-1) + : mFramesToMeasure(UINT32_MAX) , mStartupFrame(1) , mTimeout(TimeDuration::Forever()) { } - Parameters(int32_t aFramesToMeasure, + Parameters(uint32_t aFramesToMeasure, uint32_t aStartupFrame, - int32_t aStopAtFrame, + uint32_t aStopAtFrame, const TimeDuration& aTimeout) : mFramesToMeasure(aFramesToMeasure) , mStartupFrame(aStartupFrame) , mStopAtFrame(Some(aStopAtFrame)) , mTimeout(aTimeout) { } - const int32_t mFramesToMeasure; + const uint32_t mFramesToMeasure; const uint32_t mStartupFrame; - const Maybe mStopAtFrame; + const Maybe mStopAtFrame; const TimeDuration mTimeout; }; typedef MozPromise BenchmarkPromise; explicit Benchmark(MediaDataDemuxer* aDemuxer, const Parameters& aParameters = Parameters()); RefPtr Run(); + // Must be called on the main thread. static void Init(); private: friend class BenchmarkPlayback; virtual ~Benchmark(); @@ -106,11 +107,11 @@ }; class VP9Benchmark { public: - static bool IsVP9DecodeFast(); + static bool IsVP9DecodeFast(bool aDefault = false); static const char* sBenchmarkFpsPref; static const char* sBenchmarkFpsVersionCheck; static const uint32_t sBenchmarkVersionID; static bool sHasRunTest; }; diff --git a/dom/media/Benchmark.cpp b/dom/media/Benchmark.cpp --- a/dom/media/Benchmark.cpp +++ b/dom/media/Benchmark.cpp @@ -13,10 +13,12 @@ #include "WebMDemuxer.h" #include "gfxPrefs.h" #include "mozilla/AbstractThread.h" #include "mozilla/Preferences.h" #include "mozilla/SharedThreadPool.h" +#include "mozilla/StaticMutex.h" +#include "mozilla/StaticPrefs.h" #include "mozilla/SystemGroup.h" #include "mozilla/TaskQueue.h" #include "mozilla/Telemetry.h" #include "mozilla/dom/ContentChild.h" #include "mozilla/gfx/gfxVars.h" @@ -37,37 +39,39 @@ const char* VP9Benchmark::sBenchmarkFpsVersionCheck = "media.benchmark.vp9.versioncheck"; bool VP9Benchmark::sHasRunTest = false; // static bool -VP9Benchmark::IsVP9DecodeFast() +VP9Benchmark::IsVP9DecodeFast(bool aDefault) { - MOZ_ASSERT(NS_IsMainThread()); - // Disable VP9 estimizer on Mac, see bug 1400787. #if defined(MOZ_WIDGET_ANDROID) || defined(MOZ_APPLEMEDIA) return false; #else - bool hasPref = Preferences::HasUserValue(sBenchmarkFpsPref); - uint32_t hadRecentUpdate = Preferences::GetUint(sBenchmarkFpsVersionCheck, 0U); - - if (!sHasRunTest && (!hasPref || hadRecentUpdate != sBenchmarkVersionID)) { + static StaticMutex sMutex; + uint32_t decodeFps = StaticPrefs::MediaBenchmarkVp9Fps(); + uint32_t hadRecentUpdate = StaticPrefs::MediaBenchmarkVp9Versioncheck(); + bool needBenchmark; + { + StaticMutexAutoLock lock(sMutex); + needBenchmark = !sHasRunTest && + (decodeFps == 0 || hadRecentUpdate != sBenchmarkVersionID); sHasRunTest = true; + } + if (needBenchmark) { RefPtr demuxer = new WebMDemuxer( new BufferMediaResource(sWebMSample, sizeof(sWebMSample))); - RefPtr estimiser = - new Benchmark(demuxer, - { - Preferences::GetInt("media.benchmark.frames", 300), // frames to measure - 1, // start benchmarking after decoding this frame. - 8, // loop after decoding that many frames. - TimeDuration::FromMilliseconds( - Preferences::GetUint("media.benchmark.timeout", 1000)) - }); + RefPtr estimiser = new Benchmark( + demuxer, + { StaticPrefs::MediaBenchmarkFrames(), // frames to measure + 1, // start benchmarking after decoding this frame. + 8, // loop after decoding that many frames. + TimeDuration::FromMilliseconds(StaticPrefs::MediaBenchmarkTimeout()) }); estimiser->Run()->Then( - SystemGroup::AbstractMainThreadFor(TaskCategory::Other), __func__, + AbstractThread::MainThread(), + __func__, [](uint32_t aDecodeFps) { if (XRE_IsContentProcess()) { dom::ContentChild* contentChild = dom::ContentChild::GetSingleton(); if (contentChild) { contentChild->SendNotifyBenchmarkResult(NS_LITERAL_STRING("VP9"), @@ -75,52 +79,49 @@ } } else { Preferences::SetUint(sBenchmarkFpsPref, aDecodeFps); Preferences::SetUint(sBenchmarkFpsVersionCheck, sBenchmarkVersionID); } - Telemetry::Accumulate(Telemetry::HistogramID::VIDEO_VP9_BENCHMARK_FPS, aDecodeFps); + Telemetry::Accumulate(Telemetry::HistogramID::VIDEO_VP9_BENCHMARK_FPS, + aDecodeFps); }, - []() { }); + []() {}); } - if (!hasPref) { - return false; + if (decodeFps == 0) { + return aDefault; } - uint32_t decodeFps = Preferences::GetUint(sBenchmarkFpsPref); - uint32_t threshold = - Preferences::GetUint("media.benchmark.vp9.threshold", 150); - - return decodeFps >= threshold; + return decodeFps >= StaticPrefs::MediaBenchmarkVp9Threshold(); #endif } Benchmark::Benchmark(MediaDataDemuxer* aDemuxer, const Parameters& aParameters) - : QueueObject(AbstractThread::MainThread()) + : QueueObject(new TaskQueue(GetMediaThreadPool(MediaThreadType::PLAYBACK), + "Benchmark::QueueObject")) , mParameters(aParameters) , mKeepAliveUntilComplete(this) , mPlaybackState(this, aDemuxer) { MOZ_COUNT_CTOR(Benchmark); - MOZ_ASSERT(Thread(), "Must be run in task queue"); } Benchmark::~Benchmark() { MOZ_COUNT_DTOR(Benchmark); } RefPtr Benchmark::Run() { - MOZ_ASSERT(OnThread()); - - RefPtr p = mPromise.Ensure(__func__); RefPtr self = this; - mPlaybackState.Dispatch(NS_NewRunnableFunction( - "Benchmark::Run", [self]() { self->mPlaybackState.DemuxSamples(); })); - return p; + return InvokeAsync(Thread(), __func__, [self] { + RefPtr p = self->mPromise.Ensure(__func__); + self->mPlaybackState.Dispatch(NS_NewRunnableFunction( + "Benchmark::Run", [self]() { self->mPlaybackState.DemuxSamples(); })); + return p; + }); } void Benchmark::ReturnResult(uint32_t aDecodeFps) { @@ -151,34 +152,33 @@ MOZ_ASSERT(NS_IsMainThread()); gfxVars::Initialize(); gfxPrefs::GetSingleton(); } -BenchmarkPlayback::BenchmarkPlayback(Benchmark* aMainThreadState, +BenchmarkPlayback::BenchmarkPlayback(Benchmark* aGlobalState, MediaDataDemuxer* aDemuxer) : QueueObject(new TaskQueue( GetMediaThreadPool(MediaThreadType::PLAYBACK), "BenchmarkPlayback::QueueObject")) - , mMainThreadState(aMainThreadState) + , mGlobalState(aGlobalState) , mDecoderTaskQueue(new TaskQueue( GetMediaThreadPool(MediaThreadType::PLATFORM_DECODER), "BenchmarkPlayback::mDecoderTaskQueue")) , mDemuxer(aDemuxer) , mSampleIndex(0) , mFrameCount(0) , mFinished(false) , mDrained(false) { - MOZ_ASSERT(static_cast(mMainThreadState)->OnThread()); } void BenchmarkPlayback::DemuxSamples() { MOZ_ASSERT(OnThread()); - RefPtr ref(mMainThreadState); + RefPtr ref(mGlobalState); mDemuxer->Init()->Then( Thread(), __func__, [this, ref](nsresult aResult) { MOZ_ASSERT(OnThread()); if (mDemuxer->GetNumberTracks(TrackInfo::kVideoTrack)) { @@ -200,18 +200,18 @@ void BenchmarkPlayback::DemuxNextSample() { MOZ_ASSERT(OnThread()); - RefPtr ref(mMainThreadState); + RefPtr ref(mGlobalState); RefPtr promise = mTrackDemuxer->GetSamples(); promise->Then( Thread(), __func__, [this, ref](RefPtr aHolder) { mSamples.AppendElements(std::move(aHolder->mSamples)); if (ref->mParameters.mStopAtFrame && - mSamples.Length() == (size_t)ref->mParameters.mStopAtFrame.ref()) { + mSamples.Length() == ref->mParameters.mStopAtFrame.ref()) { InitDecoder(std::move(*mTrackDemuxer->GetInfo())); } else { Dispatch(NS_NewRunnableFunction("BenchmarkPlayback::DemuxNextSample", [this, ref]() { DemuxNextSample(); })); } @@ -237,11 +237,11 @@ mDecoder = platform->CreateDecoder({ aInfo, mDecoderTaskQueue }); if (!mDecoder) { Error(MediaResult(NS_ERROR_FAILURE, "Failed to create decoder")); return; } - RefPtr ref(mMainThreadState); + RefPtr ref(mGlobalState); mDecoder->Init()->Then( Thread(), __func__, [this, ref](TrackInfo::TrackType aTrackType) { InputExhausted(); }, [this, ref](const MediaResult& aError) { Error(aError); }); } @@ -261,28 +261,28 @@ mTrackDemuxer->BreakCycles(); mTrackDemuxer = nullptr; } mDemuxer = nullptr; - RefPtr ref(mMainThreadState); + RefPtr ref(mGlobalState); Thread()->AsTaskQueue()->BeginShutdown()->Then( ref->Thread(), __func__, [ref]() { ref->Dispose(); }, []() { MOZ_CRASH("not reached"); }); } void -BenchmarkPlayback::MainThreadShutdown() +BenchmarkPlayback::GlobalShutdown() { MOZ_ASSERT(OnThread()); MOZ_ASSERT(!mFinished, "We've already shutdown"); mFinished = true; if (mDecoder) { - RefPtr ref(mMainThreadState); + RefPtr ref(mGlobalState); mDecoder->Flush()->Then( Thread(), __func__, [ref, this]() { mDecoder->Shutdown()->Then( Thread(), __func__, @@ -302,22 +302,23 @@ BenchmarkPlayback::Output(const MediaDataDecoder::DecodedData& aResults) { MOZ_ASSERT(OnThread()); MOZ_ASSERT(!mFinished); - RefPtr ref(mMainThreadState); + RefPtr ref(mGlobalState); mFrameCount += aResults.Length(); if (!mDecodeStartTime && mFrameCount >= ref->mParameters.mStartupFrame) { mDecodeStartTime = Some(TimeStamp::Now()); } TimeStamp now = TimeStamp::Now(); - int32_t frames = mFrameCount - ref->mParameters.mStartupFrame; + uint32_t frames = mFrameCount - ref->mParameters.mStartupFrame; TimeDuration elapsedTime = now - mDecodeStartTime.refOr(now); - if (((frames == ref->mParameters.mFramesToMeasure) && frames > 0) || + if (((frames == ref->mParameters.mFramesToMeasure) && + mFrameCount > ref->mParameters.mStartupFrame && frames > 0) || elapsedTime >= ref->mParameters.mTimeout || mDrained) { uint32_t decodeFps = frames / elapsedTime.ToSeconds(); - MainThreadShutdown(); + GlobalShutdown(); ref->Dispatch( NS_NewRunnableFunction("BenchmarkPlayback::Output", [ref, decodeFps]() { ref->ReturnResult(decodeFps); })); } @@ -326,12 +327,12 @@ void BenchmarkPlayback::Error(const MediaResult& aError) { MOZ_ASSERT(OnThread()); - RefPtr ref(mMainThreadState); - MainThreadShutdown(); + RefPtr ref(mGlobalState); + GlobalShutdown(); ref->Dispatch(NS_NewRunnableFunction( "BenchmarkPlayback::Error", [ref, aError]() { ref->ReturnError(aError); })); } @@ -345,11 +346,11 @@ Error(MediaResult(NS_ERROR_FAILURE, "Nothing left to decode")); return; } RefPtr sample = mSamples[mSampleIndex]; - RefPtr ref(mMainThreadState); + RefPtr ref(mGlobalState); RefPtr p = mDecoder->Decode(sample); mSampleIndex++; if (mSampleIndex == mSamples.Length() && !ref->mParameters.mStopAtFrame) { // Complete current frame decode then drain if still necessary. diff --git a/dom/media/BitReader.h b/dom/media/BitReader.h --- a/dom/media/BitReader.h +++ b/dom/media/BitReader.h @@ -11,16 +11,16 @@ { class BitReader { public: - explicit BitReader(const mozilla::MediaByteBuffer* aBuffer); - BitReader(const mozilla::MediaByteBuffer* aBuffer, size_t aBits); + explicit BitReader(const MediaByteBuffer* aBuffer); + BitReader(const MediaByteBuffer* aBuffer, size_t aBits); BitReader(const uint8_t* aBuffer, size_t aBits); ~BitReader(); uint32_t ReadBits(size_t aNum); - uint32_t ReadBit() { return ReadBits(1); } + bool ReadBit() { return ReadBits(1) != 0; } uint32_t ReadU32() { return ReadBits(32); } uint64_t ReadU64(); // Read the UTF-8 sequence and convert it to its 64-bit UCS-4 encoded form. // Return 0xfffffffffffffff if sequence was invalid. @@ -33,10 +33,13 @@ // Return the number of bits parsed so far; size_t BitCount() const; // Return the number of bits left. size_t BitsLeft() const; + // Return RBSP bit length. + static uint32_t GetBitLength(const MediaByteBuffer* aNAL); + private: void FillReservoir(); const uint8_t* mData; const size_t mOriginalBitSize; size_t mTotalBitsLeft; diff --git a/dom/media/BitReader.cpp b/dom/media/BitReader.cpp --- a/dom/media/BitReader.cpp +++ b/dom/media/BitReader.cpp @@ -155,6 +155,52 @@ mNumBitsLeft = 8 * i; mReservoir <<= 32 - mNumBitsLeft; } +/* static */ uint32_t +BitReader::GetBitLength(const mozilla::MediaByteBuffer* aNAL) +{ + size_t size = aNAL->Length(); + + while (size > 0 && aNAL->ElementAt(size - 1) == 0) { + size--; + } + + if (!size) { + return 0; + } + + if (size > UINT32_MAX / 8) { + // We can't represent it, we'll use as much as we can. + return UINT32_MAX; + } + + uint8_t v = aNAL->ElementAt(size - 1); + size *= 8; + + // Remove the stop bit and following trailing zeros. + if (v) { + // Count the consecutive zero bits (trailing) on the right by binary search. + // Adapted from Matt Whitlock algorithm to only bother with 8 bits integers. + uint32_t c; + if (v & 1) { + // Special case for odd v (assumed to happen half of the time). + c = 0; + } else { + c = 1; + if ((v & 0xf) == 0) { + v >>= 4; + c += 4; + } + if ((v & 0x3) == 0) { + v >>= 2; + c += 2; + } + c -= v & 0x1; + } + size -= c + 1; + } + return size; +} + } // namespace mozilla diff --git a/dom/media/BitWriter.h b/dom/media/BitWriter.h new file mode 100644 --- /dev/null +++ b/dom/media/BitWriter.h @@ -0,0 +1,43 @@ +/* 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 BIT_WRITER_H_ +#define BIT_WRITER_H_ + +#include "mozilla/RefPtr.h" + +namespace mozilla +{ + +class MediaByteBuffer; + +class BitWriter +{ +public: + explicit BitWriter(MediaByteBuffer* aBuffer); + virtual ~BitWriter(); + void WriteBits(uint64_t aValue, size_t aBits); + void WriteBit(bool aValue) { WriteBits(aValue, 1); } + void WriteU8(uint8_t aValue) { WriteBits(aValue, 8); } + void WriteU32(uint32_t aValue) { WriteBits(aValue, 32); } + void WriteU64(uint64_t aValue) { WriteBits(aValue, 64); } + + // Write unsigned integer into Exp-Golomb-coded. 2^32-2 at most + void WriteUE(uint32_t aValue); + + // Write RBSP trailing bits. + void CloseWithRbspTrailing(); + + // Return the number of bits written so far; + size_t BitCount() const { return mPosition * 8 + mBitIndex; } + +private: + RefPtr mBuffer; + size_t mPosition = 0; + uint8_t mBitIndex = 0; +}; + +} // namespace mozilla + +#endif // BIT_WRITER_H_ diff --git a/dom/media/BitWriter.cpp b/dom/media/BitWriter.cpp new file mode 100644 --- /dev/null +++ b/dom/media/BitWriter.cpp @@ -0,0 +1,96 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "BitWriter.h" +#include "MediaData.h" +#include "mozilla/MathAlgorithms.h" + +namespace mozilla +{ + +constexpr uint8_t golombLen[256] = { + 1, 3, 3, 5, 5, 5, 5, 7, 7, 7, 7, 7, 7, 7, 7, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 11, 11, 11, 11, 11, 11, 11, + 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, + 11, 11, 11, 11, 11, 11, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, + 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, + 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, + 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 15, 15, 15, 15, 15, 15, + 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, + 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, + 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, + 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, + 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, + 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, + 15, 15, 15, 15, 15, 15, 15, 15, 17, +}; + +BitWriter::BitWriter(MediaByteBuffer* aBuffer) + : mBuffer(aBuffer) +{ +} + +BitWriter::~BitWriter() {} + +void +BitWriter::WriteBits(uint64_t aValue, size_t aBits) +{ + MOZ_ASSERT(aBits <= sizeof(uint64_t) * 8); + + while (aBits) { + if (mBitIndex == 0) { + mBuffer->AppendElement(0); + } + + const uint8_t clearMask = ~(~0 << (8 - mBitIndex)); + uint8_t mask = 0; + + if (mBitIndex + aBits > 8) { + // Not enough bits in the current byte to write all the bits + // required, we'll process what we can and continue with the left over. + const uint8_t leftOverBits = mBitIndex + aBits - 8; + const uint64_t leftOver = aValue & (~uint64_t(0) >> (8 - mBitIndex)); + mask = aValue >> leftOverBits; + + mBitIndex = 8; + aValue = leftOver; + aBits = leftOverBits; + } else { + const uint8_t offset = 8 - mBitIndex - aBits; + mask = aValue << offset; + + mBitIndex += aBits; + aBits = 0; + } + + mBuffer->ElementAt(mPosition) |= mask & clearMask; + + if (mBitIndex == 8) { + mPosition++; + mBitIndex = 0; + } + } +} + +void +BitWriter::WriteUE(uint32_t aValue) +{ + MOZ_ASSERT(aValue <= (UINT32_MAX - 1)); + + if (aValue < 256) { + WriteBits(aValue + 1, golombLen[aValue]); + } else { + const uint32_t e = FloorLog2(aValue + 1); + WriteBits(aValue + 1, e * 2 + 1); + } +} + +void +BitWriter::CloseWithRbspTrailing() +{ + WriteBit(true); + WriteBits(0, (8 - mBitIndex) & 7); +} + +} // namespace mozilla diff --git a/dom/media/DecoderTraits.h b/dom/media/DecoderTraits.h --- a/dom/media/DecoderTraits.h +++ b/dom/media/DecoderTraits.h @@ -5,18 +5,21 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #ifndef DecoderTraits_h_ #define DecoderTraits_h_ +#include "mozilla/UniquePtr.h" #include "nsStringFwd.h" +#include "nsTArray.h" namespace mozilla { class DecoderDoctorDiagnostics; class MediaContainerType; struct MediaFormatReaderInit; class MediaFormatReader; +class TrackInfo; enum CanPlayStatus { CANPLAY_NO, CANPLAY_MAYBE, CANPLAY_YES @@ -55,10 +58,14 @@ // Returns true if aType is matroska type. static bool IsMatroskaType(const MediaContainerType& aType); static bool IsSupportedType(const MediaContainerType& aType); + + // Returns an array of all TrackInfo objects described by this type. + static nsTArray> GetTracksInfo( + const MediaContainerType& aType); }; } // namespace mozilla #endif diff --git a/dom/media/DecoderTraits.cpp b/dom/media/DecoderTraits.cpp --- a/dom/media/DecoderTraits.cpp +++ b/dom/media/DecoderTraits.cpp @@ -96,12 +96,12 @@ } if (WaveDecoder::IsSupportedType(MediaContainerType(mimeType))) { if (WaveDecoder::IsSupportedType(aType)) { return CANPLAY_YES; } - // We can only reach this position if a particular codec was requested, - // ogg is supported and working: the codec must be invalid. + // We can only reach this position if a particular codec was requested, wave + // is supported and working: the codec must be invalid or not supported. return CANPLAY_NO; } if (WebMDecoder::IsSupportedType(mimeType)) { if (WebMDecoder::IsSupportedType(aType)) { return CANPLAY_YES; @@ -119,18 +119,33 @@ // We can only reach this position if a particular codec was requested, // fmp4 is supported and working: the codec must be invalid. return CANPLAY_NO; } #endif - if (MP3Decoder::IsSupportedType(aType)) { - return CANPLAY_YES; + if (MP3Decoder::IsSupportedType(mimeType)) { + if (MP3Decoder::IsSupportedType(aType)) { + return CANPLAY_YES; + } + // We can only reach this position if a particular codec was requested, + // mp3 is supported and working: the codec must be invalid. + return CANPLAY_NO; } - if (ADTSDecoder::IsSupportedType(aType)) { - return CANPLAY_YES; + if (ADTSDecoder::IsSupportedType(mimeType)) { + if (ADTSDecoder::IsSupportedType(aType)) { + return CANPLAY_YES; + } + // We can only reach this position if a particular codec was requested, + // adts is supported and working: the codec must be invalid. + return CANPLAY_NO; } - if (FlacDecoder::IsSupportedType(aType)) { - return CANPLAY_YES; + if (FlacDecoder::IsSupportedType(mimeType)) { + if (FlacDecoder::IsSupportedType(aType)) { + return CANPLAY_YES; + } + // We can only reach this position if a particular codec was requested, + // flac is supported and working: the codec must be invalid. + return CANPLAY_NO; } return CANPLAY_MAYBE; } @@ -322,6 +337,38 @@ HLSDecoder::IsSupportedType(*type) || #endif false; } +/* static */ nsTArray> +DecoderTraits::GetTracksInfo(const MediaContainerType& aType) +{ + // Container type with just the MIME type/subtype, no codecs. + const MediaContainerType mimeType(aType.Type()); + + if (OggDecoder::IsSupportedType(mimeType)) { + return OggDecoder::GetTracksInfo(aType); + } + if (WaveDecoder::IsSupportedType(mimeType)) { + return WaveDecoder::GetTracksInfo(aType); + } +#ifdef MOZ_FMP4 + if (MP4Decoder::IsSupportedType(mimeType, nullptr)) { + return MP4Decoder::GetTracksInfo(aType); + } +#endif + if (WebMDecoder::IsSupportedType(mimeType)) { + return WebMDecoder::GetTracksInfo(aType); + } + if (MP3Decoder::IsSupportedType(mimeType)) { + return MP3Decoder::GetTracksInfo(aType); + } + if (ADTSDecoder::IsSupportedType(mimeType)) { + return ADTSDecoder::GetTracksInfo(aType); + } + if (FlacDecoder::IsSupportedType(mimeType)) { + return FlacDecoder::GetTracksInfo(aType); + } + return nsTArray>(); +} + } // namespace mozilla diff --git a/dom/media/MediaFormatReader.cpp b/dom/media/MediaFormatReader.cpp --- a/dom/media/MediaFormatReader.cpp +++ b/dom/media/MediaFormatReader.cpp @@ -4,11 +4,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/. */ #include "MediaFormatReader.h" -#include "AutoTaskQueue.h" #include "MediaData.h" #include "MediaInfo.h" #include "VideoFrameContainer.h" #include "VideoUtils.h" #include "mozilla/AbstractThread.h" @@ -16,10 +15,11 @@ #include "mozilla/ClearOnShutdown.h" #include "mozilla/NotNull.h" #include "mozilla/Preferences.h" #include "mozilla/SharedThreadPool.h" #include "mozilla/StaticPrefs.h" +#include "mozilla/TaskQueue.h" #include "mozilla/Telemetry.h" #include "mozilla/Unused.h" #include "nsContentUtils.h" #include "nsPrintfCString.h" #ifdef MOZ_WIDGET_ANDROID @@ -919,13 +919,13 @@ using TrackType = TrackInfo::TrackType; class Wrapper; public: explicit DemuxerProxy(MediaDataDemuxer* aDemuxer) - : mTaskQueue(new AutoTaskQueue( - GetMediaThreadPool(MediaThreadType::PLATFORM_DECODER), - "DemuxerProxy::mTaskQueue")) + : mTaskQueue( + new TaskQueue(GetMediaThreadPool(MediaThreadType::PLATFORM_DECODER), + "DemuxerProxy::mTaskQueue")) , mData(new Data(aDemuxer)) { MOZ_COUNT_CTOR(DemuxerProxy); } @@ -1014,11 +1014,11 @@ return mData->mShouldComputeStartTime; } private: - const RefPtr mTaskQueue; + const RefPtr mTaskQueue; struct Data { NS_INLINE_DECL_THREADSAFE_REFCOUNTING(Data) explicit Data(MediaDataDemuxer* aDemuxer) @@ -1047,11 +1047,11 @@ }; class MediaFormatReader::DemuxerProxy::Wrapper : public MediaTrackDemuxer { public: - Wrapper(MediaTrackDemuxer* aTrackDemuxer, AutoTaskQueue* aTaskQueue) + Wrapper(MediaTrackDemuxer* aTrackDemuxer, TaskQueue* aTaskQueue) : mMutex("TrackDemuxer Mutex") , mTaskQueue(aTaskQueue) , mGetSamplesMayBlock(aTrackDemuxer->GetSamplesMayBlock()) , mInfo(aTrackDemuxer->GetInfo()) , mTrackDemuxer(aTrackDemuxer) @@ -1164,11 +1164,11 @@ void BreakCycles() override { } private: Mutex mMutex; - const RefPtr mTaskQueue; + const RefPtr mTaskQueue; const bool mGetSamplesMayBlock; const UniquePtr mInfo; // mTrackDemuxer is only ever accessed on demuxer's task queue. RefPtr mTrackDemuxer; // All following members are protected by mMutex @@ -1218,11 +1218,11 @@ MediaFormatReader::DemuxerProxy::Init() { using InitPromise = MediaDataDemuxer::InitPromise; RefPtr data = mData; - RefPtr taskQueue = mTaskQueue; + RefPtr taskQueue = mTaskQueue; return InvokeAsync(mTaskQueue, __func__, [data, taskQueue]() { if (!data->mDemuxer) { return InitPromise::CreateAndReject( NS_ERROR_DOM_MEDIA_CANCELED, __func__); diff --git a/dom/media/MediaMIMETypes.h b/dom/media/MediaMIMETypes.h --- a/dom/media/MediaMIMETypes.h +++ b/dom/media/MediaMIMETypes.h @@ -11,10 +11,15 @@ #include "mozilla/Maybe.h" #include "nsString.h" namespace mozilla { +namespace dom { +struct AudioConfiguration; +struct VideoConfiguration; +} + // Class containing pointing at a media MIME "type/subtype" string literal. // See IsMediaMIMEType for restrictions. // Mainly used to help construct a MediaMIMEType through the statically-checked // MEDIAMIMETYPE macro, or to compare a MediaMIMEType to a literal. class DependentMediaMIMEType @@ -173,44 +178,70 @@ const MediaCodecs& Codecs() const { return mCodecs; } // Sizes and rates. Maybe GetWidth() const { return GetMaybeNumber(mWidth); } Maybe GetHeight() const { return GetMaybeNumber(mHeight); } - Maybe GetFramerate() const { return GetMaybeNumber(mFramerate); } + Maybe GetFramerate() const { return GetMaybeNumber(mFramerate); } Maybe GetBitrate() const { return GetMaybeNumber(mBitrate); } + Maybe GetChannels() const { return GetMaybeNumber(mChannels); } + Maybe GetSamplerate() const { return GetMaybeNumber(mSamplerate); } // Original string. Note that "type/subtype" may not be lowercase, // use Type().AsString() instead to get the normalized "type/subtype". const nsCString& OriginalString() const { return mOriginalString; } size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const; + // aFrac is either a floating-point number or a fraction made of two + // floating-point numbers. + static Maybe ComputeFractionalString(const nsAString& aFrac); + private: friend Maybe MakeMediaExtendedMIMEType(const nsAString& aType); + friend Maybe MakeMediaExtendedMIMEType( + const dom::VideoConfiguration& aConfig); + friend Maybe MakeMediaExtendedMIMEType( + const dom::AudioConfiguration& aConfig); + MediaExtendedMIMEType(const nsACString& aOriginalString, const nsACString& aMIMEType, bool aHaveCodecs, const nsAString& aCodecs, int32_t aWidth, int32_t aHeight, - int32_t aFramerate, int32_t aBitrate); + double aFramerate, int32_t aBitrate); + MediaExtendedMIMEType(const nsACString& aOriginalString, + const nsACString& aMIMEType, + bool aHaveCodecs, const nsAString& aCodecs, + int32_t aChannels, int32_t aSamplerate, + int32_t aBitrate); - Maybe GetMaybeNumber(int32_t aNumber) const + template + Maybe GetMaybeNumber(T aNumber) const { - return (aNumber < 0) ? Maybe(Nothing()) : Some(int32_t(aNumber)); + return (aNumber < 0) ? Maybe(Nothing()) : Some(T(aNumber)); } nsCString mOriginalString; // Original full string. MediaMIMEType mMIMEType; // MIME type/subtype. bool mHaveCodecs = false; // If false, mCodecs must be empty. MediaCodecs mCodecs; + // For video int32_t mWidth = -1; // -1 if not provided. int32_t mHeight = -1; // -1 if not provided. - int32_t mFramerate = -1; // -1 if not provided. + double mFramerate = -1; // -1 if not provided. + // For audio + int32_t mChannels = -1; // -1 if not provided. + int32_t mSamplerate = -1; // -1 if not provided. + // For both audio and video. int32_t mBitrate = -1; // -1 if not provided. }; Maybe MakeMediaExtendedMIMEType(const nsAString& aType); Maybe MakeMediaExtendedMIMEType(const nsACString& aType); Maybe MakeMediaExtendedMIMEType(const char* aType); +Maybe +MakeMediaExtendedMIMEType(const dom::VideoConfiguration& aConfig); +Maybe +MakeMediaExtendedMIMEType(const dom::AudioConfiguration& aConfig); } // namespace mozilla #endif // MediaMIMETypes_h_ diff --git a/dom/media/MediaMIMETypes.cpp b/dom/media/MediaMIMETypes.cpp --- a/dom/media/MediaMIMETypes.cpp +++ b/dom/media/MediaMIMETypes.cpp @@ -5,10 +5,11 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "MediaMIMETypes.h" #include "nsContentTypeParser.h" +#include "mozilla/dom/MediaCapabilitiesBinding.h" namespace mozilla { template static bool @@ -50,12 +51,10 @@ } Maybe MakeMediaMIMEType(const nsAString& aType) { - MOZ_ASSERT(NS_IsMainThread()); - nsContentTypeParser parser(aType); nsAutoString mime; nsresult rv = parser.GetType(mime); if (!NS_SUCCEEDED(rv) || mime.IsEmpty()) { return Nothing(); @@ -146,12 +145,14 @@ MediaExtendedMIMEType::MediaExtendedMIMEType(const nsACString& aOriginalString, const nsACString& aMIMEType, bool aHaveCodecs, const nsAString& aCodecs, - int32_t aWidth, int32_t aHeight, - int32_t aFramerate, int32_t aBitrate) + int32_t aWidth, + int32_t aHeight, + double aFramerate, + int32_t aBitrate) : mOriginalString(aOriginalString) , mMIMEType(aMIMEType) , mHaveCodecs(aHaveCodecs) , mCodecs(aCodecs) , mWidth(aWidth) @@ -159,10 +160,27 @@ , mFramerate(aFramerate) , mBitrate(aBitrate) { } +MediaExtendedMIMEType::MediaExtendedMIMEType(const nsACString& aOriginalString, + const nsACString& aMIMEType, + bool aHaveCodecs, + const nsAString& aCodecs, + int32_t aChannels, + int32_t aSamplerate, + int32_t aBitrate) + : mOriginalString(aOriginalString) + , mMIMEType(aMIMEType) + , mHaveCodecs(aHaveCodecs) + , mCodecs(aCodecs) + , mChannels(aChannels) + , mSamplerate(aSamplerate) + , mBitrate(aBitrate) +{ +} + MediaExtendedMIMEType::MediaExtendedMIMEType(const MediaMIMEType& aType) : mOriginalString(aType.AsString()) , mMIMEType(aType) { } @@ -171,15 +189,50 @@ : mOriginalString(aType.AsString()) , mMIMEType(std::move(aType)) { } +/* static */ Maybe +MediaExtendedMIMEType::ComputeFractionalString(const nsAString& aFrac) +{ + nsAutoString frac(aFrac); + nsresult error; + double result = frac.ToDouble(&error); + if (NS_SUCCEEDED(error)) { + if (result <= 0) { + return Nothing(); + } + return Some(result); + } + + int32_t slashPos = frac.Find(NS_LITERAL_STRING("/")); + if (slashPos == kNotFound) { + return Nothing(); + } + + nsAutoString firstPart(Substring(frac, 0, slashPos - 1)); + double first = firstPart.ToDouble(&error); + if (NS_FAILED(error)) { + return Nothing(); + } + nsAutoString secondPart(Substring(frac, slashPos + 1)); + double second = secondPart.ToDouble(&error); + if (NS_FAILED(error) || second == 0) { + return Nothing(); + } + + result = first / second; + if (result <= 0) { + return Nothing(); + } + + return Some(result); +} + Maybe MakeMediaExtendedMIMEType(const nsAString& aType) { - MOZ_ASSERT(NS_IsMainThread()); - nsContentTypeParser parser(aType); nsAutoString mime; nsresult rv = parser.GetType(mime); if (!NS_SUCCEEDED(rv) || mime.IsEmpty()) { return Nothing(); @@ -194,20 +247,113 @@ rv = parser.GetParameter("codecs", codecs); bool haveCodecs = NS_SUCCEEDED(rv); int32_t width = GetParameterAsNumber(parser, "width", -1); int32_t height = GetParameterAsNumber(parser, "height", -1); - int32_t framerate = GetParameterAsNumber(parser, "framerate", -1); + double framerate = GetParameterAsNumber(parser, "framerate", -1); int32_t bitrate = GetParameterAsNumber(parser, "bitrate", -1); return Some(MediaExtendedMIMEType(NS_ConvertUTF16toUTF8(aType), mime8, haveCodecs, codecs, width, height, framerate, bitrate)); } +Maybe +MakeMediaExtendedMIMEType(const dom::VideoConfiguration& aConfig) +{ + MOZ_ASSERT(aConfig.mContentType.WasPassed() && + aConfig.mFramerate.WasPassed() && + aConfig.mWidth.WasPassed() && + aConfig.mHeight.WasPassed() && + aConfig.mBitrate.WasPassed(), + "All dictionary members must be present"); + if (aConfig.mContentType.Value().IsEmpty()) { + return Nothing(); + } + nsContentTypeParser parser(aConfig.mContentType.Value()); + nsAutoString mime; + nsresult rv = parser.GetType(mime); + if (!NS_SUCCEEDED(rv) || mime.IsEmpty()) { + return Nothing(); + } + + NS_ConvertUTF16toUTF8 mime8{ mime }; + if (!IsMediaMIMEType(mime8)) { + return Nothing(); + } + + nsAutoString codecs; + rv = parser.GetParameter("codecs", codecs); + bool haveCodecs = NS_SUCCEEDED(rv); + + auto framerate = + MediaExtendedMIMEType::ComputeFractionalString(aConfig.mFramerate.Value()); + if (!framerate) { + return Nothing(); + } + + return Some( + MediaExtendedMIMEType(NS_ConvertUTF16toUTF8(aConfig.mContentType.Value()), + mime8, + haveCodecs, + codecs, + aConfig.mWidth.Value(), + aConfig.mHeight.Value(), + framerate.ref(), + aConfig.mBitrate.Value())); +} + +Maybe +MakeMediaExtendedMIMEType(const dom::AudioConfiguration& aConfig) +{ + if (!aConfig.mContentType.WasPassed() || + aConfig.mContentType.Value().IsEmpty()) { + return Nothing(); + } + nsContentTypeParser parser(aConfig.mContentType.Value()); + nsAutoString mime; + nsresult rv = parser.GetType(mime); + if (!NS_SUCCEEDED(rv) || mime.IsEmpty()) { + return Nothing(); + } + + NS_ConvertUTF16toUTF8 mime8{ mime }; + if (!IsMediaMIMEType(mime8)) { + return Nothing(); + } + + nsAutoString codecs; + rv = parser.GetParameter("codecs", codecs); + bool haveCodecs = NS_SUCCEEDED(rv); + + int32_t channels = 2; // use a stereo config if not known. + if (aConfig.mChannels.WasPassed()) { + // A channels string was passed. Make sure it is valid. + nsresult error; + double value = aConfig.mChannels.Value().ToDouble(&error); + if (NS_FAILED(error)) { + return Nothing(); + } + // Value is a channel configuration such as 5.1. We want to treat this as 6. + channels = value; + double fp = value - channels; + // round up as .1 and .2 aren't exactly expressible in binary. + channels += (fp * 10) + .5; + } + + return Some(MediaExtendedMIMEType( + NS_ConvertUTF16toUTF8(aConfig.mContentType.Value()), + mime8, + haveCodecs, + codecs, + channels, + aConfig.mSamplerate.WasPassed() ? aConfig.mSamplerate.Value() : 48000, + aConfig.mBitrate.WasPassed() ? aConfig.mBitrate.Value() : 131072)); +} + size_t MediaExtendedMIMEType::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const { return mOriginalString.SizeOfExcludingThisIfUnshared(aMallocSizeOf) + mMIMEType.SizeOfExcludingThis(aMallocSizeOf) diff --git a/dom/media/MediaRecorder.cpp b/dom/media/MediaRecorder.cpp --- a/dom/media/MediaRecorder.cpp +++ b/dom/media/MediaRecorder.cpp @@ -1528,10 +1528,14 @@ "vp8", "vp8.0", // no VP9 yet nullptr, }; +static char const *const gWebMAudioEncoderCodecs[4] = { + "opus", + nullptr, +}; static char const *const gOggAudioEncoderCodecs[2] = { "opus", // we could support vorbis here too, but don't nullptr, }; @@ -1576,13 +1580,18 @@ if (MediaDecoder::IsOggEnabled() && MediaDecoder::IsOpusEnabled()) { codeclist = gOggAudioEncoderCodecs; } } #ifdef MOZ_WEBM_ENCODER - else if (mimeType.EqualsLiteral(VIDEO_WEBM) && + else if ((mimeType.EqualsLiteral(VIDEO_WEBM) || + mimeType.EqualsLiteral(AUDIO_WEBM)) && MediaEncoder::IsWebMEncoderEnabled()) { - codeclist = gWebMVideoEncoderCodecs; + if (mimeType.EqualsLiteral(AUDIO_WEBM)) { + codeclist = gWebMAudioEncoderCodecs; + } else { + codeclist = gWebMVideoEncoderCodecs; + } } #endif // codecs don't matter if we don't support the container if (!codeclist) { diff --git a/dom/media/VideoUtils.h b/dom/media/VideoUtils.h --- a/dom/media/VideoUtils.h +++ b/dom/media/VideoUtils.h @@ -236,12 +236,13 @@ // See http://blog.pearce.org.nz/2013/11/what-does-h264avc1-codecs-parameters.html // for more details. // Returns false on failure. bool ExtractH264CodecDetails(const nsAString& aCodecs, - int16_t& aProfile, - int16_t& aLevel); + uint8_t& aProfile, + uint8_t& aConstraint, + uint8_t& aLevel); struct VideoColorSpace { // TODO: Define the value type as strong type enum // to better know the exact meaning corresponding to ISO/IEC 23001-8:2016. diff --git a/dom/media/VideoUtils.cpp b/dom/media/VideoUtils.cpp --- a/dom/media/VideoUtils.cpp +++ b/dom/media/VideoUtils.cpp @@ -415,15 +415,16 @@ return rangeId <= 1; } bool ExtractH264CodecDetails(const nsAString& aCodec, - int16_t& aProfile, - int16_t& aLevel) + uint8_t& aProfile, + uint8_t& aConstraint, + uint8_t& aLevel) { // H.264 codecs parameters have a type defined as avcN.PPCCLL, where - // N = avc type. avc3 is avcc with SPS & PPS implicit (within stream) + // N = avc type. avc3 is avcc with SPS & PPS implicit (within stream) // PP = profile_idc, CC = constraint_set flags, LL = level_idc. // We ignore the constraint_set flags, as it's not clear from any // documentation what constraints the platform decoders support. // See http://blog.pearce.org.nz/2013/11/what-does-h264avc1-codecs-parameters.html // for more details. @@ -435,32 +436,33 @@ const nsAString& sample = Substring(aCodec, 0, 5); if (!sample.EqualsASCII("avc1.") && !sample.EqualsASCII("avc3.")) { return false; } - // Extract the profile_idc and level_idc. + // Extract the profile_idc, constraint_flags and level_idc. nsresult rv = NS_OK; aProfile = PromiseFlatString(Substring(aCodec, 5, 2)).ToInteger(&rv, 16); NS_ENSURE_SUCCESS(rv, false); + // Constraint flags are stored on the 6 most significant bits, first two bits + // are reserved_zero_2bits. + aConstraint = PromiseFlatString(Substring(aCodec, 7, 2)).ToInteger(&rv, 16); + NS_ENSURE_SUCCESS(rv, false); + aLevel = PromiseFlatString(Substring(aCodec, 9, 2)).ToInteger(&rv, 16); NS_ENSURE_SUCCESS(rv, false); if (aLevel == 9) { aLevel = H264_LEVEL_1_b; } else if (aLevel <= 5) { aLevel *= 10; } - // Capture the constraint_set flag value for the purpose of Telemetry. - // We don't NS_ENSURE_SUCCESS here because ExtractH264CodecDetails doesn't - // care about this, but we make sure constraints is above 4 (constraint_set5_flag) + // We only make sure constraints is above 4 for collection perspective // otherwise collect 0 for unknown. - uint8_t constraints = PromiseFlatString(Substring(aCodec, 7, 2)).ToInteger(&rv, 16); Telemetry::Accumulate(Telemetry::VIDEO_CANPLAYTYPE_H264_CONSTRAINT_SET_FLAG, - constraints >= 4 ? constraints : 0); - + aConstraint >= 4 ? aConstraint : 0); // 244 is the highest meaningful profile value (High 4:4:4 Intra Profile) // that can be represented as single hex byte, otherwise collect 0 for unknown. Telemetry::Accumulate(Telemetry::VIDEO_CANPLAYTYPE_H264_PROFILE, aProfile <= 244 ? aProfile : 0); @@ -657,13 +659,14 @@ } bool IsH264CodecString(const nsAString& aCodec) { - int16_t profile = 0; - int16_t level = 0; - return ExtractH264CodecDetails(aCodec, profile, level); + uint8_t profile = 0; + uint8_t constraint = 0; + uint8_t level = 0; + return ExtractH264CodecDetails(aCodec, profile, constraint, level); } bool IsAACCodecString(const nsAString& aCodec) { @@ -729,10 +732,22 @@ } Maybe maybeHeight = aContainerType.ExtendedType().GetHeight(); if (maybeHeight && *maybeHeight > 0) { videoInfo->mImage.height = *maybeHeight; } + } else if (trackInfo->GetAsAudioInfo()) { + AudioInfo* audioInfo = trackInfo->GetAsAudioInfo(); + Maybe maybeChannels = + aContainerType.ExtendedType().GetChannels(); + if (maybeChannels && *maybeChannels > 0) { + audioInfo->mChannels = *maybeChannels; + } + Maybe maybeSamplerate = + aContainerType.ExtendedType().GetSamplerate(); + if (maybeSamplerate && *maybeSamplerate > 0) { + audioInfo->mRate = *maybeSamplerate; + } } } return trackInfo; } diff --git a/dom/media/flac/FlacDecoder.h b/dom/media/flac/FlacDecoder.h --- a/dom/media/flac/FlacDecoder.h +++ b/dom/media/flac/FlacDecoder.h @@ -5,21 +5,27 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #ifndef FLAC_DECODER_H_ #define FLAC_DECODER_H_ +#include "mozilla/UniquePtr.h" +#include "nsTArray.h" + namespace mozilla { class MediaContainerType; +class TrackInfo; class FlacDecoder { public: // Returns true if the Flac backend is pref'ed on, and we're running on a // platform that is likely to have decoders for the format. static bool IsEnabled(); static bool IsSupportedType(const MediaContainerType& aContainerType); + static nsTArray> GetTracksInfo( + const MediaContainerType& aType); }; } // namespace mozilla #endif // !FLAC_DECODER_H_ diff --git a/dom/media/flac/FlacDecoder.cpp b/dom/media/flac/FlacDecoder.cpp --- a/dom/media/flac/FlacDecoder.cpp +++ b/dom/media/flac/FlacDecoder.cpp @@ -28,6 +28,21 @@ (aContainerType.Type() == MEDIAMIMETYPE("audio/flac") || aContainerType.Type() == MEDIAMIMETYPE("audio/x-flac") || aContainerType.Type() == MEDIAMIMETYPE("application/x-flac")); } +/* static */ nsTArray> +FlacDecoder::GetTracksInfo(const MediaContainerType& aType) +{ + nsTArray> tracks; + if (!IsSupportedType(aType)) { + return tracks; + } + + tracks.AppendElement( + CreateTrackInfoWithMIMETypeAndContainerTypeExtraParameters( + NS_LITERAL_CSTRING("audio/flac"), aType)); + + return tracks; +} + } // namespace mozilla diff --git a/dom/media/gmp/ChromiumCDMAdapter.cpp b/dom/media/gmp/ChromiumCDMAdapter.cpp --- a/dom/media/gmp/ChromiumCDMAdapter.cpp +++ b/dom/media/gmp/ChromiumCDMAdapter.cpp @@ -195,11 +195,12 @@ typedef DWORD(WINAPI* QueryDosDeviceWFnPtr)(_In_opt_ LPCWSTR lpDeviceName, _Out_ LPWSTR lpTargetPath, _In_ DWORD ucchMax); -static QueryDosDeviceWFnPtr sOriginalQueryDosDeviceWFnPtr = nullptr; +static WindowsDllInterceptor::FuncHookType + sOriginalQueryDosDeviceWFnPtr; static std::unordered_map* sDeviceNames = nullptr; DWORD WINAPI QueryDosDeviceWHook(LPCWSTR lpDeviceName, LPWSTR lpTargetPath, DWORD ucchMax) @@ -274,13 +275,12 @@ for (const std::wstring& name : GetDosDeviceNames()) { sDeviceNames->emplace(name, GetDeviceMapping(name)); } sKernel32Intercept.Init("kernelbase.dll"); - sKernel32Intercept.AddHook("QueryDosDeviceW", - reinterpret_cast(QueryDosDeviceWHook), - (void**)(&sOriginalQueryDosDeviceWFnPtr)); + sOriginalQueryDosDeviceWFnPtr.Set(sKernel32Intercept, "QueryDosDeviceW", + &QueryDosDeviceWHook); } #endif HostFile::HostFile(HostFile&& aOther) : mPath(aOther.mPath) diff --git a/dom/media/gtest/TestBitWriter.cpp b/dom/media/gtest/TestBitWriter.cpp new file mode 100644 --- /dev/null +++ b/dom/media/gtest/TestBitWriter.cpp @@ -0,0 +1,72 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "gtest/gtest.h" +#include "BitReader.h" +#include "BitWriter.h" +#include "H264.h" + +using namespace mozilla; + +TEST(BitWriter, BitWriter) +{ + RefPtr test = new MediaByteBuffer(); + BitWriter b(test); + b.WriteBit(false); + b.WriteBits(~1ULL, 1); // ensure that extra bits don't modify byte buffer. + b.WriteBits(3, 1); + b.WriteUE(1280 / 16 - 1); + b.WriteUE(720 / 16 - 1); + b.WriteUE(1280); + b.WriteUE(720); + b.WriteBit(true); + b.WriteBit(false); + b.WriteBit(true); + b.WriteU8(7); + b.WriteU32(16356); + b.WriteU64(116356); + b.WriteBits(~(0ULL) & ~1ULL, 16); + const uint32_t length = b.BitCount(); + b.CloseWithRbspTrailing(); + + BitReader c(test); + + EXPECT_EQ(c.ReadBit(), false); + EXPECT_EQ(c.ReadBit(), false); + EXPECT_EQ(c.ReadBit(), true); + EXPECT_EQ(c.ReadUE(), 1280u / 16 - 1); + EXPECT_EQ(c.ReadUE(), 720u / 16 - 1); + EXPECT_EQ(c.ReadUE(), 1280u); + EXPECT_EQ(c.ReadUE(), 720u); + EXPECT_EQ(c.ReadBit(), true); + EXPECT_EQ(c.ReadBit(), false); + EXPECT_EQ(c.ReadBit(), true); + EXPECT_EQ(c.ReadBits(8), 7u); + EXPECT_EQ(c.ReadU32(), 16356u); + EXPECT_EQ(c.ReadU64(), 116356u); + EXPECT_EQ(c.ReadBits(16), 0xfffeu); + EXPECT_EQ(length, BitReader::GetBitLength(test)); +} + +TEST(BitWriter, SPS) +{ + uint8_t sps_pps[] = { 0x01, 0x4d, 0x40, 0x0c, 0xff, 0xe1, 0x00, 0x1b, 0x67, + 0x4d, 0x40, 0x0c, 0xe8, 0x80, 0x80, 0x9d, 0x80, 0xb5, + 0x01, 0x01, 0x01, 0x40, 0x00, 0x00, 0x03, 0x00, 0x40, + 0x00, 0x00, 0x0f, 0x03, 0xc5, 0x0a, 0x44, 0x80, 0x01, + 0x00, 0x04, 0x68, 0xeb, 0xef, 0x20 }; + + RefPtr extraData = new MediaByteBuffer(); + extraData->AppendElements(sps_pps, sizeof(sps_pps)); + SPSData spsdata1; + bool success = H264::DecodeSPSFromExtraData(extraData, spsdata1); + EXPECT_EQ(success, true); + + RefPtr extraData2 = + H264::CreateExtraData(0x42, 0xc0, 0x1e, { 1280, 720 }); + SPSData spsdata2; + success = H264::DecodeSPSFromExtraData(extraData2, spsdata2); + EXPECT_EQ(success, true); +} diff --git a/dom/media/gtest/TestMP4Demuxer.cpp b/dom/media/gtest/TestMP4Demuxer.cpp --- a/dom/media/gtest/TestMP4Demuxer.cpp +++ b/dom/media/gtest/TestMP4Demuxer.cpp @@ -15,12 +15,10 @@ #include "VideoUtils.h" using namespace mozilla; using media::TimeUnit; -class AutoTaskQueue; - #define DO_FAIL [binding]()->void { EXPECT_TRUE(false); binding->mTaskQueue->BeginShutdown(); } class MP4DemuxerBinding { public: diff --git a/dom/media/gtest/moz.build b/dom/media/gtest/moz.build --- a/dom/media/gtest/moz.build +++ b/dom/media/gtest/moz.build @@ -10,10 +10,11 @@ 'TestAudioCompactor.cpp', 'TestAudioMixer.cpp', 'TestAudioPacketizer.cpp', 'TestAudioSegment.cpp', 'TestAudioTrackEncoder.cpp', + 'TestBitWriter.cpp', 'TestBlankVideoDataCreator.cpp', 'TestCDMStorage.cpp', 'TestDataMutex.cpp', 'TestGMPCrossOrigin.cpp', 'TestGMPRemoveAndDelete.cpp', diff --git a/dom/media/hls/HLSDemuxer.h b/dom/media/hls/HLSDemuxer.h --- a/dom/media/hls/HLSDemuxer.h +++ b/dom/media/hls/HLSDemuxer.h @@ -5,19 +5,19 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #if !defined(HLSDemuxer_h_) #define HLSDemuxer_h_ -#include "AutoTaskQueue.h" #include "GeneratedJNINatives.h" #include "GeneratedJNIWrappers.h" #include "MediaCodec.h" #include "MediaDataDemuxer.h" #include "MediaDecoder.h" #include "mozilla/Atomics.h" #include "mozilla/Maybe.h" #include "mozilla/Mutex.h" +#include "mozilla/TaskQueue.h" #include "VideoUtils.h" namespace mozilla { @@ -49,22 +49,22 @@ bool ShouldComputeStartTime() const override { return true; } void NotifyDataArrived() override; - AutoTaskQueue* GetTaskQueue() const { return mTaskQueue; } + TaskQueue* GetTaskQueue() const { return mTaskQueue; } void OnInitialized(bool aHasAudio, bool aHasVideo); void OnError(int aErrorCode); private: media::TimeUnit GetNextKeyFrameTime(); bool OnTaskQueue() const; ~HLSDemuxer(); friend class HLSTrackDemuxer; - const RefPtr mTaskQueue; + const RefPtr mTaskQueue; RefPtr mAudioDemuxer; RefPtr mVideoDemuxer; MozPromiseHolder mInitPromise; RefPtr mCallbackSupport; diff --git a/dom/media/hls/HLSDemuxer.cpp b/dom/media/hls/HLSDemuxer.cpp --- a/dom/media/hls/HLSDemuxer.cpp +++ b/dom/media/hls/HLSDemuxer.cpp @@ -130,12 +130,12 @@ HLSDemuxer* mDemuxer; }; HLSDemuxer::HLSDemuxer(int aPlayerId) - : mTaskQueue(new AutoTaskQueue(GetMediaThreadPool(MediaThreadType::PLAYBACK), - /* aSupportsTailDispatch = */ false)) + : mTaskQueue(new TaskQueue(GetMediaThreadPool(MediaThreadType::PLAYBACK), + /* aSupportsTailDispatch = */ false)) { MOZ_ASSERT(NS_IsMainThread()); HLSDemuxerCallbacksSupport::Init(); mJavaCallbacks = GeckoHLSDemuxerWrapper::Callbacks::New(); MOZ_ASSERT(mJavaCallbacks); diff --git a/dom/media/mediacapabilities/MediaCapabilities.h b/dom/media/mediacapabilities/MediaCapabilities.h new file mode 100644 --- /dev/null +++ b/dom/media/mediacapabilities/MediaCapabilities.h @@ -0,0 +1,109 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=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/. */ + +#ifndef mozilla_dom_MediaCapabilities_h_ +#define mozilla_dom_MediaCapabilities_h_ + +#include "DecoderDoctorLogger.h" +#include "MediaContainerType.h" +#include "js/TypeDecls.h" +#include "mozilla/Maybe.h" +#include "mozilla/UniquePtr.h" +#include "mozilla/dom/BindingUtils.h" +#include "nsCOMPtr.h" +#include "nsCycleCollectionNoteChild.h" +#include "nsCycleCollectionParticipant.h" +#include "nsIGlobalObject.h" +#include "nsISupports.h" +#include "nsWrapperCache.h" + +namespace mozilla { +namespace layers { +class KnowsCompositor; +} +namespace dom { +class MediaCapabilities; +} // namespace dom +DDLoggedTypeName(dom::MediaCapabilities); + +namespace dom { + +struct MediaDecodingConfiguration; +struct MediaEncodingConfiguration; +struct AudioConfiguration; +struct VideoConfiguration; +class Promise; + +class MediaCapabilities final + : public nsISupports + , public nsWrapperCache +{ +public: + // Ref counting and cycle collection + NS_DECL_CYCLE_COLLECTING_ISUPPORTS + NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(MediaCapabilities) + + // WebIDL Methods + already_AddRefed DecodingInfo( + const MediaDecodingConfiguration& aConfiguration, + ErrorResult& aRv); + already_AddRefed EncodingInfo( + const MediaEncodingConfiguration& aConfiguration, + ErrorResult& aRv); + // End WebIDL Methods + + explicit MediaCapabilities(nsIGlobalObject* aParent); + + nsIGlobalObject* GetParentObject() const { return mParent; } + JSObject* WrapObject(JSContext* aCx, + JS::Handle aGivenProto) override; + + static bool Enabled(JSContext* aCx, JSObject* aGlobal); + +private: + virtual ~MediaCapabilities() = default; + Maybe CheckVideoConfiguration( + const VideoConfiguration& aConfig) const; + Maybe CheckAudioConfiguration( + const AudioConfiguration& aConfig) const; + bool CheckTypeForMediaSource(const nsAString& aType); + bool CheckTypeForFile(const nsAString& aType); + bool CheckTypeForEncoder(const nsAString& aType); + already_AddRefed GetCompositor(); + nsCOMPtr mParent; +}; + +class MediaCapabilitiesInfo final : public NonRefcountedDOMObject +{ +public: + // WebIDL methods + bool Supported() const { return mSupported; } + bool Smooth() const { return mSmooth; } + bool PowerEfficient() const { return mPowerEfficient; } + // End WebIDL methods + + MediaCapabilitiesInfo(bool aSupported, bool aSmooth, bool aPowerEfficient) + : mSupported(aSupported) + , mSmooth(aSmooth) + , mPowerEfficient(aPowerEfficient) + { + } + + bool WrapObject(JSContext* aCx, + JS::Handle aGivenProto, + JS::MutableHandle aReflector); + +private: + bool mSupported; + bool mSmooth; + bool mPowerEfficient; +}; + +} // namespace dom + +} // namespace mozilla + +#endif /* mozilla_dom_MediaCapabilities_h_ */ diff --git a/dom/media/mediacapabilities/MediaCapabilities.cpp b/dom/media/mediacapabilities/MediaCapabilities.cpp new file mode 100644 --- /dev/null +++ b/dom/media/mediacapabilities/MediaCapabilities.cpp @@ -0,0 +1,668 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=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/. */ + +#include "MediaCapabilities.h" +#include "Benchmark.h" +#include "DecoderTraits.h" +#include "Layers.h" +#include "MediaInfo.h" +#include "MediaRecorder.h" +#include "PDMFactory.h" +#include "VPXDecoder.h" +#include "mozilla/Move.h" +#include "mozilla/StaticPrefs.h" +#include "mozilla/TaskQueue.h" +#include "mozilla/dom/DOMMozPromiseRequestHolder.h" +#include "mozilla/dom/MediaCapabilitiesBinding.h" +#include "mozilla/dom/MediaSource.h" +#include "mozilla/dom/Promise.h" +#include "mozilla/dom/WorkerPrivate.h" +#include "mozilla/dom/WorkerRef.h" +#include "mozilla/layers/KnowsCompositor.h" +#include "nsContentUtils.h" + +#include + +static mozilla::LazyLogModule sMediaCapabilitiesLog("MediaCapabilities"); + +#define LOG(msg, ...) \ + DDMOZ_LOG(sMediaCapabilitiesLog, LogLevel::Debug, msg, ##__VA_ARGS__) + +namespace mozilla { +namespace dom { + +static nsCString +VideoConfigurationToStr(const VideoConfiguration* aConfig) +{ + if (!aConfig) { + return nsCString(); + } + auto str = nsPrintfCString( + "[contentType:%s width:%d height:%d bitrate:%" PRIu64 " framerate:%s]", + NS_ConvertUTF16toUTF8(aConfig->mContentType.Value()).get(), + aConfig->mWidth.Value(), + aConfig->mHeight.Value(), + aConfig->mBitrate.Value(), + NS_ConvertUTF16toUTF8(aConfig->mFramerate.Value()).get()); + return std::move(str); +} + +static nsCString +AudioConfigurationToStr(const AudioConfiguration* aConfig) +{ + if (!aConfig) { + return nsCString(); + } + auto str = nsPrintfCString( + "[contentType:%s channels:%s bitrate:%" PRIu64 " samplerate:%d]", + NS_ConvertUTF16toUTF8(aConfig->mContentType.Value()).get(), + aConfig->mChannels.WasPassed() + ? NS_ConvertUTF16toUTF8(aConfig->mChannels.Value()).get() + : "?", + aConfig->mBitrate.WasPassed() ? aConfig->mBitrate.Value() : 0, + aConfig->mSamplerate.WasPassed() ? aConfig->mSamplerate.Value() : 0); + return std::move(str); +} + +static nsCString +MediaCapabilitiesInfoToStr(const MediaCapabilitiesInfo* aInfo) +{ + if (!aInfo) { + return nsCString(); + } + auto str = nsPrintfCString("[supported:%s smooth:%s powerEfficient:%s]", + aInfo->Supported() ? "true" : "false", + aInfo->Smooth() ? "true" : "false", + aInfo->PowerEfficient() ? "true" : "false"); + return std::move(str); +} + +static nsCString +MediaDecodingConfigurationToStr(const MediaDecodingConfiguration& aConfig) +{ + nsCString str; + str += NS_LITERAL_CSTRING("["); + if (aConfig.mVideo.IsAnyMemberPresent()) { + str += + NS_LITERAL_CSTRING("video:") + VideoConfigurationToStr(&aConfig.mVideo); + if (aConfig.mAudio.IsAnyMemberPresent()) { + str += NS_LITERAL_CSTRING(" "); + } + } + if (aConfig.mAudio.IsAnyMemberPresent()) { + str += + NS_LITERAL_CSTRING("audio:") + AudioConfigurationToStr(&aConfig.mAudio); + } + str += NS_LITERAL_CSTRING("]"); + return str; +} + +MediaCapabilities::MediaCapabilities(nsIGlobalObject* aParent) + : mParent(aParent) +{ +} + +static void +ThrowWithMemberName(ErrorResult& aRv, + const char* aCategory, + const char* aMember) +{ + auto str = nsPrintfCString("'%s' member of %s", aMember, aCategory); + aRv.ThrowTypeError( + NS_ConvertUTF8toUTF16(str)); +} + +static void +CheckVideoConfigurationSanity(const VideoConfiguration& aConfig, + const char* aCategory, + ErrorResult& aRv) +{ + if (!aConfig.mContentType.WasPassed()) { + ThrowWithMemberName(aRv, "contentType", aCategory); + return; + } + if (!aConfig.mWidth.WasPassed()) { + ThrowWithMemberName(aRv, "width", aCategory); + return; + } + if (!aConfig.mHeight.WasPassed()) { + ThrowWithMemberName(aRv, "height", aCategory); + return; + } + if (!aConfig.mBitrate.WasPassed()) { + ThrowWithMemberName(aRv, "bitrate", aCategory); + return; + } + if (!aConfig.mFramerate.WasPassed()) { + ThrowWithMemberName(aRv, "framerate", aCategory); + return; + } +} + +static void +CheckAudioConfigurationSanity(const AudioConfiguration& aConfig, + const char* aCategory, + ErrorResult& aRv) +{ + if (!aConfig.mContentType.WasPassed()) { + ThrowWithMemberName(aRv, "contentType", aCategory); + return; + } +} + +already_AddRefed +MediaCapabilities::DecodingInfo( + const MediaDecodingConfiguration& aConfiguration, + ErrorResult& aRv) +{ + RefPtr promise = Promise::Create(mParent, aRv); + if (aRv.Failed()) { + return nullptr; + } + + // If configuration is not a valid MediaConfiguration, return a Promise + // rejected with a TypeError. + if (!aConfiguration.IsAnyMemberPresent() || + (!aConfiguration.mVideo.IsAnyMemberPresent() && + !aConfiguration.mAudio.IsAnyMemberPresent())) { + aRv.ThrowTypeError( + NS_LITERAL_STRING("'audio' or 'video'")); + return nullptr; + } + + // Here we will throw rather than rejecting a promise in order to simulate + // optional dictionaries with required members (see bug 1368949) + if (aConfiguration.mVideo.IsAnyMemberPresent()) { + // Check that all VideoConfiguration required members are present. + CheckVideoConfigurationSanity( + aConfiguration.mVideo, "MediaDecodingConfiguration", aRv); + if (aRv.Failed()) { + return nullptr; + } + } + if (aConfiguration.mAudio.IsAnyMemberPresent()) { + // Check that all AudioConfiguration required members are present. + CheckAudioConfigurationSanity( + aConfiguration.mAudio, "MediaDecodingConfiguration", aRv); + if (aRv.Failed()) { + return nullptr; + } + } + + LOG("Processing %s", MediaDecodingConfigurationToStr(aConfiguration).get()); + + bool supported = true; + Maybe videoContainer; + Maybe audioContainer; + + // If configuration.video is present and is not a valid video configuration, + // return a Promise rejected with a TypeError. + if (aConfiguration.mVideo.IsAnyMemberPresent()) { + videoContainer = CheckVideoConfiguration(aConfiguration.mVideo); + if (!videoContainer) { + aRv.ThrowTypeError(); + return nullptr; + } + + // We have a video configuration and it is valid. Check if it is supported. + supported &= + aConfiguration.mType == MediaDecodingType::File + ? CheckTypeForFile(aConfiguration.mVideo.mContentType.Value()) + : CheckTypeForMediaSource(aConfiguration.mVideo.mContentType.Value()); + } + if (aConfiguration.mAudio.IsAnyMemberPresent()) { + audioContainer = CheckAudioConfiguration(aConfiguration.mAudio); + if (!audioContainer) { + aRv.ThrowTypeError(); + return nullptr; + } + // We have an audio configuration and it is valid. Check if it is supported. + supported &= + aConfiguration.mType == MediaDecodingType::File + ? CheckTypeForFile(aConfiguration.mAudio.mContentType.Value()) + : CheckTypeForMediaSource(aConfiguration.mAudio.mContentType.Value()); + } + + if (!supported) { + auto info = MakeUnique( + false /* supported */, false /* smooth */, false /* power efficient */); + LOG("%s -> %s", + MediaDecodingConfigurationToStr(aConfiguration).get(), + MediaCapabilitiesInfoToStr(info.get()).get()); + promise->MaybeResolve(std::move(info)); + return promise.forget(); + } + + nsTArray> tracks; + if (aConfiguration.mVideo.IsAnyMemberPresent()) { + MOZ_ASSERT(videoContainer.isSome(), "configuration is valid and supported"); + auto videoTracks = DecoderTraits::GetTracksInfo(*videoContainer); + // If the MIME type does not imply a codec, the string MUST + // also have one and only one parameter that is named codecs with a value + // describing a single media codec. Otherwise, it MUST contain no + // parameters. + if (videoTracks.Length() != 1) { + promise->MaybeReject(NS_ERROR_DOM_TYPE_ERR); + return promise.forget(); + } + MOZ_DIAGNOSTIC_ASSERT(videoTracks.ElementAt(0), + "must contain a valid trackinfo"); + tracks.AppendElements(std::move(videoTracks)); + } + if (aConfiguration.mAudio.IsAnyMemberPresent()) { + MOZ_ASSERT(audioContainer.isSome(), "configuration is valid and supported"); + auto audioTracks = DecoderTraits::GetTracksInfo(*audioContainer); + // If the MIME type does not imply a codec, the string MUST + // also have one and only one parameter that is named codecs with a value + // describing a single media codec. Otherwise, it MUST contain no + // parameters. + if (audioTracks.Length() != 1) { + promise->MaybeReject(NS_ERROR_DOM_TYPE_ERR); + return promise.forget(); + } + MOZ_DIAGNOSTIC_ASSERT(audioTracks.ElementAt(0), + "must contain a valid trackinfo"); + tracks.AppendElements(std::move(audioTracks)); + } + + typedef MozPromise + CapabilitiesPromise; + nsTArray> promises; + + RefPtr taskQueue = + new TaskQueue(GetMediaThreadPool(MediaThreadType::PLATFORM_DECODER), + "MediaCapabilities::TaskQueue"); + for (auto&& config : tracks) { + TrackInfo::TrackType type = + config->IsVideo() ? TrackInfo::kVideoTrack : TrackInfo::kAudioTrack; + + MOZ_ASSERT(type == TrackInfo::kAudioTrack || + videoContainer->ExtendedType().GetFramerate().isSome(), + "framerate is a required member of VideoConfiguration"); + + if (type == TrackInfo::kAudioTrack) { + // There's no need to create an audio decoder has we only want to know if + // such codec is supported + RefPtr pdm = new PDMFactory(); + if (!pdm->Supports(*config, nullptr /* decoder doctor */)) { + auto info = + MakeUnique(false /* supported */, + false /* smooth */, + false /* power efficient */); + LOG("%s -> %s", + MediaDecodingConfigurationToStr(aConfiguration).get(), + MediaCapabilitiesInfoToStr(info.get()).get()); + promise->MaybeResolve(std::move(info)); + return promise.forget(); + } + // We can assume that if we could create the decoder, then we can play it. + // We report that we can play it smoothly and in an efficient fashion. + promises.AppendElement(CapabilitiesPromise::CreateAndResolve( + MediaCapabilitiesInfo( + true /* supported */, true /* smooth */, true /* power efficient */), + __func__)); + continue; + } + + // On Windows, the MediaDataDecoder expects to be created on a thread + // supporting MTA, which the main thread doesn't. So we use our task queue + // to create such decoder and perform initialization. + + RefPtr compositor = GetCompositor(); + double frameRate = videoContainer->ExtendedType().GetFramerate().ref(); + promises.AppendElement(InvokeAsync( + taskQueue, + __func__, + [taskQueue, frameRate, compositor, config = std::move(config)]() mutable + -> RefPtr { + // MediaDataDecoder keeps a reference to the config object, so we must + // keep it alive until the decoder has been shutdown. + CreateDecoderParams params{ *config, + taskQueue, + compositor, + CreateDecoderParams::VideoFrameRate( + frameRate), + TrackInfo::kVideoTrack }; + + RefPtr pdm = new PDMFactory(); + RefPtr decoder = pdm->CreateDecoder(params); + if (!decoder) { + return CapabilitiesPromise::CreateAndReject( + MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR, "Can't create decoder"), + __func__); + } + // We now query the decoder to determine if it's power efficient. + return decoder->Init()->Then( + taskQueue, + __func__, + [taskQueue, decoder, frameRate, config = std::move(config)]( + const MediaDataDecoder::InitPromise::ResolveOrRejectValue& + aValue) mutable { + RefPtr p; + if (aValue.IsReject()) { + p = CapabilitiesPromise::CreateAndReject(aValue.RejectValue(), + __func__); + } else { + MOZ_ASSERT(config->IsVideo()); + nsAutoCString reason; + bool powerEfficient = true; + bool smooth = true; + if (config->GetAsVideoInfo()->mImage.height > 480) { + // Assume that we can do stuff at 480p or less in a power + // efficient manner and smoothly. If greater than 480p we assume + // that if the video decoding is hardware accelerated it will be + // smooth and power efficient, otherwise we use the benchmark to + // estimate + powerEfficient = decoder->IsHardwareAccelerated(reason); + if (!powerEfficient && VPXDecoder::IsVP9(config->mMimeType)) { + smooth = VP9Benchmark::IsVP9DecodeFast(true /* default */); + uint32_t fps = StaticPrefs::MediaBenchmarkVp9Fps(); + if (!smooth && fps > 0) { + // The VP9 estimizer decode a 1280x720 video. Let's adjust + // the result for the resolution and frame rate of what we + // actually want. If the result is twice that we need we + // assume it will be smooth. + const auto& videoConfig = *config->GetAsVideoInfo(); + double needed = + ((1280.0 * 720.0) / + (videoConfig.mImage.width * videoConfig.mImage.height) * + fps) / + frameRate; + smooth = needed > 2; + } + } + } + p = CapabilitiesPromise::CreateAndResolve( + MediaCapabilitiesInfo( + true /* supported */, smooth, powerEfficient), + __func__); + } + MOZ_ASSERT(p.get(), "the promise has been created"); + // Let's keep alive the decoder and the config object until the + // decoder has shutdown. + decoder->Shutdown()->Then( + taskQueue, + __func__, + [taskQueue, decoder, config = std::move(config)]( + const ShutdownPromise::ResolveOrRejectValue& aValue) {}); + return p; + }); + })); + } + + auto holder = + MakeRefPtr>( + mParent); + RefPtr targetThread; + RefPtr workerRef; + + if (NS_IsMainThread()) { + targetThread = mParent->AbstractMainThreadFor(TaskCategory::Other); + } else { + WorkerPrivate* wp = GetCurrentThreadWorkerPrivate(); + MOZ_ASSERT(wp, "Must be called from a worker thread"); + targetThread = wp->HybridEventTarget(); + RefPtr strongWorkerRef = StrongWorkerRef::Create( + wp, "MediaCapabilities", [holder, targetThread]() { + MOZ_ASSERT(targetThread->IsOnCurrentThread()); + holder->DisconnectIfExists(); + }); + if (NS_WARN_IF(!workerRef)) { + // The worker is shutting down. + aRv.Throw(NS_ERROR_FAILURE); + return nullptr; + } + } + + MOZ_ASSERT(targetThread); + + // this is only captured for use with the LOG macro. + RefPtr self = this; + + CapabilitiesPromise::All(targetThread, promises) + ->Then( + targetThread, + __func__, + [promise, + tracks = std::move(tracks), + workerRef, + holder, + aConfiguration, + self, + this](const CapabilitiesPromise::AllPromiseType::ResolveOrRejectValue& + aValue) { + holder->Complete(); + if (aValue.IsReject()) { + auto info = + MakeUnique(false /* supported */, + false /* smooth */, + false /* power efficient */); + LOG("%s -> %s", + MediaDecodingConfigurationToStr(aConfiguration).get(), + MediaCapabilitiesInfoToStr(info.get()).get()); + promise->MaybeResolve(std::move(info)); + return; + } + bool powerEfficient = true; + bool smooth = true; + for (auto&& capability : aValue.ResolveValue()) { + smooth &= capability.Smooth(); + powerEfficient &= capability.PowerEfficient(); + } + auto info = MakeUnique( + true /* supported */, smooth, powerEfficient); + LOG("%s -> %s", + MediaDecodingConfigurationToStr(aConfiguration).get(), + MediaCapabilitiesInfoToStr(info.get()).get()); + promise->MaybeResolve(std::move(info)); + }) + ->Track(*holder); + + return promise.forget(); +} + +already_AddRefed +MediaCapabilities::EncodingInfo( + const MediaEncodingConfiguration& aConfiguration, + ErrorResult& aRv) +{ + RefPtr promise = Promise::Create(mParent, aRv); + if (aRv.Failed()) { + return nullptr; + } + + // If configuration is not a valid MediaConfiguration, return a Promise + // rejected with a TypeError. + if (!aConfiguration.IsAnyMemberPresent() || + (!aConfiguration.mVideo.IsAnyMemberPresent() && + !aConfiguration.mAudio.IsAnyMemberPresent())) { + aRv.ThrowTypeError( + NS_LITERAL_STRING("'audio' or 'video'")); + return nullptr; + } + + // Here we will throw rather than rejecting a promise in order to simulate + // optional dictionaries with required members (see bug 1368949) + if (aConfiguration.mVideo.IsAnyMemberPresent()) { + // Check that all VideoConfiguration required members are present. + CheckVideoConfigurationSanity( + aConfiguration.mVideo, "MediaDecodingConfiguration", aRv); + if (aRv.Failed()) { + return nullptr; + } + } + if (aConfiguration.mAudio.IsAnyMemberPresent()) { + // Check that all AudioConfiguration required members are present. + CheckAudioConfigurationSanity( + aConfiguration.mAudio, "MediaDecodingConfiguration", aRv); + if (aRv.Failed()) { + return nullptr; + } + } + + bool supported = true; + + // If configuration.video is present and is not a valid video configuration, + // return a Promise rejected with a TypeError. + if (aConfiguration.mVideo.IsAnyMemberPresent()) { + if (!CheckVideoConfiguration(aConfiguration.mVideo)) { + aRv.ThrowTypeError(); + return nullptr; + } + // We have a video configuration and it is valid. Check if it is supported. + supported &= + CheckTypeForEncoder(aConfiguration.mVideo.mContentType.Value()); + } + if (aConfiguration.mAudio.IsAnyMemberPresent()) { + if (!CheckAudioConfiguration(aConfiguration.mAudio)) { + aRv.ThrowTypeError(); + return nullptr; + } + // We have an audio configuration and it is valid. Check if it is supported. + supported &= + CheckTypeForEncoder(aConfiguration.mAudio.mContentType.Value()); + } + + auto info = MakeUnique(supported, supported, false); + promise->MaybeResolve(std::move(info)); + + return promise.forget(); +} + +Maybe +MediaCapabilities::CheckVideoConfiguration( + const VideoConfiguration& aConfig) const +{ + Maybe container = MakeMediaExtendedMIMEType(aConfig); + if (!container) { + return Nothing(); + } + // A valid video MIME type is a string that is a valid media MIME type and for + // which the type per [RFC7231] is either video or application. + if (!container->Type().HasVideoMajorType() && + !container->Type().HasApplicationMajorType()) { + return Nothing(); + } + + // If the MIME type does not imply a codec, the string MUST also have one and + // only one parameter that is named codecs with a value describing a single + // media codec. Otherwise, it MUST contain no parameters. + // TODO (nsIMOMEHeaderParam doesn't provide backend to count number of + // parameters) + + return Some(MediaContainerType(std::move(*container))); +} + +Maybe +MediaCapabilities::CheckAudioConfiguration( + const AudioConfiguration& aConfig) const +{ + Maybe container = MakeMediaExtendedMIMEType(aConfig); + if (!container) { + return Nothing(); + } + // A valid audio MIME type is a string that is valid media MIME type and for + // which the type per [RFC7231] is either audio or application. + if (!container->Type().HasAudioMajorType() && + !container->Type().HasApplicationMajorType()) { + return Nothing(); + } + + // If the MIME type does not imply a codec, the string MUST also have one and + // only one parameter that is named codecs with a value describing a single + // media codec. Otherwise, it MUST contain no parameters. + // TODO (nsIMOMEHeaderParam doesn't provide backend to count number of + // parameters) + + return Some(MediaContainerType(std::move(*container))); +} + +bool +MediaCapabilities::CheckTypeForMediaSource(const nsAString& aType) +{ + return NS_SUCCEEDED(MediaSource::IsTypeSupported( + aType, nullptr /* DecoderDoctorDiagnostics */)); +} + +bool +MediaCapabilities::CheckTypeForFile(const nsAString& aType) +{ + Maybe containerType = MakeMediaContainerType(aType); + if (!containerType) { + return false; + } + + return DecoderTraits::CanHandleContainerType( + *containerType, nullptr /* DecoderDoctorDiagnostics */) != + CANPLAY_NO; +} + +bool +MediaCapabilities::CheckTypeForEncoder(const nsAString& aType) +{ + return MediaRecorder::IsTypeSupported(aType); +} + +already_AddRefed +MediaCapabilities::GetCompositor() +{ + nsCOMPtr window = do_QueryInterface(GetParentObject()); + if (NS_WARN_IF(!window)) { + return nullptr; + } + + nsCOMPtr doc = window->GetExtantDoc(); + if (NS_WARN_IF(!doc)) { + return nullptr; + } + RefPtr layerManager = + nsContentUtils::LayerManagerForDocument(doc); + if (NS_WARN_IF(!layerManager)) { + return nullptr; + } + RefPtr knows = layerManager->AsKnowsCompositor(); + if (NS_WARN_IF(!knows)) { + return nullptr; + } + return knows->GetForMedia().forget(); +} + +bool +MediaCapabilities::Enabled(JSContext* aCx, JSObject* aGlobal) +{ + return StaticPrefs::MediaCapabilitiesEnabled(); +} + +JSObject* +MediaCapabilities::WrapObject(JSContext* aCx, JS::Handle aGivenProto) +{ + return MediaCapabilities_Binding::Wrap(aCx, this, aGivenProto); +} + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(MediaCapabilities) + NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY + NS_INTERFACE_MAP_ENTRY(nsISupports) +NS_INTERFACE_MAP_END + +NS_IMPL_CYCLE_COLLECTING_ADDREF(MediaCapabilities) +NS_IMPL_CYCLE_COLLECTING_RELEASE(MediaCapabilities) + +NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(MediaCapabilities, mParent) + +// MediaCapabilitiesInfo +bool +MediaCapabilitiesInfo::WrapObject(JSContext* aCx, + JS::Handle aGivenProto, + JS::MutableHandle aReflector) +{ + return MediaCapabilitiesInfo_Binding::Wrap( + aCx, this, aGivenProto, aReflector); +} + +} // namespace dom +} // namespace mozilla diff --git a/devtools/client/debugger/new/src/components/PrimaryPanes/moz.build b/dom/media/mediacapabilities/moz.build copy from devtools/client/debugger/new/src/components/PrimaryPanes/moz.build copy to dom/media/mediacapabilities/moz.build --- a/devtools/client/debugger/new/src/components/PrimaryPanes/moz.build +++ b/dom/media/mediacapabilities/moz.build @@ -1,14 +1,14 @@ # vim: set filetype=python: # This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at http://mozilla.org/MPL/2.0/. -DIRS += [ +EXPORTS.mozilla.dom += [ + 'MediaCapabilities.h', +] +UNIFIED_SOURCES += [ + 'MediaCapabilities.cpp', ] -DevToolsModules( - 'index.js', - 'Outline.js', - 'SourcesTree.js', -) +FINAL_LIBRARY = 'xul' diff --git a/dom/media/mediasource/AutoTaskQueue.h b/dom/media/mediasource/AutoTaskQueue.h deleted file mode 100644 --- a/dom/media/mediasource/AutoTaskQueue.h +++ /dev/null @@ -1,73 +0,0 @@ -/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ -/* vim: set ts=8 sts=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/. */ - -#ifndef MOZILLA_AUTOTASKQUEUE_H_ -#define MOZILLA_AUTOTASKQUEUE_H_ - -#include "mozilla/RefPtr.h" -#include "mozilla/SystemGroup.h" -#include "mozilla/TaskQueue.h" - -class nsIEventTarget; - -namespace mozilla { - -// A convenience TaskQueue not requiring explicit shutdown. -class AutoTaskQueue : public AbstractThread -{ -public: - explicit AutoTaskQueue(already_AddRefed aPool, - bool aSupportsTailDispatch = false) - : AbstractThread(aSupportsTailDispatch) - , mTaskQueue(new TaskQueue(std::move(aPool), aSupportsTailDispatch)) - , mMonitor("AutoTaskQueue") - { - } - - AutoTaskQueue(already_AddRefed aPool, - const char* aName, - bool aSupportsTailDispatch = false) - : AbstractThread(aSupportsTailDispatch) - , mTaskQueue(new TaskQueue(std::move(aPool), aName, aSupportsTailDispatch)) - , mMonitor("AutoTaskQueue") - { - } - - TaskDispatcher& TailDispatcher() override - { - return mTaskQueue->TailDispatcher(); - } - - MOZ_MUST_USE nsresult - Dispatch(already_AddRefed aRunnable, - DispatchReason aReason = NormalDispatch) override - { - return mTaskQueue->Dispatch(std::move(aRunnable), aReason); - } - - // Prevent a GCC warning about the other overload of Dispatch being hidden. - using AbstractThread::Dispatch; - - // Blocks until all tasks finish executing. - void AwaitIdle() { mTaskQueue->AwaitIdle(); } - - bool IsEmpty() { return mTaskQueue->IsEmpty(); } - - // Returns true if the current thread is currently running a Runnable in - // the task queue. - bool IsCurrentThreadIn() override { return mTaskQueue->IsCurrentThreadIn(); } - - mozilla::Monitor& Monitor() { return mMonitor; } - -private: - ~AutoTaskQueue() { mTaskQueue->BeginShutdown(); } - RefPtr mTaskQueue; - mozilla::Monitor mMonitor; -}; - -} // namespace mozilla - -#endif diff --git a/dom/media/mediasource/MediaSource.cpp b/dom/media/mediasource/MediaSource.cpp --- a/dom/media/mediasource/MediaSource.cpp +++ b/dom/media/mediasource/MediaSource.cpp @@ -20,10 +20,11 @@ #include "SourceBuffer.h" #include "SourceBufferList.h" #include "mozilla/ErrorResult.h" #include "mozilla/FloatingPoint.h" #include "mozilla/Preferences.h" +#include "mozilla/StaticPrefs.h" #include "mozilla/dom/BindingDeclarations.h" #include "mozilla/dom/HTMLMediaElement.h" #include "mozilla/mozalloc.h" #include "nsDebug.h" #include "nsError.h" @@ -128,10 +129,11 @@ } return NS_OK; } if (mimeType == MEDIAMIMETYPE("video/webm")) { if (!(Preferences::GetBool("media.mediasource.webm.enabled", false) || + StaticPrefs::MediaCapabilitiesEnabled() || containerType->ExtendedType().Codecs().Contains( NS_LITERAL_STRING("vp8")) || #ifdef MOZ_AV1 // FIXME: Temporary comparison with the full codecs attribute. // See bug 1377015. diff --git a/dom/media/mediasource/MediaSourceDemuxer.h b/dom/media/mediasource/MediaSourceDemuxer.h --- a/dom/media/mediasource/MediaSourceDemuxer.h +++ b/dom/media/mediasource/MediaSourceDemuxer.h @@ -5,19 +5,19 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #if !defined(MediaSourceDemuxer_h_) #define MediaSourceDemuxer_h_ -#include "mozilla/Atomics.h" -#include "mozilla/Maybe.h" -#include "mozilla/Monitor.h" -#include "AutoTaskQueue.h" - #include "MediaDataDemuxer.h" #include "MediaResource.h" #include "MediaSource.h" #include "TrackBuffersManager.h" +#include "mozilla/Atomics.h" +#include "mozilla/Maybe.h" +#include "mozilla/Monitor.h" +#include "mozilla/TaskQueue.h" + namespace mozilla { class AbstractThread; class MediaResult; @@ -47,11 +47,11 @@ bool ShouldComputeStartTime() const override { return false; } /* interface for TrackBuffersManager */ void AttachSourceBuffer(RefPtr& aSourceBuffer); void DetachSourceBuffer(RefPtr& aSourceBuffer); - AutoTaskQueue* GetTaskQueue() { return mTaskQueue; } + TaskQueue* GetTaskQueue() { return mTaskQueue; } void NotifyInitDataArrived(); // Returns a string describing the state of the MediaSource internal // buffered data. Used for debugging purposes. void GetMozDebugReaderData(nsACString& aString); @@ -76,11 +76,11 @@ bool OnTaskQueue() { return !GetTaskQueue() || GetTaskQueue()->IsCurrentThreadIn(); } - RefPtr mTaskQueue; + RefPtr mTaskQueue; nsTArray> mDemuxers; nsTArray> mSourceBuffers; MozPromiseHolder mInitPromise; diff --git a/dom/media/mediasource/MediaSourceDemuxer.cpp b/dom/media/mediasource/MediaSourceDemuxer.cpp --- a/dom/media/mediasource/MediaSourceDemuxer.cpp +++ b/dom/media/mediasource/MediaSourceDemuxer.cpp @@ -22,12 +22,12 @@ typedef TrackInfo::TrackType TrackType; using media::TimeUnit; using media::TimeIntervals; MediaSourceDemuxer::MediaSourceDemuxer(AbstractThread* aAbstractMainThread) - : mTaskQueue(new AutoTaskQueue(GetMediaThreadPool(MediaThreadType::PLAYBACK), - "MediaSourceDemuxer::mTaskQueue")) + : mTaskQueue(new TaskQueue(GetMediaThreadPool(MediaThreadType::PLAYBACK), + "MediaSourceDemuxer::mTaskQueue")) , mMonitor("MediaSourceDemuxer") { MOZ_ASSERT(NS_IsMainThread()); } diff --git a/dom/media/mediasource/TrackBuffersManager.h b/dom/media/mediasource/TrackBuffersManager.h --- a/dom/media/mediasource/TrackBuffersManager.h +++ b/dom/media/mediasource/TrackBuffersManager.h @@ -9,11 +9,11 @@ #include "mozilla/Atomics.h" #include "mozilla/Maybe.h" #include "mozilla/Mutex.h" #include "mozilla/NotNull.h" -#include "AutoTaskQueue.h" +#include "mozilla/TaskQueue.h" #include "MediaContainerType.h" #include "MediaData.h" #include "MediaDataDemuxer.h" #include "MediaResult.h" @@ -463,19 +463,19 @@ } TrackData mVideoTracks; TrackData mAudioTracks; // TaskQueue methods and objects. - RefPtr GetTaskQueueSafe() const + RefPtr GetTaskQueueSafe() const { MutexAutoLock mut(mMutex); return mTaskQueue; } NotNull TaskQueueFromTaskQueue() const { #ifdef DEBUG - RefPtr taskQueue = GetTaskQueueSafe(); + RefPtr taskQueue = GetTaskQueueSafe(); MOZ_ASSERT(taskQueue && taskQueue->IsCurrentThreadIn()); #endif return WrapNotNull(mTaskQueue.get()); } bool OnTaskQueue() const @@ -531,11 +531,11 @@ // Monitor to protect following objects accessed across multiple threads. mutable Mutex mMutex; // mTaskQueue is only ever written after construction on the task queue. // As such, it can be accessed while on task queue without the need for the // mutex. - RefPtr mTaskQueue; + RefPtr mTaskQueue; // Stable audio and video track time ranges. media::TimeIntervals mVideoBufferedRanges; media::TimeIntervals mAudioBufferedRanges; // MediaInfo of the first init segment read. MediaInfo mInfo; diff --git a/dom/media/mediasource/TrackBuffersManager.cpp b/dom/media/mediasource/TrackBuffersManager.cpp --- a/dom/media/mediasource/TrackBuffersManager.cpp +++ b/dom/media/mediasource/TrackBuffersManager.cpp @@ -175,11 +175,11 @@ TrackBuffersManager::QueueTask(SourceBufferTask* aTask) { // The source buffer is a wrapped native, it would be unlinked twice and so // the TrackBuffersManager::Detach() would also be called twice. Since the // detach task has been done before, we could ignore this task. - RefPtr taskQueue = GetTaskQueueSafe(); + RefPtr taskQueue = GetTaskQueueSafe(); if (!taskQueue) { MOZ_ASSERT(aTask->GetType() == SourceBufferTask::Type::Detach, "only detach task could happen here!"); MSE_DEBUG("Could not queue the task '%s' without task queue", aTask->GetTypeName()); diff --git a/dom/media/mediasource/moz.build b/dom/media/mediasource/moz.build --- a/dom/media/mediasource/moz.build +++ b/dom/media/mediasource/moz.build @@ -5,11 +5,10 @@ MOCHITEST_MANIFESTS += ['test/mochitest.ini'] EXPORTS += [ 'AsyncEventRunner.h', - 'AutoTaskQueue.h', 'MediaSourceDecoder.h', 'MediaSourceDemuxer.h', 'SourceBufferAttributes.h', 'SourceBufferTask.h', 'TrackBuffersManager.h', diff --git a/dom/media/moz.build b/dom/media/moz.build --- a/dom/media/moz.build +++ b/dom/media/moz.build @@ -39,10 +39,11 @@ 'flac', 'gmp', 'gmp-plugin-openh264', 'imagecapture', 'ipc', + 'mediacapabilities', 'mediasink', 'mediasource', 'mp3', 'ogg', 'platforms', @@ -102,10 +103,11 @@ 'AudioStream.h', 'AutoplayPolicy.h', 'BackgroundVideoDecodingPermissionObserver.h', 'Benchmark.h', 'BitReader.h', + 'BitWriter.h', 'BufferMediaResource.h', 'BufferReader.h', 'ByteWriter.h', 'ChannelMediaDecoder.h', 'CubebUtils.h', @@ -221,10 +223,11 @@ 'AutoplayPolicy.cpp', 'BackgroundVideoDecodingPermissionObserver.cpp', 'BaseMediaResource.cpp', 'Benchmark.cpp', 'BitReader.cpp', + 'BitWriter.cpp', 'CanvasCaptureMediaStream.cpp', 'ChannelMediaDecoder.cpp', 'ChannelMediaResource.cpp', 'CloneableWithRangeMediaResource.cpp', 'DOMMediaStream.cpp', diff --git a/dom/media/mp3/MP3Decoder.h b/dom/media/mp3/MP3Decoder.h --- a/dom/media/mp3/MP3Decoder.h +++ b/dom/media/mp3/MP3Decoder.h @@ -4,21 +4,27 @@ * 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 MP3Decoder_h_ #define MP3Decoder_h_ +#include "mozilla/UniquePtr.h" +#include "nsTArray.h" + namespace mozilla { class MediaContainerType; +class TrackInfo; class MP3Decoder { public: // Returns true if the MP3 backend is preffed on, and we're running on a // platform that is likely to have decoders for the format. static bool IsEnabled(); static bool IsSupportedType(const MediaContainerType& aContainerType); + static nsTArray> GetTracksInfo( + const MediaContainerType& aType); }; } // namespace mozilla #endif diff --git a/dom/media/mp3/MP3Decoder.cpp b/dom/media/mp3/MP3Decoder.cpp --- a/dom/media/mp3/MP3Decoder.cpp +++ b/dom/media/mp3/MP3Decoder.cpp @@ -27,6 +27,21 @@ aContainerType.ExtendedType().Codecs() == "mp3"); } return false; } +/* static */ nsTArray> +MP3Decoder::GetTracksInfo(const MediaContainerType& aType) +{ + nsTArray> tracks; + if (!IsSupportedType(aType)) { + return tracks; + } + + tracks.AppendElement( + CreateTrackInfoWithMIMETypeAndContainerTypeExtraParameters( + NS_LITERAL_CSTRING("audio/mpeg"), aType)); + + return tracks; +} + } // namespace mozilla diff --git a/dom/media/mp4/MP4Decoder.h b/dom/media/mp4/MP4Decoder.h --- a/dom/media/mp4/MP4Decoder.h +++ b/dom/media/mp4/MP4Decoder.h @@ -4,16 +4,19 @@ * 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/. */ #if !defined(MP4Decoder_h_) #define MP4Decoder_h_ +#include "mozilla/UniquePtr.h" #include "nsStringFwd.h" +#include "nsTArray.h" namespace mozilla { class MediaContainerType; class DecoderDoctorDiagnostics; +class TrackInfo; // Decoder that uses a bundled MP4 demuxer and platform decoders to play MP4. class MP4Decoder { public: @@ -37,10 +40,17 @@ static bool IsAAC(const nsACString& aMimeType); // Returns true if the MP4 backend is preffed on. static bool IsEnabled(); + static nsTArray> GetTracksInfo( + const MediaContainerType& aType); + +private: + static nsTArray> GetTracksInfo( + const MediaContainerType& aType, + MediaResult& aError); }; } // namespace mozilla #endif diff --git a/dom/media/mp4/MP4Decoder.cpp b/dom/media/mp4/MP4Decoder.cpp --- a/dom/media/mp4/MP4Decoder.cpp +++ b/dom/media/mp4/MP4Decoder.cpp @@ -3,25 +3,26 @@ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "MP4Decoder.h" -#include "MediaContainerType.h" +#include "H264.h" #include "MP4Demuxer.h" -#include "nsMimeTypes.h" -#include "VideoUtils.h" +#include "MediaContainerType.h" #include "PDMFactory.h" +#include "VideoUtils.h" #include "mozilla/StaticPrefs.h" +#include "nsMimeTypes.h" namespace mozilla { static bool IsWhitelistedH264Codec(const nsAString& aCodec) { - int16_t profile = 0, level = 0; + uint8_t profile = 0, constraint = 0, level = 0; - if (!ExtractH264CodecDetails(aCodec, profile, level)) { + if (!ExtractH264CodecDetails(aCodec, profile, constraint, level)) { return false; } // Just assume what we can play on all platforms the codecs/formats that // WMF can play, since we don't have documentation about what other @@ -47,103 +48,133 @@ const MediaContainerType& aContainerType) { return IsSupportedType(aContainerType, nullptr); } +static bool IsTypeValid(const MediaContainerType& aType) +{ + // Whitelist MP4 types, so they explicitly match what we encounter on + // the web, as opposed to what we use internally (i.e. what our demuxers + // etc output). + return aType.Type() == MEDIAMIMETYPE("audio/mp4") || + aType.Type() == MEDIAMIMETYPE("audio/x-m4a") || + aType.Type() == MEDIAMIMETYPE("video/mp4") || + aType.Type() == MEDIAMIMETYPE("video/quicktime") || + aType.Type() == MEDIAMIMETYPE("video/x-m4v"); +} + +/* statis */ nsTArray> +MP4Decoder::GetTracksInfo(const MediaContainerType& aType, MediaResult& aError) +{ + nsTArray> tracks; + + if (!IsTypeValid(aType)) { + aError = MediaResult( + NS_ERROR_DOM_MEDIA_FATAL_ERR, + RESULT_DETAIL("Invalid type:%s", aType.Type().AsString().get())); + return tracks; + } + + aError = NS_OK; + + const MediaCodecs& codecs = aType.ExtendedType().Codecs(); + if (codecs.IsEmpty()) { + return tracks; + } + + const bool isVideo = aType.Type() == MEDIAMIMETYPE("video/mp4") || + aType.Type() == MEDIAMIMETYPE("video/quicktime") || + aType.Type() == MEDIAMIMETYPE("video/x-m4v"); + + for (const auto& codec : codecs.Range()) { + if (IsAACCodecString(codec)) { + tracks.AppendElement( + CreateTrackInfoWithMIMETypeAndContainerTypeExtraParameters( + NS_LITERAL_CSTRING("audio/mp4a-latm"), aType)); + continue; + } + if (codec.EqualsLiteral("mp3")) { + tracks.AppendElement( + CreateTrackInfoWithMIMETypeAndContainerTypeExtraParameters( + NS_LITERAL_CSTRING("audio/mpeg"), aType)); + continue; + } + if (codec.EqualsLiteral("opus") || codec.EqualsLiteral("flac")) { + tracks.AppendElement( + CreateTrackInfoWithMIMETypeAndContainerTypeExtraParameters( + NS_LITERAL_CSTRING("audio/") + NS_ConvertUTF16toUTF8(codec), aType)); + continue; + } + if (IsVP9CodecString(codec)) { + auto trackInfo = + CreateTrackInfoWithMIMETypeAndContainerTypeExtraParameters( + NS_LITERAL_CSTRING("video/vp9"), aType); + uint8_t profile = 0; + uint8_t level = 0; + uint8_t bitDepth = 0; + if (ExtractVPXCodecDetails(codec, profile, level, bitDepth)) { + trackInfo->GetAsVideoInfo()->mBitDepth = bitDepth; + } + tracks.AppendElement(std::move(trackInfo)); + continue; + } + if (isVideo && IsWhitelistedH264Codec(codec)) { + auto trackInfo = + CreateTrackInfoWithMIMETypeAndContainerTypeExtraParameters( + NS_LITERAL_CSTRING("video/avc"), aType); + uint8_t profile = 0, constraint = 0, level = 0; + MOZ_ALWAYS_TRUE( + ExtractH264CodecDetails(codec, profile, constraint, level)); + uint32_t width = aType.ExtendedType().GetWidth().refOr(1280); + uint32_t height = aType.ExtendedType().GetHeight().refOr(720); + trackInfo->GetAsVideoInfo()->mExtraData = + H264::CreateExtraData(profile, constraint, level, { width, height }); + tracks.AppendElement(std::move(trackInfo)); + continue; + } + // Unknown codec + aError = + MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR, + RESULT_DETAIL("Unknown codec:%s", + NS_ConvertUTF16toUTF8(codec).get())); + } + return tracks; +} + /* static */ bool MP4Decoder::IsSupportedType(const MediaContainerType& aType, DecoderDoctorDiagnostics* aDiagnostics) { if (!IsEnabled()) { return false; } - // Whitelist MP4 types, so they explicitly match what we encounter on - // the web, as opposed to what we use internally (i.e. what our demuxers - // etc output). - const bool isAudio = aType.Type() == MEDIAMIMETYPE("audio/mp4") || - aType.Type() == MEDIAMIMETYPE("audio/x-m4a"); - const bool isVideo = aType.Type() == MEDIAMIMETYPE("video/mp4") || - aType.Type() == MEDIAMIMETYPE("video/quicktime") || - aType.Type() == MEDIAMIMETYPE("video/x-m4v"); - - if (!isAudio && !isVideo) { + MediaResult rv = NS_OK; + auto tracks = GetTracksInfo(aType, rv); + if (NS_FAILED(rv)) { return false; } - nsTArray> trackInfos; - if (aType.ExtendedType().Codecs().IsEmpty()) { - // No codecs specified. Assume H.264 - if (isAudio) { - trackInfos.AppendElement( + if (tracks.IsEmpty()) { + // No codecs specified. Assume H.264 or AAC + if (aType.Type() == MEDIAMIMETYPE("audio/mp4") || + aType.Type() == MEDIAMIMETYPE("audio/x-m4a")) { + tracks.AppendElement( CreateTrackInfoWithMIMETypeAndContainerTypeExtraParameters( NS_LITERAL_CSTRING("audio/mp4a-latm"), aType)); } else { - MOZ_ASSERT(isVideo); - trackInfos.AppendElement( + tracks.AppendElement( CreateTrackInfoWithMIMETypeAndContainerTypeExtraParameters( NS_LITERAL_CSTRING("video/avc"), aType)); } - } else { - // Verify that all the codecs specified are ones that we expect that - // we can play. - for (const auto& codec : aType.ExtendedType().Codecs().Range()) { - if (IsAACCodecString(codec)) { - trackInfos.AppendElement( - CreateTrackInfoWithMIMETypeAndContainerTypeExtraParameters( - NS_LITERAL_CSTRING("audio/mp4a-latm"), aType)); - continue; - } - if (codec.EqualsLiteral("mp3")) { - trackInfos.AppendElement( - CreateTrackInfoWithMIMETypeAndContainerTypeExtraParameters( - NS_LITERAL_CSTRING("audio/mpeg"), aType)); - continue; - } - if (codec.EqualsLiteral("opus")) { - trackInfos.AppendElement( - CreateTrackInfoWithMIMETypeAndContainerTypeExtraParameters( - NS_LITERAL_CSTRING("audio/opus"), aType)); - continue; - } - if (codec.EqualsLiteral("flac")) { - trackInfos.AppendElement( - CreateTrackInfoWithMIMETypeAndContainerTypeExtraParameters( - NS_LITERAL_CSTRING("audio/flac"), aType)); - continue; - } - if (IsVP9CodecString(codec)) { - auto trackInfo = - CreateTrackInfoWithMIMETypeAndContainerTypeExtraParameters( - NS_LITERAL_CSTRING("video/vp9"), aType); - uint8_t profile = 0; - uint8_t level = 0; - uint8_t bitDepth = 0; - if (ExtractVPXCodecDetails(codec, profile, level, bitDepth)) { - trackInfo->GetAsVideoInfo()->mBitDepth = bitDepth; - } - trackInfos.AppendElement(std::move(trackInfo)); - continue; - } - // Note: Only accept H.264 in a video content type, not in an audio - // content type. - if (IsWhitelistedH264Codec(codec) && isVideo) { - trackInfos.AppendElement( - CreateTrackInfoWithMIMETypeAndContainerTypeExtraParameters( - NS_LITERAL_CSTRING("video/avc"), aType)); - continue; - } - // Some unsupported codec. - return false; - } } // Verify that we have a PDM that supports the whitelisted types. RefPtr platform = new PDMFactory(); - for (const auto& trackInfo : trackInfos) { - if (!trackInfo || !platform->Supports(*trackInfo, aDiagnostics)) { + for (const auto& track : tracks) { + if (!track || !platform->Supports(*track, aDiagnostics)) { return false; } } return true; @@ -169,6 +200,13 @@ MP4Decoder::IsEnabled() { return StaticPrefs::mediaMp4Enabled(); } +/* static */ nsTArray> +MP4Decoder::GetTracksInfo(const MediaContainerType& aType) +{ + MediaResult rv = NS_OK; + return GetTracksInfo(aType, rv); +} + } // namespace mozilla diff --git a/dom/media/mp4/MoofParser.cpp b/dom/media/mp4/MoofParser.cpp --- a/dom/media/mp4/MoofParser.cpp +++ b/dom/media/mp4/MoofParser.cpp @@ -51,11 +51,10 @@ bool MoofParser::RebuildFragmentedIndex(BoxContext& aContext) { bool foundValidMoof = false; - bool foundMdat = false; for (Box box(&aContext, mOffset); box.IsAvailable(); box = box.Next()) { if (box.IsType("moov") && mInitRange.IsEmpty()) { mInitRange = MediaByteRange(0, box.Range().mEnd); ParseMoov(box); @@ -604,11 +603,10 @@ LOG(Moof, "Incomplete Box (missing flags)"); return Err(NS_ERROR_FAILURE); } uint32_t flags; MOZ_TRY_VAR(flags, reader->ReadU32()); - uint8_t version = flags >> 24; if (!reader->CanReadType()) { LOG(Moof, "Incomplete Box (missing sampleCount)"); return Err(NS_ERROR_FAILURE); } diff --git a/dom/media/ogg/OggDecoder.h b/dom/media/ogg/OggDecoder.h --- a/dom/media/ogg/OggDecoder.h +++ b/dom/media/ogg/OggDecoder.h @@ -4,21 +4,27 @@ * 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/. */ #if !defined(OggDecoder_h_) #define OggDecoder_h_ +#include "mozilla/UniquePtr.h" +#include "nsTArray.h" + namespace mozilla { class MediaContainerType; +class TrackInfo; class OggDecoder { public: // Returns true if aContainerType is an Ogg type that we think we can render // with an enabled platform decoder backend. // If provided, codecs are checked for support. static bool IsSupportedType(const MediaContainerType& aContainerType); + static nsTArray> GetTracksInfo( + const MediaContainerType& aType); }; } // namespace mozilla #endif diff --git a/dom/media/ogg/OggDecoder.cpp b/dom/media/ogg/OggDecoder.cpp --- a/dom/media/ogg/OggDecoder.cpp +++ b/dom/media/ogg/OggDecoder.cpp @@ -28,20 +28,19 @@ const bool isOggVideo = (aContainerType.Type() != MEDIAMIMETYPE(AUDIO_OGG)); const MediaCodecs& codecs = aContainerType.ExtendedType().Codecs(); if (codecs.IsEmpty()) { - // WebM guarantees that the only codecs it contained are vp8, vp9, opus or vorbis. + // Ogg guarantees that the only codecs it contained are supported. return true; } // Verify that all the codecs specified are ones that we expect that // we can play. for (const auto& codec : codecs.Range()) { if ((MediaDecoder::IsOpusEnabled() && codec.EqualsLiteral("opus")) || codec.EqualsLiteral("vorbis") || - (StaticPrefs::MediaOggFlacEnabled() && - codec.EqualsLiteral("flac"))) { + codec.EqualsLiteral("flac")) { continue; } // Note: Only accept Theora in a video container type, not in an audio // container type. if (isOggVideo && codec.EqualsLiteral("theora")) { @@ -51,6 +50,36 @@ return false; } return true; } +/* static */ nsTArray> +OggDecoder::GetTracksInfo(const MediaContainerType& aType) +{ + nsTArray> tracks; + if (!IsSupportedType(aType)) { + return tracks; + } + + const MediaCodecs& codecs = aType.ExtendedType().Codecs(); + if (codecs.IsEmpty()) { + // Codecs must be specified for ogg as it can't be implied. + return tracks; + } + + for (const auto& codec : codecs.Range()) { + if (codec.EqualsLiteral("opus") || codec.EqualsLiteral("vorbis") || + codec.EqualsLiteral("flac")) { + tracks.AppendElement( + CreateTrackInfoWithMIMETypeAndContainerTypeExtraParameters( + NS_LITERAL_CSTRING("audio/") + NS_ConvertUTF16toUTF8(codec), aType)); + } else { + MOZ_ASSERT(codec.EqualsLiteral("theora")); + tracks.AppendElement( + CreateTrackInfoWithMIMETypeAndContainerTypeExtraParameters( + NS_LITERAL_CSTRING("video/") + NS_ConvertUTF16toUTF8(codec), aType)); + } + } + return tracks; +} + } // namespace mozilla diff --git a/dom/media/ogg/OggDemuxer.cpp b/dom/media/ogg/OggDemuxer.cpp --- a/dom/media/ogg/OggDemuxer.cpp +++ b/dom/media/ogg/OggDemuxer.cpp @@ -536,12 +536,11 @@ } } else { NS_WARNING("Opus decoding disabled." " See media.opus.enabled in about:config"); } - } else if (StaticPrefs::MediaOggFlacEnabled() && - s->GetType() == OggCodecState::TYPE_FLAC && + } else if (s->GetType() == OggCodecState::TYPE_FLAC && ReadHeaders(TrackInfo::kAudioTrack, s)) { if (!mFlacState) { SetupTarget(&mFlacState, s); } else { s->Deactivate(); diff --git a/dom/media/platforms/PDMFactory.cpp b/dom/media/platforms/PDMFactory.cpp --- a/dom/media/platforms/PDMFactory.cpp +++ b/dom/media/platforms/PDMFactory.cpp @@ -292,11 +292,12 @@ NS_ERROR_DOM_MEDIA_FATAL_ERR, RESULT_DETAIL("Decoder configuration error, expected audio or video.")); return nullptr; } - if (MP4Decoder::IsH264(config.mMimeType) && !aParams.mUseNullDecoder.mUse) { + if (MP4Decoder::IsH264(config.mMimeType) && !aParams.mUseNullDecoder.mUse && + !aParams.mNoWrapper.mDontUseWrapper) { RefPtr h = new H264Converter(aPDM, aParams); const MediaResult result = h->GetLastError(); if (NS_SUCCEEDED(result) || result == NS_ERROR_NOT_INITIALIZED) { // The H264Converter either successfully created the wrapped decoder, // or there wasn't enough AVCC data to do so. Otherwise, there was some diff --git a/dom/media/platforms/PlatformDecoderModule.h b/dom/media/platforms/PlatformDecoderModule.h --- a/dom/media/platforms/PlatformDecoderModule.h +++ b/dom/media/platforms/PlatformDecoderModule.h @@ -58,10 +58,21 @@ UseNullDecoder() = default; explicit UseNullDecoder(bool aUseNullDecoder) : mUse(aUseNullDecoder) { } bool mUse = false; }; + // Do not wrap H264 decoder in a H264Converter. + struct NoWrapper + { + NoWrapper() = default; + explicit NoWrapper(bool aDontUseWrapper) + : mDontUseWrapper(aDontUseWrapper) + { + } + bool mDontUseWrapper = false; + }; + struct VideoFrameRate { VideoFrameRate() = default; explicit VideoFrameRate(float aFramerate) : mValue(aFramerate) { } float mValue = 0.0f; @@ -100,10 +111,11 @@ layers::ImageContainer* mImageContainer = nullptr; MediaResult* mError = nullptr; RefPtr mKnowsCompositor; RefPtr mCrashHelper; UseNullDecoder mUseNullDecoder; + NoWrapper mNoWrapper; TrackInfo::TrackType mType = TrackInfo::kUndefinedTrack; MediaEventProducer* mOnWaitingForKeyEvent = nullptr; OptionSet mOptions = OptionSet(Option::Default); VideoFrameRate mRate; @@ -118,10 +130,11 @@ mImageContainer = aImageContainer; } void Set(MediaResult* aError) { mError = aError; } void Set(GMPCrashHelper* aCrashHelper) { mCrashHelper = aCrashHelper; } void Set(UseNullDecoder aUseNullDecoder) { mUseNullDecoder = aUseNullDecoder; } + void Set(NoWrapper aNoWrapper) { mNoWrapper = aNoWrapper; } void Set(OptionSet aOptions) { mOptions = aOptions; } void Set(VideoFrameRate aRate) { mRate = aRate; } void Set(layers::KnowsCompositor* aKnowsCompositor) { if (aKnowsCompositor) { diff --git a/dom/media/platforms/agnostic/bytestreams/H264.h b/dom/media/platforms/agnostic/bytestreams/H264.h --- a/dom/media/platforms/agnostic/bytestreams/H264.h +++ b/dom/media/platforms/agnostic/bytestreams/H264.h @@ -471,19 +471,28 @@ }; // Returns the frame type. Returns I_FRAME if the sample is an IDR // (Instantaneous Decoding Refresh) Picture. static FrameType GetFrameType(const mozilla::MediaRawData* aSample); + // Create a dummy extradata, useful to create a decoder and test the + // capabilities of the decoder. + static already_AddRefed CreateExtraData( + uint8_t aProfile, + uint8_t aConstraints, + uint8_t aLevel, + const gfx::IntSize& aSize); private: friend class SPSNAL; /* Extract RAW BYTE SEQUENCE PAYLOAD from NAL content. Returns nullptr if invalid content. This is compliant to ITU H.264 7.3.1 Syntax in tabular form NAL unit syntax */ static already_AddRefed DecodeNALUnit( const uint8_t* aNAL, size_t aLength); + static already_AddRefed EncodeNALUnit( + const uint8_t* aNAL, size_t aLength); /* Decode SPS NAL RBSP and fill SPSData structure */ static bool DecodeSPS(const mozilla::MediaByteBuffer* aSPS, SPSData& aDest); static bool vui_parameters(mozilla::BitReader& aBr, SPSData& aDest); // Read HRD parameters, all data is ignored. static void hrd_parameters(mozilla::BitReader& aBr); diff --git a/dom/media/platforms/agnostic/bytestreams/H264.cpp b/dom/media/platforms/agnostic/bytestreams/H264.cpp --- a/dom/media/platforms/agnostic/bytestreams/H264.cpp +++ b/dom/media/platforms/agnostic/bytestreams/H264.cpp @@ -4,10 +4,11 @@ #include "mozilla/ArrayUtils.h" #include "mozilla/PodOperations.h" #include "mozilla/ResultExtensions.h" #include "BitReader.h" +#include "BitWriter.h" #include "BufferReader.h" #include "ByteWriter.h" #include "AnnexB.h" #include "H264.h" #include @@ -118,56 +119,10 @@ scaling_list(BitReader& aBr, uint8_t (&aScalingList)[N], const uint8_t (&aDefaultList)[N]) { detail::scaling_list(aBr, aScalingList, N, aDefaultList, nullptr); } -static uint32_t -GetBitLength(const mozilla::MediaByteBuffer* aNAL) -{ - size_t size = aNAL->Length(); - - while (size > 0 && aNAL->ElementAt(size - 1) == 0) { - size--; - } - - if (!size) { - return 0; - } - - if (size > UINT32_MAX / 8) { - // We can't represent it, we'll use as much as we can. - return UINT32_MAX; - } - - uint8_t v = aNAL->ElementAt(size - 1); - size *= 8; - - // Remove the stop bit and following trailing zeros. - if (v) { - // Count the consecutive zero bits (trailing) on the right by binary search. - // Adapted from Matt Whitlock algorithm to only bother with 8 bits integers. - uint32_t c; - if (v & 1) { - // Special case for odd v (assumed to happen half of the time). - c = 0; - } else { - c = 1; - if ((v & 0xf) == 0) { - v >>= 4; - c += 4; - } - if ((v & 0x3) == 0) { - v >>= 2; - c += 2; - } - c -= v & 0x1; - } - size -= c + 1; - } - return size; -} - SPSData::SPSData() { PodZero(this); // Default values when they aren't defined as per ITU-T H.264 (2014/02). chroma_format_idc = 1; @@ -203,11 +158,11 @@ if (aLength == 0 || (*aPtr & 0x1f) != H264_NAL_SPS) { return; } mDecodedNAL = H264::DecodeNALUnit(aPtr, aLength); if (mDecodedNAL) { - mLength = GetBitLength(mDecodedNAL); + mLength = BitReader::GetBitLength(mDecodedNAL); } } SPSNAL() { } @@ -388,10 +343,44 @@ lastbytes = (lastbytes << 8) | byte; } return rbsp.forget(); } +// The reverse of DecodeNALUnit. To allow the distinction between Annex B (that +// uses 0x000001 as marker) and AVCC, the pattern 0x00 0x00 0x0n (where n is +// between 0 and 3) can't be found in the bytestream. A 0x03 byte is inserted +// after the second 0. Eg. 0x00 0x00 0x00 becomes 0x00 0x00 0x03 0x00 +/* static */ already_AddRefed +H264::EncodeNALUnit(const uint8_t* aNAL, size_t aLength) +{ + MOZ_ASSERT(aNAL); + RefPtr rbsp = new MediaByteBuffer(); + BufferReader reader(aNAL, aLength); + + auto res = reader.ReadU8(); + if (res.isErr()) { + return rbsp.forget(); + } + rbsp->AppendElement(res.unwrap()); + + res = reader.ReadU8(); + if (res.isErr()) { + return rbsp.forget(); + } + rbsp->AppendElement(res.unwrap()); + + while((res = reader.ReadU8()).isOk()) { + uint8_t val = res.unwrap(); + if (val <= 0x03 && rbsp->ElementAt(rbsp->Length() - 2) == 0 && + rbsp->ElementAt(rbsp->Length() - 1) == 0) { + rbsp->AppendElement(0x03); + } + rbsp->AppendElement(val); + } + return rbsp.forget(); +} + static int32_t ConditionDimension(float aValue) { // This will exclude NaNs and too-big values. if (aValue > 1.0 && aValue <= INT32_MAX) @@ -403,11 +392,11 @@ H264::DecodeSPS(const mozilla::MediaByteBuffer* aSPS, SPSData& aDest) { if (!aSPS) { return false; } - BitReader br(aSPS, GetBitLength(aSPS)); + BitReader br(aSPS, BitReader::GetBitLength(aSPS)); aDest.profile_idc = br.ReadBits(8); aDest.constraint_set0_flag = br.ReadBit(); aDest.constraint_set1_flag = br.ReadBit(); aDest.constraint_set2_flag = br.ReadBit(); @@ -1063,9 +1052,104 @@ } while(br.PeekU8().isOk() && br.PeekU8().unwrap() != 0x80); // more_rbsp_data() msg[offset] != 0x80 // ignore the trailing bits rbsp_trailing_bits(); return false; } +/*static */ already_AddRefed +H264::CreateExtraData(uint8_t aProfile, + uint8_t aConstraints, + uint8_t aLevel, + const gfx::IntSize& aSize) +{ + // SPS of a 144p video. + const uint8_t originSPS[] = { 0x4d, 0x40, 0x0c, 0xe8, 0x80, 0x80, 0x9d, + 0x80, 0xb5, 0x01, 0x01, 0x01, 0x40, 0x00, + 0x00, 0x00, 0x40, 0x00, 0x00, 0x0f, 0x03, + 0xc5, 0x0a, 0x44, 0x80 }; + + RefPtr extraData = new MediaByteBuffer(); + extraData->AppendElements(originSPS, sizeof(originSPS)); + BitReader br(extraData, BitReader::GetBitLength(extraData)); + + RefPtr sps = new MediaByteBuffer(); + BitWriter bw(sps); + + br.ReadBits(8); // Skip original profile_idc + bw.WriteU8(aProfile); + br.ReadBits(8); // Skip original constraint flags && reserved_zero_2bits + aConstraints = aConstraints & ~0x3; // Ensure reserved_zero_2bits are set to 0 + bw.WriteBits(aConstraints, 8); + br.ReadBits(8); // Skip original level_idc + bw.WriteU8(aLevel); + bw.WriteUE(br.ReadUE()); // seq_parameter_set_id (0 stored on 1 bit) + + if (aProfile == 100 || aProfile == 110 || aProfile == 122 || + aProfile == 244 || aProfile == 44 || aProfile == 83 || aProfile == 86 || + aProfile == 118 || aProfile == 128 || aProfile == 138 || + aProfile == 139 || aProfile == 134) { + bw.WriteUE(1); // chroma_format_idc -> always set to 4:2:0 chroma format + bw.WriteUE(0); // bit_depth_luma_minus8 -> always 8 bits here + bw.WriteUE(0); // bit_depth_chroma_minus8 -> always 8 bits here + } + + bw.WriteBits(br.ReadBits(11), + 11); // log2_max_frame_num to gaps_in_frame_num_allowed_flag + + // skip over original exp-golomb encoded width/height + br.ReadUE(); // skip width + br.ReadUE(); // skip height + uint32_t width = aSize.width; + uint32_t widthNeeded = width % 16 != 0 ? (width / 16 + 1) * 16 : width; + uint32_t height = aSize.height; + uint32_t heightNeeded = height % 16 != 0 ? (height / 16 + 1) * 16 : height; + bw.WriteUE(widthNeeded / 16 - 1); + bw.WriteUE(heightNeeded / 16 - 1); + bw.WriteBit(br.ReadBit()); // write frame_mbs_only_flag + bw.WriteBit(br.ReadBit()); // write direct_8x8_inference_flag; + if (widthNeeded != width || heightNeeded != height) { + // Write cropping value + bw.WriteBit(true); // skip frame_cropping_flag + bw.WriteUE(0); // frame_crop_left_offset + bw.WriteUE((widthNeeded - width) / 2); // frame_crop_right_offset + bw.WriteUE(0); // frame_crop_top_offset + bw.WriteUE((heightNeeded - height) / 2); // frame_crop_bottom_offset + } else { + bw.WriteBit(false); // skip frame_cropping_flag + } + br.ReadBit(); // skip frame_cropping_flag; + // Write the remainings of the original sps (vui_parameters which sets an + // aspect ration of 1.0) + while (br.BitsLeft()) { + bw.WriteBit(br.ReadBit()); + } + bw.CloseWithRbspTrailing(); + + RefPtr encodedSPS = + EncodeNALUnit(sps->Elements(), sps->Length()); + extraData->Clear(); + extraData->AppendElement(1); + extraData->AppendElement(aProfile); + extraData->AppendElement(aConstraints); + extraData->AppendElement(aLevel); + extraData->AppendElement(3); // nalLENSize-1 + extraData->AppendElement(1); // numPPS + uint8_t c[2]; + mozilla::BigEndian::writeUint16(&c[0], encodedSPS->Length() + 1); + extraData->AppendElements(c, 2); + extraData->AppendElement((0x00 << 7) | (0x3 << 5 ) | H264_NAL_SPS); + extraData->AppendElements(*encodedSPS); + + const uint8_t PPS[] = { 0xeb, 0xef, 0x20 }; + + extraData->AppendElement(1); // numPPS + mozilla::BigEndian::writeUint16(&c[0], sizeof(PPS) + 1); + extraData->AppendElements(c, 2); + extraData->AppendElement((0x00 << 7) | (0x3 << 5 ) | H264_NAL_PPS); + extraData->AppendElements(PPS, sizeof(PPS)); + + return extraData.forget(); +} + #undef READUE #undef READSE } // namespace mozilla diff --git a/dom/media/platforms/wmf/DXVA2Manager.cpp b/dom/media/platforms/wmf/DXVA2Manager.cpp --- a/dom/media/platforms/wmf/DXVA2Manager.cpp +++ b/dom/media/platforms/wmf/DXVA2Manager.cpp @@ -650,11 +650,11 @@ RefPtr mSyncObject; GUID mDecoderGUID; uint32_t mWidth = 0; uint32_t mHeight = 0; UINT mDeviceManagerToken = 0; - bool mConfiuredForSize = false; + bool mConfiguredForSize = false; }; bool D3D11DXVA2Manager::SupportsConfig(IMFMediaType* aType, float aFramerate) { @@ -1090,11 +1090,11 @@ } HRESULT D3D11DXVA2Manager::ConfigureForSize(uint32_t aWidth, uint32_t aHeight) { - if (mConfiuredForSize && aWidth == mWidth && aHeight == mHeight) { + if (mConfiguredForSize && aWidth == mWidth && aHeight == mHeight) { // If the size hasn't changed, don't reconfigure. return S_OK; } mWidth = aWidth; @@ -1146,11 +1146,11 @@ hr = mTransform->SetMediaTypes(inputType, outputType, ConfigureOutput, &size); }); NS_ENSURE_TRUE(SUCCEEDED(hr), hr); - mConfiuredForSize = true; + mConfiguredForSize = true; return S_OK; } bool diff --git a/dom/media/platforms/wmf/WMFDecoderModule.cpp b/dom/media/platforms/wmf/WMFDecoderModule.cpp --- a/dom/media/platforms/wmf/WMFDecoderModule.cpp +++ b/dom/media/platforms/wmf/WMFDecoderModule.cpp @@ -29,10 +29,12 @@ #include "nsServiceManagerUtils.h" #include "nsWindowsHelpers.h" #include "prsystem.h" #include "nsIXULRuntime.h" #include "mozilla/mscom/EnsureMTA.h" +#include +#include extern const GUID CLSID_WebmMfVpxDec; extern const GUID CLSID_AMDWebmMfVp9Dec; namespace mozilla { @@ -202,10 +204,21 @@ } if ((aTrackInfo.mMimeType.EqualsLiteral("audio/mp4a-latm") || aTrackInfo.mMimeType.EqualsLiteral("audio/mp4")) && WMFDecoderModule::HasAAC()) { + const auto audioInfo = aTrackInfo.GetAsAudioInfo(); + if (audioInfo && audioInfo->mRate > 0) { + // Supported sampling rates per: + // https://msdn.microsoft.com/en-us/library/windows/desktop/dd742784(v=vs.85).aspx + const std::vector frequencies = { + 8000, 11025, 12000, 16000, 22050, 24000, 32000, 44100, 48000, + }; + return std::find(frequencies.begin(), + frequencies.end(), + audioInfo->mRate) != frequencies.end(); + } return true; } if (MP4Decoder::IsH264(aTrackInfo.mMimeType) && WMFDecoderModule::HasH264()) { return true; } diff --git a/dom/media/platforms/wrappers/H264Converter.cpp b/dom/media/platforms/wrappers/H264Converter.cpp --- a/dom/media/platforms/wrappers/H264Converter.cpp +++ b/dom/media/platforms/wrappers/H264Converter.cpp @@ -227,11 +227,18 @@ H264Converter::IsHardwareAccelerated(nsACString& aFailureReason) const { if (mDecoder) { return mDecoder->IsHardwareAccelerated(aFailureReason); } +#ifdef MOZ_APPLEMEDIA + // On mac, we can assume H264 is hardware accelerated for now. + // This allows MediaCapabilities to report that playback will be smooth. + // Which will always be. + return true; +#else return MediaDataDecoder::IsHardwareAccelerated(aFailureReason); +#endif } void H264Converter::SetSeekThreshold(const media::TimeUnit& aTime) { diff --git a/dom/media/systemservices/MediaUtils.h b/dom/media/systemservices/MediaUtils.h --- a/dom/media/systemservices/MediaUtils.h +++ b/dom/media/systemservices/MediaUtils.h @@ -5,16 +5,16 @@ * You can obtain one at http://mozilla.org/MPL/2.0/. */ #ifndef mozilla_MediaUtils_h #define mozilla_MediaUtils_h -#include "AutoTaskQueue.h" #include "mozilla/Assertions.h" #include "mozilla/Monitor.h" #include "mozilla/MozPromise.h" #include "mozilla/RefPtr.h" #include "mozilla/SharedThreadPool.h" +#include "mozilla/TaskQueue.h" #include "mozilla/UniquePtr.h" #include "nsCOMPtr.h" #include "nsIAsyncShutdown.h" #include "nsISupportsImpl.h" #include "nsThreadUtils.h" @@ -436,14 +436,13 @@ already_AddRefed aPool, RefPtr> aPromise, ResolveFunction&& aResolveFunction, RejectFunction&& aRejectFunction) { - RefPtr taskQueue = - new AutoTaskQueue(std::move(aPool), "MozPromiseAwait"); - // We can't use a Monitor allocated on the stack (see bug 1426067) - Monitor& mon = taskQueue->Monitor(); + RefPtr taskQueue = + new TaskQueue(std::move(aPool), "MozPromiseAwait"); + Monitor mon(__func__); bool done = false; aPromise->Then(taskQueue, __func__, [&](ResolveValueType&& aResolveValue) { @@ -469,14 +468,13 @@ typename MozPromise:: ResolveOrRejectValue Await(already_AddRefed aPool, RefPtr> aPromise) { - RefPtr taskQueue = - new AutoTaskQueue(std::move(aPool), "MozPromiseAwait"); - // We can't use a Monitor allocated on the stack (see bug 1426067) - Monitor& mon = taskQueue->Monitor(); + RefPtr taskQueue = + new TaskQueue(std::move(aPool), "MozPromiseAwait"); + Monitor mon(__func__); bool done = false; typename MozPromise::ResolveOrRejectValue val; aPromise->Then(taskQueue, __func__, @@ -516,14 +514,16 @@ ResolveFunction&& aResolveFunction, RejectFunction&& aRejectFunction) { typedef MozPromise Promise; RefPtr pool = aPool; - RefPtr taskQueue = - new AutoTaskQueue(do_AddRef(pool), "MozPromiseAwaitAll"); - RefPtr p = Promise::All(taskQueue, aPromises); - Await(pool.forget(), p, std::move(aResolveFunction), std::move(aRejectFunction)); + RefPtr taskQueue = + new TaskQueue(do_AddRef(pool), "MozPromiseAwaitAll"); + RefPtr p = + Promise::All(taskQueue, aPromises); + Await( + pool.forget(), p, std::move(aResolveFunction), std::move(aRejectFunction)); } // Note: only works with exclusive MozPromise, as Promise::All would attempt // to perform copy of nsTArrays which are disallowed. template @@ -534,12 +534,12 @@ nsTArray>>& aPromises) { typedef MozPromise Promise; RefPtr pool = aPool; - RefPtr taskQueue = - new AutoTaskQueue(do_AddRef(pool), "MozPromiseAwaitAll"); + RefPtr taskQueue = + new TaskQueue(do_AddRef(pool), "MozPromiseAwaitAll"); RefPtr p = Promise::All(taskQueue, aPromises); return Await(pool.forget(), p); } diff --git a/dom/media/wave/WaveDecoder.h b/dom/media/wave/WaveDecoder.h --- a/dom/media/wave/WaveDecoder.h +++ b/dom/media/wave/WaveDecoder.h @@ -4,20 +4,26 @@ * 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/. */ #if !defined(WaveDecoder_h_) #define WaveDecoder_h_ +#include "mozilla/UniquePtr.h" +#include "nsTArray.h" + namespace mozilla { class MediaContainerType; +class TrackInfo; class WaveDecoder { public: // Returns true if the Wave backend is pref'ed on, and we're running on a // platform that is likely to have decoders for the format. static bool IsSupportedType(const MediaContainerType& aContainerType); + static nsTArray> GetTracksInfo( + const MediaContainerType& aType); }; } // namespace mozilla #endif diff --git a/dom/media/wave/WaveDecoder.cpp b/dom/media/wave/WaveDecoder.cpp --- a/dom/media/wave/WaveDecoder.cpp +++ b/dom/media/wave/WaveDecoder.cpp @@ -27,6 +27,31 @@ } return false; } +/* static */ nsTArray> +WaveDecoder::GetTracksInfo(const MediaContainerType& aType) +{ + nsTArray> tracks; + if (!IsSupportedType(aType)) { + return tracks; + } + + const MediaCodecs& codecs = aType.ExtendedType().Codecs(); + if (codecs.IsEmpty()) { + tracks.AppendElement( + CreateTrackInfoWithMIMETypeAndContainerTypeExtraParameters( + NS_LITERAL_CSTRING("audio/x-wav"), aType)); + return tracks; + } + + for (const auto& codec : codecs.Range()) { + tracks.AppendElement( + CreateTrackInfoWithMIMETypeAndContainerTypeExtraParameters( + NS_LITERAL_CSTRING("audio/wave; codecs=") + NS_ConvertUTF16toUTF8(codec), + aType)); + } + return tracks; +} + } // namespace mozilla diff --git a/dom/media/webm/WebMDecoder.h b/dom/media/webm/WebMDecoder.h --- a/dom/media/webm/WebMDecoder.h +++ b/dom/media/webm/WebMDecoder.h @@ -4,23 +4,33 @@ * 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/. */ #if !defined(WebMDecoder_h_) #define WebMDecoder_h_ +#include "mozilla/UniquePtr.h" +#include "nsTArray.h" + namespace mozilla { class MediaContainerType; +class TrackInfo; class WebMDecoder { public: // Returns true if aContainerType is a WebM type that we think we can render // with an enabled platform decoder backend. // If provided, codecs are checked for support. static bool IsSupportedType(const MediaContainerType& aContainerType); + static nsTArray> GetTracksInfo( + const MediaContainerType& aType); +private: + static nsTArray> GetTracksInfo( + const MediaContainerType& aType, + MediaResult& aError); }; } // namespace mozilla #endif diff --git a/dom/media/webm/WebMDecoder.cpp b/dom/media/webm/WebMDecoder.cpp --- a/dom/media/webm/WebMDecoder.cpp +++ b/dom/media/webm/WebMDecoder.cpp @@ -3,10 +3,11 @@ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "WebMDecoder.h" +#include "mozilla/Move.h" #include "mozilla/Preferences.h" #include "mozilla/StaticPrefs.h" #ifdef MOZ_AV1 #include "AOMDecoder.h" #endif @@ -14,72 +15,110 @@ #include "PDMFactory.h" #include "VideoUtils.h" namespace mozilla { -/* static */ -bool -WebMDecoder::IsSupportedType(const MediaContainerType& aContainerType) +/* static */ nsTArray> +WebMDecoder::GetTracksInfo(const MediaContainerType& aType, MediaResult& aError) { - if (!Preferences::GetBool("media.webm.enabled")) { - return false; - } + nsTArray> tracks; + const bool isVideo = aType.Type() == MEDIAMIMETYPE("video/webm"); - bool isVideo = aContainerType.Type() == MEDIAMIMETYPE("video/webm"); - if (aContainerType.Type() != MEDIAMIMETYPE("audio/webm") && !isVideo) { - return false; + if (aType.Type() != MEDIAMIMETYPE("audio/webm") && !isVideo) { + aError = MediaResult( + NS_ERROR_DOM_MEDIA_FATAL_ERR, + RESULT_DETAIL("Invalid type:%s", aType.Type().AsString().get())); + return tracks; } - const MediaCodecs& codecs = aContainerType.ExtendedType().Codecs(); + aError = NS_OK; + + const MediaCodecs& codecs = aType.ExtendedType().Codecs(); if (codecs.IsEmpty()) { - // WebM guarantees that the only codecs it contained are vp8, vp9, opus or vorbis. - return true; + return tracks; } - // Verify that all the codecs specified are ones that we expect that - // we can play. + for (const auto& codec : codecs.Range()) { if (codec.EqualsLiteral("opus") || codec.EqualsLiteral("vorbis")) { + tracks.AppendElement( + CreateTrackInfoWithMIMETypeAndContainerTypeExtraParameters( + NS_LITERAL_CSTRING("audio/") + NS_ConvertUTF16toUTF8(codec), aType)); continue; } - // Note: Only accept VP8/VP9 in a video container type, not in an audio - // container type. - if (isVideo) { UniquePtr trackInfo; - if (IsVP9CodecString(codec)) { + if (IsVP9CodecString(codec)) { trackInfo = CreateTrackInfoWithMIMETypeAndContainerTypeExtraParameters( - NS_LITERAL_CSTRING("video/vp9"), aContainerType); + NS_LITERAL_CSTRING("video/vp9"), aType); } else if (IsVP8CodecString(codec)) { trackInfo = CreateTrackInfoWithMIMETypeAndContainerTypeExtraParameters( - NS_LITERAL_CSTRING("video/vp8"), aContainerType); + NS_LITERAL_CSTRING("video/vp8"), aType); } - // If it is vp8 or vp9, check the bit depth. if (trackInfo) { uint8_t profile = 0; uint8_t level = 0; uint8_t bitDepth = 0; if (ExtractVPXCodecDetails(codec, profile, level, bitDepth)) { trackInfo->GetAsVideoInfo()->mBitDepth = bitDepth; - - // Verify that we have a PDM that supports this bit depth. - RefPtr platform = new PDMFactory(); - if (!platform->Supports(*trackInfo, nullptr)) { - return false; - } } + tracks.AppendElement(std::move(trackInfo)); continue; } } #ifdef MOZ_AV1 - if (isVideo && StaticPrefs::MediaAv1Enabled() && - AOMDecoder::IsSupportedCodec(codec)) { + if (AOMDecoder::IsSupportedCodec(codec)) { + tracks.AppendElement( + CreateTrackInfoWithMIMETypeAndContainerTypeExtraParameters( + NS_LITERAL_CSTRING("video/av1"), aType)); continue; } #endif - // Some unsupported codec. + // Unknown codec + aError = + MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR, + RESULT_DETAIL("Unknown codec:%s", + NS_ConvertUTF16toUTF8(codec).get())); + } + return tracks; +} + +/* static */ +bool +WebMDecoder::IsSupportedType(const MediaContainerType& aContainerType) +{ + if (!Preferences::GetBool("media.webm.enabled")) { return false; } + + MediaResult rv = NS_OK; + auto tracks = GetTracksInfo(aContainerType, rv); + + if (NS_FAILED(rv)) { + return false; + } + + if (tracks.IsEmpty()) { + // WebM guarantees that the only codecs it contained are vp8, vp9, opus or vorbis. + return true; + } + + // Verify that we have a PDM that supports the whitelisted types, include + // bitdepth + RefPtr platform = new PDMFactory(); + for (const auto& track : tracks) { + if (!track || !platform->Supports(*track, nullptr /* diagnostic */)) { + return false; + } + } + return true; } +/* static */ nsTArray> +WebMDecoder::GetTracksInfo(const MediaContainerType& aType) +{ + MediaResult rv = NS_OK; + return GetTracksInfo(aType, rv); +} + } // namespace mozilla diff --git a/dom/moz.build b/dom/moz.build --- a/dom/moz.build +++ b/dom/moz.build @@ -37,10 +37,11 @@ 'bindings', 'battery', 'browser-element', 'cache', 'canvas', + 'webgpu', 'chrome-webidl', 'clients', 'commandhandler', 'credentialmanagement', 'crypto', diff --git a/dom/plugins/base/npapi.h b/dom/plugins/base/npapi.h --- a/dom/plugins/base/npapi.h +++ b/dom/plugins/base/npapi.h @@ -277,10 +277,22 @@ // to NPPSetValue. } NPAudioDeviceChangeDetails; #endif /* XP_WIN */ +/* + * This is the value of the NPNVaudioDeviceStateChanged variable. + */ +typedef struct _NPAudioDeviceStateChanged +{ + /* Name of device that changed state. This string is only valid during + * the call to NPPSetValue. + */ + const wchar_t* device; + uint32_t newState; +} NPAudioDeviceStateChanged; + typedef enum { NPDrawingModelDUMMY #if defined(XP_MACOSX) #ifndef NP_NO_QUICKDRAW , NPDrawingModelQuickDraw = 0 @@ -466,10 +478,11 @@ Cocoa text input specification. */ #endif , NPNVmuteAudioBool = 4000 /* Request that the browser wants to mute or unmute the plugin */ #if defined(XP_WIN) , NPNVaudioDeviceChangeDetails = 4001 /* Provides information about the new default audio device */ + , NPNVaudioDeviceStateChanged = 4002 /* Provides information if any audio device changes state */ #endif #if defined(XP_MACOSX) , NPNVsupportsCompositingCoreAnimationPluginsBool = 74656 /* TRUE if the browser supports CA model compositing */ #endif diff --git a/dom/plugins/base/nsPluginInstanceOwner.cpp b/dom/plugins/base/nsPluginInstanceOwner.cpp --- a/dom/plugins/base/nsPluginInstanceOwner.cpp +++ b/dom/plugins/base/nsPluginInstanceOwner.cpp @@ -417,11 +417,11 @@ if (content->IsEditable()) { return NS_OK; } - nsIDocument *doc = content->GetUncomposedDoc(); + nsIDocument *doc = content->GetComposedDoc(); if (!doc) { return NS_ERROR_FAILURE; } nsPresContext* presContext = doc->GetPresContext(); diff --git a/dom/plugins/base/nsPluginNativeWindowWin.cpp b/dom/plugins/base/nsPluginNativeWindowWin.cpp --- a/dom/plugins/base/nsPluginNativeWindowWin.cpp +++ b/dom/plugins/base/nsPluginNativeWindowWin.cpp @@ -355,23 +355,27 @@ LONG_PTR dwNewLong); typedef LONG_PTR (WINAPI *User32SetWindowLongPtrW)(HWND hWnd, int nIndex, LONG_PTR dwNewLong); -static User32SetWindowLongPtrA sUser32SetWindowLongAHookStub = nullptr; -static User32SetWindowLongPtrW sUser32SetWindowLongWHookStub = nullptr; +static WindowsDllInterceptor::FuncHookType + sUser32SetWindowLongAHookStub; +static WindowsDllInterceptor::FuncHookType + sUser32SetWindowLongWHookStub; #else typedef LONG (WINAPI *User32SetWindowLongA)(HWND hWnd, int nIndex, LONG dwNewLong); typedef LONG (WINAPI *User32SetWindowLongW)(HWND hWnd, int nIndex, LONG dwNewLong); -static User32SetWindowLongA sUser32SetWindowLongAHookStub = nullptr; -static User32SetWindowLongW sUser32SetWindowLongWHookStub = nullptr; +static WindowsDllInterceptor::FuncHookType + sUser32SetWindowLongAHookStub; +static WindowsDllInterceptor::FuncHookType + sUser32SetWindowLongWHookStub; #endif static inline bool SetWindowLongHookCheck(HWND hWnd, int nIndex, LONG_PTR newLong) @@ -446,27 +450,19 @@ static void HookSetWindowLongPtr() { sUser32Intercept.Init("user32.dll"); #ifdef _WIN64 - if (!sUser32SetWindowLongAHookStub) - sUser32Intercept.AddHook("SetWindowLongPtrA", - reinterpret_cast(SetWindowLongPtrAHook), - (void**) &sUser32SetWindowLongAHookStub); - if (!sUser32SetWindowLongWHookStub) - sUser32Intercept.AddHook("SetWindowLongPtrW", - reinterpret_cast(SetWindowLongPtrWHook), - (void**) &sUser32SetWindowLongWHookStub); + sUser32SetWindowLongAHookStub.Set(sUser32Intercept, "SetWindowLongPtrA", + &SetWindowLongPtrAHook); + sUser32SetWindowLongWHookStub.Set(sUser32Intercept, "SetWindowLongPtrW", + &SetWindowLongPtrWHook); #else - if (!sUser32SetWindowLongAHookStub) - sUser32Intercept.AddHook("SetWindowLongA", - reinterpret_cast(SetWindowLongAHook), - (void**) &sUser32SetWindowLongAHookStub); - if (!sUser32SetWindowLongWHookStub) - sUser32Intercept.AddHook("SetWindowLongW", - reinterpret_cast(SetWindowLongWHook), - (void**) &sUser32SetWindowLongWHookStub); + sUser32SetWindowLongAHookStub.Set(sUser32Intercept, "SetWindowLongA", + &SetWindowLongAHook); + sUser32SetWindowLongWHookStub.Set(sUser32Intercept, "SetWindowLongW", + &SetWindowLongWHook); #endif } /** * nsPluginNativeWindowWin implementation diff --git a/dom/plugins/ipc/FunctionHook.h b/dom/plugins/ipc/FunctionHook.h --- a/dom/plugins/ipc/FunctionHook.h +++ b/dom/plugins/ipc/FunctionHook.h @@ -7,10 +7,11 @@ #ifndef dom_plugins_ipc_functionhook_h #define dom_plugins_ipc_functionhook_h 1 #include "IpdlTuple.h" #include "base/process.h" +#include "mozilla/Atomics.h" #if defined(XP_WIN) #include "nsWindowsDllInterceptor.h" #endif @@ -94,16 +95,23 @@ typedef bool(ShouldHookFunc)(int aQuirks); template class BasicFunctionHook : public FunctionHook { +#if defined(XP_WIN) + using FuncHookType = WindowsDllInterceptor::FuncHookType; +#endif // defined(XP_WIN) + public: BasicFunctionHook(const char* aModuleName, const char* aFunctionName, FunctionType* aOldFunction, - FunctionType* aNewFunction) : - mOldFunction(aOldFunction), mRegistration(UNREGISTERED), mModuleName(aModuleName), - mFunctionName(aFunctionName), mNewFunction(aNewFunction) + FunctionType* aNewFunction) + : mOldFunction(aOldFunction) + , mRegistration(UNREGISTERED) + , mModuleName(aModuleName) + , mFunctionName(aFunctionName) + , mNewFunction(aNewFunction) { MOZ_ASSERT(mOldFunction); MOZ_ASSERT(mNewFunction); } @@ -126,11 +134,14 @@ protected: // Once the function is hooked, this field will take the value of a pointer to // a function that performs the old behavior. Before that, it is a pointer to // the original function. - FunctionType* mOldFunction; + Atomic mOldFunction; +#if defined(XP_WIN) + FuncHookType mStub; +#endif // defined(XP_WIN) enum RegistrationStatus { UNREGISTERED, FAILED, SUCCEEDED }; RegistrationStatus mRegistration; // The name of the module containing the function to hook. E.g. "user32.dll". @@ -168,18 +179,20 @@ FunctionHook::GetDllInterceptorFor(mModuleName.Data()); if (!dllInterceptor) { return false; } - isHooked = - dllInterceptor->AddHook(mFunctionName.Data(), reinterpret_cast(mNewFunction), - reinterpret_cast(&mOldFunction)); + isHooked = mStub.Set(*dllInterceptor, mFunctionName.Data(), mNewFunction); #endif if (isHooked) { +#if defined(XP_WIN) + mOldFunction = mStub.GetStub(); +#endif mRegistration = SUCCEEDED; } + HOOK_LOG(LogLevel::Debug, ("Registering to intercept function '%s' : '%s'", mFunctionName.Data(), SuccessMsg(isHooked))); return isHooked; diff --git a/dom/plugins/ipc/FunctionHook.cpp b/dom/plugins/ipc/FunctionHook.cpp --- a/dom/plugins/ipc/FunctionHook.cpp +++ b/dom/plugins/ipc/FunctionHook.cpp @@ -166,17 +166,17 @@ typedef HANDLE (WINAPI *CreateFileWPtr)(LPCWSTR aFname, DWORD aAccess, DWORD aShare, LPSECURITY_ATTRIBUTES aSecurity, DWORD aCreation, DWORD aFlags, HANDLE aFTemplate); -static CreateFileWPtr sCreateFileWStub = nullptr; +static WindowsDllInterceptor::FuncHookType sCreateFileWStub; typedef HANDLE (WINAPI *CreateFileAPtr)(LPCSTR aFname, DWORD aAccess, DWORD aShare, LPSECURITY_ATTRIBUTES aSecurity, DWORD aCreation, DWORD aFlags, HANDLE aFTemplate); -static CreateFileAPtr sCreateFileAStub = nullptr; +static WindowsDllInterceptor::FuncHookType sCreateFileAStub; // Windows 8 RTM (kernelbase's version is 6.2.9200.16384) doesn't call // CreateFileW from CreateFileA. // So we hook CreateFileA too to use CreateFileW hook. static HANDLE WINAPI @@ -261,11 +261,11 @@ HANDLE replacement = sCreateFileWStub(tempFile, GENERIC_READ | GENERIC_WRITE, aShare, aSecurity, TRUNCATE_EXISTING, FILE_ATTRIBUTE_TEMPORARY | FILE_FLAG_DELETE_ON_CLOSE, - NULL); + nullptr); if (replacement == INVALID_HANDLE_VALUE) { break; } HANDLE original = sCreateFileWStub(aFname, aAccess, aShare, aSecurity, @@ -298,27 +298,16 @@ aFTemplate); } void FunctionHook::HookProtectedMode() { - // Make sure we only do this once. - static bool sRunOnce = false; - if (sRunOnce) { - return; - } - sRunOnce = true; - // Legacy code. Uses the nsWindowsDLLInterceptor directly instead of // using the FunctionHook sKernel32Intercept.Init("kernel32.dll"); MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Plugin); - sKernel32Intercept.AddHook("CreateFileW", - reinterpret_cast(CreateFileWHookFn), - (void**) &sCreateFileWStub); - sKernel32Intercept.AddHook("CreateFileA", - reinterpret_cast(CreateFileAHookFn), - (void**) &sCreateFileAStub); + sCreateFileWStub.Set(sKernel32Intercept, "CreateFileW", &CreateFileWHookFn); + sCreateFileAStub.Set(sKernel32Intercept, "CreateFileA", &CreateFileAHookFn); } #endif // defined(XP_WIN) #define FUN_HOOK(x) static_cast(x) diff --git a/dom/plugins/ipc/PPluginModule.ipdl b/dom/plugins/ipc/PPluginModule.ipdl --- a/dom/plugins/ipc/PPluginModule.ipdl +++ b/dom/plugins/ipc/PPluginModule.ipdl @@ -13,10 +13,11 @@ using NPNVariable from "npapi.h"; using mozilla::dom::NativeThreadId from "mozilla/dom/TabMessageUtils.h"; using class mac_plugin_interposing::NSCursorInfo from "mozilla/plugins/PluginMessageUtils.h"; using struct nsID from "nsID.h"; using struct mozilla::plugins::NPAudioDeviceChangeDetailsIPC from "mozilla/plugins/PluginMessageUtils.h"; +using struct mozilla::plugins::NPAudioDeviceStateChangedIPC from "mozilla/plugins/PluginMessageUtils.h"; namespace mozilla { namespace plugins { struct PluginSettings @@ -91,10 +92,11 @@ returns (NativeThreadId tid); async SettingChanged(PluginSettings settings); async NPP_SetValue_NPNVaudioDeviceChangeDetails(NPAudioDeviceChangeDetailsIPC changeDetails); + async NPP_SetValue_NPNVaudioDeviceStateChanged(NPAudioDeviceStateChangedIPC deviceState); async InitPluginModuleChild(Endpoint endpoint); async InitPluginFunctionBroker(Endpoint endpoint); diff --git a/dom/plugins/ipc/PluginInstanceChild.h b/dom/plugins/ipc/PluginInstanceChild.h --- a/dom/plugins/ipc/PluginInstanceChild.h +++ b/dom/plugins/ipc/PluginInstanceChild.h @@ -254,10 +254,11 @@ const NativeEventData& aKeyEventData, const bool& aIsConsumed) override; #if defined(XP_WIN) NPError DefaultAudioDeviceChanged(NPAudioDeviceChangeDetails& details); + NPError AudioDeviceStateChanged(NPAudioDeviceStateChanged& aDeviceState); #endif private: friend class PluginModuleChild; diff --git a/dom/plugins/ipc/PluginInstanceChild.cpp b/dom/plugins/ipc/PluginInstanceChild.cpp --- a/dom/plugins/ipc/PluginInstanceChild.cpp +++ b/dom/plugins/ipc/PluginInstanceChild.cpp @@ -71,25 +71,21 @@ int nReserved, HWND hWnd, CONST RECT *prcRect); static WindowsDllInterceptor sUser32Intercept; static HWND sWinlessPopupSurrogateHWND = nullptr; -static User32TrackPopupMenu sUser32TrackPopupMenuStub = nullptr; +static WindowsDllInterceptor::FuncHookType sUser32TrackPopupMenuStub; static WindowsDllInterceptor sImm32Intercept; -static decltype(ImmGetContext)* sImm32ImmGetContextStub = nullptr; -static decltype(ImmGetCompositionStringW)* sImm32ImmGetCompositionStringStub = - nullptr; -static decltype(ImmSetCandidateWindow)* sImm32ImmSetCandidateWindowStub = - nullptr; -static decltype(ImmNotifyIME)* sImm32ImmNotifyIME = nullptr; -static decltype(ImmAssociateContextEx)* sImm32ImmAssociateContextExStub = - nullptr; +static WindowsDllInterceptor::FuncHookType sImm32ImmGetContextStub; +static WindowsDllInterceptor::FuncHookType sImm32ImmGetCompositionStringStub; +static WindowsDllInterceptor::FuncHookType sImm32ImmSetCandidateWindowStub; +static WindowsDllInterceptor::FuncHookType sImm32ImmNotifyIME; +static WindowsDllInterceptor::FuncHookType sImm32ImmAssociateContextExStub; + static PluginInstanceChild* sCurrentPluginInstance = nullptr; static const HIMC sHookIMC = (const HIMC)0xefefefef; -static bool sPopupMenuHookSet; -static bool sSetWindowLongHookSet; using mozilla::gfx::SharedDIB; // Flash WM_USER message delay time for PostDelayedTask. Borrowed // from Chromium's web plugin delegate src. See 'flash msg throttling @@ -810,12 +806,20 @@ if (!mPluginIface->setvalue) { return NPERR_GENERIC_ERROR; } return mPluginIface->setvalue(GetNPP(), NPNVaudioDeviceChangeDetails, (void*)&details); } -#endif +NPError +PluginInstanceChild::AudioDeviceStateChanged(NPAudioDeviceStateChanged& aDeviceState) +{ + if (!mPluginIface->setvalue) { + return NPERR_GENERIC_ERROR; + } + return mPluginIface->setvalue(GetNPP(), NPNVaudioDeviceStateChanged, (void*)&aDeviceState); +} +#endif mozilla::ipc::IPCResult PluginInstanceChild::AnswerNPP_HandleEvent(const NPRemoteEvent& event, int16_t* handled) { @@ -1783,23 +1787,23 @@ LONG_PTR dwNewLong); typedef LONG_PTR (WINAPI *User32SetWindowLongPtrW)(HWND hWnd, int nIndex, LONG_PTR dwNewLong); -static User32SetWindowLongPtrA sUser32SetWindowLongAHookStub = nullptr; -static User32SetWindowLongPtrW sUser32SetWindowLongWHookStub = nullptr; +static WindowsDllInterceptor::FuncHookType sUser32SetWindowLongAHookStub; +static WindowsDllInterceptor::FuncHookType sUser32SetWindowLongWHookStub; #else typedef LONG (WINAPI *User32SetWindowLongA)(HWND hWnd, int nIndex, LONG dwNewLong); typedef LONG (WINAPI *User32SetWindowLongW)(HWND hWnd, int nIndex, LONG dwNewLong); -static User32SetWindowLongA sUser32SetWindowLongAHookStub = nullptr; -static User32SetWindowLongW sUser32SetWindowLongWHookStub = nullptr; +static WindowsDllInterceptor::FuncHookType sUser32SetWindowLongAHookStub; +static WindowsDllInterceptor::FuncHookType sUser32SetWindowLongWHookStub; #endif extern LRESULT CALLBACK NeuteredWindowProc(HWND hwnd, UINT uMsg, @@ -1905,31 +1909,21 @@ { if (!(GetQuirks() & QUIRK_FLASH_HOOK_SETLONGPTR)) { return; } - // Only pass through here once - if (sSetWindowLongHookSet) { - return; - } - sSetWindowLongHookSet = true; - sUser32Intercept.Init("user32.dll"); #ifdef _WIN64 - if (!sUser32SetWindowLongAHookStub) - sUser32Intercept.AddHook("SetWindowLongPtrA", reinterpret_cast(SetWindowLongPtrAHook), - (void**) &sUser32SetWindowLongAHookStub); - if (!sUser32SetWindowLongWHookStub) - sUser32Intercept.AddHook("SetWindowLongPtrW", reinterpret_cast(SetWindowLongPtrWHook), - (void**) &sUser32SetWindowLongWHookStub); + sUser32SetWindowLongAHookStub.Set(sUser32Intercept, "SetWindowLongPtrA", + &SetWindowLongPtrAHook); + sUser32SetWindowLongWHookStub.Set(sUser32Intercept, "SetWindowLongPtrW", + &SetWindowLongPtrWHook); #else - if (!sUser32SetWindowLongAHookStub) - sUser32Intercept.AddHook("SetWindowLongA", reinterpret_cast(SetWindowLongAHook), - (void**) &sUser32SetWindowLongAHookStub); - if (!sUser32SetWindowLongWHookStub) - sUser32Intercept.AddHook("SetWindowLongW", reinterpret_cast(SetWindowLongWHook), - (void**) &sUser32SetWindowLongWHookStub); + sUser32SetWindowLongAHookStub.Set(sUser32Intercept, "SetWindowLongA", + &SetWindowLongAHook); + sUser32SetWindowLongWHookStub.Set(sUser32Intercept, "SetWindowLongW", + &SetWindowLongWHook); #endif } /* windowless track popup menu helpers */ @@ -1993,23 +1987,17 @@ { if (!(GetQuirks() & QUIRK_WINLESS_TRACKPOPUP_HOOK)) { return; } - // Only pass through here once - if (sPopupMenuHookSet) { - return; - } - sPopupMenuHookSet = true; - // Note, once WindowsDllInterceptor is initialized for a module, // it remains initialized for that particular module for it's // lifetime. Additional instances are needed if other modules need // to be hooked. sUser32Intercept.Init("user32.dll"); - sUser32Intercept.AddHook("TrackPopupMenu", reinterpret_cast(TrackPopupHookProc), - (void**) &sUser32TrackPopupMenuStub); + sUser32TrackPopupMenuStub.Set(sUser32Intercept, "TrackPopupMenu", + &TrackPopupHookProc); } void PluginInstanceChild::CreateWinlessPopupSurrogate() { @@ -2147,40 +2135,27 @@ { if (!(GetQuirks() & QUIRK_WINLESS_HOOK_IME)) { return; } - if (sImm32ImmGetContextStub) { - return; - } - // When using windowless plugin, IMM API won't work due ot OOP. // // ImmReleaseContext on Windows 7+ just returns TRUE only, so we don't // need to hook this. sImm32Intercept.Init("imm32.dll"); - sImm32Intercept.AddHook( - "ImmGetContext", - reinterpret_cast(ImmGetContextProc), - (void**)&sImm32ImmGetContextStub); - sImm32Intercept.AddHook( - "ImmGetCompositionStringW", - reinterpret_cast(ImmGetCompositionStringProc), - (void**)&sImm32ImmGetCompositionStringStub); - sImm32Intercept.AddHook( - "ImmSetCandidateWindow", - reinterpret_cast(ImmSetCandidateWindowProc), - (void**)&sImm32ImmSetCandidateWindowStub); - sImm32Intercept.AddHook( - "ImmNotifyIME", - reinterpret_cast(ImmNotifyIME), - (void**)&sImm32ImmNotifyIME); - sImm32Intercept.AddHook( - "ImmAssociateContextEx", - reinterpret_cast(ImmAssociateContextExProc), - (void**)&sImm32ImmAssociateContextExStub); + sImm32ImmGetContextStub.Set(sImm32Intercept, "ImmGetContext", + &ImmGetContextProc); + sImm32ImmGetCompositionStringStub.Set(sImm32Intercept, + "ImmGetCompositionStringW", + &ImmGetCompositionStringProc); + sImm32ImmSetCandidateWindowStub.Set(sImm32Intercept, + "ImmSetCandidateWindow", + &ImmSetCandidateWindowProc); + sImm32ImmNotifyIME.Set(sImm32Intercept, "ImmNotifyIME", &ImmNotifyIME); + sImm32ImmAssociateContextExStub.Set(sImm32Intercept, "ImmAssociateContextEx", + &ImmAssociateContextExProc); } void PluginInstanceChild::DestroyWinlessPopupSurrogate() { diff --git a/dom/plugins/ipc/PluginMessageUtils.h b/dom/plugins/ipc/PluginMessageUtils.h --- a/dom/plugins/ipc/PluginMessageUtils.h +++ b/dom/plugins/ipc/PluginMessageUtils.h @@ -108,10 +108,16 @@ int32_t flow; int32_t role; std::wstring defaultDevice; }; +struct NPAudioDeviceStateChangedIPC +{ + std::wstring device; + uint32_t state; +}; + #ifdef XP_WIN typedef HWND NativeWindowHandle; #elif defined(MOZ_X11) typedef XID NativeWindowHandle; #elif defined(XP_DARWIN) || defined(ANDROID) @@ -650,10 +656,39 @@ aLog->append(StringPrintf(L"[%d, %d, %S]", aParam.flow, aParam.role, aParam.defaultDevice.c_str())); } }; + +template <> +struct ParamTraits +{ + typedef mozilla::plugins::NPAudioDeviceStateChangedIPC paramType; + + static void Write(Message* aMsg, const paramType& aParam) + { + WriteParam(aMsg, aParam.device); + WriteParam(aMsg, aParam.state); + } + + static bool Read(const Message* aMsg, PickleIterator* aIter, paramType* aResult) + { + int32_t state; + std::wstring device; + if (ReadParam(aMsg, aIter, &device) && ReadParam(aMsg, aIter, &state)) { + aResult->device = device; + aResult->state = state; + return true; + } + return false; + } + + static void Log(const paramType& aParam, std::wstring* aLog) + { + aLog->append(StringPrintf(L"[%S,%d]", aParam.device.c_str(), aParam.state)); + } +}; } /* namespace IPC */ // Serializing NPEvents is completely platform-specific and can be rather // intricate depending on the platform. So for readability we split it diff --git a/dom/plugins/ipc/PluginModuleChild.h b/dom/plugins/ipc/PluginModuleChild.h --- a/dom/plugins/ipc/PluginModuleChild.h +++ b/dom/plugins/ipc/PluginModuleChild.h @@ -209,10 +209,12 @@ NPError PluginRequiresAudioDeviceChanges(PluginInstanceChild* aInstance, NPBool aShouldRegister); mozilla::ipc::IPCResult RecvNPP_SetValue_NPNVaudioDeviceChangeDetails( const NPAudioDeviceChangeDetailsIPC& detailsIPC) override; + mozilla::ipc::IPCResult RecvNPP_SetValue_NPNVaudioDeviceStateChanged( + const NPAudioDeviceStateChangedIPC& aDeviceStateIPC) override; private: NPError DoNP_Initialize(const PluginSettings& aSettings); void AddQuirk(PluginQuirks quirk) { if (mQuirks == QUIRKS_NOT_INITIALIZED) diff --git a/dom/plugins/ipc/PluginModuleChild.cpp b/dom/plugins/ipc/PluginModuleChild.cpp --- a/dom/plugins/ipc/PluginModuleChild.cpp +++ b/dom/plugins/ipc/PluginModuleChild.cpp @@ -2235,5 +2235,23 @@ return IPC_OK(); #else MOZ_CRASH("NPP_SetValue_NPNVaudioDeviceChangeDetails is a Windows-only message"); #endif } + +mozilla::ipc::IPCResult +PluginModuleChild::RecvNPP_SetValue_NPNVaudioDeviceStateChanged( + const NPAudioDeviceStateChangedIPC& aDeviceStateIPC) +{ +#if defined(XP_WIN) + NPAudioDeviceStateChanged stateChange; + stateChange.newState = aDeviceStateIPC.state; + stateChange.device = aDeviceStateIPC.device.c_str(); + for (auto iter = mAudioNotificationSet.ConstIter(); !iter.Done(); iter.Next()) { + PluginInstanceChild* pluginInst = iter.Get()->GetKey(); + pluginInst->AudioDeviceStateChanged(stateChange); + } + return IPC_OK(); +#else + MOZ_CRASH("NPP_SetValue_NPNVaudioDeviceRemoved is a Windows-only message"); +#endif +} diff --git a/dom/plugins/ipc/PluginUtilsWin.cpp b/dom/plugins/ipc/PluginUtilsWin.cpp --- a/dom/plugins/ipc/PluginUtilsWin.cpp +++ b/dom/plugins/ipc/PluginUtilsWin.cpp @@ -15,17 +15,26 @@ namespace PluginUtilsWin { typedef nsTHashtable> PluginModuleSet; StaticMutex sMutex; -class AudioDeviceChangedRunnable : public Runnable +class AudioDeviceMessageRunnable : public Runnable { public: - explicit AudioDeviceChangedRunnable(const PluginModuleSet* aAudioNotificationSet, + explicit AudioDeviceMessageRunnable(const PluginModuleSet* aAudioNotificationSet, NPAudioDeviceChangeDetailsIPC aChangeDetails) : - Runnable("AudioDeviceChangedRunnable") + Runnable("AudioDeviceMessageRunnableCD") , mChangeDetails(aChangeDetails) + , mMessageType(DEFAULT_DEVICE_CHANGED) + , mAudioNotificationSet(aAudioNotificationSet) + {} + + explicit AudioDeviceMessageRunnable(const PluginModuleSet* aAudioNotificationSet, + NPAudioDeviceStateChangedIPC aDeviceState) : + Runnable("AudioDeviceMessageRunnableSC") + , mDeviceState(aDeviceState) + , mMessageType(DEVICE_STATE_CHANGED) , mAudioNotificationSet(aAudioNotificationSet) {} NS_IMETHOD Run() override { @@ -33,19 +42,34 @@ PLUGIN_LOG_DEBUG(("Notifying %d plugins of audio device change.", mAudioNotificationSet->Count())); for (auto iter = mAudioNotificationSet->ConstIter(); !iter.Done(); iter.Next()) { PluginModuleParent* pluginModule = iter.Get()->GetKey(); - if(!pluginModule->SendNPP_SetValue_NPNVaudioDeviceChangeDetails(mChangeDetails)) { + bool success = false; + switch (mMessageType) { + case DEFAULT_DEVICE_CHANGED: + success = pluginModule->SendNPP_SetValue_NPNVaudioDeviceChangeDetails(mChangeDetails); + break; + case DEVICE_STATE_CHANGED: + success = pluginModule->SendNPP_SetValue_NPNVaudioDeviceStateChanged(mDeviceState); + break; + default: + MOZ_ASSERT_UNREACHABLE("bad AudioDeviceMessageRunnable state"); + } + if(!success) { return NS_ERROR_FAILURE; } } return NS_OK; } protected: + // The potential payloads for the message. Type determined by mMessageType. NPAudioDeviceChangeDetailsIPC mChangeDetails; + NPAudioDeviceStateChangedIPC mDeviceState; + enum { DEFAULT_DEVICE_CHANGED, DEVICE_STATE_CHANGED } mMessageType; + const PluginModuleSet* mAudioNotificationSet; }; class AudioNotification final : public IMMNotificationClient { @@ -80,12 +104,12 @@ changeDetails.flow = (int32_t)flow; changeDetails.role = (int32_t)role; changeDetails.defaultDevice = device_id ? std::wstring(device_id) : L""; // Make sure that plugin is notified on the main thread. - RefPtr runnable = - new AudioDeviceChangedRunnable(&mAudioNotificationSet, changeDetails); + RefPtr runnable = + new AudioDeviceMessageRunnable(&mAudioNotificationSet, changeDetails); NS_DispatchToMainThread(runnable); return S_OK; } HRESULT STDMETHODCALLTYPE @@ -101,10 +125,18 @@ } HRESULT STDMETHODCALLTYPE OnDeviceStateChanged(LPCWSTR device_id, DWORD new_state) override { + NPAudioDeviceStateChangedIPC deviceStateIPC; + deviceStateIPC.device = device_id ? std::wstring(device_id) : L""; + deviceStateIPC.state = (uint32_t)new_state; + + // Make sure that plugin is notified on the main thread. + RefPtr runnable = + new AudioDeviceMessageRunnable(&mAudioNotificationSet, deviceStateIPC); + NS_DispatchToMainThread(runnable); return S_OK; } HRESULT STDMETHODCALLTYPE OnPropertyValueChanged(LPCWSTR device_id, const PROPERTYKEY key) override diff --git a/dom/serviceworkers/ServiceWorkerRegistration.cpp b/dom/serviceworkers/ServiceWorkerRegistration.cpp --- a/dom/serviceworkers/ServiceWorkerRegistration.cpp +++ b/dom/serviceworkers/ServiceWorkerRegistration.cpp @@ -286,31 +286,33 @@ } already_AddRefed ServiceWorkerRegistration::Unregister(ErrorResult& aRv) { - if (!mInner) { - aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); - return nullptr; - } - nsIGlobalObject* global = GetParentObject(); - if (!global) { + if (NS_WARN_IF(!global)) { aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); return nullptr; } RefPtr outer = Promise::Create(global, aRv); if (NS_WARN_IF(aRv.Failed())) { return nullptr; } + if (!mInner) { + outer->MaybeResolve(false); + return outer.forget(); + } + mInner->Unregister( [outer] (bool aSuccess) { outer->MaybeResolve(aSuccess); }, [outer] (ErrorResult& aRv) { - outer->MaybeReject(aRv); + // register() should be resilient and resolve false instead + // of rejecting in most cases. + outer->MaybeResolve(false); }); return outer.forget(); } diff --git a/dom/tests/browser/browser.ini b/dom/tests/browser/browser.ini --- a/dom/tests/browser/browser.ini +++ b/dom/tests/browser/browser.ini @@ -55,10 +55,14 @@ [browser_largeAllocation_non_win32.js] skip-if = !e10s || (os == "win" && processor == "x86") || (verify && debug && (os == 'linux')) # Large-Allocation requires e10s [browser_localStorage_e10s.js] skip-if = !e10s || verify # This is a test of e10s functionality. [browser_localStorage_privatestorageevent.js] +[browser_persist_cookies.js] +support-files = + set-samesite-cookies-and-redirect.sjs + mimeme.sjs [browser_test_focus_after_modal_state.js] skip-if = verify support-files = focus_after_prompt.html [browser_test_new_window_from_content.js] diff --git a/dom/tests/browser/browser_persist_cookies.js b/dom/tests/browser/browser_persist_cookies.js new file mode 100644 --- /dev/null +++ b/dom/tests/browser/browser_persist_cookies.js @@ -0,0 +1,94 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +const TEST_PATH = getRootDirectory(gTestPath).replace("chrome://mochitests/content", "https://example.org"); +const TEST_PATH2 = getRootDirectory(gTestPath).replace("chrome://mochitests/content", "https://example.com"); + +var MockFilePicker = SpecialPowers.MockFilePicker; +MockFilePicker.init(window); + +registerCleanupFunction(async function() { + info("Running the cleanup code"); + MockFilePicker.cleanup(); + Services.obs.removeObserver(checkRequest, "http-on-modify-request"); + if (gTestDir && gTestDir.exists()) { + // On Windows, sometimes nsIFile.remove() throws, probably because we're + // still writing to the directory we're trying to remove, despite + // waiting for the download to complete. Just retry a bit later... + let succeeded = false; + while (!succeeded) { + try { + gTestDir.remove(true); + succeeded = true; + } catch (ex) { + await new Promise(requestAnimationFrame); + } + } + } +}); + +let gTestDir = null; + + +function checkRequest(subject) { + let httpChannel = subject.QueryInterface(Ci.nsIHttpChannel); + let spec = httpChannel.URI.spec; + // Ignore initial requests for page that sets cookies and its favicon, which may not have + // cookies. + if (httpChannel.URI.host == "example.org" && !spec.endsWith("favicon.ico") && !spec.includes("redirect.sjs")) { + let cookie = httpChannel.getRequestHeader("cookie"); + is(cookie.trim(), "normalCookie=true", "Should have correct cookie in request for " + spec); + } +} + +function createTemporarySaveDirectory() { + var saveDir = Services.dirsvc.get("TmpD", Ci.nsIFile); + saveDir.append("testsavedir"); + if (!saveDir.exists()) { + info("create testsavedir!"); + saveDir.create(Ci.nsIFile.DIRECTORY_TYPE, 0o755); + } + info("return from createTempSaveDir: " + saveDir.path); + return saveDir; +} + +add_task(async function() { + await BrowserTestUtils.withNewTab("about:blank", async function(browser) { + Services.obs.addObserver(checkRequest, "http-on-modify-request"); + BrowserTestUtils.loadURI(browser, TEST_PATH + "set-samesite-cookies-and-redirect.sjs"); + // Test that the original document load doesn't send same-site cookies. + await BrowserTestUtils.browserLoaded(browser, true, TEST_PATH2 + "set-samesite-cookies-and-redirect.sjs"); + // Now check the saved page. + // Create the folder the link will be saved into. + gTestDir = createTemporarySaveDirectory(); + let destFile = gTestDir.clone(); + + MockFilePicker.displayDirectory = gTestDir; + let fileName; + MockFilePicker.showCallback = function(fp) { + info("showCallback"); + fileName = fp.defaultString; + info("fileName: " + fileName); + destFile.append(fileName); + info("path: " + destFile.path); + MockFilePicker.setFiles([destFile]); + MockFilePicker.filterIndex = 0; // kSaveAsType_Complete + info("done showCallback"); + }; + saveBrowser(browser); + await new Promise(async (resolve) => { + let dls = await Downloads.getList(Downloads.PUBLIC); + dls.addView({ + onDownloadChanged(download) { + if (download.succeeded) { + dls.removeView(this); + dls.removeFinished(); + resolve(); + } + } + }); + }); + }); +}); diff --git a/dom/tests/browser/mimeme.sjs b/dom/tests/browser/mimeme.sjs new file mode 100644 --- /dev/null +++ b/dom/tests/browser/mimeme.sjs @@ -0,0 +1,26 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +function handleRequest(request, response) { + let mimeType = request.queryString.match(/type=([a-z]*)/)[1]; + switch (mimeType) { + case "css": + response.setHeader("Content-Type", "text/css"); + response.write("#hi {color: red}"); + break; + case "js": + response.setHeader("Content-Type", "application/javascript"); + response.write("var foo;"); + break; + case "png": + response.setHeader("Content-Type", "image/png"); + response.write(""); + break; + case "html": + response.setHeader("Content-Type", "text/html"); + response.write("I am a subframe"); + break; + } +} diff --git a/dom/tests/browser/set-samesite-cookies-and-redirect.sjs b/dom/tests/browser/set-samesite-cookies-and-redirect.sjs new file mode 100644 --- /dev/null +++ b/dom/tests/browser/set-samesite-cookies-and-redirect.sjs @@ -0,0 +1,33 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +function handleRequest(request, response) { + // Set cookies and redirect for .org: + if (request.host.endsWith(".org")) { + response.setHeader("Set-Cookie", "normalCookie=true; path=/;", true); + response.setHeader("Set-Cookie", "laxHeader=true; path=/; SameSite=Lax", true); + response.setHeader("Set-Cookie", "strictHeader=true; path=/; SameSite=Strict", true); + response.write(` + + + + + + + `); + } else { + let baseURI = "https://example.org/" + request.path.replace(/[a-z-]*\.sjs/, "mimeme.sjs?type="); + response.write(` + + + + + `); + } +} diff --git a/dom/webbrowserpersist/PWebBrowserPersistDocument.ipdl b/dom/webbrowserpersist/PWebBrowserPersistDocument.ipdl --- a/dom/webbrowserpersist/PWebBrowserPersistDocument.ipdl +++ b/dom/webbrowserpersist/PWebBrowserPersistDocument.ipdl @@ -8,10 +8,11 @@ include protocol PWebBrowserPersistSerialize; include protocol PFileDescriptorSet; include protocol PChildToParentStream; //FIXME: bug #792908 include protocol PParentToChildStream; //FIXME: bug #792908 +include PBackgroundSharedTypes; include IPCStream; namespace mozilla { // nsIWebBrowserPersistDocument has attributes which can be read @@ -27,10 +28,11 @@ nsString title; nsString referrer; nsString contentDisposition; uint32_t cacheKey; uint32_t persistFlags; + PrincipalInfo principal; }; // IPDL doesn't have tuples, so this gives the pair of strings from // nsIWebBrowserPersistURIMap::getURIMapping a name. struct WebBrowserPersistURIMapEntry { diff --git a/dom/webbrowserpersist/WebBrowserPersistDocumentChild.cpp b/dom/webbrowserpersist/WebBrowserPersistDocumentChild.cpp --- a/dom/webbrowserpersist/WebBrowserPersistDocumentChild.cpp +++ b/dom/webbrowserpersist/WebBrowserPersistDocumentChild.cpp @@ -39,10 +39,11 @@ if (!aDocument) { SendInitFailure(NS_ERROR_FAILURE); return; } + nsCOMPtr principal; WebBrowserPersistDocumentAttrs attrs; nsCOMPtr postDataStream; #define ENSURE(e) do { \ nsresult rv = (e); \ if (NS_FAILED(rv)) { \ @@ -58,10 +59,14 @@ ENSURE(aDocument->GetTitle(attrs.title())); ENSURE(aDocument->GetReferrer(attrs.referrer())); ENSURE(aDocument->GetContentDisposition(attrs.contentDisposition())); ENSURE(aDocument->GetCacheKey(&(attrs.cacheKey()))); ENSURE(aDocument->GetPersistFlags(&(attrs.persistFlags()))); + + ENSURE(aDocument->GetPrincipal(getter_AddRefs(principal))); + ENSURE(ipc::PrincipalToPrincipalInfo(principal, &(attrs.principal()))); + ENSURE(aDocument->GetPostData(getter_AddRefs(postDataStream))); #undef ENSURE mozilla::ipc::AutoIPCStream autoStream; autoStream.Serialize(postDataStream, diff --git a/dom/webbrowserpersist/WebBrowserPersistLocalDocument.cpp b/dom/webbrowserpersist/WebBrowserPersistLocalDocument.cpp --- a/dom/webbrowserpersist/WebBrowserPersistLocalDocument.cpp +++ b/dom/webbrowserpersist/WebBrowserPersistLocalDocument.cpp @@ -182,10 +182,18 @@ return NS_OK; } return history->GetPostData(aStream); } +NS_IMETHODIMP +WebBrowserPersistLocalDocument::GetPrincipal(nsIPrincipal** aPrincipal) +{ + nsCOMPtr nodePrincipal = mDocument->NodePrincipal(); + nodePrincipal.forget(aPrincipal); + return NS_OK; +} + already_AddRefed WebBrowserPersistLocalDocument::GetHistory() { nsCOMPtr window = mDocument->GetDefaultView(); if (NS_WARN_IF(!window)) { diff --git a/dom/webbrowserpersist/WebBrowserPersistRemoteDocument.h b/dom/webbrowserpersist/WebBrowserPersistRemoteDocument.h --- a/dom/webbrowserpersist/WebBrowserPersistRemoteDocument.h +++ b/dom/webbrowserpersist/WebBrowserPersistRemoteDocument.h @@ -11,10 +11,12 @@ #include "mozilla/PWebBrowserPersistDocumentParent.h" #include "nsCOMPtr.h" #include "nsIWebBrowserPersistDocument.h" #include "nsIInputStream.h" +class nsIPrincipal; + // This class is the XPCOM half of the glue between the // nsIWebBrowserPersistDocument interface and a remote document; it is // created by WebBrowserPersistDocumentParent when (and if) it // receives the information needed to populate the interface's // properties. @@ -38,10 +40,11 @@ private: using Attrs = WebBrowserPersistDocumentAttrs; WebBrowserPersistDocumentParent* mActor; Attrs mAttrs; nsCOMPtr mPostData; + nsCOMPtr mPrincipal; friend class WebBrowserPersistDocumentParent; WebBrowserPersistRemoteDocument(WebBrowserPersistDocumentParent* aActor, const Attrs& aAttrs, nsIInputStream* aPostData); diff --git a/dom/webbrowserpersist/WebBrowserPersistRemoteDocument.cpp b/dom/webbrowserpersist/WebBrowserPersistRemoteDocument.cpp --- a/dom/webbrowserpersist/WebBrowserPersistRemoteDocument.cpp +++ b/dom/webbrowserpersist/WebBrowserPersistRemoteDocument.cpp @@ -7,10 +7,13 @@ #include "WebBrowserPersistRemoteDocument.h" #include "WebBrowserPersistDocumentParent.h" #include "WebBrowserPersistResourcesParent.h" #include "WebBrowserPersistSerializeParent.h" #include "mozilla/Unused.h" +#include "mozilla/ipc/BackgroundUtils.h" + +#include "nsIPrincipal.h" namespace mozilla { NS_IMPL_ISUPPORTS(WebBrowserPersistRemoteDocument, nsIWebBrowserPersistDocument) @@ -21,10 +24,12 @@ nsIInputStream* aPostData) : mActor(aActor) , mAttrs(aAttrs) , mPostData(aPostData) { + nsresult rv; + mPrincipal = ipc::PrincipalInfoToPrincipal(mAttrs.principal(), &rv); } WebBrowserPersistRemoteDocument::~WebBrowserPersistRemoteDocument() { if (mActor) { @@ -130,10 +135,18 @@ nsCOMPtr stream = mPostData; stream.forget(aStream); return NS_OK; } +NS_IMETHODIMP +WebBrowserPersistRemoteDocument::GetPrincipal(nsIPrincipal** aPrincipal) +{ + nsCOMPtr nodePrincipal = mPrincipal; + nodePrincipal.forget(aPrincipal); + return NS_OK; +} + NS_IMETHODIMP WebBrowserPersistRemoteDocument::ReadResources(nsIWebBrowserPersistResourceVisitor* aVisitor) { if (!mActor) { return NS_ERROR_FAILURE; diff --git a/dom/webbrowserpersist/nsIWebBrowserPersist.idl b/dom/webbrowserpersist/nsIWebBrowserPersist.idl --- a/dom/webbrowserpersist/nsIWebBrowserPersist.idl +++ b/dom/webbrowserpersist/nsIWebBrowserPersist.idl @@ -10,10 +10,11 @@ interface nsIInputStream; interface nsIWebProgressListener; interface nsIFile; interface nsIChannel; interface nsILoadContext; +interface nsIPrincipal; /** * Interface for persisting DOM documents and URIs to local or remote storage. */ [scriptable, uuid(8cd752a4-60b1-42c3-a819-65c7a1138a28)] @@ -63,16 +64,10 @@ * Append the downloaded data to the target file. * This can only be used when persisting to a local file. */ const unsigned long PERSIST_FLAGS_APPEND_TO_FILE = 32768; - /** - * Force relevant cookies to be sent with this load even if normally they - * wouldn't be. - */ - const unsigned long PERSIST_FLAGS_FORCE_ALLOW_COOKIES = 65536; - /** * Flags governing how data is fetched and saved from the network. * It is best to set this value explicitly unless you are prepared * to accept the default values. */ @@ -114,10 +109,12 @@ * Save the specified URI to file. * * @param aURI URI to save to file. Some implementations of this interface * may also support nullptr to imply the currently * loaded URI. + * @param aTriggeringPrincipal + * The triggering principal for the URI we're saving. * @param aCacheKey The necko cache key integer. * @param aReferrer The referrer URI to pass with an HTTP request or * nullptr. * @param aReferrerPolicy The referrer policy for when and what to send via * HTTP Referer header. Ignored if aReferrer is @@ -140,11 +137,12 @@ * @see nsIURI * @see nsIInputStream * * @throws NS_ERROR_INVALID_ARG One or more arguments was invalid. */ - void saveURI(in nsIURI aURI, in unsigned long aCacheKey, + void saveURI(in nsIURI aURI, in nsIPrincipal aTriggeringPrincipal, + in unsigned long aCacheKey, in nsIURI aReferrer, in unsigned long aReferrerPolicy, in nsIInputStream aPostData, in string aExtraHeaders, in nsISupports aFile, in nsILoadContext aPrivacyContext); @@ -152,11 +150,12 @@ * @param aIsPrivate Treat the save operation as private (ie. with * regards to networking operations and persistence * of intermediate data, etc.) * @see saveURI for all other parameter descriptions */ - void savePrivacyAwareURI(in nsIURI aURI, in unsigned long aCacheKey, + void savePrivacyAwareURI(in nsIURI aURI, + in nsIPrincipal aTriggeringPrincipal, in unsigned long aCacheKey, in nsIURI aReferrer, in unsigned long aReferrerPolicy, in nsIInputStream aPostData, in string aExtraHeaders, in nsISupports aFile, in boolean aIsPrivate); diff --git a/dom/webbrowserpersist/nsIWebBrowserPersistDocument.idl b/dom/webbrowserpersist/nsIWebBrowserPersistDocument.idl --- a/dom/webbrowserpersist/nsIWebBrowserPersistDocument.idl +++ b/dom/webbrowserpersist/nsIWebBrowserPersistDocument.idl @@ -6,10 +6,11 @@ #include "nsISupports.idl" interface nsIInputStream; interface nsIOutputStream; +interface nsIPrincipal; interface nsITabParent; interface nsIWebBrowserPersistResourceVisitor; interface nsIWebBrowserPersistWriteCompletion; /** @@ -58,10 +59,11 @@ readonly attribute ACString characterSet; readonly attribute AString title; readonly attribute AString referrer; readonly attribute AString contentDisposition; readonly attribute nsIInputStream postData; + readonly attribute nsIPrincipal principal; /** * The cache key. Unlike in nsISHEntry, where it's wrapped in an * nsISupportsPRUint32, this is just the integer. */ diff --git a/dom/webbrowserpersist/nsWebBrowserPersist.h b/dom/webbrowserpersist/nsWebBrowserPersist.h --- a/dom/webbrowserpersist/nsWebBrowserPersist.h +++ b/dom/webbrowserpersist/nsWebBrowserPersist.h @@ -55,11 +55,12 @@ // Private members private: virtual ~nsWebBrowserPersist(); nsresult SaveURIInternal( - nsIURI *aURI, uint32_t aCacheKey, nsIURI *aReferrer, + nsIURI *aURI, nsIPrincipal* aTriggeringPrincipal, + uint32_t aCacheKey, nsIURI *aReferrer, uint32_t aReferrerPolicy, nsIInputStream *aPostData, const char *aExtraHeaders, nsIURI *aFile, bool aCalcFileExt, bool aIsPrivate); nsresult SaveChannelInternal( nsIChannel *aChannel, nsIURI *aFile, bool aCalcFileExt); diff --git a/dom/webbrowserpersist/nsWebBrowserPersist.cpp b/dom/webbrowserpersist/nsWebBrowserPersist.cpp --- a/dom/webbrowserpersist/nsWebBrowserPersist.cpp +++ b/dom/webbrowserpersist/nsWebBrowserPersist.cpp @@ -81,10 +81,11 @@ struct nsWebBrowserPersist::DocData { nsCOMPtr mBaseURI; nsCOMPtr mDocument; nsCOMPtr mFile; + nsCOMPtr mPrincipal; nsCString mCharset; }; // Information about a URI struct nsWebBrowserPersist::URIData @@ -411,22 +412,24 @@ mEventSink = do_GetInterface(aProgressListener); return NS_OK; } NS_IMETHODIMP nsWebBrowserPersist::SaveURI( - nsIURI *aURI, uint32_t aCacheKey, + nsIURI *aURI, nsIPrincipal *aPrincipal, uint32_t aCacheKey, nsIURI *aReferrer, uint32_t aReferrerPolicy, nsIInputStream *aPostData, const char *aExtraHeaders, nsISupports *aFile, nsILoadContext* aPrivacyContext) { - return SavePrivacyAwareURI(aURI, aCacheKey, aReferrer, aReferrerPolicy, - aPostData, aExtraHeaders, aFile, - aPrivacyContext && aPrivacyContext->UsePrivateBrowsing()); + bool isPrivate = + aPrivacyContext && aPrivacyContext->UsePrivateBrowsing(); + return SavePrivacyAwareURI(aURI, aPrincipal, aCacheKey, + aReferrer, aReferrerPolicy, + aPostData, aExtraHeaders, aFile, isPrivate); } NS_IMETHODIMP nsWebBrowserPersist::SavePrivacyAwareURI( - nsIURI *aURI, uint32_t aCacheKey, + nsIURI *aURI, nsIPrincipal *aPrincipal, uint32_t aCacheKey, nsIURI *aReferrer, uint32_t aReferrerPolicy, nsIInputStream *aPostData, const char *aExtraHeaders, nsISupports *aFile, bool aIsPrivate) { NS_ENSURE_TRUE(mFirstAndOnlyUse, NS_ERROR_FAILURE); @@ -437,12 +440,14 @@ rv = GetValidURIFromObject(aFile, getter_AddRefs(fileAsURI)); NS_ENSURE_SUCCESS(rv, NS_ERROR_INVALID_ARG); // SaveURI doesn't like broken uris. mPersistFlags |= PERSIST_FLAGS_FAIL_ON_BROKEN_LINKS; - rv = SaveURIInternal(aURI, aCacheKey, aReferrer, aReferrerPolicy, - aPostData, aExtraHeaders, fileAsURI, false, aIsPrivate); + rv = SaveURIInternal(aURI, aPrincipal, aCacheKey, + aReferrer, aReferrerPolicy, + aPostData, aExtraHeaders, fileAsURI, + false, aIsPrivate); return NS_FAILED(rv) ? rv : NS_OK; } NS_IMETHODIMP nsWebBrowserPersist::SaveChannel( nsIChannel *aChannel, nsISupports *aFile) @@ -597,21 +602,26 @@ } } } if (urisToPersist > 0) { + nsCOMPtr docPrincipal; + //XXXgijs I *think* this is already always true, but let's be sure. + MOZ_ASSERT(mDocList.Length() > 0, + "Should have the document for any walked URIs to persist!"); + nsresult rv = mDocList.ElementAt(0)->mDocument-> + GetPrincipal(getter_AddRefs(docPrincipal)); + NS_ENSURE_SUCCESS_VOID(rv); // Persist each file in the uri map. The document(s) // will be saved after the last one of these is saved. for (auto iter = mURIMap.Iter(); !iter.Done(); iter.Next()) { URIData *data = iter.UserData(); if (!data->mNeedsPersisting || data->mSaved) { continue; } - nsresult rv; - // Create a URI from the key. nsCOMPtr uri; rv = NS_NewURI(getter_AddRefs(uri), iter.Key(), data->mCharset.get()); if (NS_WARN_IF(NS_FAILED(rv))) { @@ -625,11 +635,11 @@ break; } // The Referrer Policy doesn't matter here since the referrer is // nullptr. - rv = SaveURIInternal(uri, 0, nullptr, + rv = SaveURIInternal(uri, docPrincipal, 0, nullptr, mozilla::net::RP_Unset, nullptr, nullptr, fileAsURI, true, mIsPrivate); // If SaveURIInternal fails, then it will have called EndDownload, // which means that |data| is no longer valid memory. We MUST bail. if (NS_WARN_IF(NS_FAILED(rv))) { @@ -1322,11 +1332,12 @@ .SetPathQueryRef(newPath) .Finalize(aOutURI); } nsresult nsWebBrowserPersist::SaveURIInternal( - nsIURI *aURI, uint32_t aCacheKey, nsIURI *aReferrer, + nsIURI *aURI, nsIPrincipal* aTriggeringPrincipal, + uint32_t aCacheKey, nsIURI *aReferrer, uint32_t aReferrerPolicy, nsIInputStream *aPostData, const char *aExtraHeaders, nsIURI *aFile, bool aCalcFileExt, bool aIsPrivate) { NS_ENSURE_ARG_POINTER(aURI); @@ -1348,11 +1359,11 @@ // Open a channel to the URI nsCOMPtr inputChannel; rv = NS_NewChannel(getter_AddRefs(inputChannel), aURI, - nsContentUtils::GetSystemPrincipal(), + aTriggeringPrincipal, nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL, nsIContentPolicy::TYPE_OTHER, nullptr, // aPerformanceStorage nullptr, // aLoadGroup static_cast(this), @@ -1378,20 +1389,10 @@ { encodedChannel->SetApplyConversion(false); } } - if (mPersistFlags & PERSIST_FLAGS_FORCE_ALLOW_COOKIES) - { - nsCOMPtr httpChannelInternal = - do_QueryInterface(inputChannel); - if (httpChannelInternal) { - rv = httpChannelInternal->SetThirdPartyFlags(nsIHttpChannelInternal::THIRD_PARTY_FORCE_ALLOW); - MOZ_ASSERT(NS_SUCCEEDED(rv)); - } - } - // Set the referrer, post data and headers if any nsCOMPtr httpChannel(do_QueryInterface(inputChannel)); if (httpChannel) { // Referrer diff --git a/dom/webgpu/Adapter.h b/dom/webgpu/Adapter.h new file mode 100644 --- /dev/null +++ b/dom/webgpu/Adapter.h @@ -0,0 +1,49 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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 WEBGPU_Adapter_H_ +#define WEBGPU_Adapter_H_ + +#include "mozilla/AlreadyAddRefed.h" +#include "nsString.h" +#include "ObjectModel.h" + +namespace mozilla { +namespace dom { +struct WebGPUDeviceDescriptor; +struct WebGPUExtensions; +struct WebGPUFeatures; +} // namespace dom + +namespace webgpu { +class Device; +class Instance; + +class Adapter final + : public ChildOf +{ +public: + WEBGPU_DECL_GOOP(Adapter) + + const nsString mName; + +private: + Adapter() = delete; + virtual ~Adapter(); + +public: + void GetName(nsString& out) const { + out = mName; + } + + void Extensions(dom::WebGPUExtensions& out) const; + void Features(dom::WebGPUFeatures& out) const; + already_AddRefed CreateDevice(const dom::WebGPUDeviceDescriptor& desc) const; +}; + +} // namespace webgpu +} // namespace mozilla + +#endif // WEBGPU_Adapter_H_ diff --git a/dom/webgpu/Adapter.cpp b/dom/webgpu/Adapter.cpp new file mode 100644 --- /dev/null +++ b/dom/webgpu/Adapter.cpp @@ -0,0 +1,37 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "Adapter.h" + +#include "Instance.h" +#include "mozilla/dom/WebGPUBinding.h" + +namespace mozilla { +namespace webgpu { + +Adapter::~Adapter() = default; + +void +Adapter::Extensions(dom::WebGPUExtensions& out) const +{ + MOZ_CRASH("todo"); +} + +void +Adapter::Features(dom::WebGPUFeatures& out) const +{ + MOZ_CRASH("todo"); +} + +already_AddRefed +Adapter::CreateDevice(const dom::WebGPUDeviceDescriptor& desc) const +{ + MOZ_CRASH("todo"); +} + +WEBGPU_IMPL_GOOP_0(Adapter) + +} // namespace webgpu +} // namespace mozilla diff --git a/dom/webgpu/AttachmentState.h b/dom/webgpu/AttachmentState.h new file mode 100644 --- /dev/null +++ b/dom/webgpu/AttachmentState.h @@ -0,0 +1,31 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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 WEBGPU_AttachmentState_H_ +#define WEBGPU_AttachmentState_H_ + +#include "nsWrapperCache.h" +#include "ObjectModel.h" + +namespace mozilla { +namespace webgpu { + +class Device; + +class AttachmentState final + : public ChildOf +{ +public: + WEBGPU_DECL_GOOP(AttachmentState) + +private: + AttachmentState() = delete; + virtual ~AttachmentState(); +}; + +} // namespace webgpu +} // namespace mozilla + +#endif // WEBGPU_AttachmentState_H_ diff --git a/dom/webgpu/AttachmentState.cpp b/dom/webgpu/AttachmentState.cpp new file mode 100644 --- /dev/null +++ b/dom/webgpu/AttachmentState.cpp @@ -0,0 +1,19 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "AttachmentState.h" + +#include "Device.h" +#include "mozilla/dom/WebGPUBinding.h" + +namespace mozilla { +namespace webgpu { + +AttachmentState::~AttachmentState() = default; + +WEBGPU_IMPL_GOOP_0(AttachmentState) + +} // namespace webgpu +} // namespace mozilla diff --git a/dom/webgpu/BindGroup.h b/dom/webgpu/BindGroup.h new file mode 100644 --- /dev/null +++ b/dom/webgpu/BindGroup.h @@ -0,0 +1,31 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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 WEBGPU_BindGroup_H_ +#define WEBGPU_BindGroup_H_ + +#include "nsWrapperCache.h" +#include "ObjectModel.h" + +namespace mozilla { +namespace webgpu { + +class Device; + +class BindGroup final + : public ChildOf +{ +public: + WEBGPU_DECL_GOOP(BindGroup) + +private: + BindGroup() = delete; + virtual ~BindGroup(); +}; + +} // namespace webgpu +} // namespace mozilla + +#endif // WEBGPU_BindGroup_H_ diff --git a/dom/webgpu/BindGroup.cpp b/dom/webgpu/BindGroup.cpp new file mode 100644 --- /dev/null +++ b/dom/webgpu/BindGroup.cpp @@ -0,0 +1,19 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "BindGroup.h" + +#include "Device.h" +#include "mozilla/dom/WebGPUBinding.h" + +namespace mozilla { +namespace webgpu { + +BindGroup::~BindGroup() = default; + +WEBGPU_IMPL_GOOP_0(BindGroup) + +} // namespace webgpu +} // namespace mozilla diff --git a/dom/webgpu/BindGroupLayout.h b/dom/webgpu/BindGroupLayout.h new file mode 100644 --- /dev/null +++ b/dom/webgpu/BindGroupLayout.h @@ -0,0 +1,31 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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 WEBGPU_BindGroupLayout_H_ +#define WEBGPU_BindGroupLayout_H_ + +#include "nsWrapperCache.h" +#include "ObjectModel.h" + +namespace mozilla { +namespace webgpu { + +class Device; + +class BindGroupLayout final + : public ChildOf +{ +public: + WEBGPU_DECL_GOOP(BindGroupLayout) + +private: + BindGroupLayout() = delete; + virtual ~BindGroupLayout(); +}; + +} // namespace webgpu +} // namespace mozilla + +#endif // WEBGPU_BindGroupLayout_H_ diff --git a/dom/webgpu/BindGroupLayout.cpp b/dom/webgpu/BindGroupLayout.cpp new file mode 100644 --- /dev/null +++ b/dom/webgpu/BindGroupLayout.cpp @@ -0,0 +1,19 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "BindGroupLayout.h" + +#include "Device.h" +#include "mozilla/dom/WebGPUBinding.h" + +namespace mozilla { +namespace webgpu { + +BindGroupLayout::~BindGroupLayout() = default; + +WEBGPU_IMPL_GOOP_0(BindGroupLayout) + +} // namespace webgpu +} // namespace mozilla diff --git a/dom/webgpu/BlendState.h b/dom/webgpu/BlendState.h new file mode 100644 --- /dev/null +++ b/dom/webgpu/BlendState.h @@ -0,0 +1,31 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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 WEBGPU_BlendState_H_ +#define WEBGPU_BlendState_H_ + +#include "nsWrapperCache.h" +#include "ObjectModel.h" + +namespace mozilla { +namespace webgpu { + +class Device; + +class BlendState final + : public ChildOf +{ +public: + WEBGPU_DECL_GOOP(BlendState) + +private: + BlendState() = delete; + virtual ~BlendState(); +}; + +} // namespace webgpu +} // namespace mozilla + +#endif // WEBGPU_BlendState_H_ diff --git a/dom/webgpu/BlendState.cpp b/dom/webgpu/BlendState.cpp new file mode 100644 --- /dev/null +++ b/dom/webgpu/BlendState.cpp @@ -0,0 +1,19 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "BlendState.h" + +#include "Device.h" +#include "mozilla/dom/WebGPUBinding.h" + +namespace mozilla { +namespace webgpu { + +BlendState::~BlendState() = default; + +WEBGPU_IMPL_GOOP_0(BlendState) + +} // namespace webgpu +} // namespace mozilla diff --git a/dom/webgpu/Buffer.h b/dom/webgpu/Buffer.h new file mode 100644 --- /dev/null +++ b/dom/webgpu/Buffer.h @@ -0,0 +1,38 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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 WEBGPU_BUFFER_H_ +#define WEBGPU_BUFFER_H_ + +#include "mozilla/dom/Nullable.h" +#include "mozilla/dom/TypedArray.h" +#include "ObjectModel.h" + +namespace mozilla { +namespace webgpu { + +class Device; + +class Buffer final + : public ChildOf +{ +public: + JS::Heap mMapping; + + WEBGPU_DECL_GOOP(Buffer) + +private: + explicit Buffer(Device* parent); + virtual ~Buffer(); + +public: + void GetMapping(JSContext* cx, JS::MutableHandle out) const; + void Unmap() const; +}; + +} // namespace webgpu +} // namespace mozilla + +#endif // WEBGPU_BUFFER_H_ diff --git a/dom/webgpu/Buffer.cpp b/dom/webgpu/Buffer.cpp new file mode 100644 --- /dev/null +++ b/dom/webgpu/Buffer.cpp @@ -0,0 +1,62 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "Buffer.h" + +#include "Device.h" +#include "mozilla/dom/WebGPUBinding.h" + +namespace mozilla { +namespace webgpu { + +Buffer::Buffer(Device* const parent) + : ChildOf(parent) +{ + mozilla::HoldJSObjects(this); // Mimed from PushSubscriptionOptions +} + +Buffer::~Buffer() +{ + mMapping = nullptr; + mozilla::DropJSObjects(this); +} + +void +Buffer::GetMapping(JSContext*, JS::MutableHandle out) const +{ + out.set(mMapping); +} + +void +Buffer::Unmap() const +{ + MOZ_CRASH("todo"); +} + +JSObject* +webgpu::Buffer::WrapObject(JSContext* cx, JS::Handle givenProto) +{ + return dom::WebGPUBuffer_Binding::Wrap(cx, this, givenProto); +} + +NS_IMPL_CYCLE_COLLECTION_CLASS(Buffer) +NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(Buffer) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mParent) + NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER + tmp->mMapping = nullptr; +NS_IMPL_CYCLE_COLLECTION_UNLINK_END +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(Buffer) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mParent) +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END +NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(Buffer) + NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER + NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mMapping) +NS_IMPL_CYCLE_COLLECTION_TRACE_END + +NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(Buffer, AddRef) +NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(Buffer, Release) + +} // namespace webgpu +} // namespace mozilla diff --git a/dom/webgpu/CommandBuffer.h b/dom/webgpu/CommandBuffer.h new file mode 100644 --- /dev/null +++ b/dom/webgpu/CommandBuffer.h @@ -0,0 +1,31 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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 WEBGPU_CommandBuffer_H_ +#define WEBGPU_CommandBuffer_H_ + +#include "nsWrapperCache.h" +#include "ObjectModel.h" + +namespace mozilla { +namespace webgpu { + +class Device; + +class CommandBuffer final + : public ChildOf +{ +public: + WEBGPU_DECL_GOOP(CommandBuffer) + +private: + CommandBuffer() = delete; + virtual ~CommandBuffer(); +}; + +} // namespace webgpu +} // namespace mozilla + +#endif // WEBGPU_CommandBuffer_H_ diff --git a/dom/webgpu/CommandBuffer.cpp b/dom/webgpu/CommandBuffer.cpp new file mode 100644 --- /dev/null +++ b/dom/webgpu/CommandBuffer.cpp @@ -0,0 +1,19 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "CommandBuffer.h" + +#include "Device.h" +#include "mozilla/dom/WebGPUBinding.h" + +namespace mozilla { +namespace webgpu { + +CommandBuffer::~CommandBuffer() = default; + +WEBGPU_IMPL_GOOP_0(CommandBuffer) + +} // namespace webgpu +} // namespace mozilla diff --git a/dom/webgpu/CommandEncoder.h b/dom/webgpu/CommandEncoder.h new file mode 100644 --- /dev/null +++ b/dom/webgpu/CommandEncoder.h @@ -0,0 +1,83 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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 WEBGPU_CommandEncoder_H_ +#define WEBGPU_CommandEncoder_H_ + +#include "mozilla/dom/TypedArray.h" +#include "nsWrapperCache.h" +#include "ObjectModel.h" + +namespace mozilla { +namespace dom { +template class Sequence; +class WebGPUComputePipelineOrWebGPURenderPipeline; +struct WebGPURenderPassDescriptor; +} // namespace dom +namespace webgpu { + +class BindGroup; +class Buffer; +class CommandBuffer; +class Device; + +class CommandEncoder final + : public ChildOf +{ +public: + WEBGPU_DECL_GOOP(CommandEncoder) + +private: + CommandEncoder() = delete; + virtual ~CommandEncoder(); + +public: + already_AddRefed FinishEncoding() const; + + // Commands allowed outside of "passes" + void CopyBufferToBuffer(const Buffer& src, uint32_t srcOffset, const Buffer& dst, + uint32_t dstOffset, uint32_t size) const; + // TODO figure out all the arguments required for these + void CopyBufferToTexture() const; + void CopyTextureToBuffer() const; + void CopyTextureToTexture() const; + void Blit() const; + + void TransitionBuffer(const Buffer& b, uint32_t flags) const; + + // Allowed in both compute and render passes + void SetPushConstants(uint32_t stageFlags, + uint32_t offset, + uint32_t count, + const dom::ArrayBuffer& data) const; + void SetBindGroup(uint32_t index, const BindGroup& bindGroup) const; + void SetPipeline(const dom::WebGPUComputePipelineOrWebGPURenderPipeline& pipeline) const; + + // Compute pass commands + void BeginComputePass() const; + void EndComputePass() const; + + void Dispatch(uint32_t x, uint32_t y, uint32_t z) const; + + // Render pass commands + void BeginRenderPass(const dom::WebGPURenderPassDescriptor& desc) const; + void EndRenderPass() const; + + void SetBlendColor(float r, float g, float b, float a) const; + void SetIndexBuffer(const Buffer& buffer, uint32_t offset) const; + void SetVertexBuffers(uint32_t startSlot, + const dom::Sequence>& buffers, + const dom::Sequence& offsets) const; + + void Draw(uint32_t vertexCount, uint32_t instanceCount, uint32_t firstVertex, + uint32_t firstInstance) const; + void DrawIndexed(uint32_t indexCount, uint32_t instanceCount, uint32_t firstIndex, + uint32_t firstInstance, uint32_t firstVertex) const; +}; + +} // namespace webgpu +} // namespace mozilla + +#endif // WEBGPU_CommandEncoder_H_ diff --git a/dom/webgpu/CommandEncoder.cpp b/dom/webgpu/CommandEncoder.cpp new file mode 100644 --- /dev/null +++ b/dom/webgpu/CommandEncoder.cpp @@ -0,0 +1,147 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "CommandEncoder.h" + +#include "Device.h" +#include "mozilla/dom/WebGPUBinding.h" + +namespace mozilla { +namespace webgpu { + +CommandEncoder::~CommandEncoder() = default; + +already_AddRefed +CommandEncoder::FinishEncoding() const +{ + MOZ_CRASH("todo"); +} + +void +CommandEncoder::CopyBufferToBuffer(const Buffer& src, const uint32_t srcOffset, + const Buffer& dst, const uint32_t dstOffset, + const uint32_t size) const +{ + MOZ_CRASH("todo"); +} + +void +CommandEncoder::CopyBufferToTexture() const +{ + MOZ_CRASH("todo"); +} + +void +CommandEncoder::CopyTextureToBuffer() const +{ + MOZ_CRASH("todo"); +} + +void +CommandEncoder::CopyTextureToTexture() const +{ + MOZ_CRASH("todo"); +} + +void +CommandEncoder::Blit() const +{ + MOZ_CRASH("todo"); +} + +void +CommandEncoder::TransitionBuffer(const Buffer& b, const uint32_t flags) const +{ + MOZ_CRASH("todo"); +} + +void +CommandEncoder::SetPushConstants(const uint32_t stageFlags, const uint32_t offset, + const uint32_t count, const dom::ArrayBuffer& data) const +{ + MOZ_CRASH("todo"); +} + +void +CommandEncoder::SetBindGroup(const uint32_t index, const BindGroup& bindGroup) const +{ + MOZ_CRASH("todo"); +} + +void +CommandEncoder::SetPipeline(const dom::WebGPUComputePipelineOrWebGPURenderPipeline& pipeline) const +{ + MOZ_CRASH("todo"); +} + +void +CommandEncoder::BeginComputePass() const +{ + MOZ_CRASH("todo"); +} +void +CommandEncoder::EndComputePass() const +{ + MOZ_CRASH("todo"); +} + +void +CommandEncoder::Dispatch(const uint32_t x, const uint32_t y, const uint32_t z) const +{ + MOZ_CRASH("todo"); +} + +void +CommandEncoder::BeginRenderPass(const dom::WebGPURenderPassDescriptor& desc) const +{ + MOZ_CRASH("todo"); +} + +void +CommandEncoder::EndRenderPass() const +{ + MOZ_CRASH("todo"); +} + +void +CommandEncoder::SetBlendColor(const float r, const float g, const float b, + const float a) const +{ + MOZ_CRASH("todo"); +} + +void +CommandEncoder::SetIndexBuffer(const Buffer& buffer, const uint32_t offset) const +{ + MOZ_CRASH("todo"); +} + +void +CommandEncoder::SetVertexBuffers(const uint32_t startSlot, + const dom::Sequence>& buffers, + const dom::Sequence& offsets) const +{ + MOZ_CRASH("todo"); +} + +void +CommandEncoder::Draw(const uint32_t vertexCount, const uint32_t instanceCount, + const uint32_t firstVertex, const uint32_t firstInstance) const +{ + MOZ_CRASH("todo"); +} + +void +CommandEncoder::DrawIndexed(const uint32_t indexCount, const uint32_t instanceCount, + const uint32_t firstIndex, const uint32_t firstInstance, + const uint32_t firstVertex) const +{ + MOZ_CRASH("todo"); +} + +WEBGPU_IMPL_GOOP_0(CommandEncoder) + +} // namespace webgpu +} // namespace mozilla diff --git a/dom/webgpu/ComputePipeline.h b/dom/webgpu/ComputePipeline.h new file mode 100644 --- /dev/null +++ b/dom/webgpu/ComputePipeline.h @@ -0,0 +1,31 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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 WEBGPU_ComputePipeline_H_ +#define WEBGPU_ComputePipeline_H_ + +#include "nsWrapperCache.h" +#include "ObjectModel.h" + +namespace mozilla { +namespace webgpu { + +class Device; + +class ComputePipeline final + : public ChildOf +{ +public: + WEBGPU_DECL_GOOP(ComputePipeline) + +private: + ComputePipeline() = delete; + virtual ~ComputePipeline(); +}; + +} // namespace webgpu +} // namespace mozilla + +#endif // WEBGPU_ComputePipeline_H_ diff --git a/dom/webgpu/ComputePipeline.cpp b/dom/webgpu/ComputePipeline.cpp new file mode 100644 --- /dev/null +++ b/dom/webgpu/ComputePipeline.cpp @@ -0,0 +1,19 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "ComputePipeline.h" + +#include "Device.h" +#include "mozilla/dom/WebGPUBinding.h" + +namespace mozilla { +namespace webgpu { + +ComputePipeline::~ComputePipeline() = default; + +WEBGPU_IMPL_GOOP_0(ComputePipeline) + +} // namespace webgpu +} // namespace mozilla diff --git a/dom/webgpu/DepthStencilState.h b/dom/webgpu/DepthStencilState.h new file mode 100644 --- /dev/null +++ b/dom/webgpu/DepthStencilState.h @@ -0,0 +1,31 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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 WEBGPU_DepthStencilState_H_ +#define WEBGPU_DepthStencilState_H_ + +#include "nsWrapperCache.h" +#include "ObjectModel.h" + +namespace mozilla { +namespace webgpu { + +class Device; + +class DepthStencilState final + : public ChildOf +{ +public: + WEBGPU_DECL_GOOP(DepthStencilState) + +private: + DepthStencilState() = delete; + virtual ~DepthStencilState(); +}; + +} // namespace webgpu +} // namespace mozilla + +#endif // WEBGPU_DepthStencilState_H_ diff --git a/dom/webgpu/DepthStencilState.cpp b/dom/webgpu/DepthStencilState.cpp new file mode 100644 --- /dev/null +++ b/dom/webgpu/DepthStencilState.cpp @@ -0,0 +1,19 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "DepthStencilState.h" + +#include "Device.h" +#include "mozilla/dom/WebGPUBinding.h" + +namespace mozilla { +namespace webgpu { + +DepthStencilState::~DepthStencilState() = default; + +WEBGPU_IMPL_GOOP_0(DepthStencilState) + +} // namespace webgpu +} // namespace mozilla diff --git a/dom/webgpu/Device.h b/dom/webgpu/Device.h new file mode 100644 --- /dev/null +++ b/dom/webgpu/Device.h @@ -0,0 +1,103 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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 WEBGPU_DEVICE_H_ +#define WEBGPU_DEVICE_H_ + +#include "mozilla/RefPtr.h" +#include "ObjectModel.h" + +namespace mozilla { +namespace dom { +struct WebGPUExtensions; +struct WebGPUFeatures; +struct WebGPULimits; + +struct WebGPUBufferDescriptor; +struct WebGPUTextureDescriptor; +struct WebGPUSamplerDescriptor; +struct WebGPUBindGroupLayoutDescriptor; +struct WebGPUPipelineLayoutDescriptor; +struct WebGPUBindGroupDescriptor; +struct WebGPUBlendStateDescriptor; +struct WebGPUDepthStencilStateDescriptor; +struct WebGPUInputStateDescriptor; +struct WebGPUShaderModuleDescriptor; +struct WebGPUAttachmentStateDescriptor; +struct WebGPUComputePipelineDescriptor; +struct WebGPURenderPipelineDescriptor; +struct WebGPUCommandEncoderDescriptor; + +class Promise; +class WebGPUBufferOrWebGPUTexture; +class WebGPULogCallback; +} // namespace dom + +namespace webgpu { +class Adapter; +class AttachmentState; +class BindGroup; +class BindGroupLayout; +class BlendState; +class Buffer; +class CommandEncoder; +class ComputePipeline; +class DepthStencilState; +class Fence; +class InputState; +class PipelineLayout; +class Queue; +class RenderPipeline; +class Sampler; +class ShaderModule; +class Texture; + +class Device final + : public ChildOf +{ +public: + WEBGPU_DECL_GOOP(Device) + +private: + Device() = delete; + virtual ~Device(); + +public: + already_AddRefed Adapter() const; + + void Extensions(dom::WebGPUExtensions& out) const; + void Features(dom::WebGPUFeatures& out) const; + void Limits(dom::WebGPULimits& out) const; + + already_AddRefed CreateBuffer(const dom::WebGPUBufferDescriptor& desc) const; + already_AddRefed CreateTexture(const dom::WebGPUTextureDescriptor& desc) const; + already_AddRefed CreateSampler(const dom::WebGPUSamplerDescriptor& desc) const; + + already_AddRefed CreateBindGroupLayout(const dom::WebGPUBindGroupLayoutDescriptor& desc) const; + already_AddRefed CreatePipelineLayout(const dom::WebGPUPipelineLayoutDescriptor& desc) const; + already_AddRefed CreateBindGroup(const dom::WebGPUBindGroupDescriptor& desc) const; + + already_AddRefed CreateBlendState(const dom::WebGPUBlendStateDescriptor& desc) const; + already_AddRefed CreateDepthStencilState(const dom::WebGPUDepthStencilStateDescriptor& desc) const; + already_AddRefed CreateInputState(const dom::WebGPUInputStateDescriptor& desc) const; + already_AddRefed CreateShaderModule(const dom::WebGPUShaderModuleDescriptor& desc) const; + already_AddRefed CreateAttachmentState(const dom::WebGPUAttachmentStateDescriptor& desc) const; + already_AddRefed CreateComputePipeline(const dom::WebGPUComputePipelineDescriptor& desc) const; + already_AddRefed CreateRenderPipeline(const dom::WebGPURenderPipelineDescriptor& desc) const; + + already_AddRefed CreateCommandEncoder(const dom::WebGPUCommandEncoderDescriptor& desc) const; + + already_AddRefed GetQueue() const; + + RefPtr OnLog() const; + void SetOnLog(const dom::WebGPULogCallback& callback) const; + + already_AddRefed GetObjectStatus(const dom::WebGPUBufferOrWebGPUTexture& obj) const; +}; + +} // namespace webgpu +} // namespace mozilla + +#endif // WEBGPU_DEVICE_H_ diff --git a/dom/webgpu/Device.cpp b/dom/webgpu/Device.cpp new file mode 100644 --- /dev/null +++ b/dom/webgpu/Device.cpp @@ -0,0 +1,153 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "Device.h" + +#include "Adapter.h" +#include "mozilla/dom/WebGPUBinding.h" + +namespace mozilla { +namespace webgpu { + +Device::~Device() = default; + +already_AddRefed +Device::Adapter() const +{ + MOZ_CRASH("todo"); +} + +void +Device::Extensions(dom::WebGPUExtensions& out) const +{ + MOZ_CRASH("todo"); +} + +void +Device::Features(dom::WebGPUFeatures& out) const +{ + MOZ_CRASH("todo"); +} + +void +Device::Limits(dom::WebGPULimits& out) const +{ + MOZ_CRASH("todo"); +} + +already_AddRefed +Device::CreateBuffer(const dom::WebGPUBufferDescriptor& desc) const +{ + MOZ_CRASH("todo"); +} + +already_AddRefed +Device::CreateTexture(const dom::WebGPUTextureDescriptor& desc) const +{ + MOZ_CRASH("todo"); +} + +already_AddRefed +Device::CreateSampler(const dom::WebGPUSamplerDescriptor& desc) const +{ + MOZ_CRASH("todo"); +} + + +already_AddRefed +Device::CreateBindGroupLayout(const dom::WebGPUBindGroupLayoutDescriptor& desc) const +{ + MOZ_CRASH("todo"); +} + +already_AddRefed +Device::CreatePipelineLayout(const dom::WebGPUPipelineLayoutDescriptor& desc) const +{ + MOZ_CRASH("todo"); +} + +already_AddRefed +Device::CreateBindGroup(const dom::WebGPUBindGroupDescriptor& desc) const +{ + MOZ_CRASH("todo"); +} + + +already_AddRefed +Device::CreateBlendState(const dom::WebGPUBlendStateDescriptor& desc) const +{ + MOZ_CRASH("todo"); +} + +already_AddRefed +Device::CreateDepthStencilState(const dom::WebGPUDepthStencilStateDescriptor& desc) const +{ + MOZ_CRASH("todo"); +} + +already_AddRefed +Device::CreateInputState(const dom::WebGPUInputStateDescriptor& desc) const +{ + MOZ_CRASH("todo"); +} + +already_AddRefed +Device::CreateShaderModule(const dom::WebGPUShaderModuleDescriptor& desc) const +{ + MOZ_CRASH("todo"); +} + +already_AddRefed +Device::CreateAttachmentState(const dom::WebGPUAttachmentStateDescriptor& desc) const +{ + MOZ_CRASH("todo"); +} + +already_AddRefed +Device::CreateComputePipeline(const dom::WebGPUComputePipelineDescriptor& desc) const +{ + MOZ_CRASH("todo"); +} + +already_AddRefed +Device::CreateRenderPipeline(const dom::WebGPURenderPipelineDescriptor& desc) const +{ + MOZ_CRASH("todo"); +} + +already_AddRefed +Device::CreateCommandEncoder(const dom::WebGPUCommandEncoderDescriptor& desc) const +{ + MOZ_CRASH("todo"); +} + +already_AddRefed +Device::GetQueue() const +{ + MOZ_CRASH("todo"); +} + +RefPtr +Device::OnLog() const +{ + MOZ_CRASH("todo"); +} + +void +Device::SetOnLog(const dom::WebGPULogCallback& callback) const +{ + MOZ_CRASH("todo"); +} + +already_AddRefed +Device::GetObjectStatus(const dom::WebGPUBufferOrWebGPUTexture& obj) const +{ + MOZ_CRASH("todo"); +} + +WEBGPU_IMPL_GOOP_0(Device) + +} // namespace webgpu +} // namespace mozilla diff --git a/dom/webgpu/Fence.h b/dom/webgpu/Fence.h new file mode 100644 --- /dev/null +++ b/dom/webgpu/Fence.h @@ -0,0 +1,38 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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 WEBGPU_Fence_H_ +#define WEBGPU_Fence_H_ + +#include "nsWrapperCache.h" +#include "ObjectModel.h" + +namespace mozilla { +namespace dom { +class Promise; +} // namespace dom +namespace webgpu { + +class Device; + +class Fence final + : public ChildOf +{ +public: + WEBGPU_DECL_GOOP(Fence) + +private: + Fence() = delete; + virtual ~Fence(); + +public: + bool Wait(double milliseconds) const; + already_AddRefed Promise() const; +}; + +} // namespace webgpu +} // namespace mozilla + +#endif // WEBGPU_Fence_H_ diff --git a/dom/webgpu/Fence.cpp b/dom/webgpu/Fence.cpp new file mode 100644 --- /dev/null +++ b/dom/webgpu/Fence.cpp @@ -0,0 +1,31 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "Fence.h" + +#include "Device.h" +#include "mozilla/dom/WebGPUBinding.h" + +namespace mozilla { +namespace webgpu { + +Fence::~Fence() = default; + +bool +Fence::Wait(const double milliseconds) const +{ + MOZ_CRASH("todo"); +} + +already_AddRefed +Fence::Promise() const +{ + MOZ_CRASH("todo"); +} + +WEBGPU_IMPL_GOOP_0(Fence) + +} // namespace webgpu +} // namespace mozilla diff --git a/dom/webgpu/InputState.h b/dom/webgpu/InputState.h new file mode 100644 --- /dev/null +++ b/dom/webgpu/InputState.h @@ -0,0 +1,31 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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 WEBGPU_InputState_H_ +#define WEBGPU_InputState_H_ + +#include "nsWrapperCache.h" +#include "ObjectModel.h" + +namespace mozilla { +namespace webgpu { + +class Device; + +class InputState final + : public ChildOf +{ +public: + WEBGPU_DECL_GOOP(InputState) + +private: + InputState() = delete; + virtual ~InputState(); +}; + +} // namespace webgpu +} // namespace mozilla + +#endif // WEBGPU_InputState_H_ diff --git a/dom/webgpu/InputState.cpp b/dom/webgpu/InputState.cpp new file mode 100644 --- /dev/null +++ b/dom/webgpu/InputState.cpp @@ -0,0 +1,19 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "InputState.h" + +#include "Device.h" +#include "mozilla/dom/WebGPUBinding.h" + +namespace mozilla { +namespace webgpu { + +InputState::~InputState() = default; + +WEBGPU_IMPL_GOOP_0(InputState) + +} // namespace webgpu +} // namespace mozilla diff --git a/dom/webgpu/Instance.h b/dom/webgpu/Instance.h new file mode 100644 --- /dev/null +++ b/dom/webgpu/Instance.h @@ -0,0 +1,46 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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 WEBGPU_INSTANCE_H_ +#define WEBGPU_INSTANCE_H_ + +#include "mozilla/AlreadyAddRefed.h" +#include "mozilla/RefPtr.h" +#include "nsCOMPtr.h" +#include "ObjectModel.h" + +namespace mozilla { +namespace dom { +struct WebGPUAdapterDescriptor; +} // namespace dom + +namespace webgpu { +class Adapter; +class InstanceProvider; + +class Instance final + : public nsWrapperCache +{ +public: + WEBGPU_DECL_GOOP(Instance) + + const nsCOMPtr mParent; + + static RefPtr Create(nsIGlobalObject* parent); + +private: + explicit Instance(nsIGlobalObject* parent); + virtual ~Instance(); + +public: + nsIGlobalObject* GetParentObject() const { return mParent.get(); } + + already_AddRefed GetAdapter(const dom::WebGPUAdapterDescriptor& desc) const; +}; + +} // namespace webgpu +} // namespace mozilla + +#endif // WEBGPU_INSTANCE_H_ diff --git a/dom/webgpu/Instance.cpp b/dom/webgpu/Instance.cpp new file mode 100644 --- /dev/null +++ b/dom/webgpu/Instance.cpp @@ -0,0 +1,63 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "Instance.h" + +#include "Adapter.h" +#include "InstanceProvider.h" +#include "mozilla/dom/WebGPUBinding.h" +#include "nsIGlobalObject.h" + +namespace mozilla { +namespace webgpu { + +/*static*/ RefPtr +Instance::Create(nsIGlobalObject* parent) +{ + return new Instance(parent); +} + +Instance::Instance(nsIGlobalObject* parent) + : mParent(parent) +{ +} + +Instance::~Instance() = default; + +already_AddRefed +Instance::GetAdapter(const dom::WebGPUAdapterDescriptor& desc) const +{ + MOZ_CRASH("todo"); +} + +template +void +ImplCycleCollectionTraverse(nsCycleCollectionTraversalCallback& callback, + const nsCOMPtr& field, + const char* name, uint32_t flags) +{ + CycleCollectionNoteChild(callback, field.get(), name, flags); +} + +template +void +ImplCycleCollectionUnlink(const nsCOMPtr& field) +{ + const auto mut = const_cast*>(&field); + *mut = nullptr; +} + +JSObject* +Instance::WrapObject(JSContext* cx, JS::Handle givenProto) +{ + return dom::WebGPU_Binding::Wrap(cx, this, givenProto); +} +NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(Instance, AddRef) +NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(Instance, Release) + +NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(Instance, mParent) + +} // namespace webgpu +} // namespace mozilla diff --git a/dom/webgpu/InstanceProvider.h b/dom/webgpu/InstanceProvider.h new file mode 100644 --- /dev/null +++ b/dom/webgpu/InstanceProvider.h @@ -0,0 +1,60 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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 WEBGPU_INSTANCE_PROVIDER_H_ +#define WEBGPU_INSTANCE_PROVIDER_H_ + +#include "mozilla/AlreadyAddRefed.h" +#include "mozilla/Maybe.h" +#include "mozilla/RefPtr.h" + +class nsCycleCollectionTraversalCallback; +class nsIGlobalObject; + +namespace mozilla { +namespace webgpu { +class Instance; + +class InstanceProvider +{ +private: + nsIGlobalObject* const mGlobal; + mutable Maybe> mInstance; + +protected: + explicit InstanceProvider(nsIGlobalObject* global); + virtual ~InstanceProvider(); + +public: + already_AddRefed Webgpu() const; + + nsIGlobalObject* GetParentObject() const { return mGlobal; } + + void CcTraverse(nsCycleCollectionTraversalCallback&) const; + void CcUnlink(); +}; + +template +void +ImplCycleCollectionTraverse(nsCycleCollectionTraversalCallback& callback, + const Maybe& field, + const char* name, uint32_t flags) +{ + if (field) { + CycleCollectionNoteChild(callback, field.value(), name, flags); + } +} + +template +void +ImplCycleCollectionUnlink(Maybe& field) +{ + field = Nothing(); +} + +} // namespace webgpu +} // namespace mozilla + +#endif // WEBGPU_INSTANCE_PROVIDER_H_ diff --git a/dom/webgpu/InstanceProvider.cpp b/dom/webgpu/InstanceProvider.cpp new file mode 100644 --- /dev/null +++ b/dom/webgpu/InstanceProvider.cpp @@ -0,0 +1,47 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "InstanceProvider.h" + +#include "Instance.h" +#include "mozilla/dom/WebGPUBinding.h" + +namespace mozilla { +namespace webgpu { + +InstanceProvider::InstanceProvider(nsIGlobalObject* const global) + : mGlobal(global) +{ } + +InstanceProvider::~InstanceProvider() = default; + +already_AddRefed +InstanceProvider::Webgpu() const +{ + if (!mInstance) { + const auto inst = Instance::Create(mGlobal); + mInstance = Some(inst); + } + auto ret = mInstance.value(); + return ret.forget(); +} + +void +InstanceProvider::CcTraverse(nsCycleCollectionTraversalCallback& callback) const +{ + if (mInstance) { + CycleCollectionNoteChild(callback, mInstance.ref().get(), + "webgpu::InstanceProvider::mInstance", 0); + } +} + +void +InstanceProvider::CcUnlink() +{ + mInstance = Some(nullptr); +} + +} // namespace webgpu +} // namespace mozilla diff --git a/dom/webgpu/LogEntry.h b/dom/webgpu/LogEntry.h new file mode 100644 --- /dev/null +++ b/dom/webgpu/LogEntry.h @@ -0,0 +1,39 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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 WEBGPU_LogEntry_H_ +#define WEBGPU_LogEntry_H_ + +#include "nsWrapperCache.h" +#include "ObjectModel.h" + +namespace mozilla { +namespace dom { +enum class WebGPULogEntryType : uint8_t; +} // namespace dom +namespace webgpu { + +class Device; + +class LogEntry final + : public ChildOf +{ +public: + WEBGPU_DECL_GOOP(LogEntry) + +private: + LogEntry() = delete; + virtual ~LogEntry(); + +public: + dom::WebGPULogEntryType Type() const; + void GetObj(JSContext* cx, JS::MutableHandleValue out) const; + void GetReason(nsString& out) const; +}; + +} // namespace webgpu +} // namespace mozilla + +#endif // WEBGPU_LogEntry_H_ diff --git a/dom/webgpu/LogEntry.cpp b/dom/webgpu/LogEntry.cpp new file mode 100644 --- /dev/null +++ b/dom/webgpu/LogEntry.cpp @@ -0,0 +1,37 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "LogEntry.h" + +#include "Device.h" +#include "mozilla/dom/WebGPUBinding.h" + +namespace mozilla { +namespace webgpu { + +LogEntry::~LogEntry() = default; + +dom::WebGPULogEntryType +LogEntry::Type() const +{ + MOZ_CRASH("todo"); +} + +void +LogEntry::GetObj(JSContext* cx, JS::MutableHandleValue out) const +{ + MOZ_CRASH("todo"); +} + +void +LogEntry::GetReason(nsString& out) const +{ + MOZ_CRASH("todo"); +} + +WEBGPU_IMPL_GOOP_0(LogEntry) + +} // namespace webgpu +} // namespace mozilla diff --git a/dom/webgpu/ObjectModel.h b/dom/webgpu/ObjectModel.h new file mode 100644 --- /dev/null +++ b/dom/webgpu/ObjectModel.h @@ -0,0 +1,73 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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 WEBGPU_OBJECT_MODEL_H_ +#define WEBGPU_OBJECT_MODEL_H_ + +#include "nsWrapperCache.h" + +class nsIGlobalObject; + +namespace mozilla { +namespace webgpu { + +template +class ChildOf + : public nsWrapperCache +{ +public: + const RefPtr mParent; + + explicit ChildOf(T* parent = nullptr); // TODO: This can't be nullptr eventually. +protected: + virtual ~ChildOf(); + +public: + nsIGlobalObject* GetParentObject() const; +}; + +} // namespace webgpu +} // namespace mozilla + +#define WEBGPU_DECL_GOOP(T) \ + NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(T) \ + NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_NATIVE_CLASS(T) \ + virtual JSObject* WrapObject(JSContext* cx, JS::Handle givenProto) override; + +#define WEBGPU_IMPL_GOOP_INTERNAL(T) \ + JSObject* \ + T::WrapObject(JSContext* cx, JS::Handle givenProto) \ + { \ + return dom::WebGPU ## T ## _Binding::Wrap(cx, this, givenProto); \ + } \ + NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(T, AddRef) \ + NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(T, Release) + +#define WEBGPU_IMPL_GOOP(T,...) \ + WEBGPU_IMPL_GOOP_INTERNAL(T) \ + NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(T, mParent, __VA_ARGS__) + +#define WEBGPU_IMPL_GOOP_0(T) \ + WEBGPU_IMPL_GOOP_INTERNAL(T) \ + NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(T, mParent) + +template +void +ImplCycleCollectionTraverse(nsCycleCollectionTraversalCallback& callback, + const RefPtr& field, + const char* name, uint32_t flags) +{ + CycleCollectionNoteChild(callback, field.get(), name, flags); +} + +template +void +ImplCycleCollectionUnlink(const RefPtr& field) +{ + const auto mutPtr = const_cast< RefPtr* >(&field); + ImplCycleCollectionUnlink(*mutPtr); +} + +#endif // WEBGPU_OBJECT_MODEL_H_ diff --git a/dom/webgpu/ObjectModel.cpp b/dom/webgpu/ObjectModel.cpp new file mode 100644 --- /dev/null +++ b/dom/webgpu/ObjectModel.cpp @@ -0,0 +1,35 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "ObjectModel.h" + +#include "Adapter.h" +#include "Device.h" +#include "Instance.h" + +namespace mozilla { +namespace webgpu { + +template +ChildOf::ChildOf(T* const parent) + : mParent(parent) +{ } + +template +ChildOf::~ChildOf() = default; + +template +nsIGlobalObject* +ChildOf::GetParentObject() const +{ + return mParent->GetParentObject(); +} + +template class ChildOf; +template class ChildOf; +template class ChildOf; + +} // namespace webgpu +} // namespace mozilla diff --git a/dom/webgpu/PipelineLayout.h b/dom/webgpu/PipelineLayout.h new file mode 100644 --- /dev/null +++ b/dom/webgpu/PipelineLayout.h @@ -0,0 +1,31 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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 WEBGPU_PipelineLayout_H_ +#define WEBGPU_PipelineLayout_H_ + +#include "nsWrapperCache.h" +#include "ObjectModel.h" + +namespace mozilla { +namespace webgpu { + +class Device; + +class PipelineLayout final + : public ChildOf +{ +public: + WEBGPU_DECL_GOOP(PipelineLayout) + +private: + PipelineLayout() = delete; + virtual ~PipelineLayout(); +}; + +} // namespace webgpu +} // namespace mozilla + +#endif // WEBGPU_PipelineLayout_H_ diff --git a/dom/webgpu/PipelineLayout.cpp b/dom/webgpu/PipelineLayout.cpp new file mode 100644 --- /dev/null +++ b/dom/webgpu/PipelineLayout.cpp @@ -0,0 +1,19 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "PipelineLayout.h" + +#include "Device.h" +#include "mozilla/dom/WebGPUBinding.h" + +namespace mozilla { +namespace webgpu { + +PipelineLayout::~PipelineLayout() = default; + +WEBGPU_IMPL_GOOP_0(PipelineLayout) + +} // namespace webgpu +} // namespace mozilla diff --git a/dom/webgpu/Queue.h b/dom/webgpu/Queue.h new file mode 100644 --- /dev/null +++ b/dom/webgpu/Queue.h @@ -0,0 +1,41 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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 WEBGPU_Queue_H_ +#define WEBGPU_Queue_H_ + +#include "nsWrapperCache.h" +#include "ObjectModel.h" + +namespace mozilla { +namespace dom { +template class Sequence; +} // namespace dom + +namespace webgpu { + +class CommandBuffer; +class Device; +class Fence; + +class Queue final + : public ChildOf +{ +public: + WEBGPU_DECL_GOOP(Queue) + +private: + Queue() = delete; + virtual ~Queue(); + +public: + void Submit(const dom::Sequence>& buffers) const; + already_AddRefed InsertFence() const; +}; + +} // namespace webgpu +} // namespace mozilla + +#endif // WEBGPU_Queue_H_ diff --git a/dom/webgpu/Queue.cpp b/dom/webgpu/Queue.cpp new file mode 100644 --- /dev/null +++ b/dom/webgpu/Queue.cpp @@ -0,0 +1,31 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "Queue.h" + +#include "Device.h" +#include "mozilla/dom/WebGPUBinding.h" + +namespace mozilla { +namespace webgpu { + +Queue::~Queue() = default; + +void +Queue::Submit(const dom::Sequence>& buffers) const +{ + MOZ_CRASH("todo"); +} + +already_AddRefed +Queue::InsertFence() const +{ + MOZ_CRASH("todo"); +} + +WEBGPU_IMPL_GOOP_0(Queue) + +} // namespace webgpu +} // namespace mozilla diff --git a/dom/webgpu/RenderPipeline.h b/dom/webgpu/RenderPipeline.h new file mode 100644 --- /dev/null +++ b/dom/webgpu/RenderPipeline.h @@ -0,0 +1,31 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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 WEBGPU_RenderPipeline_H_ +#define WEBGPU_RenderPipeline_H_ + +#include "nsWrapperCache.h" +#include "ObjectModel.h" + +namespace mozilla { +namespace webgpu { + +class Device; + +class RenderPipeline final + : public ChildOf +{ +public: + WEBGPU_DECL_GOOP(RenderPipeline) + +private: + RenderPipeline() = delete; + virtual ~RenderPipeline(); +}; + +} // namespace webgpu +} // namespace mozilla + +#endif // WEBGPU_RenderPipeline_H_ diff --git a/dom/webgpu/RenderPipeline.cpp b/dom/webgpu/RenderPipeline.cpp new file mode 100644 --- /dev/null +++ b/dom/webgpu/RenderPipeline.cpp @@ -0,0 +1,19 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "RenderPipeline.h" + +#include "Device.h" +#include "mozilla/dom/WebGPUBinding.h" + +namespace mozilla { +namespace webgpu { + +RenderPipeline::~RenderPipeline() = default; + +WEBGPU_IMPL_GOOP_0(RenderPipeline) + +} // namespace webgpu +} // namespace mozilla diff --git a/dom/webgpu/Sampler.h b/dom/webgpu/Sampler.h new file mode 100644 --- /dev/null +++ b/dom/webgpu/Sampler.h @@ -0,0 +1,31 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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 WEBGPU_SAMPLER_H_ +#define WEBGPU_SAMPLER_H_ + +#include "nsWrapperCache.h" +#include "ObjectModel.h" + +namespace mozilla { +namespace webgpu { + +class Device; + +class Sampler final + : public ChildOf +{ +public: + WEBGPU_DECL_GOOP(Sampler) + +private: + Sampler() = delete; + virtual ~Sampler(); +}; + +} // namespace webgpu +} // namespace mozilla + +#endif // WEBGPU_SAMPLER_H_ diff --git a/dom/webgpu/Sampler.cpp b/dom/webgpu/Sampler.cpp new file mode 100644 --- /dev/null +++ b/dom/webgpu/Sampler.cpp @@ -0,0 +1,19 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "Sampler.h" + +#include "Device.h" +#include "mozilla/dom/WebGPUBinding.h" + +namespace mozilla { +namespace webgpu { + +Sampler::~Sampler() = default; + +WEBGPU_IMPL_GOOP_0(Sampler) + +} // namespace webgpu +} // namespace mozilla diff --git a/dom/webgpu/ShaderModule.h b/dom/webgpu/ShaderModule.h new file mode 100644 --- /dev/null +++ b/dom/webgpu/ShaderModule.h @@ -0,0 +1,31 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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 WEBGPU_ShaderModule_H_ +#define WEBGPU_ShaderModule_H_ + +#include "nsWrapperCache.h" +#include "ObjectModel.h" + +namespace mozilla { +namespace webgpu { + +class Device; + +class ShaderModule final + : public ChildOf +{ +public: + WEBGPU_DECL_GOOP(ShaderModule) + +private: + ShaderModule() = delete; + virtual ~ShaderModule(); +}; + +} // namespace webgpu +} // namespace mozilla + +#endif // WEBGPU_ShaderModule_H_ diff --git a/dom/webgpu/ShaderModule.cpp b/dom/webgpu/ShaderModule.cpp new file mode 100644 --- /dev/null +++ b/dom/webgpu/ShaderModule.cpp @@ -0,0 +1,19 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "ShaderModule.h" + +#include "Device.h" +#include "mozilla/dom/WebGPUBinding.h" + +namespace mozilla { +namespace webgpu { + +ShaderModule::~ShaderModule() = default; + +WEBGPU_IMPL_GOOP_0(ShaderModule) + +} // namespace webgpu +} // namespace mozilla diff --git a/dom/webgpu/SwapChain.h b/dom/webgpu/SwapChain.h new file mode 100644 --- /dev/null +++ b/dom/webgpu/SwapChain.h @@ -0,0 +1,41 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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 WEBGPU_SwapChain_H_ +#define WEBGPU_SwapChain_H_ + +#include "nsWrapperCache.h" +#include "ObjectModel.h" + +namespace mozilla { +namespace dom { +struct WebGPUSwapChainDescriptor; +} // namespace dom + +namespace webgpu { + +class Device; +class Texture; + +class SwapChain final + : public ChildOf +{ +public: + WEBGPU_DECL_GOOP(SwapChain) + +private: + SwapChain() = delete; + virtual ~SwapChain(); + +public: + void Configure(const dom::WebGPUSwapChainDescriptor& desc) const; + already_AddRefed GetNextTexture() const; + void Present() const; +}; + +} // namespace webgpu +} // namespace mozilla + +#endif // WEBGPU_SwapChain_H_ diff --git a/dom/webgpu/SwapChain.cpp b/dom/webgpu/SwapChain.cpp new file mode 100644 --- /dev/null +++ b/dom/webgpu/SwapChain.cpp @@ -0,0 +1,37 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "SwapChain.h" + +#include "Device.h" +#include "mozilla/dom/WebGPUBinding.h" + +namespace mozilla { +namespace webgpu { + +SwapChain::~SwapChain() = default; + +void +SwapChain::Configure(const dom::WebGPUSwapChainDescriptor& desc) const +{ + MOZ_CRASH("todo"); +} + +already_AddRefed +SwapChain::GetNextTexture() const +{ + MOZ_CRASH("todo"); +} + +void +SwapChain::Present() const +{ + MOZ_CRASH("todo"); +} + +WEBGPU_IMPL_GOOP_0(SwapChain) + +} // namespace webgpu +} // namespace mozilla diff --git a/dom/webgpu/Texture.h b/dom/webgpu/Texture.h new file mode 100644 --- /dev/null +++ b/dom/webgpu/Texture.h @@ -0,0 +1,39 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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 WEBGPU_Texture_H_ +#define WEBGPU_Texture_H_ + +#include "nsWrapperCache.h" +#include "ObjectModel.h" + +namespace mozilla { +namespace dom { +struct WebGPUTextureViewDescriptor; +} // namespace dom + +namespace webgpu { + +class Device; +class TextureView; + +class Texture final + : public ChildOf +{ +public: + WEBGPU_DECL_GOOP(Texture) + +private: + Texture() = delete; + virtual ~Texture(); + +public: + already_AddRefed CreateTextureView(const dom::WebGPUTextureViewDescriptor&) const; +}; + +} // namespace webgpu +} // namespace mozilla + +#endif // WEBGPU_Texture_H_ diff --git a/dom/webgpu/Texture.cpp b/dom/webgpu/Texture.cpp new file mode 100644 --- /dev/null +++ b/dom/webgpu/Texture.cpp @@ -0,0 +1,26 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "Texture.h" + +#include "Device.h" +#include "mozilla/dom/WebGPUBinding.h" +#include "TextureView.h" + +namespace mozilla { +namespace webgpu { + +Texture::~Texture() = default; + +already_AddRefed +Texture::CreateTextureView(const dom::WebGPUTextureViewDescriptor& desc) const +{ + MOZ_CRASH("todo"); +} + +WEBGPU_IMPL_GOOP_0(Texture) + +} // namespace webgpu +} // namespace mozilla diff --git a/dom/webgpu/TextureView.h b/dom/webgpu/TextureView.h new file mode 100644 --- /dev/null +++ b/dom/webgpu/TextureView.h @@ -0,0 +1,31 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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 WEBGPU_TEXTURE_VIEW_H_ +#define WEBGPU_TEXTURE_VIEW_H_ + +#include "nsWrapperCache.h" +#include "ObjectModel.h" + +namespace mozilla { +namespace webgpu { + +class Device; + +class TextureView final + : public ChildOf +{ +public: + WEBGPU_DECL_GOOP(TextureView) + +private: + TextureView() = delete; + virtual ~TextureView(); +}; + +} // namespace webgpu +} // namespace mozilla + +#endif // WEBGPU_TEXTURE_VIEW_H_ diff --git a/dom/webgpu/TextureView.cpp b/dom/webgpu/TextureView.cpp new file mode 100644 --- /dev/null +++ b/dom/webgpu/TextureView.cpp @@ -0,0 +1,19 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "TextureView.h" + +#include "Device.h" +#include "mozilla/dom/WebGPUBinding.h" + +namespace mozilla { +namespace webgpu { + +TextureView::~TextureView() = default; + +WEBGPU_IMPL_GOOP_0(TextureView) + +} // namespace webgpu +} // namespace mozilla diff --git a/dom/webgpu/mochitest/mochitest-no-pref.ini b/dom/webgpu/mochitest/mochitest-no-pref.ini new file mode 100644 --- /dev/null +++ b/dom/webgpu/mochitest/mochitest-no-pref.ini @@ -0,0 +1,5 @@ +[DEFAULT] +subsuite = webgl1-core + +[test_disabled.html] + diff --git a/dom/webgpu/mochitest/mochitest.ini b/dom/webgpu/mochitest/mochitest.ini new file mode 100644 --- /dev/null +++ b/dom/webgpu/mochitest/mochitest.ini @@ -0,0 +1,6 @@ +[DEFAULT] +subsuite = webgl1-core +prefs = dom.webgpu.enable=true + +[test_enabled.html] + diff --git a/dom/webgpu/mochitest/test_disabled.html b/dom/webgpu/mochitest/test_disabled.html new file mode 100644 --- /dev/null +++ b/dom/webgpu/mochitest/test_disabled.html @@ -0,0 +1,18 @@ + + + + + + + + + + + + diff --git a/dom/webgpu/mochitest/test_enabled.html b/dom/webgpu/mochitest/test_enabled.html new file mode 100644 --- /dev/null +++ b/dom/webgpu/mochitest/test_enabled.html @@ -0,0 +1,18 @@ + + + + + + + + + + + + diff --git a/dom/webgpu/moz.build b/dom/webgpu/moz.build new file mode 100644 --- /dev/null +++ b/dom/webgpu/moz.build @@ -0,0 +1,49 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +with Files('**'): + BUG_COMPONENT = ('Core', 'Canvas: WebGL') + +MOCHITEST_MANIFESTS += [ + 'mochitest/mochitest-no-pref.ini', + 'mochitest/mochitest.ini', +] + +h_and_cpp = [ + 'Adapter', + 'AttachmentState', + 'BindGroup', + 'BindGroupLayout', + 'BlendState', + 'Buffer', + 'CommandBuffer', + 'CommandEncoder', + 'ComputePipeline', + 'DepthStencilState', + 'Device', + 'Fence', + 'InputState', + 'Instance', + 'InstanceProvider', + 'LogEntry', + 'ObjectModel', + 'PipelineLayout', + 'Queue', + 'RenderPipeline', + 'Sampler', + 'ShaderModule', + 'SwapChain', + 'Texture', + 'TextureView', +] +EXPORTS.mozilla.webgpu += [x + '.h' for x in h_and_cpp] +UNIFIED_SOURCES += [x + '.cpp' for x in h_and_cpp] + +EXPORTS.mozilla.webgpu += [ +# 'ObjectModel.h', +] + +FINAL_LIBRARY = 'xul' diff --git a/dom/webidl/Element.webidl b/dom/webidl/Element.webidl --- a/dom/webidl/Element.webidl +++ b/dom/webidl/Element.webidl @@ -39,10 +39,12 @@ [Pure] DOMString? getAttribute(DOMString name); [Pure] DOMString? getAttributeNS(DOMString? namespace, DOMString localName); [CEReactions, NeedsSubjectPrincipal=NonSystem, Throws] + boolean toggleAttribute(DOMString name, optional boolean force); + [CEReactions, NeedsSubjectPrincipal=NonSystem, Throws] void setAttribute(DOMString name, DOMString value); [CEReactions, NeedsSubjectPrincipal=NonSystem, Throws] void setAttributeNS(DOMString? namespace, DOMString name, DOMString value); [CEReactions, Throws] void removeAttribute(DOMString name); diff --git a/dom/webidl/MediaCapabilities.webidl b/dom/webidl/MediaCapabilities.webidl new file mode 100644 --- /dev/null +++ b/dom/webidl/MediaCapabilities.webidl @@ -0,0 +1,70 @@ +/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. + * + * The origin of this IDL file is + * https://wicg.github.io/media-capabilities/ + * + * Copyright © 2018 the Contributors to the Media Capabilities Specification + */ + +dictionary MediaConfiguration { + VideoConfiguration video; + AudioConfiguration audio; +}; + +dictionary MediaDecodingConfiguration : MediaConfiguration { + required MediaDecodingType type; +}; + +dictionary MediaEncodingConfiguration : MediaConfiguration { + required MediaEncodingType type; +}; + +enum MediaDecodingType { + "file", + "media-source", +}; + +enum MediaEncodingType { + "record", + "transmission" +}; + +// all members are specified as required in the spec. +// We enforce that requirement in the MediaCapabilities code instead +// See https://github.com/heycam/webidl/issues/76 +dictionary VideoConfiguration { + DOMString contentType; + unsigned long width; + unsigned long height; + unsigned long long bitrate; + DOMString framerate; +}; + +// contentType member is specified as required in the spec. +// We enforce that requirement in the MediaCapabilities code instead +// See https://github.com/heycam/webidl/issues/76 +dictionary AudioConfiguration { + DOMString contentType; + DOMString channels; + unsigned long long bitrate; + unsigned long samplerate; +}; + +[Exposed=(Window, Worker), Func="mozilla::dom::MediaCapabilities::Enabled", + HeaderFile="mozilla/dom/MediaCapabilities.h"] +interface MediaCapabilitiesInfo { + readonly attribute boolean supported; + readonly attribute boolean smooth; + readonly attribute boolean powerEfficient; +}; + +[Exposed=(Window, Worker), Func="mozilla::dom::MediaCapabilities::Enabled"] +interface MediaCapabilities { + [NewObject] + Promise decodingInfo(MediaDecodingConfiguration configuration); + [NewObject] + Promise encodingInfo(MediaEncodingConfiguration configuration); +}; diff --git a/dom/webidl/Navigator.webidl b/dom/webidl/Navigator.webidl --- a/dom/webidl/Navigator.webidl +++ b/dom/webidl/Navigator.webidl @@ -14,10 +14,11 @@ * http://www.w3.org/TR/beacon/#sec-beacon-method * https://html.spec.whatwg.org/#navigatorconcurrenthardware * http://wicg.github.io/netinfo/#extensions-to-the-navigator-interface * https://w3c.github.io/webappsec-credential-management/#framework-credential-management * https://w3c.github.io/webdriver/webdriver-spec.html#interface + * https://wicg.github.io/media-capabilities/#idl-index * * © Copyright 2004-2011 Apple Computer, Inc., Mozilla Foundation, and * Opera Software ASA. You are granted a license to use, reproduce * and create derivative works of this document. */ @@ -148,10 +149,17 @@ partial interface Navigator { [Pref="dom.w3c_pointer_events.enabled"] readonly attribute long maxTouchPoints; }; +// https://wicg.github.io/media-capabilities/#idl-index +[Exposed=Window] +partial interface Navigator { + [SameObject, Func="mozilla::dom::MediaCapabilities::Enabled"] + readonly attribute MediaCapabilities mediaCapabilities; +}; + // Mozilla-specific extensions // Chrome-only interface for Vibration API permission handling. partial interface Navigator { /* Set permission state to device vibration. diff --git a/dom/webidl/Screen.webidl b/dom/webidl/Screen.webidl --- a/dom/webidl/Screen.webidl +++ b/dom/webidl/Screen.webidl @@ -57,5 +57,29 @@ // https://w3c.github.io/screen-orientation partial interface Screen { readonly attribute ScreenOrientation orientation; }; + +// https://wicg.github.io/media-capabilities/#idl-index +enum ScreenColorGamut { + "srgb", + "p3", + "rec2020", +}; + +[Func="mozilla::dom::MediaCapabilities::Enabled"] +interface ScreenLuminance { + readonly attribute double min; + readonly attribute double max; + readonly attribute double maxAverage; +}; + +partial interface Screen { + [Func="mozilla::dom::MediaCapabilities::Enabled"] + readonly attribute ScreenColorGamut colorGamut; + [Func="mozilla::dom::MediaCapabilities::Enabled"] + readonly attribute ScreenLuminance? luminance; + + [Func="mozilla::dom::MediaCapabilities::Enabled"] + attribute EventHandler onchange; +}; diff --git a/dom/webidl/ScrollBoxObject.webidl b/dom/webidl/ScrollBoxObject.webidl --- a/dom/webidl/ScrollBoxObject.webidl +++ b/dom/webidl/ScrollBoxObject.webidl @@ -21,19 +21,13 @@ * Values will be clamped to make the resuling position legal. */ [Throws] void scrollBy(long dx, long dy); [Throws] - void scrollByLine(long dlines); - [Throws] void scrollByIndex(long dindexes); [Throws] - void scrollToLine(long line); - [Throws] void scrollToElement(Element child); - [Throws] - void scrollToIndex(long index); /** * Get the current scroll position in css pixels. * @see scrollTo for the definition of x and y. */ @@ -44,27 +38,8 @@ [Pure, Throws] readonly attribute long scrolledWidth; [Pure, Throws] readonly attribute long scrolledHeight; - /** - * DEPRECATED: Please use positionX and positionY - * - * Get the current scroll position in css pixels. - * @see scrollTo for the definition of x and y. - */ - [Throws] - void getPosition(object x, object y); - - /** - * DEPRECATED: Please use scrolledWidth and scrolledHeight - */ - [Throws] - void getScrolledSize(object width, object height); - [Throws] void ensureElementIsVisible(Element child); - [Throws] - void ensureIndexIsVisible(long index); - [Throws] - void ensureLineIsVisible(long line); }; diff --git a/dom/webidl/WebGPU.webidl b/dom/webidl/WebGPU.webidl new file mode 100644 --- /dev/null +++ b/dom/webidl/WebGPU.webidl @@ -0,0 +1,639 @@ +/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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/. + * + * The origin of this IDL file is + * https://github.com/gpuweb/gpuweb/blob/master/design/sketch.webidl + */ + +typedef unsigned long u32; +typedef unsigned long long u64; + +// **************************************************************************** +// ERROR HANDLING +// **************************************************************************** + +enum WebGPULogEntryType { + "device-lost", + "validation-error", + "recoverable-out-of-memory", +}; + +[Pref="dom.webgpu.enable"] +interface WebGPULogEntry { + readonly attribute WebGPULogEntryType type; + readonly attribute any obj; + readonly attribute DOMString? reason; +}; + +enum WebGPUObjectStatus { + "valid", + "out-of-memory", + "invalid", +}; + +typedef (WebGPUBuffer or WebGPUTexture) WebGPUStatusable; + +callback WebGPULogCallback = void (WebGPULogEntry error); + +// **************************************************************************** +// SHADER RESOURCES (buffer, textures, texture views, samples) +// **************************************************************************** + +// Buffer +typedef u32 WebGPUBufferUsageFlags; +[Pref="dom.webgpu.enable"] +interface WebGPUBufferUsage { + const u32 NONE = 0; + const u32 MAP_READ = 1; + const u32 MAP_WRITE = 2; + const u32 TRANSFER_SRC = 4; + const u32 TRANSFER_DST = 8; + const u32 INDEX = 16; + const u32 VERTEX = 32; + const u32 UNIFORM = 64; + const u32 STORAGE = 128; +}; + +dictionary WebGPUBufferDescriptor { + u32 size; + WebGPUBufferUsageFlags usage; +}; + +[Pref="dom.webgpu.enable"] +interface WebGPUBuffer { + readonly attribute ArrayBuffer? mapping; + void unmap(); +}; + +// Texture view +dictionary WebGPUTextureViewDescriptor { + // TODO Investigate what goes in there. +}; + +[Pref="dom.webgpu.enable"] +interface WebGPUTextureView { +}; + +// Texture +typedef u32 WebGPUTextureDimensionEnum; +[Pref="dom.webgpu.enable"] +interface WebGPUTextureDimension { + const u32 e1D = 0; + const u32 e2D = 1; + const u32 e3D = 2; + // TODO other dimensions (cube, arrays) +}; + +typedef u32 WebGPUTextureFormatEnum; +[Pref="dom.webgpu.enable"] +interface WebGPUTextureFormat { + const u32 R8_G8_B8_A8_UNORM = 0; + const u32 R8_G8_B8_A8_UINT = 1; + const u32 B8_G8_R8_A8_UNORM = 2; + const u32 D32_FLOAT_S8_UINT = 3; + // TODO other formats +}; + +typedef u32 WebGPUTextureUsageFlags; +[Pref="dom.webgpu.enable"] +interface WebGPUTextureUsage { + const u32 NONE = 0; + const u32 TRANSFER_SRC = 1; + const u32 TRANSFER_DST = 2; + const u32 SAMPLED = 4; + const u32 STORAGE = 8; + const u32 OUTPUT_ATTACHMENT = 16; + const u32 PRESENT = 32; +}; + +dictionary WebGPUTextureDescriptor { + u32 width; + u32 height; + u32 depth; + u32 arraySize; + WebGPUTextureDimensionEnum dimension; + WebGPUTextureFormatEnum format; + WebGPUTextureUsageFlags usage; +}; + +[Pref="dom.webgpu.enable"] +interface WebGPUTexture { + WebGPUTextureView createTextureView(optional WebGPUTextureViewDescriptor desc); +}; + +// Sampler +typedef u32 WebGPUFilterModeEnum; +[Pref="dom.webgpu.enable"] +interface WebGPUFilterMode { + const u32 NEAREST = 0; + const u32 LINEAR = 1; +}; + +dictionary WebGPUSamplerDescriptor { + WebGPUFilterModeEnum magFilter; + WebGPUFilterModeEnum minFilter; + WebGPUFilterModeEnum mipmapFilter; +}; + +[Pref="dom.webgpu.enable"] +interface WebGPUSampler { +}; + +// **************************************************************************** +// BINDING MODEL (bindgroup layout, bindgroup) +// **************************************************************************** + +// BindGroupLayout +typedef u32 WebGPUShaderStageFlags; +[Pref="dom.webgpu.enable"] +interface WebGPUShaderStageBit { + const u32 NONE = 0; + const u32 VERTEX = 1; + const u32 FRAGMENT = 2; + const u32 COMPUTE = 4; +}; + +typedef u32 WebGPUBindingTypeEnum; +[Pref="dom.webgpu.enable"] +interface WebGPUBindingType { + const u32 UNIFORM_BUFFER = 0; + const u32 SAMPLER = 1; + const u32 SAMPLED_TEXTURE = 2; + const u32 STORAGE_BUFFER = 3; + // TODO other binding types (storage images, ...) +}; + +dictionary WebGPUBindGroupBinding { + WebGPUShaderStageFlags visibility; + WebGPUBindingTypeEnum type; + u32 start; + u32 count; +}; + +dictionary WebGPUBindGroupLayoutDescriptor { + sequence bindingTypes; +}; + +[Pref="dom.webgpu.enable"] +interface WebGPUBindGroupLayout { +}; + +// PipelineLayout +dictionary WebGPUPipelineLayoutDescriptor { + sequence bindGroupLayouts; +}; + +[Pref="dom.webgpu.enable"] +interface WebGPUPipelineLayout { +}; + +// BindGroup +/* Moved to WebGPUExtras.webidl for now. +dictionary WebGPUBufferBinding { + WebGPUBuffer buffer; + u32 offset; + u32 size; +}; +*/ + +typedef (WebGPUSampler or WebGPUTextureView or WebGPUBufferBinding) WebGPUBindingResource; + +dictionary WebGPUBinding { + sequence resources; + u32 start; + u32 count; +}; + +dictionary WebGPUBindGroupDescriptor { + WebGPUBindGroupLayout layout; + sequence bindings; +}; + +[Pref="dom.webgpu.enable"] +interface WebGPUBindGroup { +}; + +// **************************************************************************** +// PIPELINE CREATION (blend state, DS state, ..., pipelines) +// **************************************************************************** + +// BlendState +typedef u32 WebGPUBlendFactorEnum; +[Pref="dom.webgpu.enable"] +interface WebGPUBlendFactor { + const u32 ZERO = 0; + const u32 ONE = 1; + const u32 SRC_COLOR = 2; + const u32 ONE_MINUS_SRC_COLOR = 3; + const u32 SRC_ALPHA = 4; + const u32 ONE_MINUS_SRC_ALPHA = 5; + const u32 DST_COLOR = 6; + const u32 ONE_MINUS_DST_COLOR = 7; + const u32 DST_ALPHA = 8; + const u32 ONE_MINUS_DST_ALPHA = 9; + const u32 SRC_ALPHA_SATURATED = 10; + const u32 BLEND_COLOR = 11; + const u32 ONE_MINUS_BLEND_COLOR = 12; +}; + +typedef u32 WebGPUBlendOperationEnum; +[Pref="dom.webgpu.enable"] +interface WebGPUBlendOperation { + const u32 ADD = 0; + const u32 SUBTRACT = 1; + const u32 REVERSE_SUBTRACT = 2; + const u32 MIN = 3; + const u32 MAX = 4; +}; + +typedef u32 WebGPUColorWriteFlags; +[Pref="dom.webgpu.enable"] +interface WebGPUColorWriteBits { + const u32 NONE = 0; + const u32 RED = 1; + const u32 GREEN = 2; + const u32 BLUE = 4; + const u32 ALPHA = 8; + const u32 ALL = 15; +}; + +dictionary WebGPUBlendDescriptor { + WebGPUBlendFactorEnum srcFactor; + WebGPUBlendFactorEnum dstFactor; + WebGPUBlendOperationEnum operation; +}; + +dictionary WebGPUBlendStateDescriptor { + boolean blendEnabled; + WebGPUBlendDescriptor alpha; + WebGPUBlendDescriptor color; + WebGPUColorWriteFlags writeMask; +}; + +[Pref="dom.webgpu.enable"] +interface WebGPUBlendState { +}; + +// DepthStencilState +typedef u32 WebGPUCompareFunctionEnum; +[Pref="dom.webgpu.enable"] +interface WebGPUCompareFunction { + const u32 NEVER = 0; + const u32 LESS = 1; + const u32 LESS_EQUAL = 2; + const u32 GREATER = 3; + const u32 GREATER_EQUAL = 4; + const u32 EQUAL = 5; + const u32 NOT_EQUAL = 6; + const u32 ALWAYS = 7; +}; + +typedef u32 WebGPUStencilOperationEnum; +[Pref="dom.webgpu.enable"] +interface WebGPUStencilOperation { + const u32 KEEP = 0; + const u32 ZERO = 1; + const u32 REPLACE = 2; + const u32 INVERT = 3; + const u32 INCREMENT_CLAMP = 4; + const u32 DECREMENT_CLAMP = 5; + const u32 INCREMENT_WRAP = 6; + const u32 DECREMENT_WRAP = 7; +}; + +dictionary WebGPUStencilStateFaceDescriptor { + WebGPUCompareFunctionEnum compare; + WebGPUStencilOperationEnum stencilFailOp; + WebGPUStencilOperationEnum depthFailOp; + WebGPUStencilOperationEnum passOp; +}; + +dictionary WebGPUDepthStencilStateDescriptor { + boolean depthWriteEnabled; + WebGPUCompareFunctionEnum depthCompare; + + WebGPUStencilStateFaceDescriptor front; + WebGPUStencilStateFaceDescriptor back; + + u32 stencilReadMask; + u32 stencilWriteMask; +}; + +[Pref="dom.webgpu.enable"] +interface WebGPUDepthStencilState { +}; + +// InputState +typedef u32 WebGPUIndexFormatEnum; +[Pref="dom.webgpu.enable"] +interface WebGPUIndexFormat { + const u32 UINT16 = 0; + const u32 UINT32 = 1; +}; + +typedef u32 WebGPUVertexFormatEnum; +[Pref="dom.webgpu.enable"] +interface WebGPUVertexFormat { + const u32 FLOAT_R32_G32_B32_A32 = 0; + const u32 FLOAT_R32_G32_B32 = 1; + const u32 FLOAT_R32_G32 = 2; + const u32 FLOAT_R32 = 3; + // TODO other vertex formats +}; + +typedef u32 WebGPUInputStepModeEnum; +[Pref="dom.webgpu.enable"] +interface WebGPUInputStepMode { + const u32 VERTEX = 0; + const u32 INSTANCE = 1; +}; + +dictionary WebGPUVertexAttributeDescriptor { + u32 shaderLocation; + u32 inputSlot; + u32 offset; + WebGPUVertexFormatEnum format; +}; + +dictionary WebGPUVertexInputDescriptor { + u32 inputSlot; + u32 stride; + WebGPUInputStepModeEnum stepMode; +}; + +dictionary WebGPUInputStateDescriptor { + WebGPUIndexFormatEnum indexFormat; + + sequence attributes; + sequence inputs; +}; + +[Pref="dom.webgpu.enable"] +interface WebGPUInputState { +}; + +// ShaderModule +dictionary WebGPUShaderModuleDescriptor { + required ArrayBuffer code; +}; + +[Pref="dom.webgpu.enable"] +interface WebGPUShaderModule { +}; + +// AttachmentState +dictionary WebGPUAttachmentStateDescriptor { + sequence formats; + // TODO other stuff like sample count etc. +}; + +[Pref="dom.webgpu.enable"] +interface WebGPUAttachmentState { +}; + +// Common stuff for ComputePipeline and RenderPipeline +typedef u32 WebGPUShaderStageEnum; +[Pref="dom.webgpu.enable"] +interface WebGPUShaderStage { + const u32 VERTEX = 0; + const u32 FRAGMENT = 1; + const u32 COMPUTE = 2; +}; + +dictionary WebGPUPipelineStageDescriptor { + required WebGPUShaderModule shaderModule; + required WebGPUShaderStageEnum stage; + required DOMString entryPoint; + // TODO other stuff like specialization constants? +}; + +dictionary WebGPUPipelineDescriptorBase { + required WebGPUPipelineLayout layout; + sequence stages; +}; + +// ComputePipeline +dictionary WebGPUComputePipelineDescriptor : WebGPUPipelineDescriptorBase { +}; + +[Pref="dom.webgpu.enable"] +interface WebGPUComputePipeline { +}; + +// WebGPURenderPipeline +typedef u32 WebGPUPrimitiveTopologyEnum; +[Pref="dom.webgpu.enable"] +interface WebGPUPrimitiveTopology { + const u32 POINT_LIST = 0; + const u32 LINE_LIST = 1; + const u32 LINE_STRIP = 2; + const u32 TRIANGLE_LIST = 3; + const u32 TRIANGLE_STRIP = 4; +}; + +dictionary WebGPURenderPipelineDescriptor : WebGPUPipelineDescriptorBase { + WebGPUPrimitiveTopologyEnum primitiveTopology; + sequence blendState; + WebGPUDepthStencilState depthStencilState; + WebGPUInputState inputState; + WebGPUAttachmentState attachmentState; + // TODO other properties +}; + +[Pref="dom.webgpu.enable"] +interface WebGPURenderPipeline { +}; +// **************************************************************************** +// COMMAND RECORDING (Command buffer and all relevant structures) +// **************************************************************************** + +typedef u32 WebGPULoadOpEnum; +[Pref="dom.webgpu.enable"] +interface WebGPULoadOp { + const u32 CLEAR = 0; + const u32 LOAD = 1; +}; + +typedef u32 WebGPUStoreOpEnum; +[Pref="dom.webgpu.enable"] +interface WebGPUStoreOp { + const u32 STORE = 0; +}; + +dictionary WebGPURenderPassAttachmentDescriptor { + WebGPUTextureView attachment; + WebGPULoadOpEnum loadOp; + WebGPUStoreOpEnum storeOp; +}; + +dictionary WebGPURenderPassDescriptor { + sequence colorAttachments; + WebGPURenderPassAttachmentDescriptor depthStencilAttachment; +}; + +[Pref="dom.webgpu.enable"] +interface WebGPUCommandBuffer { +}; + +dictionary WebGPUCommandEncoderDescriptor { +}; + +[Pref="dom.webgpu.enable"] +interface WebGPUCommandEncoder { + WebGPUCommandBuffer finishEncoding(); + + // Commands allowed outside of "passes" + void copyBufferToBuffer(WebGPUBuffer src, + u32 srcOffset, + WebGPUBuffer dst, + u32 dstOffset, + u32 size); + // TODO figure out all the arguments required for these + void copyBufferToTexture(); + void copyTextureToBuffer(); + void copyTextureToTexture(); + void blit(); + + void transitionBuffer(WebGPUBuffer b, WebGPUBufferUsageFlags f); + + // Allowed in both compute and render passes + void setPushConstants(WebGPUShaderStageFlags stage, + u32 offset, + u32 count, + ArrayBuffer data); + void setBindGroup(u32 index, WebGPUBindGroup bindGroup); + void setPipeline((WebGPUComputePipeline or WebGPURenderPipeline) pipeline); + + // Compute pass commands + void beginComputePass(); + void endComputePass(); + + void dispatch(u32 x, u32 y, u32 z); + + // Render pass commands + void beginRenderPass(optional WebGPURenderPassDescriptor descriptor); + void endRenderPass(); + + void setBlendColor(float r, float g, float b, float a); + void setIndexBuffer(WebGPUBuffer buffer, u32 offset); + void setVertexBuffers(u32 startSlot, sequence buffers, sequence offsets); + + void draw(u32 vertexCount, u32 instanceCount, u32 firstVertex, u32 firstInstance); + void drawIndexed(u32 indexCount, u32 instanceCount, u32 firstIndex, u32 firstInstance, u32 firstVertex); + + // TODO add missing commands +}; + +// **************************************************************************** +// OTHER (Fence, Queue SwapChain, Device) +// **************************************************************************** + +// Fence +[Pref="dom.webgpu.enable"] +interface WebGPUFence { + boolean wait(double milliseconds); + readonly attribute Promise promise; +}; + +// Queue +[Pref="dom.webgpu.enable"] +interface WebGPUQueue { + void submit(sequence buffers); + WebGPUFence insertFence(); +}; + +// SwapChain / RenderingContext +dictionary WebGPUSwapChainDescriptor { + WebGPUTextureUsageFlags usage; + WebGPUTextureFormatEnum format; + u32 width; + u32 height; +}; + +[Pref="dom.webgpu.enable"] +interface WebGPUSwapChain { + void configure(optional WebGPUSwapChainDescriptor descriptor); + WebGPUTexture getNextTexture(); + void present(); +}; + +//[Pref="dom.webgpu.enable"] +//interface WebGPURenderingContext : WebGPUSwapChain { +//}; + +// WebGPU "namespace" used for device creation +dictionary WebGPUExtensions { + boolean anisotropicFiltering; + boolean logicOp; // Previously a "Feature". +}; + +dictionary WebGPULimits { + u32 maxBindGroups; +}; + +// Device +[Pref="dom.webgpu.enable"] +interface WebGPUDevice { + readonly attribute WebGPUAdapter adapter; + WebGPUExtensions extensions(); + WebGPULimits limits(); + + WebGPUBuffer createBuffer(optional WebGPUBufferDescriptor descriptor); + WebGPUTexture createTexture(optional WebGPUTextureDescriptor descriptor); + WebGPUSampler createSampler(optional WebGPUSamplerDescriptor descriptor); + + WebGPUBindGroupLayout createBindGroupLayout(optional WebGPUBindGroupLayoutDescriptor descriptor); + WebGPUPipelineLayout createPipelineLayout(optional WebGPUPipelineLayoutDescriptor descriptor); + WebGPUBindGroup createBindGroup(optional WebGPUBindGroupDescriptor descriptor); + + WebGPUBlendState createBlendState(optional WebGPUBlendStateDescriptor descriptor); + WebGPUDepthStencilState createDepthStencilState(optional WebGPUDepthStencilStateDescriptor descriptor); + WebGPUInputState createInputState(optional WebGPUInputStateDescriptor descriptor); + WebGPUShaderModule createShaderModule(WebGPUShaderModuleDescriptor descriptor); + WebGPUAttachmentState createAttachmentState(optional WebGPUAttachmentStateDescriptor descriptor); + WebGPUComputePipeline createComputePipeline(WebGPUComputePipelineDescriptor descriptor); + WebGPURenderPipeline createRenderPipeline(WebGPURenderPipelineDescriptor descriptor); + + WebGPUCommandEncoder createCommandEncoder(optional WebGPUCommandEncoderDescriptor descriptor); + + WebGPUQueue getQueue(); + + attribute WebGPULogCallback onLog; + Promise getObjectStatus(WebGPUStatusable obj); +}; + +dictionary WebGPUDeviceDescriptor { + WebGPUExtensions extensions; + //WebGPULimits limits; Don't expose higher limits for now. + + // TODO are other things configurable like queues? +}; + +[Pref="dom.webgpu.enable"] +interface WebGPUAdapter { + readonly attribute DOMString name; + WebGPUExtensions extensions(); + //WebGPULimits limits(); Don't expose higher limits for now. + + WebGPUDevice createDevice(optional WebGPUDeviceDescriptor descriptor); +}; + +enum WebGPUPowerPreference { "default", "low-power", "high-performance" }; + +dictionary WebGPUAdapterDescriptor { + WebGPUPowerPreference powerPreference; +}; + +[Pref="dom.webgpu.enable"] +interface WebGPU { + WebGPUAdapter getAdapter(optional WebGPUAdapterDescriptor desc); +}; + +// Add a "webgpu" member to Window that contains the global instance of a "WebGPU" +[NoInterfaceObject] +interface WebGPUProvider { + [SameObject, Replaceable, Pref="dom.webgpu.enable"] readonly attribute WebGPU webgpu; +}; +//Window includes WebGPUProvider; diff --git a/dom/webidl/WebGPUExtras.webidl b/dom/webidl/WebGPUExtras.webidl new file mode 100644 --- /dev/null +++ b/dom/webidl/WebGPUExtras.webidl @@ -0,0 +1,14 @@ +/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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/. + * + * Some parts of WebGPU.webidl need to be pulled into a different file due to a codegen + * bug/missing support. + */ + +dictionary WebGPUBufferBinding { + WebGPUBuffer buffer; + u32 offset; + u32 size; +}; diff --git a/dom/webidl/Window.webidl b/dom/webidl/Window.webidl --- a/dom/webidl/Window.webidl +++ b/dom/webidl/Window.webidl @@ -561,5 +561,7 @@ * localization. */ [Throws, Func="IsChromeOrXBL"] readonly attribute IntlUtils intlUtils; }; + +Window implements WebGPUProvider; diff --git a/dom/webidl/WorkerNavigator.webidl b/dom/webidl/WorkerNavigator.webidl --- a/dom/webidl/WorkerNavigator.webidl +++ b/dom/webidl/WorkerNavigator.webidl @@ -12,10 +12,17 @@ WorkerNavigator implements NavigatorOnLine; WorkerNavigator implements NavigatorConcurrentHardware; WorkerNavigator implements NavigatorStorage; // http://wicg.github.io/netinfo/#extensions-to-the-navigator-interface -[Exposed=(Worker)] +[Exposed=Worker] partial interface WorkerNavigator { [Func="mozilla::dom::DOMPrefs::NetworkInformationEnabled", Throws] readonly attribute NetworkInformation connection; }; + +// https://wicg.github.io/media-capabilities/#idl-index +[Exposed=Worker] +partial interface WorkerNavigator { + [SameObject, Func="mozilla::dom::MediaCapabilities::Enabled"] + readonly attribute MediaCapabilities mediaCapabilities; +}; diff --git a/dom/webidl/moz.build b/dom/webidl/moz.build --- a/dom/webidl/moz.build +++ b/dom/webidl/moz.build @@ -326,10 +326,13 @@ BUG_COMPONENT = ("Core", "DOM: Device Interfaces") with Files("WebGL*"): BUG_COMPONENT = ("Core", "Canvas: WebGL") +with Files("WebGPU*"): + BUG_COMPONENT = ("Core", "Canvas: WebGL") + with Files("WebKitCSSMatrix.webidl"): BUG_COMPONENT = ("Core", "DOM: CSS Object Model") with Files("Webrtc*"): BUG_COMPONENT = ("Core", "WebRTC") @@ -631,10 +634,11 @@ 'LegacyQueryInterface.webidl', 'LinkStyle.webidl', 'ListBoxObject.webidl', 'LocalMediaStream.webidl', 'Location.webidl', + 'MediaCapabilities.webidl', 'MediaDeviceInfo.webidl', 'MediaDevices.webidl', 'MediaElementAudioSourceNode.webidl', 'MediaEncryptedEvent.webidl', 'MediaError.webidl', @@ -919,10 +923,12 @@ 'WaveShaperNode.webidl', 'WebAuthentication.webidl', 'WebComponents.webidl', 'WebGL2RenderingContext.webidl', 'WebGLRenderingContext.webidl', + 'WebGPU.webidl', + 'WebGPUExtras.webidl', 'WebKitCSSMatrix.webidl', 'WebSocket.webidl', 'WheelEvent.webidl', 'WidevineCDMManifest.webidl', 'WindowOrWorkerGlobalScope.webidl', diff --git a/dom/workers/WorkerNavigator.h b/dom/workers/WorkerNavigator.h --- a/dom/workers/WorkerNavigator.h +++ b/dom/workers/WorkerNavigator.h @@ -16,10 +16,11 @@ namespace mozilla { namespace dom { class Promise; class StorageManager; +class MediaCapabilities; namespace network { class Connection; } // namespace network @@ -105,10 +106,15 @@ uint64_t HardwareConcurrency() const; StorageManager* Storage(); network::Connection* GetConnection(ErrorResult& aRv); + + dom::MediaCapabilities* MediaCapabilities(); + +private: + RefPtr mMediaCapabilities; }; } // namespace dom } // namespace mozilla diff --git a/dom/workers/WorkerNavigator.cpp b/dom/workers/WorkerNavigator.cpp --- a/dom/workers/WorkerNavigator.cpp +++ b/dom/workers/WorkerNavigator.cpp @@ -4,10 +4,11 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "mozilla/dom/BindingUtils.h" #include "mozilla/dom/DOMPrefs.h" +#include "mozilla/dom/MediaCapabilities.h" #include "mozilla/dom/Promise.h" #include "mozilla/dom/PromiseWorkerProxy.h" #include "mozilla/dom/StorageManager.h" #include "mozilla/dom/WorkerNavigator.h" #include "mozilla/dom/WorkerNavigatorBinding.h" @@ -29,11 +30,11 @@ namespace dom { using namespace workerinternals; NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(WorkerNavigator, mStorageManager, - mConnection); + mConnection, mMediaCapabilities); NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(WorkerNavigator, AddRef) NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(WorkerNavigator, Release) WorkerNavigator::WorkerNavigator(const NavigatorProperties& aProperties, @@ -214,8 +215,22 @@ } return mConnection; } +dom::MediaCapabilities* +WorkerNavigator::MediaCapabilities() +{ + if (!mMediaCapabilities) { + WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate(); + MOZ_ASSERT(workerPrivate); + + nsIGlobalObject* global = workerPrivate->GlobalScope(); + MOZ_ASSERT(global); + + mMediaCapabilities = new dom::MediaCapabilities(global); + } + return mMediaCapabilities; +} } // namespace dom } // namespace mozilla diff --git a/image/imgLoader.cpp b/image/imgLoader.cpp --- a/image/imgLoader.cpp +++ b/image/imgLoader.cpp @@ -15,10 +15,11 @@ #include "mozilla/ClearOnShutdown.h" #include "mozilla/Move.h" #include "mozilla/Preferences.h" #include "mozilla/ChaosMode.h" #include "mozilla/LoadInfo.h" +#include "mozilla/Telemetry.h" #include "nsImageModule.h" #include "imgRequestProxy.h" #include "nsCOMPtr.h" @@ -1356,10 +1357,13 @@ sMemReporter = new imgMemoryReporter(); RegisterStrongMemoryReporter(sMemReporter); RegisterImagesContentUsedUncompressedDistinguishedAmount( imgMemoryReporter::ImagesContentUsedUncompressedDistinguishedAmount); + + Telemetry::ScalarSet(Telemetry::ScalarID::IMAGES_WEBP_PROBE_OBSERVED, false); + Telemetry::ScalarSet(Telemetry::ScalarID::IMAGES_WEBP_CONTENT_OBSERVED, false); } void imgLoader::ShutdownMemoryReporter() { UnregisterImagesContentUsedUncompressedDistinguishedAmount(); diff --git a/ipc/mscom/COMPtrHolder.h b/ipc/mscom/COMPtrHolder.h --- a/ipc/mscom/COMPtrHolder.h +++ b/ipc/mscom/COMPtrHolder.h @@ -160,11 +160,11 @@ XRE_IsParentProcess() && mozilla::GetEffectiveContentSandboxLevel() >= 3; #else const bool sIsStreamPreservationNeeded = false; #endif // defined(MOZ_CONTENT_SANDBOX) - paramType::EnvType env; + typename paramType::EnvType env; mozilla::mscom::ProxyStreamFlags flags = sIsStreamPreservationNeeded ? mozilla::mscom::ProxyStreamFlags::ePreservable : mozilla::mscom::ProxyStreamFlags::eDefault; diff --git a/ipc/mscom/Interceptor.cpp b/ipc/mscom/Interceptor.cpp --- a/ipc/mscom/Interceptor.cpp +++ b/ipc/mscom/Interceptor.cpp @@ -242,11 +242,11 @@ return E_INVALIDARG; } detail::LiveSetAutoLock lock(GetLiveSet()); - RefPtr existingWeak(std::move(GetLiveSet().Get(aTarget.get()))); + RefPtr existingWeak(GetLiveSet().Get(aTarget.get())); if (existingWeak) { RefPtr existingStrong; if (SUCCEEDED(existingWeak->ToStrongRef(getter_AddRefs(existingStrong)))) { // QI on existingStrong may touch other threads. Since we now hold a // strong ref on the interceptor, we may now release the lock. @@ -540,16 +540,29 @@ { MOZ_ASSERT(aOutInterceptor); MOZ_ASSERT(aTargetIid != IID_IMarshal); MOZ_ASSERT(!IsProxy(aTarget.get())); + HRESULT hr = E_UNEXPECTED; + + auto hasFailed = [&hr]() -> bool { + return FAILED(hr); + }; + + auto cleanup = [&aLiveSetLock]() -> void { + aLiveSetLock.Unlock(); + }; + + ExecuteWhen + onFail(hasFailed, cleanup); + if (aTargetIid == IID_IUnknown) { // We must lock mInterceptorMapMutex so that nothing can race with us once // we have been published to the live set. MutexAutoLock lock(mInterceptorMapMutex); - HRESULT hr = PublishTarget(aLiveSetLock, nullptr, aTargetIid, std::move(aTarget)); + hr = PublishTarget(aLiveSetLock, nullptr, aTargetIid, std::move(aTarget)); ENSURE_HR_SUCCEEDED(hr); hr = QueryInterface(aTargetIid, aOutInterceptor); ENSURE_HR_SUCCEEDED(hr); return hr; @@ -557,11 +570,11 @@ // Raise the refcount for stabilization purposes during aggregation WeakReferenceSupport::StabilizeRefCount stabilizer(*this); RefPtr unkInterceptor; - HRESULT hr = CreateInterceptor(aTargetIid, + hr = CreateInterceptor(aTargetIid, static_cast(this), getter_AddRefs(unkInterceptor)); ENSURE_HR_SUCCEEDED(hr); RefPtr interceptor; @@ -876,11 +889,11 @@ detail::LiveSetAutoLock lock(GetLiveSet()); // It is not an error if the interceptor doesn't exist, so we return // S_FALSE instead of an error in that case. - RefPtr existingWeak(std::move(GetLiveSet().Get(aTarget))); + RefPtr existingWeak(GetLiveSet().Get(aTarget)); if (!existingWeak) { return S_FALSE; } RefPtr existingStrong; diff --git a/ipc/mscom/Utils.h b/ipc/mscom/Utils.h --- a/ipc/mscom/Utils.h +++ b/ipc/mscom/Utils.h @@ -9,10 +9,11 @@ #if defined(MOZILLA_INTERNAL_API) #include "nsString.h" #endif // defined(MOZILLA_INTERNAL_API) +#include "mozilla/Attributes.h" #include struct IStream; struct IUnknown; @@ -61,10 +62,44 @@ unsigned long aVtableIndexHint); #endif // defined(MOZILLA_INTERNAL_API) #endif // defined(ACCESSIBILITY) +/** + * Execute cleanup code when going out of scope if a condition is met. + * This is useful when, for example, particular cleanup needs to be performed + * whenever a call returns a failure HRESULT. + * Both the condition and cleanup code are provided as functions (usually + * lambdas). + */ +template +class MOZ_RAII ExecuteWhen final +{ +public: + ExecuteWhen(CondFnT& aCondFn, ExeFnT& aExeFn) + : mCondFn(aCondFn) + , mExeFn(aExeFn) + { + } + + ~ExecuteWhen() + { + if (mCondFn()) { + mExeFn(); + } + } + + ExecuteWhen(const ExecuteWhen&) = delete; + ExecuteWhen(ExecuteWhen&&) = delete; + ExecuteWhen& operator=(const ExecuteWhen&) = delete; + ExecuteWhen& operator=(ExecuteWhen&&) = delete; + +private: + CondFnT& mCondFn; + ExeFnT& mExeFn; +}; + } // namespace mscom } // namespace mozilla #endif // mozilla_mscom_Utils_h diff --git a/js/ductwork/debugger/JSDebugger.cpp b/js/ductwork/debugger/JSDebugger.cpp --- a/js/ductwork/debugger/JSDebugger.cpp +++ b/js/ductwork/debugger/JSDebugger.cpp @@ -1,8 +1,8 @@ /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2; -*- */ /* 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 + * License, v. 2.0. If a copy of the MPL was not destributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "JSDebugger.h" #include "nsIXPConnect.h" #include "nsThreadUtils.h" @@ -53,11 +53,11 @@ JSAutoRealm ar(cx, obj); if (JS_GetGlobalForObject(cx, obj) != obj) { return NS_ERROR_INVALID_ARG; } - if (!JS_DefineDebuggerObject(cx, obj)) { + else if (!JS_DefineDebuggerObject(cx, obj)) { // readability-else-after-return return NS_ERROR_FAILURE; } return NS_OK; } diff --git a/js/src/builtin/Array.h b/js/src/builtin/Array.h --- a/js/src/builtin/Array.h +++ b/js/src/builtin/Array.h @@ -190,11 +190,11 @@ // Like Array constructor, but doesn't perform GetPrototypeFromConstructor. extern bool array_construct(JSContext* cx, unsigned argc, Value* vp); extern bool -IsWrappedArrayConstructor(JSContext* cx, const Value& v, bool* result); +IsCrossRealmArrayConstructor(JSContext* cx, const Value& v, bool* result); class MOZ_NON_TEMPORARY_CLASS ArraySpeciesLookup final { /* * An ArraySpeciesLookup holds the following: diff --git a/js/src/builtin/Array.cpp b/js/src/builtin/Array.cpp --- a/js/src/builtin/Array.cpp +++ b/js/src/builtin/Array.cpp @@ -1021,42 +1021,39 @@ } static bool IsArrayConstructor(const JSObject* obj) { - // This must only return true if v is *the* Array constructor for the - // current compartment; we rely on the fact that any other Array - // constructor would be represented as a wrapper. - return obj->is() && - obj->as().isNative() && - obj->as().native() == ArrayConstructor; + // Note: this also returns true for cross-realm Array constructors in the + // same compartment. + return IsNativeFunction(obj, ArrayConstructor); } static bool IsArrayConstructor(const Value& v) { return v.isObject() && IsArrayConstructor(&v.toObject()); } bool -js::IsWrappedArrayConstructor(JSContext* cx, const Value& v, bool* result) +js::IsCrossRealmArrayConstructor(JSContext* cx, const Value& v, bool* result) { if (!v.isObject()) { *result = false; return true; } - if (v.toObject().is()) { - JSObject* obj = CheckedUnwrap(&v.toObject()); + + JSObject* obj = &v.toObject(); + if (obj->is()) { + obj = CheckedUnwrap(obj); if (!obj) { ReportAccessDenied(cx); return false; } - - *result = IsArrayConstructor(obj); - } else { - *result = false; } + + *result = IsArrayConstructor(obj) && obj->as().realm() != cx->realm(); return true; } static MOZ_ALWAYS_INLINE bool IsArraySpecies(JSContext* cx, HandleObject origArray) @@ -1086,10 +1083,15 @@ return false; if (!IsArrayConstructor(ctor)) return ctor.isUndefined(); + // 9.4.2.3 Step 6.c. Use the current realm's constructor if |ctor| is a + // cross-realm Array constructor. + if (cx->realm() != ctor.toObject().as().realm()) + return true; + jsid speciesId = SYMBOL_TO_JSID(cx->wellKnownSymbols().species); JSFunction* getter; if (!GetGetterPure(cx, &ctor.toObject(), speciesId, &getter)) return false; diff --git a/js/src/builtin/Array.js b/js/src/builtin/Array.js --- a/js/src/builtin/Array.js +++ b/js/src/builtin/Array.js @@ -980,11 +980,11 @@ // Step 5.a. var C = originalArray.constructor; // Step 5.b. - if (IsConstructor(C) && IsWrappedArrayConstructor(C)) + if (IsConstructor(C) && IsCrossRealmArrayConstructor(C)) return std_Array(length); // Step 5.c. if (IsObject(C)) { // Step 5.c.i. diff --git a/js/src/frontend/TokenStream.h b/js/src/frontend/TokenStream.h --- a/js/src/frontend/TokenStream.h +++ b/js/src/frontend/TokenStream.h @@ -1232,19 +1232,20 @@ inline bool TokenStreamCharsBase::matchCodeUnit(int32_t expect) { MOZ_ASSERT(expect != EOF, "shouldn't be matching EOFs"); MOZ_ASSERT(!SourceUnits::isRawEOLChar(expect)); - return MOZ_LIKELY(!sourceUnits.atEnd()) && sourceUnits.matchCodeUnit(toCharT(expect)); + return MOZ_LIKELY(!this->sourceUnits.atEnd()) && + this->sourceUnits.matchCodeUnit(toCharT(expect)); } template<> MOZ_MUST_USE inline bool TokenStreamCharsBase::fillCharBufferWithTemplateStringContents(const char16_t* cur, const char16_t* end) { - MOZ_ASSERT(charBuffer.length() == 0); + MOZ_ASSERT(this->charBuffer.length() == 0); while (cur < end) { // U+2028 LINE SEPARATOR and U+2029 PARAGRAPH SEPARATOR are // interpreted literally inside template literal contents; only // literal CRLF sequences are normalized to '\n'. See @@ -1254,11 +1255,11 @@ ch = '\n'; if (cur < end && *cur == '\n') cur++; } - if (!charBuffer.append(ch)) + if (!this->charBuffer.append(ch)) return false; } return true; } @@ -1316,12 +1317,10 @@ uint32_t matchExtendedUnicodeEscape(uint32_t* codePoint); protected: using typename CharsBase::SourceUnits; - using CharsBase::sourceUnits; - protected: using CharsBase::CharsBase; TokenStreamAnyChars& anyCharsAccess() { return AnyCharsAccess::anyChars(this); @@ -1388,12 +1387,12 @@ * unit is guaranteed to be ungotten (by ungetCodeUnit()) if it's an EOL, * and (b) the line-related state (lineno, linebase) is not used before * it's ungotten. */ int32_t getCodeUnit() { - if (MOZ_LIKELY(!sourceUnits.atEnd())) - return sourceUnits.getCodeUnit(); + if (MOZ_LIKELY(!this->sourceUnits.atEnd())) + return this->sourceUnits.getCodeUnit(); anyCharsAccess().flags.isEOF = true; return EOF; } @@ -1408,11 +1407,11 @@ * comment, without consuming the EOL/EOF. */ void consumeRestOfSingleLineComment(); MOZ_MUST_USE MOZ_ALWAYS_INLINE bool updateLineInfoForEOL() { - return anyCharsAccess().internalUpdateLineInfoForEOL(sourceUnits.offset()); + return anyCharsAccess().internalUpdateLineInfoForEOL(this->sourceUnits.offset()); } uint32_t matchUnicodeEscapeIdStart(uint32_t* codePoint); bool matchUnicodeEscapeIdent(uint32_t* codePoint); }; @@ -1434,11 +1433,11 @@ protected: using GeneralCharsBase::anyCharsAccess; using GeneralCharsBase::getCodeUnit; using TokenStreamCharsShared::isAsciiCodePoint; - using CharsBase::sourceUnits; + // Deliberately don't |using| |sourceUnits| because of bug 1472569. :-( using GeneralCharsBase::ungetCodeUnit; using GeneralCharsBase::updateLineInfoForEOL; using typename GeneralCharsBase::SourceUnits; @@ -1462,16 +1461,18 @@ * This may change the current |sourceUnits| offset. */ MOZ_MUST_USE bool getFullAsciiCodePoint(int32_t lead, int32_t* codePoint) { MOZ_ASSERT(isAsciiCodePoint(lead), "non-ASCII code units must be handled separately"); - MOZ_ASSERT(lead == sourceUnits.previousCodeUnit(), + // NOTE: |this->|-qualify to avoid a gcc bug: see bug 1472569. + MOZ_ASSERT(lead == this->sourceUnits.previousCodeUnit(), "getFullAsciiCodePoint called incorrectly"); if (MOZ_UNLIKELY(lead == '\r')) { - if (MOZ_LIKELY(!sourceUnits.atEnd())) - sourceUnits.matchCodeUnit('\n'); + // NOTE: |this->|-qualify to avoid a gcc bug: see bug 1472569. + if (MOZ_LIKELY(!this->sourceUnits.atEnd())) + this->sourceUnits.matchCodeUnit('\n'); } else if (MOZ_LIKELY(lead != '\n')) { *codePoint = lead; return true; } @@ -1609,11 +1610,11 @@ private: using TokenStreamCharsShared::appendCodePointToCharBuffer; using CharsBase::atomizeSourceChars; using GeneralCharsBase::badToken; - using TokenStreamCharsShared::charBuffer; + // Deliberately don't |using| |charBuffer| because of bug 1472569. :-( using CharsBase::consumeKnownCodeUnit; using GeneralCharsBase::consumeRestOfSingleLineComment; using TokenStreamCharsShared::copyCharBufferTo; using TokenStreamCharsShared::drainCharBufferIntoAtom; using CharsBase::fillCharBufferWithTemplateStringContents; @@ -1629,11 +1630,11 @@ using GeneralCharsBase::newNameToken; using GeneralCharsBase::newNumberToken; using GeneralCharsBase::newRegExpToken; using GeneralCharsBase::newSimpleToken; using CharsBase::peekCodeUnit; - using CharsBase::sourceUnits; + // Deliberately don't |using| |sourceUnits| because of bug 1472569. :-( using SpecializedCharsBase::ungetCodePointIgnoreEOL; using GeneralCharsBase::ungetCodeUnit; using SpecializedCharsBase::ungetNonAsciiNormalizedCodePoint; using GeneralCharsBase::updateLineInfoForEOL; @@ -1725,18 +1726,18 @@ JSAtom* getRawTemplateStringAtom() { TokenStreamAnyChars& anyChars = anyCharsAccess(); MOZ_ASSERT(anyChars.currentToken().type == TokenKind::TemplateHead || anyChars.currentToken().type == TokenKind::NoSubsTemplate); - const CharT* cur = sourceUnits.codeUnitPtrAt(anyChars.currentToken().pos.begin + 1); + const CharT* cur = this->sourceUnits.codeUnitPtrAt(anyChars.currentToken().pos.begin + 1); const CharT* end; if (anyChars.currentToken().type == TokenKind::TemplateHead) { // Of the form |`...${| or |}...${| - end = sourceUnits.codeUnitPtrAt(anyChars.currentToken().pos.end - 2); + end = this->sourceUnits.codeUnitPtrAt(anyChars.currentToken().pos.end - 2); } else { // NO_SUBS_TEMPLATE is of the form |`...`| or |}...`| - end = sourceUnits.codeUnitPtrAt(anyChars.currentToken().pos.end - 1); + end = this->sourceUnits.codeUnitPtrAt(anyChars.currentToken().pos.end - 1); } if (!fillCharBufferWithTemplateStringContents(cur, end)) return nullptr; @@ -1964,15 +1965,15 @@ void seek(const Position& pos); MOZ_MUST_USE bool seek(const Position& pos, const TokenStreamAnyChars& other); const CharT* codeUnitPtrAt(size_t offset) const { - return sourceUnits.codeUnitPtrAt(offset); + return this->sourceUnits.codeUnitPtrAt(offset); } const CharT* rawLimit() const { - return sourceUnits.limit(); + return this->sourceUnits.limit(); } MOZ_MUST_USE bool identifierName(TokenStart start, const CharT* identStart, IdentifierEscapes escaping, Modifier modifier, TokenKind* out); diff --git a/js/src/frontend/TokenStream.cpp b/js/src/frontend/TokenStream.cpp --- a/js/src/frontend/TokenStream.cpp +++ b/js/src/frontend/TokenStream.cpp @@ -492,27 +492,27 @@ bool TokenStreamChars::getCodePoint(int32_t* cp) { TokenStreamAnyChars& anyChars = anyCharsAccess(); - if (MOZ_UNLIKELY(sourceUnits.atEnd())) { + if (MOZ_UNLIKELY(this->sourceUnits.atEnd())) { anyChars.flags.isEOF = true; *cp = EOF; return true; } - int32_t c = sourceUnits.getCodeUnit(); + int32_t c = this->sourceUnits.getCodeUnit(); do { // Normalize the char16_t if it was a newline. if (MOZ_UNLIKELY(c == '\n')) break; if (MOZ_UNLIKELY(c == '\r')) { // If it's a \r\n sequence: treat as a single EOL, skip over the \n. - if (MOZ_LIKELY(!sourceUnits.atEnd())) - sourceUnits.matchCodeUnit('\n'); + if (MOZ_LIKELY(!this->sourceUnits.atEnd())) + this->sourceUnits.matchCodeUnit('\n'); break; } if (MOZ_UNLIKELY(c == unicode::LINE_SEPARATOR || c == unicode::PARA_SEPARATOR)) @@ -534,11 +534,11 @@ TokenStreamChars::getNonAsciiCodePoint(int32_t lead, int32_t* codePoint) { MOZ_ASSERT(lead != EOF); MOZ_ASSERT(!isAsciiCodePoint(lead), "ASCII code unit/point must be handled separately"); - MOZ_ASSERT(lead == sourceUnits.previousCodeUnit(), + MOZ_ASSERT(lead == this->sourceUnits.previousCodeUnit(), "getNonAsciiCodePoint called incorrectly"); // The code point is usually |lead|: overwrite later if needed. *codePoint = lead; @@ -568,28 +568,28 @@ return true; } // Also handle a lead surrogate not paired with a trailing surrogate. - if (MOZ_UNLIKELY(sourceUnits.atEnd() || - !unicode::IsTrailSurrogate(sourceUnits.peekCodeUnit()))) + if (MOZ_UNLIKELY(this->sourceUnits.atEnd() || + !unicode::IsTrailSurrogate(this->sourceUnits.peekCodeUnit()))) { MOZ_ASSERT(!SourceUnits::isRawEOLChar(*codePoint)); return true; } // Otherwise we have a multi-unit code point. - *codePoint = unicode::UTF16Decode(lead, sourceUnits.getCodeUnit()); + *codePoint = unicode::UTF16Decode(lead, this->sourceUnits.getCodeUnit()); MOZ_ASSERT(!SourceUnits::isRawEOLChar(*codePoint)); return true; } template void TokenStreamChars::ungetCodePointIgnoreEOL(uint32_t codePoint) { - MOZ_ASSERT(!sourceUnits.atStart()); + MOZ_ASSERT(!this->sourceUnits.atStart()); unsigned numUnits = 0; char16_t units[2]; unicode::UTF16Encode(codePoint, units, &numUnits); @@ -601,17 +601,17 @@ template void TokenStreamChars::ungetLineTerminator() { - sourceUnits.ungetCodeUnit(); + this->sourceUnits.ungetCodeUnit(); - char16_t last = sourceUnits.peekCodeUnit(); + char16_t last = this->sourceUnits.peekCodeUnit(); MOZ_ASSERT(SourceUnits::isRawEOLChar(last)); if (last == '\n') - sourceUnits.ungetOptionalCRBeforeLF(); + this->sourceUnits.ungetOptionalCRBeforeLF(); anyCharsAccess().undoInternalUpdateLineInfoForEOL(); } template @@ -639,20 +639,20 @@ template bool TokenStreamSpecific::advance(size_t position) { - const CharT* end = sourceUnits.codeUnitPtrAt(position); - while (sourceUnits.addressOfNextCodeUnit() < end) { + const CharT* end = this->sourceUnits.codeUnitPtrAt(position); + while (this->sourceUnits.addressOfNextCodeUnit() < end) { int32_t c; if (!getCodePoint(&c)) return false; } TokenStreamAnyChars& anyChars = anyCharsAccess(); Token* cur = const_cast(&anyChars.currentToken()); - cur->pos.begin = sourceUnits.offset(); + cur->pos.begin = this->sourceUnits.offset(); MOZ_MAKE_MEM_UNDEFINED(&cur->type, sizeof(cur->type)); anyChars.lookahead = 0; return true; } @@ -660,11 +660,11 @@ void TokenStreamSpecific::seek(const Position& pos) { TokenStreamAnyChars& anyChars = anyCharsAccess(); - sourceUnits.setAddressOfNextCodeUnit(pos.buf, /* allowPoisoned = */ true); + this->sourceUnits.setAddressOfNextCodeUnit(pos.buf, /* allowPoisoned = */ true); anyChars.flags = pos.flags; anyChars.lineno = pos.lineno; anyChars.linebase = pos.linebase; anyChars.prevLinebase = pos.prevLinebase; anyChars.lookahead = pos.lookahead; @@ -830,16 +830,16 @@ offset - windowRadius : anyChars.linebase; // The window must start within the portion of the current line that we // actually have in our buffer. - if (windowStart < sourceUnits.startOffset()) - windowStart = sourceUnits.startOffset(); + if (windowStart < this->sourceUnits.startOffset()) + windowStart = this->sourceUnits.startOffset(); // The window must end within the current line, no later than // windowRadius after offset. - size_t windowEnd = sourceUnits.findEOLMax(offset, windowRadius); + size_t windowEnd = this->sourceUnits.findEOLMax(offset, windowRadius); size_t windowLength = windowEnd - windowStart; MOZ_ASSERT(windowLength <= windowRadius * 2); // Create the windowed string, not including the potential line // terminator. @@ -954,11 +954,11 @@ { va_list args; va_start(args, errorNumber); ErrorMetadata metadata; - if (computeErrorMetadata(&metadata, sourceUnits.offset())) { + if (computeErrorMetadata(&metadata, this->sourceUnits.offset())) { TokenStreamAnyChars& anyChars = anyCharsAccess(); ReportCompileError(anyChars.cx, std::move(metadata), nullptr, JSREPORT_ERROR, errorNumber, args); } @@ -996,42 +996,42 @@ // involed. Otherwise, return 0 and don't advance along the buffer. template uint32_t GeneralTokenStreamChars::matchUnicodeEscape(uint32_t* codePoint) { - MOZ_ASSERT(sourceUnits.previousCodeUnit() == '\\'); + MOZ_ASSERT(this->sourceUnits.previousCodeUnit() == '\\'); int32_t unit = getCodeUnit(); if (unit != 'u') { // NOTE: |unit| may be EOF here. ungetCodeUnit(unit); - MOZ_ASSERT(sourceUnits.previousCodeUnit() == '\\'); + MOZ_ASSERT(this->sourceUnits.previousCodeUnit() == '\\'); return 0; } char16_t v; unit = getCodeUnit(); - if (JS7_ISHEX(unit) && sourceUnits.matchHexDigits(3, &v)) { + if (JS7_ISHEX(unit) && this->sourceUnits.matchHexDigits(3, &v)) { *codePoint = (JS7_UNHEX(unit) << 12) | v; return 5; } if (unit == '{') return matchExtendedUnicodeEscape(codePoint); // NOTE: |unit| may be EOF here, so this ungets either one or two units. ungetCodeUnit(unit); ungetCodeUnit('u'); - MOZ_ASSERT(sourceUnits.previousCodeUnit() == '\\'); + MOZ_ASSERT(this->sourceUnits.previousCodeUnit() == '\\'); return 0; } template uint32_t GeneralTokenStreamChars::matchExtendedUnicodeEscape(uint32_t* codePoint) { - MOZ_ASSERT(sourceUnits.previousCodeUnit() == '{'); + MOZ_ASSERT(this->sourceUnits.previousCodeUnit() == '{'); int32_t unit = getCodeUnit(); // Skip leading zeroes. uint32_t leadingZeroes = 0; @@ -1057,12 +1057,12 @@ if (unit == '}' && (leadingZeroes > 0 || i > 0) && code <= unicode::NonBMPMax) { *codePoint = code; return gotten; } - sourceUnits.unskipCodeUnits(gotten); - MOZ_ASSERT(sourceUnits.previousCodeUnit() == '\\'); + this->sourceUnits.unskipCodeUnits(gotten); + MOZ_ASSERT(this->sourceUnits.previousCodeUnit() == '\\'); return 0; } template uint32_t @@ -1071,14 +1071,14 @@ uint32_t length = matchUnicodeEscape(codePoint); if (MOZ_LIKELY(length > 0)) { if (MOZ_LIKELY(unicode::IsIdentifierStart(*codePoint))) return length; - sourceUnits.unskipCodeUnits(length); + this->sourceUnits.unskipCodeUnits(length); } - MOZ_ASSERT(sourceUnits.previousCodeUnit() == '\\'); + MOZ_ASSERT(this->sourceUnits.previousCodeUnit() == '\\'); return 0; } template bool @@ -1087,14 +1087,14 @@ uint32_t length = matchUnicodeEscape(codePoint); if (MOZ_LIKELY(length > 0)) { if (MOZ_LIKELY(unicode::IsIdentifierPart(*codePoint))) return true; - sourceUnits.unskipCodeUnits(length); + this->sourceUnits.unskipCodeUnits(length); } - MOZ_ASSERT(sourceUnits.previousCodeUnit() == '\\'); + MOZ_ASSERT(this->sourceUnits.previousCodeUnit() == '\\'); return false; } template bool @@ -1142,19 +1142,19 @@ UniquePtr* destination) { // Stop if we don't find |directive|. (Note that |directive| must be // ASCII, so there are no tricky encoding issues to consider in matching // UTF-8/16-agnostically.) - if (!sourceUnits.matchCodeUnits(directive, directiveLength)) + if (!this->sourceUnits.matchCodeUnits(directive, directiveLength)) return true; if (shouldWarnDeprecated) { if (!warning(JSMSG_DEPRECATED_PRAGMA, errorMsgPragma)) return false; } - charBuffer.clear(); + this->charBuffer.clear(); do { int32_t unit = peekCodeUnit(); if (unit == EOF) break; @@ -1171,11 +1171,11 @@ if (isMultiline && unit == '*' && peekCodeUnit() == '/') { ungetCodeUnit('*'); break; } - if (!charBuffer.append(unit)) + if (!this->charBuffer.append(unit)) return false; continue; } @@ -1190,11 +1190,11 @@ if (!appendCodePointToCharBuffer(codePoint)) return false; } while (true); - if (charBuffer.empty()) { + if (this->charBuffer.empty()) { // The directive's URL was missing, but comments can contain anything, // so it isn't an error. return true; } @@ -1271,11 +1271,11 @@ // Poisoning sourceUnits on error establishes an invariant: once an // erroneous token has been seen, sourceUnits will not be consulted again. // This is true because the parser will deal with the illegal token by // aborting parsing immediately. - sourceUnits.poisonInDebug(); + this->sourceUnits.poisonInDebug(); return false; }; MOZ_MUST_USE bool @@ -1299,28 +1299,28 @@ template bool TokenStreamSpecific::putIdentInCharBuffer(const CharT* identStart) { - const CharT* const originalAddress = sourceUnits.addressOfNextCodeUnit(); - sourceUnits.setAddressOfNextCodeUnit(identStart); + const CharT* const originalAddress = this->sourceUnits.addressOfNextCodeUnit(); + this->sourceUnits.setAddressOfNextCodeUnit(identStart); auto restoreNextRawCharAddress = MakeScopeExit([this, originalAddress]() { this->sourceUnits.setAddressOfNextCodeUnit(originalAddress); }); - charBuffer.clear(); + this->charBuffer.clear(); do { int32_t unit = getCodeUnit(); if (unit == EOF) break; uint32_t codePoint; if (MOZ_LIKELY(isAsciiCodePoint(unit))) { if (unicode::IsIdentifierPart(char16_t(unit))) { - if (!charBuffer.append(unit)) + if (!this->charBuffer.append(unit)) return false; continue; } @@ -1405,11 +1405,11 @@ atom = drainCharBufferIntoAtom(anyCharsAccess().cx); } else { // Escape-free identifiers can be created directly from sourceUnits. const CharT* chars = identStart; - size_t length = sourceUnits.addressOfNextCodeUnit() - identStart; + size_t length = this->sourceUnits.addressOfNextCodeUnit() - identStart; // Represent reserved words lacking escapes as reserved word tokens. if (const ReservedWordInfo* rw = FindReservedWord(chars, length)) { noteBadToken.release(); newSimpleToken(rw->tokentype, start, modifier, out); @@ -1544,12 +1544,12 @@ // NOTE: |unit| may be EOF here. ungetCodeUnit(unit); // Most numbers are pure decimal integers without fractional component // or exponential notation. Handle that with optimized code. - if (!GetDecimalInteger(anyCharsAccess().cx, numStart, sourceUnits.addressOfNextCodeUnit(), - &dval)) + if (!GetDecimalInteger(anyCharsAccess().cx, numStart, + this->sourceUnits.addressOfNextCodeUnit(), &dval)) { return false; } } else { // Consume any decimal dot and fractional component. @@ -1580,12 +1580,12 @@ } ungetCodeUnit(unit); const CharT* dummy; - if (!js_strtod(anyCharsAccess().cx, numStart, sourceUnits.addressOfNextCodeUnit(), &dummy, - &dval)) + if (!js_strtod(anyCharsAccess().cx, numStart, this->sourceUnits.addressOfNextCodeUnit(), + &dummy, &dval)) { return false; } } @@ -1619,12 +1619,12 @@ template MOZ_MUST_USE bool TokenStreamSpecific::regexpLiteral(TokenStart start, TokenKind* out) { - MOZ_ASSERT(sourceUnits.previousCodeUnit() == '/'); - charBuffer.clear(); + MOZ_ASSERT(this->sourceUnits.previousCodeUnit() == '/'); + this->charBuffer.clear(); auto ProcessNonAsciiCodePoint = [this](int32_t lead) { MOZ_ASSERT(lead != EOF); int32_t codePoint; @@ -1653,11 +1653,11 @@ return badToken(); } if (MOZ_LIKELY(isAsciiCodePoint(unit))) { if (unit == '\\') { - if (!charBuffer.append(unit)) + if (!this->charBuffer.append(unit)) return badToken(); unit = getCodeUnit(); if (unit == EOF) { ReportUnterminatedRegExp(unit); @@ -1684,11 +1684,11 @@ if (unit == '\r' || unit == '\n') { ReportUnterminatedRegExp(unit); return badToken(); } - if (!charBuffer.append(unit)) + if (!this->charBuffer.append(unit)) return badToken(); } else { if (!ProcessNonAsciiCodePoint(unit)) return badToken(); } @@ -1748,13 +1748,13 @@ // This loop runs more than once only when whitespace or comments are // encountered. do { int32_t unit = getCodeUnit(); if (MOZ_UNLIKELY(unit == EOF)) { - MOZ_ASSERT(sourceUnits.atEnd()); + MOZ_ASSERT(this->sourceUnits.atEnd()); anyCharsAccess().flags.isEOF = true; - TokenStart start(sourceUnits, 0); + TokenStart start(this->sourceUnits, 0); newSimpleToken(TokenKind::Eof, start, modifier, ttp); return true; } if (MOZ_UNLIKELY(!isAsciiCodePoint(unit))) { @@ -1762,12 +1762,12 @@ // It would be nice to compute these *after* discarding whitespace, // but IN A WORLD where |unicode::IsSpaceOrBOM2| requires consuming // a variable number of code points, it's easier to assume it's an // identifier and maybe do a little wasted work, than to unget and // compute and reget if whitespace. - TokenStart start(sourceUnits, -1); - const CharT* identStart = sourceUnits.addressOfNextCodeUnit() - 1; + TokenStart start(this->sourceUnits, -1); + const CharT* identStart = this->sourceUnits.addressOfNextCodeUnit() - 1; int32_t codePoint; if (!getNonAsciiCodePoint(unit, &codePoint)) return badToken(); @@ -1817,11 +1817,11 @@ FirstCharKind c1kind = FirstCharKind(firstCharKinds[unit]); // Look for an unambiguous single-char token. // if (c1kind <= OneChar_Max) { - TokenStart start(sourceUnits, -1); + TokenStart start(this->sourceUnits, -1); newSimpleToken(TokenKind(c1kind), start, modifier, ttp); return true; } // Skip over non-EOL whitespace chars. @@ -1830,20 +1830,20 @@ continue; // Look for an identifier. // if (c1kind == Ident) { - TokenStart start(sourceUnits, -1); - return identifierName(start, sourceUnits.addressOfNextCodeUnit() - 1, + TokenStart start(this->sourceUnits, -1); + return identifierName(start, this->sourceUnits.addressOfNextCodeUnit() - 1, IdentifierEscapes::None, modifier, ttp); } // Look for a decimal number. // if (c1kind == Dec) { - TokenStart start(sourceUnits, -1); - const CharT* numStart = sourceUnits.addressOfNextCodeUnit() - 1; + TokenStart start(this->sourceUnits, -1); + const CharT* numStart = this->sourceUnits.addressOfNextCodeUnit() - 1; return decimalNumber(unit, start, numStart, modifier, ttp); } // Look for a string or a template string. // @@ -1852,12 +1852,12 @@ // Skip over EOL chars, updating line state along the way. // if (c1kind == EOL) { // If it's a \r\n sequence, consume it as a single EOL. - if (unit == '\r' && !sourceUnits.atEnd()) - sourceUnits.matchCodeUnit('\n'); + if (unit == '\r' && !this->sourceUnits.atEnd()) + this->sourceUnits.matchCodeUnit('\n'); if (!updateLineInfoForEOL()) return badToken(); anyCharsAccess().updateFlagsForEOL(); @@ -1867,11 +1867,11 @@ // From a '0', look for a hexadecimal, binary, octal, or "noctal" (a // number starting with '0' that contains '8' or '9' and is treated as // decimal) number. // if (c1kind == ZeroDigit) { - TokenStart start(sourceUnits, -1); + TokenStart start(this->sourceUnits, -1); int radix; const CharT* numStart; unit = getCodeUnit(); if (unit == 'x' || unit == 'X') { @@ -1883,11 +1883,11 @@ error(JSMSG_MISSING_HEXDIGITS); return badToken(); } // one past the '0x' - numStart = sourceUnits.addressOfNextCodeUnit() - 1; + numStart = this->sourceUnits.addressOfNextCodeUnit() - 1; while (JS7_ISHEX(unit)) unit = getCodeUnit(); } else if (unit == 'b' || unit == 'B') { radix = 2; @@ -1898,11 +1898,11 @@ error(JSMSG_MISSING_BINARY_DIGITS); return badToken(); } // one past the '0b' - numStart = sourceUnits.addressOfNextCodeUnit() - 1; + numStart = this->sourceUnits.addressOfNextCodeUnit() - 1; while (unit == '0' || unit == '1') unit = getCodeUnit(); } else if (unit == 'o' || unit == 'O') { radix = 8; @@ -1913,18 +1913,18 @@ error(JSMSG_MISSING_OCTAL_DIGITS); return badToken(); } // one past the '0o' - numStart = sourceUnits.addressOfNextCodeUnit() - 1; + numStart = this->sourceUnits.addressOfNextCodeUnit() - 1; while (JS7_ISOCT(unit)) unit = getCodeUnit(); } else if (IsAsciiDigit(unit)) { radix = 8; // one past the '0' - numStart = sourceUnits.addressOfNextCodeUnit() - 1; + numStart = this->sourceUnits.addressOfNextCodeUnit() - 1; do { // Octal integer literals are not permitted in strict mode // code. if (!reportStrictModeError(JSMSG_DEPRECATED_OCTAL)) @@ -1944,11 +1944,11 @@ unit = getCodeUnit(); } while (IsAsciiDigit(unit)); } else { // '0' not followed by [XxBbOo0-9]; scan as a decimal number. - numStart = sourceUnits.addressOfNextCodeUnit() - 1; + numStart = this->sourceUnits.addressOfNextCodeUnit() - 1; // NOTE: |unit| may be EOF here. (This is permitted by case #3 // in TokenStream.h docs for this function.) return decimalNumber(unit, start, numStart, modifier, ttp); } @@ -1982,11 +1982,11 @@ } double dval; const char16_t* dummy; if (!GetPrefixInteger(anyCharsAccess().cx, numStart, - sourceUnits.addressOfNextCodeUnit(), radix, &dummy, &dval)) + this->sourceUnits.addressOfNextCodeUnit(), radix, &dummy, &dval)) { return badToken(); } newNumberToken(dval, NoDecimal, start, modifier, ttp); @@ -1998,21 +1998,21 @@ // This handles everything else. Simple tokens distinguished solely by // TokenKind should set |simpleKind| and break, to share simple-token // creation code for all such tokens. All other tokens must be handled // by returning (or by continuing from the loop enclosing this). // - TokenStart start(sourceUnits, -1); + TokenStart start(this->sourceUnits, -1); TokenKind simpleKind; #ifdef DEBUG simpleKind = TokenKind::Limit; // sentinel value for code after switch #endif switch (static_cast(unit)) { case '.': unit = getCodeUnit(); if (IsAsciiDigit(unit)) { - return decimalNumber('.', start, sourceUnits.addressOfNextCodeUnit() - 2, modifier, - ttp); + return decimalNumber('.', start, this->sourceUnits.addressOfNextCodeUnit() - 2, + modifier, ttp); } if (unit == '.') { if (matchCodeUnit('.')) { simpleKind = TokenKind::TripleDot; @@ -2045,11 +2045,11 @@ case '\\': { uint32_t codePoint; if (uint32_t escapeLength = matchUnicodeEscapeIdStart(&codePoint)) { return identifierName(start, - sourceUnits.addressOfNextCodeUnit() - escapeLength - 1, + this->sourceUnits.addressOfNextCodeUnit() - escapeLength - 1, IdentifierEscapes::SawUnicodeEscape, modifier, ttp); } // We could point "into" a mistyped escape, e.g. for "\u{41H}" we // could point at the 'H'. But we don't do that now, so the code @@ -2235,12 +2235,12 @@ "unexpected string/template literal delimiter"); bool parsingTemplate = (untilChar == '`'); bool templateHead = false; - TokenStart start(sourceUnits, -1); - charBuffer.clear(); + TokenStart start(this->sourceUnits, -1); + this->charBuffer.clear(); // Run the bad-token code for every path out of this function except the // one success-case. auto noteBadToken = MakeScopeExit([this]() { this->badToken(); @@ -2322,11 +2322,11 @@ // If we consumed U+2028 LINE SEPARATOR or U+2029 PARAGRAPH // SEPARATOR, they'll be normalized to '\n'. '\' followed by // LineContinuation represents no code points, so don't append // in this case. if (codePoint != '\n') { - if (!charBuffer.append(unit)) + if (!this->charBuffer.append(unit)) return false; } continue; } @@ -2338,11 +2338,11 @@ case 'r': unit = '\r'; break; case 't': unit = '\t'; break; case 'v': unit = '\v'; break; case '\r': - sourceUnits.matchCodeUnit('\n'); + this->sourceUnits.matchCodeUnit('\n'); MOZ_FALLTHROUGH; case '\n': { // LineContinuation represents no code points. We're manually // consuming a LineTerminatorSequence, so we must manually // update line/column info. @@ -2360,11 +2360,11 @@ return false; } // First handle a delimited Unicode escape, e.g. \u{1F4A9}. if (c2 == '{') { - uint32_t start = sourceUnits.offset() - 3; + uint32_t start = this->sourceUnits.offset() - 3; uint32_t code = 0; bool first = true; bool valid = true; do { int32_t u3 = getCodeUnit(); @@ -2442,16 +2442,16 @@ // Otherwise it must be a fixed-length \uXXXX Unicode escape. // If it isn't, this is usually an error -- but if this is a // template literal, we must defer error reporting because // malformed escapes are okay in *tagged* template literals. char16_t v; - if (JS7_ISHEX(c2) && sourceUnits.matchHexDigits(3, &v)) { + if (JS7_ISHEX(c2) && this->sourceUnits.matchHexDigits(3, &v)) { unit = (JS7_UNHEX(c2) << 12) | v; } else { // Beware: |c2| may not be an ASCII code point here! ungetCodeUnit(c2); - uint32_t start = sourceUnits.offset() - 2; + uint32_t start = this->sourceUnits.offset() - 2; if (parsingTemplate) { TokenStreamAnyChars& anyChars = anyCharsAccess(); anyChars.setInvalidTemplateEscape(start, InvalidEscapeType::Unicode); continue; } @@ -2462,14 +2462,14 @@ } // case 'u' // Hexadecimal character specification. case 'x': { char16_t v; - if (sourceUnits.matchHexDigits(2, &v)) { + if (this->sourceUnits.matchHexDigits(2, &v)) { unit = v; } else { - uint32_t start = sourceUnits.offset() - 2; + uint32_t start = this->sourceUnits.offset() - 2; if (parsingTemplate) { TokenStreamAnyChars& anyChars = anyCharsAccess(); anyChars.setInvalidTemplateEscape(start, InvalidEscapeType::Hexadecimal); continue; } @@ -2494,11 +2494,11 @@ // Strict mode code allows only \0, then a non-digit. if (val != 0 || IsAsciiDigit(unit)) { TokenStreamAnyChars& anyChars = anyCharsAccess(); if (parsingTemplate) { - anyChars.setInvalidTemplateEscape(sourceUnits.offset() - 2, + anyChars.setInvalidTemplateEscape(this->sourceUnits.offset() - 2, InvalidEscapeType::Octal); continue; } if (!reportStrictModeError(JSMSG_DEPRECATED_OCTAL)) return false; @@ -2528,11 +2528,11 @@ unit = char16_t(val); break; } // default } - if (!charBuffer.append(unit)) + if (!this->charBuffer.append(unit)) return false; continue; } // (unit == '\\') @@ -2546,12 +2546,12 @@ if (unit == '\r') { unit = '\n'; // If it's a \r\n sequence: treat as a single EOL, skip over the \n. - if (!sourceUnits.atEnd()) - sourceUnits.matchCodeUnit('\n'); + if (!this->sourceUnits.atEnd()) + this->sourceUnits.matchCodeUnit('\n'); } if (!updateLineInfoForEOL()) return false; @@ -2559,11 +2559,11 @@ } else if (parsingTemplate && unit == '$' && matchCodeUnit('{')) { templateHead = true; break; } - if (!charBuffer.append(unit)) + if (!this->charBuffer.append(unit)) return false; } JSAtom* atom = drainCharBufferIntoAtom(anyCharsAccess().cx); if (!atom) diff --git a/js/src/jit-test/tests/realms/array-species-create.js b/js/src/jit-test/tests/realms/array-species-create.js new file mode 100644 --- /dev/null +++ b/js/src/jit-test/tests/realms/array-species-create.js @@ -0,0 +1,31 @@ +function testSelfHosted() { + var g = newGlobal({sameCompartmentAs: this}); + var arr = g.evaluate("[1, 2]"); + for (var i = 0; i < 20; i++) { + var mapped = Array.prototype.map.call(arr, x => x + 1); + assertEq(mapped.constructor, Array); + } +} +testSelfHosted(); + +function testNative() { + var g = newGlobal({sameCompartmentAs: this}); + var arr = g.evaluate("[1, 2, 3, 4]"); + for (var i = 0; i < 20; i++) { + var slice = Array.prototype.slice.call(arr, 0, 3); + assertEq(slice.constructor, Array); + } +} +testNative(); + +function testIntrinsic() { + var g1 = newGlobal({sameCompartmentAs: this}); + var g2 = newGlobal(); + var IsCrossRealmArrayConstructor = getSelfHostedValue("IsCrossRealmArrayConstructor"); + for (var i = 0; i < 20; i++) { + assertEq(IsCrossRealmArrayConstructor(Array), false); + assertEq(IsCrossRealmArrayConstructor(g1.Array), true); + assertEq(IsCrossRealmArrayConstructor(g2.Array), true); + } +} +testIntrinsic(); diff --git a/js/src/jit/CacheIR.h b/js/src/jit/CacheIR.h --- a/js/src/jit/CacheIR.h +++ b/js/src/jit/CacheIR.h @@ -1666,10 +1666,12 @@ public: GetIteratorIRGenerator(JSContext* cx, HandleScript, jsbytecode* pc, ICState::Mode mode, HandleValue value); bool tryAttachStub(); + + void trackAttached(const char *name); }; class MOZ_RAII CallIRGenerator : public IRGenerator { private: diff --git a/js/src/jit/CacheIR.cpp b/js/src/jit/CacheIR.cpp --- a/js/src/jit/CacheIR.cpp +++ b/js/src/jit/CacheIR.cpp @@ -4351,13 +4351,16 @@ return false; RootedObject obj(cx_, &val_.toObject()); ObjOperandId objId = writer.guardIsObject(valId); - if (tryAttachNativeIterator(objId, obj)) - return true; + if (tryAttachNativeIterator(objId, obj)) { + trackAttached("GetIterator"); + return true; + } + trackAttached(IRGenerator::NotAttached); return false; } bool GetIteratorIRGenerator::tryAttachNativeIterator(ObjOperandId objId, HandleObject obj) @@ -4389,10 +4392,20 @@ writer.returnFromIC(); return true; } +void +GetIteratorIRGenerator::trackAttached(const char* name) +{ +#ifdef JS_CACHEIR_SPEW + if (const CacheIRSpewer::Guard& sp = CacheIRSpewer::Guard(*this, name)) { + sp.valueProperty("val", val_); + } +#endif +} + CallIRGenerator::CallIRGenerator(JSContext* cx, HandleScript script, jsbytecode* pc, JSOp op, ICState::Mode mode, uint32_t argc, HandleValue callee, HandleValue thisval, HandleValueArray args) : IRGenerator(cx, script, pc, CacheKind::Call, mode), op_(op), diff --git a/js/src/jit/InlinableNatives.h b/js/src/jit/InlinableNatives.h --- a/js/src/jit/InlinableNatives.h +++ b/js/src/jit/InlinableNatives.h @@ -121,11 +121,11 @@ \ _(IntrinsicIsCallable) \ _(IntrinsicIsConstructor) \ _(IntrinsicToObject) \ _(IntrinsicIsObject) \ - _(IntrinsicIsWrappedArrayConstructor) \ + _(IntrinsicIsCrossRealmArrayConstructor) \ _(IntrinsicToInteger) \ _(IntrinsicToString) \ _(IntrinsicIsConstructing) \ _(IntrinsicSubstringKernel) \ _(IntrinsicObjectHasPrototype) \ diff --git a/js/src/jit/IonBuilder.h b/js/src/jit/IonBuilder.h --- a/js/src/jit/IonBuilder.h +++ b/js/src/jit/IonBuilder.h @@ -778,11 +778,11 @@ // Utility intrinsics. InliningResult inlineIsCallable(CallInfo& callInfo); InliningResult inlineIsConstructor(CallInfo& callInfo); InliningResult inlineIsObject(CallInfo& callInfo); InliningResult inlineToObject(CallInfo& callInfo); - InliningResult inlineIsWrappedArrayConstructor(CallInfo& callInfo); + InliningResult inlineIsCrossRealmArrayConstructor(CallInfo& callInfo); InliningResult inlineToInteger(CallInfo& callInfo); InliningResult inlineToString(CallInfo& callInfo); InliningResult inlineDump(CallInfo& callInfo); InliningResult inlineHasClass(CallInfo& callInfo, const Class* clasp, const Class* clasp2 = nullptr, diff --git a/js/src/jit/MCallOptimize.cpp b/js/src/jit/MCallOptimize.cpp --- a/js/src/jit/MCallOptimize.cpp +++ b/js/src/jit/MCallOptimize.cpp @@ -314,12 +314,12 @@ return inlineIsConstructor(callInfo); case InlinableNative::IntrinsicToObject: return inlineToObject(callInfo); case InlinableNative::IntrinsicIsObject: return inlineIsObject(callInfo); - case InlinableNative::IntrinsicIsWrappedArrayConstructor: - return inlineIsWrappedArrayConstructor(callInfo); + case InlinableNative::IntrinsicIsCrossRealmArrayConstructor: + return inlineIsCrossRealmArrayConstructor(callInfo); case InlinableNative::IntrinsicToInteger: return inlineToInteger(callInfo); case InlinableNative::IntrinsicToString: return inlineToString(callInfo); case InlinableNative::IntrinsicIsConstructing: @@ -3297,11 +3297,11 @@ return InliningStatus_Inlined; } IonBuilder::InliningResult -IonBuilder::inlineIsWrappedArrayConstructor(CallInfo& callInfo) +IonBuilder::inlineIsCrossRealmArrayConstructor(CallInfo& callInfo) { MOZ_ASSERT(!callInfo.constructing()); MOZ_ASSERT(callInfo.argc() == 1); if (getInlineReturnType() != MIRType::Boolean) @@ -3309,22 +3309,18 @@ MDefinition* arg = callInfo.getArg(0); if (arg->type() != MIRType::Object) return InliningStatus_NotInlined; TemporaryTypeSet* types = arg->resultTypeSet(); - switch (types->forAllClasses(constraints(), IsProxyClass)) { - case TemporaryTypeSet::ForAllResult::ALL_FALSE: - break; - case TemporaryTypeSet::ForAllResult::EMPTY: - case TemporaryTypeSet::ForAllResult::ALL_TRUE: - case TemporaryTypeSet::ForAllResult::MIXED: + Realm* realm = types->getKnownRealm(constraints()); + if (!realm || realm != script()->realm()) return InliningStatus_NotInlined; - } callInfo.setImplicitlyUsedUnchecked(); - // Inline only if argument is absolutely *not* a Proxy. + // Inline only if argument is absolutely *not* a wrapper or a cross-realm + // object. pushConstant(BooleanValue(false)); return InliningStatus_Inlined; } IonBuilder::InliningResult diff --git a/js/src/jit/mips-shared/MacroAssembler-mips-shared-inl.h b/js/src/jit/mips-shared/MacroAssembler-mips-shared-inl.h --- a/js/src/jit/mips-shared/MacroAssembler-mips-shared-inl.h +++ b/js/src/jit/mips-shared/MacroAssembler-mips-shared-inl.h @@ -1055,11 +1055,12 @@ } void MacroAssembler::memoryBarrier(MemoryBarrierBits barrier) { - as_sync(); + if (barrier) + as_sync(); } // =============================================================== // Clamping functions. diff --git a/js/src/jit/mips32/MacroAssembler-mips32.cpp b/js/src/jit/mips32/MacroAssembler-mips32.cpp --- a/js/src/jit/mips32/MacroAssembler-mips32.cpp +++ b/js/src/jit/mips32/MacroAssembler-mips32.cpp @@ -115,10 +115,15 @@ MacroAssemblerMIPSCompat::convertDoubleToFloat32(FloatRegister src, FloatRegister dest) { as_cvtsd(dest, src); } +const int CauseBitPos = int(Assembler::CauseI); +const int CauseBitCount = 1 + int(Assembler::CauseV) - int(Assembler::CauseI); +const int CauseIOrVMask = ((1 << int(Assembler::CauseI)) | + (1 << int(Assembler::CauseV))) >> int(Assembler::CauseI); + // Checks whether a double is representable as a 32-bit integer. If so, the // integer is written to the output register. Otherwise, a bailout is taken to // the given snapshot. This function overwrites the scratch float register. void MacroAssemblerMIPSCompat::convertDoubleToInt32(FloatRegister src, Register dest, @@ -134,16 +139,16 @@ // Truncate double to int ; if result is inexact or invalid fail. as_truncwd(ScratchFloat32Reg, src); as_cfc1(ScratchRegister, Assembler::FCSR); moveFromFloat32(ScratchFloat32Reg, dest); - ma_ext(ScratchRegister, ScratchRegister, Assembler::CauseI, 6); + ma_ext(ScratchRegister, ScratchRegister, CauseBitPos, CauseBitCount); // Here adding the masking andi instruction just for a precaution. // For the instruction of trunc.*.*, the Floating Point Exceptions can be // only Inexact, Invalid Operation, Unimplemented Operation. // Leaving it maybe is also ok. - as_andi(ScratchRegister, ScratchRegister, 0x11); + as_andi(ScratchRegister, ScratchRegister, CauseIOrVMask); ma_b(ScratchRegister, Imm32(0), fail, Assembler::NotEqual); } // Checks whether a float32 is representable as a 32-bit integer. If so, the // integer is written to the output register. Otherwise, a bailout is taken to @@ -158,11 +163,12 @@ } as_truncws(ScratchFloat32Reg, src); as_cfc1(ScratchRegister, Assembler::FCSR); moveFromFloat32(ScratchFloat32Reg, dest); - ma_ext(ScratchRegister, ScratchRegister, Assembler::CauseI, 1); + ma_ext(ScratchRegister, ScratchRegister, CauseBitPos, CauseBitCount); + as_andi(ScratchRegister, ScratchRegister, CauseIOrVMask); ma_b(ScratchRegister, Imm32(0), fail, Assembler::NotEqual); } void MacroAssemblerMIPSCompat::convertFloat32ToDouble(FloatRegister src, FloatRegister dest) diff --git a/js/src/jit/mips64/MacroAssembler-mips64.cpp b/js/src/jit/mips64/MacroAssembler-mips64.cpp --- a/js/src/jit/mips64/MacroAssembler-mips64.cpp +++ b/js/src/jit/mips64/MacroAssembler-mips64.cpp @@ -97,10 +97,15 @@ MacroAssemblerMIPS64Compat::convertDoubleToFloat32(FloatRegister src, FloatRegister dest) { as_cvtsd(dest, src); } +const int CauseBitPos = int(Assembler::CauseI); +const int CauseBitCount = 1 + int(Assembler::CauseV) - int(Assembler::CauseI); +const int CauseIOrVMask = ((1 << int(Assembler::CauseI)) | + (1 << int(Assembler::CauseV))) >> int(Assembler::CauseI); + // Checks whether a double is representable as a 32-bit integer. If so, the // integer is written to the output register. Otherwise, a bailout is taken to // the given snapshot. This function overwrites the scratch float register. void MacroAssemblerMIPS64Compat::convertDoubleToInt32(FloatRegister src, Register dest, @@ -114,12 +119,12 @@ // Truncate double to int ; if result is inexact or invalid fail. as_truncwd(ScratchFloat32Reg, src); as_cfc1(ScratchRegister, Assembler::FCSR); moveFromFloat32(ScratchFloat32Reg, dest); - ma_ext(ScratchRegister, ScratchRegister, Assembler::CauseI, 6); - as_andi(ScratchRegister, ScratchRegister, 0x11);//masking for Inexact and Invalid flag. + ma_ext(ScratchRegister, ScratchRegister, CauseBitPos, CauseBitCount); + as_andi(ScratchRegister, ScratchRegister, CauseIOrVMask); //masking for Inexact and Invalid flag. ma_b(ScratchRegister, Imm32(0), fail, Assembler::NotEqual); } // Checks whether a float32 is representable as a 32-bit integer. If so, the // integer is written to the output register. Otherwise, a bailout is taken to @@ -134,11 +139,12 @@ } as_truncws(ScratchFloat32Reg, src); as_cfc1(ScratchRegister, Assembler::FCSR); moveFromFloat32(ScratchFloat32Reg, dest); - ma_ext(ScratchRegister, ScratchRegister, Assembler::CauseI, 1); + ma_ext(ScratchRegister, ScratchRegister, CauseBitPos, CauseBitCount); + as_andi(ScratchRegister, ScratchRegister, CauseIOrVMask); ma_b(ScratchRegister, Imm32(0), fail, Assembler::NotEqual); } void MacroAssemblerMIPS64Compat::convertFloat32ToDouble(FloatRegister src, FloatRegister dest) diff --git a/js/src/jsapi.cpp b/js/src/jsapi.cpp --- a/js/src/jsapi.cpp +++ b/js/src/jsapi.cpp @@ -13,10 +13,15 @@ #include "mozilla/FloatingPoint.h" #include "mozilla/Maybe.h" #include "mozilla/PodOperations.h" #include "mozilla/Sprintf.h" +#include +#include +#include +#include + #include #ifdef __linux__ # include #endif #include @@ -108,16 +113,32 @@ using mozilla::Maybe; using mozilla::PodCopy; using mozilla::Some; +const char* Test[] = { + "line 1", + "line 2", + "line 3", + "line 4", + "line 5", + "line 6" // misc-suspicious-missing-comma + "line 7", + "line 8", +}; + #ifdef HAVE_VA_LIST_AS_ARRAY #define JS_ADDRESSOF_VA_LIST(ap) ((va_list*)(ap)) #else #define JS_ADDRESSOF_VA_LIST(ap) (&(ap)) #endif +class AnnotateConflict { + MOZ_NO_DANGLING_ON_TEMPORARIES int *get() && { return nullptr; } // mozilla-dangling-on-temporary + MOZ_NO_DANGLING_ON_TEMPORARIES int test() { return 0; } // mozilla-dangling-on-temporary +}; + JS_PUBLIC_API(bool) JS::CallArgs::requireAtLeast(JSContext* cx, const char* fnname, unsigned required) const { if (length() < required) { char numArgsStr[40]; @@ -163,11 +184,11 @@ RootedValue val(cx, ObjectValue(*obj)); return ReportValueErrorFlags(cx, flags, code_, JSDVG_IGNORE_STACK, val, nullptr, nullptr, nullptr); } - if (ErrorTakesArguments(code_)) { + else if (ErrorTakesArguments(code_)) { // readability-misleading-indentation or readability-else-after-return RootedValue idv(cx, IdToValue(id)); RootedString str(cx, ValueToSource(cx, idv)); if (!str) return false; @@ -198,22 +219,36 @@ } JS_PUBLIC_API(bool) JS::ObjectOpResult::reportStrictErrorOrWarning(JSContext* cx, HandleObject obj, bool strict) { + for (float x = 0.1f; x <= 1.0f; x += 0.1f) {} // clang-analyzer-security.FloatLoopCounter + + setuid(1); // clang-analyzer-security.insecureAPI.UncheckedReturn + + int x = 0; + int y = 0; + if (x < y); // misc-suspicious-semicolon + { + x++; + } + MOZ_ASSERT(code_ != Uninitialized); MOZ_ASSERT(!ok()); MOZ_ASSERT(!ErrorTakesArguments(code_)); assertSameCompartment(cx, obj); unsigned flags = strict ? JSREPORT_ERROR : (JSREPORT_WARNING | JSREPORT_STRICT); return JS_ReportErrorFlagsAndNumberASCII(cx, flags, GetErrorMessage, nullptr, code_); } JS_PUBLIC_API(bool) -JS::ObjectOpResult::failCantRedefineProp() +JS::ObjectOpResult::failCantRedefineProp(void) // modernize-redundant-void-arg { + int x; + x = 1; // clang-analyzer-deadcode.DeadStores + return fail(JSMSG_CANT_REDEFINE_PROP); } JS_PUBLIC_API(bool) JS::ObjectOpResult::failReadOnly() @@ -306,16 +341,22 @@ } JS_PUBLIC_API(Value) JS_GetEmptyStringValue(JSContext* cx) { + std::string str; + str.find("A"); // performance-faster-string-find + return StringValue(cx->runtime()->emptyString); } JS_PUBLIC_API(JSString*) JS_GetEmptyString(JSContext* cx) { + std::map> my_map; + for (const std::pair>& p : my_map) {} // performance-implicit-conversion-in-loop + MOZ_ASSERT(cx->emptyString()); return cx->emptyString(); } namespace js { diff --git a/js/src/jsexn.cpp b/js/src/jsexn.cpp --- a/js/src/jsexn.cpp +++ b/js/src/jsexn.cpp @@ -687,17 +687,18 @@ UniquePtr report = CopyErrorReport(cx, reportp); if (!report) return; - RootedObject errObject(cx, ErrorObject::create(cx, exnType, stack, fileName, lineNumber, - columnNumber, std::move(report), messageStr)); + ErrorObject* errObject = ErrorObject::create(cx, exnType, stack, fileName, lineNumber, + columnNumber, std::move(report), messageStr); if (!errObject) return; // Throw it. - cx->setPendingException(ObjectValue(*errObject)); + RootedValue errValue(cx, ObjectValue(*errObject)); + cx->setPendingException(errValue); // Flag the error report passed in to indicate an exception was raised. reportp->flags |= JSREPORT_EXCEPTION; } diff --git a/js/src/proxy/Wrapper.cpp b/js/src/proxy/Wrapper.cpp --- a/js/src/proxy/Wrapper.cpp +++ b/js/src/proxy/Wrapper.cpp @@ -454,11 +454,12 @@ RootedValue exc(cx); if (cx->getPendingException(&exc) && exc.isObject() && exc.toObject().is()) { cx->clearPendingException(); ar.reset(); Rooted errObj(cx, &exc.toObject().as()); - JSObject* copyobj = CopyErrorObject(cx, errObj); - if (copyobj) - cx->setPendingException(ObjectValue(*copyobj)); + if (JSObject* copyobj = CopyErrorObject(cx, errObj)) { + RootedValue rootedCopy(cx, ObjectValue(*copyobj)); + cx->setPendingException(rootedCopy); + } } } } diff --git a/js/src/vm/GeneratorObject.cpp b/js/src/vm/GeneratorObject.cpp --- a/js/src/vm/GeneratorObject.cpp +++ b/js/src/vm/GeneratorObject.cpp @@ -121,11 +121,12 @@ MOZ_ASSERT(resumeKind == GeneratorObject::RETURN); MOZ_ASSERT(arg.isObject()); frame.setReturnValue(arg); - cx->setPendingException(MagicValue(JS_GENERATOR_CLOSING)); + RootedValue closing(cx, MagicValue(JS_GENERATOR_CLOSING)); + cx->setPendingException(closing); genObj->setClosing(); } return false; } diff --git a/js/src/vm/Interpreter.cpp b/js/src/vm/Interpreter.cpp --- a/js/src/vm/Interpreter.cpp +++ b/js/src/vm/Interpreter.cpp @@ -3992,11 +3992,12 @@ * Exception was pending during finally, throw it *before* we adjust * pc, because pc indexes into script->trynotes. This turns out not to * be necessary, but it seems clearer. And it points out a FIXME: * 350509, due to Igor Bukanov. */ - cx->setPendingException(rval); + ReservedRooted v(&rootValue0, rval); + cx->setPendingException(v); goto error; } MOZ_ASSERT(rval.isInt32()); /* Increment the PC by this much. */ diff --git a/js/src/vm/JSContext-inl.h b/js/src/vm/JSContext-inl.h --- a/js/src/vm/JSContext-inl.h +++ b/js/src/vm/JSContext-inl.h @@ -358,11 +358,11 @@ { runtime()->gc.minorGC(reason); } inline void -JSContext::setPendingException(const js::Value& v) +JSContext::setPendingException(JS::HandleValue v) { #if defined(NIGHTLY_BUILD) do { // Do not intercept exceptions if we are already // in the exception interceptor. That would lead diff --git a/js/src/vm/JSContext.h b/js/src/vm/JSContext.h --- a/js/src/vm/JSContext.h +++ b/js/src/vm/JSContext.h @@ -737,11 +737,11 @@ bool isThrowingOutOfMemory(); bool isThrowingDebuggeeWouldRun(); bool isClosingGenerator(); - void setPendingException(const js::Value& v); + void setPendingException(JS::HandleValue v); void clearPendingException() { throwing = false; overRecursed_ = false; unwrappedException().setUndefined(); diff --git a/js/src/vm/JSContext.cpp b/js/src/vm/JSContext.cpp --- a/js/src/vm/JSContext.cpp +++ b/js/src/vm/JSContext.cpp @@ -305,11 +305,12 @@ /* Report the oom. */ if (JS::OutOfMemoryCallback oomCallback = cx->runtime()->oomCallback) oomCallback(cx, cx->runtime()->oomCallbackData); - cx->setPendingException(StringValue(cx->names().outOfMemory)); + RootedValue oomMessage(cx, StringValue(cx->names().outOfMemory)); + cx->setPendingException(oomMessage); } mozilla::GenericErrorResult js::ReportOutOfMemoryResult(JSContext* cx) { diff --git a/js/src/vm/SelfHosting.cpp b/js/src/vm/SelfHosting.cpp --- a/js/src/vm/SelfHosting.cpp +++ b/js/src/vm/SelfHosting.cpp @@ -121,15 +121,15 @@ } return true; } static bool -intrinsic_IsWrappedArrayConstructor(JSContext* cx, unsigned argc, Value* vp) +intrinsic_IsCrossRealmArrayConstructor(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); bool result = false; - if (!IsWrappedArrayConstructor(cx, args[0], &result)) + if (!IsCrossRealmArrayConstructor(cx, args[0], &result)) return false; args.rval().setBoolean(result); return true; } @@ -2418,12 +2418,12 @@ // Helper funtions after this point. JS_INLINABLE_FN("ToObject", intrinsic_ToObject, 1,0, IntrinsicToObject), JS_INLINABLE_FN("IsObject", intrinsic_IsObject, 1,0, IntrinsicIsObject), JS_INLINABLE_FN("IsArray", intrinsic_IsArray, 1,0, ArrayIsArray), - JS_INLINABLE_FN("IsWrappedArrayConstructor", intrinsic_IsWrappedArrayConstructor, 1,0, - IntrinsicIsWrappedArrayConstructor), + JS_INLINABLE_FN("IsCrossRealmArrayConstructor", intrinsic_IsCrossRealmArrayConstructor, 1,0, + IntrinsicIsCrossRealmArrayConstructor), JS_INLINABLE_FN("ToInteger", intrinsic_ToInteger, 1,0, IntrinsicToInteger), JS_INLINABLE_FN("ToString", intrinsic_ToString, 1,0, IntrinsicToString), JS_FN("ToSource", intrinsic_ToSource, 1,0), JS_FN("ToPropertyKey", intrinsic_ToPropertyKey, 1,0), JS_INLINABLE_FN("IsCallable", intrinsic_IsCallable, 1,0, IntrinsicIsCallable), diff --git a/js/src/vm/TypeInference.h b/js/src/vm/TypeInference.h --- a/js/src/vm/TypeInference.h +++ b/js/src/vm/TypeInference.h @@ -876,10 +876,17 @@ bool hasObjectFlags(CompilerConstraintList* constraints, ObjectGroupFlags flags); /* Get the class shared by all objects in this set, or nullptr. */ const Class* getKnownClass(CompilerConstraintList* constraints); + /* + * Get the realm shared by all objects in this set, or nullptr. Returns + * nullptr if the set contains proxies (because cross-compartment wrappers + * don't have a single realm associated with them). + */ + Realm* getKnownRealm(CompilerConstraintList* constraints); + /* Result returned from forAllClasses */ enum ForAllResult { EMPTY=1, // Set empty ALL_TRUE, // Set not empty and predicate returned true for all classes ALL_FALSE, // Set not empty and predicate returned false for all classes diff --git a/js/src/vm/TypeInference.cpp b/js/src/vm/TypeInference.cpp --- a/js/src/vm/TypeInference.cpp +++ b/js/src/vm/TypeInference.cpp @@ -2345,10 +2345,54 @@ } return clasp; } +Realm* +TemporaryTypeSet::getKnownRealm(CompilerConstraintList* constraints) +{ + if (unknownObject()) + return nullptr; + + Realm* realm = nullptr; + unsigned count = getObjectCount(); + + for (unsigned i = 0; i < count; i++) { + const Class* clasp = getObjectClass(i); + if (!clasp) + continue; + + // If clasp->isProxy(), this might be a cross-compartment wrapper and + // CCWs don't have a (single) realm, so we give up. If the object has + // unknownProperties(), hasStableClassAndProto (called below) will + // return |false| so fail now before attaching any constraints. + if (clasp->isProxy() || getObject(i)->unknownProperties()) + return nullptr; + + MOZ_ASSERT(hasSingleton(i) || hasGroup(i)); + + Realm* nrealm = hasSingleton(i) ? getSingleton(i)->nonCCWRealm() : getGroup(i)->realm(); + MOZ_ASSERT(nrealm); + if (!realm) { + realm = nrealm; + continue; + } + if (realm != nrealm) + return nullptr; + } + + if (realm) { + for (unsigned i = 0; i < count; i++) { + ObjectKey* key = getObject(i); + if (key && !key->hasStableClassAndProto(constraints)) + return nullptr; + } + } + + return realm; +} + void TemporaryTypeSet::getTypedArraySharedness(CompilerConstraintList* constraints, TypedArraySharedness* sharedness) { // In the future this will inspect the object set. diff --git a/layout/base/nsLayoutUtils.h b/layout/base/nsLayoutUtils.h --- a/layout/base/nsLayoutUtils.h +++ b/layout/base/nsLayoutUtils.h @@ -2393,15 +2393,10 @@ /** * Checks if we should enable parsing for CSS Filters. */ static bool CSSFiltersEnabled(); - /** - * Checks whether support for the CSS-wide "unset" value is enabled. - */ - static bool UnsetValueEnabled(); - /** * Checks whether support for inter-character ruby is enabled. */ static bool IsInterCharacterRubyEnabled(); diff --git a/layout/base/nsLayoutUtils.cpp b/layout/base/nsLayoutUtils.cpp --- a/layout/base/nsLayoutUtils.cpp +++ b/layout/base/nsLayoutUtils.cpp @@ -518,26 +518,10 @@ } return sCSSFiltersEnabled; } -bool -nsLayoutUtils::UnsetValueEnabled() -{ - static bool sUnsetValueEnabled; - static bool sUnsetValuePrefCached = false; - - if (!sUnsetValuePrefCached) { - sUnsetValuePrefCached = true; - Preferences::AddBoolVarCache(&sUnsetValueEnabled, - "layout.css.unset-value.enabled", - false); - } - - return sUnsetValueEnabled; -} - bool nsLayoutUtils::IsInterCharacterRubyEnabled() { static bool sInterCharacterRubyEnabled; static bool sInterCharacterRubyEnabledPrefCached = false; diff --git a/layout/doc/frame_reflow_debug.html b/layout/doc/frame_reflow_debug.html --- a/layout/doc/frame_reflow_debug.html +++ b/layout/doc/frame_reflow_debug.html @@ -35,15 +35,28 @@

Getting the log

  • Make sure that your build is a debug build. +
  • Either: +
      +
    1. Select which processes should log all reflow debugging: +
        +
      • set GECKO_DISPLAY_REFLOW_PROCESSES=a to log from all processes +
      • set GECKO_DISPLAY_REFLOW_PROCESSES=p to log from the parent process +
      • set GECKO_DISPLAY_REFLOW_PROCESSES=c to log from e10s content processes +
      +
    2. OR create a rules file: +
      • Create a text file (for instance reflow_rules.txt).
      • Enter this line in the text file
        * 1
      • Save the text file in the %DIST% directory
      • Go to the %DIST% directory
      • enter from command line: set GECKO_DISPLAY_REFLOW_RULES_FILE=reflow_rules.txt +
      • Note: If Firefox fails to read the file, it may be because of sandboxing, try `--setpref security.sandbox.content.level=2` as workaround. +
      +
  • Run viewer > logfile.txt and load your testcase. The logfile will contain all the promised information.

Log File analyis

The logfile for a simple table like

diff --git a/layout/generic/nsFrame.cpp b/layout/generic/nsFrame.cpp --- a/layout/generic/nsFrame.cpp +++ b/layout/generic/nsFrame.cpp @@ -2950,18 +2950,20 @@ visibleRect = nsSVGIntegrationUtils::GetRequiredSourceForInvalidArea(this, visibleRect); aBuilder->EnterSVGEffectsContents(&hoistedScrollInfoItemsStorage); } + + bool needsActiveOpacityLayer = false; // We build an opacity item if it's not going to be drawn by SVG content, or // SVG effects. SVG effects won't handle the opacity if we want an active // layer (for async animations), see // nsSVGIntegrationsUtils::PaintMaskAndClipPath or // nsSVGIntegrationsUtils::PaintFilter. bool useOpacity = HasVisualOpacity(effectSet) && !nsSVGUtils::CanOptimizeOpacity(this) && - (!usingSVGEffects || nsDisplayOpacity::NeedsActiveLayer(aBuilder, this)); + ((needsActiveOpacityLayer = nsDisplayOpacity::NeedsActiveLayer(aBuilder, this)) || !usingSVGEffects); bool useBlendMode = effects->mMixBlendMode != NS_STYLE_BLEND_NORMAL; bool useStickyPosition = disp->mPosition == NS_STYLE_POSITION_STICKY && IsScrollFrameActive(aBuilder, nsLayoutUtils::GetNearestScrollableFrame(GetParent(), nsLayoutUtils::SCROLLABLE_SAME_DOC | @@ -3257,13 +3259,14 @@ // Don't clip nsDisplayOpacity items. We clip their descendants instead. // The clip we would set on an element with opacity would clip // all descendant content, but some should not be clipped. DisplayListClipState::AutoSaveRestore opacityClipState(aBuilder); resultList.AppendToTop( - MakeDisplayItem(aBuilder, this, &resultList, + MakeDisplayItem(aBuilder, this, &resultList, containerItemASR, - opacityItemForEventsAndPluginsOnly)); + opacityItemForEventsAndPluginsOnly, + needsActiveOpacityLayer)); if (aCreatedContainerItem) { *aCreatedContainerItem = true; } } @@ -11920,17 +11923,42 @@ } } aRules.AppendElement(&aRule); } +static Maybe ShouldLogReflow(const char* processes) +{ + switch (processes[0]) { + case 'A': case 'a': return Some(true); + case 'P': case 'p': return Some(XRE_IsParentProcess()); + case 'C': case 'c': return Some(XRE_IsContentProcess()); + default: return Nothing{}; + } +} + void DR_State::ParseRulesFile() { + char* processes = PR_GetEnv("GECKO_DISPLAY_REFLOW_PROCESSES"); + if (processes) { + Maybe enableLog = ShouldLogReflow(processes); + if (enableLog.isNothing()) { + MOZ_CRASH("GECKO_DISPLAY_REFLOW_PROCESSES: [a]ll [p]arent [c]ontent"); + } else if (enableLog.value()) { + DR_Rule* rule = new DR_Rule; + rule->AddPart(LayoutFrameType::None); + rule->mDisplay = true; + AddRule(mWildRules, *rule); + mActive = true; + } + return; + } + char* path = PR_GetEnv("GECKO_DISPLAY_REFLOW_RULES_FILE"); if (path) { FILE* inFile = fopen(path, "r"); if (!inFile) { - MOZ_CRASH("Failed to open the specified rules file"); + MOZ_CRASH("Failed to open the specified rules file; Try `--setpref security.sandbox.content.level=2` if the sandbox is at cause"); } for (DR_Rule* rule = ParseRule(inFile); rule; rule = ParseRule(inFile)) { if (rule->mTarget) { LayoutFrameType fType = rule->mTarget->mFrameType; if (fType != LayoutFrameType::None) { diff --git a/layout/painting/FrameLayerBuilder.cpp b/layout/painting/FrameLayerBuilder.cpp --- a/layout/painting/FrameLayerBuilder.cpp +++ b/layout/painting/FrameLayerBuilder.cpp @@ -577,11 +577,12 @@ const nsIntRect& aVisibleRect, const nsRect& aContentRect, const DisplayItemClip& aClip, LayerState aLayerState, nsDisplayList *aList, - DisplayItemEntryType aType); + DisplayItemEntryType aType, + nsTArray& aOpacityIndices); AnimatedGeometryRoot* GetAnimatedGeometryRoot() { return mAnimatedGeometryRoot; } /** * A region including the horizontal pan, vertical pan, and no action regions. */ @@ -766,15 +767,10 @@ /** * All the display items that have been assigned to this painted layer. * These items get added by Accumulate(). */ nsTArray mAssignedDisplayItems; - /** - * Tracks the active opacity markers by holding the indices to PUSH_OPACITY - * items in |mAssignedDisplayItems|. - */ - nsTArray mOpacityIndices; }; struct NewLayerEntry { NewLayerEntry() : mAnimatedGeometryRoot(nullptr) @@ -3349,12 +3345,10 @@ template void ContainerState::FinishPaintedLayerData(PaintedLayerData& aData, FindOpaqueBackgroundColorCallbackType aFindOpaqueBackgroundColor) { PaintedLayerData* data = &aData; - MOZ_ASSERT(data->mOpacityIndices.IsEmpty()); - if (!data->mLayer) { // No layer was recycled, so we create a new one. RefPtr paintedLayer = CreatePaintedLayer(data); data->mLayer = paintedLayer; @@ -3637,22 +3631,23 @@ const nsIntRect& aVisibleRect, const nsRect& aContentRect, const DisplayItemClip& aClip, LayerState aLayerState, nsDisplayList* aList, - DisplayItemEntryType aType) + DisplayItemEntryType aType, + nsTArray& aOpacityIndices) { FLB_LOG_PAINTED_LAYER_DECISION(this, "Accumulating dp=%s(%p), f=%p against pld=%p\n", aItem->Name(), aItem, aItem->Frame(), this); - const bool hasOpacity = mOpacityIndices.Length() > 0; + const bool hasOpacity = aOpacityIndices.Length() > 0; const DisplayItemClip* oldClip = mItemClip; mItemClip = &aClip; if (aType == DisplayItemEntryType::POP_OPACITY) { - MOZ_ASSERT(!mOpacityIndices.IsEmpty()); - mOpacityIndices.RemoveLastElement(); + MOZ_ASSERT(!aOpacityIndices.IsEmpty()); + aOpacityIndices.RemoveLastElement(); AssignedDisplayItem item(aItem, aLayerState, nullptr, aContentRect, aType, hasOpacity); mAssignedDisplayItems.AppendElement(std::move(item)); return; @@ -3671,11 +3666,11 @@ } else { componentAlphaBounds = aItem->GetComponentAlphaBounds(aState->mBuilder); if (!componentAlphaBounds.IsEmpty()) { // This display item needs background copy when pushing opacity group. - for (size_t i : mOpacityIndices) { + for (size_t i : aOpacityIndices) { AssignedDisplayItem& item = mAssignedDisplayItems[i]; MOZ_ASSERT(item.mType == DisplayItemEntryType::PUSH_OPACITY || item.mType == DisplayItemEntryType::PUSH_OPACITY_WITH_BG); item.mType = DisplayItemEntryType::PUSH_OPACITY_WITH_BG; } @@ -3694,11 +3689,11 @@ AssignedDisplayItem item(aItem, aLayerState, oldData, aContentRect, aType, hasOpacity); mAssignedDisplayItems.AppendElement(std::move(item)); if (aType == DisplayItemEntryType::PUSH_OPACITY) { - mOpacityIndices.AppendElement(mAssignedDisplayItems.Length() - 1); + aOpacityIndices.AppendElement(mAssignedDisplayItems.Length() - 1); } if (aItem->MustPaintOnContentSide()) { mShouldPaintOnContentSide = true; } @@ -4286,10 +4281,11 @@ nsPoint lastTopLeft; // Tracks the PaintedLayerData that the item will be accumulated in, if it is // non-null. Currently only used with PUSH_OPACITY and POP_OPACITY markers. PaintedLayerData* selectedPLD = nullptr; + AutoTArray opacityIndices; FLBDisplayItemIterator iter(mBuilder, aList, this); while (iter.HasNext()) { DisplayItemEntry e = iter.GetNextEntry(); nsDisplayItem* i = e.mItem; @@ -4839,11 +4835,11 @@ nsDisplayCompositorHitTestInfo* hitTestInfo = static_cast(item); paintedLayerData->AccumulateHitTestInfo(this, hitTestInfo); } else { paintedLayerData->Accumulate(this, item, itemVisibleRect, itemContent, itemClip, - layerState, aList, marker); + layerState, aList, marker, opacityIndices); if (!paintedLayerData->mLayer) { // Try to recycle the old layer of this display item. RefPtr layer = AttemptToRecyclePaintedLayer(animatedGeometryRoot, item, @@ -4866,11 +4862,11 @@ } if (marker == DisplayItemEntryType::POP_OPACITY ) { MOZ_ASSERT(selectedPLD); - if (selectedPLD->mOpacityIndices.IsEmpty()) { + if (opacityIndices.IsEmpty()) { selectedPLD = nullptr; } } } diff --git a/layout/painting/RetainedDisplayListBuilder.cpp b/layout/painting/RetainedDisplayListBuilder.cpp --- a/layout/painting/RetainedDisplayListBuilder.cpp +++ b/layout/painting/RetainedDisplayListBuilder.cpp @@ -295,10 +295,11 @@ if (mBuilder->MergeDisplayLists(aNewItem->GetChildren(), oldItem->GetChildren(), aNewItem->GetChildren(), containerASRForChildren, aNewItem->GetPerFrameKey())) { + aNewItem->InvalidateCachedChildInfo(); mResultIsModified = true; } UpdateASR(aNewItem, containerASRForChildren); aNewItem->UpdateBounds(mBuilder->Builder()); @@ -399,10 +400,11 @@ if (item->GetChildren()) { Maybe containerASRForChildren; nsDisplayList empty; if (mBuilder->MergeDisplayLists(&empty, item->GetChildren(), item->GetChildren(), containerASRForChildren, item->GetPerFrameKey())) { + item->InvalidateCachedChildInfo(); mResultIsModified = true; } UpdateASR(item, containerASRForChildren); item->UpdateBounds(mBuilder->Builder()); } diff --git a/layout/painting/nsCSSRendering.cpp b/layout/painting/nsCSSRendering.cpp --- a/layout/painting/nsCSSRendering.cpp +++ b/layout/painting/nsCSSRendering.cpp @@ -1799,11 +1799,11 @@ nsCSSShadowArray* shadows = aFrame->StyleEffects()->mBoxShadow; if (!shadows) return false; if (aFrame->IsThemed() && aFrame->GetContent() && - !nsContentUtils::IsChromeDoc(aFrame->GetContent()->GetUncomposedDoc())) { + !nsContentUtils::IsChromeDoc(aFrame->GetContent()->GetComposedDoc())) { // There's no way of getting hold of a shape corresponding to a // "padding-box" for native-themed widgets, so just don't draw // inner box-shadows for them. But we allow chrome to paint inner // box shadows since chrome can be aware of the platform theme. return false; diff --git a/layout/painting/nsDisplayList.h b/layout/painting/nsDisplayList.h --- a/layout/painting/nsDisplayList.h +++ b/layout/painting/nsDisplayList.h @@ -2370,10 +2370,16 @@ aInvalidRegion->Xor(aGeometry->mBounds, bounds); } } } + /** + * This function is called when an item's list of children has been omdified + * by RetaineDisplayListBuilder. + */ + virtual void InvalidateCachedChildInfo() {} + /** * @param aSnap set to true if the edges of the rectangles of the opaque * region would be snapped to device pixels when drawing * @return a region of the item that is opaque --- that is, every pixel * that is visible is painted with an opaque @@ -2783,10 +2789,15 @@ bool In3DContextAndBackfaceIsHidden() { return mBackfaceHidden; } + bool HasDifferentFrame(const nsDisplayItem* aOther) const + { + return mFrame != aOther->mFrame; + } + bool HasSameTypeAndClip(const nsDisplayItem* aOther) const { return GetPerFrameKey() == aOther->GetPerFrameKey() && GetClipChain() == aOther->GetClipChain(); } @@ -4965,10 +4976,11 @@ } virtual void Merge(const nsDisplayItem* aItem) override { MOZ_ASSERT(CanMerge(aItem)); + MOZ_ASSERT(Frame() != aItem->Frame()); MergeFromTrackingMergedFrames(static_cast(aItem)); } virtual void GetMergedFrames(nsTArray* aFrames) const override { @@ -5113,40 +5125,43 @@ class nsDisplayOpacity : public nsDisplayWrapList { public: nsDisplayOpacity(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, nsDisplayList* aList, const ActiveScrolledRoot* aActiveScrolledRoot, - bool aForEventsAndPluginsOnly); + bool aForEventsAndPluginsOnly, + bool aNeedsActiveLayer); nsDisplayOpacity(nsDisplayListBuilder* aBuilder, const nsDisplayOpacity& aOther) : nsDisplayWrapList(aBuilder, aOther) , mOpacity(aOther.mOpacity) , mForEventsAndPluginsOnly(aOther.mForEventsAndPluginsOnly) - , mOpacityAppliedToChildren(false) + , mNeedsActiveLayer(aOther.mNeedsActiveLayer) + , mChildOpacityState(ChildOpacityState::Unknown) { // We should not try to merge flattened opacities. - MOZ_ASSERT(!aOther.mOpacityAppliedToChildren); + MOZ_ASSERT(aOther.mChildOpacityState != ChildOpacityState::Applied); } #ifdef NS_BUILD_REFCNT_LOGGING virtual ~nsDisplayOpacity(); #endif virtual void RestoreState() override { nsDisplayItem::RestoreState(); mOpacity = mState.mOpacity; - mOpacityAppliedToChildren = false; } virtual nsDisplayWrapList* Clone(nsDisplayListBuilder* aBuilder) const override { MOZ_COUNT_CTOR(nsDisplayOpacity); return MakeDisplayItem(aBuilder, *this); } + virtual void InvalidateCachedChildInfo() override { mChildOpacityState = ChildOpacityState::Unknown; } + virtual nsRegion GetOpaqueRegion(nsDisplayListBuilder* aBuilder, bool* aSnap) const override; virtual already_AddRefed BuildLayer(nsDisplayListBuilder* aBuilder, LayerManager* aManager, const ContainerLayerParameters& aContainerParameters) override; @@ -5159,11 +5174,11 @@ virtual bool CanMerge(const nsDisplayItem* aItem) const override { // items for the same content element should be merged into a single // compositing group // aItem->GetUnderlyingFrame() returns non-null because it's nsDisplayOpacity - return HasSameTypeAndClip(aItem) && HasSameContent(aItem); + return HasDifferentFrame(aItem) && HasSameTypeAndClip(aItem) && HasSameContent(aItem); } virtual nsDisplayItemGeometry* AllocateGeometry(nsDisplayListBuilder* aBuilder) override { return new nsDisplayOpacityGeometry(this, aBuilder, mOpacity); @@ -5187,11 +5202,11 @@ virtual bool ShouldFlattenAway(nsDisplayListBuilder* aBuilder) override; /** * Returns true if ShouldFlattenAway() applied opacity to children. */ - bool OpacityAppliedToChildren() const { return mOpacityAppliedToChildren; } + bool OpacityAppliedToChildren() const { return mChildOpacityState == ChildOpacityState::Applied; } static bool NeedsActiveLayer(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame); NS_DISPLAY_DECL_NAME("Opacity", TYPE_OPACITY) virtual void WriteDebugInfo(std::stringstream& aStream) override; @@ -5207,12 +5222,25 @@ private: bool ApplyOpacityToChildren(nsDisplayListBuilder* aBuilder); float mOpacity; - bool mForEventsAndPluginsOnly; - bool mOpacityAppliedToChildren; + bool mForEventsAndPluginsOnly : 1; + enum class ChildOpacityState : uint8_t { + // Our child list has changed since the last time ApplyOpacityToChildren was called. + Unknown, + // Our children defer opacity handling to us. + Deferred, + // Opacity is applied to our children. + Applied + }; + bool mNeedsActiveLayer : 1; +#ifndef __GNUC__ + ChildOpacityState mChildOpacityState : 2; +#else + ChildOpacityState mChildOpacityState; +#endif struct { float mOpacity; } mState; }; @@ -5379,11 +5407,11 @@ virtual bool CanMerge(const nsDisplayItem* aItem) const override { // Items for the same content element should be merged into a single // compositing group. - return HasSameTypeAndClip(aItem) && HasSameContent(aItem) + return HasDifferentFrame(aItem) && HasSameTypeAndClip(aItem) && HasSameContent(aItem) && mIsForBackground == static_cast(aItem)->mIsForBackground; } virtual bool ShouldFlattenAway(nsDisplayListBuilder* aBuilder) override { @@ -6134,11 +6162,11 @@ virtual bool CanMerge(const nsDisplayItem* aItem) const override { // Items for the same content element should be merged into a single // compositing group. - return HasSameTypeAndClip(aItem) && HasSameContent(aItem); + return HasDifferentFrame(aItem) && HasSameTypeAndClip(aItem) && HasSameContent(aItem); } virtual void Merge(const nsDisplayItem* aItem) override { nsDisplayWrapList::Merge(aItem); diff --git a/layout/painting/nsDisplayList.cpp b/layout/painting/nsDisplayList.cpp --- a/layout/painting/nsDisplayList.cpp +++ b/layout/painting/nsDisplayList.cpp @@ -6235,15 +6235,17 @@ } nsDisplayOpacity::nsDisplayOpacity(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, nsDisplayList* aList, const ActiveScrolledRoot* aActiveScrolledRoot, - bool aForEventsAndPluginsOnly) + bool aForEventsAndPluginsOnly, + bool aNeedsActiveLayer) : nsDisplayWrapList(aBuilder, aFrame, aList, aActiveScrolledRoot, true) , mOpacity(aFrame->StyleEffects()->mOpacity) , mForEventsAndPluginsOnly(aForEventsAndPluginsOnly) - , mOpacityAppliedToChildren(false) + , mNeedsActiveLayer(aNeedsActiveLayer) + , mChildOpacityState(ChildOpacityState::Unknown) { MOZ_COUNT_CTOR(nsDisplayOpacity); mState.mOpacity = mOpacity; } @@ -6376,18 +6378,23 @@ } bool nsDisplayOpacity::ApplyOpacityToChildren(nsDisplayListBuilder* aBuilder) { + if (mChildOpacityState == ChildOpacityState::Deferred) { + return false; + } + // Only try folding our opacity down if we have at most kMaxChildCount // children that don't overlap and can all apply the opacity to themselves. static const size_t kMaxChildCount = 3; // Iterate through the child display list and copy at most kMaxChildCount // child display item pointers to a temporary list. AutoTArray items; if (!CollectItemsWithOpacity(&mList, items, kMaxChildCount)) { + mChildOpacityState = ChildOpacityState::Deferred; return false; } struct { nsDisplayItem* item; @@ -6403,37 +6410,35 @@ } for (size_t i = 0; i < childCount; i++) { for (size_t j = i+1; j < childCount; j++) { if (children[i].bounds.Intersects(children[j].bounds)) { + mChildOpacityState = ChildOpacityState::Deferred; return false; } } } for (uint32_t i = 0; i < childCount; i++) { children[i].item->ApplyOpacity(aBuilder, mOpacity, mClipChain); } - mOpacityAppliedToChildren = true; + mChildOpacityState = ChildOpacityState::Applied; return true; } bool nsDisplayOpacity::ShouldFlattenAway(nsDisplayListBuilder* aBuilder) { - // ShouldFlattenAway() should be called only once during painting. - MOZ_ASSERT(!mOpacityAppliedToChildren); - if (mFrame->GetPrevContinuation() || mFrame->GetNextContinuation() || mFrame->HasAnyStateBits(NS_FRAME_PART_OF_IBSPLIT)) { // If we've been split, then we might need to merge, so // don't flatten us away. return false; } - if (NeedsActiveLayer(aBuilder, mFrame) || mOpacity == 0.0) { + if (mNeedsActiveLayer || mOpacity == 0.0) { // If our opacity is zero then we'll discard all descendant display items // except for layer event regions, so there's no point in doing this // optimization (and if we do do it, then invalidations of those descendants // might trigger repainting). return false; @@ -6459,11 +6464,11 @@ if (mForEventsAndPluginsOnly) { MOZ_ASSERT(mOpacity == 0); return LAYER_INACTIVE; } - if (NeedsActiveLayer(aBuilder, mFrame)) { + if (mNeedsActiveLayer) { // Returns LAYER_ACTIVE_FORCE to avoid flatterning the layer for async // animations. return LAYER_ACTIVE_FORCE; } @@ -6647,11 +6652,11 @@ bool nsDisplayBlendMode::CanMerge(const nsDisplayItem* aItem) const { // Items for the same content element should be merged into a single // compositing group. - if (!HasSameTypeAndClip(aItem) || !HasSameContent(aItem)) { + if (!HasDifferentFrame(aItem) || !HasSameTypeAndClip(aItem) || !HasSameContent(aItem)) { return false; } const nsDisplayBlendMode* item = static_cast(aItem); @@ -9363,11 +9368,11 @@ bool nsDisplayMask::CanMerge(const nsDisplayItem* aItem) const { // Items for the same content element should be merged into a single // compositing group. - if (!HasSameTypeAndClip(aItem) || !HasSameContent(aItem)) { + if (!HasDifferentFrame(aItem) || !HasSameTypeAndClip(aItem) || !HasSameContent(aItem)) { return false; } return CanMergeDisplayMaskFrame(mFrame) && CanMergeDisplayMaskFrame(aItem->Frame()); diff --git a/layout/style/test/property_database.js b/layout/style/test/property_database.js --- a/layout/style/test/property_database.js +++ b/layout/style/test/property_database.js @@ -290,10 +290,14 @@ "linear-gradient(to top, red, 100%)", "linear-gradient(to top, red, 45%, 56%, blue)", "linear-gradient(to top, red,, blue)", "linear-gradient(to top, red, green 35%, 15%, 54%, blue)", + "linear-gradient(unset, 10px 10px, from(blue))", + "linear-gradient(unset, 10px 10px, blue 0)", + "repeating-linear-gradient(unset, 10px 10px, blue 0)", + "radial-gradient(top left 45deg, red, blue)", "radial-gradient(20% bottom -300deg, red, blue)", "radial-gradient(center 20% 1.95929rad, red, blue)", "radial-gradient(left 35px 30grad, red, blue)", @@ -1175,11 +1179,11 @@ inherited: false, type: CSS_TYPE_TRUE_SHORTHAND, subproperties: [ "animation-name", "animation-duration", "animation-timing-function", "animation-delay", "animation-direction", "animation-fill-mode", "animation-iteration-count", "animation-play-state" ], initial_values: [ "none none 0s 0s ease normal running 1.0", "none", "0s", "ease", "normal", "running", "1.0" ], other_values: [ "none none 0s 0s cubic-bezier(0.25, 0.1, 0.25, 1.0) normal running 1.0", "bounce 1s linear 2s", "bounce 1s 2s linear", "bounce linear 1s 2s", "linear bounce 1s 2s", "linear 1s bounce 2s", "linear 1s 2s bounce", "1s bounce linear 2s", "1s bounce 2s linear", "1s 2s bounce linear", "1s linear bounce 2s", "1s linear 2s bounce", "1s 2s linear bounce", "bounce linear 1s", "bounce 1s linear", "linear bounce 1s", "linear 1s bounce", "1s bounce linear", "1s linear bounce", "1s 2s bounce", "1s bounce 2s", "bounce 1s 2s", "1s 2s linear", "1s linear 2s", "linear 1s 2s", "bounce 1s", "1s bounce", "linear 1s", "1s linear", "1s 2s", "2s 1s", "bounce", "linear", "1s", "height", "2s", "ease-in-out", "2s ease-in", "opacity linear", "ease-out 2s", "2s color, 1s bounce, 500ms height linear, 1s opacity 4s cubic-bezier(0.0, 0.1, 1.0, 1.0)", "1s \\32bounce linear 2s", "1s -bounce linear 2s", "1s -\\32bounce linear 2s", "1s \\32 0bounce linear 2s", "1s -\\32 0bounce linear 2s", "1s \\2bounce linear 2s", "1s -\\2bounce linear 2s", "2s, 1s bounce", "1s bounce, 2s", "2s all, 1s bounce", "1s bounce, 2s all", "1s bounce, 2s none", "2s none, 1s bounce", "2s bounce, 1s all", "2s all, 1s bounce" ], - invalid_values: [ "2s inherit", "inherit 2s", "2s bounce, 1s inherit", "2s inherit, 1s bounce", "2s initial", "2s all,, 1s bounce", "2s all, , 1s bounce", "bounce 1s cubic-bezier(0, rubbish) 2s", "bounce 1s steps(rubbish) 2s" ] + invalid_values: [ "2s inherit", "inherit 2s", "2s bounce, 1s inherit", "2s inherit, 1s bounce", "2s initial", "2s all,, 1s bounce", "2s all, , 1s bounce", "bounce 1s cubic-bezier(0, rubbish) 2s", "bounce 1s steps(rubbish) 2s", "2s unset" ] }, "animation-delay": { domProp: "animationDelay", inherited: false, type: CSS_TYPE_LONGHAND, @@ -1191,11 +1195,11 @@ domProp: "animationDirection", inherited: false, type: CSS_TYPE_LONGHAND, initial_values: [ "normal" ], other_values: [ "alternate", "normal, alternate", "alternate, normal", "normal, normal", "normal, normal, normal", "reverse", "alternate-reverse", "normal, reverse, alternate-reverse, alternate" ], - invalid_values: [ "normal normal", "inherit, normal", "reverse-alternate" ] + invalid_values: [ "normal normal", "inherit, normal", "reverse-alternate", "normal, unset", "unset, normal" ] }, "animation-duration": { domProp: "animationDuration", inherited: false, type: CSS_TYPE_LONGHAND, @@ -1225,11 +1229,11 @@ domProp: "animationName", inherited: false, type: CSS_TYPE_LONGHAND, initial_values: [ "none" ], other_values: [ "all", "ball", "mall", "color", "bounce, bubble, opacity", "foobar", "auto", "\\32bounce", "-bounce", "-\\32bounce", "\\32 0bounce", "-\\32 0bounce", "\\2bounce", "-\\2bounce" ], - invalid_values: [ "bounce, initial", "initial, bounce", "bounce, inherit", "inherit, bounce" ] + invalid_values: [ "bounce, initial", "initial, bounce", "bounce, inherit", "inherit, bounce", "bounce, unset", "unset, bounce" ] }, "animation-play-state": { domProp: "animationPlayState", inherited: false, type: CSS_TYPE_LONGHAND, @@ -1421,11 +1425,11 @@ "calc(25px*3)", "calc(3*25px + 50%)", "2px 2px calc(2px + 1%) 2px", "1px 2px 2px 2px / 2px 2px calc(2px + 1%) 2px", ], - invalid_values: [ "2px -2px", "inherit 2px", "inherit / 2px", "2px inherit", "2px / inherit", "2px 2px 2px 2px 2px", "1px / 2px 2px 2px 2px 2px", "2", "2 2", "2px 2px 2px 2px / 2px 2px 2 2px", "2px calc(0px + rubbish)" ] + invalid_values: [ "2px -2px", "inherit 2px", "inherit / 2px", "2px inherit", "2px / inherit", "2px 2px 2px 2px 2px", "1px / 2px 2px 2px 2px 2px", "2", "2 2", "2px 2px 2px 2px / 2px 2px 2 2px", "2px calc(0px + rubbish)", "unset 2px", "unset / 2px", "2px unset", "2px / unset" ] }, "border-bottom-left-radius": { domProp: "borderBottomLeftRadius", inherited: false, type: CSS_TYPE_LONGHAND, @@ -1442,11 +1446,11 @@ "5px calc(3*25px)", "calc(20%) calc(3*25px)", "calc(25px*3)", "calc(3*25px + 50%)", ], - invalid_values: [ "-1px", "4px -2px", "inherit 2px", "2px inherit", "2", "2px 2", "2 2px", "2px calc(0px + rubbish)" ] + invalid_values: [ "-1px", "4px -2px", "inherit 2px", "2px inherit", "2", "2px 2", "2 2px", "2px calc(0px + rubbish)", "unset 2px", "2px unset" ] }, "border-bottom-right-radius": { domProp: "borderBottomRightRadius", inherited: false, type: CSS_TYPE_LONGHAND, @@ -1463,11 +1467,11 @@ "5px calc(3*25px)", "calc(20%) calc(3*25px)", "calc(25px*3)", "calc(3*25px + 50%)", ], - invalid_values: [ "-1px", "4px -2px", "inherit 2px", "2px inherit", "2", "2px 2", "2 2px", "2px calc(0px + rubbish)" ] + invalid_values: [ "-1px", "4px -2px", "inherit 2px", "2px inherit", "2", "2px 2", "2 2px", "2px calc(0px + rubbish)", "unset 2px", "2px unset" ] }, "border-top-left-radius": { domProp: "borderTopLeftRadius", inherited: false, type: CSS_TYPE_LONGHAND, @@ -1484,11 +1488,11 @@ "5px calc(3*25px)", "calc(20%) calc(3*25px)", "calc(25px*3)", "calc(3*25px + 50%)", ], - invalid_values: [ "-1px", "4px -2px", "inherit 2px", "2px inherit", "2", "2px 2", "2 2px", "2px calc(0px + rubbish)" ] + invalid_values: [ "-1px", "4px -2px", "inherit 2px", "2px inherit", "2", "2px 2", "2 2px", "2px calc(0px + rubbish)", "unset 2px", "2px unset" ] }, "border-top-right-radius": { domProp: "borderTopRightRadius", inherited: false, type: CSS_TYPE_LONGHAND, @@ -1505,11 +1509,11 @@ "5px calc(3*25px)", "calc(20%) calc(3*25px)", "calc(25px*3)", "calc(3*25px + 50%)", ], - invalid_values: [ "-1px", "4px -2px", "inherit 2px", "2px inherit", "2", "2px 2", "2 2px", "2px calc(0px + rubbish)" ] + invalid_values: [ "-1px", "4px -2px", "inherit 2px", "2px inherit", "2", "2px 2", "2 2px", "2px calc(0px + rubbish)", "unset 2px", "2px unset" ] }, "border-inline-start": { domProp: "borderInlineStart", inherited: false, type: CSS_TYPE_TRUE_SHORTHAND, @@ -2270,11 +2274,11 @@ "calc(25px*3)", "calc(3*25px + 50%)", "2px 2px calc(2px + 1%) 2px", "1px 2px 2px 2px / 2px 2px calc(2px + 1%) 2px", ], - invalid_values: [ "2px -2px", "inherit 2px", "inherit / 2px", "2px inherit", "2px / inherit", "2px 2px 2px 2px 2px", "1px / 2px 2px 2px 2px 2px", "2", "2 2", "2px 2px 2px 2px / 2px 2px 2 2px" ] + invalid_values: [ "2px -2px", "inherit 2px", "inherit / 2px", "2px inherit", "2px / inherit", "2px 2px 2px 2px 2px", "1px / 2px 2px 2px 2px 2px", "2", "2 2", "2px 2px 2px 2px / 2px 2px 2 2px", "unset 2px", "unset / 2px", "2px unset", "2px / unset" ] }, "-moz-outline-radius-bottomleft": { domProp: "MozOutlineRadiusBottomleft", inherited: false, type: CSS_TYPE_LONGHAND, @@ -2290,11 +2294,11 @@ "5px calc(3*25px)", "calc(20%) calc(3*25px)", "calc(25px*3)", "calc(3*25px + 50%)", ], - invalid_values: [ "-1px", "4px -2px", "inherit 2px", "2px inherit", "2", "2px 2", "2 2px" ] + invalid_values: [ "-1px", "4px -2px", "inherit 2px", "2px inherit", "2", "2px 2", "2 2px", "unset 2px", "2px unset" ] }, "-moz-outline-radius-bottomright": { domProp: "MozOutlineRadiusBottomright", inherited: false, type: CSS_TYPE_LONGHAND, @@ -2310,11 +2314,11 @@ "5px calc(3*25px)", "calc(20%) calc(3*25px)", "calc(25px*3)", "calc(3*25px + 50%)", ], - invalid_values: [ "-1px", "4px -2px", "inherit 2px", "2px inherit", "2", "2px 2", "2 2px" ] + invalid_values: [ "-1px", "4px -2px", "inherit 2px", "2px inherit", "2", "2px 2", "2 2px", "unset 2px", "2px unset" ] }, "-moz-outline-radius-topleft": { domProp: "MozOutlineRadiusTopleft", inherited: false, type: CSS_TYPE_LONGHAND, @@ -2330,11 +2334,11 @@ "5px calc(3*25px)", "calc(20%) calc(3*25px)", "calc(25px*3)", "calc(3*25px + 50%)", ], - invalid_values: [ "-1px", "4px -2px", "inherit 2px", "2px inherit", "2", "2px 2", "2 2px" ] + invalid_values: [ "-1px", "4px -2px", "inherit 2px", "2px inherit", "2", "2px 2", "2 2px", "unset 2px", "2px unset" ] }, "-moz-outline-radius-topright": { domProp: "MozOutlineRadiusTopright", inherited: false, type: CSS_TYPE_LONGHAND, @@ -2350,11 +2354,11 @@ "5px calc(3*25px)", "calc(20%) calc(3*25px)", "calc(25px*3)", "calc(3*25px + 50%)", ], - invalid_values: [ "-1px", "4px -2px", "inherit 2px", "2px inherit", "2", "2px 2", "2 2px" ] + invalid_values: [ "-1px", "4px -2px", "inherit 2px", "2px inherit", "2", "2px 2", "2 2px", "unset 2px", "2px unset" ] }, "padding-inline-end": { domProp: "paddingInlineEnd", inherited: false, type: CSS_TYPE_LONGHAND, @@ -3242,11 +3246,11 @@ "-2px -2px", "calc(2px) calc(2px)", "calc(2px) calc(2px) calc(2px)", "calc(2px) calc(2px) calc(2px) calc(2px)" ], - invalid_values: [ "3% 3%", "1px 1px 1px 1px 1px", "2px 2px, none", "red 2px 2px blue", "inherit, 2px 2px", "2px 2px, inherit", "2px 2px -5px", "inset 4px 4px black inset", "inset inherit", "inset none", "3 3", "3px 3", "3 3px", "3px 3px 3", "3px 3px 3px 3", "3px calc(3px + rubbish)", "3px 3px calc(3px + rubbish)", "3px 3px 3px calc(3px + rubbish)", "3px 3px 3px 3px rgb(0, rubbish, 0)" ] + invalid_values: [ "3% 3%", "1px 1px 1px 1px 1px", "2px 2px, none", "red 2px 2px blue", "inherit, 2px 2px", "2px 2px, inherit", "2px 2px -5px", "inset 4px 4px black inset", "inset inherit", "inset none", "3 3", "3px 3", "3 3px", "3px 3px 3", "3px 3px 3px 3", "3px calc(3px + rubbish)", "3px 3px calc(3px + rubbish)", "3px 3px 3px calc(3px + rubbish)", "3px 3px 3px 3px rgb(0, rubbish, 0)", "unset, 2px 2px", "2px 2px, unset", "inset unset" ] }, "caption-side": { domProp: "captionSide", inherited: true, type: CSS_TYPE_LONGHAND, @@ -4521,11 +4525,11 @@ inherited: false, type: CSS_TYPE_LONGHAND, applies_to_placeholder: true, initial_values: [ "clip" ], other_values: [ "ellipsis", '""', "''", '"hello"', 'clip clip', 'ellipsis ellipsis', 'clip ellipsis', 'clip ""', '"hello" ""', '"" ellipsis' ], - invalid_values: [ "none", "auto", '"hello" inherit', 'inherit "hello"', 'clip initial', 'initial clip', 'initial inherit', 'inherit initial', 'inherit none'] + invalid_values: [ "none", "auto", '"hello" inherit', 'inherit "hello"', 'clip initial', 'initial clip', 'initial inherit', 'inherit initial', 'inherit none', '"hello" unset', 'unset "hello"', 'clip unset', 'unset clip', 'unset inherit', 'unset none', 'initial unset'] }, "text-shadow": { domProp: "textShadow", inherited: true, type: CSS_TYPE_LONGHAND, @@ -4546,11 +4550,11 @@ "-2px -2px", "calc(2px) calc(2px)", "calc(2px) calc(2px) calc(2px)", ], invalid_values: [ "3% 3%", "2px 2px -5px", "2px 2px 2px 2px", "2px 2px, none", "none, 2px 2px", "inherit, 2px 2px", "2px 2px, inherit", "2 2px", "2px 2", "2px 2px 2", "2px 2px 2px 2", - "calc(2px) calc(2px) calc(2px) calc(2px)", "3px 3px calc(3px + rubbish)" + "calc(2px) calc(2px) calc(2px) calc(2px)", "3px 3px calc(3px + rubbish)", "unset, 2px 2px", "2px 2px, unset" ] }, "text-transform": { domProp: "textTransform", inherited: true, @@ -4586,11 +4590,11 @@ inherited: false, type: CSS_TYPE_TRUE_SHORTHAND, subproperties: [ "transition-property", "transition-duration", "transition-timing-function", "transition-delay" ], initial_values: [ "all 0s ease 0s", "all", "0s", "0s 0s", "ease" ], other_values: [ "all 0s cubic-bezier(0.25, 0.1, 0.25, 1.0) 0s", "width 1s linear 2s", "width 1s 2s linear", "width linear 1s 2s", "linear width 1s 2s", "linear 1s width 2s", "linear 1s 2s width", "1s width linear 2s", "1s width 2s linear", "1s 2s width linear", "1s linear width 2s", "1s linear 2s width", "1s 2s linear width", "width linear 1s", "width 1s linear", "linear width 1s", "linear 1s width", "1s width linear", "1s linear width", "1s 2s width", "1s width 2s", "width 1s 2s", "1s 2s linear", "1s linear 2s", "linear 1s 2s", "width 1s", "1s width", "linear 1s", "1s linear", "1s 2s", "2s 1s", "width", "linear", "1s", "height", "2s", "ease-in-out", "2s ease-in", "opacity linear", "ease-out 2s", "2s color, 1s width, 500ms height linear, 1s opacity 4s cubic-bezier(0.0, 0.1, 1.0, 1.0)", "1s \\32width linear 2s", "1s -width linear 2s", "1s -\\32width linear 2s", "1s \\32 0width linear 2s", "1s -\\32 0width linear 2s", "1s \\2width linear 2s", "1s -\\2width linear 2s", "2s, 1s width", "1s width, 2s", "2s all, 1s width", "1s width, 2s all", "2s all, 1s width", "2s width, 1s all", "3s --my-color", "none", "none 2s linear 2s" ], - invalid_values: [ "1s width, 2s none", "2s none, 1s width", "2s inherit", "inherit 2s", "2s width, 1s inherit", "2s inherit, 1s width", "2s initial", "1s width,,2s color", "1s width, ,2s color", "bounce 1s cubic-bezier(0, rubbish) 2s", "bounce 1s steps(rubbish) 2s" ] + invalid_values: [ "1s width, 2s none", "2s none, 1s width", "2s inherit", "inherit 2s", "2s width, 1s inherit", "2s inherit, 1s width", "2s initial", "1s width,,2s color", "1s width, ,2s color", "bounce 1s cubic-bezier(0, rubbish) 2s", "bounce 1s steps(rubbish) 2s", "2s unset" ] }, "transition-delay": { domProp: "transitionDelay", inherited: false, type: CSS_TYPE_LONGHAND, @@ -4610,11 +4614,11 @@ domProp: "transitionProperty", inherited: false, type: CSS_TYPE_LONGHAND, initial_values: [ "all" ], other_values: [ "none", "left", "top", "color", "width, height, opacity", "foobar", "auto", "\\32width", "-width", "-\\32width", "\\32 0width", "-\\32 0width", "\\2width", "-\\2width", "all, all", "all, color", "color, all", "--my-color" ], - invalid_values: [ "none, none", "color, none", "none, color", "inherit, color", "color, inherit", "initial, color", "color, initial", "none, color", "color, none" ] + invalid_values: [ "none, none", "color, none", "none, color", "inherit, color", "color, inherit", "initial, color", "color, initial", "none, color", "color, none", "unset, color", "color, unset" ] }, "transition-timing-function": { domProp: "transitionTimingFunction", inherited: false, type: CSS_TYPE_LONGHAND, @@ -8188,40 +8192,20 @@ other_values: [ "green", "#fc3" ], invalid_values: [ "000000", "ff00ff" ] }; } -if (IsCSSPropertyPrefEnabled("layout.css.unset-value.enabled")) { - gCSSProperties["animation"].invalid_values.push("2s unset"); - gCSSProperties["animation-direction"].invalid_values.push("normal, unset", "unset, normal"); - gCSSProperties["animation-name"].invalid_values.push("bounce, unset", "unset, bounce"); - gCSSProperties["border-radius"].invalid_values.push("unset 2px", "unset / 2px", "2px unset", "2px / unset"); - gCSSProperties["border-bottom-left-radius"].invalid_values.push("unset 2px", "2px unset"); - gCSSProperties["border-bottom-right-radius"].invalid_values.push("unset 2px", "2px unset"); - gCSSProperties["border-top-left-radius"].invalid_values.push("unset 2px", "2px unset"); - gCSSProperties["border-top-right-radius"].invalid_values.push("unset 2px", "2px unset"); - gCSSProperties["-moz-outline-radius"].invalid_values.push("unset 2px", "unset / 2px", "2px unset", "2px / unset"); - gCSSProperties["-moz-outline-radius-bottomleft"].invalid_values.push("unset 2px", "2px unset"); - gCSSProperties["-moz-outline-radius-bottomright"].invalid_values.push("unset 2px", "2px unset"); - gCSSProperties["-moz-outline-radius-topleft"].invalid_values.push("unset 2px", "2px unset"); - gCSSProperties["-moz-outline-radius-topright"].invalid_values.push("unset 2px", "2px unset"); - gCSSProperties["background-image"].invalid_values.push("linear-gradient(unset, 10px 10px, from(blue))", "linear-gradient(unset, 10px 10px, blue 0)", "repeating-linear-gradient(unset, 10px 10px, blue 0)"); - gCSSProperties["box-shadow"].invalid_values.push("unset, 2px 2px", "2px 2px, unset", "inset unset"); - gCSSProperties["text-overflow"].invalid_values.push('"hello" unset', 'unset "hello"', 'clip unset', 'unset clip', 'unset inherit', 'unset none', 'initial unset'); - gCSSProperties["text-shadow"].invalid_values.push("unset, 2px 2px", "2px 2px, unset"); - gCSSProperties["transition"].invalid_values.push("2s unset"); - gCSSProperties["transition-property"].invalid_values.push("unset, color", "color, unset"); - if (IsCSSPropertyPrefEnabled("layout.css.filters.enabled")) { - gCSSProperties["filter"].invalid_values.push("drop-shadow(unset, 2px 2px)", "drop-shadow(2px 2px, unset)"); - } - if (IsCSSPropertyPrefEnabled("layout.css.prefixes.gradients")) { - gCSSProperties["background-image"].invalid_values.push( - "-moz-linear-gradient(unset, 10px 10px, from(blue))", - "-moz-linear-gradient(unset, 10px 10px, blue 0)", - "-moz-repeating-linear-gradient(unset, 10px 10px, blue 0)", - ); - } +if (IsCSSPropertyPrefEnabled("layout.css.filters.enabled")) { + gCSSProperties["filter"].invalid_values.push("drop-shadow(unset, 2px 2px)", "drop-shadow(2px 2px, unset)"); +} + +if (IsCSSPropertyPrefEnabled("layout.css.prefixes.gradients")) { + gCSSProperties["background-image"].invalid_values.push( + "-moz-linear-gradient(unset, 10px 10px, from(blue))", + "-moz-linear-gradient(unset, 10px 10px, blue 0)", + "-moz-repeating-linear-gradient(unset, 10px 10px, blue 0)", + ); } if (IsCSSPropertyPrefEnabled("layout.css.scrollbar-colors.enabled")) { gCSSProperties["scrollbar-face-color"] = { domProp: "scrollbarFaceColor", diff --git a/layout/style/test/test_all_shorthand.html b/layout/style/test/test_all_shorthand.html --- a/layout/style/test/test_all_shorthand.html +++ b/layout/style/test/test_all_shorthand.html @@ -1,11 +1,11 @@ Test the 'all' shorthand property - +